Add an option to remove all tags from selected books in the bulk metadata editor. Add a tweak to control how the dates in the Date column are formatted. SONY driver: Use the tz field (available in newer readers) to set timestamps correctly, when available.

This commit is contained in:
Kovid Goyal 2010-08-24 10:13:20 -06:00
commit d23d99017d
6 changed files with 61 additions and 35 deletions

View File

@ -44,7 +44,7 @@ bool_custom_columns_are_tristate = 'yes'
# title within authors. # title within authors.
sort_columns_at_startup = None sort_columns_at_startup = None
# Format to be used for publication date # Format to be used for publication date and the timestamp (date).
# A string controlling how the publication date is displayed in the GUI # A string controlling how the publication date is displayed in the GUI
# d the day as number without a leading zero (1 to 31) # d the day as number without a leading zero (1 to 31)
# dd the day as number with a leading zero (01 to 31) # dd the day as number with a leading zero (01 to 31)
@ -59,8 +59,10 @@ sort_columns_at_startup = None
# For example, given the date of 9 Jan 2010, the following formats show # For example, given the date of 9 Jan 2010, the following formats show
# MMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010 # MMM yyyy ==> Jan 2010 yyyy ==> 2010 dd MMM yyyy ==> 09 Jan 2010
# MM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10 # MM/yyyy ==> 01/2010 d/M/yy ==> 9/1/10 yy ==> 10
# default if not set: MMM yyyy # publication default if not set: MMM yyyy
# timestamp default if not set: dd MMM yyyy
gui_pubdate_display_format = 'MMM yyyy' gui_pubdate_display_format = 'MMM yyyy'
gui_timestamp_display_format = 'dd MMM yyyy'
# Control title and series sorting in the library view. # Control title and series sorting in the library view.
# If set to 'library_order', Leading articles such as The and A will be ignored. # If set to 'library_order', Leading articles such as The and A will be ignored.

View File

@ -354,19 +354,22 @@ class XMLCache(object):
root = self.record_roots[i] root = self.record_roots[i]
lpath_map = self.build_lpath_map(root) lpath_map = self.build_lpath_map(root)
gtz_count = ltz_count = 0 gtz_count = ltz_count = 0
use_tz_var = False
for book in booklist: for book in booklist:
path = os.path.join(self.prefixes[i], *(book.lpath.split('/'))) path = os.path.join(self.prefixes[i], *(book.lpath.split('/')))
record = lpath_map.get(book.lpath, None) record = lpath_map.get(book.lpath, None)
if record is None: if record is None:
record = self.create_text_record(root, i, book.lpath) record = self.create_text_record(root, i, book.lpath)
(gtz_count, ltz_count) = self.update_text_record(record, book, (gtz_count, ltz_count, use_tz_var) = \
path, i, gtz_count, ltz_count) self.update_text_record(record, book, path, i,
gtz_count, ltz_count, use_tz_var)
# Ensure the collections in the XML database are recorded for # Ensure the collections in the XML database are recorded for
# this book # this book
if book.device_collections is None: if book.device_collections is None:
book.device_collections = [] book.device_collections = []
book.device_collections = playlist_map.get(book.lpath, []) book.device_collections = playlist_map.get(book.lpath, [])
debug_print('Timezone votes: %d GMT, %d LTZ'%(gtz_count, ltz_count)) debug_print('Timezone votes: %d GMT, %d LTZ, use_tz_var='%
(gtz_count, ltz_count, use_tz_var))
self.update_playlists(i, root, booklist, collections_attributes) self.update_playlists(i, root, booklist, collections_attributes)
# Update the device collections because update playlist could have added # Update the device collections because update playlist could have added
# some new ones. # some new ones.
@ -464,21 +467,27 @@ class XMLCache(object):
root.append(ans) root.append(ans)
return ans return ans
def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count): def update_text_record(self, record, book, path, bl_index,
gtz_count, ltz_count, use_tz_var):
''' '''
Update the Sony database from the book. This is done if the timestamp in Update the Sony database from the book. This is done if the timestamp in
the db differs from the timestamp on the file. the db differs from the timestamp on the file.
''' '''
# It seems that a Sony device can sometimes know what timezone it is in, # 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 # and apparently converts the dates to GMT when it writes them to its
# db. Unfortunately, we can't tell when it does this, so we use a # DB. We can detect that a device is timezone-aware because there is a
# horrible heuristic. First, set dates only for new books, trying to # 'tz' variable in the Sony DB, which we can set to "0" to tell the
# avoid upsetting the sony. Use the timezone determined through the # device to ignore its own timezone when comparing mtime to the date in
# voting described next. Second, voting: if a book is not new, compare # the DB.
# 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 # Unfortunately, if there is no tz variable in the DB, then we can't
# if the case of a tie, and hope it is right. # tell when the device applies a timezone conversion. We use a horrible
# heuristic to work around this problem. First, set dates only for new
# books, trying to avoid upsetting the sony. 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) timestamp = os.path.getmtime(path)
rec_date = record.get('date', None) rec_date = record.get('date', None)
@ -489,20 +498,25 @@ class XMLCache(object):
return x return x
if not getattr(book, '_new_book', False): # book is not new if not getattr(book, '_new_book', False): # book is not new
if strftime(timestamp, zone=time.gmtime) == rec_date: if record.get('tz', None) is not None:
gtz_count += 1 use_tz_var = True
elif strftime(timestamp, zone=time.localtime) == rec_date: if strftime(timestamp, zone=time.gmtime) == rec_date:
ltz_count += 1 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 else: # book is new. Set the time using the current votes
if ltz_count >= gtz_count: if use_tz_var:
tz = time.localtime tz = time.localtime
debug_print("Using localtime TZ for new book", book.lpath) record.set('tz', '0')
debug_print("Use localtime TZ and tz='0' for new book", book.lpath)
elif ltz_count >= gtz_count:
tz = time.localtime
debug_print("Use localtime TZ for new book", book.lpath)
else: else:
tz = time.gmtime tz = time.gmtime
debug_print("Using GMT TZ for new book", book.lpath) debug_print("Use GMT TZ for new book", book.lpath)
date = strftime(timestamp, zone=tz) date = strftime(timestamp, zone=tz)
record.set('date', clean(date)) record.set('date', clean(date))
record.set('size', clean(str(os.stat(path).st_size))) record.set('size', clean(str(os.stat(path).st_size)))
title = book.title if book.title else _('Unknown') title = book.title if book.title else _('Unknown')
record.set('title', clean(title)) record.set('title', clean(title))
@ -532,7 +546,7 @@ class XMLCache(object):
if 'id' not in record.attrib: if 'id' not in record.attrib:
num = self.max_id(record.getroottree().getroot()) num = self.max_id(record.getroottree().getroot())
record.set('id', str(num+1)) record.set('id', str(num+1))
return (gtz_count, ltz_count) return (gtz_count, ltz_count, use_tz_var)
# }}} # }}}
# Writing the XML files {{{ # Writing the XML files {{{

