mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Pull from trunk
This commit is contained in:
commit
998c9f22a1
@ -13,12 +13,14 @@ def devices():
|
||||
from calibre.devices.kindle.driver import KINDLE
|
||||
from calibre.devices.kindle.driver import KINDLE2
|
||||
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
|
||||
|
||||
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_MONTH_MAP = dict(zip(MONTH_MAP.values(), MONTH_MAP.keys()))
|
||||
|
||||
|
@ -11,37 +11,36 @@ from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
from calibre.devices.usbms.driver import USBMS
|
||||
import calibre.devices.cybookg3.t2b as t2b
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
|
||||
class CYBOOKG3(USBMS):
|
||||
# Ordered list of supported formats
|
||||
# Be sure these have an entry in calibre.devices.mime
|
||||
FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt']
|
||||
|
||||
|
||||
VENDOR_ID = [0x0bda, 0x3034]
|
||||
PRODUCT_ID = [0x0703, 0x1795]
|
||||
BCD = [0x110, 0x132]
|
||||
|
||||
|
||||
VENDOR_NAME = 'BOOKEEN'
|
||||
WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD'
|
||||
WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD'
|
||||
|
||||
|
||||
OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media'
|
||||
OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media'
|
||||
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
|
||||
|
||||
|
||||
EBOOK_DIR_MAIN = "eBooks"
|
||||
EBOOK_DIR_CARD = "eBooks"
|
||||
THUMBNAIL_HEIGHT = 144
|
||||
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):
|
||||
if on_card and not self._card_prefix:
|
||||
raise ValueError(_('The reader has no storage card connected.'))
|
||||
|
||||
|
||||
if not on_card:
|
||||
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
|
||||
else:
|
||||
@ -66,7 +65,7 @@ class CYBOOKG3(USBMS):
|
||||
paths = []
|
||||
names = iter(names)
|
||||
metadata = iter(metadata)
|
||||
|
||||
|
||||
for infile in files:
|
||||
newpath = path
|
||||
mdata = metadata.next()
|
||||
@ -83,20 +82,20 @@ class CYBOOKG3(USBMS):
|
||||
newpath += tag
|
||||
newpath = os.path.normpath(newpath)
|
||||
break
|
||||
|
||||
|
||||
if newpath == path:
|
||||
newpath = os.path.join(newpath, authors_to_string(mdata.get('authors', '')))
|
||||
newpath = os.path.join(newpath, mdata.get('title', ''))
|
||||
|
||||
if not os.path.exists(newpath):
|
||||
os.makedirs(newpath)
|
||||
|
||||
|
||||
filepath = os.path.join(newpath, names.next())
|
||||
paths.append(filepath)
|
||||
|
||||
|
||||
if hasattr(infile, 'read'):
|
||||
infile.seek(0)
|
||||
|
||||
|
||||
dest = open(filepath, 'wb')
|
||||
shutil.copyfileobj(infile, dest, 10*1024*1024)
|
||||
|
||||
@ -104,33 +103,33 @@ class CYBOOKG3(USBMS):
|
||||
dest.close()
|
||||
else:
|
||||
shutil.copy2(infile, filepath)
|
||||
|
||||
coverdata = None
|
||||
|
||||
coverdata = None
|
||||
if 'cover' in mdata.keys():
|
||||
if mdata['cover'] != None:
|
||||
coverdata = mdata['cover'][2]
|
||||
|
||||
|
||||
t2bfile = open('%s_6090.t2b' % (os.path.splitext(filepath)[0]), 'wb')
|
||||
t2b.write_t2b(t2bfile, coverdata)
|
||||
t2bfile.close()
|
||||
|
||||
|
||||
return zip(paths, cycle([on_card]))
|
||||
|
||||
def delete_books(self, paths, end_session=True):
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
filepath, ext = os.path.splitext(path)
|
||||
|
||||
|
||||
# Delete the ebook auxiliary file
|
||||
if os.path.exists(filepath + '.mbp'):
|
||||
os.unlink(filepath + '.mbp')
|
||||
|
||||
|
||||
# Delete the thumbnails file auto generated for the ebook
|
||||
if os.path.exists(filepath + '_6090.t2b'):
|
||||
os.unlink(filepath + '_6090.t2b')
|
||||
|
||||
|
||||
try:
|
||||
os.removedirs(os.path.dirname(path))
|
||||
except:
|
||||
|
2
src/calibre/devices/eb600/__init__.py
Executable file
2
src/calibre/devices/eb600/__init__.py
Executable file
@ -0,0 +1,2 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
41
src/calibre/devices/eb600/driver.py
Executable file
41
src/calibre/devices/eb600/driver.py
Executable 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
|
||||
|
||||
|
@ -174,6 +174,14 @@ class Device(_Device):
|
||||
|
||||
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):
|
||||
time.sleep(6)
|
||||
drives = {}
|
||||
@ -188,11 +196,14 @@ class Device(_Device):
|
||||
if 'main' in drives.keys() and 'card' in drives.keys():
|
||||
break
|
||||
|
||||
drives = self.windows_sort_drives(drives)
|
||||
self._main_prefix = drives.get('main')
|
||||
self._card_prefix = drives.get('card')
|
||||
|
||||
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):
|
||||
if raw is None:
|
||||
|
@ -11,9 +11,7 @@ from urllib import unquote, quote
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
from calibre.constants import __version__ as VERSION
|
||||
from calibre import relpath
|
||||
from calibre.utils.config import OptionParser
|
||||
|
||||
def string_to_authors(raw):
|
||||
raw = raw.replace('&&', u'\uffff')
|
||||
@ -189,11 +187,11 @@ class MetaInformation(object):
|
||||
'publisher', 'series', 'series_index', 'rating',
|
||||
'isbn', 'tags', 'cover_data', 'application_id', 'guide',
|
||||
'manifest', 'spine', 'toc', 'cover', 'language',
|
||||
'book_producer', 'timestamp'):
|
||||
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'):
|
||||
if hasattr(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 authors: List of strings or []
|
||||
@ -204,9 +202,9 @@ class MetaInformation(object):
|
||||
title = mi.title
|
||||
authors = mi.authors
|
||||
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 []
|
||||
self.authors = authors
|
||||
self.authors = list(authors) if authors else []
|
||||
self.tags = getattr(mi, 'tags', [])
|
||||
#: mi.cover_data = (ext, data)
|
||||
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',
|
||||
'series', 'series_index', 'rating', 'isbn', 'language',
|
||||
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
|
||||
'book_producer', 'timestamp'
|
||||
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'
|
||||
):
|
||||
setattr(self, x, getattr(mi, x, None))
|
||||
|
||||
@ -229,15 +227,15 @@ class MetaInformation(object):
|
||||
if mi.authors and mi.authors[0] != _('Unknown'):
|
||||
self.authors = mi.authors
|
||||
|
||||
|
||||
for attr in ('author_sort', 'title_sort', 'category',
|
||||
'publisher', 'series', 'series_index', 'rating',
|
||||
'isbn', 'application_id', 'manifest', 'spine', 'toc',
|
||||
'cover', 'language', 'guide', 'book_producer',
|
||||
'timestamp'):
|
||||
val = getattr(mi, attr, None)
|
||||
if val is not None:
|
||||
setattr(self, attr, val)
|
||||
'timestamp', 'lccn', 'lcc', 'ddc'):
|
||||
if hasattr(mi, attr):
|
||||
val = getattr(mi, attr)
|
||||
if val is not None:
|
||||
setattr(self, attr, val)
|
||||
|
||||
if 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:
|
||||
self.cover_data = mi.cover_data
|
||||
|
||||
|
||||
my_comments = getattr(self, 'comments', '')
|
||||
other_comments = getattr(mi, 'comments', '')
|
||||
if not my_comments:
|
||||
@ -254,7 +252,7 @@ class MetaInformation(object):
|
||||
other_comments = ''
|
||||
if len(other_comments.strip()) > len(my_comments.strip()):
|
||||
self.comments = other_comments
|
||||
|
||||
|
||||
def format_series_index(self):
|
||||
try:
|
||||
x = float(self.series_index)
|
||||
@ -293,6 +291,13 @@ class MetaInformation(object):
|
||||
fmt('Rating', self.rating)
|
||||
if self.timestamp is not None:
|
||||
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)
|
||||
|
||||
def to_html(self):
|
||||
@ -302,6 +307,12 @@ class MetaInformation(object):
|
||||
ans += [(_('Producer'), unicode(self.book_producer))]
|
||||
ans += [(_('Comments'), unicode(self.comments))]
|
||||
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]))]
|
||||
if self.series:
|
||||
ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())]
|
||||
|
@ -59,8 +59,9 @@ class EXTHHeader(object):
|
||||
elif id == 502:
|
||||
# last update time
|
||||
pass
|
||||
elif id == 503 and (not title or title == _('Unknown')):
|
||||
title = content
|
||||
elif id == 503: # Long title
|
||||
if not title or title == _('Unknown'):
|
||||
title = content
|
||||
#else:
|
||||
# print 'unknown record', id, repr(content)
|
||||
if title:
|
||||
@ -87,6 +88,8 @@ class EXTHHeader(object):
|
||||
content, '%Y-%m-%d',).date()
|
||||
except:
|
||||
pass
|
||||
elif id == 108:
|
||||
pass # Producer
|
||||
#else:
|
||||
# print 'unhandled metadata record', id, repr(content)
|
||||
|
||||
@ -522,7 +525,8 @@ class MobiReader(object):
|
||||
else:
|
||||
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():
|
||||
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
|
||||
|
||||
|
||||
|
@ -466,5 +466,3 @@ class Application(QApplication):
|
||||
self.translator.loadFromData(data)
|
||||
self.installTranslator(self.translator)
|
||||
|
||||
|
||||
|
||||
|
@ -199,7 +199,7 @@ class EmailAccounts(QAbstractTableModel):
|
||||
return (account, self.accounts[account])
|
||||
if role == Qt.ToolTipRole:
|
||||
return self.tooltips[col]
|
||||
if role == Qt.DisplayRole:
|
||||
if role in [Qt.DisplayRole, Qt.EditRole]:
|
||||
if col == 0:
|
||||
return QVariant(account)
|
||||
if col == 1:
|
||||
@ -397,6 +397,9 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
self.separate_cover_flow.setChecked(config['separate_cover_flow'])
|
||||
self.setup_email_page()
|
||||
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):
|
||||
opts = smtp_prefs().parse()
|
||||
|
@ -371,7 +371,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="delete_news">
|
||||
<property name="text">
|
||||
<string>&Delete news from library when it is sent to reader</string>
|
||||
<string>&Delete news from library when it is automatically sent to reader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -324,7 +324,7 @@
|
||||
<string>Book </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
|
@ -25,7 +25,7 @@ from calibre.gui2.dialogs.user_profiles import UserProfiles
|
||||
config = DynamicConfig('scheduler')
|
||||
|
||||
class Recipe(object):
|
||||
|
||||
|
||||
def __init__(self, id=None, recipe_class=None, builtin=True):
|
||||
self.id = id
|
||||
self.title = getattr(recipe_class, 'title', None)
|
||||
@ -39,14 +39,14 @@ class Recipe(object):
|
||||
if self.author == _('Unknown') and not builtin:
|
||||
self.author = _('You')
|
||||
self.needs_subscription = getattr(recipe_class, 'needs_subscription', False)
|
||||
|
||||
|
||||
def pickle(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
|
||||
def unpickle(self, dict):
|
||||
self.__dict__.update(dict)
|
||||
return self
|
||||
|
||||
|
||||
def __cmp__(self, other):
|
||||
if self.id == getattr(other, 'id', None):
|
||||
return 0
|
||||
@ -59,38 +59,39 @@ class Recipe(object):
|
||||
if not self.builtin and getattr(other, 'builtin', True):
|
||||
return -1
|
||||
return english_sort(self.title, getattr(other, 'title', ''))
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == getattr(other, 'id', None)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
schedule = self.schedule
|
||||
if schedule and schedule > 1e5:
|
||||
schedule = decode_schedule(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)]
|
||||
|
||||
def save_recipes(recipes):
|
||||
config['scheduled_recipes'] = [r.pickle() for r in recipes]
|
||||
|
||||
|
||||
def load_recipes():
|
||||
config.refresh()
|
||||
recipes = []
|
||||
for r in config.get('scheduled_recipes', []):
|
||||
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
|
||||
recipes.append(r)
|
||||
return recipes
|
||||
|
||||
class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
|
||||
|
||||
LOCATIONS = ['all']
|
||||
|
||||
|
||||
def __init__(self, db, *args):
|
||||
QAbstractItemModel.__init__(self, *args)
|
||||
SearchQueryParser.__init__(self)
|
||||
@ -104,18 +105,18 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
self.bold_font = QFont()
|
||||
self.bold_font.setBold(True)
|
||||
self.bold_font = QVariant(self.bold_font)
|
||||
|
||||
|
||||
|
||||
|
||||
def refresh(self):
|
||||
sr = load_recipes()
|
||||
for recipe in self.recipes:
|
||||
if recipe in sr:
|
||||
recipe.schedule = sr[sr.index(recipe)].schedule
|
||||
recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded
|
||||
|
||||
|
||||
self.recipes.sort()
|
||||
self.num_of_recipes = len(self.recipes)
|
||||
|
||||
|
||||
self.category_map = {}
|
||||
for r in self.recipes:
|
||||
category = getattr(r, 'language', _('Unknown'))
|
||||
@ -126,12 +127,12 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
if category not in self.category_map.keys():
|
||||
self.category_map[category] = []
|
||||
self.category_map[category].append(r)
|
||||
|
||||
|
||||
self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories)
|
||||
self._map = dict(self.category_map)
|
||||
|
||||
|
||||
def sort_categories(self, x, y):
|
||||
|
||||
|
||||
def decorate(x):
|
||||
if x == _('Scheduled'):
|
||||
x = '0' + x
|
||||
@ -140,13 +141,13 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
else:
|
||||
x = '2' + x
|
||||
return x
|
||||
|
||||
|
||||
return cmp(decorate(x), decorate(y))
|
||||
|
||||
|
||||
|
||||
|
||||
def universal_set(self):
|
||||
return set(self.recipes)
|
||||
|
||||
|
||||
def get_matches(self, location, query):
|
||||
query = query.strip().lower()
|
||||
if not query:
|
||||
@ -154,9 +155,9 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
results = set([])
|
||||
for recipe in self.recipes:
|
||||
if query in recipe.title.lower() or query in recipe.description.lower():
|
||||
results.add(recipe)
|
||||
results.add(recipe)
|
||||
return results
|
||||
|
||||
|
||||
def search(self, query):
|
||||
try:
|
||||
results = self.parse(unicode(query))
|
||||
@ -170,24 +171,24 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
if recipe in results:
|
||||
self._map[category].append(recipe)
|
||||
self.reset()
|
||||
|
||||
|
||||
def resort(self):
|
||||
self.recipes.sort()
|
||||
self.reset()
|
||||
|
||||
|
||||
def index(self, row, column, parent):
|
||||
return self.createIndex(row, column, parent.row() if parent.isValid() else -1)
|
||||
|
||||
|
||||
def parent(self, index):
|
||||
if index.internalId() == -1:
|
||||
return QModelIndex()
|
||||
return self.createIndex(index.internalId(), 0, -1)
|
||||
|
||||
|
||||
def columnCount(self, parent):
|
||||
if not parent.isValid() or not parent.parent().isValid():
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def rowCount(self, parent):
|
||||
if not parent.isValid():
|
||||
return len(self.categories)
|
||||
@ -195,7 +196,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
category = self.categories[parent.row()]
|
||||
return len(self._map[category])
|
||||
return 0
|
||||
|
||||
|
||||
def data(self, index, role):
|
||||
if index.parent().isValid():
|
||||
category = self.categories[index.parent().row()]
|
||||
@ -206,7 +207,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
return recipe
|
||||
elif role == Qt.DecorationRole:
|
||||
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:
|
||||
icon = self.custom_icon
|
||||
elif QFile().exists(icon_path):
|
||||
@ -222,18 +223,18 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
elif role == Qt.ForegroundRole and category == _('Scheduled'):
|
||||
return QVariant(QColor(0, 255, 0))
|
||||
return NONE
|
||||
|
||||
|
||||
def update_recipe_schedule(self, recipe):
|
||||
for srecipe in self.recipes:
|
||||
if srecipe == recipe:
|
||||
srecipe.schedule = recipe.schedule
|
||||
|
||||
|
||||
|
||||
class Search(QLineEdit):
|
||||
|
||||
|
||||
HELP_TEXT = _('Search')
|
||||
INTERVAL = 500 #: Time to wait before emitting search signal
|
||||
|
||||
|
||||
def __init__(self, *args):
|
||||
QLineEdit.__init__(self, *args)
|
||||
self.default_palette = QApplication.palette(self)
|
||||
@ -244,20 +245,20 @@ class Search(QLineEdit):
|
||||
self.clear_to_help_mode()
|
||||
self.timer = None
|
||||
self.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
|
||||
|
||||
|
||||
def focusInEvent(self, ev):
|
||||
self.setPalette(QApplication.palette(self))
|
||||
if self.in_help_mode():
|
||||
self.setText('')
|
||||
return QLineEdit.focusInEvent(self, ev)
|
||||
|
||||
|
||||
def in_help_mode(self):
|
||||
return unicode(self.text()) == self.HELP_TEXT
|
||||
|
||||
|
||||
def clear_to_help_mode(self):
|
||||
self.setPalette(self.gray)
|
||||
self.setText(self.HELP_TEXT)
|
||||
|
||||
|
||||
def text_edited_slot(self, text):
|
||||
text = unicode(text)
|
||||
self.timer = self.startTimer(self.INTERVAL)
|
||||
@ -281,7 +282,7 @@ def decode_schedule(num):
|
||||
return day-1, hour-1, minute-1
|
||||
|
||||
class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
|
||||
|
||||
def __init__(self, db, *args):
|
||||
QDialog.__init__(self, *args)
|
||||
self.setupUi(self)
|
||||
@ -308,25 +309,25 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.search.setFocus(Qt.OtherFocusReason)
|
||||
self.old_news.setValue(gconf['oldest_news'])
|
||||
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')):
|
||||
self.day.addItem(day)
|
||||
|
||||
|
||||
def currentChanged(self, current, previous):
|
||||
if current.parent().isValid():
|
||||
self.show_recipe(current)
|
||||
|
||||
|
||||
def download_now(self):
|
||||
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
|
||||
self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe)
|
||||
|
||||
|
||||
def set_account_info(self, *args):
|
||||
username, password = map(unicode, (self.username.text(), self.password.text()))
|
||||
username, password = username.strip(), password.strip()
|
||||
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
|
||||
key = 'recipe_account_info_%s'%recipe.id
|
||||
config[key] = (username, password) if username and password else None
|
||||
|
||||
|
||||
def do_schedule(self, *args):
|
||||
if not getattr(self, 'allow_scheduling', False):
|
||||
return
|
||||
@ -342,7 +343,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
recipe.last_downloaded = datetime.fromordinal(1)
|
||||
recipes.append(recipe)
|
||||
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_()
|
||||
self.schedule.setCheckState(Qt.Unchecked)
|
||||
return
|
||||
@ -364,7 +365,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
save_recipes(recipes)
|
||||
self._model.update_recipe_schedule(recipe)
|
||||
self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes)
|
||||
|
||||
|
||||
def show_recipe(self, index):
|
||||
recipe = self._model.data(index, Qt.UserRole)
|
||||
self.current_recipe = recipe
|
||||
@ -395,7 +396,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.interval_button.setChecked(False)
|
||||
self.interval.setEnabled(False)
|
||||
self.schedule.setChecked(recipe.schedule is not None)
|
||||
self.allow_scheduling = True
|
||||
self.allow_scheduling = True
|
||||
self.detail_box.setVisible(True)
|
||||
self.account.setVisible(recipe.needs_subscription)
|
||||
self.interval.setEnabled(self.schedule.checkState() == Qt.Checked)
|
||||
@ -417,11 +418,11 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.last_downloaded.setText(_('Last downloaded')+': '+tm)
|
||||
else:
|
||||
self.last_downloaded.setText(_('Last downloaded: never'))
|
||||
|
||||
|
||||
class Scheduler(QObject):
|
||||
|
||||
|
||||
INTERVAL = 1 # minutes
|
||||
|
||||
|
||||
def __init__(self, main):
|
||||
self.main = main
|
||||
self.verbose = main.verbose
|
||||
@ -439,7 +440,7 @@ class Scheduler(QObject):
|
||||
self.oldest = gconf['oldest_news']
|
||||
self.oldest_timer.start(int(60 * 60000))
|
||||
self.oldest_check()
|
||||
|
||||
|
||||
self.news_menu = QMenu()
|
||||
self.news_icon = QIcon(':/images/news.svg')
|
||||
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.connect(self.cac, SIGNAL('triggered(bool)'), self.customize_feeds)
|
||||
self.news_menu.addAction(self.cac)
|
||||
|
||||
|
||||
def oldest_check(self):
|
||||
if self.oldest > 0:
|
||||
delta = timedelta(days=self.oldest)
|
||||
ids = self.main.library_view.model().db.tags_older_than(_('News'), delta)
|
||||
if ids:
|
||||
self.main.library_view.model().delete_books_by_id(ids)
|
||||
|
||||
|
||||
def customize_feeds(self, *args):
|
||||
main = self.main
|
||||
d = UserProfiles(main, main.library_view.model().db.get_feeds())
|
||||
d.exec_()
|
||||
feeds = tuple(d.profiles())
|
||||
main.library_view.model().db.set_feeds(feeds)
|
||||
|
||||
|
||||
|
||||
|
||||
def debug(self, *args):
|
||||
if self.verbose:
|
||||
sys.stdout.write(' '.join(map(unicode, args))+'\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def check(self):
|
||||
if not self.lock.tryLock():
|
||||
return
|
||||
@ -494,15 +495,15 @@ class Scheduler(QObject):
|
||||
matches = day_matches and (hour*60+minute) < tnow
|
||||
if matches and recipe.last_downloaded.toordinal() < date.today().toordinal():
|
||||
needs_downloading.add(recipe)
|
||||
|
||||
|
||||
self.debug('Needs downloading:', needs_downloading)
|
||||
|
||||
|
||||
needs_downloading = [r for r in needs_downloading if r not in self.queue]
|
||||
for recipe in needs_downloading:
|
||||
self.do_download(recipe)
|
||||
finally:
|
||||
self.lock.unlock()
|
||||
|
||||
|
||||
def do_download(self, recipe):
|
||||
try:
|
||||
id = int(recipe.id)
|
||||
@ -538,7 +539,7 @@ class Scheduler(QObject):
|
||||
finally:
|
||||
self.lock.unlock()
|
||||
self.debug('Downloaded:', recipe)
|
||||
|
||||
|
||||
def download(self, recipe):
|
||||
self.lock.lock()
|
||||
try:
|
||||
@ -548,10 +549,10 @@ class Scheduler(QObject):
|
||||
self.do_download(recipe)
|
||||
finally:
|
||||
self.lock.unlock()
|
||||
|
||||
|
||||
def refresh_schedule(self, recipes):
|
||||
self.recipes = recipes
|
||||
|
||||
|
||||
def show_dialog(self, *args):
|
||||
self.lock.lock()
|
||||
try:
|
||||
|
BIN
src/calibre/gui2/images/news/azstarnet.png
Normal file
BIN
src/calibre/gui2/images/news/azstarnet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 360 B |
BIN
src/calibre/gui2/images/news/corriere_della_sera_en.png
Normal file
BIN
src/calibre/gui2/images/news/corriere_della_sera_en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 524 B |
BIN
src/calibre/gui2/images/news/corriere_della_sera_it.png
Normal file
BIN
src/calibre/gui2/images/news/corriere_della_sera_it.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 524 B |
BIN
src/calibre/gui2/images/news/msdnmag_en.png
Normal file
BIN
src/calibre/gui2/images/news/msdnmag_en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 694 B |
@ -1119,27 +1119,30 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
return
|
||||
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)
|
||||
try:
|
||||
ext = os.path.splitext(name)[1].upper().replace('.', '')
|
||||
if ext in config['internally_viewed_formats']:
|
||||
if ext == 'LRF':
|
||||
args = ['lrfviewer', name]
|
||||
self.job_manager.server.run_free_job('lrfviewer',
|
||||
kwdargs=dict(args=args))
|
||||
else:
|
||||
args = ['ebook-viewer', name]
|
||||
if isosx:
|
||||
args.append('--raise-window')
|
||||
self.job_manager.server.run_free_job('ebook-viewer',
|
||||
kwdargs=dict(args=args))
|
||||
if internal:
|
||||
args = [viewer]
|
||||
if isosx and 'ebook' in viewer:
|
||||
args.append('--raise-window')
|
||||
if name is not None:
|
||||
args.append(name)
|
||||
self.job_manager.server.run_free_job(viewer,
|
||||
kwdargs=dict(args=args))
|
||||
else:
|
||||
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(name))#launch(name)
|
||||
|
||||
time.sleep(5) # User feedback
|
||||
finally:
|
||||
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):
|
||||
rows = self.library_view.selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
@ -1174,8 +1177,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
rows = self.current_view().selectionModel().selectedRows()
|
||||
if self.current_view() is self.library_view:
|
||||
if not rows or len(rows) == 0:
|
||||
d = error_dialog(self, _('Cannot view'), _('No book selected'))
|
||||
d.exec_()
|
||||
self._launch_viewer()
|
||||
return
|
||||
|
||||
row = rows[0].row()
|
||||
|
@ -15,6 +15,7 @@ from calibre import terminal_controller, preferred_encoding
|
||||
from calibre.utils.config import OptionParser, prefs
|
||||
try:
|
||||
from calibre.utils.single_qt_application import send_message
|
||||
send_message
|
||||
except:
|
||||
send_message = None
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
@ -55,7 +56,7 @@ XML_TEMPLATE = '''\
|
||||
</py:for>
|
||||
</formats>
|
||||
</record>
|
||||
</py:for>
|
||||
</py:for>
|
||||
</calibredb>
|
||||
'''
|
||||
|
||||
@ -114,7 +115,7 @@ def get_db(dbpath, options):
|
||||
dbpath = os.path.abspath(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'):
|
||||
if sort_by:
|
||||
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 j, field in enumerate(fields):
|
||||
widths[j] = max(widths[j], len(unicode(i[str(field)])))
|
||||
|
||||
|
||||
screen_width = terminal_controller.COLS if line_width < 0 else 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)):
|
||||
@ -150,14 +151,14 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
|
||||
break
|
||||
if not adjusted:
|
||||
break
|
||||
|
||||
|
||||
widths = list(base_widths)
|
||||
titles = map(lambda x, y: '%-*s'%(x, y), 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))
|
||||
@ -178,9 +179,9 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator,
|
||||
if isinstance(x['fmt_epub'], unicode):
|
||||
x['fmt_epub'] = x['fmt_epub'].encode('utf-8')
|
||||
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')
|
||||
|
||||
|
||||
|
||||
|
||||
def command_list(args, dbpath):
|
||||
@ -199,7 +200,7 @@ List the books available in the calibre database.
|
||||
help=_('Sort results in ascending order'))
|
||||
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.'))
|
||||
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.'))
|
||||
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.'))
|
||||
@ -264,14 +265,14 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates):
|
||||
|
||||
formats.append(format)
|
||||
metadata.append(mi)
|
||||
|
||||
|
||||
file_duplicates = []
|
||||
if files:
|
||||
file_duplicates = db.add_books(files, formats, metadata,
|
||||
file_duplicates = db.add_books(files, formats, metadata,
|
||||
add_duplicates=add_duplicates)
|
||||
if file_duplicates:
|
||||
file_duplicates = file_duplicates[0]
|
||||
|
||||
|
||||
|
||||
dir_dups = []
|
||||
for dir in dirs:
|
||||
|
@ -24,7 +24,7 @@ from calibre.translations.msgfmt import make
|
||||
_run_once = False
|
||||
if not _run_once:
|
||||
_run_once = True
|
||||
|
||||
|
||||
################################################################################
|
||||
# Setup translations
|
||||
|
||||
@ -32,7 +32,8 @@ if not _run_once:
|
||||
lang = prefs['language']
|
||||
if lang is not None:
|
||||
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
|
||||
try:
|
||||
lang = os.environ['LANG']
|
||||
|
@ -38,6 +38,7 @@ def get_linux_data(version='1.0.0'):
|
||||
('exherbo', 'Exherbo'),
|
||||
('foresight', 'Foresight 2.1'),
|
||||
('ubuntu', 'Ubuntu Jaunty Jackalope'),
|
||||
('linux_mint', 'Linux Mint Gloria'),
|
||||
]:
|
||||
data['supported'].append(CoolDistro(name, title,
|
||||
prefix='http://calibre.kovidgoyal.net'))
|
||||
|
BIN
src/calibre/trac/plugins/htdocs/images/linux_mint_logo.png
Normal file
BIN
src/calibre/trac/plugins/htdocs/images/linux_mint_logo.png
Normal file
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
@ -8,7 +8,7 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'newsweek', 'atlantic', 'economist', 'portfolio', 'the_register',
|
||||
'usatoday', 'outlook_india', 'bbc', 'greader', 'wsj',
|
||||
'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',
|
||||
'daily_telegraph', 'guardian', 'el_pais', 'new_scientist', 'b92',
|
||||
'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',
|
||||
'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list',
|
||||
'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
|
||||
|
63
src/calibre/web/feeds/recipes/recipe_azstarnet.py
Normal file
63
src/calibre/web/feeds/recipes/recipe_azstarnet.py
Normal 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
|
||||
|
@ -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')]
|
||||
|
@ -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' )
|
||||
]
|
||||
|
@ -12,7 +12,7 @@ from calibre.ptempfile import PersistentTemporaryFile
|
||||
class InternationalHeraldTribune(BasicNewsRecipe):
|
||||
title = u'The International Herald Tribune'
|
||||
__author__ = 'Derry FitzGerald'
|
||||
language = _('English')
|
||||
language = _('English')
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 10
|
||||
no_stylesheets = True
|
||||
@ -20,13 +20,13 @@ class InternationalHeraldTribune(BasicNewsRecipe):
|
||||
remove_tags = [dict(name='div', attrs={'class':'footer'}),
|
||||
dict(name=['form'])]
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<!-- webtrends.*', re.DOTALL),
|
||||
(re.compile(r'<!-- webtrends.*', re.DOTALL),
|
||||
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 = [
|
||||
(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'Americas', u'http://www.iht.com/rss/america.xml'),
|
||||
(u'Europe', u'http://www.iht.com/rss/europe.xml'),
|
||||
@ -45,7 +45,7 @@ class InternationalHeraldTribune(BasicNewsRecipe):
|
||||
]
|
||||
temp_files = []
|
||||
articles_are_obfuscated = True
|
||||
|
||||
|
||||
def get_obfuscated_article(self, url, logger):
|
||||
br = self.get_browser()
|
||||
br.open(url)
|
||||
@ -55,4 +55,4 @@ class InternationalHeraldTribune(BasicNewsRecipe):
|
||||
self.temp_files.append(PersistentTemporaryFile('_iht.html'))
|
||||
self.temp_files[-1].write(html)
|
||||
self.temp_files[-1].close()
|
||||
return self.temp_files[-1].name
|
||||
return self.temp_files[-1].name
|
||||
|
61
src/calibre/web/feeds/recipes/recipe_msdnmag_en.py
Normal file
61
src/calibre/web/feeds/recipes/recipe_msdnmag_en.py
Normal 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
|
||||
|
@ -9,11 +9,12 @@ newyorker.com
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class NewYorker(BasicNewsRecipe):
|
||||
|
||||
title = u'The New Yorker'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'The best of US journalism'
|
||||
description = 'The best of US journalism'
|
||||
oldest_article = 7
|
||||
language = _('English')
|
||||
language = _('English')
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = False
|
||||
use_embedded_content = False
|
||||
@ -24,7 +25,7 @@ class NewYorker(BasicNewsRecipe):
|
||||
.calibre_recipe_title {font-size:normal}
|
||||
.calibre_feed_description {font-size:xx-small}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div' , attrs={'id':'printbody' })
|
||||
|
@ -1,38 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__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
|
||||
|
||||
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"
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Hardware reviews and News'
|
||||
no_stylesheets = True
|
||||
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
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2lrf_options = [ '--comment' , description
|
||||
, '--category' , 'hardware,news'
|
||||
, '--base-font-size', '10'
|
||||
]
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open(self.INDEX+'/us/')
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open(self.LOGIN)
|
||||
br.select_form(name='connexion')
|
||||
br['login'] = self.username
|
||||
br['mdp' ] = self.password
|
||||
br.submit()
|
||||
data = urllib.urlencode({ 'action':'login_action'
|
||||
,'r':self.INDEX+'/us/'
|
||||
,'login':self.username
|
||||
,'mdp':self.password
|
||||
})
|
||||
br.open(self.LOGIN,data)
|
||||
return br
|
||||
|
||||
remove_tags = [
|
||||
@ -41,17 +50,18 @@ class Tomshardware(BasicNewsRecipe):
|
||||
]
|
||||
|
||||
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')
|
||||
]
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
main, sep, rest = url.rpartition('.html')
|
||||
rmain, rsep, article_id = main.rpartition(',')
|
||||
tmain, tsep, trest = rmain.rpartition('/reviews/')
|
||||
rind = 'http://www.tomshardware.com/news_print.php?p1='
|
||||
if tsep:
|
||||
return 'http://www.tomshardware.com/review_print.php?p1=' + article_id
|
||||
return 'http://www.tomshardware.com/news_print.php?p1=' + article_id
|
||||
rind = 'http://www.tomshardware.com/review_print.php?p1='
|
||||
return rind + article_id
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
del(soup.body['onload'])
|
||||
|
@ -11,13 +11,13 @@ class WashingtonPost(BasicNewsRecipe):
|
||||
max_articles_per_feed = 20
|
||||
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'),
|
||||
('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'),
|
||||
('Business', 'http://www.washingtonpost.com/wp-dyn/rss/business/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'),
|
||||
('Editorials', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/05/30/LI2005053000331.xml'),
|
||||
]
|
||||
|
||||
|
||||
remove_tags = [{'id':['pfmnav', 'ArticleCommentsWrapper']}]
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ class WashingtonPost(BasicNewsRecipe):
|
||||
|
||||
def print_version(self, url):
|
||||
return url.rpartition('.')[0] + '_pf.html'
|
||||
|
||||
|
||||
def postprocess_html(self, soup, first):
|
||||
for div in soup.findAll(name='div', style=re.compile('margin')):
|
||||
div['style'] = ''
|
||||
|
Loading…
x
Reference in New Issue
Block a user