Merge from trunk

This commit is contained in:
Starson17 2010-07-18 16:25:05 -04:00
commit be42c00f88
48 changed files with 26503 additions and 20441 deletions

View File

@ -4,6 +4,72 @@
# for important features/bug fixes.
# Also, each release can have new and improved recipes.
- version: 0.7.9
date: 2010-07-17
new features:
- title: "New unified toolbar"
type: major
description: >
"A new unified toolbar combines the old toolbar and device display, to save space. Now when a device is connected, buttons
are created in the unified toolbar for the device and its storage cards. Click the arrow next to the button to eject the device."
- title: "Device drivers: Add option to allow calibre to automatically manage metadata on the device in Preferences->Add/Save->Sending to device"
- title: "BibTeX output for catalogs. The list of books in calibre can now also be output as a .bib file"
- title: "A new toolbar button to choose/create different calibre libraries. Be careful using it if you also use custom columns."
- title: "Support for the MiBuk"
bug fixes:
- title: "MOBI metadata: Replace HTML entities in the title read from the MOBI file"
- title: "Conversion pipeline: Handle elements with percentage sizes that are children of zero size parents correctly."
tickets: [6155]
- title: "Fix regression that made LRF conversion less robust"
tickets: [6180]
- title: "FB2 Input: Handle embedded images correctly, so that EPUB generated from FB2 works with Adobe Digital Editions."
tickets: [6183]
- title: "Fix regression that prevented old news from being deleted in the calibre library if calibre is never kept running for more than an hour"
- title: "RTF Input: Fix handling of text align and superscript/subscripts"
tickets: [3644,5060]
- title: "Fix long series or publisher names causing convert dialog to become too wide"
- title: "SONY driver: Fix handling of invalid XML databases with null bytes"
tickets: [6165]
- title: "iTunes driver: Better series_index sorting"
- title: "Improved editing of dates for custom columns"
- title: "Linux USB scanner: Don't fail to start calibre if SYFS is not present. Instead simply fail to detect devices"
tickets: [6156]
- title: "Android driver: Show books on device if Aldiko is being used"
tickets: [6100]
- title: "Upgrade to Qt 4.6.3 in all binary builds to ensure proper rendering of the new toolbar icons"
- title: "Fix handling of entities in epub files by the epub-fix command"
tickets: [6136]
new recipes:
- title: "EL Pain Impresso"
author: Darko Miletic
- title: "MIT Technology Review, Alternet, Waco Tribune Herald and Orlando Sentinel"
author: rty
improved recipes:
- Google Reader
- version: 0.7.8
date: 2010-07-09

View File

@ -440,6 +440,9 @@ xml_entity_to_unicode = partial(entity_to_unicode, result_exceptions = {
'>' : '>',
'&' : '&'})
def replace_entities(raw):
return _ent_pat.sub(entity_to_unicode, raw)
def prepare_string_for_xml(raw, attribute=False):
raw = _ent_pat.sub(entity_to_unicode, raw)
raw = raw.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.7.8'
__version__ = '0.7.9'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re

View File

@ -60,6 +60,9 @@ class FB2Input(InputFormatPlugin):
transform = etree.XSLT(styledoc)
result = transform(doc)
for img in result.xpath('//img[@src]'):
src = img.get('src')
img.set('src', self.binary_map.get(src, src))
open('index.xhtml', 'wb').write(transform.tostring(result))
stream.seek(0)
mi = get_metadata(stream, 'fb2')
@ -83,9 +86,15 @@ class FB2Input(InputFormatPlugin):
return os.path.join(os.getcwd(), 'metadata.opf')
def extract_embedded_content(self, doc):
self.binary_map = {}
for elem in doc.xpath('./*'):
if 'binary' in elem.tag and elem.attrib.has_key('id'):
ct = elem.get('content-type', '')
fname = elem.attrib['id']
ext = ct.rpartition('/')[-1].lower()
if ext in ('png', 'jpeg', 'jpg'):
fname += '.' + ext
self.binary_map[elem.get('id')] = fname
data = b64decode(elem.text.strip())
open(fname, 'wb').write(data)

View File

