Add a preference setting to control how device collections are managed

This commit is contained in:
Kovid Goyal 2010-06-24 11:50:29 -06:00
commit da8a09694d
12 changed files with 94 additions and 23 deletions

View File

@ -6,7 +6,6 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, time
from pprint import pprint
from base64 import b64decode
from uuid import uuid4
from lxml import etree
@ -179,8 +178,8 @@ class XMLCache(object):
path = record.get('path', None)
if path:
if path not in playlist_map:
playlist_map[path] = set()
playlist_map[path].add(title)
playlist_map[path] = []
playlist_map[path].append(title)
debug_print('Finish build_id_playlist_map. Found', len(playlist_map))
return playlist_map
@ -309,14 +308,14 @@ class XMLCache(object):
book.thumbnail = raw
break
break
book.device_collections = list(playlist_map.get(book.lpath, set()))
book.device_collections = playlist_map.get(book.lpath, [])
debug_print('Finished updating JSON cache:', bl_index)
# }}}
# Update XML from JSON {{{
def update(self, booklists, collections_attributes):
debug_print('In update. Starting update XML from JSON')
debug_print('Starting update', collections_attributes)
for i, booklist in booklists.items():
playlist_map = self.build_id_playlist_map(i)
debug_print('Updating XML Cache:', i)
@ -332,8 +331,7 @@ class XMLCache(object):
# this book
if book.device_collections is None:
book.device_collections = []
book.device_collections = list(set(book.device_collections) |
playlist_map.get(book.lpath, set()))
book.device_collections = playlist_map.get(book.lpath, [])
self.update_playlists(i, root, booklist, collections_attributes)
# Update the device collections because update playlist could have added
# some new ones.
@ -341,10 +339,9 @@ class XMLCache(object):
for i, booklist in booklists.items():
playlist_map = self.build_id_playlist_map(i)
for book in booklist:
book.device_collections = list(set(book.device_collections) |
playlist_map.get(book.lpath, set()))
book.device_collections = playlist_map.get(book.lpath, [])
self.fix_ids()
debug_print('Finished update XML from JSON')
debug_print('Finished update')
def rebuild_collections(self, booklist, bl_index):
if bl_index not in self.record_roots:

View File

@ -11,6 +11,7 @@ from calibre.devices.mime import mime_type_ext
from calibre.devices.interface import BookList as _BookList
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre import isbytestring
from calibre.utils.config import prefs
class Book(MetaInformation):
@ -76,7 +77,7 @@ class Book(MetaInformation):
in C{other} takes precedence, unless the information in C{other} is NULL.
'''
MetaInformation.smart_update(self, other)
MetaInformation.smart_update(self, other, replace_tags=True)
for attr in self.BOOK_ATTRS:
if hasattr(other, attr):
@ -132,7 +133,9 @@ class CollectionsBookList(BookList):
def get_collections(self, collection_attributes):
collections = {}
series_categories = set([])
collection_attributes = list(collection_attributes)+['device_collections']
collection_attributes = list(collection_attributes)
if prefs['preserve_user_collections']:
collection_attributes += ['device_collections']
for attr in collection_attributes:
attr = attr.strip()
for book in self:

View File

@ -268,7 +268,7 @@ class MetaInformation(object):
):
prints(x, getattr(self, x, 'None'))
def smart_update(self, mi):
def smart_update(self, mi, replace_tags=False):
'''
Merge the information in C{mi} into self. In case of conflicts, the information
in C{mi} takes precedence, unless the information in mi is NULL.
@ -291,7 +291,10 @@ class MetaInformation(object):
setattr(self, attr, val)
if mi.tags:
self.tags += mi.tags
if replace_tags:
self.tags = mi.tags
else:
self.tags += mi.tags
self.tags = list(set(self.tags))
if mi.author_sort_map:

View File

@ -324,6 +324,7 @@ def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
whitespace_pat = re.compile(r'\s+')
def sort_func(x, y):
def cleanup_title(s):
s = s.strip().lower()
s = prefix_pat.sub(' ', s)

View File

@ -45,6 +45,7 @@ class AddSave(QTabWidget, Ui_TabWidget):
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
self.opt_add_formats_to_existing.setChecked(prefs['add_formats_to_existing'])
self.preserve_user_collections.setChecked(prefs['preserve_user_collections'])
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
self.save_template.initialize('save_to_disk', opts.template, help)
self.send_template.initialize('send_to_device', opts.send_template, help)
@ -71,6 +72,7 @@ class AddSave(QTabWidget, Ui_TabWidget):
prefs['filename_pattern'] = pattern
prefs['swap_author_names'] = bool(self.opt_swap_author_names.isChecked())
prefs['add_formats_to_existing'] = bool(self.opt_add_formats_to_existing.isChecked())
prefs['preserve_user_collections'] = bool(self.preserve_user_collections.isChecked())
return True

View File

