1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
5 from datetime import datetime
10 from django.core.management.base import BaseCommand
11 from slughifi import char_map
13 from api.helpers import timestamp
14 from api.settings import MOBILE_INIT_DB
15 from catalogue.models import Book, Tag
16 from catalogue.views import tagged_object_list # this should be somewhere else
19 class Command(BaseCommand):
20 help = 'Creates an initial SQLite file for the mobile app.'
22 def handle(self, **options):
23 # those should be versioned
24 last_checked = timestamp(datetime.now())
25 db = init_db(last_checked)
26 for b in Book.objects.all():
28 for t in Tag.objects.exclude(category__in=('book', 'set', 'theme')):
35 def pretty_size(size):
36 """ Turns size in bytes into a prettier string.
38 >>> pretty_size(100000)
43 units = ['B', 'KiB', 'MiB', 'GiB']
46 while size > 1000 and units:
50 return "%.1f %s" % (size, unit)
51 return "%d %s" % (size, unit)
54 special_marks = {'ż': '|', 'Ż': '|',}
57 if char_map.has_key(char):
58 special = special_marks.get(char, '{')
59 return char_map[char] + special
65 Turns Unicode into ASCII-sortable str
69 >>> slughifi('aa') < slughifi('a a') < slughifi('ą') < slughifi('b')
74 if not isinstance(value, unicode):
75 value = unicode(value, 'utf-8')
77 # try to replace chars
78 value = re.sub('[^a-zA-Z0-9\\s\\-]{1}', replace_char, value)
80 value = re.sub(r'[^a-z0-9{|}]+', '~', value)
82 return value.encode('ascii', 'ignore')
86 def init_db(last_checked):
87 if not os.path.isdir(MOBILE_INIT_DB):
88 os.makedirs(MOBILE_INIT_DB)
89 db = sqlite3.connect(os.path.join(MOBILE_INIT_DB, 'initial.db-%d' % last_checked))
93 id INTEGER PRIMARY KEY,
96 html_file_size INTEGER,
98 parent_number INTEGER,
105 CREATE INDEX IF NOT EXISTS book_title_index ON book (sort_key);
106 CREATE INDEX IF NOT EXISTS book_title_index ON book (title);
107 CREATE INDEX IF NOT EXISTS book_parent_index ON book (parent);
110 id INTEGER PRIMARY KEY,
115 CREATE INDEX IF NOT EXISTS tag_name_index ON tag (name);
116 CREATE INDEX IF NOT EXISTS tag_category_index ON tag (category);
117 CREATE INDEX IF NOT EXISTS tag_sort_key_index ON tag (sort_key);
119 CREATE TABLE book_tag (book INTEGER, tag INTEGER);
120 CREATE INDEX IF NOT EXISTS book_tag_book ON book_tag (book);
121 CREATE INDEX IF NOT EXISTS book_tag_tag_index ON book_tag (tag);
123 CREATE TABLE state (last_checked INTEGER);
126 db.executescript(schema)
127 db.execute("INSERT INTO state VALUES (:last_checked)", locals())
131 def current(last_checked):
132 target = os.path.join(MOBILE_INIT_DB, 'initial.db')
135 'initial.db-%d' % last_checked,
143 (id, title, html_file, html_file_size, parent, parent_number, sort_key, pretty_size, authors)
145 (:id, :title, :html_file, :html_file_size, :parent, :parent_number, :sort_key, :size_str, :authors);
147 book_tag_sql = "INSERT INTO book_tag (book, tag) VALUES (:book, :tag);"
150 (id, category, name, sort_key, books)
152 (:id, :category, :name, :sort_key, :book_ids);
154 categories = {'author': 'autor',
162 def add_book(db, book):
166 html_file = book.html_file.url
167 html_file_size = book.html_file.size
169 html_file = html_file_size = None
171 parent_number = book.parent_number
172 sort_key = sortify(title)
173 size_str = pretty_size(html_file_size)
174 authors = ", ".join(t.name for t in book.tags.filter(category='author'))
175 db.execute(book_sql, locals())
178 def add_tag(db, tag):
180 category = categories[tag.category]
182 sort_key = sortify(tag.sort_key)
184 books = list(tagged_object_list(None, [tag], api=True))
185 book_ids = ','.join(str(b.id) for b in books)
186 db.execute(tag_sql, locals())
189 db.execute(book_tag_sql, {'book': b.id, 'tag': tag.id})