@ -368,7 +368,15 @@ class LRFInput(InputFormatPlugin):
if options.verbose > 2:
open('lrs.xml', 'wb').write(xml.encode('utf-8'))
parser = etree.XMLParser(no_network=True, huge_tree=True)
doc = etree.fromstring(xml, parser=parser)
try:
doc = etree.fromstring(xml, parser=parser)
except:
self.log.warn('Failed to parse XML. Trying to recover')
parser = etree.XMLParser(no_network=True, huge_tree=True,
recover=True)
doc = etree.fromstring(xml, parser=parser)
char_button_map = {}
for x in doc.xpath('//CharButton[@refobj]'):
ro = x.get('refobj')

View File

@ -14,7 +14,8 @@ except ImportError:
from lxml import html, etree
from calibre import xml_entity_to_unicode, CurrentDir, entity_to_unicode
from calibre import xml_entity_to_unicode, CurrentDir, entity_to_unicode, \
replace_entities
from calibre.utils.filenames import ascii_filename
from calibre.utils.date import parse_date
from calibre.ptempfile import TemporaryDirectory
@ -70,7 +71,7 @@ class EXTHHeader(object):
#else:
# print 'unknown record', id, repr(content)
if title:
self.mi.title = title
self.mi.title = replace_entities(title)
def process_metadata(self, id, content, codec):
if id == 100:

View File

@ -475,7 +475,8 @@ class Style(object):
value = float(m.group(1))
unit = m.group(2)
if unit == '%':
base = base or self.width
if base is None:
base = self.width
result = (value / 100.0) * base
elif unit == 'px':
result = value * 72.0 / self._profile.dpi

View File

@ -58,7 +58,7 @@
<item row="9" column="0" colspan="2">
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
</item>
<item row="10" column="0">
<item row="10" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>

View File

@ -43,12 +43,6 @@
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="minimumContentsLength">
<number>30</number>
</property>

View File

@ -10,7 +10,7 @@ import os
from PyQt4.Qt import QDialog
from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_dir
from calibre.gui2 import error_dialog, choose_dir, warning_dialog
from calibre.constants import filesystem_encoding
from calibre import isbytestring, patheq
from calibre.utils.config import prefs
@ -24,6 +24,7 @@ class ChooseLibrary(QDialog, Ui_Dialog):
self.db = db
self.new_db = None
self.callback = callback
self.location.initialize('choose_library_dialog')
lp = db.library_path
if isbytestring(lp):
@ -61,6 +62,12 @@ class ChooseLibrary(QDialog, Ui_Dialog):
return True
def perform_action(self, ac, loc):
if ac in ('new', 'existing'):
warning_dialog(self.parent(), _('Custom columns'),
_('If you use custom columns and they differ between '
'libraries, you will have various problems. Best '
'to ensure you have the same custom columns in each '
'library.'), show=True)
if ac in ('new', 'existing'):
prefs['library_path'] = loc
self.callback(loc)
@ -79,4 +86,5 @@ class ChooseLibrary(QDialog, Ui_Dialog):
loc):
return
QDialog.accept(self)
self.location.save_history()
self.perform_action(action, loc)

View File

@ -18,7 +18,7 @@
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
<item row="0" column="0" colspan="4">
<widget class="QLabel" name="old_location">
<property name="text">
<string>Your calibre library is currently located at {0}</string>
@ -38,14 +38,7 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="location">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<item row="4" column="0" colspan="4">
<widget class="QRadioButton" name="existing_library">
<property name="text">
<string>Use &amp;existing library at the new location</string>
@ -55,21 +48,21 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<item row="5" column="0" colspan="3">
<widget class="QRadioButton" name="empty_library">
<property name="text">
<string>&amp;Create an empty library at the new location</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<item row="6" column="0" colspan="3">
<widget class="QRadioButton" name="move_library">
<property name="text">
<string>&amp;Move current library to new location</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="8" column="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -79,7 +72,7 @@
</property>
</widget>
</item>
<item row="7" column="1">
<item row="7" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -118,7 +111,7 @@
</property>
</spacer>
</item>
<item row="2" column="2">
<item row="2" column="3">
<widget class="QToolButton" name="browse_button">
<property name="text">
<string>...</string>
@ -129,8 +122,18 @@
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="HistoryLineEdit" name="location"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>HistoryLineEdit</class>
<extends>QComboBox</extends>
<header>calibre/gui2/widgets.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>

View File