@ -51,7 +51,7 @@
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_add_formats_to_existing">
<property name="toolTip">
<string>If an existing book with a similar title and author is found that does not have the format being added, the format is added
<string>If an existing book with a similar title and author is found that does not have the format being added, the format is added
to the existing book, instead of creating a new entry. If the existing book already has the format, then it is silently ignored.
Title match ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;, &quot;an&quot;), punctuation, case, etc. Author match is exact.</string>
@ -179,7 +179,31 @@ Title match ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;,
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<widget class="QCheckBox" name="preserve_user_collections">
<property name="text">
<string>Preserve device collections.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_41">
<property name="text">
<string>If checked, collections will not be deleted even if a book with changed metadata is resent and the collection is not in the book's metadata. In addition, editing collections in the device view will be enabled. If unchecked, collections will be always reflect only the metadata in the calibre library.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_42">
<property name="text">
<string> </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_43">
<property name="text">
<string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences-&gt;Plugins</string>
</property>

View File

@ -7,6 +7,36 @@ from PyQt4.QtGui import QDialog, QListWidgetItem
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
from calibre.gui2 import question_dialog, error_dialog
class ListWidgetItem(QListWidgetItem):
def __init__(self, txt):
QListWidgetItem.__init__(self, txt)
self.old_value = txt
self.cur_value = txt
def data(self, role):
if role == Qt.DisplayRole:
if self.old_value != self.cur_value:
return _('%s (was %s)'%(self.cur_value, self.old_value))
else:
return self.cur_value
elif role == Qt.EditRole:
return self.cur_value
else:
return QListWidgetItem.data(self, role)
def setData(self, role, data):
if role == Qt.EditRole:
self.cur_value = data.toString()
QListWidgetItem.setData(self, role, data)
def text(self):
return self.cur_value
def setText(self, txt):
self.cur_value = txt
QListWidgetItem.setText(txt)
class TagListEditor(QDialog, Ui_TagListEditor):
def __init__(self, window, tag_to_match, data, compare):
@ -21,7 +51,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
for k,v in data:
self.all_tags[v] = k
for tag in sorted(self.all_tags.keys(), cmp=compare):
item = QListWidgetItem(tag)
item = ListWidgetItem(tag)
item.setData(Qt.UserRole, self.all_tags[tag])
self.available_tags.addItem(item)

View File

@ -16,7 +16,7 @@ from calibre.gui2 import NONE, config, UNDEFINED_QDATE
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import tweaks
from calibre.utils.config import tweaks, prefs
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.utils.search_query_parser import SearchQueryParser
@ -928,11 +928,12 @@ class DeviceBooksModel(BooksModel): # {{{
if index.isValid() and self.editable:
cname = self.column_map[index.column()]
if cname in ('title', 'authors') or \
(cname == 'collections' and self.db.supports_collections()):
(cname == 'collections' and \
self.db.supports_collections() and \
prefs['preserve_user_collections']):
flags |= Qt.ItemIsEditable
return flags
def search(self, text, reset=True):
if not text or not text.strip():
self.map = list(range(len(self.db)))

View File

@ -15,7 +15,7 @@ from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
from calibre.utils.config import tweaks
from calibre.utils.config import tweaks, prefs
from calibre.gui2 import error_dialog, gprefs
from calibre.gui2.library import DEFAULT_SORT
@ -500,7 +500,9 @@ class DeviceBooksView(BooksView): # {{{
self.setAcceptDrops(False)
def contextMenuEvent(self, event):
self.edit_collections_menu.setVisible(self._model.db.supports_collections())
self.edit_collections_menu.setVisible(
self._model.db.supports_collections() and \
prefs['preserve_user_collections'])
self.context_menu.popup(event.globalPos())
event.accept()

View File

@ -473,6 +473,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
self.search_restriction.setEnabled(False)
for action in list(self.delete_menu.actions())[1:]:
action.setEnabled(False)
# Reset the view in case something changed while it was invisible
self.current_view().reset()
self.set_number_of_books_shown()

View File

@ -325,6 +325,10 @@ Post any output you see in a help message on the `Forum <http://www.mobileread.c
|app| is not starting on OS X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One common cause of failures on OS X is the use of accessibility technologies that are incompatible with the graphics toolkit |app| uses.
Try turning off VoiceOver if you have it on. Also go to System Preferences->System->Universal Access and turn off the setting for enabling
access for assistive devices in all the tabs.
You can obtain debug output about why |app| is not starting by running `Console.app`. Debug output will
be printed to it. If the debug output contains a line that looks like::
@ -334,9 +338,9 @@ then the problem is probably a corrupted font cache. You can clear the cache by
`instructions <http://www.macworld.com/article/139383/2009/03/fontcacheclear.html>`_. If that doesn't
solve it, look for a corrupted font file on your system, in ~/Library/Fonts or the like.
My antivirus program claims |app| is a virus/trojan?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Your antivirus program is wrong. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing |app|, disable it temporarily, install |app| and then re-enable it.
How do I use purchased EPUB books with |app|?

View File

@ -698,6 +698,8 @@ def _prefs():
# calibre server can execute searches
c.add_opt('saved_searches', default={}, help=_('List of named saved searches'))
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
c.add_opt('preserve_user_collections', default=True,
help=_('Preserve all collections even if not in library metadata.'))
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
return c