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.
 
   6 from urlparse import urljoin
 
   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
 
  16 from basicauth import logged_in_or_basicauth, factory_decorator
 
  17 from catalogue.models import Book, Tag
 
  19 from search import Search, SearchResult, JVM
 
  20 from lucene import Term, QueryWrapperFilter, TermQuery
 
  24 from stats.utils import piwik_track
 
  29         u"link": u"opds_user",
 
  31         u"title": u"Moje półki",
 
  32         u"description": u"Półki użytkownika dostępne po zalogowaniu"
 
  35         u"category": u"author",
 
  36         u"link": u"opds_by_category",
 
  37         u"link_args": [u"author"],
 
  39         u"description": u"Utwory wg autorów"
 
  43         u"link": u"opds_by_category",
 
  44         u"link_args": [u"kind"],
 
  46         u"description": u"Utwory wg rodzajów"
 
  49         u"category": u"genre",
 
  50         u"link": u"opds_by_category",
 
  51         u"link_args": [u"genre"],
 
  53         u"description": u"Utwory wg gatunków"
 
  56         u"category": u"epoch",
 
  57         u"link": u"opds_by_category",
 
  58         u"link_args": [u"epoch"],
 
  60         u"description": u"Utwory wg epok"
 
  66     return urljoin("http://%s" % Site.objects.get_current().domain, url)
 
  69 class OPDSFeed(Atom1Feed):
 
  70     link_rel = u"subsection"
 
  71     link_type = u"application/atom+xml"
 
  73     _book_parent_img = full_url(os.path.join(settings.STATIC_URL, "img/book-parent.png"))
 
  75         _book_parent_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book-parent.png")))
 
  77         _book_parent_img_size = ''
 
  79     _book_img = full_url(os.path.join(settings.STATIC_URL, "img/book.png"))
 
  81         _book_img_size = unicode(os.path.getsize(os.path.join(settings.STATIC_ROOT, "img/book.png")))
 
  86     def add_root_elements(self, handler):
 
  87         super(OPDSFeed, self).add_root_elements(handler)
 
  88         handler.addQuickElement(u"link", None,
 
  89                                 {u"href": reverse("opds_authors"),
 
  91                                  u"type": u"application/atom+xml"})
 
  92         handler.addQuickElement(u"link", None, 
 
  93                                 {u"href": full_url(os.path.join(settings.STATIC_URL, "opensearch.xml")),
 
  95                                  u"type": u"application/opensearchdescription+xml"})
 
  98     def add_item_elements(self, handler, item):
 
  99         """ modified from Atom1Feed.add_item_elements """
 
 100         handler.addQuickElement(u"title", item['title'])
 
 102         # add a OPDS Navigation link if there's no enclosure
 
 103         if item['enclosure'] is None:
 
 104             handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"subsection", u"type": u"application/atom+xml"})
 
 105             # add a "green book" icon
 
 106             handler.addQuickElement(u"link", '',
 
 107                 {u"rel": u"http://opds-spec.org/thumbnail",
 
 108                  u"href": self._book_parent_img,
 
 109                  u"length": self._book_parent_img_size,
 
 110                  u"type": u"image/png"})
 
 111         if item['pubdate'] is not None:
 
 112             handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
 
 114         # Author information.
 
 115         if item['author_name'] is not None:
 
 116             handler.startElement(u"author", {})
 
 117             handler.addQuickElement(u"name", item['author_name'])
 
 118             if item['author_email'] is not None:
 
 119                 handler.addQuickElement(u"email", item['author_email'])
 
 120             if item['author_link'] is not None:
 
 121                 handler.addQuickElement(u"uri", item['author_link'])
 
 122             handler.endElement(u"author")
 
 125         if item['unique_id'] is not None:
 
 126             unique_id = item['unique_id']
 
 128             unique_id = get_tag_uri(item['link'], item['pubdate'])
 
 129         handler.addQuickElement(u"id", unique_id)
 
 132         # OPDS needs type=text
 
 133         if item['description'] is not None:
 
 134             handler.addQuickElement(u"summary", item['description'], {u"type": u"text"})
 
 136         # Enclosure as OPDS Acquisition Link
 
 137         if item['enclosure'] is not None:
 
 138             handler.addQuickElement(u"link", '',
 
 139                 {u"rel": u"http://opds-spec.org/acquisition",
 
 140                  u"href": item['enclosure'].url,
 
 141                  u"length": item['enclosure'].length,
 
 142                  u"type": item['enclosure'].mime_type})
 
 143             # add a "red book" icon
 
 144             handler.addQuickElement(u"link", '',
 
 145                 {u"rel": u"http://opds-spec.org/thumbnail",
 
 146                  u"href": self._book_img,
 
 147                  u"length": self._book_img_size,
 
 148                  u"type": u"image/png"})
 
 151         for cat in item['categories']:
 
 152             handler.addQuickElement(u"category", u"", {u"term": cat})
 
 155         if item['item_copyright'] is not None:
 
 156             handler.addQuickElement(u"rights", item['item_copyright'])
 
 159 class AcquisitionFeed(Feed):
 
 161     link = u'http://www.wolnelektury.pl/'
 
 162     item_enclosure_mime_type = "application/epub+zip"
 
 163     author_name = u"Wolne Lektury"
 
 164     author_link = u"http://www.wolnelektury.pl/"
 
 166     def item_title(self, book):
 
 169     def item_description(self):
 
 172     def item_link(self, book):
 
 173         return book.get_absolute_url()
 
 175     def item_author_name(self, book):
 
 177             return book.tags.filter(category='author')[0].name
 
 181     def item_author_link(self, book):
 
 183             return book.tags.filter(category='author')[0].get_absolute_url()
 
 187     def item_enclosure_url(self, book):
 
 188         return full_url(book.epub_file.url) if book.epub_file else None
 
 190     def item_enclosure_length(self, book):
 
 191         return book.epub_file.size if book.epub_file else None
 
 194 class RootFeed(Feed):
 
 196     title = u'Wolne Lektury'
 
 197     link = u'http://wolnelektury.pl/'
 
 198     description = u"Spis utworów na stronie http://WolneLektury.pl"
 
 199     author_name = u"Wolne Lektury"
 
 200     author_link = u"http://wolnelektury.pl/"
 
 205     def item_title(self, item):
 
 208     def item_link(self, item):
 
 209         return reverse(item['link'], args=item['link_args'])
 
 211     def item_description(self, item):
 
 212         return item['description']
 
 215 class ByCategoryFeed(Feed):
 
 217     link = u'http://wolnelektury.pl/'
 
 218     description = u"Spis utworów na stronie http://WolneLektury.pl"
 
 219     author_name = u"Wolne Lektury"
 
 220     author_link = u"http://wolnelektury.pl/"
 
 222     def get_object(self, request, category):
 
 223         feed = [feed for feed in _root_feeds if feed['category']==category]
 
 231     def title(self, feed):
 
 234     def items(self, feed):
 
 235         return Tag.objects.filter(category=feed['category']).exclude(book_count=0)
 
 237     def item_title(self, item):
 
 240     def item_link(self, item):
 
 241         return reverse("opds_by_tag", args=[item.category, item.slug])
 
 243     def item_description(self):
 
 247 class ByTagFeed(AcquisitionFeed):
 
 249         return tag.get_absolute_url()
 
 251     def title(self, tag):
 
 254     def description(self, tag):
 
 255         return u"Spis utworów na stronie http://WolneLektury.pl"
 
 257     def get_object(self, request, category, slug):
 
 258         return get_object_or_404(Tag, category=category, slug=slug)
 
 260     def items(self, tag):
 
 261         books = Book.tagged.with_any([tag])
 
 262         l_tags = Tag.objects.filter(category='book', slug__in=[book.book_tag_slug() for book in books.iterator()])
 
 263         descendants_keys = [book.pk for book in Book.tagged.with_any(l_tags)]
 
 265             books = books.exclude(pk__in=descendants_keys)
 
 270 @factory_decorator(logged_in_or_basicauth())
 
 272 class UserFeed(Feed):
 
 274     link = u'http://www.wolnelektury.pl/'
 
 275     description = u"Półki użytkownika na stronie http://WolneLektury.pl"
 
 276     author_name = u"Wolne Lektury"
 
 277     author_link = u"http://wolnelektury.pl/"
 
 279     def get_object(self, request):
 
 282     def title(self, user):
 
 283         return u"Półki użytkownika %s" % user.username
 
 285     def items(self, user):
 
 286         return Tag.objects.filter(category='set', user=user).exclude(book_count=0)
 
 288     def item_title(self, item):
 
 291     def item_link(self, item):
 
 292         return reverse("opds_user_set", args=[item.slug])
 
 294     def item_description(self):
 
 297 # no class decorators in python 2.5
 
 298 #UserFeed = factory_decorator(logged_in_or_basicauth())(UserFeed)
 
 301 @factory_decorator(logged_in_or_basicauth())
 
 303 class UserSetFeed(AcquisitionFeed):
 
 305         return tag.get_absolute_url()
 
 307     def title(self, tag):
 
 310     def description(self, tag):
 
 311         return u"Spis utworów na stronie http://WolneLektury.pl"
 
 313     def get_object(self, request, slug):
 
 314         return get_object_or_404(Tag, category='set', slug=slug, user=request.user)
 
 316     def items(self, tag):
 
 317         return Book.tagged.with_any([tag])
 
 319 # no class decorators in python 2.5
 
 320 #UserSetFeed = factory_decorator(logged_in_or_basicauth())(UserSetFeed)
 
 324 class SearchFeed(AcquisitionFeed):
 
 325     description = u"Wyniki wyszukiwania na stronie WolneLektury.pl"
 
 326     title = u"Wyniki wyszukiwania"
 
 328     INLINE_QUERY_RE = re.compile(r"(author:(?P<author>[^ ]+)|title:(?P<title>[^ ]+)|categories:(?P<categories>[^ ]+)|description:(?P<description>[^ ]+))")
 
 330     def get_object(self, request):
 
 332         For OPDS 1.1 We should handle a query for search terms
 
 333         and criteria provided either as opensearch or 'inline' query.
 
 334         OpenSearch defines fields: atom:author, atom:contributor (treated as translator),
 
 335         atom:title. Inline query provides author, title, categories (treated as book tags),
 
 336         description (treated as content search terms).
 
 338         if search terms are provided, we shall search for books
 
 339         according to Hint information (from author & contributror & title).
 
 341         but if search terms are empty, we should do a different search
 
 342         (perhaps for is_book=True)
 
 345         JVM.attachCurrentThread()
 
 347         query = request.GET.get('q', '')
 
 349         inline_criteria = re.findall(self.INLINE_QUERY_RE, query)
 
 351             def get_criteria(criteria, name, position):
 
 352                 e = filter(lambda el: el[0][0:len(name)] == name, criteria)
 
 358                 if c[0] == '"' and c[-1] == '"':
 
 360                     c = c.replace('+', ' ')
 
 363             #import pdb; pdb.set_trace()
 
 364             author = get_criteria(inline_criteria, 'author', 1)
 
 365             title = get_criteria(inline_criteria, 'title', 2)
 
 367             categories = get_criteria(inline_criteria, 'categories', 3)
 
 368             query = get_criteria(inline_criteria, 'description', 4)
 
 370             author = request.GET.get('author', '')
 
 371             title = request.GET.get('title', '')
 
 372             translator = request.GET.get('translator', '')
 
 380         # Scenario 1: full search terms provided.
 
 381         # Use auxiliarry information to narrow it and make it better.
 
 386                 print "narrow to author %s" % author
 
 387                 hint.tags(srch.search_tags(author, filt=srch.term_filter(Term('tag_category', 'author'))))
 
 390                 print "filter by translator %s" % translator
 
 391                 filters.append(QueryWrapperFilter(
 
 392                     srch.make_phrase(srch.get_tokens(translator, field='translators'),
 
 393                                      field='translators')))
 
 396                 filters.append(QueryWrapperFilter(
 
 397                     srch.make_phrase(srch.get_tokens(categories, field="tag_name_pl"),
 
 398                                      field='tag_name_pl')))
 
 400             flt = srch.chain_filters(filters)
 
 402                 print "hint by book title %s" % title
 
 403                 q = srch.make_phrase(srch.get_tokens(title, field='title'), field='title')
 
 404                 hint.books(*srch.search_books(q, filt=flt))
 
 406             toks = srch.get_tokens(query)
 
 407             print "tokens: %s" % toks
 
 408             #            import pdb; pdb.set_trace()
 
 409             results = SearchResult.aggregate(srch.search_perfect_book(toks, fuzzy=fuzzy, hint=hint),
 
 410                 srch.search_perfect_parts(toks, fuzzy=fuzzy, hint=hint),
 
 411                 srch.search_everywhere(toks, fuzzy=fuzzy, hint=hint))
 
 412             results.sort(reverse=True)
 
 413             return [r.book for r in results]
 
 415             # Scenario 2: since we no longer have to figure out what the query term means to the user,
 
 416             # we can just use filters and not the Hint class.
 
 421                 'translators': translator,
 
 425             for fld, q in fields.items():
 
 427                     filters.append(QueryWrapperFilter(
 
 428                         srch.make_phrase(srch.get_tokens(q, field=fld), field=fld)))
 
 430             flt = srch.chain_filters(filters)
 
 431             books = srch.search_books(TermQuery(Term('is_book', 'true')), filt=flt)
 
 434     def get_link(self, query):
 
 435         return "%s?q=%s" % (reverse('search'), query)
 
 437     def items(self, books):