Pull from trunk

This commit is contained in:
Kovid Goyal 2009-04-08 19:58:55 -07:00
commit 998c9f22a1
56 changed files with 23409 additions and 10914 deletions

View File

@ -13,12 +13,14 @@ def devices():
from calibre.devices.kindle.driver import KINDLE from calibre.devices.kindle.driver import KINDLE
from calibre.devices.kindle.driver import KINDLE2 from calibre.devices.kindle.driver import KINDLE2
from calibre.devices.blackberry.driver import BLACKBERRY from calibre.devices.blackberry.driver import BLACKBERRY
return (PRS500, PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY) from calibre.devices.eb600.driver import EB600
return (PRS500, PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2,
BLACKBERRY, EB600)
import time import time
DAY_MAP = dict(Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6) DAY_MAP = dict(Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6)
MONTH_MAP = dict(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12) MONTH_MAP = dict(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12)
INVERSE_DAY_MAP = dict(zip(DAY_MAP.values(), DAY_MAP.keys())) INVERSE_DAY_MAP = dict(zip(DAY_MAP.values(), DAY_MAP.keys()))
INVERSE_MONTH_MAP = dict(zip(MONTH_MAP.values(), MONTH_MAP.keys())) INVERSE_MONTH_MAP = dict(zip(MONTH_MAP.values(), MONTH_MAP.keys()))

View File

@ -11,37 +11,36 @@ from calibre.ebooks.metadata import authors_to_string
from calibre.devices.errors import FreeSpaceError from calibre.devices.errors import FreeSpaceError
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
import calibre.devices.cybookg3.t2b as t2b import calibre.devices.cybookg3.t2b as t2b
from calibre.devices.errors import FreeSpaceError
class CYBOOKG3(USBMS): class CYBOOKG3(USBMS):
# Ordered list of supported formats # Ordered list of supported formats
# Be sure these have an entry in calibre.devices.mime # Be sure these have an entry in calibre.devices.mime
FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt'] FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt']
VENDOR_ID = [0x0bda, 0x3034] VENDOR_ID = [0x0bda, 0x3034]
PRODUCT_ID = [0x0703, 0x1795] PRODUCT_ID = [0x0703, 0x1795]
BCD = [0x110, 0x132] BCD = [0x110, 0x132]
VENDOR_NAME = 'BOOKEEN' VENDOR_NAME = 'BOOKEEN'
WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD' WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD'
WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD' WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD'
OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media'
OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media' OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media'
MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
EBOOK_DIR_MAIN = "eBooks" EBOOK_DIR_MAIN = "eBooks"
EBOOK_DIR_CARD = "eBooks" EBOOK_DIR_CARD = "eBooks"
THUMBNAIL_HEIGHT = 144 THUMBNAIL_HEIGHT = 144
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=False, end_session=True,
metadata=None): metadata=None):
if on_card and not self._card_prefix: if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.')) raise ValueError(_('The reader has no storage card connected.'))
if not on_card: if not on_card:
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
else: else:
@ -66,7 +65,7 @@ class CYBOOKG3(USBMS):
paths = [] paths = []
names = iter(names) names = iter(names)
metadata = iter(metadata) metadata = iter(metadata)
for infile in files: for infile in files:
newpath = path newpath = path
mdata = metadata.next() mdata = metadata.next()
@ -83,20 +82,20 @@ class CYBOOKG3(USBMS):
newpath += tag newpath += tag
newpath = os.path.normpath(newpath) newpath = os.path.normpath(newpath)
break break
if newpath == path: if newpath == path:
newpath = os.path.join(newpath, authors_to_string(mdata.get('authors', ''))) newpath = os.path.join(newpath, authors_to_string(mdata.get('authors', '')))
newpath = os.path.join(newpath, mdata.get('title', '')) newpath = os.path.join(newpath, mdata.get('title', ''))
if not os.path.exists(newpath): if not os.path.exists(newpath):
os.makedirs(newpath) os.makedirs(newpath)
filepath = os.path.join(newpath, names.next()) filepath = os.path.join(newpath, names.next())
paths.append(filepath) paths.append(filepath)
if hasattr(infile, 'read'): if hasattr(infile, 'read'):
infile.seek(0) infile.seek(0)
dest = open(filepath, 'wb') dest = open(filepath, 'wb')
shutil.copyfileobj(infile, dest, 10*1024*1024) shutil.copyfileobj(infile, dest, 10*1024*1024)
@ -104,33 +103,33 @@ class CYBOOKG3(USBMS):
dest.close() dest.close()
else: else:
shutil.copy2(infile, filepath) shutil.copy2(infile, filepath)
coverdata = None coverdata = None
if 'cover' in mdata.keys(): if 'cover' in mdata.keys():
if mdata['cover'] != None: if mdata['cover'] != None:
coverdata = mdata['cover'][2] coverdata = mdata['cover'][2]
t2bfile = open('%s_6090.t2b' % (os.path.splitext(filepath)[0]), 'wb') t2bfile = open('%s_6090.t2b' % (os.path.splitext(filepath)[0]), 'wb')
t2b.write_t2b(t2bfile, coverdata) t2b.write_t2b(t2bfile, coverdata)
t2bfile.close() t2bfile.close()
return zip(paths, cycle([on_card])) return zip(paths, cycle([on_card]))
def delete_books(self, paths, end_session=True): def delete_books(self, paths, end_session=True):
for path in paths: for path in paths:
if os.path.exists(path): if os.path.exists(path):
os.unlink(path) os.unlink(path)
filepath, ext = os.path.splitext(path) filepath, ext = os.path.splitext(path)
# Delete the ebook auxiliary file # Delete the ebook auxiliary file
if os.path.exists(filepath + '.mbp'): if os.path.exists(filepath + '.mbp'):
os.unlink(filepath + '.mbp') os.unlink(filepath + '.mbp')
# Delete the thumbnails file auto generated for the ebook # Delete the thumbnails file auto generated for the ebook
if os.path.exists(filepath + '_6090.t2b'): if os.path.exists(filepath + '_6090.t2b'):
os.unlink(filepath + '_6090.t2b') os.unlink(filepath + '_6090.t2b')
try: try:
os.removedirs(os.path.dirname(path)) os.removedirs(os.path.dirname(path))
except: except:

View File

@ -0,0 +1,2 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'

View File

@ -0,0 +1,41 @@
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
'''
Device driver for the Netronix EB600
'''
from calibre.devices.usbms.driver import USBMS
class EB600(USBMS):
# Ordered list of supported formats
FORMATS = ['epub', 'prc', 'chm', 'djvu', 'html', 'rtf', 'txt', 'pdf']
DRM_FORMATS = ['prc', 'mobi', 'html', 'pdf', 'txt']
VENDOR_ID = [0x1f85]
PRODUCT_ID = [0x1688]
BCD = [0x110]
VENDOR_NAME = 'NETRONIX'
WINDOWS_MAIN_MEM = 'EBOOK'
WINDOWS_CARD_MEM = 'EBOOK'
OSX_MAIN_MEM = 'EB600 Internal Storage Media'
OSX_CARD_MEM = 'EB600 Card Storage Media'
MAIN_MEMORY_VOLUME_LABEL = 'EB600 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'EB600 Storage Card'
EBOOK_DIR_MAIN = ''
EBOOK_DIR_CARD = ''
SUPPORTS_SUB_DIRS = True
def windows_sort_drives(self, drives):
main = drives['main']
card = drives['card']
if card and main and card < main:
drives['main'] = card
drives['card'] = main
return drives

