8e225f95a791236ff8763be07e0e90bb69d7180f
[wolnelektury.git] / apps / api / management / commands / mobileinit.py
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.
4 #
5 from datetime import datetime
6 import os
7 import os.path
8 import re
9 import sqlite3
10 from django.core.management.base import BaseCommand
11 from slughifi import char_map
12
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
17
18
19 class Command(BaseCommand):
20     help = 'Creates an initial SQLite file for the mobile app.'
21
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():
27             add_book(db, b)
28         for t in Tag.objects.exclude(category__in=('book', 'set', 'theme')):
29             add_tag(db, t)
30         db.commit()
31         db.close()
32         current(last_checked)
33
34
35 def pretty_size(size):
36     """ Turns size in bytes into a prettier string.
37
38         >>> pretty_size(100000)
39         '97 KiB'
40     """
41     if not size:
42         return None
43     units = ['B', 'KiB', 'MiB', 'GiB']
44     size = float(size)
45     unit = units.pop(0)
46     while size > 1000 and units:
47         size /= 1024
48         unit = units.pop(0)
49     if size < 10:
50         return "%.1f %s" % (size, unit)
51     return "%d %s" % (size, unit)
52
53
54 special_marks = {'ż': '|', 'Ż': '|',}
55 def replace_char(m):
56     char = m.group()
57     if char_map.has_key(char):
58         special = special_marks.get(char, '{')
59         return char_map[char] + special
60     else:
61         return char
62
63 def sortify(value):
64     """
65         Turns Unicode into ASCII-sortable str
66
67         Examples :
68
69         >>> slughifi('aa') < slughifi('a a') < slughifi('ą') < slughifi('b')
70         True
71
72     """
73
74     if not isinstance(value, unicode):
75         value = unicode(value, 'utf-8')
76
77     # try to replace chars
78     value = re.sub('[^a-zA-Z0-9\\s\\-]{1}', replace_char, value)
79     value = value.lower()
80     value = re.sub(r'[^a-z0-9{|}]+', '~', value)
81     
82     return value.encode('ascii', 'ignore')
83
84
85
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))
90
91     schema = """
92 CREATE TABLE book (
93     id INTEGER PRIMARY KEY, 
94     title VARCHAR, 
95     html_file VARCHAR, 
96     html_file_size INTEGER, 
97     parent INTEGER,
98     parent_number INTEGER,
99
100     sort_key VARCHAR,
101     pretty_size VARCHAR,
102     authors VARCHAR,
103     _local BOOLEAN
104 );
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);
108
109 CREATE TABLE tag (
110     id INTEGER PRIMARY KEY, 
111     name VARCHAR, 
112     category VARCHAR, 
113     sort_key VARCHAR, 
114     books VARCHAR);
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);
118
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);
122
123 CREATE TABLE state (last_checked INTEGER);
124 """
125
126     db.executescript(schema)
127     db.execute("INSERT INTO state VALUES (:last_checked)", locals())
128     return db
129
130
131 def current(last_checked):
132     target = os.path.join(MOBILE_INIT_DB, 'initial.db')
133     os.unlink(target)
134     os.symlink(
135         'initial.db-%d' % last_checked,
136         target,
137     )
138     
139
140
141 book_sql = """
142     INSERT INTO book 
143         (id, title, html_file,  html_file_size, parent, parent_number, sort_key, pretty_size, authors) 
144     VALUES 
145         (:id, :title, :html_file, :html_file_size, :parent, :parent_number, :sort_key, :size_str, :authors);
146 """
147 book_tag_sql = "INSERT INTO book_tag (book, tag) VALUES (:book, :tag);"
148 tag_sql = """
149     INSERT INTO tag
150         (id, category, name, sort_key, books)
151     VALUES
152         (:id, :category, :name, :sort_key, :book_ids);
153 """
154 categories = {'author': 'autor',
155               'epoch': 'epoka', 
156               'genre': 'gatunek', 
157               'kind': 'rodzaj', 
158               'theme': 'motyw'
159               }
160
161
162 def add_book(db, book):
163     id = book.id
164     title = book.title
165     if book.html_file:
166         html_file = book.html_file.url
167         html_file_size = book.html_file.size
168     else:
169         html_file = html_file_size = None
170     parent = book.parent
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())
176
177
178 def add_tag(db, tag):
179     id = tag.id
180     category = categories[tag.category]
181     name = tag.name
182     sort_key = sortify(tag.sort_key)
183
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())
187
188     for b in books:
189         db.execute(book_tag_sql, {'book': b.id, 'tag': tag.id})