diff --git a/src/calibre/devices/kobo/bookmark.py b/src/calibre/devices/kobo/bookmark.py new file mode 100644 index 0000000000..8e199f77a6 --- /dev/null +++ b/src/calibre/devices/kobo/bookmark.py @@ -0,0 +1,112 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2011, Timothy Legge and Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os +from contextlib import closing + +import sqlite3 as sqlite + +class Bookmark(): # {{{ + ''' + A simple class fetching bookmark data + kobo-specific + ''' + def __init__(self, db_path, contentid, path, id, book_format, bookmark_extension): + self.book_format = book_format + self.bookmark_extension = bookmark_extension + self.book_length = 0 # Not Used + self.id = id + self.last_read = 0 + self.last_read_location = 0 # Not Used + self.path = path + self.timestamp = 0 + self.user_notes = None + self.db_path = db_path + self.contentid = contentid + self.percent_read = 0 + self.get_bookmark_data() + self.get_book_length() # Not Used + + def get_bookmark_data(self): + ''' Return the timestamp and last_read_location ''' + + user_notes = {} + self.timestamp = os.path.getmtime(self.path) + with closing(sqlite.connect(self.db_path)) as connection: + # return bytestrings if the content cannot the decoded as unicode + connection.text_factory = lambda x: unicode(x, "utf-8", "ignore") + + cursor = connection.cursor() + t = (self.contentid,) + + cursor.execute('select bm.bookmarkid, bm.contentid, bm.volumeid, ' + 'bm.text, bm.annotation, bm.ChapterProgress, ' + 'bm.StartContainerChildIndex, bm.StartOffset, c.BookTitle, ' + 'c.TITLE, c.volumeIndex, c.___NumPages ' + 'from Bookmark bm inner join Content c on ' + 'bm.contentid = c.contentid and ' + 'bm.volumeid = ? order by bm.volumeid, bm.chapterprogress', t) + + previous_chapter = 0 + bm_count = 0 + for row in cursor: + current_chapter = row[10] + if previous_chapter == current_chapter: + bm_count = bm_count + 1 + else: + bm_count = 0 + + text = row[3] + annotation = row[4] + + # A dog ear (bent upper right corner) is a bookmark + if row[6] == row[7] == 0: # StartContainerChildIndex = StartOffset = 0 + e_type = 'Bookmark' + text = row[9] + # highlight is text with no annotation + elif text is not None and (annotation is None or annotation == ""): + e_type = 'Highlight' + elif text and annotation: + e_type = 'Annotation' + else: + e_type = 'Unknown annotation type' + + note_id = row[10] + bm_count + chapter_title = row[9] + # book_title = row[8] + chapter_progress = min(round(float(100*row[5]),2),100) + user_notes[note_id] = dict(id=self.id, + displayed_location=note_id, + type=e_type, + text=text, + annotation=annotation, + chapter=row[10], + chapter_title=chapter_title, + chapter_progress=chapter_progress) + previous_chapter = row[10] + # debug_print("e_type:" , e_type, '\t', 'loc: ', note_id, 'text: ', text, + # 'annotation: ', annotation, 'chapter_title: ', chapter_title, + # 'chapter_progress: ', chapter_progress, 'date: ') + + cursor.execute('select datelastread, ___PercentRead from content ' + 'where bookid is Null and ' + 'contentid = ?', t) + for row in cursor: + self.last_read = row[0] + self.percent_read = row[1] + # print row[1] + cursor.close() + +# self.last_read_location = self.last_read - self.pdf_page_offset + self.user_notes = user_notes + + + def get_book_length(self): +#TL self.book_length = 0 +#TL self.book_length = int(unpack('>I', record0[0x04:0x08])[0]) + pass + +# }}} diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 9739046db0..479beed089 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -2,15 +2,16 @@ # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai __license__ = 'GPL v3' -__copyright__ = '2010, Timothy Legge and Kovid Goyal ' +__copyright__ = '2010, Timothy Legge and Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, time, calendar import sqlite3 as sqlite from contextlib import closing from calibre.devices.usbms.books import BookList from calibre.devices.kobo.books import Book from calibre.devices.kobo.books import ImageWrapper +from calibre.devices.kobo.bookmark import Bookmark from calibre.devices.mime import mime_type_ext from calibre.devices.usbms.driver import USBMS, debug_print from calibre import prints @@ -24,7 +25,7 @@ class KOBO(USBMS): gui_name = 'Kobo Reader' description = _('Communicate with the Kobo Reader') author = 'Timothy Legge' - version = (1, 0, 10) + version = (1, 0, 11) dbversion = 0 fwversion = 0 @@ -47,6 +48,7 @@ class KOBO(USBMS): EBOOK_DIR_MAIN = '' SUPPORTS_SUB_DIRS = True + SUPPORTS_ANNOTATIONS = True VIRTUAL_BOOK_EXTENSIONS = frozenset(['kobo']) @@ -77,11 +79,6 @@ class KOBO(USBMS): self.book_class = Book self.dbversion = 7 - def create_annotations_path(self, mdata, device_path=None): - if device_path: - return device_path - return USBMS.create_annotations_path(self, mdata) - def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext @@ -111,6 +108,7 @@ class KOBO(USBMS): if self.fwversion != '1.0' and self.fwversion != '1.4': self.has_kepubs = True + debug_print('Version of driver: ', self.version, 'Has kepubs:', self.has_kepubs) debug_print('Version of firmware: ', self.fwversion, 'Has kepubs:', self.has_kepubs) self.booklist_class.rebuild_collections = self.rebuild_collections @@ -893,3 +891,198 @@ class KOBO(USBMS): tf.write(r.read()) paths[idx] = tf.name return paths + + def create_annotations_path(self, mdata, device_path=None): + if device_path: + return device_path + return USBMS.create_annotations_path(self, mdata) + + def get_annotations(self, path_map): + EPUB_FORMATS = [u'epub'] + epub_formats = set(EPUB_FORMATS) + + def get_storage(): + storage = [] + if self._main_prefix: + storage.append(os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)) + if self._card_a_prefix: + storage.append(os.path.join(self._card_a_prefix, self.EBOOK_DIR_CARD_A)) + if self._card_b_prefix: + storage.append(os.path.join(self._card_b_prefix, self.EBOOK_DIR_CARD_B)) + return storage + + def resolve_bookmark_paths(storage, path_map): + pop_list = [] + book_ext = {} + for id in path_map: + file_fmts = set() + for fmt in path_map[id]['fmts']: + file_fmts.add(fmt) + bookmark_extension = None + if file_fmts.intersection(epub_formats): + book_extension = list(file_fmts.intersection(epub_formats))[0] + bookmark_extension = 'epub' + + if bookmark_extension: + for vol in storage: + bkmk_path = path_map[id]['path'] + bkmk_path = bkmk_path + if os.path.exists(bkmk_path): + path_map[id] = bkmk_path + book_ext[id] = book_extension + break + else: + pop_list.append(id) + else: + pop_list.append(id) + + # Remove non-existent bookmark templates + for id in pop_list: + path_map.pop(id) + return path_map, book_ext + + storage = get_storage() + path_map, book_ext = resolve_bookmark_paths(storage, path_map) + + bookmarked_books = {} + for id in path_map: + extension = os.path.splitext(path_map[id])[1] + ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(path_map[id]) + ContentID = self.contentid_from_path(path_map[id], ContentType) + + bookmark_ext = extension + + db_path = self.normalize_path(self._main_prefix + '.kobo/KoboReader.sqlite') + myBookmark = Bookmark(db_path, ContentID, path_map[id], id, book_ext[id], bookmark_ext) + bookmarked_books[id] = self.UserAnnotation(type='kobo_bookmark', value=myBookmark) + + # This returns as job.result in gui2.ui.annotations_fetched(self,job) + return bookmarked_books + + def generate_annotation_html(self, bookmark): + from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString + # Returns
...
+ #last_read_location = bookmark.last_read_location + #timestamp = bookmark.timestamp + percent_read = bookmark.percent_read + debug_print("Date: ", bookmark.last_read) + if bookmark.last_read is not None: + try: + last_read = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(calendar.timegm(time.strptime(bookmark.last_read, "%Y-%m-%dT%H:%M:%S")))) + except: + last_read = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(calendar.timegm(time.strptime(bookmark.last_read, "%Y-%m-%dT%H:%M:%S.%f")))) + else: + #self.datetime = time.gmtime() + last_read = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) + + # debug_print("Percent read: ", percent_read) + ka_soup = BeautifulSoup() + dtc = 0 + divTag = Tag(ka_soup,'div') + divTag['class'] = 'user_annotations' + + # Add the last-read location + spanTag = Tag(ka_soup, 'span') + spanTag['style'] = 'font-weight:normal' + if bookmark.book_format == 'epub': + spanTag.insert(0,NavigableString( + _("
Book Last Read: %(time)s
Percentage Read: %(pr)d%%
") % \ + dict(time=last_read, + #loc=last_read_location, + pr=percent_read))) + else: + spanTag.insert(0,NavigableString( + _("
Book Last Read: %(time)s
Percentage Read: %(pr)d%%
") % \ + dict(time=last_read, + #loc=last_read_location, + pr=percent_read))) + + divTag.insert(dtc, spanTag) + dtc += 1 + divTag.insert(dtc, Tag(ka_soup,'br')) + dtc += 1 + + if bookmark.user_notes: + user_notes = bookmark.user_notes + annotations = [] + + # Add the annotations sorted by location + for location in sorted(user_notes): + if user_notes[location]['type'] == 'Bookmark': + annotations.append( + _('Chapter %(chapter)d: %(chapter_title)s
%(typ)s
Chapter Progress: %(chapter_progress)s%%
%(annotation)s