View File

@ -174,6 +174,14 @@ class Device(_Device):
return prefix return prefix
def windows_sort_drives(self, drives):
'''
Called to disambiguate main memory and storage card for devices that
do not distinguish between them on the basis of `WINDOWS_CARD_NAME`.
For e.g.: The EB600
'''
return drives
def open_windows(self): def open_windows(self):
time.sleep(6) time.sleep(6)
drives = {} drives = {}
@ -188,11 +196,14 @@ class Device(_Device):
if 'main' in drives.keys() and 'card' in drives.keys(): if 'main' in drives.keys() and 'card' in drives.keys():
break break
drives = self.windows_sort_drives(drives)
self._main_prefix = drives.get('main') self._main_prefix = drives.get('main')
self._card_prefix = drives.get('card') self._card_prefix = drives.get('card')
if not self._main_prefix: if not self._main_prefix:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) raise DeviceError(
_('Unable to detect the %s disk drive. Try rebooting.') %
self.__class__.__name__)
def get_osx_mountpoints(self, raw=None): def get_osx_mountpoints(self, raw=None):
if raw is None: if raw is None:

View File

@ -11,9 +11,7 @@ from urllib import unquote, quote
from urlparse import urlparse from urlparse import urlparse
from calibre.constants import __version__ as VERSION
from calibre import relpath from calibre import relpath
from calibre.utils.config import OptionParser
def string_to_authors(raw): def string_to_authors(raw):
raw = raw.replace('&&', u'\uffff') raw = raw.replace('&&', u'\uffff')
@ -189,11 +187,11 @@ class MetaInformation(object):
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'tags', 'cover_data', 'application_id', 'guide', 'isbn', 'tags', 'cover_data', 'application_id', 'guide',
'manifest', 'spine', 'toc', 'cover', 'language', 'manifest', 'spine', 'toc', 'cover', 'language',
'book_producer', 'timestamp'): 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'):
if hasattr(mi, attr): if hasattr(mi, attr):
setattr(ans, attr, getattr(mi, attr)) setattr(ans, attr, getattr(mi, attr))
def __init__(self, title, authors=[_('Unknown')]): def __init__(self, title, authors=(_('Unknown'),)):
''' '''
@param title: title or ``_('Unknown')`` or a MetaInformation object @param title: title or ``_('Unknown')`` or a MetaInformation object
@param authors: List of strings or [] @param authors: List of strings or []
@ -204,9 +202,9 @@ class MetaInformation(object):
title = mi.title title = mi.title
authors = mi.authors authors = mi.authors
self.title = title self.title = title
self.author = authors # Needed for backward compatibility self.author = list(authors) if authors else []# Needed for backward compatibility
#: List of strings or [] #: List of strings or []
self.authors = authors self.authors = list(authors) if authors else []
self.tags = getattr(mi, 'tags', []) self.tags = getattr(mi, 'tags', [])
#: mi.cover_data = (ext, data) #: mi.cover_data = (ext, data)
self.cover_data = getattr(mi, 'cover_data', (None, None)) self.cover_data = getattr(mi, 'cover_data', (None, None))
@ -214,7 +212,7 @@ class MetaInformation(object):
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher',
'series', 'series_index', 'rating', 'isbn', 'language', 'series', 'series_index', 'rating', 'isbn', 'language',
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
'book_producer', 'timestamp' 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'
): ):
setattr(self, x, getattr(mi, x, None)) setattr(self, x, getattr(mi, x, None))
@ -229,15 +227,15 @@ class MetaInformation(object):
if mi.authors and mi.authors[0] != _('Unknown'): if mi.authors and mi.authors[0] != _('Unknown'):
self.authors = mi.authors self.authors = mi.authors
for attr in ('author_sort', 'title_sort', 'category', for attr in ('author_sort', 'title_sort', 'category',
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'application_id', 'manifest', 'spine', 'toc', 'isbn', 'application_id', 'manifest', 'spine', 'toc',
'cover', 'language', 'guide', 'book_producer', 'cover', 'language', 'guide', 'book_producer',
'timestamp'): 'timestamp', 'lccn', 'lcc', 'ddc'):
val = getattr(mi, attr, None) if hasattr(mi, attr):
if val is not None: val = getattr(mi, attr)
setattr(self, attr, val) if val is not None:
setattr(self, attr, val)
if mi.tags: if mi.tags:
self.tags += mi.tags self.tags += mi.tags
@ -245,7 +243,7 @@ class MetaInformation(object):
if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None: if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None:
self.cover_data = mi.cover_data self.cover_data = mi.cover_data
my_comments = getattr(self, 'comments', '') my_comments = getattr(self, 'comments', '')
other_comments = getattr(mi, 'comments', '') other_comments = getattr(mi, 'comments', '')
if not my_comments: if not my_comments:
@ -254,7 +252,7 @@ class MetaInformation(object):
other_comments = '' other_comments = ''
if len(other_comments.strip()) > len(my_comments.strip()): if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments self.comments = other_comments
def format_series_index(self): def format_series_index(self):
try: try:
x = float(self.series_index) x = float(self.series_index)
@ -293,6 +291,13 @@ class MetaInformation(object):
fmt('Rating', self.rating) fmt('Rating', self.rating)
if self.timestamp is not None: if self.timestamp is not None:
fmt('Timestamp', self.timestamp.isoformat(' ')) fmt('Timestamp', self.timestamp.isoformat(' '))
if self.lccn:
fmt('LCCN', unicode(self.lccn))
if self.lcc:
fmt('LCC', unicode(self.lcc))
if self.ddc:
fmt('DDC', unicode(self.ddc))
return u'\n'.join(ans) return u'\n'.join(ans)
def to_html(self): def to_html(self):
@ -302,6 +307,12 @@ class MetaInformation(object):
ans += [(_('Producer'), unicode(self.book_producer))] ans += [(_('Producer'), unicode(self.book_producer))]
ans += [(_('Comments'), unicode(self.comments))] ans += [(_('Comments'), unicode(self.comments))]
ans += [('ISBN', unicode(self.isbn))] ans += [('ISBN', unicode(self.isbn))]
if self.lccn:
ans += [('LCCN', unicode(self.lccn))]
if self.lcc:
ans += [('LCC', unicode(self.lcc))]
if self.ddc:
ans += [('DDC', unicode(self.ddc))]
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))] ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
if self.series: if self.series:
ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())] ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]

View File

@ -59,8 +59,9 @@ class EXTHHeader(object):
elif id == 502: elif id == 502:
# last update time # last update time
pass pass
elif id == 503 and (not title or title == _('Unknown')): elif id == 503: # Long title
title = content if not title or title == _('Unknown'):
title = content
#else: #else:
# print 'unknown record', id, repr(content) # print 'unknown record', id, repr(content)
if title: if title:
@ -87,6 +88,8 @@ class EXTHHeader(object):
content, '%Y-%m-%d',).date() content, '%Y-%m-%d',).date()
except: except:
pass pass
elif id == 108:
pass # Producer
#else: #else:
# print 'unhandled metadata record', id, repr(content) # print 'unhandled metadata record', id, repr(content)
@ -522,7 +525,8 @@ class MobiReader(object):
else: else:
raise MobiError('Unknown compression algorithm: %s'%repr(self.book_header.compression_type)) raise MobiError('Unknown compression algorithm: %s'%repr(self.book_header.compression_type))
if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower(): if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower():
self.mobi_html = self.mobi_html.replace('\r ', '\n\n').replace('\0', '') self.mobi_html = self.mobi_html.replace('\r ', '\n\n ')
self.mobi_html = self.mobi_html.replace('\0', '')
return processed_records return processed_records

