def icon_map(): global _icon_map with _icon_map_lock: if _icon_map is None: _icon_map = category_icon_map.copy() custom_icons = JSONConfig('gui').get('tags_browser_category_icons', {}) for k, v in custom_icons.iteritems(): if os.access(os.path.join(config_dir, 'tb_icons', v), os.R_OK): _icon_map[k] = '_' + quote(v) _icon_map['file_type_icons'] = { k:'mimetypes/%s.png' % v for k, v in EXT_MAP.iteritems() } return _icon_map
def __init__(self, parent=None, config_name='shortcuts/main'): QObject.__init__(self, parent) self.config = JSONConfig(config_name) self.shortcuts = OrderedDict() self.keys_map = {} self.groups = {}
def __init__(self, *args, **kwargs): super(DJVUmaker, self).__init__(*args, **kwargs) self.prints = prints # Easer access because of Calibre load plugins instead of importing # Set default preferences for JSONConfig DEFAULT_STORE_VALUES = {} DEFAULT_STORE_VALUES['plugin_version'] = PLUGINVER DEFAULT_STORE_VALUES['postimport'] = False for item in self.REGISTERED_BACKENDS: DEFAULT_STORE_VALUES[item] = { 'flags' : [], 'installed' : False, 'version' : None} if 'djvudigital' in self.REGISTERED_BACKENDS: DEFAULT_STORE_VALUES['use_backend'] = 'djvudigital' else: raise Exception('No djvudigital backend.') # JSONConfig is a dict-like object, # if coresponding .json file has not a specific key, it's got from .defaults self.plugin_prefs = JSONConfig(os.path.join('plugins', PLUGINNAME)) self.plugin_prefs.defaults = DEFAULT_STORE_VALUES # make sure to create plugins/djvumaker.json # self.plugin_prefs.values() doesn't use self.plugin_prefs.__getitem__() # and returns real json, not defaults if not self.plugin_prefs.values(): for key, val in DEFAULT_STORE_VALUES.iteritems(): self.plugin_prefs[key] = val
def cache(self): if not hasattr(self, '_mr_cache'): from calibre.utils.config import JSONConfig self._mr_cache = JSONConfig('mobileread_get_books') self._mr_cache.file_path = os.path.join(cache_dir(), 'mobileread_get_books.json') self._mr_cache.refresh() return self._mr_cache
def __init__(self, database, book_id, connections): self._connections = connections book_path = database.field_for('path', book_id).replace('/', os.sep) self._prefs = JSONConfig(os.path.join(book_path, 'book_settings'), base_path=LIBRARY) self._prefs.setdefault('asin', '') self._prefs.setdefault('goodreads_url', '') self._prefs.setdefault('aliases', {}) self._prefs.setdefault('sample_xray', '') self._prefs.commit() self._title = database.field_for('title', book_id) self._author = ' & '.join(database.field_for('authors', book_id)) self._asin = self._prefs['asin'] if self._prefs['asin'] != '' else None self._goodreads_url = self._prefs['goodreads_url'] self._sample_xray = self._prefs['sample_xray'] if not self._asin: identifiers = database.field_for('identifiers', book_id) if 'mobi-asin' in identifiers.keys(): self._asin = database.field_for('identifiers', book_id)['mobi-asin'].decode('ascii') self._prefs['asin'] = self._asin else: self._asin = self.search_for_asin_on_amazon(self.title_and_author) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin if self._goodreads_url == '': url = None if self._asin: url = self.search_for_goodreads_url(self._asin) if not url and self._title != 'Unknown' and self._author != 'Unknown': url = self.search_for_goodreads_url(self.title_and_author) if url: self._goodreads_url = url self._prefs['goodreads_url'] = self._goodreads_url if not self._asin: self._asin = self.search_for_asin_on_goodreads(self._goodreads_url) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin self._aliases = self._prefs['aliases'] self.save()
def reload_cache(self): if not hasattr(self, 'cache'): from calibre.utils.config import JSONConfig self.cache = JSONConfig('fonts/scanner_cache') else: self.cache.refresh() if self.cache.get('version', None) != self.CACHE_VERSION: self.cache.clear() self.cached_fonts = self.cache.get('fonts', {})
def builtins_loaded(self): self.last_check_time = 0 self.version_map = {} self.cached_version_map = {} self.name_rmap = {} for key, val in self.iteritems(): prefix, name = val.__module__.rpartition('.')[0::2] if prefix == 'calibre.gui2.store.stores' and name.endswith('_plugin'): module = sys.modules[val.__module__] sv = getattr(module, 'store_version', None) if sv is not None: name = name.rpartition('_')[0] self.version_map[name] = sv self.name_rmap[name] = key self.cache_file = JSONConfig('store/plugin_cache') self.load_cache()
def __init__(self, db, book_id, aConnection, sConnection): self._db = db self._book_id = book_id self._aConnection = aConnection self._sConnection = sConnection book_path = self._db.field_for('path', book_id).replace('/', os.sep) self._prefs = JSONConfig(os.path.join(book_path, 'book_settings'), base_path=self.LIBRARY) self._prefs.setdefault('asin', '') self._prefs.setdefault('shelfari_url', '') self._prefs.setdefault('aliases', {}) self._prefs.commit() self._title = self._db.field_for('title', book_id) self._author = ' & '.join(self._db.field_for('authors', self._book_id)) self.asin = self._prefs['asin'] if self.asin == '': identifiers = self._db.field_for('identifiers', self._book_id) self.asin = self._db.field_for('identifiers', self._book_id)['mobi-asin'].decode('ascii') if 'mobi-asin' in identifiers.keys() else None if not self.asin: self.asin = self.get_asin() if self.asin: self._prefs['asin'] = self.asin self.shelfari_url = self._prefs['shelfari_url'] if self.shelfari_url == '': url = '' if self._prefs['asin'] != '': url = self.search_shelfari(self._prefs['asin']) if url != '' and self.title != 'Unknown' and self.author != 'Unknown': url = self.search_shelfari(self.title_and_author) if url != '': self.shelfari_url = url self._prefs['shelfari_url'] = self.shelfari_url self._aliases = self._prefs['aliases'] if len(self._aliases.keys()) == 0 and self.shelfari_url != '': self.update_aliases() self.save()
from PyQt5.Qt import (QImage, Qt, QFont, QPainter, QPointF, QTextLayout, QTextOption, QFontMetrics, QTextCharFormat, QColor, QRect, QBrush, QLinearGradient, QPainterPath, QPen, QRectF, QTransform, QRadialGradient) from calibre import force_unicode, fit_image from calibre.constants import __appname__, __version__ from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.gui2 import ensure_app, config, load_builtin_fonts, pixmap_to_data from calibre.utils.cleantext import clean_ascii_chars, clean_xml_chars from calibre.utils.config import JSONConfig # Default settings {{{ cprefs = JSONConfig('cover_generation') cprefs.defaults['title_font_size'] = 120 # px cprefs.defaults['subtitle_font_size'] = 80 # px cprefs.defaults['footer_font_size'] = 80 # px cprefs.defaults['cover_width'] = 1200 # px cprefs.defaults['cover_height'] = 1600 # px cprefs.defaults['title_font_family'] = None cprefs.defaults['subtitle_font_family'] = None cprefs.defaults['footer_font_family'] = None cprefs.defaults['color_themes'] = {} cprefs.defaults['disabled_color_themes'] = [] cprefs.defaults['disabled_styles'] = [] cprefs.defaults['title_template'] = '<b>{title}' cprefs.defaults[ 'subtitle_template'] = '''{series:'test($, strcat("<i>", $, "</i> - ", raw_field("formatted_series_index")), "")'}''' cprefs.defaults['footer_template'] = r'''program:
from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' __copyright__ = '2013, 2014, Jellby <*****@*****.**>' __docformat__ = 'restructuredtext en' try: from PyQt5.Qt import Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QCheckBox, QComboBox, QGroupBox except ImportError: from PyQt4.Qt import Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QCheckBox, QComboBox, QGroupBox from calibre.utils.config import JSONConfig from calibre_plugins.prince_pdf.texteditwithtooltip import TextEditWithTooltip load_translations() prefs = JSONConfig('plugins/prince_pdf') # Set defaults prefs.defaults['prince_exe'] = 'prince' prefs.defaults['formats'] = ['epub','azw3','htmlz'] prefs.defaults['add_book'] = True prefs.defaults['show_CSS'] = False prefs.defaults['default_CSS'] = _('default') prefs.defaults['custom_CSS_list'] = {_('default'):'''/* @font-face { font-family: serif; src: local("Droid Serif") } */ @page {
from calibre import sanitize_file_name from calibre.ebooks.conversion.plugins.pdf_output import PAPER_SIZES from calibre.gui2 import ( Application, choose_save_file, dynamic, elided_text, error_dialog, open_local_file ) from calibre.gui2.widgets import PaperSizes from calibre.gui2.widgets2 import Dialog from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import JSONConfig from calibre.utils.filenames import expanduser from calibre.utils.ipc.simple_worker import start_pipe_worker from calibre.utils.serialize import msgpack_dumps, msgpack_loads vprefs = JSONConfig('viewer') class PrintDialog(Dialog): OUTPUT_NAME = 'print-to-pdf-choose-file' def __init__(self, book_title, parent=None, prefs=vprefs): self.book_title = book_title self.default_file_name = sanitize_file_name(book_title[:75] + '.pdf') self.paper_size_map = {a:getattr(QPageSize, a.capitalize()) for a in PAPER_SIZES} Dialog.__init__(self, _('Print to PDF'), 'print-to-pdf', prefs=prefs, parent=parent) def setup_ui(self): self.vl = vl = QVBoxLayout(self) self.l = l = QFormLayout()
class BookSettings(object): '''Holds book specific settings''' def __init__(self, database, book_id, connections): self._connections = connections book_path = database.field_for('path', book_id).replace('/', os.sep) self._prefs = JSONConfig(os.path.join(book_path, 'book_settings'), base_path=LIBRARY) self._prefs.setdefault('asin', '') self._prefs.setdefault('goodreads_url', '') self._prefs.setdefault('aliases', {}) self._prefs.setdefault('sample_xray', '') self._prefs.commit() self._title = database.field_for('title', book_id) self._author = ' & '.join(database.field_for('authors', book_id)) self._asin = self._prefs['asin'] if self._prefs['asin'] != '' else None self._goodreads_url = self._prefs['goodreads_url'] self._sample_xray = self._prefs['sample_xray'] if not self._asin: identifiers = database.field_for('identifiers', book_id) if 'mobi-asin' in identifiers.keys(): self._asin = database.field_for('identifiers', book_id)['mobi-asin'].decode('ascii') self._prefs['asin'] = self._asin else: self._asin = self.search_for_asin_on_amazon(self.title_and_author) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin if self._goodreads_url == '': url = None if self._asin: url = self.search_for_goodreads_url(self._asin) if not url and self._title != 'Unknown' and self._author != 'Unknown': url = self.search_for_goodreads_url(self.title_and_author) if url: self._goodreads_url = url self._prefs['goodreads_url'] = self._goodreads_url if not self._asin: self._asin = self.search_for_asin_on_goodreads(self._goodreads_url) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin self._aliases = self._prefs['aliases'] self.save() @property def prefs(self): return self._prefs @property def asin(self): return self._asin @asin.setter def asin(self, val): self._asin = val @property def sample_xray(self): return self._sample_xray @sample_xray.setter def sample_xray(self, value): self._sample_xray = value @property def title(self): return self._title @property def author(self): return self._author @property def title_and_author(self): return '{0} - {1}'.format(self._title, self._author) @property def goodreads_url(self): return self._goodreads_url @goodreads_url.setter def goodreads_url(self, val): self._goodreads_url = val @property def aliases(self): return self._aliases def set_aliases(self, label, aliases): '''Sets label's aliases to aliases''' # 'aliases' is a string containing a comma separated list of aliases. # Split it, remove whitespace from each element, drop empty strings (strangely, # split only does this if you don't specify a separator) # so "" -> [] "foo,bar" and " foo , bar " -> ["foo", "bar"] aliases = [x.strip() for x in aliases.split(",") if x.strip()] self._aliases[label] = aliases def save(self): '''Saves current settings in book's settings file''' self._prefs['asin'] = self._asin self._prefs['goodreads_url'] = self._goodreads_url self._prefs['aliases'] = self._aliases self._prefs['sample_xray'] = self._sample_xray def search_for_asin_on_amazon(self, query): '''Search for book's asin on amazon using given query''' query = urlencode({'keywords': query}) url = '/s/ref=sr_qz_back?sf=qz&rh=i%3Adigital-text%2Cn%3A154606011%2Ck%3A' + query[9:] + '&' + query try: response = open_url(self._connections['amazon'], url) except PageDoesNotExist: return None # check to make sure there are results if ('did not match any products' in response and 'Did you mean:' not in response and 'so we searched in All Departments' not in response): return None soup = BeautifulSoup(response) results = soup.findAll('div', {'id': 'resultsCol'}) if not results: return None for result in results: if 'Buy now with 1-Click' in str(result): asin_search = AMAZON_ASIN_PAT.search(str(result)) if asin_search: return asin_search.group(1) return None def search_for_goodreads_url(self, keywords): '''Searches for book's goodreads url using given keywords''' query = urlencode({'q': keywords}) try: response = open_url(self._connections['goodreads'], '/search?' + query) except PageDoesNotExist: return None # check to make sure there are results if 'No results' in response: return None urlsearch = GOODREADS_URL_PAT.search(response) if not urlsearch: return None # return the full URL with the query parameters removed url = 'https://www.goodreads.com' + urlsearch.group(1) return urlparse.urlparse(url)._replace(query=None).geturl() def search_for_asin_on_goodreads(self, url): '''Searches for ASIN of book at given url''' book_id_search = BOOK_ID_PAT.search(url) if not book_id_search: return None book_id = book_id_search.group(1) try: response = open_url(self._connections['goodreads'], '/buttons/glide/' + book_id) except PageDoesNotExist: return None book_asin_search = GOODREADS_ASIN_PAT.search(response) if not book_asin_search: return None return book_asin_search.group(1) def update_aliases(self, source, source_type='url'): if source_type.lower() == 'url': self.update_aliases_from_url(source) return if source_type.lower() == 'asc': self.update_aliases_from_asc(source) return if source_type.lower() == 'json': self.update_aliases_from_json(source) def update_aliases_from_asc(self, filename): '''Gets aliases from sample x-ray file and expands them if users settings say to do so''' cursor = connect(filename).cursor() characters = {x[1]: [x[1]] for x in cursor.execute('SELECT * FROM entity').fetchall() if x[3] == 1} self._aliases = {} for alias, fullname in auto_expand_aliases(characters).items(): if fullname not in self._aliases.keys(): self._aliases[fullname] = [alias] continue self._aliases[fullname].append(alias) def update_aliases_from_json(self, filename): '''Gets aliases from json file''' data = json.load(open(filename)) self._aliases = {name: char['aliases'] for name, char in data['characters'].items()} if 'characters' in data else {} if 'settings' in data: self._aliases.update({name: setting['aliases'] for name, setting in data['settings'].items()}) def update_aliases_from_url(self, url): '''Gets aliases from Goodreads and expands them if users settings say to do so''' try: goodreads_parser = GoodreadsParser(url, self._connections['goodreads'], self._asin) goodreads_chars = goodreads_parser.get_characters(1) goodreads_settings = goodreads_parser.get_settings(len(goodreads_chars)) except PageDoesNotExist: goodreads_chars = {} goodreads_settings = {} self._aliases = {} for char_data in goodreads_chars.values() + goodreads_settings.values(): if char_data['label'] not in self._aliases.keys(): self._aliases[char_data['label']] = char_data['aliases']
'Paranormal > Vampires': ['Vampires'], 'War': ['War'], 'Western': ['Western'], 'Language > Writing': ['Writing'], 'Writing > Essays': ['Writing'], 'Young Adult': ['Young Adult'], } DEFAULT_STORE_VALUES = { KEY_GET_EDITIONS: False, KEY_GET_ALL_AUTHORS: False, KEY_GENRE_MAPPINGS: copy.deepcopy(DEFAULT_GENRE_MAPPINGS) } # This is where all preferences for this plugin will be stored plugin_prefs = JSONConfig('plugins/Shelfari') # Set defaults plugin_prefs.defaults[STORE_NAME] = DEFAULT_STORE_VALUES class GenreTagMappingsTableWidget(QTableWidget): def __init__(self, parent, all_tags): QTableWidget.__init__(self, parent) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.tags_values = all_tags def populate_table(self, tag_mappings): self.clear() self.setAlternatingRowColors(True) self.setRowCount(len(tag_mappings))
''' Configuration for the Comicvine metadata source ''' from PyQt4.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit from calibre.utils.config import JSONConfig try: import pycomicvine #pylint: disable=F0401 except ImportError: from calibre_plugins.comicvine import pycomicvine_dist as pycomicvine PREFS = JSONConfig('plugins/comicvine') PREFS.defaults['api_key'] = '' PREFS.defaults['worker_threads'] = '16' pycomicvine.api_key = PREFS['api_key'] class ConfigWidget(QWidget): 'Configuration widget' def __init__(self): QWidget.__init__(self) self.layout = QHBoxLayout() self.setLayout(self.layout) self.key_label = QLabel('&api key:') self.layout.addWidget(self.key_label) self.key_msg = QLineEdit(self) self.key_msg.setText(PREFS['api_key']) self.layout.addWidget(self.key_msg) self.key_label.setBuddy(self.key_msg)
import cPickle, os, glob, shutil from collections import namedtuple from operator import attrgetter from itertools import chain from calibre.constants import plugins, config_dir from calibre.utils.config import JSONConfig from calibre.utils.localization import get_lang, canonicalize_lang, get_system_locale DictionaryLocale = namedtuple("DictionaryLocale", "langcode countrycode") Dictionary = namedtuple("Dictionary", "primary_locale locales dicpath affpath builtin name id") LoadedDictionary = namedtuple("Dictionary", "primary_locale locales obj builtin name id") hunspell = plugins["hunspell"][0] if hunspell is None: raise RuntimeError("Failed to load hunspell: %s" % plugins[1]) dprefs = JSONConfig("dictionaries/prefs.json") dprefs.defaults["preferred_dictionaries"] = {} dprefs.defaults["preferred_locales"] = {} dprefs.defaults["user_dictionaries"] = [{"name": _("Default"), "is_active": True, "words": []}] not_present = object() class UserDictionary(object): __slots__ = ("name", "is_active", "words") def __init__(self, **kwargs): self.name = kwargs["name"] self.is_active = kwargs["is_active"] self.words = {(w, langcode) for w, langcode in kwargs["words"]}
def __init__(self, *args, **kw): # This has to be loaded on instantiation as it can be shared by # multiple processes self.PREFS_OBJECT = JSONConfig(self.PREFS_OBJECT_NAME) RulesDialogBase.__init__(self, *args, **kw)
} UPDATE_OPTIONS_DEFAULTS = { KEY_DO_UPDATE_CHECK: False, KEY_LAST_FIRMWARE_CHECK_TIME: 0, KEY_DO_EARLY_FIRMWARE_CHECK: False } BACKUP_OPTIONS_DEFAULTS = { KEY_DO_DAILY_BACKUP: False, KEY_BACKUP_COPIES_TO_KEEP: 5, KEY_BACKUP_DEST_DIRECTORY: '' } # This is where all preferences for this plugin will be stored plugin_prefs = JSONConfig('plugins/Sony Utilities') # Set defaults plugin_prefs.defaults[BOOKMARK_OPTIONS_STORE_NAME] = BOOKMARK_OPTIONS_DEFAULTS plugin_prefs.defaults[METADATA_OPTIONS_STORE_NAME] = METADATA_OPTIONS_DEFAULTS plugin_prefs.defaults[READING_OPTIONS_STORE_NAME] = READING_OPTIONS_DEFAULTS plugin_prefs.defaults[COMMON_OPTIONS_STORE_NAME] = COMMON_OPTIONS_DEFAULTS plugin_prefs.defaults[DISMISSTILES_OPTIONS_STORE_NAME] = DISMISSTILES_OPTIONS_DEFAULTS plugin_prefs.defaults[FIXDUPLICATESHELVES_OPTIONS_STORE_NAME] = FIXDUPLICATESHELVES_OPTIONS_DEFAULTS plugin_prefs.defaults[ORDERSERIESSHELVES_OPTIONS_STORE_NAME] = ORDERSERIESSHELVES_OPTIONS_DEFAULTS plugin_prefs.defaults[STORE_LIBRARIES] = {} plugin_prefs.defaults[UPDATE_OPTIONS_STORE_NAME] = UPDATE_OPTIONS_DEFAULTS plugin_prefs.defaults[BACKUP_OPTIONS_STORE_NAME] = BACKUP_OPTIONS_DEFAULTS try:
from calibre.gui2.metadata.config import ConfigWidget as DefaultConfigWidget from calibre.utils.config import JSONConfig STORE_NAME = 'Options' # KEY_MAX_PAGES = 'maxPages' KEY_MAX_DOWNLOADS = 'maxDownloads' KEY_GET_ADDITIONAL_INFO = 'getAdditionalInfo' KEY_THRESHOLD = 'threshold' KEY_TRY_EXCHANGING = 'tryExchanging' DEFAULT_STORE_VALUES = { KEY_MAX_DOWNLOADS: 1, } # This is where all preferences for this plugin will be stored plugin_prefs = JSONConfig('plugins/GoogleScholar') # Set defaults plugin_prefs.defaults[STORE_NAME] = DEFAULT_STORE_VALUES def getOption(option_key): default_value = DEFAULT_STORE_VALUES[option_key] return plugin_prefs[STORE_NAME].get(option_key, default_value) class ConfigWidget(DefaultConfigWidget): def __init__(self, plugin): DefaultConfigWidget.__init__(self, plugin) c = plugin_prefs[STORE_NAME]
library_config = old_prefs[library_id] set_library_config(library_config) del old_prefs[library_id] if library_config is None: #print("get prefs from db") library_config = db.prefs.get_namespaced(PREFS_NAMESPACE, PREFS_KEY_SETTINGS, copy.deepcopy(default_prefs)) return library_config # This is where all preferences for this plugin *were* stored # Remember that this name (i.e. plugins/epubsplit) is also # in a global namespace, so make it as unique as possible. # You should always prefix your config file name with plugins/, # so as to ensure you dont accidentally clobber a calibre config file old_prefs = JSONConfig('plugins/EpubSplit') # fake out so I don't have to change the prefs calls anywhere. The # Java programmer in me is offended by op-overloading, but it's very # tidy. class PrefsFacade(): def __init__(self,default_prefs): self.default_prefs = default_prefs self.libraryid = None self.current_prefs = None def _get_prefs(self): libraryid = get_library_uuid(get_gui().current_db) if self.current_prefs == None or self.libraryid != libraryid: #print("self.current_prefs == None(%s) or self.libraryid != libraryid(%s)"%(self.current_prefs == None,self.libraryid != libraryid)) self.libraryid = libraryid
class SpanDivEdit(Tool): name = 'SpanDivEdit' #: If True the user can choose to place this tool in the plugins toolbar allowed_in_toolbar = True #: If True the user can choose to place this tool in the plugins menu allowed_in_menu = True def create_action(self, for_toolbar=True): self.plugin_prefs = JSONConfig('plugins/{0}_SpanDivEdit'.format(PLUGIN_SAFE_NAME)) self.plugin_prefs.defaults['parse_current'] = True # Create an action, this will be added to the plugins toolbar and # the plugins menu ac = QAction(get_icon('images/spandivedit_icon.png'), _('Edit Spans && Divs'), self.gui) self.restore_prefs() if not for_toolbar: # Register a keyboard shortcut for this toolbar action. We only # register it for the action created for the menu, not the toolbar, # to avoid a double trigger self.register_shortcut(ac, 'edit-spans-divs', default_keys=('Ctrl+Shift+Alt+E',)) else: menu = QMenu() ac.setMenu(menu) checked_menu_item = menu.addAction(_('Edit current file only'), self.toggle_parse_current) checked_menu_item.setCheckable(True) checked_menu_item.setChecked(self.parse_current) menu.addSeparator() config_menu_item = menu.addAction(_('Customize'), self.show_configuration) ac.triggered.connect(self.dispatcher) return ac def toggle_parse_current(self): self.parse_current = not self.parse_current self.save_prefs() def dispatcher(self): container = self.current_container # The book being edited as a container object if not container: return info_dialog(self.gui, _('No book open'), _('Need to have a book open first.'), show=True) if self.parse_current: name = editor_name(self.gui.central.current_editor) if not name or container.mime_map[name] not in OEB_DOCS: return info_dialog(self.gui, _('Cannot Process'), _('No file open for editing or the current file is not an (x)html file.'), show=True) self.cleanasawhistle = True self.changed_files = [] from calibre_plugins.diaps_toolbag.dialogs import RemoveDialog dlg = RemoveDialog(self.gui) if dlg.exec_(): criteria = dlg.getCriteria() # Ensure any in progress editing the user is doing is present in the container self.boss.commit_all_editors_to_container() self.boss.add_savepoint(_('Before: Span Div Edit')) try: self.process_files(criteria) except Exception: # Something bad happened report the error to the user import traceback error_dialog(self.gui, _('Failed'), _('Failed to process divs or spans, click "Show details" for more info'), det_msg=traceback.format_exc(), show=True) # Revert to the saved restore point self.boss.revert_requested(self.boss.global_undo.previous_container) else: if not self.cleanasawhistle: # Show the user what changes we have made, # allowing then to revert them if necessary accepted = ResultsDialog(self.gui, self.changed_files).exec_() if accepted == QDialog.Accepted: self.boss.show_current_diff() # Update the editor UI to take into account all the changes we # have made self.boss.apply_container_update_to_gui() else: info_dialog(self.gui, _('Nothing changed'), '<p>{0}'.format(_('Nothing matching your criteria was found.')), show=True) def process_files(self, criteria): container = self.current_container # The book being edited as a container object if self.parse_current: name = editor_name(self.gui.central.current_editor) data = container.raw_data(name) htmlstr = self.delete_modify(data, criteria) if htmlstr != data: self.cleanasawhistle = False container.open(name, 'wb').write(htmlstr) else: from calibre_plugins.diaps_toolbag.dialogs import ShowProgressDialog d = ShowProgressDialog(self.gui, container, OEB_DOCS, criteria, self.delete_modify, _('Parsing')) self.cleanasawhistle = d.clean self.changed_files.extend(d.changed_files) def delete_modify(self, data, criteria): _parser = MarkupParser(data, srch_str=criteria[0], srch_method=criteria[1], tag=criteria[2], attrib=criteria[3], action=criteria[4], new_tag=criteria[5], new_str=criteria[6], copy=criteria[7]) htmlstr = _parser.processml() return htmlstr def show_configuration(self): from calibre_plugins.diaps_toolbag.span_div_config import ConfigWidget dlg = ConfigWidget(self.gui) if dlg.exec_(): pass def restore_prefs(self): self.parse_current = self.plugin_prefs.get('parse_current') def save_prefs(self): self.plugin_prefs['parse_current'] = self.parse_current
class CSScm2em(Tool): name = 'CSScm2em' #: If True the user can choose to place this tool in the plugins toolbar allowed_in_toolbar = True #: If True the user can choose to place this tool in the plugins menu allowed_in_menu = True def create_action(self, for_toolbar=True): self.plugin_prefs = JSONConfig('plugins/{0}_CSScm2em'.format(PLUGIN_SAFE_NAME)) self.plugin_prefs.defaults['parse_current'] = True # Create an action, this will be added to the plugins toolbar and # the plugins menu ac = QAction(get_icon('images/css_icon.png'), _('Convert CSS cm to em'), self.gui) self.restore_prefs() if not for_toolbar: # Register a keyboard shortcut for this toolbar action. We only # register it for the action created for the menu, not the toolbar, # to avoid a double trigger self.register_shortcut(ac, 'css-cms-to-ems', default_keys=('Ctrl+Shift+Alt+C',)) menu = QMenu() ac.setMenu(menu) checked_menu_item = menu.addAction(_('Convert current CSS file only'), self.toggle_parse_current) checked_menu_item.setCheckable(True) checked_menu_item.setChecked(self.parse_current) ac.triggered.connect(self.dispatcher) return ac def toggle_parse_current(self): self.parse_current = not self.parse_current self.save_prefs() def dispatcher(self): container = self.current_container if not container: return info_dialog(self.gui, _('No book open'), _('Need to have a book open first.'), show=True) if self.parse_current: name = editor_name(self.gui.central.current_editor) if not name or container.mime_map[name] not in OEB_STYLES: return info_dialog(self.gui, _('Cannot Process'), _('No file open for editing or the current file is not a CSS file.'), show=True) self.cleanasawhistle = True self.changed_files = [] self.boss.commit_all_editors_to_container() self.boss.add_savepoint(_('Before: Convert CM to EM')) try: self.process_files() except Exception: # Something bad happened report the error to the user import traceback error_dialog(self.gui, _('Failed'), _('Failed to convert CMs to EMs, click "Show details" for more info'), det_msg=traceback.format_exc(), show=True) # Revert to the saved restore point self.boss.revert_requested(self.boss.global_undo.previous_container) else: if not self.cleanasawhistle: # Show the user what changes we have made, # allowing then to revert them if necessary accepted = ResultsDialog(self.gui, self.changed_files).exec_() if accepted == QDialog.Accepted: self.boss.show_current_diff() # Update the editor UI to take into account all the changes we # have made self.boss.apply_container_update_to_gui() else: info_dialog(self.gui, _('Nothing Converted'), '<p>{0}'.format(_('No CM dimensions were found to convert.')), show=True) def process_files(self): def cssparse(sheet): SHEET_DIRTY = False for rule in sheet.cssRules: if rule.type in (rule.STYLE_RULE, rule.PAGE_RULE): for property in rule.style: val = property.propertyValue for x in val: if x.type == 'DIMENSION' and x.dimension == 'cm': SHEET_DIRTY = True x.cssText = self.convertcm2em(x.value) + 'em' if SHEET_DIRTY: self.cleanasawhistle = False self.changed_files.append(name) container.dirty(name) container = self.current_container # The book being edited as a container object if self.parse_current: name = editor_name(self.gui.central.current_editor) sheet = container.parsed(name) cssparse(sheet) else: for name, mt in container.mime_map.iteritems(): if mt in OEB_STYLES: sheet = container.parsed(name) cssparse(sheet) def restore_prefs(self): self.parse_current = self.plugin_prefs.get('parse_current') def save_prefs(self): self.plugin_prefs['parse_current'] = self.parse_current def convertcm2em(self, value): conv=2.37106301584 return '%.2f' % (conv*float(value))
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit from calibre.utils.config import JSONConfig # This is where all preferences for this plugin will be stored # You should always prefix your config file name with plugins/, # so as to ensure you dont accidentally clobber a calibre config file prefs = JSONConfig('plugins/K4MobiDeDRM') # Set defaults prefs.defaults['pids'] = "" prefs.defaults['serials'] = "" prefs.defaults['WINEPREFIX'] = None class ConfigWidget(QWidget): def __init__(self): QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)') self.l.addWidget(self.serialLabel) self.serials = QLineEdit(self) self.serials.setText(prefs['serials']) self.l.addWidget(self.serials) self.serialLabel.setBuddy(self.serials) self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)')
QApplication, QDialog, QUrl, QFont, QFontDatabase, QLocale, QFontInfo) ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' from calibre import prints from calibre.constants import (islinux, iswindows, isbsd, isfrozen, isosx, plugins, config_dir, filesystem_encoding, isxp) from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig from calibre.ebooks.metadata import MetaInformation from calibre.utils.date import UNDEFINED_DATE from calibre.utils.localization import get_lang from calibre.utils.filenames import expanduser from calibre.utils.file_type_icons import EXT_MAP # Setup gprefs {{{ gprefs = JSONConfig('gui') defs = gprefs.defaults native_menubar_defaults = { 'action-layout-menubar': ( 'Add Books', 'Edit Metadata', 'Convert Books', 'Choose Library', 'Save To Disk', 'Preferences', 'Help', ), 'action-layout-menubar-device': ( 'Add Books', 'Edit Metadata', 'Convert Books', 'Location Manager', 'Send To Device', 'Save To Disk', 'Preferences', 'Help', ) }
def migrate_previous_viewer_prefs(): new_prefs = vprefs if new_prefs['old_prefs_migrated']: return old_vprefs = JSONConfig('viewer') old_prefs = JSONConfig('viewer.py') with new_prefs: sd = new_prefs['session_data'] fs = sd.get('standalone_font_settings', {}) for k in ('serif', 'sans', 'mono'): defval = 'Liberation ' + k.capitalize() k += '_family' if old_prefs.get(k) and old_prefs[k] != defval: fs[k] = old_prefs[k] if old_prefs.get('standard_font') in ('serif', 'sans', 'mono'): fs['standard_font'] = old_prefs['standard_font'] if old_prefs.get('minimum_font_size' ) is not None and old_prefs['minimum_font_size'] != 8: fs['minimum_font_size'] = old_prefs['minimum_font_size'] sd['standalone_font_settings'] = fs ms = sd.get('standalone_misc_settings', {}) ms['remember_window_geometry'] = bool( old_prefs.get('remember_window_size', False)) ms['remember_last_read'] = bool( old_prefs.get('remember_current_page', True)) ms['save_annotations_in_ebook'] = bool( old_prefs.get('copy_bookmarks_to_file', True)) ms['singleinstance'] = bool(old_vprefs.get('singleinstance', False)) sd['standalone_misc_settings'] = ms for k in ('top', 'bottom'): v = old_prefs.get(k + '_margin') if v != 20 and v is not None: sd['margin_' + k] = v v = old_prefs.get('side_margin') if v is not None and v != 40: sd['margin_left'] = sd['margin_right'] = v // 2 if old_prefs.get('user_css'): sd['user_stylesheet'] = old_prefs['user_css'] cps = {'portrait': 0, 'landscape': 0} cp = old_prefs.get('cols_per_screen_portrait') if cp and cp > 1: cps['portrait'] = cp cl = old_prefs.get('cols_per_screen_landscape') if cl and cl > 1: cps['landscape'] = cp if cps['portrait'] or cps['landscape']: sd['columns_per_screen'] = cps if old_vprefs.get('in_paged_mode') is False: sd['read_mode'] = 'flow' new_prefs.set('session_data', sd) new_prefs.set('old_prefs_migrated', True)
class FontScanner(Thread): CACHE_VERSION = 2 def __init__(self, folders=[], allowed_extensions={'ttf', 'otf'}): Thread.__init__(self) self.folders = folders + font_dirs() + [ os.path.join(config_dir, 'fonts'), P('fonts/liberation') ] self.folders = [ os.path.normcase(os.path.abspath(f)) for f in self.folders ] self.font_families = () self.allowed_extensions = allowed_extensions # API {{{ def find_font_families(self): self.join() return self.font_families def fonts_for_family(self, family): ''' Return a list of the faces belonging to the specified family. The first face is the "Regular" face of family. Each face is a dictionary with many keys, the most important of which are: path, font-family, font-weight, font-style, font-stretch. The font-* properties follow the CSS 3 Fonts specification. ''' self.join() try: return self.font_family_map[icu_lower(family)] except KeyError: raise NoFonts('No fonts found for the family: %r' % family) def legacy_fonts_for_family(self, family): ''' Return a simple set of regular, bold, italic and bold-italic faces for the specified family. Returns a dictionary with each element being a 2-tuple of (path to font, full font name) and the keys being: normal, bold, italic, bi. ''' ans = {} try: faces = self.fonts_for_family(family) except NoFonts: return ans for i, face in enumerate(faces): if i == 0: key = 'normal' elif face['font-style'] in {'italic', 'oblique'}: key = 'bi' if face['font-weight'] == 'bold' else 'italic' elif face['font-weight'] == 'bold': key = 'bold' else: continue ans[key] = (face['path'], face['full_name']) return ans def get_font_data(self, font_or_path): path = font_or_path if isinstance(font_or_path, dict): path = font_or_path['path'] with lopen(path, 'rb') as f: return f.read() def find_font_for_text(self, text, allowed_families={'serif', 'sans-serif'}, preferred_families=('serif', 'sans-serif', 'monospace', 'cursive', 'fantasy')): ''' Find a font on the system capable of rendering the given text. Returns a font family (as given by fonts_for_family()) that has a "normal" font and that can render the supplied text. If no such font exists, returns None. :return: (family name, faces) or None, None ''' from calibre.utils.fonts.utils import (supports_text, panose_to_css_generic_family, get_printable_characters) if not isinstance(text, unicode_type): raise TypeError(u'%r is not unicode' % text) text = get_printable_characters(text) found = {} def filter_faces(font): try: raw = self.get_font_data(font) return supports_text(raw, text) except: pass return False for family in self.find_font_families(): faces = list(filter(filter_faces, self.fonts_for_family(family))) if not faces: continue generic_family = panose_to_css_generic_family(faces[0]['panose']) if generic_family in allowed_families or generic_family == preferred_families[ 0]: return (family, faces) elif generic_family not in found: found[generic_family] = (family, faces) for f in preferred_families: if f in found: return found[f] return None, None # }}} def reload_cache(self): if not hasattr(self, 'cache'): from calibre.utils.config import JSONConfig self.cache = JSONConfig('fonts/scanner_cache') else: self.cache.refresh() if self.cache.get('version', None) != self.CACHE_VERSION: self.cache.clear() self.cached_fonts = self.cache.get('fonts', {}) def run(self): self.do_scan() def do_scan(self): self.reload_cache() if isworker: # Dont scan font files in worker processes, use whatever is # cached. Font files typically dont change frequently enough to # justify a rescan in a worker process. self.build_families() return cached_fonts = self.cached_fonts.copy() self.cached_fonts.clear() for folder in self.folders: if not os.path.isdir(folder): continue try: files = tuple(walk(folder)) except EnvironmentError as e: if DEBUG: prints('Failed to walk font folder:', folder, as_unicode(e)) continue for candidate in files: if (candidate.rpartition('.')[-1].lower() not in self.allowed_extensions or not os.path.isfile(candidate)): continue candidate = os.path.normcase(os.path.abspath(candidate)) try: s = os.stat(candidate) except EnvironmentError: continue fileid = '{0}||{1}:{2}'.format(candidate, s.st_size, s.st_mtime) if fileid in cached_fonts: # Use previously cached metadata, since the file size and # last modified timestamp have not changed. self.cached_fonts[fileid] = cached_fonts[fileid] continue try: self.read_font_metadata(candidate, fileid) except Exception as e: if DEBUG: prints('Failed to read metadata from font file:', candidate, as_unicode(e)) continue if frozenset(cached_fonts) != frozenset(self.cached_fonts): # Write out the cache only if some font files have changed self.write_cache() self.build_families() def build_families(self): self.font_family_map, self.font_families = build_families( self.cached_fonts, self.folders) def write_cache(self): with self.cache: self.cache['version'] = self.CACHE_VERSION self.cache['fonts'] = self.cached_fonts def force_rescan(self): self.cached_fonts = {} self.write_cache() def read_font_metadata(self, path, fileid): with lopen(path, 'rb') as f: try: fm = FontMetadata(f) except UnsupportedFont: self.cached_fonts[fileid] = {} else: data = fm.to_dict() data['path'] = path self.cached_fonts[fileid] = data def dump_fonts(self): self.join() for family in self.font_families: prints(family) for font in self.fonts_for_family(family): prints('\t%s: %s' % (font['full_name'], font['path'])) prints(end='\t') for key in ('font-stretch', 'font-weight', 'font-style'): prints('%s: %s' % (key, font[key]), end=' ') prints() prints( '\tSub-family:', font['wws_subfamily_name'] or font['preferred_subfamily_name'] or font['subfamily_name']) prints() prints()
#!/usr/bin/env python2 # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' __copyright__ = '2016, Samreen Zarroug & Alex Mayer' __docformat__ = 'restructuredtext en' from PyQt5.Qt import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QGroupBox from calibre.utils.config import JSONConfig prefs = JSONConfig('plugins/xray_creator') # Set defaults prefs.defaults['spoilers'] = False prefs.defaults['send_to_device'] = True prefs.defaults['create_xray_when_sending'] = True prefs.defaults['mobi'] = True prefs.defaults['azw3'] = True class ConfigWidget(QWidget): def __init__(self): QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) self.spoilers = QCheckBox('Use spoilers when creating x-ray') self.spoilers.setChecked(prefs['spoilers']) self.l.addWidget(self.spoilers)
#!/usr/bin/env python # vim:fileencoding=utf-8 from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' from calibre.utils.config import JSONConfig tprefs = JSONConfig('tweak_book_gui') tprefs.defaults['editor_theme'] = None tprefs.defaults['editor_font_family'] = None tprefs.defaults['editor_font_size'] = 12 tprefs.defaults['editor_line_wrap'] = True tprefs.defaults['preview_refresh_time'] = 2 _current_container = None def current_container(): return _current_container def set_current_container(container): global _current_container _current_container = container def elided_text(font, text, width=200, mode=None): from PyQt4.Qt import QFontMetrics, Qt if mode is None: mode = Qt.ElideMiddle fm = QFontMetrics(font)
class MobileReadStore(BasicStoreConfig, StorePlugin): def __init__(self, *args, **kwargs): StorePlugin.__init__(self, *args, **kwargs) self.lock = Lock() @property def cache(self): if not hasattr(self, '_mr_cache'): from calibre.utils.config import JSONConfig self._mr_cache = JSONConfig('mobileread_get_books') self._mr_cache.file_path = os.path.join( cache_dir(), 'mobileread_get_books.json') self._mr_cache.refresh() return self._mr_cache def open(self, parent=None, detail_item=None, external=False): url = 'https://www.mobileread.com/' if external or self.config.get('open_external', False): open_url(QUrl(detail_item if detail_item else url)) else: if detail_item: d = WebStoreDialog(self.gui, url, parent, detail_item) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) d.exec_() else: self.update_cache(parent, 30) d = MobileReadStoreDialog(self, parent) d.setWindowTitle(self.name) d.exec_() def search(self, query, max_results=10, timeout=60): books = self.get_book_list() if not books: return sf = SearchFilter(books) matches = sf.parse(query.decode('utf-8', 'replace')) for book in matches: book.price = '$0.00' book.drm = SearchResult.DRM_UNLOCKED yield book def update_cache(self, parent=None, timeout=10, force=False, suppress_progress=False): if self.lock.acquire(False): try: update_thread = CacheUpdateThread(self.cache, self.seralize_books, timeout) if not suppress_progress: progress = CacheProgressDialog(parent) progress.set_message( _('Updating MobileRead book cache...')) update_thread.total_changed.connect(progress.set_total) update_thread.update_progress.connect( progress.set_progress) update_thread.update_details.connect(progress.set_details) progress.rejected.connect(update_thread.abort) progress.open() update_thread.start() while update_thread.is_alive() and not progress.canceled: QCoreApplication.processEvents() if progress.isVisible(): progress.accept() return not progress.canceled else: update_thread.start() finally: self.lock.release() def get_book_list(self): return self.deseralize_books(self.cache.get('book_list', [])) def seralize_books(self, books): sbooks = [] for b in books: data = {} data['author'] = b.author data['title'] = b.title data['detail_item'] = b.detail_item data['formats'] = b.formats sbooks.append(data) return sbooks def deseralize_books(self, sbooks): books = [] for s in sbooks: b = SearchResult() b.author = s.get('author', '') b.title = s.get('title', '') b.detail_item = s.get('detail_item', '') b.formats = s.get('formats', '') books.append(b) return books
QImage, Qt, QFont, QPainter, QPointF, QTextLayout, QTextOption, QFontMetrics, QTextCharFormat, QColor, QRect, QBrush, QLinearGradient, QPainterPath, QPen, QRectF, QTransform, QRadialGradient ) from calibre import force_unicode, fit_image from calibre.constants import __appname__, __version__ from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.gui2 import ensure_app, config, load_builtin_fonts, pixmap_to_data from calibre.utils.cleantext import clean_ascii_chars, clean_xml_chars from calibre.utils.config import JSONConfig # Default settings {{{ cprefs = JSONConfig('cover_generation') cprefs.defaults['title_font_size'] = 120 # px cprefs.defaults['subtitle_font_size'] = 80 # px cprefs.defaults['footer_font_size'] = 80 # px cprefs.defaults['cover_width'] = 1200 # px cprefs.defaults['cover_height'] = 1600 # px cprefs.defaults['title_font_family'] = None cprefs.defaults['subtitle_font_family'] = None cprefs.defaults['footer_font_family'] = None cprefs.defaults['color_themes'] = {} cprefs.defaults['disabled_color_themes'] = [] cprefs.defaults['disabled_styles'] = [] cprefs.defaults['title_template'] = '<b>{title}' cprefs.defaults['subtitle_template'] = '''{series:'test($, strcat("<i>", $, "</i> - ", raw_field("formatted_series_index")), "")'}''' cprefs.defaults['footer_template'] = r'''program: # Show at most two authors, on separate lines.
print_function) __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <*****@*****.**>' __docformat__ = 'restructuredtext en' from PyQt5.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit from calibre.utils.config import JSONConfig # This is where all preferences for this plugin will be stored # Remember that this name (i.e. plugins/interface_demo) is also # in a global namespace, so make it as unique as possible. # You should always prefix your config file name with plugins/, # so as to ensure you dont accidentally clobber a calibre config file prefs = JSONConfig('plugins/interface_demo') # Set defaults prefs.defaults['hello_world_msg'] = 'Hello, World!' class ConfigWidget(QWidget): def __init__(self): QWidget.__init__(self) self.l = QHBoxLayout() self.setLayout(self.l) self.label = QLabel('Hello world &message:') self.l.addWidget(self.label) self.msg = QLineEdit(self)
__license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <*****@*****.**>' __docformat__ = 'restructuredtext en' import re, threading from future_builtins import map from calibre import browser, random_user_agent from calibre.customize import Plugin from calibre.utils.config import JSONConfig from calibre.utils.titlecase import titlecase from calibre.utils.icu import capitalize, lower, upper from calibre.ebooks.metadata import check_isbn msprefs = JSONConfig('metadata_sources/global.json') msprefs.defaults['txt_comments'] = False msprefs.defaults['ignore_fields'] = [] msprefs.defaults['user_default_ignore_fields'] = [] msprefs.defaults['max_tags'] = 20 msprefs.defaults['wait_after_first_identify_result'] = 30 # seconds msprefs.defaults['wait_after_first_cover_result'] = 60 # seconds msprefs.defaults['swap_author_names'] = False msprefs.defaults['fewer_tags'] = True msprefs.defaults['find_first_edition_date'] = False # Google covers are often poor quality (scans/errors) but they have high # resolution, so they trump covers from better sources. So make sure they # are only used if no other covers are found. msprefs.defaults['cover_priorities'] = {'Google':2, 'Google Images':2, 'Big Book Search':2}
#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net> import os from calibre.constants import config_dir from calibre.utils.config import JSONConfig vprefs = JSONConfig('viewer-webengine') viewer_config_dir = os.path.join(config_dir, 'viewer') vprefs.defaults['session_data'] = {} vprefs.defaults['local_storage'] = {} vprefs.defaults['main_window_state'] = None vprefs.defaults['main_window_geometry'] = None vprefs.defaults['old_prefs_migrated'] = False vprefs.defaults['bookmarks_sort'] = 'title' vprefs.defaults['highlight_export_format'] = 'txt' vprefs.defaults['auto_update_lookup'] = True def get_session_pref(name, default=None, group='standalone_misc_settings'): sd = vprefs['session_data'] g = sd.get(group, {}) if group else sd return g.get(name, default)
print_function) __license__ = 'GPL v3' __copyright__ = '2014, Kenny Billiau <*****@*****.**>' __docformat__ = 'restructuredtext en' from PyQt4.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit from calibre.utils.config import JSONConfig # This is where all preferences for this plugin will be stored # Remember that this name (i.e. plugins/interface_demo) is also # in a global namespace, so make it as unique as possible. # You should always prefix your config file name with plugins/, # so as to ensure you dont accidentally clobber a calibre config file prefs = JSONConfig('plugins/opml') # Set defaults prefs.defaults['oldest_article'] = '7' prefs.defaults['max_articles'] = '100' class ConfigWidget(QWidget): def __init__(self): QWidget.__init__(self) self.l = QHBoxLayout() self.setLayout(self.l) self.oldest_articlel = QLabel('Oldest article:') self.l.addWidget(self.oldest_articlel)
from calibre import prints from calibre.constants import plugins, config_dir from calibre.spell import parse_lang_code from calibre.utils.config import JSONConfig from calibre.utils.icu import capitalize from calibre.utils.localization import get_lang, get_system_locale Dictionary = namedtuple( 'Dictionary', 'primary_locale locales dicpath affpath builtin name id') LoadedDictionary = namedtuple('Dictionary', 'primary_locale locales obj builtin name id') hunspell = plugins['hunspell'][0] if hunspell is None: raise RuntimeError('Failed to load hunspell: %s' % plugins['hunspell'][1]) dprefs = JSONConfig('dictionaries/prefs.json') dprefs.defaults['preferred_dictionaries'] = {} dprefs.defaults['preferred_locales'] = {} dprefs.defaults['user_dictionaries'] = [{ 'name': _('Default'), 'is_active': True, 'words': [] }] not_present = object() class UserDictionary(object): __slots__ = ('name', 'is_active', 'words') def __init__(self, **kwargs):
import textwrap from PyQt5.Qt import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QListWidget, QIcon, QSize, QComboBox, QLineEdit, QListWidgetItem, QStyledItemDelegate, QStaticText, Qt, QStyle, QToolButton, QInputDialog, QMenu, pyqtSignal) from calibre.ebooks.metadata.tag_mapper import map_tags, compile_pat from calibre.gui2 import error_dialog, Application, question_dialog from calibre.gui2.ui import get_gui from calibre.gui2.widgets2 import Dialog from calibre.utils.config import JSONConfig from calibre.utils.localization import localize_user_manual_link from polyglot.builtins import iteritems, unicode_type, range, filter tag_maps = JSONConfig('tag-map-rules') def intelligent_strip(action, val): ans = val.strip() if not ans and action == 'split': ans = ' ' return ans class QueryEdit(QLineEdit): def contextMenuEvent(self, ev): menu = self.createStandardContextMenu() self.parent().specialise_context_menu(menu) menu.exec_(ev.globalPos())
icon = QIcon(I('blank.png')) else: icon = QIcon(pmap) return icon, entry.get('name', entry.get('Name')) or _('Unknown') if iswindows: # Windows {{{ import subprocess from calibre.utils.open_with.windows import (load_icon_for_cmdline, load_icon_resource) from calibre.utils.winreg.default_programs import (find_programs, friendly_app_name) from calibre_extensions import winutil oprefs = JSONConfig('windows_open_with') def entry_sort_key(entry): return sort_key(entry.get('name') or '') def icon_for_entry(entry, delete_icon_resource=False, as_data=False): res = entry.pop( 'icon_resource', None) if delete_icon_resource else entry.get('icon_resource') if res is None: return load_icon_for_cmdline(entry['cmdline'], as_data=as_data) try: return load_icon_resource(res, as_data=as_data) except Exception: import traceback traceback.print_exc()
#!/usr/bin/env python # vim:fileencoding=utf-8 __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' import string from polyglot.builtins import iteritems, map from calibre.utils.config import JSONConfig from calibre.spell.dictionary import Dictionaries, parse_lang_code CONTAINER_DND_MIMETYPE = 'application/x-calibre-container-name-list' tprefs = JSONConfig('tweak_book_gui') d = tprefs.defaults d['editor_theme'] = None d['editor_font_family'] = None d['editor_font_size'] = 12 d['editor_line_wrap'] = True d['editor_tab_stop_width'] = 2 d['editor_show_char_under_cursor'] = True d['replace_entities_as_typed'] = True d['preview_refresh_time'] = 2 d['choose_tweak_fmt'] = True d['tweak_fmt_order'] = ['EPUB', 'AZW3'] d['update_metadata_from_calibre'] = True d['nestable_dock_widgets'] = False d['dock_top_left'] = 'horizontal' d['dock_top_right'] = 'horizontal'
if len(toc) == 0: return error_dialog( self, _('No items found'), _('No files were found that could be added to the Table of Contents.' ), show=True) self.insert_toc_fragment(toc) def undo(self): self.tocw.pop_history() # }}} te_prefs = JSONConfig('toc-editor') class TOCEditor(QDialog): # {{{ explode_done = pyqtSignal(object) writing_done = pyqtSignal(object) def __init__(self, pathtobook, title=None, parent=None, prefs=None): QDialog.__init__(self, parent) self.prefs = prefs or te_prefs self.pathtobook = pathtobook self.working = True t = title or os.path.basename(pathtobook) self.book_title = t
#!/usr/bin/env python2 __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' ''' Builtin recipes. ''' import re, time, io from calibre.web.feeds.news import (BasicNewsRecipe, CustomIndexRecipe, AutomaticNewsRecipe, CalibrePeriodical) from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.utils.config import JSONConfig basic_recipes = (BasicNewsRecipe, AutomaticNewsRecipe, CustomIndexRecipe, CalibrePeriodical) custom_recipes = JSONConfig('custom_recipes/index.json') def custom_recipe_filename(id_, title): from calibre.utils.filenames import ascii_filename return ascii_filename(title[:50]) + \ ('_%s.recipe'%id_) def compile_recipe(src): ''' Compile the code in src and return a recipe object, if found. :param src: Python source code as bytestring or unicode object :return: Recipe class or None, if no such class was found in src
from calibre.gui2 import choose_images, error_dialog, safe_open_url from calibre.gui2.webengine import (Bridge, RestartingWebEngineView, create_script, from_js, insert_scripts, secure_webengine, to_js) from calibre.srv.code import get_translations_data from calibre.utils.config import JSONConfig from calibre.utils.serialize import json_loads from polyglot.builtins import as_bytes, iteritems try: from PyQt5 import sip except ImportError: import sip SANDBOX_HOST = FAKE_HOST.rpartition('.')[0] + '.sandbox' vprefs = JSONConfig('viewer-webengine') viewer_config_dir = os.path.join(config_dir, 'viewer') vprefs.defaults['session_data'] = {} vprefs.defaults['local_storage'] = {} vprefs.defaults['main_window_state'] = None vprefs.defaults['main_window_geometry'] = None vprefs.defaults['old_prefs_migrated'] = False # Override network access to load data from the book {{{ def set_book_path(path, pathtoebook): set_book_path.pathtoebook = pathtoebook set_book_path.path = os.path.abspath(path) set_book_path.metadata = get_data('calibre-book-metadata.json')[0] set_book_path.manifest, set_book_path.manifest_mime = get_data(
from calibre.gui2.viewer.toc import TOC from calibre.gui2.widgets import ProgressIndicator from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files, info_dialog, error_dialog, open_url, setup_gui_option_parser) from calibre.ebooks.oeb.iterator.book import EbookIterator from calibre.ebooks import DRMError from calibre.constants import islinux, filesystem_encoding from calibre.utils.config import Config, StringConfig, JSONConfig from calibre.customize.ui import available_input_formats from calibre import as_unicode, force_unicode, isbytestring from calibre.ptempfile import reset_base_dir from calibre.utils.zipfile import BadZipfile from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1, get_lang vprefs = JSONConfig('viewer') dprefs = JSONConfig('viewer_dictionaries') dprefs.defaults['word_lookups'] = {} class Worker(Thread): def run(self): try: Thread.run(self) self.exception = self.traceback = None except BadZipfile: self.exception = _( 'This ebook is corrupted and cannot be opened. If you ' 'downloaded it from somewhere, try downloading it again.') self.traceback = '' except Exception as err:
class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): s_r_functions = {'' : lambda x: x, _('Lower Case') : lambda x: icu_lower(x), _('Upper Case') : lambda x: icu_upper(x), _('Title Case') : lambda x: titlecase(x), _('Capitalize') : lambda x: capitalize(x), } s_r_match_modes = [_('Character match'), _('Regular Expression'), ] s_r_replace_modes = [_('Replace field'), _('Prepend to field'), _('Append to field'), ] def __init__(self, window, rows, model, tab, refresh_books): ResizableDialog.__init__(self, window) Ui_MetadataBulkDialog.__init__(self) self.model = model self.db = model.db self.refresh_book_list.setChecked(gprefs['refresh_book_list_on_bulk_edit']) self.refresh_book_list.toggled.connect(self.save_refresh_booklist) self.ids = [self.db.id(r) for r in rows] self.first_title = self.db.title(self.ids[0], index_is_id=True) self.cover_clone.setToolTip(unicode(self.cover_clone.toolTip()) + ' (%s)' % self.first_title) self.box_title.setText('<p>' + _('Editing meta information for <b>%d books</b>') % len(rows)) self.write_series = False self.changed = False self.refresh_books = refresh_books self.comments = null self.comments_button.clicked.connect(self.set_comments) all_tags = self.db.all_tags() self.tags.update_items_cache(all_tags) self.remove_tags.update_items_cache(all_tags) self.initialize_combos() for f in sorted(self.db.all_formats()): self.remove_format.addItem(f) self.remove_format.setCurrentIndex(-1) self.series.currentIndexChanged[int].connect(self.series_changed) self.series.editTextChanged.connect(self.series_changed) self.tag_editor_button.clicked.connect(self.tag_editor) self.autonumber_series.stateChanged[int].connect(self.auto_number_changed) self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME) self.pubdate_cw = CalendarWidget(self.pubdate) self.pubdate.setCalendarWidget(self.pubdate_cw) pubdate_format = tweaks['gui_pubdate_display_format'] if pubdate_format is not None: self.pubdate.setDisplayFormat(pubdate_format) self.pubdate.setSpecialValueText(_('Undefined')) self.clear_pubdate_button.clicked.connect(self.clear_pubdate) self.pubdate.dateTimeChanged.connect(self.do_apply_pubdate) self.adddate.setDateTime(QDateTime.currentDateTime()) self.adddate.setMinimumDateTime(UNDEFINED_QDATETIME) adddate_format = tweaks['gui_timestamp_display_format'] if adddate_format is not None: self.adddate.setDisplayFormat(adddate_format) self.adddate.setSpecialValueText(_('Undefined')) self.clear_adddate_button.clicked.connect(self.clear_adddate) self.adddate.dateTimeChanged.connect(self.do_apply_adddate) if len(self.db.custom_field_keys(include_composites=False)) == 0: self.central_widget.removeTab(1) else: self.create_custom_column_editors() self.prepare_search_and_replace() self.button_box.clicked.connect(self.button_clicked) self.button_box.button(QDialogButtonBox.Apply).setToolTip(_( 'Immediately make all changes without closing the dialog. ' 'This operation cannot be canceled or undone')) self.do_again = False self.central_widget.setCurrentIndex(tab) geom = gprefs.get('bulk_metadata_window_geometry', None) if geom is not None: self.restoreGeometry(bytes(geom)) ct = gprefs.get('bulk_metadata_window_tab', 0) self.central_widget.setCurrentIndex(ct) self.languages.init_langs(self.db) self.languages.setEditText('') self.authors.setFocus(Qt.OtherFocusReason) self.exec_() def set_comments(self): from calibre.gui2.dialogs.comments_dialog import CommentsDialog d = CommentsDialog(self, '' if self.comments is null else (self.comments or ''), _('Comments')) if d.exec_() == d.Accepted: self.comments = d.textbox.html b = self.comments_button b.setStyleSheet('QPushButton { font-weight: bold }') if unicode(b.text())[-1] != '*': b.setText(unicode(b.text()) + ' *') def save_refresh_booklist(self, *args): gprefs['refresh_book_list_on_bulk_edit'] = bool(self.refresh_book_list.isChecked()) def save_state(self, *args): gprefs['bulk_metadata_window_geometry'] = \ bytearray(self.saveGeometry()) gprefs['bulk_metadata_window_tab'] = self.central_widget.currentIndex() def do_apply_pubdate(self, *args): self.apply_pubdate.setChecked(True) def clear_pubdate(self, *args): self.pubdate.setDateTime(UNDEFINED_QDATETIME) def do_apply_adddate(self, *args): self.apply_adddate.setChecked(True) def clear_adddate(self, *args): self.adddate.setDateTime(UNDEFINED_QDATETIME) def button_clicked(self, which): if which == self.button_box.button(QDialogButtonBox.Apply): self.do_again = True self.accept() # S&R {{{ def prepare_search_and_replace(self): self.search_for.initialize('bulk_edit_search_for') self.replace_with.initialize('bulk_edit_replace_with') self.s_r_template.initialize('bulk_edit_template') self.test_text.initialize('bulk_edit_test_test') self.all_fields = [''] self.writable_fields = [''] fm = self.db.field_metadata for f in fm: if (f in ['author_sort'] or (fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments'] and fm[f].get('search_terms', None) and f not in ['formats', 'ondevice', 'series_sort']) or (fm[f]['datatype'] in ['int', 'float', 'bool', 'datetime'] and f not in ['id', 'timestamp'])): self.all_fields.append(f) self.writable_fields.append(f) if fm[f]['datatype'] == 'composite': self.all_fields.append(f) self.all_fields.sort() self.all_fields.insert(1, '{template}') self.writable_fields.sort() self.search_field.setMaxVisibleItems(25) self.destination_field.setMaxVisibleItems(25) self.testgrid.setColumnStretch(1, 1) self.testgrid.setColumnStretch(2, 1) offset = 10 self.s_r_number_of_books = min(10, len(self.ids)) for i in range(1,self.s_r_number_of_books+1): w = QLabel(self.tabWidgetPage3) w.setText(_('Book %d:')%i) self.testgrid.addWidget(w, i+offset, 0, 1, 1) w = QLineEdit(self.tabWidgetPage3) w.setReadOnly(True) name = 'book_%d_text'%i setattr(self, name, w) self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 1, 1, 1) w = QLineEdit(self.tabWidgetPage3) w.setReadOnly(True) name = 'book_%d_result'%i setattr(self, name, w) self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 2, 1, 1) ident_types = sorted(self.db.get_all_identifier_types(), key=sort_key) self.s_r_dst_ident.setCompleter(QCompleter(ident_types)) try: self.s_r_dst_ident.setPlaceholderText(_('Enter an identifier type')) except: pass self.s_r_src_ident.addItems(ident_types) self.main_heading = _( '<b>You can destroy your library using this feature.</b> ' 'Changes are permanent. There is no undo function. ' 'You are strongly encouraged to back up your library ' 'before proceeding.<p>' 'Search and replace in text fields using character matching ' 'or regular expressions. ') self.character_heading = _( 'In character mode, the field is searched for the entered ' 'search text. The text is replaced by the specified replacement ' 'text everywhere it is found in the specified field. After ' 'replacement is finished, the text can be changed to ' 'upper-case, lower-case, or title-case. If the case-sensitive ' 'check box is checked, the search text must match exactly. If ' 'it is unchecked, the search text will match both upper- and ' 'lower-case letters' ) self.regexp_heading = _( 'In regular expression mode, the search text is an ' 'arbitrary python-compatible regular expression. The ' 'replacement text can contain backreferences to parenthesized ' 'expressions in the pattern. The search is not anchored, ' 'and can match and replace multiple times on the same string. ' 'The modification functions (lower-case etc) are applied to the ' 'matched text, not to the field as a whole. ' 'The destination box specifies the field where the result after ' 'matching and replacement is to be assigned. You can replace ' 'the text in the field, or prepend or append the matched text. ' 'See <a href="http://docs.python.org/library/re.html"> ' 'this reference</a> for more information on python\'s regular ' 'expressions, and in particular the \'sub\' function.' ) self.search_mode.addItems(self.s_r_match_modes) self.search_mode.setCurrentIndex(dynamic.get('s_r_search_mode', 0)) self.replace_mode.addItems(self.s_r_replace_modes) self.replace_mode.setCurrentIndex(0) self.s_r_search_mode = 0 self.s_r_error = None self.s_r_obj = None self.replace_func.addItems(sorted(self.s_r_functions.keys())) self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed) self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed) self.destination_field.currentIndexChanged[int].connect(self.s_r_destination_field_changed) self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results) self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results) self.search_for.editTextChanged[str].connect(self.s_r_paint_results) self.replace_with.editTextChanged[str].connect(self.s_r_paint_results) self.test_text.editTextChanged[str].connect(self.s_r_paint_results) self.comma_separated.stateChanged.connect(self.s_r_paint_results) self.case_sensitive.stateChanged.connect(self.s_r_paint_results) self.s_r_src_ident.currentIndexChanged[int].connect(self.s_r_identifier_type_changed) self.s_r_dst_ident.textChanged.connect(self.s_r_paint_results) self.s_r_template.lost_focus.connect(self.s_r_template_changed) self.central_widget.setCurrentIndex(0) self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive) self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_template.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_search_mode_changed(self.search_mode.currentIndex()) self.multiple_separator.setFixedWidth(30) self.multiple_separator.setText(' ::: ') self.multiple_separator.textChanged.connect(self.s_r_separator_changed) self.results_count.valueChanged[int].connect(self.s_r_display_bounds_changed) self.starting_from.valueChanged[int].connect(self.s_r_display_bounds_changed) self.save_button.clicked.connect(self.s_r_save_query) self.remove_button.clicked.connect(self.s_r_remove_query) self.queries = JSONConfig("search_replace_queries") self.saved_search_name = '' self.query_field.addItem("") self.query_field_values = sorted([q for q in self.queries], key=sort_key) self.query_field.addItems(self.query_field_values) self.query_field.currentIndexChanged[str].connect(self.s_r_query_change) self.query_field.setCurrentIndex(0) self.search_field.setCurrentIndex(0) self.s_r_search_field_changed(0) def s_r_sf_itemdata(self, idx): if idx is None: idx = self.search_field.currentIndex() return unicode(self.search_field.itemData(idx).toString()) def s_r_df_itemdata(self, idx): if idx is None: idx = self.destination_field.currentIndex() return unicode(self.destination_field.itemData(idx).toString()) def s_r_get_field(self, mi, field): if field: if field == '{template}': v = SafeFormat().safe_format( unicode(self.s_r_template.text()), mi, _('S/R TEMPLATE ERROR'), mi) return [v] fm = self.db.metadata_for_field(field) if field == 'sort': val = mi.get('title_sort', None) elif fm['datatype'] == 'datetime': val = mi.format_field(field)[1] else: val = mi.get(field, None) if isinstance(val, (int, float, bool)): val = str(val) elif fm['is_csp']: # convert the csp dict into a list id_type = unicode(self.s_r_src_ident.currentText()) if id_type: val = [val.get(id_type, '')] else: val = [u'%s:%s'%(t[0], t[1]) for t in val.iteritems()] if val is None: val = [] if fm['is_multiple'] else [''] elif not fm['is_multiple']: val = [val] elif fm['datatype'] == 'composite': val = [v2.strip() for v2 in val.split(fm['is_multiple']['ui_to_list'])] elif field == 'authors': val = [v2.replace('|', ',') for v2 in val] else: val = [] if not val: val = [''] return val def s_r_display_bounds_changed(self, i): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_template_changed(self): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_identifier_type_changed(self, idx): self.s_r_search_field_changed(self.search_field.currentIndex()) self.s_r_paint_results(idx) def s_r_search_field_changed(self, idx): self.s_r_template.setVisible(False) self.template_label.setVisible(False) self.s_r_src_ident_label.setVisible(False) self.s_r_src_ident.setVisible(False) if idx == 1: # Template self.s_r_template.setVisible(True) self.template_label.setVisible(True) elif self.s_r_sf_itemdata(idx) == 'identifiers': self.s_r_src_ident_label.setVisible(True) self.s_r_src_ident.setVisible(True) for i in range(0, self.s_r_number_of_books): w = getattr(self, 'book_%d_text'%(i+1)) mi = self.db.get_metadata(self.ids[i], index_is_id=True) src = self.s_r_sf_itemdata(idx) t = self.s_r_get_field(mi, src) if len(t) > 1: t = t[self.starting_from.value()-1: self.starting_from.value()-1 + self.results_count.value()] w.setText(unicode(self.multiple_separator.text()).join(t)) if self.search_mode.currentIndex() == 0: self.destination_field.setCurrentIndex(idx) else: self.s_r_destination_field_changed(self.destination_field.currentIndex()) self.s_r_paint_results(None) def s_r_destination_field_changed(self, idx): self.s_r_dst_ident_label.setVisible(False) self.s_r_dst_ident.setVisible(False) txt = self.s_r_df_itemdata(idx) if not txt: txt = self.s_r_sf_itemdata(None) if txt and txt in self.writable_fields: if txt == 'identifiers': self.s_r_dst_ident_label.setVisible(True) self.s_r_dst_ident.setVisible(True) self.destination_field_fm = self.db.metadata_for_field(txt) self.s_r_paint_results(None) def s_r_search_mode_changed(self, val): self.search_field.clear() self.destination_field.clear() if val == 0: for f in self.writable_fields: self.search_field.addItem(f if f != 'sort' else 'title_sort', f) self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) self.destination_field.setCurrentIndex(0) self.destination_field.setVisible(False) self.destination_field_label.setVisible(False) self.replace_mode.setCurrentIndex(0) self.replace_mode.setVisible(False) self.replace_mode_label.setVisible(False) self.comma_separated.setVisible(False) self.s_r_heading.setText('<p>'+self.main_heading + self.character_heading) else: self.search_field.blockSignals(True) self.destination_field.blockSignals(True) for f in self.all_fields: self.search_field.addItem(f if f != 'sort' else 'title_sort', f) for f in self.writable_fields: self.destination_field.addItem(f if f != 'sort' else 'title_sort', f) self.search_field.blockSignals(False) self.destination_field.blockSignals(False) self.destination_field.setVisible(True) self.destination_field_label.setVisible(True) self.replace_mode.setVisible(True) self.replace_mode_label.setVisible(True) self.comma_separated.setVisible(True) self.s_r_heading.setText('<p>'+self.main_heading + self.regexp_heading) self.s_r_paint_results(None) def s_r_separator_changed(self, txt): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_set_colors(self): if self.s_r_error is not None: col = 'rgb(255, 0, 0, 20%)' self.test_result.setText(self.s_r_error.message) else: col = 'rgb(0, 255, 0, 20%)' self.test_result.setStyleSheet('QLineEdit { color: black; ' 'background-color: %s; }'%col) for i in range(0,self.s_r_number_of_books): getattr(self, 'book_%d_result'%(i+1)).setText('') def s_r_func(self, match): rfunc = self.s_r_functions[unicode(self.replace_func.currentText())] rtext = unicode(self.replace_with.text()) rtext = match.expand(rtext) return rfunc(rtext) def s_r_do_regexp(self, mi): src_field = self.s_r_sf_itemdata(None) src = self.s_r_get_field(mi, src_field) result = [] rfunc = self.s_r_functions[unicode(self.replace_func.currentText())] for s in src: t = self.s_r_obj.sub(self.s_r_func, s) if self.search_mode.currentIndex() == 0: t = rfunc(t) result.append(t) return result def s_r_do_destination(self, mi, val): src = self.s_r_sf_itemdata(None) if src == '': return '' dest = self.s_r_df_itemdata(None) if dest == '': if (src == '{template}' or self.db.metadata_for_field(src)['datatype'] == 'composite'): raise Exception(_('You must specify a destination when source is ' 'a composite field or a template')) dest = src dest_mode = self.replace_mode.currentIndex() if self.destination_field_fm['is_csp']: dest_ident = unicode(self.s_r_dst_ident.text()) if not dest_ident or (src == 'identifiers' and dest_ident == '*'): raise Exception(_('You must specify a destination identifier type')) if self.destination_field_fm['is_multiple']: if self.comma_separated.isChecked(): splitter = self.destination_field_fm['is_multiple']['ui_to_list'] res = [] for v in val: res.extend([x.strip() for x in v.split(splitter) if x.strip()]) val = res else: val = [v.replace(',', '') for v in val] if dest_mode != 0: dest_val = mi.get(dest, '') if self.db.metadata_for_field(dest)['is_csp']: dst_id_type = unicode(self.s_r_dst_ident.text()) if dst_id_type: dest_val = [dest_val.get(dst_id_type, '')] else: # convert the csp dict into a list dest_val = [u'%s:%s'%(t[0], t[1]) for t in dest_val.iteritems()] if dest_val is None: dest_val = [] elif not isinstance(dest_val, list): dest_val = [dest_val] else: dest_val = [] if dest_mode == 1: val.extend(dest_val) elif dest_mode == 2: val[0:0] = dest_val return val def s_r_replace_mode_separator(self): if self.comma_separated.isChecked(): return ',' return '' def s_r_paint_results(self, txt): self.s_r_error = None self.s_r_set_colors() if self.case_sensitive.isChecked(): flags = 0 else: flags = re.I flags |= re.UNICODE try: stext = unicode(self.search_for.text()) if not stext: raise Exception(_('You must specify a search expression in the "Search for" field')) if self.search_mode.currentIndex() == 0: self.s_r_obj = re.compile(re.escape(stext), flags) else: self.s_r_obj = re.compile(stext, flags) except Exception as e: self.s_r_obj = None self.s_r_error = e self.s_r_set_colors() return try: self.test_result.setText(self.s_r_obj.sub(self.s_r_func, unicode(self.test_text.text()))) except Exception as e: self.s_r_error = e self.s_r_set_colors() return for i in range(0,self.s_r_number_of_books): mi = self.db.get_metadata(self.ids[i], index_is_id=True) wr = getattr(self, 'book_%d_result'%(i+1)) try: result = self.s_r_do_regexp(mi) t = self.s_r_do_destination(mi, result) if len(t) > 1 and self.destination_field_fm['is_multiple']: t = t[self.starting_from.value()-1: self.starting_from.value()-1 + self.results_count.value()] t = unicode(self.multiple_separator.text()).join(t) else: t = self.s_r_replace_mode_separator().join(t) wr.setText(t) except Exception as e: self.s_r_error = e self.s_r_set_colors() break def do_search_replace(self, book_id): source = self.s_r_sf_itemdata(None) if not source or not self.s_r_obj: return dest = self.s_r_df_itemdata(None) if not dest: dest = source dfm = self.db.field_metadata[dest] mi = self.db.new_api.get_proxy_metadata(book_id) val = self.s_r_do_regexp(mi) val = self.s_r_do_destination(mi, val) if dfm['is_multiple']: if dfm['is_csp']: # convert the colon-separated pair strings back into a dict, # which is what set_identifiers wants dst_id_type = unicode(self.s_r_dst_ident.text()) if dst_id_type and dst_id_type != '*': v = ''.join(val) ids = mi.get(dest) ids[dst_id_type] = v val = ids else: try: val = dict([(t.split(':')) for t in val]) except: raise Exception(_('Invalid identifier string. It must be a ' 'comma-separated list of pairs of ' 'strings separated by a colon')) else: val = self.s_r_replace_mode_separator().join(val) if dest == 'title' and len(val) == 0: val = _('Unknown') self.set_field_calls[dest][book_id] = val # }}} def create_custom_column_editors(self): w = self.central_widget.widget(1) layout = QGridLayout() self.custom_column_widgets, self.__cc_spacers = \ populate_metadata_page(layout, self.db, self.ids, parent=w, two_column=False, bulk=True) w.setLayout(layout) self.__custom_col_layouts = [layout] ans = self.custom_column_widgets for i in range(len(ans)-1): w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1]) for c in range(2, len(ans[i].widgets), 2): w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1]) def initialize_combos(self): self.initalize_authors() self.initialize_series() self.initialize_publisher() for x in ('authors', 'publisher', 'series'): x = getattr(self, x) x.setSizeAdjustPolicy(x.AdjustToMinimumContentsLengthWithIcon) x.setMinimumContentsLength(25) def initalize_authors(self): all_authors = self.db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) self.authors.set_separator('&') self.authors.set_space_before_sep(True) self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.update_items_cache(self.db.all_author_names()) self.authors.show_initial_value('') def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series]) self.series.show_initial_value('') def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x : sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers]) self.publisher.show_initial_value('') def tag_editor(self, *args): d = TagEditor(self, self.db, None) d.exec_() if d.result() == QDialog.Accepted: tag_string = ', '.join(d.tags) self.tags.setText(tag_string) self.tags.update_items_cache(self.db.all_tags()) self.remove_tags.update_items_cache(self.db.all_tags()) def auto_number_changed(self, state): if state: self.series_numbering_restarts.setEnabled(True) self.series_start_number.setEnabled(True) else: self.series_numbering_restarts.setEnabled(False) self.series_numbering_restarts.setChecked(False) self.series_start_number.setEnabled(False) self.series_start_number.setValue(1) def reject(self): self.save_state() ResizableDialog.reject(self) def accept(self): self.save_state() if len(self.ids) < 1: return QDialog.accept(self) try: source = self.s_r_sf_itemdata(None) except: source = '' do_sr = source and self.s_r_obj if self.s_r_error is not None and do_sr: error_dialog(self, _('Search/replace invalid'), _('Search pattern is invalid: %s')%self.s_r_error.message, show=True) return False self.changed = bool(self.ids) # Cache values from GUI so that Qt widgets are not used in # non GUI thread for w in getattr(self, 'custom_column_widgets', []): w.gui_val remove_all = self.remove_all_tags.isChecked() remove = [] if not remove_all: remove = unicode(self.remove_tags.text()).strip().split(',') add = unicode(self.tags.text()).strip().split(',') au = unicode(self.authors.text()) aus = unicode(self.author_sort.text()) do_aus = self.author_sort.isEnabled() rating = self.rating.value() pub = unicode(self.publisher.text()) do_series = self.write_series clear_series = self.clear_series.isChecked() clear_pub = self.clear_pub.isChecked() series = unicode(self.series.currentText()).strip() do_autonumber = self.autonumber_series.isChecked() do_series_restart = self.series_numbering_restarts.isChecked() series_start_value = self.series_start_number.value() do_remove_format = self.remove_format.currentIndex() > -1 remove_format = unicode(self.remove_format.currentText()) do_swap_ta = self.swap_title_and_author.isChecked() do_remove_conv = self.remove_conversion_settings.isChecked() do_auto_author = self.auto_author_sort.isChecked() do_title_case = self.change_title_to_title_case.isChecked() do_title_sort = self.update_title_sort.isChecked() clear_languages = self.clear_languages.isChecked() restore_original = self.restore_original.isChecked() languages = self.languages.lang_codes pubdate = adddate = None if self.apply_pubdate.isChecked(): pubdate = qt_to_dt(self.pubdate.dateTime()) if self.apply_adddate.isChecked(): adddate = qt_to_dt(self.adddate.dateTime()) cover_action = None if self.cover_remove.isChecked(): cover_action = 'remove' elif self.cover_generate.isChecked(): cover_action = 'generate' elif self.cover_from_fmt.isChecked(): cover_action = 'fromfmt' elif self.cover_trim.isChecked(): cover_action = 'trim' elif self.cover_clone.isChecked(): cover_action = 'clone' args = Settings(remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, do_autonumber, do_remove_format, remove_format, do_swap_ta, do_remove_conv, do_auto_author, series, do_series_restart, series_start_value, do_title_case, cover_action, clear_series, clear_pub, pubdate, adddate, do_title_sort, languages, clear_languages, restore_original, self.comments) self.set_field_calls = defaultdict(dict) bb = MyBlockingBusy(args, self.ids, self.db, self.refresh_books, getattr(self, 'custom_column_widgets', []), self.do_search_replace, do_sr, self.set_field_calls, parent=self) # The metadata backup thread causes database commits # which can slow down bulk editing of large numbers of books self.model.stop_metadata_backup() try: bb.exec_() finally: self.model.start_metadata_backup() bb.thread = bb.db = bb.cc_widgets = None if bb.error is not None: return error_dialog(self, _('Failed'), bb.error[0], det_msg=bb.error[1], show=True) dynamic['s_r_search_mode'] = self.search_mode.currentIndex() self.db.clean() return QDialog.accept(self) def series_changed(self, *args): self.write_series = bool(unicode(self.series.currentText()).strip()) self.autonumber_series.setEnabled(True) def s_r_remove_query(self, *args): if self.query_field.currentIndex() == 0: return if not question_dialog(self, _("Delete saved search/replace"), _("The selected saved search/replace will be deleted. " "Are you sure?")): return item_id = self.query_field.currentIndex() item_name = unicode(self.query_field.currentText()) self.query_field.blockSignals(True) self.query_field.removeItem(item_id) self.query_field.blockSignals(False) self.query_field.setCurrentIndex(0) if item_name in self.queries.keys(): del(self.queries[item_name]) self.queries.commit() def s_r_save_query(self, *args): names = [''] names.extend(self.query_field_values) try: dex = names.index(self.saved_search_name) except: dex = 0 name = '' while not name: name, ok = QInputDialog.getItem(self, _('Save search/replace'), _('Search/replace name:'), names, dex, True) if not ok: return if not name: error_dialog(self, _("Save search/replace"), _("You must provide a name."), show=True) new = True name = unicode(name) if name in self.queries.keys(): if not question_dialog(self, _("Save search/replace"), _("That saved search/replace already exists and will be overwritten. " "Are you sure?")): return new = False query = {} query['name'] = name query['search_field'] = unicode(self.search_field.currentText()) query['search_mode'] = unicode(self.search_mode.currentText()) query['s_r_template'] = unicode(self.s_r_template.text()) query['s_r_src_ident'] = unicode(self.s_r_src_ident.currentText()) query['search_for'] = unicode(self.search_for.text()) query['case_sensitive'] = self.case_sensitive.isChecked() query['replace_with'] = unicode(self.replace_with.text()) query['replace_func'] = unicode(self.replace_func.currentText()) query['destination_field'] = unicode(self.destination_field.currentText()) query['s_r_dst_ident'] = unicode(self.s_r_dst_ident.text()) query['replace_mode'] = unicode(self.replace_mode.currentText()) query['comma_separated'] = self.comma_separated.isChecked() query['results_count'] = self.results_count.value() query['starting_from'] = self.starting_from.value() query['multiple_separator'] = unicode(self.multiple_separator.text()) self.queries[name] = query self.queries.commit() if new: self.query_field.blockSignals(True) self.query_field.clear() self.query_field.addItem('') self.query_field_values = sorted([q for q in self.queries], key=sort_key) self.query_field.addItems(self.query_field_values) self.query_field.blockSignals(False) self.query_field.setCurrentIndex(self.query_field.findText(name)) def s_r_query_change(self, item_name): if not item_name: self.s_r_reset_query_fields() self.saved_search_name = '' return item = self.queries.get(unicode(item_name), None) if item is None: self.s_r_reset_query_fields() return self.saved_search_name = item_name def set_text(attr, key): try: attr.setText(item[key]) except: pass def set_checked(attr, key): try: attr.setChecked(item[key]) except: attr.setChecked(False) def set_value(attr, key): try: attr.setValue(int(item[key])) except: attr.setValue(0) def set_index(attr, key): try: attr.setCurrentIndex(attr.findText(item[key])) except: attr.setCurrentIndex(0) set_index(self.search_mode, 'search_mode') set_index(self.search_field, 'search_field') set_text(self.s_r_template, 's_r_template') self.s_r_template_changed() # simulate gain/loss of focus set_index(self.s_r_src_ident, 's_r_src_ident') set_text(self.s_r_dst_ident, 's_r_dst_ident') set_text(self.search_for, 'search_for') set_checked(self.case_sensitive, 'case_sensitive') set_text(self.replace_with, 'replace_with') set_index(self.replace_func, 'replace_func') set_index(self.destination_field, 'destination_field') set_index(self.replace_mode, 'replace_mode') set_checked(self.comma_separated, 'comma_separated') set_value(self.results_count, 'results_count') set_value(self.starting_from, 'starting_from') set_text(self.multiple_separator, 'multiple_separator') def s_r_reset_query_fields(self): # Don't reset the search mode. The user will probably want to use it # as it was self.search_field.setCurrentIndex(0) self.s_r_src_ident.setCurrentIndex(0) self.s_r_template.setText("") self.search_for.setText("") self.case_sensitive.setChecked(False) self.replace_with.setText("") self.replace_func.setCurrentIndex(0) self.destination_field.setCurrentIndex(0) self.s_r_dst_ident.setText('') self.replace_mode.setCurrentIndex(0) self.comma_separated.setChecked(True) self.results_count.setValue(999) self.starting_from.setValue(1) self.multiple_separator.setText(" ::: ")
def load_themes(): return JSONConfig('viewer_themes')
def prepare_search_and_replace(self): self.search_for.initialize('bulk_edit_search_for') self.replace_with.initialize('bulk_edit_replace_with') self.s_r_template.initialize('bulk_edit_template') self.test_text.initialize('bulk_edit_test_test') self.all_fields = [''] self.writable_fields = [''] fm = self.db.field_metadata for f in fm: if (f in ['author_sort'] or (fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments'] and fm[f].get('search_terms', None) and f not in ['formats', 'ondevice', 'series_sort']) or (fm[f]['datatype'] in ['int', 'float', 'bool', 'datetime'] and f not in ['id', 'timestamp'])): self.all_fields.append(f) self.writable_fields.append(f) if fm[f]['datatype'] == 'composite': self.all_fields.append(f) self.all_fields.sort() self.all_fields.insert(1, '{template}') self.writable_fields.sort() self.search_field.setMaxVisibleItems(25) self.destination_field.setMaxVisibleItems(25) self.testgrid.setColumnStretch(1, 1) self.testgrid.setColumnStretch(2, 1) offset = 10 self.s_r_number_of_books = min(10, len(self.ids)) for i in range(1,self.s_r_number_of_books+1): w = QLabel(self.tabWidgetPage3) w.setText(_('Book %d:')%i) self.testgrid.addWidget(w, i+offset, 0, 1, 1) w = QLineEdit(self.tabWidgetPage3) w.setReadOnly(True) name = 'book_%d_text'%i setattr(self, name, w) self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 1, 1, 1) w = QLineEdit(self.tabWidgetPage3) w.setReadOnly(True) name = 'book_%d_result'%i setattr(self, name, w) self.book_1_text.setObjectName(name) self.testgrid.addWidget(w, i+offset, 2, 1, 1) ident_types = sorted(self.db.get_all_identifier_types(), key=sort_key) self.s_r_dst_ident.setCompleter(QCompleter(ident_types)) try: self.s_r_dst_ident.setPlaceholderText(_('Enter an identifier type')) except: pass self.s_r_src_ident.addItems(ident_types) self.main_heading = _( '<b>You can destroy your library using this feature.</b> ' 'Changes are permanent. There is no undo function. ' 'You are strongly encouraged to back up your library ' 'before proceeding.<p>' 'Search and replace in text fields using character matching ' 'or regular expressions. ') self.character_heading = _( 'In character mode, the field is searched for the entered ' 'search text. The text is replaced by the specified replacement ' 'text everywhere it is found in the specified field. After ' 'replacement is finished, the text can be changed to ' 'upper-case, lower-case, or title-case. If the case-sensitive ' 'check box is checked, the search text must match exactly. If ' 'it is unchecked, the search text will match both upper- and ' 'lower-case letters' ) self.regexp_heading = _( 'In regular expression mode, the search text is an ' 'arbitrary python-compatible regular expression. The ' 'replacement text can contain backreferences to parenthesized ' 'expressions in the pattern. The search is not anchored, ' 'and can match and replace multiple times on the same string. ' 'The modification functions (lower-case etc) are applied to the ' 'matched text, not to the field as a whole. ' 'The destination box specifies the field where the result after ' 'matching and replacement is to be assigned. You can replace ' 'the text in the field, or prepend or append the matched text. ' 'See <a href="http://docs.python.org/library/re.html"> ' 'this reference</a> for more information on python\'s regular ' 'expressions, and in particular the \'sub\' function.' ) self.search_mode.addItems(self.s_r_match_modes) self.search_mode.setCurrentIndex(dynamic.get('s_r_search_mode', 0)) self.replace_mode.addItems(self.s_r_replace_modes) self.replace_mode.setCurrentIndex(0) self.s_r_search_mode = 0 self.s_r_error = None self.s_r_obj = None self.replace_func.addItems(sorted(self.s_r_functions.keys())) self.search_mode.currentIndexChanged[int].connect(self.s_r_search_mode_changed) self.search_field.currentIndexChanged[int].connect(self.s_r_search_field_changed) self.destination_field.currentIndexChanged[int].connect(self.s_r_destination_field_changed) self.replace_mode.currentIndexChanged[int].connect(self.s_r_paint_results) self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results) self.search_for.editTextChanged[str].connect(self.s_r_paint_results) self.replace_with.editTextChanged[str].connect(self.s_r_paint_results) self.test_text.editTextChanged[str].connect(self.s_r_paint_results) self.comma_separated.stateChanged.connect(self.s_r_paint_results) self.case_sensitive.stateChanged.connect(self.s_r_paint_results) self.s_r_src_ident.currentIndexChanged[int].connect(self.s_r_identifier_type_changed) self.s_r_dst_ident.textChanged.connect(self.s_r_paint_results) self.s_r_template.lost_focus.connect(self.s_r_template_changed) self.central_widget.setCurrentIndex(0) self.search_for.completer().setCaseSensitivity(Qt.CaseSensitive) self.replace_with.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_template.completer().setCaseSensitivity(Qt.CaseSensitive) self.s_r_search_mode_changed(self.search_mode.currentIndex()) self.multiple_separator.setFixedWidth(30) self.multiple_separator.setText(' ::: ') self.multiple_separator.textChanged.connect(self.s_r_separator_changed) self.results_count.valueChanged[int].connect(self.s_r_display_bounds_changed) self.starting_from.valueChanged[int].connect(self.s_r_display_bounds_changed) self.save_button.clicked.connect(self.s_r_save_query) self.remove_button.clicked.connect(self.s_r_remove_query) self.queries = JSONConfig("search_replace_queries") self.saved_search_name = '' self.query_field.addItem("") self.query_field_values = sorted([q for q in self.queries], key=sort_key) self.query_field.addItems(self.query_field_values) self.query_field.currentIndexChanged[str].connect(self.s_r_query_change) self.query_field.setCurrentIndex(0) self.search_field.setCurrentIndex(0) self.s_r_search_field_changed(0)
from calibre.gui2.viewer.toc import TOC from calibre.gui2.widgets import ProgressIndicator from calibre.gui2 import ( Application, ORG_NAME, APP_UID, choose_files, info_dialog, error_dialog, open_url, setup_gui_option_parser) from calibre.ebooks.oeb.iterator.book import EbookIterator from calibre.constants import islinux, filesystem_encoding from calibre.utils.config import Config, StringConfig, JSONConfig from calibre.customize.ui import available_input_formats from calibre import as_unicode, force_unicode, isbytestring, prints from calibre.ptempfile import reset_base_dir from calibre.utils.ipc import viewer_socket_address, RC from calibre.utils.zipfile import BadZipfile from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1, get_lang vprefs = JSONConfig('viewer') vprefs.defaults['singleinstance'] = False dprefs = JSONConfig('viewer_dictionaries') dprefs.defaults['word_lookups'] = {} singleinstance_name = 'calibre_viewer' class Worker(Thread): def run(self): from calibre.utils.ipc.simple_worker import WorkerError try: Thread.run(self) self.exception = self.traceback = None except BadZipfile: self.exception = _( 'This ebook is corrupted and cannot be opened. If you '
QSize, Qt, QApplication, QIcon) from calibre.ebooks.oeb.polish.utils import apply_func_to_match_groups, apply_func_to_html_text from calibre.gui2 import error_dialog from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.tweak_book import dictionaries from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.tweak_book.editor.text import TextEdit from calibre.utils.config import JSONConfig from calibre.utils.icu import capitalize, upper, lower, swapcase from calibre.utils.titlecase import titlecase from calibre.utils.localization import localize_user_manual_link from polyglot.builtins import iteritems, unicode_type from polyglot.io import PolyglotStringIO user_functions = JSONConfig('editor-search-replace-functions') def compile_code(src, name='<string>'): if not isinstance(src, unicode_type): match = re.search(br'coding[:=]\s*([-\w.]+)', src[:200]) enc = match.group(1).decode('utf-8') if match else 'utf-8' src = src.decode(enc) if not src or not src.strip(): src = EMPTY_FUNC # Python complains if there is a coding declaration in a unicode string src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE) # Translate newlines to \n src = io.StringIO(src, newline=None).getvalue() code = compile(src, name, 'exec')
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log from calibre.utils.config import JSONConfig # If preset specified from the cli, insert stored options from JSON file if hasattr(opts, 'preset') and opts.preset: available_presets = JSONConfig("catalog_presets") if opts.preset not in available_presets: if available_presets: print(_('Error: Preset "%s" not found.' % opts.preset)) print(_('Stored presets: %s' % ', '.join([p for p in sorted(available_presets.keys())]))) else: print(_('Error: No stored presets.')) return 1 # Copy the relevant preset values to the opts object for item in available_presets[opts.preset]: if item not in ['exclusion_rules_tw', 'format', 'prefix_rules_tw']: setattr(opts, item, available_presets[opts.preset][item]) # Provide an unconnected device opts.connected_device = { 'is_device_connected': False, 'kind': None, 'name': None, 'save_template': None, 'serial': None, 'storage': None, } # Convert prefix_rules and exclusion_rules from JSON lists to tuples prs = [] for rule in opts.prefix_rules: prs.append(tuple(rule)) opts.prefix_rules = tuple(prs) ers = [] for rule in opts.exclusion_rules: ers.append(tuple(rule)) opts.exclusion_rules = tuple(ers) opts.log = log opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ opts.connected_device['serial'][:4] in ['B004', 'B005']: op = "kindle_dx" else: op = "kindle" opts.description_clip = 380 if op.endswith('dx') or 'kindle' not in op else 100 opts.author_clip = 100 if op.endswith('dx') or 'kindle' not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts, 'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append(u"%s('%s'): Generating %s %sin %s environment, locale: '%s'" % (self.name, current_library_name(), self.fmt, 'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI', calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False)) ) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == '': # opts.exclude_genre = '\[^.\]' # build_log.append(" converting empty exclude_genre to '\[^.\]'") opts.exclude_genre = 'a^' build_log.append(" converting empty exclude_genre to 'a^'") if opts.connected_device['is_device_connected'] and \ opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4))) for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) try: for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) except: build_log.append(u" (no mount points)") else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) sections_list = [] if opts.generate_authors: sections_list.append('Authors') if opts.generate_titles: sections_list.append('Titles') if opts.generate_series: sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: sections_list.append('Recently Added') if opts.generate_descriptions: sections_list.append('Descriptions') if not sections_list: if opts.cli_environment: opts.log.warn('*** No Section switches specified, enabling all Sections ***') opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = ['Authors', 'Titles', 'Series', 'Genres', 'Recently Added', 'Descriptions'] else: opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') return ["No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _("\n*** Adding 'By authors' section required for MOBI output ***") opts.log.warn(warning) sections_list.insert(0, 'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line if type(opts.prefix_rules) is not tuple: try: opts.prefix_rules = eval(opts.prefix_rules) except: log.error("malformed --prefix-rules: %s" % opts.prefix_rules) raise for rule in opts.prefix_rules: if len(rule) != 4: log.error("incorrect number of args for --prefix-rules: %s" % repr(rule)) # eval exclusion_rules if passed from command line if type(opts.exclusion_rules) is not tuple: try: opts.exclusion_rules = eval(opts.exclusion_rules) except: log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules) raise for rule in opts.exclusion_rules: if len(rule) != 3: log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts keys = sorted(opts_dict.keys()) build_log.append(" opts:") for key in keys: if key in ['catalog_title', 'author_clip', 'connected_kindle', 'creator', 'cross_reference_authors', 'description_clip', 'exclude_book_marker', 'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt', 'genre_source_field', 'header_note_source_field', 'merge_comments_rule', 'output_profile', 'prefix_rules', 'preset', 'read_book_marker', 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync', 'thumb_width', 'use_existing_cover', 'wishlist_tag']: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) # Capture start_time opts.start_time = time.time() self.opts = opts if opts.verbose: log.info(" Begin catalog source generation (%s)" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) try: catalog.build_sources() if opts.verbose: log.info(" Completed catalog source generation (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) except (AuthorSortMismatchException, EmptyCatalogException) as e: log.error(" *** Terminated catalog generation: %s ***" % e) except: log.error(" unhandled exception in catalog generator") raise else: recommendations = [] recommendations.append(('remove_fake_margins', False, OptionRecommendation.HIGH)) recommendations.append(('comments', '', OptionRecommendation.HIGH)) """ >>> Use to debug generated catalog code before pipeline conversion <<< """ GENERATE_DEBUG_EPUB = False if GENERATE_DEBUG_EPUB: catalog_debug_path = os.path.join(os.path.expanduser('~'), 'Desktop', 'Catalog debug') setattr(opts, 'debug_pipeline', os.path.expanduser(catalog_debug_path)) dp = getattr(opts, 'debug_pipeline', None) if dp is not None: recommendations.append(('debug_pipeline', dp, OptionRecommendation.HIGH)) if opts.output_profile and opts.output_profile.startswith("kindle"): recommendations.append(('output_profile', opts.output_profile, OptionRecommendation.HIGH)) recommendations.append(('book_producer', opts.output_profile, OptionRecommendation.HIGH)) if opts.fmt == 'mobi': recommendations.append(('no_inline_toc', True, OptionRecommendation.HIGH)) recommendations.append(('verbose', 2, OptionRecommendation.HIGH)) # Use existing cover or generate new cover cpath = None existing_cover = False try: search_text = 'title:"%s" author:%s' % ( opts.catalog_title.replace('"', '\\"'), 'calibre') matches = db.search(search_text, return_matches=True, sort_results=False) if matches: cpath = db.cover(matches[0], index_is_id=True, as_path=True) if cpath and os.path.exists(cpath): existing_cover = True except: pass if self.opts.use_existing_cover and not existing_cover: log.warning("no existing catalog cover found") if self.opts.use_existing_cover and existing_cover: recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) log.info("using existing catalog cover") else: from calibre.ebooks.covers import calibre_cover2 log.info("replacing catalog cover") new_cover_path = PersistentTemporaryFile(suffix='.jpg') new_cover = calibre_cover2(opts.catalog_title, 'calibre') new_cover_path.write(new_cover) new_cover_path.close() recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalog_path, opts.basename + '.opf'), path_to_output, log, report_progress=notification, abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() try: os.remove(cpath) except: pass if GENERATE_DEBUG_EPUB: from calibre.ebooks.epub import initialize_container from calibre.ebooks.tweak import zip_rebuilder from calibre.utils.zipfile import ZipFile input_path = os.path.join(catalog_debug_path, 'input') epub_shell = os.path.join(catalog_debug_path, 'epub_shell.zip') initialize_container(epub_shell, opf_name='content.opf') with ZipFile(epub_shell, 'r') as zf: zf.extractall(path=input_path) os.remove(epub_shell) zip_rebuilder(input_path, os.path.join(catalog_debug_path, 'input.epub')) if opts.verbose: log.info(" Catalog creation complete (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # returns to gui2.actions.catalog:catalog_generated() return catalog.error
from calibre.gui2.widgets import ProgressIndicator from calibre.gui2 import ( Application, ORG_NAME, APP_UID, choose_files, info_dialog, error_dialog, open_url, setup_gui_option_parser) from calibre.ebooks.oeb.iterator.book import EbookIterator from calibre.ebooks import DRMError from calibre.constants import islinux, filesystem_encoding from calibre.utils.config import Config, StringConfig, JSONConfig from calibre.customize.ui import available_input_formats from calibre import as_unicode, force_unicode, isbytestring from calibre.ptempfile import reset_base_dir from calibre.utils.zipfile import BadZipfile from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1, get_lang vprefs = JSONConfig('viewer') dprefs = JSONConfig('viewer_dictionaries') dprefs.defaults['word_lookups'] = {} class Worker(Thread): def run(self): try: Thread.run(self) self.exception = self.traceback = None except BadZipfile: self.exception = _( 'This ebook is corrupted and cannot be opened. If you ' 'downloaded it from somewhere, try downloading it again.') self.traceback = '' except Exception as err: self.exception = err
from itertools import chain from functools import partial from calibre import prints from calibre.constants import plugins, config_dir from calibre.spell import parse_lang_code from calibre.utils.config import JSONConfig from calibre.utils.icu import capitalize from calibre.utils.localization import get_lang, get_system_locale Dictionary = namedtuple('Dictionary', 'primary_locale locales dicpath affpath builtin name id') LoadedDictionary = namedtuple('Dictionary', 'primary_locale locales obj builtin name id') hunspell = plugins['hunspell'][0] if hunspell is None: raise RuntimeError('Failed to load hunspell: %s' % plugins['hunspell'][1]) dprefs = JSONConfig('dictionaries/prefs.json') dprefs.defaults['preferred_dictionaries'] = {} dprefs.defaults['preferred_locales'] = {} dprefs.defaults['user_dictionaries'] = [{'name':_('Default'), 'is_active':True, 'words':[]}] not_present = object() class UserDictionary(object): __slots__ = ('name', 'is_active', 'words') def __init__(self, **kwargs): self.name = kwargs['name'] self.is_active = kwargs['is_active'] self.words = {(w, langcode) for w, langcode in kwargs['words']}
class BookSettings(object): '''Holds book specific settings''' def __init__(self, database, book_id, connections): self._connections = connections book_path = database.field_for('path', book_id).replace('/', os.sep) self._prefs = JSONConfig(os.path.join(book_path, 'book_settings'), base_path=LIBRARY) self._prefs.setdefault('asin', '') self._prefs.setdefault('goodreads_url', '') self._prefs.setdefault('aliases', {}) self._prefs.setdefault('sample_xray', '') self._prefs.commit() self._title = database.field_for('title', book_id) self._author = ' & '.join(database.field_for('authors', book_id)) self._asin = self._prefs['asin'] if self._prefs['asin'] != '' else None self._goodreads_url = self._prefs['goodreads_url'] self._sample_xray = self._prefs['sample_xray'] if not self._asin: identifiers = database.field_for('identifiers', book_id) if 'mobi-asin' in list(identifiers.keys()): self._asin = database.field_for( 'identifiers', book_id)['mobi-asin'].decode('ascii') self._prefs['asin'] = self._asin else: self._asin = self.search_for_asin_on_amazon( self.title_and_author) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin if self._goodreads_url == '': url = None if self._asin: url = self.search_for_goodreads_url(self._asin) if not url and self._title != 'Unknown' and self._author != 'Unknown': url = self.search_for_goodreads_url(self.title_and_author) if url: self._goodreads_url = url self._prefs['goodreads_url'] = self._goodreads_url if not self._asin: self._asin = self.search_for_asin_on_goodreads( self._goodreads_url) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin self._aliases = self._prefs['aliases'] self.save() @property def prefs(self): return self._prefs @property def asin(self): return self._asin @asin.setter def asin(self, val): self._asin = val @property def sample_xray(self): return self._sample_xray @sample_xray.setter def sample_xray(self, value): self._sample_xray = value @property def title(self): return self._title @property def author(self): return self._author @property def title_and_author(self): return '{0} - {1}'.format(self._title, self._author) @property def goodreads_url(self): return self._goodreads_url @goodreads_url.setter def goodreads_url(self, val): self._goodreads_url = val @property def aliases(self): return self._aliases def set_aliases(self, label, aliases): '''Sets label's aliases to aliases''' # 'aliases' is a string containing a comma separated list of aliases. # Split it, remove whitespace from each element, drop empty strings (strangely, # split only does this if you don't specify a separator) # so "" -> [] "foo,bar" and " foo , bar " -> ["foo", "bar"] aliases = [x.strip() for x in aliases.split(",") if x.strip()] self._aliases[label] = aliases def save(self): '''Saves current settings in book's settings file''' self._prefs['asin'] = self._asin self._prefs['goodreads_url'] = self._goodreads_url self._prefs['aliases'] = self._aliases self._prefs['sample_xray'] = self._sample_xray def search_for_asin_on_amazon(self, query): '''Search for book's asin on amazon using given query''' query = urlencode({'keywords': query}) url = '/s/ref=sr_qz_back?sf=qz&rh=i%3Adigital-text%2Cn%3A154606011%2Ck%3A' + query[ 9:] + '&' + query try: response = open_url(self._connections['amazon'], url) except PageDoesNotExist: return None # check to make sure there are results if ('did not match any products' in response and 'Did you mean:' not in response and 'so we searched in All Departments' not in response): return None soup = BeautifulSoup(response) results = soup.findAll('div', {'id': 'resultsCol'}) if not results: return None for result in results: if 'Buy now with 1-Click' in str(result): asin_search = AMAZON_ASIN_PAT.search(str(result)) if asin_search: return asin_search.group(1) return None def search_for_goodreads_url(self, keywords): '''Searches for book's goodreads url using given keywords''' query = urlencode({'q': keywords}) try: response = open_url(self._connections['goodreads'], '/search?' + query) except PageDoesNotExist: return None # check to make sure there are results if 'No results' in response: return None urlsearch = GOODREADS_URL_PAT.search(response) if not urlsearch: return None # return the full URL with the query parameters removed url = 'https://www.goodreads.com' + urlsearch.group(1) return urlparse.urlparse(url)._replace(query=None).geturl() def search_for_asin_on_goodreads(self, url): '''Searches for ASIN of book at given url''' book_id_search = BOOK_ID_PAT.search(url) if not book_id_search: return None book_id = book_id_search.group(1) try: response = open_url(self._connections['goodreads'], '/buttons/glide/' + book_id) except PageDoesNotExist: return None book_asin_search = GOODREADS_ASIN_PAT.search(response) if not book_asin_search: return None return book_asin_search.group(1) def update_aliases(self, source, source_type='url'): if source_type.lower() == 'url': self.update_aliases_from_url(source) return if source_type.lower() == 'asc': self.update_aliases_from_asc(source) return if source_type.lower() == 'json': self.update_aliases_from_json(source) def update_aliases_from_asc(self, filename): '''Gets aliases from sample x-ray file and expands them if users settings say to do so''' cursor = connect(filename).cursor() characters = { x[1]: [x[1]] for x in cursor.execute('SELECT * FROM entity').fetchall() if x[3] == 1 } self._aliases = {} for alias, fullname in list(auto_expand_aliases(characters).items()): if fullname not in list(self._aliases.keys()): self._aliases[fullname] = [alias] continue self._aliases[fullname].append(alias) def update_aliases_from_json(self, filename): '''Gets aliases from json file''' data = json.load(open(filename)) self._aliases = { name: char['aliases'] for name, char in list(data['characters'].items()) } if 'characters' in data else {} if 'settings' in data: self._aliases.update({ name: setting['aliases'] for name, setting in list(data['settings'].items()) }) def update_aliases_from_url(self, url): '''Gets aliases from Goodreads and expands them if users settings say to do so''' try: goodreads_parser = GoodreadsParser(url, self._connections['goodreads'], self._asin) goodreads_chars = goodreads_parser.get_characters(1) goodreads_settings = goodreads_parser.get_settings( len(goodreads_chars)) except PageDoesNotExist: goodreads_chars = {} goodreads_settings = {} self._aliases = {} for char_data in list(goodreads_chars.values()) + list( goodreads_settings.values()): if char_data['label'] not in list(self._aliases.keys()): self._aliases[char_data['label']] = char_data['aliases']
class Manager(QObject): # {{{ def __init__(self, parent=None, config_name='shortcuts/main'): QObject.__init__(self, parent) self.config = JSONConfig(config_name) self.shortcuts = OrderedDict() self.keys_map = {} self.groups = {} def register_shortcut(self, unique_name, name, default_keys=(), description=None, action=None, group=None): ''' Register a shortcut with calibre. calibre will manage the shortcut, automatically resolving conflicts and allowing the user to customize it. :param unique_name: A string that uniquely identifies this shortcut :param name: A user visible name describing the action performed by this shortcut :param default_keys: A tuple of keys that trigger this shortcut. Each key must be a string. For example: ('Ctrl+A', 'Alt+B', 'C', 'Shift+Meta+D'). These keys will be assigned to the shortcut unless there is a conflict. :param action: A QAction object. The shortcut will cause this QAction to be triggered. Connect to its triggered signal in your code to respond to the shortcut. :param group: A string describing what "group" this shortcut belongs to. This is used to organize the list of shortcuts when the user is customizing them. ''' if unique_name in self.shortcuts: name = self.shortcuts[unique_name]['name'] raise NameConflict('Shortcut for %r already registered by %s'%( unique_name, name)) shortcut = {'name':name, 'desc':description, 'action': action, 'default_keys':tuple(default_keys)} self.shortcuts[unique_name] = shortcut group = group if group else _('Miscellaneous') self.groups[group] = self.groups.get(group, []) + [unique_name] def unregister_shortcut(self, unique_name): ''' Remove a registered shortcut. You need to call finalize() after you are done unregistering. ''' self.shortcuts.pop(unique_name, None) for group in self.groups.itervalues(): try: group.remove(unique_name) except ValueError: pass def finalize(self): custom_keys_map = {un:tuple(keys) for un, keys in self.config.get( 'map', {}).iteritems()} self.keys_map = finalize(self.shortcuts, custom_keys_map=custom_keys_map) #import pprint # pprint.pprint(self.keys_map) def replace_action(self, unique_name, new_action): ''' Replace the action associated with a shortcut. Once you're done calling replace_action() for all shortcuts you want replaced, call finalize() to have the shortcuts assigned to the replaced actions. ''' sc = self.shortcuts[unique_name] sc['action'] = new_action
def __init__(self, database, book_id, connections): self._connections = connections book_path = database.field_for('path', book_id).replace('/', os.sep) self._prefs = JSONConfig(os.path.join(book_path, 'book_settings'), base_path=LIBRARY) self._prefs.setdefault('asin', '') self._prefs.setdefault('goodreads_url', '') self._prefs.setdefault('aliases', {}) self._prefs.setdefault('sample_xray', '') self._prefs.commit() self._title = database.field_for('title', book_id) self._author = ' & '.join(database.field_for('authors', book_id)) self._asin = self._prefs['asin'] if self._prefs['asin'] != '' else None self._goodreads_url = self._prefs['goodreads_url'] self._sample_xray = self._prefs['sample_xray'] if not self._asin: identifiers = database.field_for('identifiers', book_id) if 'mobi-asin' in list(identifiers.keys()): self._asin = database.field_for( 'identifiers', book_id)['mobi-asin'].decode('ascii') self._prefs['asin'] = self._asin else: self._asin = self.search_for_asin_on_amazon( self.title_and_author) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin if self._goodreads_url == '': url = None if self._asin: url = self.search_for_goodreads_url(self._asin) if not url and self._title != 'Unknown' and self._author != 'Unknown': url = self.search_for_goodreads_url(self.title_and_author) if url: self._goodreads_url = url self._prefs['goodreads_url'] = self._goodreads_url if not self._asin: self._asin = self.search_for_asin_on_goodreads( self._goodreads_url) if self._asin: metadata = database.get_metadata(book_id) identifiers = metadata.get_identifiers() identifiers['mobi-asin'] = self._asin metadata.set_identifiers(identifiers) database.set_metadata(book_id, metadata) self._prefs['asin'] = self._asin self._aliases = self._prefs['aliases'] self.save()
class Stores(OrderedDict): CHECK_INTERVAL = 24 * 60 * 60 def builtins_loaded(self): self.last_check_time = 0 self.version_map = {} self.cached_version_map = {} self.name_rmap = {} for key, val in self.iteritems(): prefix, name = val.__module__.rpartition('.')[0::2] if prefix == 'calibre.gui2.store.stores' and name.endswith('_plugin'): module = sys.modules[val.__module__] sv = getattr(module, 'store_version', None) if sv is not None: name = name.rpartition('_')[0] self.version_map[name] = sv self.name_rmap[name] = key self.cache_file = JSONConfig('store/plugin_cache') self.load_cache() def load_cache(self): # Load plugins from on disk cache remove = set() pat = re.compile(r'^store_version\s*=\s*(\d+)', re.M) for name, src in self.cache_file.iteritems(): try: key = self.name_rmap[name] except KeyError: # Plugin has been disabled m = pat.search(src[:512]) if m is not None: try: self.cached_version_map[name] = int(m.group(1)) except (TypeError, ValueError): pass continue try: obj, ver = self.load_object(src, key) except VersionMismatch as e: self.cached_version_map[name] = e.ver continue except: import traceback prints('Failed to load cached store:', name) traceback.print_exc() else: if not self.replace_plugin(ver, name, obj, 'cached'): # Builtin plugin is newer than cached remove.add(name) if remove: with self.cache_file: for name in remove: del self.cache_file[name] def check_for_updates(self): if hasattr(self, 'update_thread') and self.update_thread.is_alive(): return if time.time() - self.last_check_time < self.CHECK_INTERVAL: return self.last_check_time = time.time() try: self.update_thread.start() except (RuntimeError, AttributeError): self.update_thread = Thread(target=self.do_update) self.update_thread.start() def join(self, timeout=None): hasattr(self, 'update_thread') and self.update_thread.join(timeout) def download_updates(self): ver_map = {name:max(ver, self.cached_version_map.get(name, -1)) for name, ver in self.version_map.iteritems()} try: updates = download_updates(ver_map) except: import traceback traceback.print_exc() else: for name, code in updates: yield name, code def do_update(self): replacements = {} for name, src in self.download_updates(): try: key = self.name_rmap[name] except KeyError: # Plugin has been disabled replacements[name] = src continue try: obj, ver = self.load_object(src, key) except VersionMismatch as e: self.cached_version_map[name] = e.ver replacements[name] = src continue except: import traceback prints('Failed to load downloaded store:', name) traceback.print_exc() else: if self.replace_plugin(ver, name, obj, 'downloaded'): replacements[name] = src if replacements: with self.cache_file: for name, src in replacements.iteritems(): self.cache_file[name] = src def replace_plugin(self, ver, name, obj, source): if ver > self.version_map[name]: if DEBUG: prints('Loaded', source, 'store plugin for:', self.name_rmap[name], 'at version:', ver) self[self.name_rmap[name]] = obj self.version_map[name] = ver return True return False def load_object(self, src, key): namespace = {} builtin = self[key] exec src in namespace ver = namespace['store_version'] cls = None for x in namespace.itervalues(): if (isinstance(x, type) and issubclass(x, StorePlugin) and x is not StorePlugin): cls = x break if cls is None: raise ValueError('No store plugin found') if cls.minimum_calibre_version > numeric_version: raise VersionMismatch(ver) return cls(builtin.gui, builtin.name, config=builtin.config, base_plugin=builtin.base_plugin), ver
def prefs(self): if self._config_obj is None: from calibre.utils.config import JSONConfig self._config_obj = JSONConfig('metadata_sources/%s.json' % self.name) return self._config_obj
if data is None: icon = QIcon(I('blank.png')) else: pmap = QPixmap() pmap.loadFromData(bytes(data)) icon = QIcon(pmap) return icon, entry.get('name', entry.get('Name')) or _('Unknown') if iswindows: # Windows {{{ from calibre.utils.winreg.default_programs import find_programs, friendly_app_name from calibre.utils.open_with.windows import load_icon_resource from win32process import CreateProcess, STARTUPINFO from win32event import WaitForInputIdle import win32con oprefs = JSONConfig('windows_open_with') def entry_sort_key(entry): return sort_key(entry.get('name') or '') def finalize_entry(entry): try: data = load_icon_resource(entry.pop('icon_resource', None), as_data=True) except Exception: data = None import traceback traceback.print_exc() if data: entry['icon_data'] = data return entry
except ImportError as e: from PyQt4 import QtGui from PyQt4.Qt import QWidget, QGridLayout, QLabel, QLineEdit, QPushButton from calibre.utils.config import JSONConfig from calibre_plugins.extract_isbn.common_utils import KeyValueComboBox, KeyboardConfigDialog STORE_NAME = 'Options' KEY_PATH_BROWSER = 'pathBrowser' DEFAULT_STORE_VALUES = { KEY_PATH_BROWSER: '', } # This is where all preferences for this plugin will be stored plugin_prefs = JSONConfig('plugins/Bibi Calibre') # Set defaults plugin_prefs.defaults[STORE_NAME] = DEFAULT_STORE_VALUES class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QGridLayout(self) self.setLayout(layout) c = plugin_prefs[STORE_NAME] layout.addWidget(QLabel('Browser command:', self), 0, 0)
import bz2 import hashlib import json import sys import time from threading import Thread import calibre.ebooks.metadata.sources.search_engines as builtin_search_engines from calibre import as_unicode, prints from calibre.constants import DEBUG, numeric_version from calibre.ebooks.metadata.sources.base import Source from calibre.utils.config import JSONConfig from calibre.utils.https import get_https_resource_securely cache = JSONConfig('metadata-sources-cache.json') UPDATE_INTERVAL = 12 * 60 * 60 current_search_engines = builtin_search_engines def search_engines_module(): return current_search_engines def debug_print(*args, **k): if DEBUG: prints(*args, **k)
__license__ = 'GPL v3' __copyright__ = '2013, Marcell Mars <*****@*****.**>' __docformat__ = 'restructuredtext en' from PyQt4.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout from calibre.utils.config import JSONConfig # This is where all preferences for this plugin will be stored # Remember that this name (i.e. plugins/interface_demo) is also # in a global namespace, so make it as unique as possible. # You should always prefix your config file name with plugins/, # so as to ensure you dont accidentally clobber a calibre config file prefs = JSONConfig('plugins/letssharebooks.conf') # Set defaults prefs.defaults['lsb_server'] = 'memoryoftheworld.org' prefs.defaults['server_prefix'] = 'https' try: prefs['library_uuid'] except: prefs.defaults['library_uuid'] = str(uuid.uuid4()) try: prefs.defaults['librarian'] except: prefs.defaults['librarian'] = 'l'