@ -60,7 +60,7 @@
<item>
<widget class="QPushButton" name="stop_all_jobs_button">
<property name="text">
<string>Stop &amp;all jobs</string>
<string>Stop &amp;all non device jobs</string>
</property>
</widget>
</item>

View File

@ -10,7 +10,7 @@ Scheduler for automated recipe downloads
from datetime import timedelta
from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \
QAction, QIcon, QMutex, QTimer
QAction, QIcon, QMutex, QTimer, pyqtSignal
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
from calibre.gui2.search_box import SearchBox2
@ -203,6 +203,9 @@ class Scheduler(QObject):
INTERVAL = 1 # minutes
delete_old_news = pyqtSignal(object)
start_recipe_fetch = pyqtSignal(object)
def __init__(self, parent, db):
QObject.__init__(self, parent)
self.internet_connection_failed = False
@ -225,20 +228,23 @@ class Scheduler(QObject):
self.download_all_scheduled)
self.timer = QTimer(self)
self.timer.start(int(self.INTERVAL * 60000))
self.timer.start(int(self.INTERVAL * 60 * 1000))
self.oldest_timer = QTimer()
self.connect(self.oldest_timer, SIGNAL('timeout()'), self.oldest_check)
self.connect(self.timer, SIGNAL('timeout()'), self.check)
self.oldest = gconf['oldest_news']
self.oldest_timer.start(int(60 * 60000))
self.oldest_check()
self.oldest_timer.start(int(60 * 60 * 1000))
QTimer.singleShot(5 * 1000, self.oldest_check)
self.database_changed = self.recipe_model.database_changed
def oldest_check(self):
if self.oldest > 0:
delta = timedelta(days=self.oldest)
ids = self.recipe_model.db.tags_older_than(_('News'), delta)
if ids:
self.emit(SIGNAL('delete_old_news(PyQt_PyObject)'), ids)
ids = list(ids)
if ids:
self.delete_old_news.emit(ids)
def show_dialog(self, *args):
self.lock.lock()
@ -282,7 +288,7 @@ class Scheduler(QObject):
'urn':urn,
}
self.download_queue.add(urn)
self.emit(SIGNAL('start_recipe_fetch(PyQt_PyObject)'), arg)
self.start_recipe_fetch.emit(arg)
finally:
self.lock.unlock()

View File