View File

@ -466,5 +466,3 @@ class Application(QApplication):
self.translator.loadFromData(data) self.translator.loadFromData(data)
self.installTranslator(self.translator) self.installTranslator(self.translator)

View File

@ -199,7 +199,7 @@ class EmailAccounts(QAbstractTableModel):
return (account, self.accounts[account]) return (account, self.accounts[account])
if role == Qt.ToolTipRole: if role == Qt.ToolTipRole:
return self.tooltips[col] return self.tooltips[col]
if role == Qt.DisplayRole: if role in [Qt.DisplayRole, Qt.EditRole]:
if col == 0: if col == 0:
return QVariant(account) return QVariant(account)
if col == 1: if col == 1:
@ -397,6 +397,9 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.separate_cover_flow.setChecked(config['separate_cover_flow']) self.separate_cover_flow.setChecked(config['separate_cover_flow'])
self.setup_email_page() self.setup_email_page()
self.category_view.setCurrentIndex(self.category_view.model().index(0)) self.category_view.setCurrentIndex(self.category_view.model().index(0))
self.delete_news.setEnabled(bool(self.sync_news.isChecked()))
self.connect(self.sync_news, SIGNAL('toggled(bool)'),
self.delete_news.setEnabled)
def setup_email_page(self): def setup_email_page(self):
opts = smtp_prefs().parse() opts = smtp_prefs().parse()

View File

@ -371,7 +371,7 @@
<item> <item>
<widget class="QCheckBox" name="delete_news"> <widget class="QCheckBox" name="delete_news">
<property name="text"> <property name="text">
<string>&amp;Delete news from library when it is sent to reader</string> <string>&amp;Delete news from library when it is automatically sent to reader</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -324,7 +324,7 @@
<string>Book </string> <string>Book </string>
</property> </property>
<property name="minimum"> <property name="minimum">
<number>1</number> <number>0</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>10000</number> <number>10000</number>

View File