View File

@ -550,9 +550,7 @@ class BulkText(BulkBase):
remove_all, adding, rtext = self.gui_val remove_all, adding, rtext = self.gui_val
remove = set() remove = set()
if remove_all: if remove_all:
for book_id in book_ids: remove = set(self.db.all_custom(num=self.col_id))
remove |= set(self.db.get_custom(book_id, num=self.col_id,
index_is_id=True))
else: else:
txt = rtext txt = rtext
if txt: if txt:

View File

@ -199,7 +199,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
for w in getattr(self, 'custom_column_widgets', []): for w in getattr(self, 'custom_column_widgets', []):
w.gui_val w.gui_val
remove = unicode(self.remove_tags.text()).strip().split(',') if self.remove_all_tags.isChecked():
remove = self.db.all_tags()
else:
remove = unicode(self.remove_tags.text()).strip().split(',')
add = unicode(self.tags.text()).strip().split(',') add = unicode(self.tags.text()).strip().split(',')
au = unicode(self.authors.text()) au = unicode(self.authors.text())
aus = unicode(self.author_sort.text()) aus = unicode(self.author_sort.text())

View File

@ -140,7 +140,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="4" column="1" colspan="2">
<widget class="EnComboBox" name="publisher"> <widget class="EnComboBox" name="publisher">
<property name="editable"> <property name="editable">
<bool>true</bool> <bool>true</bool>
@ -191,14 +191,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1" colspan="2"> <item row="6" column="1">
<widget class="TagsLineEdit" name="remove_tags"> <widget class="TagsLineEdit" name="remove_tags">
<property name="toolTip"> <property name="toolTip">
<string>Comma separated list of tags to remove from the books. </string> <string>Comma separated list of tags to remove from the books. </string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="6" column="2">
<widget class="QCheckBox" name="remove_all_tags">
<property name="text">
<string>Remove all</string>
</property>
<property name="toolTip">
<string>Check this box to remove all tags from the books.</string>
</property>
</widget>
</item><item row="7" column="0">
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
<property name="text"> <property name="text">
<string>&amp;Series:</string> <string>&amp;Series:</string>

View File

@ -98,14 +98,14 @@ class DateDelegate(QStyledItemDelegate): # {{{
d = val.toDate() d = val.toDate()
if d <= UNDEFINED_QDATE: if d <= UNDEFINED_QDATE:
return '' return ''
return format_date(d.toPyDate(), 'dd MMM yyyy') format = tweaks['gui_timestamp_display_format']
if format is None:
format = 'dd MMM yyyy'
return format_date(d.toPyDate(), format)
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index) qde = QStyledItemDelegate.createEditor(self, parent, option, index)
stdformat = unicode(qde.displayFormat()) qde.setDisplayFormat('dd MMM yyyy')
if 'yyyy' not in stdformat:
stdformat = stdformat.replace('yy', 'yyyy')
qde.setDisplayFormat(stdformat)
qde.setMinimumDate(UNDEFINED_QDATE) qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined')) qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True) qde.setCalendarPopup(True)