@ -21,6 +21,7 @@ from calibre.gui2.widgets import ComboBoxWithHelp
from calibre import human_readable
from calibre.utils.config import prefs
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2.dialogs.scheduler import Scheduler
ICON_SIZE = 48
@ -83,7 +84,7 @@ class LocationManager(QObject): # {{{
ac('library', _('Library'), 'lt.png',
_('Show books in calibre library'))
ac('main', _('Main'), 'reader.svg',
ac('main', _('Reader'), 'reader.svg',
_('Show books in the main memory of the device'))
ac('carda', _('Card A'), 'sd.svg',
_('Show books in storage card A'))
@ -307,7 +308,7 @@ class Action(QAction):
class MainWindowMixin(object):
def __init__(self):
def __init__(self, db):
self.device_connected = None
self.setObjectName('MainWindow')
self.setWindowIcon(QIcon(I('library.png')))
@ -323,6 +324,7 @@ class MainWindowMixin(object):
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
self.location_manager = LocationManager(self)
self.init_scheduler(db)
all_actions = self.setup_actions()
self.search_bar = SearchBar(self)
@ -334,6 +336,11 @@ class MainWindowMixin(object):
l = self.centralwidget.layout()
l.addWidget(self.search_bar)
def init_scheduler(self, db):
self.scheduler = Scheduler(self, db)
self.scheduler.start_recipe_fetch.connect(
self.download_scheduled_recipe, type=Qt.QueuedConnection)
def read_toolbar_settings(self):
pass
@ -392,6 +399,10 @@ class MainWindowMixin(object):
ac(-1, -1, 0, 'books_with_the_same_tags', _('Books with the same tags'),
'tags.svg')
self.action_news.setMenu(self.scheduler.news_menu)
self.action_news.triggered.connect(
self.scheduler.show_dialog)
self.action_help.triggered.connect(self.show_help)
md = QMenu()
md.addAction(_('Edit metadata individually'),

View File

@ -218,9 +218,10 @@ class BooksView(QTableView): # {{{
# Only save if we have been initialized (set_database called)
if len(self.column_map) > 0 and self.was_restored:
state = self.get_state()
db = getattr(self.model(), 'db', None)
name = unicode(self.objectName())
if name:
gprefs.set(name + ' books view state', state)
if name and db is not None:
db.prefs.set(name + ' books view state', state)
def cleanup_sort_history(self, sort_history):
history = []
@ -298,11 +299,27 @@ class BooksView(QTableView): # {{{
old_state['column_sizes'][name] += 12
return old_state
def restore_state(self):
def get_old_state(self):
ans = None
name = unicode(self.objectName())
old_state = None
if name:
old_state = gprefs.get(name + ' books view state', None)
name += ' books view state'
db = getattr(self.model(), 'db', None)
if db is not None:
ans = db.prefs.get(name, None)
if ans is None:
ans = gprefs.get(name, None)
try:
del gprefs[name]
except:
pass
if ans is not None:
db.prefs[name] = ans
return ans
def restore_state(self):
old_state = self.get_old_state()
if old_state is None:
old_state = self.get_default_state()

View File

@ -27,7 +27,6 @@ from calibre.gui2 import error_dialog, GetMetadata, open_local_file, \
gprefs, max_available_height, config, info_dialog
from calibre.gui2.cover_flow import CoverFlowMixin
from calibre.gui2.widgets import ProgressIndicator
from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import UpdateMixin
from calibre.gui2.main_window import MainWindow
from calibre.gui2.layout import MainWindowMixin
@ -119,7 +118,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.another_instance_wants_to_talk)
self.check_messages_timer.start(1000)
MainWindowMixin.__init__(self)
MainWindowMixin.__init__(self, db)
# Jobs Button {{{
self.job_manager = JobManager()
@ -253,18 +252,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
db, server_config().parse())
self.test_server_timer = QTimer.singleShot(10000, self.test_server)
self.scheduler = Scheduler(self, self.library_view.model().db)
self.action_news.setMenu(self.scheduler.news_menu)
self.connect(self.action_news, SIGNAL('triggered(bool)'),
self.scheduler.show_dialog)
self.connect(self.scheduler, SIGNAL('delete_old_news(PyQt_PyObject)'),
self.library_view.model().delete_books_by_id,
Qt.QueuedConnection)
self.connect(self.scheduler,
SIGNAL('start_recipe_fetch(PyQt_PyObject)'),
self.download_scheduled_recipe, Qt.QueuedConnection)
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
AddAction.__init__(self)
@ -272,6 +259,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.finalize_layout()
self.donate_button.start_animation()
self.scheduler.delete_old_news.connect(
self.library_view.model().delete_books_by_id,
type=Qt.QueuedConnection)
def resizeEvent(self, ev):
MainWindow.resizeEvent(self, ev)
self.search.setMaximumWidth(self.width()-150)
@ -402,6 +394,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.search.clear_to_help()
self.book_details.reset_info()
self.library_view.model().count_changed()
self.scheduler.database_changed(db)
prefs['library_path'] = self.library_path
def show_book_info(self, *args):

View File