@ -25,7 +25,7 @@ from calibre.gui2.dialogs.user_profiles import UserProfiles
config = DynamicConfig('scheduler') config = DynamicConfig('scheduler')
class Recipe(object): class Recipe(object):
def __init__(self, id=None, recipe_class=None, builtin=True): def __init__(self, id=None, recipe_class=None, builtin=True):
self.id = id self.id = id
self.title = getattr(recipe_class, 'title', None) self.title = getattr(recipe_class, 'title', None)
@ -39,14 +39,14 @@ class Recipe(object):
if self.author == _('Unknown') and not builtin: if self.author == _('Unknown') and not builtin:
self.author = _('You') self.author = _('You')
self.needs_subscription = getattr(recipe_class, 'needs_subscription', False) self.needs_subscription = getattr(recipe_class, 'needs_subscription', False)
def pickle(self): def pickle(self):
return self.__dict__.copy() return self.__dict__.copy()
def unpickle(self, dict): def unpickle(self, dict):
self.__dict__.update(dict) self.__dict__.update(dict)
return self return self
def __cmp__(self, other): def __cmp__(self, other):
if self.id == getattr(other, 'id', None): if self.id == getattr(other, 'id', None):
return 0 return 0
@ -59,38 +59,39 @@ class Recipe(object):
if not self.builtin and getattr(other, 'builtin', True): if not self.builtin and getattr(other, 'builtin', True):
return -1 return -1
return english_sort(self.title, getattr(other, 'title', '')) return english_sort(self.title, getattr(other, 'title', ''))
def __hash__(self): def __hash__(self):
return hash(self.id) return hash(self.id)
def __eq__(self, other): def __eq__(self, other):
return self.id == getattr(other, 'id', None) return self.id == getattr(other, 'id', None)
def __repr__(self): def __repr__(self):
schedule = self.schedule schedule = self.schedule
if schedule and schedule > 1e5: if schedule and schedule > 1e5:
schedule = decode_schedule(schedule) schedule = decode_schedule(schedule)
return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), schedule) return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), schedule)
builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)] builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)]
def save_recipes(recipes): def save_recipes(recipes):
config['scheduled_recipes'] = [r.pickle() for r in recipes] config['scheduled_recipes'] = [r.pickle() for r in recipes]
def load_recipes(): def load_recipes():
config.refresh() config.refresh()
recipes = [] recipes = []
for r in config.get('scheduled_recipes', []): for r in config.get('scheduled_recipes', []):
r = Recipe().unpickle(r) r = Recipe().unpickle(r)
if r.builtin and not str(r.id).startswith('recipe_'): if r.builtin and \
(not str(r.id).startswith('recipe_') or not str(r.id) in recipe_modules):
continue continue
recipes.append(r) recipes.append(r)
return recipes return recipes
class RecipeModel(QAbstractItemModel, SearchQueryParser): class RecipeModel(QAbstractItemModel, SearchQueryParser):
LOCATIONS = ['all'] LOCATIONS = ['all']
def __init__(self, db, *args): def __init__(self, db, *args):
QAbstractItemModel.__init__(self, *args) QAbstractItemModel.__init__(self, *args)
SearchQueryParser.__init__(self) SearchQueryParser.__init__(self)
@ -104,18 +105,18 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
self.bold_font = QFont() self.bold_font = QFont()
self.bold_font.setBold(True) self.bold_font.setBold(True)
self.bold_font = QVariant(self.bold_font) self.bold_font = QVariant(self.bold_font)
def refresh(self): def refresh(self):
sr = load_recipes() sr = load_recipes()
for recipe in self.recipes: for recipe in self.recipes:
if recipe in sr: if recipe in sr:
recipe.schedule = sr[sr.index(recipe)].schedule recipe.schedule = sr[sr.index(recipe)].schedule
recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded
self.recipes.sort() self.recipes.sort()
self.num_of_recipes = len(self.recipes) self.num_of_recipes = len(self.recipes)
self.category_map = {} self.category_map = {}
for r in self.recipes: for r in self.recipes:
category = getattr(r, 'language', _('Unknown')) category = getattr(r, 'language', _('Unknown'))
@ -126,12 +127,12 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
if category not in self.category_map.keys(): if category not in self.category_map.keys():
self.category_map[category] = [] self.category_map[category] = []
self.category_map[category].append(r) self.category_map[category].append(r)
self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories) self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories)
self._map = dict(self.category_map) self._map = dict(self.category_map)
def sort_categories(self, x, y): def sort_categories(self, x, y):
def decorate(x): def decorate(x):
if x == _('Scheduled'): if x == _('Scheduled'):
x = '0' + x x = '0' + x
@ -140,13 +141,13 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
else: else:
x = '2' + x x = '2' + x
return x return x
return cmp(decorate(x), decorate(y)) return cmp(decorate(x), decorate(y))
def universal_set(self): def universal_set(self):
return set(self.recipes) return set(self.recipes)
def get_matches(self, location, query): def get_matches(self, location, query):
query = query.strip().lower() query = query.strip().lower()
if not query: if not query:
@ -154,9 +155,9 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
results = set([]) results = set([])
for recipe in self.recipes: for recipe in self.recipes:
if query in recipe.title.lower() or query in recipe.description.lower(): if query in recipe.title.lower() or query in recipe.description.lower():
results.add(recipe) results.add(recipe)
return results return results
def search(self, query): def search(self, query):
try: try:
results = self.parse(unicode(query)) results = self.parse(unicode(query))
@ -170,24 +171,24 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
if recipe in results: if recipe in results:
self._map[category].append(recipe) self._map[category].append(recipe)
self.reset() self.reset()
def resort(self): def resort(self):
self.recipes.sort() self.recipes.sort()
self.reset() self.reset()
def index(self, row, column, parent): def index(self, row, column, parent):
return self.createIndex(row, column, parent.row() if parent.isValid() else -1) return self.createIndex(row, column, parent.row() if parent.isValid() else -1)
def parent(self, index): def parent(self, index):
if index.internalId() == -1: if index.internalId() == -1:
return QModelIndex() return QModelIndex()
return self.createIndex(index.internalId(), 0, -1) return self.createIndex(index.internalId(), 0, -1)
def columnCount(self, parent): def columnCount(self, parent):
if not parent.isValid() or not parent.parent().isValid(): if not parent.isValid() or not parent.parent().isValid():
return 1 return 1
return 0 return 0
def rowCount(self, parent): def rowCount(self, parent):
if not parent.isValid(): if not parent.isValid():
return len(self.categories) return len(self.categories)
@ -195,7 +196,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
category = self.categories[parent.row()] category = self.categories[parent.row()]
return len(self._map[category]) return len(self._map[category])
return 0 return 0
def data(self, index, role): def data(self, index, role):
if index.parent().isValid(): if index.parent().isValid():
category = self.categories[index.parent().row()] category = self.categories[index.parent().row()]
@ -206,7 +207,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
return recipe return recipe
elif role == Qt.DecorationRole: elif role == Qt.DecorationRole:
icon = self.default_icon icon = self.default_icon
icon_path = (':/images/news/%s.png'%recipe.id).replace('recipe_', '') icon_path = (':/images/news/%s.png'%recipe.id).replace('recipe_', '')
if not recipe.builtin: if not recipe.builtin:
icon = self.custom_icon icon = self.custom_icon
elif QFile().exists(icon_path): elif QFile().exists(icon_path):
@ -222,18 +223,18 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
elif role == Qt.ForegroundRole and category == _('Scheduled'): elif role == Qt.ForegroundRole and category == _('Scheduled'):
return QVariant(QColor(0, 255, 0)) return QVariant(QColor(0, 255, 0))
return NONE return NONE
def update_recipe_schedule(self, recipe): def update_recipe_schedule(self, recipe):
for srecipe in self.recipes: for srecipe in self.recipes:
if srecipe == recipe: if srecipe == recipe:
srecipe.schedule = recipe.schedule srecipe.schedule = recipe.schedule
class Search(QLineEdit): class Search(QLineEdit):
HELP_TEXT = _('Search') HELP_TEXT = _('Search')
INTERVAL = 500 #: Time to wait before emitting search signal INTERVAL = 500 #: Time to wait before emitting search signal
def __init__(self, *args): def __init__(self, *args):
QLineEdit.__init__(self, *args) QLineEdit.__init__(self, *args)
self.default_palette = QApplication.palette(self) self.default_palette = QApplication.palette(self)
@ -244,20 +245,20 @@ class Search(QLineEdit):
self.clear_to_help_mode() self.clear_to_help_mode()
self.timer = None self.timer = None
self.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot) self.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
def focusInEvent(self, ev): def focusInEvent(self, ev):
self.setPalette(QApplication.palette(self)) self.setPalette(QApplication.palette(self))
if self.in_help_mode(): if self.in_help_mode():
self.setText('') self.setText('')
return QLineEdit.focusInEvent(self, ev) return QLineEdit.focusInEvent(self, ev)
def in_help_mode(self): def in_help_mode(self):
return unicode(self.text()) == self.HELP_TEXT return unicode(self.text()) == self.HELP_TEXT
def clear_to_help_mode(self): def clear_to_help_mode(self):
self.setPalette(self.gray) self.setPalette(self.gray)
self.setText(self.HELP_TEXT) self.setText(self.HELP_TEXT)
def text_edited_slot(self, text): def text_edited_slot(self, text):
text = unicode(text) text = unicode(text)
self.timer = self.startTimer(self.INTERVAL) self.timer = self.startTimer(self.INTERVAL)
@ -281,7 +282,7 @@ def decode_schedule(num):
return day-1, hour-1, minute-1 return day-1, hour-1, minute-1
class SchedulerDialog(QDialog, Ui_Dialog): class SchedulerDialog(QDialog, Ui_Dialog):
def __init__(self, db, *args): def __init__(self, db, *args):
QDialog.__init__(self, *args) QDialog.__init__(self, *args)
self.setupUi(self) self.setupUi(self)
@ -308,25 +309,25 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.search.setFocus(Qt.OtherFocusReason) self.search.setFocus(Qt.OtherFocusReason)
self.old_news.setValue(gconf['oldest_news']) self.old_news.setValue(gconf['oldest_news'])
self.rnumber.setText(_('%d recipes')%self._model.num_of_recipes) self.rnumber.setText(_('%d recipes')%self._model.num_of_recipes)
for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'), for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'),
_('Thursday'), _('Friday'), _('Saturday'), _('Sunday')): _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')):
self.day.addItem(day) self.day.addItem(day)
def currentChanged(self, current, previous): def currentChanged(self, current, previous):
if current.parent().isValid(): if current.parent().isValid():
self.show_recipe(current) self.show_recipe(current)
def download_now(self): def download_now(self):
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe) self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe)
def set_account_info(self, *args): def set_account_info(self, *args):
username, password = map(unicode, (self.username.text(), self.password.text())) username, password = map(unicode, (self.username.text(), self.password.text()))
username, password = username.strip(), password.strip() username, password = username.strip(), password.strip()
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
key = 'recipe_account_info_%s'%recipe.id key = 'recipe_account_info_%s'%recipe.id
config[key] = (username, password) if username and password else None config[key] = (username, password) if username and password else None
def do_schedule(self, *args): def do_schedule(self, *args):
if not getattr(self, 'allow_scheduling', False): if not getattr(self, 'allow_scheduling', False):
return return
@ -342,7 +343,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
recipe.last_downloaded = datetime.fromordinal(1) recipe.last_downloaded = datetime.fromordinal(1)
recipes.append(recipe) recipes.append(recipe)
if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]: if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]:
error_dialog(self, _('Must set account information'), error_dialog(self, _('Must set account information'),
_('This recipe requires a username and password')).exec_() _('This recipe requires a username and password')).exec_()
self.schedule.setCheckState(Qt.Unchecked) self.schedule.setCheckState(Qt.Unchecked)
return return
@ -364,7 +365,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
save_recipes(recipes) save_recipes(recipes)
self._model.update_recipe_schedule(recipe) self._model.update_recipe_schedule(recipe)
self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes) self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes)
def show_recipe(self, index): def show_recipe(self, index):
recipe = self._model.data(index, Qt.UserRole) recipe = self._model.data(index, Qt.UserRole)
self.current_recipe = recipe self.current_recipe = recipe
@ -395,7 +396,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.interval_button.setChecked(False) self.interval_button.setChecked(False)
self.interval.setEnabled(False) self.interval.setEnabled(False)
self.schedule.setChecked(recipe.schedule is not None) self.schedule.setChecked(recipe.schedule is not None)
self.allow_scheduling = True self.allow_scheduling = True
self.detail_box.setVisible(True) self.detail_box.setVisible(True)
self.account.setVisible(recipe.needs_subscription) self.account.setVisible(recipe.needs_subscription)
self.interval.setEnabled(self.schedule.checkState() == Qt.Checked) self.interval.setEnabled(self.schedule.checkState() == Qt.Checked)
@ -417,11 +418,11 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.last_downloaded.setText(_('Last downloaded')+': '+tm) self.last_downloaded.setText(_('Last downloaded')+': '+tm)
else: else:
self.last_downloaded.setText(_('Last downloaded: never')) self.last_downloaded.setText(_('Last downloaded: never'))
class Scheduler(QObject): class Scheduler(QObject):
INTERVAL = 1 # minutes INTERVAL = 1 # minutes
def __init__(self, main): def __init__(self, main):
self.main = main self.main = main
self.verbose = main.verbose self.verbose = main.verbose
@ -439,7 +440,7 @@ class Scheduler(QObject):
self.oldest = gconf['oldest_news'] self.oldest = gconf['oldest_news']
self.oldest_timer.start(int(60 * 60000)) self.oldest_timer.start(int(60 * 60000))
self.oldest_check() self.oldest_check()
self.news_menu = QMenu() self.news_menu = QMenu()
self.news_icon = QIcon(':/images/news.svg') self.news_icon = QIcon(':/images/news.svg')
self.scheduler_action = QAction(QIcon(':/images/scheduler.svg'), _('Schedule news download'), self) self.scheduler_action = QAction(QIcon(':/images/scheduler.svg'), _('Schedule news download'), self)
@ -448,27 +449,27 @@ class Scheduler(QObject):
self.cac = QAction(QIcon(':/images/user_profile.svg'), _('Add a custom news source'), self) self.cac = QAction(QIcon(':/images/user_profile.svg'), _('Add a custom news source'), self)
self.connect(self.cac, SIGNAL('triggered(bool)'), self.customize_feeds) self.connect(self.cac, SIGNAL('triggered(bool)'), self.customize_feeds)
self.news_menu.addAction(self.cac) self.news_menu.addAction(self.cac)
def oldest_check(self): def oldest_check(self):
if self.oldest > 0: if self.oldest > 0:
delta = timedelta(days=self.oldest) delta = timedelta(days=self.oldest)
ids = self.main.library_view.model().db.tags_older_than(_('News'), delta) ids = self.main.library_view.model().db.tags_older_than(_('News'), delta)
if ids: if ids:
self.main.library_view.model().delete_books_by_id(ids) self.main.library_view.model().delete_books_by_id(ids)
def customize_feeds(self, *args): def customize_feeds(self, *args):
main = self.main main = self.main
d = UserProfiles(main, main.library_view.model().db.get_feeds()) d = UserProfiles(main, main.library_view.model().db.get_feeds())
d.exec_() d.exec_()
feeds = tuple(d.profiles()) feeds = tuple(d.profiles())
main.library_view.model().db.set_feeds(feeds) main.library_view.model().db.set_feeds(feeds)
def debug(self, *args): def debug(self, *args):
if self.verbose: if self.verbose:
sys.stdout.write(' '.join(map(unicode, args))+'\n') sys.stdout.write(' '.join(map(unicode, args))+'\n')
sys.stdout.flush() sys.stdout.flush()
def check(self): def check(self):
if not self.lock.tryLock(): if not self.lock.tryLock():
return return
@ -494,15 +495,15 @@ class Scheduler(QObject):
matches = day_matches and (hour*60+minute) < tnow matches = day_matches and (hour*60+minute) < tnow
if matches and recipe.last_downloaded.toordinal() < date.today().toordinal(): if matches and recipe.last_downloaded.toordinal() < date.today().toordinal():
needs_downloading.add(recipe) needs_downloading.add(recipe)
self.debug('Needs downloading:', needs_downloading) self.debug('Needs downloading:', needs_downloading)
needs_downloading = [r for r in needs_downloading if r not in self.queue] needs_downloading = [r for r in needs_downloading if r not in self.queue]
for recipe in needs_downloading: for recipe in needs_downloading:
self.do_download(recipe) self.do_download(recipe)
finally: finally:
self.lock.unlock() self.lock.unlock()
def do_download(self, recipe): def do_download(self, recipe):
try: try:
id = int(recipe.id) id = int(recipe.id)
@ -538,7 +539,7 @@ class Scheduler(QObject):
finally: finally:
self.lock.unlock() self.lock.unlock()
self.debug('Downloaded:', recipe) self.debug('Downloaded:', recipe)
def download(self, recipe): def download(self, recipe):
self.lock.lock() self.lock.lock()
try: try:
@ -548,10 +549,10 @@ class Scheduler(QObject):
self.do_download(recipe) self.do_download(recipe)
finally: finally:
self.lock.unlock() self.lock.unlock()
def refresh_schedule(self, recipes): def refresh_schedule(self, recipes):
self.recipes = recipes self.recipes = recipes
def show_dialog(self, *args): def show_dialog(self, *args):
self.lock.lock() self.lock.lock()
try: try:

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

