From 0c8e1e20586c8321e237c797494a6a1840eaf414 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 3 Jul 2010 19:19:12 +0100 Subject: [PATCH 1/5] Implement timezone heuristic hack for sony devices --- src/calibre/devices/prs505/sony_cache.py | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 181a18bcf9..43832bca2b 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -345,6 +345,7 @@ class XMLCache(object): debug_print('Updating XML Cache:', i) root = self.record_roots[i] lpath_map = self.build_lpath_map(root) + self.timezone_for_dates = None for book in booklist: path = os.path.join(self.prefixes[i], *(book.lpath.split('/'))) record = lpath_map.get(book.lpath, None) @@ -458,10 +459,38 @@ class XMLCache(object): Update the Sony database from the book. This is done if the timestamp in the db differs from the timestamp on the file. ''' + + # It seems that a Sony device can sometimes know what timezone it is in. + # In this case it appears to convert the dates to GMT when it writes + # them to the db. Unfortunately, we can't tell when it is doing this, so + # we use a horrible heuristic to tell. As we check dates, check both + # localtime and gmtime. If neither match, set the date using localtime + # and hope it is right. If one of them matches, then assume that the + # rest of the dates in that db use that timezone. timestamp = os.path.getmtime(path) - date = strftime(timestamp) - if date != record.get('date', None): + if self.timezone_for_dates is None: + tz = time.localtime + else: + tz = self.timezone_for_dates + date = strftime(timestamp, zone=tz) + rec_date = record.get('date', None) + if date != rec_date: + if self.timezone_for_dates is None: + # We haven't yet identified a timezone. See if gmtime() matches + date = strftime(timestamp, zone=time.gmtime) + if date == rec_date: + # It did. Use gmtime for the rest of the dates. + debug_print("Using GMT TZ for dates") + self.timezone_for_dates = time.gmtime + # We now may or may not have identified a timezone. In either event, + # save the date. If gmtime matched, we are modifying it to itself, + # but that is OK for the one time it happens. record.set('date', date) + elif self.timezone_for_dates is None: + # Dates matched. Use localtime from here on. + debug_print("Using localtime TZ for dates") + self.timezone_for_dates = tz + record.set('size', str(os.stat(path).st_size)) title = book.title if book.title else _('Unknown') record.set('title', title) From 53b04dec5492d104f03bc8f9a939c6fdc1818742 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 3 Jul 2010 19:39:48 +0100 Subject: [PATCH 2/5] Check that db.get_collections () exists and is callable. --- src/calibre/gui2/library/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index c487a8a252..7ffbc42f02 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -937,6 +937,7 @@ class DeviceBooksModel(BooksModel): # {{{ cname = self.column_map[index.column()] if cname in ('title', 'authors') or \ (cname == 'collections' and \ + callable(getattr(self.db, 'supports_collections', None)) and \ self.db.supports_collections() and \ prefs['preserve_user_collections']): flags |= Qt.ItemIsEditable From 2a0f148013fbc05ec3156013202dfb0e21177d4a Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 3 Jul 2010 22:06:13 +0100 Subject: [PATCH 3/5] Add .doc files to the list of supported formats, as of Boox firmware 1.4 20100625 --- src/calibre/devices/hanlin/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/hanlin/driver.py b/src/calibre/devices/hanlin/driver.py index 6f98186fbb..f19135a7e7 100644 --- a/src/calibre/devices/hanlin/driver.py +++ b/src/calibre/devices/hanlin/driver.py @@ -109,7 +109,8 @@ class BOOX(HANLINV3): METADATA_CACHE = '.metadata.calibre' # Ordered list of supported formats - FORMATS = ['epub', 'fb2', 'djvu', 'pdf', 'html', 'txt', 'rtf', 'mobi', 'prc', 'chm'] + FORMATS = ['epub', 'fb2', 'djvu', 'pdf', 'html', 'txt', 'rtf', 'mobi', + 'prc', 'chm', 'doc'] VENDOR_ID = [0x0525] PRODUCT_ID = [0xa4a5] From dbdd7dc4b624374bcf3463308e1b973c2d19c4b8 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 4 Jul 2010 08:19:47 +0100 Subject: [PATCH 4/5] Add path and fix missing format from dialog delete_matching_from_device.py --- .../gui2/dialogs/delete_matching_from_device.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/dialogs/delete_matching_from_device.py b/src/calibre/gui2/dialogs/delete_matching_from_device.py index dbac2fe4ad..f30f2e00c7 100644 --- a/src/calibre/gui2/dialogs/delete_matching_from_device.py +++ b/src/calibre/gui2/dialogs/delete_matching_from_device.py @@ -25,6 +25,12 @@ class tableItem(QTableWidgetItem): def __lt__(self, other): return self.sort < other.sort +class centeredTableItem(tableItem): + + def __init__(self, text): + tableItem.__init__(self, text) + self.setTextAlignment(Qt.AlignCenter) + class titleTableItem(tableItem): def __init__(self, text): @@ -64,10 +70,10 @@ class DeleteMatchingFromDeviceDialog(QDialog, Ui_DeleteMatchingFromDeviceDialog) self.buttonBox.accepted.connect(self.accepted) self.table.cellClicked.connect(self.cell_clicked) self.table.setSelectionMode(QAbstractItemView.NoSelection) - self.table.setColumnCount(5) + self.table.setColumnCount(7) self.table.setHorizontalHeaderLabels( - ['', _('Location'), _('Title'), - _('Author'), _('Date'), _('Format')]) + ['', _('Location'), _('Title'), _('Author'), + _('Date'), _('Format'), _('Path')]) rows = 0 for card in items: rows += len(items[card][1]) @@ -85,7 +91,8 @@ class DeleteMatchingFromDeviceDialog(QDialog, Ui_DeleteMatchingFromDeviceDialog) self.table.setItem(row, 2, titleTableItem(book.title)) self.table.setItem(row, 3, authorTableItem(book)) self.table.setItem(row, 4, dateTableItem(book.datetime)) - self.table.setItem(row, 5, tableItem(book.path.rpartition('.')[2])) + self.table.setItem(row, 5, centeredTableItem(book.path.rpartition('.')[2])) + self.table.setItem(row, 6, tableItem(book.path)) row += 1 self.table.setCurrentCell(0, 1) self.table.resizeColumnsToContents() From b8d8081c5a2e887a831646576fd915980fc10f75 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 4 Jul 2010 11:04:38 +0100 Subject: [PATCH 5/5] Another attempt at sony dates --- src/calibre/devices/prs505/sony_cache.py | 58 ++++++++++++------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 43832bca2b..d465929059 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -345,18 +345,20 @@ class XMLCache(object): debug_print('Updating XML Cache:', i) root = self.record_roots[i] lpath_map = self.build_lpath_map(root) - self.timezone_for_dates = None + gtz_count = ltz_count = 0 for book in booklist: path = os.path.join(self.prefixes[i], *(book.lpath.split('/'))) record = lpath_map.get(book.lpath, None) if record is None: record = self.create_text_record(root, i, book.lpath) - self.update_text_record(record, book, path, i) + (gtz_count, ltz_count) = self.update_text_record(record, book, + path, i, gtz_count, ltz_count) # Ensure the collections in the XML database are recorded for # this book if book.device_collections is None: book.device_collections = [] book.device_collections = playlist_map.get(book.lpath, []) + debug_print('Timezone votes: %d GMT, %d LTZ'%(gtz_count, ltz_count)) self.update_playlists(i, root, booklist, collections_attributes) # Update the device collections because update playlist could have added # some new ones. @@ -454,42 +456,37 @@ class XMLCache(object): root.append(ans) return ans - def update_text_record(self, record, book, path, bl_index): + def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count): ''' Update the Sony database from the book. This is done if the timestamp in the db differs from the timestamp on the file. ''' - # It seems that a Sony device can sometimes know what timezone it is in. - # In this case it appears to convert the dates to GMT when it writes - # them to the db. Unfortunately, we can't tell when it is doing this, so - # we use a horrible heuristic to tell. As we check dates, check both - # localtime and gmtime. If neither match, set the date using localtime - # and hope it is right. If one of them matches, then assume that the - # rest of the dates in that db use that timezone. + # It seems that a Sony device can sometimes know what timezone it is in, + # and apparently converts the dates to GMT when it writes them to the + # db. Unfortunately, we can't tell when it does this, so we use a + # horrible heuristic. First, set dates only for new books, trying to + # avoid upsetting the sony. Use the timezone determined through the + # voting described next. Second, voting: if a book is not new, compare + # its Sony DB date against localtime and gmtime. Count the matches. When + # we must set a date, use the one with the most matches. Use localtime + # if the case of a tie, and hope it is right. timestamp = os.path.getmtime(path) - if self.timezone_for_dates is None: - tz = time.localtime - else: - tz = self.timezone_for_dates - date = strftime(timestamp, zone=tz) rec_date = record.get('date', None) - if date != rec_date: - if self.timezone_for_dates is None: - # We haven't yet identified a timezone. See if gmtime() matches - date = strftime(timestamp, zone=time.gmtime) - if date == rec_date: - # It did. Use gmtime for the rest of the dates. - debug_print("Using GMT TZ for dates") - self.timezone_for_dates = time.gmtime - # We now may or may not have identified a timezone. In either event, - # save the date. If gmtime matched, we are modifying it to itself, - # but that is OK for the one time it happens. + if not getattr(book, '_new_book', False): # book is not new + if strftime(timestamp, zone=time.gmtime) == rec_date: + gtz_count += 1 + elif strftime(timestamp, zone=time.localtime) == rec_date: + ltz_count += 1 + else: # book is new. Set the time using the current votes + if ltz_count >= gtz_count: + tz = time.localtime + debug_print("Using localtime TZ for new book", book.lpath) + else: + tz = self.time.gmtime + debug_print("Using GMT TZ for new book", book.lpath) + date = strftime(timestamp, zone=tz) record.set('date', date) - elif self.timezone_for_dates is None: - # Dates matched. Use localtime from here on. - debug_print("Using localtime TZ for dates") - self.timezone_for_dates = tz record.set('size', str(os.stat(path).st_size)) title = book.title if book.title else _('Unknown') @@ -515,6 +512,7 @@ class XMLCache(object): if 'id' not in record.attrib: num = self.max_id(record.getroottree().getroot()) record.set('id', str(num+1)) + return (gtz_count, ltz_count) # }}} # Writing the XML files {{{