Merge branch 'master' into production
[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
12 from api.helpers import timestamp
13 from api.settings import MOBILE_INIT_DB
14 from catalogue.models import Book, Tag
15
16
17 class Command(BaseCommand):
18     help = 'Creates an initial SQLite file for the mobile app.'
19
20     def handle(self, **options):
21         # those should be versioned
22         last_checked = timestamp(datetime.now())
23         db = init_db(last_checked)
24         for b in Book.objects.all():
25             add_book(db, b)
26         for t in Tag.objects.exclude(
27                 category__in=('book', 'set', 'theme')).exclude(book_count=0):
28             # only add non-empty tags
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     if not isinstance(value, unicode):
55         value = unicode(value, 'utf-8')
56
57     # try to replace chars
58     value = re.sub('[^a-zA-Z0-9\\s\\-]{1}', replace_char, value)
59     value = value.lower()
60     value = re.sub(r'[^a-z0-9{|}]+', '~', value)
61     
62     return value.encode('ascii', 'ignore')
63
64
65
66 def init_db(last_checked):
67     if not os.path.isdir(MOBILE_INIT_DB):
68         os.makedirs(MOBILE_INIT_DB)
69     db = sqlite3.connect(os.path.join(MOBILE_INIT_DB, 'initial.db-%d' % last_checked))
70
71     schema = """
72 CREATE TABLE book (
73     id INTEGER PRIMARY KEY, 
74     title VARCHAR,
75     cover VARCHAR,
76     html_file VARCHAR, 
77     html_file_size INTEGER, 
78     parent INTEGER,
79     parent_number INTEGER,
80
81     sort_key VARCHAR,
82     pretty_size VARCHAR,
83     authors VARCHAR,
84     _local BOOLEAN
85 );
86 CREATE INDEX IF NOT EXISTS book_title_index ON book (sort_key);
87 CREATE INDEX IF NOT EXISTS book_title_index ON book (title);
88 CREATE INDEX IF NOT EXISTS book_parent_index ON book (parent);
89
90 CREATE TABLE tag (
91     id INTEGER PRIMARY KEY, 
92     name VARCHAR, 
93     category VARCHAR, 
94     sort_key VARCHAR, 
95     books VARCHAR);
96 CREATE INDEX IF NOT EXISTS tag_name_index ON tag (name);
97 CREATE INDEX IF NOT EXISTS tag_category_index ON tag (category);
98 CREATE INDEX IF NOT EXISTS tag_sort_key_index ON tag (sort_key);
99
100 CREATE TABLE state (last_checked INTEGER);
101 """
102
103     db.executescript(schema)
104     db.execute("INSERT INTO state VALUES (:last_checked)", locals())
105     return db
106
107
108 def current(last_checked):
109     target = os.path.join(MOBILE_INIT_DB, 'initial.db')
110     if os.path.lexists(target):
111         os.unlink(target)
112     os.symlink(
113         'initial.db-%d' % last_checked,
114         target,
115     )
116     
117
118
119 book_sql = """
120     INSERT INTO book 
121         (id, title, cover, html_file,  html_file_size, parent, parent_number, sort_key, pretty_size, authors) 
122     VALUES 
123         (:id, :title, :cover, :html_file, :html_file_size, :parent, :parent_number, :sort_key, :size_str, :authors);
124 """
125 book_tag_sql = "INSERT INTO book_tag (book, tag) VALUES (:book, :tag);"
126 tag_sql = """
127     INSERT INTO tag
128         (id, category, name, sort_key, books)
129     VALUES
130         (:id, :category, :name, :sort_key, :book_ids);
131 """
132 categories = {'author': 'autor',
133               'epoch': 'epoka', 
134               'genre': 'gatunek', 
135               'kind': 'rodzaj', 
136               'theme': 'motyw'
137               }
138
139
140 def add_book(db, book):
141     id = book.id
142     title = book.title
143     if book.html_file:
144         html_file = book.html_file.url
145         html_file_size = book.html_file.size
146     else:
147         html_file = html_file_size = None
148     if book.cover:
149         cover = book.cover.url
150     else:
151         cover = None
152     parent = book.parent_id
153     parent_number = book.parent_number
154     sort_key = book.sort_key
155     size_str = pretty_size(html_file_size)
156     authors = ", ".join(t.name for t in book.tags.filter(category='author'))
157     db.execute(book_sql, locals())
158
159
160 def add_tag(db, tag):
161     id = tag.id
162     #    category = categories[tag.category] # localized names here?
163     category = tag.category
164     name = tag.name
165     sort_key = tag.sort_key
166
167     books = Book.tagged_top_level([tag])
168     book_ids = ','.join(str(b.id) for b in books)
169     db.execute(tag_sql, locals())