View File

@ -1119,27 +1119,30 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
return return
self._view_file(job.result) self._view_file(job.result)
def _view_file(self, name): def _launch_viewer(self, name=None, viewer='ebook-viewer', internal=True):
self.setCursor(Qt.BusyCursor) self.setCursor(Qt.BusyCursor)
try: try:
ext = os.path.splitext(name)[1].upper().replace('.', '') if internal:
if ext in config['internally_viewed_formats']: args = [viewer]
if ext == 'LRF': if isosx and 'ebook' in viewer:
args = ['lrfviewer', name] args.append('--raise-window')
self.job_manager.server.run_free_job('lrfviewer', if name is not None:
kwdargs=dict(args=args)) args.append(name)
else: self.job_manager.server.run_free_job(viewer,
args = ['ebook-viewer', name] kwdargs=dict(args=args))
if isosx:
args.append('--raise-window')
self.job_manager.server.run_free_job('ebook-viewer',
kwdargs=dict(args=args))
else: else:
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name) QDesktopServices.openUrl(QUrl.fromLocalFile(name))#launch(name)
time.sleep(5) # User feedback time.sleep(5) # User feedback
finally: finally:
self.unsetCursor() self.unsetCursor()
def _view_file(self, name):
ext = os.path.splitext(name)[1].upper().replace('.', '')
viewer = 'lrfviewer' if ext == 'LRF' else 'ebook-viewer'
internal = ext in config['internally_viewed_formats']
self._launch_viewer(name, viewer, internal)
def view_specific_format(self, triggered): def view_specific_format(self, triggered):
rows = self.library_view.selectionModel().selectedRows() rows = self.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
@ -1174,8 +1177,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
rows = self.current_view().selectionModel().selectedRows() rows = self.current_view().selectionModel().selectedRows()
if self.current_view() is self.library_view: if self.current_view() is self.library_view:
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
d = error_dialog(self, _('Cannot view'), _('No book selected')) self._launch_viewer()
d.exec_()
return return
row = rows[0].row() row = rows[0].row()

