mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
GwR bug fixes to iTunes driver
This commit is contained in:
commit
d6f975e9f2
@ -1,13 +1,10 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
newyorker.com
|
newyorker.com
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import Tag
|
|
||||||
|
|
||||||
class NewYorker(BasicNewsRecipe):
|
class NewYorker(BasicNewsRecipe):
|
||||||
title = 'The New Yorker'
|
title = 'The New Yorker'
|
||||||
@ -15,36 +12,46 @@ class NewYorker(BasicNewsRecipe):
|
|||||||
description = 'The best of US journalism'
|
description = 'The best of US journalism'
|
||||||
oldest_article = 15
|
oldest_article = 15
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
publisher = 'Conde Nast Publications'
|
publisher = 'Conde Nast Publications'
|
||||||
category = 'news, politics, USA'
|
category = 'news, politics, USA'
|
||||||
encoding = 'cp1252'
|
encoding = 'cp1252'
|
||||||
|
publication_type = 'magazine'
|
||||||
|
masthead_url = 'http://www.newyorker.com/css/i/hed/logo.gif'
|
||||||
|
extra_css = """
|
||||||
|
body {font-family: "Times New Roman",Times,serif}
|
||||||
|
.articleauthor{color: #9F9F9F; font-family: Arial, sans-serif; font-size: small; text-transform: uppercase}
|
||||||
|
.rubric{color: #CD0021; font-family: Arial, sans-serif; font-size: small; text-transform: uppercase}
|
||||||
|
"""
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'printbody'})]
|
conversion_options = {
|
||||||
remove_tags_after = dict(name='div',attrs={'id':'articlebody'})
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher' : publisher
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [dict(name='div', attrs={'id':['articleheads','articleRail','articletext','photocredits']})]
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='div', attrs={'class':['utils','articleRailLinks','icons'] })
|
dict(name=['meta','iframe','base','link','embed','object'])
|
||||||
,dict(name='link')
|
,dict(name='div', attrs={'class':['utils','articleRailLinks','icons'] })
|
||||||
]
|
]
|
||||||
|
remove_attributes = ['lang']
|
||||||
feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')]
|
feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')]
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
return url + '?printable=true'
|
return url + '?printable=true'
|
||||||
|
|
||||||
def get_article_url(self, article):
|
def image_url_processor(self, baseurl, url):
|
||||||
return article.get('guid', None)
|
return url.strip()
|
||||||
|
|
||||||
|
def get_cover_url(self):
|
||||||
|
cover_url = None
|
||||||
|
soup = self.index_to_soup('http://www.newyorker.com/magazine/toc/')
|
||||||
|
cover_item = soup.find('img',attrs={'id':'inThisIssuePhoto'})
|
||||||
|
if cover_item:
|
||||||
|
cover_url = 'http://www.newyorker.com' + cover_item['src'].strip()
|
||||||
|
return cover_url
|
||||||
|
|
||||||
def postprocess_html(self, soup, x):
|
|
||||||
body = soup.find('body')
|
|
||||||
if body:
|
|
||||||
html = soup.find('html')
|
|
||||||
if html:
|
|
||||||
body.extract()
|
|
||||||
html.insert(2, body)
|
|
||||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")])
|
|
||||||
soup.head.insert(1,mcharset)
|
|
||||||
return soup
|
|
||||||
|
@ -260,6 +260,8 @@ class ITUNES(DriverBase):
|
|||||||
self.report_progress(1.0, _('Updating device metadata listing...'))
|
self.report_progress(1.0, _('Updating device metadata listing...'))
|
||||||
|
|
||||||
# Add new books to booklists[0]
|
# Add new books to booklists[0]
|
||||||
|
# Charles thinks this should be
|
||||||
|
# for new_book in metadata[0]:
|
||||||
for new_book in locations[0]:
|
for new_book in locations[0]:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" adding '%s' by '%s' to booklists[0]" %
|
self.log.info(" adding '%s' by '%s' to booklists[0]" %
|
||||||
@ -1209,6 +1211,10 @@ class ITUNES(DriverBase):
|
|||||||
except:
|
except:
|
||||||
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
|
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
|
||||||
self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title))
|
self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title))
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
return thumb
|
return thumb
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -2401,7 +2407,7 @@ class ITUNES(DriverBase):
|
|||||||
try:
|
try:
|
||||||
storage_path = os.path.split(cached_book['lib_book'].location().path)
|
storage_path = os.path.split(cached_book['lib_book'].location().path)
|
||||||
if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \
|
if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \
|
||||||
not storage_path[0].startswith(self.calibre_library_path):
|
not storage_path[0].startswith(prefs['library_path']):
|
||||||
title_storage_path = storage_path[0]
|
title_storage_path = storage_path[0]
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" removing title_storage_path: %s" % title_storage_path)
|
self.log.info(" removing title_storage_path: %s" % title_storage_path)
|
||||||
@ -2453,7 +2459,7 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
if book:
|
if book:
|
||||||
if self.iTunes_media and path.startswith(self.iTunes_media) and \
|
if self.iTunes_media and path.startswith(self.iTunes_media) and \
|
||||||
not path.startswith(self.calibre_library_path):
|
not path.startswith(prefs['library_path']):
|
||||||
storage_path = os.path.split(path)
|
storage_path = os.path.split(path)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" removing '%s' at %s" %
|
self.log.info(" removing '%s' at %s" %
|
||||||
|
@ -411,6 +411,22 @@ class DevicePlugin(Plugin):
|
|||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def set_plugboards(self, plugboards, pb_func):
|
||||||
|
'''
|
||||||
|
provide the driver the current set of plugboards and a function to
|
||||||
|
select a specific plugboard. This method is called immediately before
|
||||||
|
add_books and sync_booklists.
|
||||||
|
|
||||||
|
pb_func is a callable with the following signature:
|
||||||
|
def pb_func(device_name, format, plugboards)
|
||||||
|
You give it the current device name (either the class name or
|
||||||
|
DEVICE_PLUGBOARD_NAME), the format you are interested in (a 'real'
|
||||||
|
format or 'device_db'), and the plugboards (you were given those by
|
||||||
|
set_plugboards, the same place you got this method).
|
||||||
|
|
||||||
|
Return value: None or a single plugboard instance.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
class BookList(list):
|
class BookList(list):
|
||||||
'''
|
'''
|
||||||
|
@ -38,6 +38,7 @@ class SafeFormat(TemplateFormatter):
|
|||||||
|
|
||||||
def get_value(self, key, args, kwargs):
|
def get_value(self, key, args, kwargs):
|
||||||
try:
|
try:
|
||||||
|
key = field_metadata.search_term_to_field_key(key.lower())
|
||||||
b = self.book.get_user_metadata(key, False)
|
b = self.book.get_user_metadata(key, False)
|
||||||
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
|
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
|
||||||
v = ''
|
v = ''
|
||||||
@ -221,6 +222,11 @@ class Metadata(object):
|
|||||||
v = _data.get(attr, None)
|
v = _data.get(attr, None)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
result[attr] = v
|
result[attr] = v
|
||||||
|
# separate these because it uses the self.get(), not _data.get()
|
||||||
|
for attr in TOP_LEVEL_CLASSIFIERS:
|
||||||
|
v = self.get(attr, None)
|
||||||
|
if v is not None:
|
||||||
|
result[attr] = v
|
||||||
for attr in _data['user_metadata'].iterkeys():
|
for attr in _data['user_metadata'].iterkeys():
|
||||||
v = self.get(attr, None)
|
v = self.get(attr, None)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
|
@ -83,7 +83,7 @@ class ISBNDBMetadata(Metadata):
|
|||||||
|
|
||||||
summ = tostring(book.find('summary'))
|
summ = tostring(book.find('summary'))
|
||||||
if summ:
|
if summ:
|
||||||
self.comments = 'SUMMARY:\n'+summ.string
|
self.comments = 'SUMMARY:\n'+summ
|
||||||
|
|
||||||
|
|
||||||
def build_isbn(base_url, opts):
|
def build_isbn(base_url, opts):
|
||||||
|
@ -17,7 +17,7 @@ from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
|
|||||||
question_dialog, info_dialog
|
question_dialog, info_dialog
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
|
||||||
class LibraryUsageStats(object):
|
class LibraryUsageStats(object): # {{{
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.stats = {}
|
self.stats = {}
|
||||||
@ -73,7 +73,7 @@ class LibraryUsageStats(object):
|
|||||||
if stats is not None:
|
if stats is not None:
|
||||||
self.stats[newloc] = stats
|
self.stats[newloc] = stats
|
||||||
self.write_stats()
|
self.write_stats()
|
||||||
|
# }}}
|
||||||
|
|
||||||
class ChooseLibraryAction(InterfaceAction):
|
class ChooseLibraryAction(InterfaceAction):
|
||||||
|
|
||||||
@ -147,9 +147,11 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
self.qs_locations = [i[1] for i in locations]
|
self.qs_locations = [i[1] for i in locations]
|
||||||
self.rename_menu.clear()
|
self.rename_menu.clear()
|
||||||
self.delete_menu.clear()
|
self.delete_menu.clear()
|
||||||
|
quick_actions = []
|
||||||
for name, loc in locations:
|
for name, loc in locations:
|
||||||
self.quick_menu.addAction(name, Dispatcher(partial(self.switch_requested,
|
ac = self.quick_menu.addAction(name, Dispatcher(partial(self.switch_requested,
|
||||||
loc)))
|
loc)))
|
||||||
|
quick_actions.append(ac)
|
||||||
self.rename_menu.addAction(name, Dispatcher(partial(self.rename_requested,
|
self.rename_menu.addAction(name, Dispatcher(partial(self.rename_requested,
|
||||||
name, loc)))
|
name, loc)))
|
||||||
self.delete_menu.addAction(name, Dispatcher(partial(self.delete_requested,
|
self.delete_menu.addAction(name, Dispatcher(partial(self.delete_requested,
|
||||||
@ -164,6 +166,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
self.quick_menu_action.setVisible(bool(locations))
|
self.quick_menu_action.setVisible(bool(locations))
|
||||||
self.rename_menu_action.setVisible(bool(locations))
|
self.rename_menu_action.setVisible(bool(locations))
|
||||||
self.delete_menu_action.setVisible(bool(locations))
|
self.delete_menu_action.setVisible(bool(locations))
|
||||||
|
self.gui.location_manager.set_switch_actions(quick_actions)
|
||||||
|
|
||||||
|
|
||||||
def location_selected(self, loc):
|
def location_selected(self, loc):
|
||||||
@ -263,11 +266,6 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
c.exec_()
|
c.exec_()
|
||||||
|
|
||||||
def change_library_allowed(self):
|
def change_library_allowed(self):
|
||||||
if self.gui.device_connected:
|
|
||||||
warning_dialog(self.gui, _('Not allowed'),
|
|
||||||
_('You cannot change libraries when a device is'
|
|
||||||
' connected.'), show=True)
|
|
||||||
return False
|
|
||||||
if self.gui.job_manager.has_jobs():
|
if self.gui.job_manager.has_jobs():
|
||||||
warning_dialog(self.gui, _('Not allowed'),
|
warning_dialog(self.gui, _('Not allowed'),
|
||||||
_('You cannot change libraries while jobs'
|
_('You cannot change libraries while jobs'
|
||||||
|
@ -149,6 +149,18 @@ class DeleteAction(InterfaceAction):
|
|||||||
self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(),
|
self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(),
|
||||||
self.gui.library_view.currentIndex())
|
self.gui.library_view.currentIndex())
|
||||||
|
|
||||||
|
|
||||||
|
def library_ids_deleted(self, ids_deleted, current_row=None):
|
||||||
|
view = self.gui.library_view
|
||||||
|
for v in (self.gui.memory_view, self.gui.card_a_view, self.gui.card_b_view):
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
v.model().clear_ondevice(ids_deleted)
|
||||||
|
if current_row is not None:
|
||||||
|
ci = view.model().index(current_row, 0)
|
||||||
|
if ci.isValid():
|
||||||
|
view.set_current_row(current_row)
|
||||||
|
|
||||||
def delete_books(self, *args):
|
def delete_books(self, *args):
|
||||||
'''
|
'''
|
||||||
Delete selected books from device or library.
|
Delete selected books from device or library.
|
||||||
@ -168,14 +180,7 @@ class DeleteAction(InterfaceAction):
|
|||||||
if ci.isValid():
|
if ci.isValid():
|
||||||
row = ci.row()
|
row = ci.row()
|
||||||
ids_deleted = view.model().delete_books(rows)
|
ids_deleted = view.model().delete_books(rows)
|
||||||
for v in (self.gui.memory_view, self.gui.card_a_view, self.gui.card_b_view):
|
self.library_ids_deleted(ids_deleted, row)
|
||||||
if v is None:
|
|
||||||
continue
|
|
||||||
v.model().clear_ondevice(ids_deleted)
|
|
||||||
if row is not None:
|
|
||||||
ci = view.model().index(row, 0)
|
|
||||||
if ci.isValid():
|
|
||||||
view.set_current_row(row)
|
|
||||||
else:
|
else:
|
||||||
if not confirm('<p>'+_('The selected books will be '
|
if not confirm('<p>'+_('The selected books will be '
|
||||||
'<b>permanently deleted</b> '
|
'<b>permanently deleted</b> '
|
||||||
|
@ -299,7 +299,9 @@ class Series(Base):
|
|||||||
val, s_index = self.gui_val
|
val, s_index = self.gui_val
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if val != self.initial_val or s_index != self.initial_index:
|
if val != self.initial_val or s_index != self.initial_index:
|
||||||
if s_index == 0.0:
|
if val == '':
|
||||||
|
val = s_index = None
|
||||||
|
elif s_index == 0.0:
|
||||||
if tweaks['series_index_auto_increment'] == 'next':
|
if tweaks['series_index_auto_increment'] == 'next':
|
||||||
s_index = self.db.get_next_cc_series_num_for(val,
|
s_index = self.db.get_next_cc_series_num_for(val,
|
||||||
num=self.col_id)
|
num=self.col_id)
|
||||||
@ -488,7 +490,7 @@ class BulkSeries(BulkBase):
|
|||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
val, update_indices, force_start, at_value, clear = self.gui_val
|
val, update_indices, force_start, at_value, clear = self.gui_val
|
||||||
val = '' if clear else self.normalize_ui_val(val)
|
val = None if clear else self.normalize_ui_val(val)
|
||||||
if clear or val != '':
|
if clear or val != '':
|
||||||
extras = []
|
extras = []
|
||||||
next_index = self.db.get_next_cc_series_num_for(val, num=self.col_id)
|
next_index = self.db.get_next_cc_series_num_for(val, num=self.col_id)
|
||||||
|
@ -24,6 +24,7 @@ class LocationManager(QObject): # {{{
|
|||||||
locations_changed = pyqtSignal()
|
locations_changed = pyqtSignal()
|
||||||
unmount_device = pyqtSignal()
|
unmount_device = pyqtSignal()
|
||||||
location_selected = pyqtSignal(object)
|
location_selected = pyqtSignal(object)
|
||||||
|
switch_actions_set = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QObject.__init__(self, parent)
|
QObject.__init__(self, parent)
|
||||||
@ -60,7 +61,7 @@ class LocationManager(QObject): # {{{
|
|||||||
|
|
||||||
return ac
|
return ac
|
||||||
|
|
||||||
ac('library', _('Library'), 'lt.png',
|
self.library_action = ac('library', _('Library'), 'lt.png',
|
||||||
_('Show books in calibre library'))
|
_('Show books in calibre library'))
|
||||||
ac('main', _('Device'), 'reader.png',
|
ac('main', _('Device'), 'reader.png',
|
||||||
_('Show books in the main memory of the device'))
|
_('Show books in the main memory of the device'))
|
||||||
@ -69,6 +70,13 @@ class LocationManager(QObject): # {{{
|
|||||||
ac('cardb', _('Card B'), 'sd.png',
|
ac('cardb', _('Card B'), 'sd.png',
|
||||||
_('Show books in storage card B'))
|
_('Show books in storage card B'))
|
||||||
|
|
||||||
|
def set_switch_actions(self, actions):
|
||||||
|
self.switch_menu = QMenu()
|
||||||
|
for ac in actions:
|
||||||
|
self.switch_menu.addAction(ac)
|
||||||
|
self.library_action.setMenu(self.switch_menu)
|
||||||
|
self.switch_actions_set.emit(bool(actions))
|
||||||
|
|
||||||
def _location_selected(self, location, *args):
|
def _location_selected(self, location, *args):
|
||||||
if location != self.current_location and hasattr(self,
|
if location != self.current_location and hasattr(self,
|
||||||
'location_'+location):
|
'location_'+location):
|
||||||
@ -197,14 +205,14 @@ class SearchBar(QWidget): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class Spacer(QWidget):
|
class Spacer(QWidget): # {{{
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.l = QHBoxLayout()
|
self.l = QHBoxLayout()
|
||||||
self.setLayout(self.l)
|
self.setLayout(self.l)
|
||||||
self.l.addStretch(10)
|
self.l.addStretch(10)
|
||||||
|
# }}}
|
||||||
|
|
||||||
class ToolBar(QToolBar): # {{{
|
class ToolBar(QToolBar): # {{{
|
||||||
|
|
||||||
|
@ -390,6 +390,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
if self.device_connected:
|
||||||
|
self.set_books_in_library(self.booklists(), reset=True)
|
||||||
|
self.refresh_ondevice()
|
||||||
|
self.memory_view.reset()
|
||||||
|
self.card_a_view.reset()
|
||||||
|
self.card_b_view.reset()
|
||||||
|
|
||||||
|
|
||||||
def set_window_title(self):
|
def set_window_title(self):
|
||||||
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name())
|
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name())
|
||||||
|
@ -166,6 +166,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
def __init__(self, pathtoebook=None, debug_javascript=False):
|
def __init__(self, pathtoebook=None, debug_javascript=False):
|
||||||
MainWindow.__init__(self, None)
|
MainWindow.__init__(self, None)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.show_toc_on_open = False
|
||||||
|
self.current_book_has_toc = False
|
||||||
self.base_window_title = unicode(self.windowTitle())
|
self.base_window_title = unicode(self.windowTitle())
|
||||||
self.iterator = None
|
self.iterator = None
|
||||||
self.current_page = None
|
self.current_page = None
|
||||||
@ -214,11 +216,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.action_metadata.setCheckable(True)
|
self.action_metadata.setCheckable(True)
|
||||||
self.action_metadata.setShortcut(Qt.CTRL+Qt.Key_I)
|
self.action_metadata.setShortcut(Qt.CTRL+Qt.Key_I)
|
||||||
self.action_table_of_contents.setCheckable(True)
|
self.action_table_of_contents.setCheckable(True)
|
||||||
|
self.toc.setMinimumWidth(80)
|
||||||
self.action_reference_mode.setCheckable(True)
|
self.action_reference_mode.setCheckable(True)
|
||||||
self.connect(self.action_reference_mode, SIGNAL('triggered(bool)'),
|
self.connect(self.action_reference_mode, SIGNAL('triggered(bool)'),
|
||||||
lambda x: self.view.reference_mode(x))
|
lambda x: self.view.reference_mode(x))
|
||||||
self.connect(self.action_metadata, SIGNAL('triggered(bool)'), lambda x:self.metadata.setVisible(x))
|
self.connect(self.action_metadata, SIGNAL('triggered(bool)'), lambda x:self.metadata.setVisible(x))
|
||||||
self.connect(self.action_table_of_contents, SIGNAL('triggered(bool)'), lambda x:self.toc.setVisible(x))
|
self.connect(self.action_table_of_contents, SIGNAL('toggled(bool)'), lambda x:self.toc.setVisible(x))
|
||||||
self.connect(self.action_copy, SIGNAL('triggered(bool)'), self.copy)
|
self.connect(self.action_copy, SIGNAL('triggered(bool)'), self.copy)
|
||||||
self.connect(self.action_font_size_larger, SIGNAL('triggered(bool)'),
|
self.connect(self.action_font_size_larger, SIGNAL('triggered(bool)'),
|
||||||
self.font_size_larger)
|
self.font_size_larger)
|
||||||
@ -259,7 +262,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
f = functools.partial(self.load_ebook, pathtoebook)
|
f = functools.partial(self.load_ebook, pathtoebook)
|
||||||
QTimer.singleShot(50, f)
|
QTimer.singleShot(50, f)
|
||||||
self.view.setMinimumSize(100, 100)
|
self.view.setMinimumSize(100, 100)
|
||||||
self.splitter.setSizes([1, 300])
|
|
||||||
self.toc.setCursor(Qt.PointingHandCursor)
|
self.toc.setCursor(Qt.PointingHandCursor)
|
||||||
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
|
self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
@ -285,6 +287,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
def save_state(self):
|
def save_state(self):
|
||||||
state = str(self.saveState(self.STATE_VERSION))
|
state = str(self.saveState(self.STATE_VERSION))
|
||||||
dynamic['viewer_toolbar_state'] = state
|
dynamic['viewer_toolbar_state'] = state
|
||||||
|
dynamic.set('viewer_window_geometry', self.saveGeometry())
|
||||||
|
if self.current_book_has_toc:
|
||||||
|
dynamic.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
|
||||||
|
if self.toc.isVisible():
|
||||||
|
dynamic.set('viewer_splitter_state',
|
||||||
|
bytearray(self.splitter.saveState()))
|
||||||
|
|
||||||
def restore_state(self):
|
def restore_state(self):
|
||||||
state = dynamic.get('viewer_toolbar_state', None)
|
state = dynamic.get('viewer_toolbar_state', None)
|
||||||
@ -609,10 +617,15 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
title = self.iterator.opf.title
|
title = self.iterator.opf.title
|
||||||
if not title:
|
if not title:
|
||||||
title = os.path.splitext(os.path.basename(pathtoebook))[0]
|
title = os.path.splitext(os.path.basename(pathtoebook))[0]
|
||||||
self.action_table_of_contents.setDisabled(not self.iterator.toc)
|
|
||||||
if self.iterator.toc:
|
if self.iterator.toc:
|
||||||
self.toc_model = TOC(self.iterator.toc)
|
self.toc_model = TOC(self.iterator.toc)
|
||||||
self.toc.setModel(self.toc_model)
|
self.toc.setModel(self.toc_model)
|
||||||
|
if self.show_toc_on_open:
|
||||||
|
self.action_table_of_contents.setChecked(True)
|
||||||
|
else:
|
||||||
|
self.action_table_of_contents.setChecked(False)
|
||||||
|
self.action_table_of_contents.setDisabled(not self.iterator.toc)
|
||||||
|
self.current_book_has_toc = bool(self.iterator.toc)
|
||||||
self.current_title = title
|
self.current_title = title
|
||||||
self.setWindowTitle(self.base_window_title+' - '+title)
|
self.setWindowTitle(self.base_window_title+' - '+title)
|
||||||
self.pos.setMaximum(sum(self.iterator.pages))
|
self.pos.setMaximum(sum(self.iterator.pages))
|
||||||
@ -656,22 +669,21 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
self.write_settings()
|
|
||||||
if self.iterator is not None:
|
if self.iterator is not None:
|
||||||
self.save_current_position()
|
self.save_current_position()
|
||||||
self.iterator.__exit__(*args)
|
self.iterator.__exit__(*args)
|
||||||
|
|
||||||
def write_settings(self):
|
|
||||||
dynamic.set('viewer_window_geometry', self.saveGeometry())
|
|
||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
c = config().parse()
|
c = config().parse()
|
||||||
wg = dynamic['viewer_window_geometry']
|
self.splitter.setSizes([1, 300])
|
||||||
if wg is not None and c.remember_window_size:
|
if c.remember_window_size:
|
||||||
|
wg = dynamic.get('viewer_window_geometry', None)
|
||||||
|
if wg is not None:
|
||||||
self.restoreGeometry(wg)
|
self.restoreGeometry(wg)
|
||||||
|
ss = dynamic.get('viewer_splitter_state', None)
|
||||||
|
if ss is not None:
|
||||||
|
self.splitter.restoreState(ss)
|
||||||
|
self.show_toc_on_open = dynamic.get('viewer_toc_isvisible', False)
|
||||||
|
|
||||||
def config(defaults=None):
|
def config(defaults=None):
|
||||||
desc = _('Options to control the ebook viewer')
|
desc = _('Options to control the ebook viewer')
|
||||||
|
@ -956,12 +956,6 @@ def command_check_library(args, dbpath):
|
|||||||
print_one(checker, check)
|
print_one(checker, check)
|
||||||
|
|
||||||
|
|
||||||
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
|
||||||
'show_metadata', 'set_metadata', 'export', 'catalog',
|
|
||||||
'saved_searches', 'add_custom_column', 'custom_columns',
|
|
||||||
'remove_custom_column', 'set_custom', 'restore_database',
|
|
||||||
'check_library')
|
|
||||||
|
|
||||||
def restore_database_option_parser():
|
def restore_database_option_parser():
|
||||||
parser = get_parser(_(
|
parser = get_parser(_(
|
||||||
'''
|
'''
|
||||||
@ -1015,6 +1009,134 @@ def command_restore_database(args, dbpath):
|
|||||||
prints('Some errors occurred. A detailed report was '
|
prints('Some errors occurred. A detailed report was '
|
||||||
'saved to', name)
|
'saved to', name)
|
||||||
|
|
||||||
|
def list_categories_option_parser():
|
||||||
|
from calibre.library.check_library import CHECKS
|
||||||
|
parser = get_parser(_('''\
|
||||||
|
%prog list_categories [options]
|
||||||
|
|
||||||
|
Produce a report of the category information in the database. The
|
||||||
|
information is the equivalent of what is shown in the tags pane.
|
||||||
|
'''))
|
||||||
|
|
||||||
|
parser.add_option('-i', '--item_count', default=False, action='store_true',
|
||||||
|
help=_('Output only the number of items in a category instead of the '
|
||||||
|
'counts per item within the category'))
|
||||||
|
parser.add_option('-c', '--csv', default=False, action='store_true',
|
||||||
|
help=_('Output in CSV'))
|
||||||
|
parser.add_option('-q', '--quote', default='"',
|
||||||
|
help=_('The character to put around the category value in CSV mode. '
|
||||||
|
'Default is quotes (").'))
|
||||||
|
parser.add_option('-r', '--categories', default=None, dest='report',
|
||||||
|
help=_("Comma-separated list of category lookup names.\n"
|
||||||
|
"Default: all"))
|
||||||
|
parser.add_option('-w', '--line-width', default=-1, type=int,
|
||||||
|
help=_('The maximum width of a single line in the output. '
|
||||||
|
'Defaults to detecting screen size.'))
|
||||||
|
parser.add_option('-s', '--separator', default=',',
|
||||||
|
help=_('The string used to separate fields in CSV mode. '
|
||||||
|
'Default is a comma.'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def command_list_categories(args, dbpath):
|
||||||
|
parser = list_categories_option_parser()
|
||||||
|
opts, args = parser.parse_args(args)
|
||||||
|
if len(args) != 0:
|
||||||
|
parser.print_help()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if opts.library_path is not None:
|
||||||
|
dbpath = opts.library_path
|
||||||
|
|
||||||
|
if isbytestring(dbpath):
|
||||||
|
dbpath = dbpath.decode(preferred_encoding)
|
||||||
|
|
||||||
|
db = LibraryDatabase2(dbpath)
|
||||||
|
category_data = db.get_categories()
|
||||||
|
data = []
|
||||||
|
categories = [k for k in category_data.keys()
|
||||||
|
if db.metadata_for_field(k)['kind'] not in ['user', 'search']]
|
||||||
|
|
||||||
|
categories.sort(cmp=lambda x,y: cmp(x if x[0] != '#' else x[1:],
|
||||||
|
y if y[0] != '#' else y[1:]))
|
||||||
|
if not opts.item_count:
|
||||||
|
for category in categories:
|
||||||
|
is_rating = db.metadata_for_field(category)['datatype'] == 'rating'
|
||||||
|
for tag in category_data[category]:
|
||||||
|
if is_rating:
|
||||||
|
tag.name = unicode(len(tag.name))
|
||||||
|
data.append({'category':category, 'tag_name':tag.name,
|
||||||
|
'count':unicode(tag.count), 'rating':unicode(tag.avg_rating)})
|
||||||
|
else:
|
||||||
|
for category in categories:
|
||||||
|
data.append({'category':category,
|
||||||
|
'tag_name':_('CATEGORY ITEMS'),
|
||||||
|
'count': len(category_data[category]), 'rating': 0.0})
|
||||||
|
|
||||||
|
fields = ['category', 'tag_name', 'count', 'rating']
|
||||||
|
|
||||||
|
def do_list():
|
||||||
|
separator = ' '
|
||||||
|
widths = list(map(lambda x : 0, fields))
|
||||||
|
for i in data:
|
||||||
|
for j, field in enumerate(fields):
|
||||||
|
widths[j] = max(widths[j], max(len(field), len(unicode(i[field]))))
|
||||||
|
|
||||||
|
screen_width = terminal_controller.COLS if opts.line_width < 0 else opts.line_width
|
||||||
|
if not screen_width:
|
||||||
|
screen_width = 80
|
||||||
|
field_width = screen_width//len(fields)
|
||||||
|
base_widths = map(lambda x: min(x+1, field_width), widths)
|
||||||
|
|
||||||
|
while sum(base_widths) < screen_width:
|
||||||
|
adjusted = False
|
||||||
|
for i in range(len(widths)):
|
||||||
|
if base_widths[i] < widths[i]:
|
||||||
|
base_widths[i] += min(screen_width-sum(base_widths), widths[i]-base_widths[i])
|
||||||
|
adjusted = True
|
||||||
|
break
|
||||||
|
if not adjusted:
|
||||||
|
break
|
||||||
|
|
||||||
|
widths = list(base_widths)
|
||||||
|
titles = map(lambda x, y: '%-*s%s'%(x-len(separator), y, separator),
|
||||||
|
widths, fields)
|
||||||
|
print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL
|
||||||
|
|
||||||
|
wrappers = map(lambda x: TextWrapper(x-1), widths)
|
||||||
|
o = cStringIO.StringIO()
|
||||||
|
|
||||||
|
for record in data:
|
||||||
|
text = [wrappers[i].wrap(unicode(record[field]).encode('utf-8')) for i, field in enumerate(fields)]
|
||||||
|
lines = max(map(len, text))
|
||||||
|
for l in range(lines):
|
||||||
|
for i, field in enumerate(text):
|
||||||
|
ft = text[i][l] if l < len(text[i]) else ''
|
||||||
|
filler = '%*s'%(widths[i]-len(ft)-1, '')
|
||||||
|
o.write(ft)
|
||||||
|
o.write(filler+separator)
|
||||||
|
print >>o
|
||||||
|
print o.getvalue()
|
||||||
|
|
||||||
|
def do_csv():
|
||||||
|
lf = '{category},"{tag_name}",{count},{rating}'
|
||||||
|
lf = lf.replace(',', opts.separator).replace(r'\t','\t').replace(r'\n','\n')
|
||||||
|
lf = lf.replace('"', opts.quote)
|
||||||
|
for d in data:
|
||||||
|
print lf.format(**d)
|
||||||
|
|
||||||
|
if opts.csv:
|
||||||
|
do_csv()
|
||||||
|
else:
|
||||||
|
do_list()
|
||||||
|
|
||||||
|
|
||||||
|
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||||
|
'show_metadata', 'set_metadata', 'export', 'catalog',
|
||||||
|
'saved_searches', 'add_custom_column', 'custom_columns',
|
||||||
|
'remove_custom_column', 'set_custom', 'restore_database',
|
||||||
|
'check_library', 'list_categories')
|
||||||
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = OptionParser(_(
|
parser = OptionParser(_(
|
||||||
'''\
|
'''\
|
||||||
|
@ -59,10 +59,10 @@ class TemplateFormatter(string.Formatter):
|
|||||||
return value_if_empty
|
return value_if_empty
|
||||||
|
|
||||||
def _shorten(self, val, leading, center_string, trailing):
|
def _shorten(self, val, leading, center_string, trailing):
|
||||||
l = int(leading)
|
l = max(0, int(leading))
|
||||||
t = int(trailing)
|
t = max(0, int(trailing))
|
||||||
if len(val) > l + len(center_string) + t:
|
if len(val) > l + len(center_string) + t:
|
||||||
return val[0:l] + center_string + val[-t:]
|
return val[0:l] + center_string + ('' if t == 0 else val[-t:])
|
||||||
else:
|
else:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user