initial OPDS catalogue support
[wolnelektury.git] / apps / catalogue / feeds.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 base64 import b64encode
6 import os.path
7
8 from django.contrib.syndication.views import Feed
9 from django.core.urlresolvers import reverse
10 from django.shortcuts import get_object_or_404
11 from django.utils.feedgenerator import Atom1Feed
12 from django.conf import settings
13 from django.http import Http404
14 from django.contrib.sites.models import Site
15
16 from catalogue.models import Book, Tag
17
18
19 _root_feeds = (
20        {u"category": u"author", u"title": u"Autorzy", u"description": u"Utwory wg autorów"},
21        {u"category": u"kind", u"title": u"Rodzaje", u"description": u"Utwory wg rodzajów"},
22        {u"category": u"genre", u"title": u"Gatunki", u"description": u"Utwory wg gatunków"},
23        {u"category": u"epoch", u"title": u"Epoki", u"description": u"Utwory wg epok"},
24 )
25
26
27 class OPDSFeed(Atom1Feed):
28     link_rel = u"subsection"
29     link_type = u"application/atom+xml"
30
31     try:
32         with open(os.path.join(settings.STATIC_ROOT, "img/book-par ent.png")) as f:
33             t = f.read()
34             _book_parent_img_size = len(t)
35             _book_parent_img = b64encode(t)
36     except:
37         _book_parent_img = _book_parent_img_size = ''
38
39     try:
40         with open(os.path.join(settings.STATIC_ROOT, "img/bo ok.png")) as f:
41             t = f.read()
42             _book_img_size = len(t)
43             _book_img = b64encode(t)
44     except:
45         _book_img = _book_img_size = ''
46
47     def add_root_elements(self, handler):
48         super(OPDSFeed, self).add_root_elements(handler)
49         handler.addQuickElement(u"link", u"", {u"href": reverse("opds_authors"), u"rel": u"start", u"type": u"application/atom+xml"})
50
51
52     def add_item_elements(self, handler, item):
53         """ modified from Atom1Feed.add_item_elements """
54         handler.addQuickElement(u"title", item['title'])
55
56         # add a OPDS Navigation link if there's no enclosure
57         if item['enclosure'] is None:
58             handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"subsection", u"type": u"application/atom+xml"})
59             # add a "green book" icon
60             handler.addQuickElement(u"link", '',
61                 {u"rel": u"http://opds-spec.org/thumbnail",
62                  u"href": u"data:image/png;base64,%s" % self._book_parent_img,
63                  u"length": unicode(self._book_parent_img_size),
64                  u"type": u"image/png"})
65         if item['pubdate'] is not None:
66             handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
67
68         # Author information.
69         if item['author_name'] is not None:
70             handler.startElement(u"author", {})
71             handler.addQuickElement(u"name", item['author_name'])
72             if item['author_email'] is not None:
73                 handler.addQuickElement(u"email", item['author_email'])
74             if item['author_link'] is not None:
75                 handler.addQuickElement(u"uri", item['author_link'])
76             handler.endElement(u"author")
77
78         # Unique ID.
79         if item['unique_id'] is not None:
80             unique_id = item['unique_id']
81         else:
82             unique_id = get_tag_uri(item['link'], item['pubdate'])
83         handler.addQuickElement(u"id", unique_id)
84
85         # Summary.
86         # OPDS needs type=text
87         if item['description'] is not None:
88             handler.addQuickElement(u"summary", item['description'], {u"type": u"text"})
89
90         # Enclosure as OPDS Acquisition Link
91         if item['enclosure'] is not None:
92             handler.addQuickElement(u"link", '',
93                 {u"rel": u"http://opds-spec.org/acquisition",
94                  u"href": item['enclosure'].url,
95                  u"length": item['enclosure'].length,
96                  u"type": item['enclosure'].mime_type})
97             # add a "red book" icon
98             handler.addQuickElement(u"link", '',
99                 {u"rel": u"http://opds-spec.org/thumbnail",
100                  u"href": u"data:image/png;base64,%s" % self._book_img,
101                  u"length": unicode(self._book_img_size),
102                  u"type": u"image/png"})
103
104         # Categories.
105         for cat in item['categories']:
106             handler.addQuickElement(u"category", u"", {u"term": cat})
107
108         # Rights.
109         if item['item_copyright'] is not None:
110             handler.addQuickElement(u"rights", item['item_copyright'])
111
112
113 class RootFeed(Feed):
114     feed_type = OPDSFeed
115     title = u'Wolne Lektury'
116     link = u'http://www.wolnelektury.pl/'
117     description = u"Spis utworów na stronie http://WolneLektury.pl"
118     author_name = u"Wolne Lektury"
119     author_link = u"http://www.wolnelektury.pl/"
120
121     def items(self):
122         return _root_feeds
123
124     def item_title(self, item):
125         return item['title']
126
127     def item_link(self, item):
128         return reverse(u"opds_by_category", args=[item['category']])
129
130     def item_description(self, item):
131         return item['description']
132
133
134 class ByCategoryFeed(Feed):
135     feed_type = OPDSFeed
136     link = u'http://www.wolnelektury.pl/'
137     description = u"Spis utworów na stronie http://WolneLektury.pl"
138     author_name = u"Wolne Lektury"
139     author_link = u"http://www.wolnelektury.pl/"
140
141     def get_object(self, request, category):
142         feed = [feed for feed in _root_feeds if feed['category']==category]
143         if feed:
144             feed = feed[0]
145         else:
146             raise Http404
147
148         return feed
149
150     def title(self, feed):
151         return feed['title']
152
153     def items(self, feed):
154         return (tag for tag in Tag.objects.filter(category=feed['category']) if tag.get_count() > 0)
155
156     def item_title(self, item):
157         return item.name
158
159     def item_link(self, item):
160         return reverse("opds_by_tag", args=[item.category, item.slug])
161
162     def item_description(self):
163         return u''
164
165
166 class ByTagFeed(Feed):
167     feed_type = OPDSFeed
168     link = u'http://www.wolnelektury.pl/'
169     item_enclosure_mime_type = "application/epub+zip"
170     author_name = u"Wolne Lektury"
171     author_link = u"http://www.wolnelektury.pl/"
172
173     def link(self, tag):
174         return tag.get_absolute_url()
175
176     def title(self, tag):
177         return tag.name
178
179     def description(self, tag):
180         return u"Spis utworów na stronie http://WolneLektury.pl"
181
182     def get_object(self, request, category, slug):
183         return get_object_or_404(Tag, category=category, slug=slug)
184
185     def items(self, tag):
186         books = Book.tagged.with_any([tag])
187         l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books])
188         descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
189         if descendants_keys:
190             books = books.exclude(pk__in=descendants_keys)
191
192         return books
193
194     def item_title(self, book):
195         return book.title
196
197     def item_description(self):
198         return u''
199
200     def item_link(self, book):
201         return book.get_absolute_url()
202
203     def item_author_name(self, book):
204         try:
205             return book.tags.filter(category='author')[0].name
206         except KeyError:
207             return u''
208
209     def item_author_link(self, book):
210         try:
211             return book.tags.filter(category='author')[0].get_absolute_url()
212         except KeyError:
213             return u''
214
215     def item_enclosure_url(self, book):
216         return "http://%s%s" % (Site.objects.get_current().domain, book.epub_file.url)
217
218     def item_enclosure_length(self, book):
219         return book.epub_file.size