View File

@ -15,6 +15,7 @@ from calibre import terminal_controller, preferred_encoding
from calibre.utils.config import OptionParser, prefs from calibre.utils.config import OptionParser, prefs
try: try:
from calibre.utils.single_qt_application import send_message from calibre.utils.single_qt_application import send_message
send_message
except: except:
send_message = None send_message = None
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
@ -55,7 +56,7 @@ XML_TEMPLATE = '''\
</py:for> </py:for>
</formats> </formats>
</record> </record>
</py:for> </py:for>
</calibredb> </calibredb>
''' '''
@ -114,7 +115,7 @@ def get_db(dbpath, options):
dbpath = os.path.abspath(dbpath) dbpath = os.path.abspath(dbpath)
return LibraryDatabase2(dbpath) return LibraryDatabase2(dbpath)
def do_list(db, fields, sort_by, ascending, search_text, line_width, separator, def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
prefix, output_format, subtitle='Books in the calibre database'): prefix, output_format, subtitle='Books in the calibre database'):
if sort_by: if sort_by:
db.sort(sort_by, ascending) db.sort(sort_by, ascending)
@ -134,13 +135,13 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
for i in data: for i in data:
for j, field in enumerate(fields): for j, field in enumerate(fields):
widths[j] = max(widths[j], len(unicode(i[str(field)]))) widths[j] = max(widths[j], len(unicode(i[str(field)])))
screen_width = terminal_controller.COLS if line_width < 0 else line_width screen_width = terminal_controller.COLS if line_width < 0 else line_width
if not screen_width: if not screen_width:
screen_width = 80 screen_width = 80
field_width = screen_width//len(fields) field_width = screen_width//len(fields)
base_widths = map(lambda x: min(x+1, field_width), widths) base_widths = map(lambda x: min(x+1, field_width), widths)
while sum(base_widths) < screen_width: while sum(base_widths) < screen_width:
adjusted = False adjusted = False
for i in range(len(widths)): for i in range(len(widths)):
@ -150,14 +151,14 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
break break
if not adjusted: if not adjusted:
break break
widths = list(base_widths) widths = list(base_widths)
titles = map(lambda x, y: '%-*s'%(x, y), widths, fields) titles = map(lambda x, y: '%-*s'%(x, y), widths, fields)
print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL
wrappers = map(lambda x: TextWrapper(x-1), widths) wrappers = map(lambda x: TextWrapper(x-1), widths)
o = cStringIO.StringIO() o = cStringIO.StringIO()
for record in data: for record in data:
text = [wrappers[i].wrap(unicode(record[field]).encode('utf-8')) for i, field in enumerate(fields)] text = [wrappers[i].wrap(unicode(record[field]).encode('utf-8')) for i, field in enumerate(fields)]
lines = max(map(len, text)) lines = max(map(len, text))
@ -178,9 +179,9 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
if isinstance(x['fmt_epub'], unicode): if isinstance(x['fmt_epub'], unicode):
x['fmt_epub'] = x['fmt_epub'].encode('utf-8') x['fmt_epub'] = x['fmt_epub'].encode('utf-8')
template = MarkupTemplate(STANZA_TEMPLATE) template = MarkupTemplate(STANZA_TEMPLATE)
return template.generate(id="urn:calibre:main", data=data, subtitle=subtitle, return template.generate(id="urn:calibre:main", data=data, subtitle=subtitle,
sep=os.sep, quote=quote, updated=db.last_modified()).render('xml') sep=os.sep, quote=quote, updated=db.last_modified()).render('xml')
def command_list(args, dbpath): def command_list(args, dbpath):
@ -199,7 +200,7 @@ List the books available in the calibre database.
help=_('Sort results in ascending order')) help=_('Sort results in ascending order'))
parser.add_option('-s', '--search', default=None, parser.add_option('-s', '--search', default=None,
help=_('Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering.')) help=_('Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering.'))
parser.add_option('-w', '--line-width', default=-1, type=int, 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.')) help=_('The maximum width of a single line in the output. Defaults to detecting screen size.'))
parser.add_option('--separator', default=' ', help=_('The string used to separate fields. Default is a space.')) parser.add_option('--separator', default=' ', help=_('The string used to separate fields. Default is a space.'))
parser.add_option('--prefix', default=None, help=_('The prefix for all file paths. Default is the absolute path to the library folder.')) parser.add_option('--prefix', default=None, help=_('The prefix for all file paths. Default is the absolute path to the library folder.'))
@ -264,14 +265,14 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
formats.append(format) formats.append(format)
metadata.append(mi) metadata.append(mi)
file_duplicates = [] file_duplicates = []
if files: if files:
file_duplicates = db.add_books(files, formats, metadata, file_duplicates = db.add_books(files, formats, metadata,
add_duplicates=add_duplicates) add_duplicates=add_duplicates)
if file_duplicates: if file_duplicates:
file_duplicates = file_duplicates[0] file_duplicates = file_duplicates[0]
dir_dups = [] dir_dups = []
for dir in dirs: for dir in dirs:

View File

@ -24,7 +24,7 @@ from calibre.translations.msgfmt import make
_run_once = False _run_once = False
if not _run_once: if not _run_once:
_run_once = True _run_once = True
################################################################################ ################################################################################
# Setup translations # Setup translations
@ -32,7 +32,8 @@ if not _run_once:
lang = prefs['language'] lang = prefs['language']
if lang is not None: if lang is not None:
return lang return lang
lang = locale.getdefaultlocale()[0] lang = locale.getdefaultlocale(['LANGUAGE', 'LC_ALL', 'LC_CTYPE',
'LC_MESSAGES', 'LANG'])[0]
if lang is None and os.environ.has_key('LANG'): # Needed for OS X if lang is None and os.environ.has_key('LANG'): # Needed for OS X
try: try:
lang = os.environ['LANG'] lang = os.environ['LANG']

View File

@ -38,6 +38,7 @@ def get_linux_data(version='1.0.0'):
('exherbo', 'Exherbo'), ('exherbo', 'Exherbo'),
('foresight', 'Foresight 2.1'), ('foresight', 'Foresight 2.1'),
('ubuntu', 'Ubuntu Jaunty Jackalope'), ('ubuntu', 'Ubuntu Jaunty Jackalope'),
('linux_mint', 'Linux Mint Gloria'),
]: ]:
data['supported'].append(CoolDistro(name, title, data['supported'].append(CoolDistro(name, title,
prefix='http://calibre.kovidgoyal.net')) prefix='http://calibre.kovidgoyal.net'))

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ recipe_modules = ['recipe_' + r for r in (
'newsweek', 'atlantic', 'economist', 'portfolio', 'the_register', 'newsweek', 'atlantic', 'economist', 'portfolio', 'the_register',
'usatoday', 'outlook_india', 'bbc', 'greader', 'wsj', 'usatoday', 'outlook_india', 'bbc', 'greader', 'wsj',
'wired', 'globe_and_mail', 'smh', 'espn', 'business_week', 'miami_herald', 'wired', 'globe_and_mail', 'smh', 'espn', 'business_week', 'miami_herald',
'ars_technica', 'upi', 'new_yorker', 'irish_times', 'iht', 'lanacion', 'ars_technica', 'upi', 'new_yorker', 'irish_times', 'lanacion',
'discover_magazine', 'scientific_american', 'new_york_review_of_books', 'discover_magazine', 'scientific_american', 'new_york_review_of_books',
'daily_telegraph', 'guardian', 'el_pais', 'new_scientist', 'b92', 'daily_telegraph', 'guardian', 'el_pais', 'new_scientist', 'b92',
'politika', 'moscow_times', 'latimes', 'japan_times', 'san_fran_chronicle', 'politika', 'moscow_times', 'latimes', 'japan_times', 'san_fran_chronicle',
@ -37,7 +37,8 @@ recipe_modules = ['recipe_' + r for r in (
'new_york_review_of_books_no_sub', 'politico', 'adventuregamers', 'new_york_review_of_books_no_sub', 'politico', 'adventuregamers',
'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list', 'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list',
'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs', 'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs',
'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet',
'corriere_della_sera_it', 'corriere_della_sera_en', 'msdnmag_en',
)] )]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
www.azstarnet.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Azstarnet(BasicNewsRecipe):
title = 'Arizona Daily Star'
__author__ = 'Darko Miletic'
description = 'news from Arizona'
publisher = 'azstarnet.com'
category = 'news, politics, Arizona, USA'
delay = 1
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
needs_subscription = True
remove_javascript = True
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('http://azstarnet.com/registration/retro.php')
br.select_form(nr=1)
br['email'] = self.username
br['pass' ] = self.password
br.submit()
return br
keep_only_tags = [dict(name='div', attrs={'id':'storycontent'})]
remove_tags = [
dict(name=['object','link','iframe','base','img'])
,dict(name='div',attrs={'class':'bannerinstory'})
]
feeds = [(u'Tucson Region', u'http://rss.azstarnet.com/index.php?site=metro')]
def preprocess_html(self, soup):
soup.html['dir' ] = 'ltr'
soup.html['lang'] = 'en-US'
mtag = '\n<meta http-equiv="Content-Language" content="en-US"/>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n'
soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
www.corriere.it/english
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Corriere_en(BasicNewsRecipe):
title = 'Corriere della Sera in English'
__author__ = 'Darko Miletic'
description = 'News from Milan and Italy'
oldest_article = 15
publisher = 'Corriere della Sera'
category = 'news, politics, Italy'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
remove_javascript = True
language = _('English')
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
, '--ignore-tables'
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
keep_only_tags = [dict(name='div', attrs={'class':['news-dettaglio article','article']})]
remove_tags = [
dict(name=['base','object','link','embed','img'])
,dict(name='div', attrs={'class':'news-goback'})
,dict(name='ul', attrs={'class':'toolbar'})
]
remove_tags_after = dict(name='p', attrs={'class':'footnotes'})
feeds = [(u'Italian Life', u'http://www.corriere.it/rss/english.xml')]

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
www.corriere.it
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Corriere_it(BasicNewsRecipe):
title = 'Corriere della Sera'
__author__ = 'Darko Miletic'
description = 'News from Milan and Italy'
oldest_article = 7
publisher = 'Corriere della Sera'
category = 'news, politics, Italy'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
remove_javascript = True
language = _('Italian')
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
, '--ignore-tables'
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
keep_only_tags = [dict(name='div', attrs={'class':['news-dettaglio article','article']})]
remove_tags = [
dict(name=['base','object','link','embed','img'])
,dict(name='div', attrs={'class':'news-goback'})
,dict(name='ul', attrs={'class':'toolbar'})
]
remove_tags_after = dict(name='p', attrs={'class':'footnotes'})
feeds = [
(u'Ultimora' , u'http://www.corriere.it/rss/ultimora.xml' )
,(u'Cronache' , u'http://www.corriere.it/rss/cronache.xml' )
,(u'Economia' , u'http://www.corriere.it/rss/economia.xml' )
,(u'Editoriali', u'http://www.corriere.it/rss/editoriali.xml')
,(u'Esteri' , u'http://www.corriere.it/rss/esteri.xml' )
,(u'Politica' , u'http://www.corriere.it/rss/politica.xml' )
,(u'Salute' , u'http://www.corriere.it/rss/salute.xml' )
,(u'Scienze' , u'http://www.corriere.it/rss/scienze.xml' )
,(u'Spettacolo', u'http://www.corriere.it/rss/spettacoli.xml')
,(u'Sport' , u'http://www.corriere.it/rss/sport.xml' )
]

View File

@ -12,7 +12,7 @@ from calibre.ptempfile import PersistentTemporaryFile
class InternationalHeraldTribune(BasicNewsRecipe): class InternationalHeraldTribune(BasicNewsRecipe):
title = u'The International Herald Tribune' title = u'The International Herald Tribune'
__author__ = 'Derry FitzGerald' __author__ = 'Derry FitzGerald'
language = _('English') language = _('English')
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 10 max_articles_per_feed = 10
no_stylesheets = True no_stylesheets = True
@ -20,13 +20,13 @@ class InternationalHeraldTribune(BasicNewsRecipe):
remove_tags = [dict(name='div', attrs={'class':'footer'}), remove_tags = [dict(name='div', attrs={'class':'footer'}),
dict(name=['form'])] dict(name=['form'])]
preprocess_regexps = [ preprocess_regexps = [
(re.compile(r'<!-- webtrends.*', re.DOTALL), (re.compile(r'<!-- webtrends.*', re.DOTALL),
lambda m:'</body></html>') lambda m:'</body></html>')
] ]
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }' extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
feeds = [ feeds = [
(u'Frontpage', u'http://www.iht.com/rss/frontpage.xml'), (u'Frontpage', u'http://www.iht.com/rss/frontpage.xml'),
(u'Business', u'http://www.iht.com/rss/business.xml'), (u'Business', u'http://www.iht.com/rss/business.xml'),
(u'Americas', u'http://www.iht.com/rss/america.xml'), (u'Americas', u'http://www.iht.com/rss/america.xml'),
(u'Europe', u'http://www.iht.com/rss/europe.xml'), (u'Europe', u'http://www.iht.com/rss/europe.xml'),
@ -45,7 +45,7 @@ class InternationalHeraldTribune(BasicNewsRecipe):
] ]
temp_files = [] temp_files = []
articles_are_obfuscated = True articles_are_obfuscated = True
def get_obfuscated_article(self, url, logger): def get_obfuscated_article(self, url, logger):
br = self.get_browser() br = self.get_browser()
br.open(url) br.open(url)
@ -55,4 +55,4 @@ class InternationalHeraldTribune(BasicNewsRecipe):
self.temp_files.append(PersistentTemporaryFile('_iht.html')) self.temp_files.append(PersistentTemporaryFile('_iht.html'))
self.temp_files[-1].write(html) self.temp_files[-1].write(html)
self.temp_files[-1].close() self.temp_files[-1].close()
return self.temp_files[-1].name return self.temp_files[-1].name

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
msdn.microsoft.com/en-us/magazine
'''
from calibre.web.feeds.news import BasicNewsRecipe
class MSDNMagazine_en(BasicNewsRecipe):
title = 'MSDN Magazine'
__author__ = 'Darko Miletic'
description = 'The Microsoft Journal for Developers'
publisher = 'Microsoft Press'
category = 'news, IT, Microsoft, programming, windows'
oldest_article = 31
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
remove_javascript = True
current_issue = 'http://msdn.microsoft.com/en-us/magazine/default.aspx'
language = _('English')
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
feeds = [(u'Articles', u'http://msdn.microsoft.com/en-us/magazine/rss/default.aspx?z=z&iss=1')]
keep_only_tags = [dict(name='div', attrs={'class':'topic'})]
remove_tags = [
dict(name=['object','link','base','table'])
,dict(name='div', attrs={'class':'MTPS_CollapsibleRegion'})
]
def get_cover_url(self):
cover_url = None
soup = self.index_to_soup(self.current_issue)
link_item = soup.find('span',attrs={'class':'ContentsImageSpacer'})
if link_item:
imgt = link_item.find('img')
if imgt:
cover_url = imgt['src']
return cover_url
def preprocess_html(self, soup):
for item in soup.findAll('div',attrs={'class':['FeatureSmallHead','ColumnTypeSubTitle']}):
item.name="h2"
for item in soup.findAll('div',attrs={'class':['FeatureHeadline','ColumnTypeTitle']}):
item.name="h1"
for item in soup.findAll('div',attrs={'class':'ArticleTypeTitle'}):
item.name="h3"
return soup