@ -19,6 +19,7 @@ from calibre.library.schema_upgrades import SchemaUpgrade
from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.library.prefs import DBPrefs
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
@ -140,6 +141,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.initialize_dynamic()
def initialize_dynamic(self):
self.prefs = DBPrefs(self)
self.conn.executescript('''
DROP TRIGGER IF EXISTS author_insert_trg;
CREATE TEMP TRIGGER author_insert_trg

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import json
from calibre.constants import preferred_encoding
from calibre.utils.config import to_json, from_json
class DBPrefs(dict):
def __init__(self, db):
dict.__init__(self)
self.db = db
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
val = self.raw_to_object(val)
dict.__setitem__(self, key, val)
def raw_to_object(self, raw):
if not isinstance(raw, unicode):
raw = raw.decode(preferred_encoding)
return json.loads(raw, object_hook=from_json)
def to_raw(self, val):
return json.dumps(val, indent=2, default=to_json)
def __getitem__(self, key):
return dict.__getitem__(self, key)
def __delitem__(self, key):
dict.__delitem__(self, key)
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
self.db.conn.commit()
def __setitem__(self, key, val):
raw = self.to_raw(val)
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,
raw))
self.db.conn.commit()
dict.__setitem__(self, key, val)
def set(self, key, val):
self.__setitem__(key, val)

View File

@ -387,3 +387,14 @@ class SchemaUpgrade(object):
self.conn.execute('UPDATE authors SET sort=author_to_author_sort(name)')
def upgrade_version_12(self):
'DB based preference store'
script = '''
DROP TABLE IF EXISTS preferences;
CREATE TABLE preferences(id INTEGER PRIMARY KEY,
key TEXT NON NULL,
val TEXT NON NULL,
UNIQUE(key));
'''
self.conn.executescript(script)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,7 @@ and save it to a new file.
"""
import ctypes, sys, os
import ctypes, sys, os, glob
from ctypes import util
iswindows = 'win32' in sys.platform or 'win64' in sys.platform
isosx = 'darwin' in sys.platform
@ -85,7 +85,8 @@ elif iswindows:
_lib = flib if isfrozen else 'CORE_RL_wand_'
else:
if isfrozen:
_lib = os.path.join(sys.frozen_path, 'libMagickWand.so.2')
_lib = glob.glob(os.path.join(sys.frozen_path,
'libMagickWand.so.*'))[-1]
else:
_lib = util.find_library('MagickWand')
if _lib is None:

View File

@ -6,15 +6,16 @@ __docformat__ = 'restructuredtext en'
'''
Manage application-wide preferences.
'''
import os, re, cPickle, textwrap, traceback, plistlib, json, base64
import os, re, cPickle, textwrap, traceback, plistlib, json, base64, datetime
from copy import deepcopy
from functools import partial
from optparse import OptionParser as _OptionParser
from optparse import IndentedHelpFormatter
from collections import defaultdict
from calibre.constants import terminal_controller, iswindows, isosx, \
__appname__, __version__, __author__, plugins
from calibre.utils.lock import LockError, ExclusiveFile
from collections import defaultdict
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY'])
@ -632,27 +633,34 @@ class XMLConfig(dict):
f.truncate()
f.write(raw)
def to_json(obj):
if isinstance(obj, bytearray):
return {'__class__': 'bytearray',
'__value__': base64.standard_b64encode(bytes(obj))}
if isinstance(obj, datetime.datetime):
from calibre.utils.date import isoformat
return {'__class__': 'datetime.datetime',
'__value__': isoformat(obj, as_utc=True)}
raise TypeError(repr(obj) + ' is not JSON serializable')
def from_json(obj):
if '__class__' in obj:
if obj['__class__'] == 'bytearray':
return bytearray(base64.standard_b64decode(obj['__value__']))
if obj['__class__'] == 'datetime.datetime':
from calibre.utils.date import parse_date
return parse_date(obj['__value__'], assume_utc=True)
return obj
class JSONConfig(XMLConfig):
EXTENSION = '.json'
def to_json(self, obj):
if isinstance(obj, bytearray):
return {'__class__': 'bytearray',
'__value__': base64.standard_b64encode(bytes(obj))}
raise TypeError(repr(obj) + ' is not JSON serializable')
def from_json(self, obj):
if '__class__' in obj:
if obj['__class__'] == 'bytearray':
return bytearray(base64.standard_b64decode(obj['__value__']))
return obj
def raw_to_object(self, raw):
return json.loads(raw.decode('utf-8'), object_hook=self.from_json)
return json.loads(raw.decode('utf-8'), object_hook=from_json)
def to_raw(self):
return json.dumps(self, indent=2, default=self.to_json)
return json.dumps(self, indent=2, default=to_json)
def __getitem__(self, key):
return dict.__getitem__(self, key)

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
import os, copy
from PyQt4.Qt import QAbstractItemModel, QVariant, Qt, QColor, QFont, QIcon, \
QModelIndex, SIGNAL
QModelIndex, SIGNAL, QMetaObject, pyqtSlot
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.gui2 import NONE
@ -131,6 +131,16 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
self.scheduler_config = SchedulerConfig()
self.do_refresh()
@pyqtSlot()
def do_database_change(self):
self.db = self.newdb
self.newdb = None
self.do_refresh()
def database_changed(self, db):
self.newdb = db
QMetaObject.invokeMethod(self, 'do_database_change', Qt.QueuedConnection)
def get_builtin_recipe(self, urn, download=True):
if download:
try: