This commit is contained in:
Fabian Graßl 2010-10-07 20:18:48 +02:00
commit 62dae4bfc3
9 changed files with 200 additions and 48 deletions

View File

@ -1,13 +1,10 @@
#!/usr/bin/env python
__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
'''
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class NewYorker(BasicNewsRecipe):
title = 'The New Yorker'
@ -15,36 +12,46 @@ class NewYorker(BasicNewsRecipe):
description = 'The best of US journalism'
oldest_article = 15
language = 'en'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
publisher = 'Conde Nast Publications'
category = 'news, politics, USA'
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'})]
remove_tags_after = dict(name='div',attrs={'id':'articlebody'})
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='div', attrs={'id':['articleheads','articleRail','articletext','photocredits']})]
remove_tags = [
dict(name='div', attrs={'class':['utils','articleRailLinks','icons'] })
,dict(name='link')
dict(name=['meta','iframe','base','link','embed','object'])
,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')]
def print_version(self, url):
return url + '?printable=true'
def get_article_url(self, article):
return article.get('guid', None)
def image_url_processor(self, baseurl, url):
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

View File

@ -38,6 +38,7 @@ class SafeFormat(TemplateFormatter):
def get_value(self, key, args, kwargs):
try:
key = field_metadata.search_term_to_field_key(key.lower())
b = self.book.get_user_metadata(key, False)
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
v = ''
@ -221,6 +222,11 @@ class Metadata(object):
v = _data.get(attr, None)
if v is not None:
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():
v = self.get(attr, None)
if v is not None:

View File

@ -17,7 +17,7 @@ from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
question_dialog, info_dialog
from calibre.gui2.actions import InterfaceAction
class LibraryUsageStats(object):
class LibraryUsageStats(object): # {{{
def __init__(self):
self.stats = {}
@ -73,7 +73,7 @@ class LibraryUsageStats(object):
if stats is not None:
self.stats[newloc] = stats
self.write_stats()
# }}}
class ChooseLibraryAction(InterfaceAction):
@ -147,9 +147,11 @@ class ChooseLibraryAction(InterfaceAction):
self.qs_locations = [i[1] for i in locations]
self.rename_menu.clear()
self.delete_menu.clear()
quick_actions = []
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)))
quick_actions.append(ac)
self.rename_menu.addAction(name, Dispatcher(partial(self.rename_requested,
name, loc)))
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.rename_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):
@ -263,11 +266,6 @@ class ChooseLibraryAction(InterfaceAction):
c.exec_()
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():
warning_dialog(self.gui, _('Not allowed'),
_('You cannot change libraries while jobs'

View File

@ -299,7 +299,9 @@ class Series(Base):
val, s_index = self.gui_val
val = self.normalize_ui_val(val)
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':
s_index = self.db.get_next_cc_series_num_for(val,
num=self.col_id)
@ -488,7 +490,7 @@ class BulkSeries(BulkBase):
def commit(self, book_ids, notify=False):
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 != '':
extras = []
next_index = self.db.get_next_cc_series_num_for(val, num=self.col_id)

View File

@ -24,6 +24,7 @@ class LocationManager(QObject): # {{{
locations_changed = pyqtSignal()
unmount_device = pyqtSignal()
location_selected = pyqtSignal(object)
switch_actions_set = pyqtSignal(object)
def __init__(self, parent=None):
QObject.__init__(self, parent)
@ -60,7 +61,7 @@ class LocationManager(QObject): # {{{
return ac
ac('library', _('Library'), 'lt.png',
self.library_action = ac('library', _('Library'), 'lt.png',
_('Show books in calibre library'))
ac('main', _('Device'), 'reader.png',
_('Show books in the main memory of the device'))
@ -69,6 +70,13 @@ class LocationManager(QObject): # {{{
ac('cardb', _('Card B'), 'sd.png',
_('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):
if location != self.current_location and hasattr(self,
'location_'+location):
@ -197,14 +205,14 @@ class SearchBar(QWidget): # {{{
# }}}
class Spacer(QWidget):
class Spacer(QWidget): # {{{
def __init__(self, parent):
QWidget.__init__(self, parent)
self.l = QHBoxLayout()
self.setLayout(self.l)
self.l.addStretch(10)
# }}}
class ToolBar(QToolBar): # {{{

View File

@ -743,6 +743,8 @@ class BooksModel(QAbstractTableModel): # {{{
val = qt_to_dt(val, as_utc=False)
elif typ == 'series':
val, s_index = parse_series_string(self.db, label, value.toString())
if not val:
val = s_index = None
elif typ == 'composite':
tmpl = unicode(value.toString()).strip()
disp = cc['display']

View File

@ -390,6 +390,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
except:
import traceback
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):
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name())

View File

@ -956,12 +956,6 @@ def command_check_library(args, dbpath):
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():
parser = get_parser(_(
'''
@ -1015,6 +1009,134 @@ def command_restore_database(args, dbpath):
prints('Some errors occurred. A detailed report was '
'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():
parser = OptionParser(_(
'''\

View File

@ -59,10 +59,10 @@ class TemplateFormatter(string.Formatter):
return value_if_empty
def _shorten(self, val, leading, center_string, trailing):
l = int(leading)
t = int(trailing)
l = max(0, int(leading))
t = max(0, int(trailing))
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:
return val