View File

@ -9,11 +9,12 @@ newyorker.com
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class NewYorker(BasicNewsRecipe): class NewYorker(BasicNewsRecipe):
title = u'The New Yorker' title = u'The New Yorker'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'The best of US journalism' description = 'The best of US journalism'
oldest_article = 7 oldest_article = 7
language = _('English') language = _('English')
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = False no_stylesheets = False
use_embedded_content = False use_embedded_content = False
@ -24,7 +25,7 @@ class NewYorker(BasicNewsRecipe):
.calibre_recipe_title {font-size:normal} .calibre_recipe_title {font-size:normal}
.calibre_feed_description {font-size:xx-small} .calibre_feed_description {font-size:xx-small}
''' '''
keep_only_tags = [ keep_only_tags = [
dict(name='div' , attrs={'id':'printbody' }) dict(name='div' , attrs={'id':'printbody' })

View File

@ -1,38 +1,47 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
''' '''
tomshardware.com tomshardware.com/us
''' '''
import urllib
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
class Tomshardware(BasicNewsRecipe): class Tomshardware(BasicNewsRecipe):
title = "Tom's Hardware US"
__author__ = 'Darko Miletic'
description = 'Hardware reviews and News'
publisher = "Tom's Hardware"
category = 'news, IT, hardware, USA'
no_stylesheets = True
needs_subscription = True
language = _('English')
INDEX = 'http://www.tomshardware.com'
LOGIN = INDEX + '/membres/'
remove_javascript = True
use_embedded_content= False
title = "Tom's Hardware US" html2lrf_options = [
__author__ = 'Darko Miletic' '--comment', description
description = 'Hardware reviews and News' , '--category', category
no_stylesheets = True , '--publisher', publisher
needs_subscription = True ]
language = _('English')
INDEX = 'http://www.tomshardware.com'
LOGIN = 'http://www.tomshardware.com/membres/?r=%2Fus%2F#loginForm'
cover_url = 'http://img.bestofmedia.com/img/tomshardware/design/tomshardware.jpg'
html2lrf_options = [ '--comment' , description html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
, '--category' , 'hardware,news'
, '--base-font-size', '10'
]
def get_browser(self): def get_browser(self):
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
br.open(self.INDEX+'/us/')
if self.username is not None and self.password is not None: if self.username is not None and self.password is not None:
br.open(self.LOGIN) data = urllib.urlencode({ 'action':'login_action'
br.select_form(name='connexion') ,'r':self.INDEX+'/us/'
br['login'] = self.username ,'login':self.username
br['mdp' ] = self.password ,'mdp':self.password
br.submit() })
br.open(self.LOGIN,data)
return br return br
remove_tags = [ remove_tags = [
@ -41,17 +50,18 @@ class Tomshardware(BasicNewsRecipe):
] ]
feeds = [ feeds = [
(u'Latest Articles', u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-2.xml') (u'Latest Articles', u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-2.xml' )
,(u'Latest News' , u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-1.xml') ,(u'Latest News' , u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-1.xml')
] ]
def print_version(self, url): def print_version(self, url):
main, sep, rest = url.rpartition('.html') main, sep, rest = url.rpartition('.html')
rmain, rsep, article_id = main.rpartition(',') rmain, rsep, article_id = main.rpartition(',')
tmain, tsep, trest = rmain.rpartition('/reviews/') tmain, tsep, trest = rmain.rpartition('/reviews/')
rind = 'http://www.tomshardware.com/news_print.php?p1='
if tsep: if tsep:
return 'http://www.tomshardware.com/review_print.php?p1=' + article_id rind = 'http://www.tomshardware.com/review_print.php?p1='
return 'http://www.tomshardware.com/news_print.php?p1=' + article_id return rind + article_id
def preprocess_html(self, soup): def preprocess_html(self, soup):
del(soup.body['onload']) del(soup.body['onload'])

View File

@ -11,13 +11,13 @@ class WashingtonPost(BasicNewsRecipe):
max_articles_per_feed = 20 max_articles_per_feed = 20
language = _('English') language = _('English')
remove_javascript = True
remove_javascript = True
feeds = [ ('Today\'s Highlights', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/03/24/LI2005032400102.xml'), feeds = [ ('Today\'s Highlights', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/03/24/LI2005032400102.xml'),
('Politics', 'http://www.washingtonpost.com/wp-dyn/rss/politics/index.xml'), ('Politics', 'http://www.washingtonpost.com/wp-dyn/rss/politics/index.xml'),
('Nation', 'http://www.www.washingtonpost.com/wp-dyn/rss/nation/index.xml'), ('Nation', 'http://www.washingtonpost.com/wp-dyn/rss/nation/index.xml'),
('World', 'http://www.washingtonpost.com/wp-dyn/rss/world/index.xml'), ('World', 'http://www.washingtonpost.com/wp-dyn/rss/world/index.xml'),
('Business', 'http://www.washingtonpost.com/wp-dyn/rss/business/index.xml'), ('Business', 'http://www.washingtonpost.com/wp-dyn/rss/business/index.xml'),
('Technology', 'http://www.washingtonpost.com/wp-dyn/rss/technology/index.xml'), ('Technology', 'http://www.washingtonpost.com/wp-dyn/rss/technology/index.xml'),
@ -25,7 +25,7 @@ class WashingtonPost(BasicNewsRecipe):
('Education', 'http://www.washingtonpost.com/wp-dyn/rss/education/index.xml'), ('Education', 'http://www.washingtonpost.com/wp-dyn/rss/education/index.xml'),
('Editorials', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/05/30/LI2005053000331.xml'), ('Editorials', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/05/30/LI2005053000331.xml'),
] ]
remove_tags = [{'id':['pfmnav', 'ArticleCommentsWrapper']}] remove_tags = [{'id':['pfmnav', 'ArticleCommentsWrapper']}]
@ -34,7 +34,7 @@ class WashingtonPost(BasicNewsRecipe):
def print_version(self, url): def print_version(self, url):
return url.rpartition('.')[0] + '_pf.html' return url.rpartition('.')[0] + '_pf.html'
def postprocess_html(self, soup, first): def postprocess_html(self, soup, first):
for div in soup.findAll(name='div', style=re.compile('margin')): for div in soup.findAll(name='div', style=re.compile('margin')):
div['style'] = '' div['style'] = ''