') % \ + dict(chapter=user_notes[location]['chapter'], + dl=user_notes[location]['displayed_location'], + typ=user_notes[location]['type'], + chapter_title=user_notes[location]['chapter_title'], + chapter_progress=user_notes[location]['chapter_progress'], + annotation=user_notes[location]['annotation'] if user_notes[location]['annotation'] is not None else "")) + elif user_notes[location]['type'] == 'Highlight': + annotations.append( + _('Chapter %(chapter)d: %(chapter_title)s
%(typ)s
Chapter Progress: %(chapter_progress)s%%
Highlight: %(text)s

') % \ + dict(chapter=user_notes[location]['chapter'], + dl=user_notes[location]['displayed_location'], + typ=user_notes[location]['type'], + chapter_title=user_notes[location]['chapter_title'], + chapter_progress=user_notes[location]['chapter_progress'], + text=user_notes[location]['text'])) + elif user_notes[location]['type'] == 'Annotation': + annotations.append( + _('Chapter %(chapter)d: %(chapter_title)s
%(typ)s
Chapter Progress: %(chapter_progress)s%%
Highlight: %(text)s
Notes: %(annotation)s

') % \ + dict(chapter=user_notes[location]['chapter'], + dl=user_notes[location]['displayed_location'], + typ=user_notes[location]['type'], + chapter_title=user_notes[location]['chapter_title'], + chapter_progress=user_notes[location]['chapter_progress'], + text=user_notes[location]['text'], + annotation=user_notes[location]['annotation'])) + else: + annotations.append( + _('Chapter %(chapter)d: %(chapter_title)s
%(typ)s
Chapter Progress: %(chapter_progress)s%%
Highlight: %(text)s
Notes: %(annotation)s

') % \ + dict(chapter=user_notes[location]['chapter'], + dl=user_notes[location]['displayed_location'], + typ=user_notes[location]['type'], + chapter_title=user_notes[location]['chapter_title'], + chapter_progress=user_notes[location]['chapter_progress'], + text=user_notes[location]['text'], \ + annotation=user_notes[location]['annotation'])) + + for annotation in annotations: + divTag.insert(dtc, annotation) + dtc += 1 + + ka_soup.insert(0,divTag) + return ka_soup + + def add_annotation_to_library(self, db, db_id, annotation): + from calibre.ebooks.BeautifulSoup import Tag + bm = annotation + ignore_tags = set(['Catalog', 'Clippings']) + + if bm.type == 'kobo_bookmark': + mi = db.get_metadata(db_id, index_is_id=True) + user_notes_soup = self.generate_annotation_html(bm.value) + if mi.comments: + a_offset = mi.comments.find('
') + ad_offset = mi.comments.find('
') + + if a_offset >= 0: + mi.comments = mi.comments[:a_offset] + if ad_offset >= 0: + mi.comments = mi.comments[:ad_offset] + if set(mi.tags).intersection(ignore_tags): + return + if mi.comments: + hrTag = Tag(user_notes_soup,'hr') + hrTag['class'] = 'annotations_divider' + user_notes_soup.insert(0, hrTag) + + mi.comments += unicode(user_notes_soup.prettify()) + else: + mi.comments = unicode(user_notes_soup.prettify()) + # Update library comments + db.set_comment(db_id, mi.comments) + + # Add bookmark file to db_id + db.add_format_with_hooks(db_id, bm.value.bookmark_extension, + bm.value.path, index_is_id=True)