Sync to trunk.

This commit is contained in:
John Schember 2011-05-03 19:18:41 -04:00
commit b686e6615e
11 changed files with 230 additions and 38 deletions

View File

@ -12,7 +12,6 @@ class AdvancedUserRecipe1301860159(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
language = 'en_EN'
remove_javascript = True remove_javascript = True
keep_only_tags = [dict(name='div', attrs={'class':'modSectionTd2'})] keep_only_tags = [dict(name='div', attrs={'class':'modSectionTd2'})]
remove_tags = [dict(name='a'),dict(name='hr')] remove_tags = [dict(name='a'),dict(name='hr')]

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = u'2010, Tomasz Dlugosz <tomek3d@gmail.com>' __copyright__ = u'2010-2011, Tomasz Dlugosz <tomek3d@gmail.com>'
''' '''
frazpc.pl frazpc.pl
''' '''
@ -19,17 +19,20 @@ class FrazPC(BasicNewsRecipe):
use_embedded_content = False use_embedded_content = False
no_stylesheets = True no_stylesheets = True
feeds = [(u'Aktualno\u015bci', u'http://www.frazpc.pl/feed'), (u'Recenzje', u'http://www.frazpc.pl/kat/recenzje-2/feed') ] feeds = [
(u'Aktualno\u015bci', u'http://www.frazpc.pl/feed/aktualnosci'),
keep_only_tags = [dict(name='div', attrs={'id':'FRAZ_CONTENT'})] (u'Artyku\u0142y', u'http://www.frazpc.pl/feed/artykuly')
remove_tags = [dict(name='p', attrs={'class':'gray tagsP fs11'})]
preprocess_regexps = [
(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
[(r'<div id="post-[0-9]*"', lambda match: '<div id="FRAZ_CONTENT"'),
(r'href="/f/news/', lambda match: 'href="http://www.frazpc.pl/f/news/'),
(r' &nbsp; <a href="http://www.frazpc.pl/[^>]*?">(Skomentuj|Komentarz(e)?\([0-9]*\))</a>&nbsp; \|', lambda match: '')]
] ]
keep_only_tags = [dict(name='div', attrs={'class':'article'})]
remove_tags = [
dict(name='div', attrs={'class':'title-wrapper'}),
dict(name='p', attrs={'class':'tags'}),
dict(name='p', attrs={'class':'article-links'}),
dict(name='div', attrs={'class':'comments_box'})
]
preprocess_regexps = [(re.compile(r'\| <a href="#comments">Komentarze \([0-9]*\)</a>'), lambda match: '')]
remove_attributes = [ 'width', 'height' ] remove_attributes = [ 'width', 'height' ]

View File

@ -607,6 +607,7 @@ class StoreBase(Plugin): # {{{
supported_platforms = ['windows', 'osx', 'linux'] supported_platforms = ['windows', 'osx', 'linux']
author = 'John Schember' author = 'John Schember'
type = _('Store') type = _('Store')
minimum_calibre_version = (0, 7, 59)
actual_plugin = None actual_plugin = None

View File

@ -613,6 +613,7 @@ from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, \
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
from calibre.devices.kobo.driver import KOBO from calibre.devices.kobo.driver import KOBO
from calibre.devices.bambook.driver import BAMBOOK from calibre.devices.bambook.driver import BAMBOOK
from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX
from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX
from calibre.ebooks.epub.fix.unmanifested import Unmanifested from calibre.ebooks.epub.fix.unmanifested import Unmanifested
@ -743,6 +744,8 @@ plugins += [
EEEREADER, EEEREADER,
NEXTBOOK, NEXTBOOK,
ITUNES, ITUNES,
BOEYE_BEX,
BOEYE_BDX,
USER_DEFINED, USER_DEFINED,
] ]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \

View File

View File

@ -0,0 +1,56 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Ken <ken at szboeye.com>'
__docformat__ = 'restructuredtext en'
'''
Device driver for BOEYE serial readers
'''
from calibre.devices.usbms.driver import USBMS
class BOEYE_BEX(USBMS):
name = 'BOEYE BEX reader driver'
gui_name = 'BOEYE BEX'
description = _('Communicate with BOEYE BEX Serial eBook readers.')
author = 'szboeye'
supported_platforms = ['windows', 'osx', 'linux']
FORMATS = ['epub', 'mobi', 'fb2', 'lit', 'prc', 'pdf', 'rtf', 'txt', 'djvu', 'doc', 'chm', 'html', 'zip', 'pdb']
VENDOR_ID = [0x0085]
PRODUCT_ID = [0x600]
VENDOR_NAME = 'LINUX'
WINDOWS_MAIN_MEM = 'FILE-STOR_GADGET'
OSX_MAIN_MEM = 'Linux File-Stor Gadget Media'
MAIN_MEMORY_VOLUME_LABEL = 'BOEYE BEX Storage Card'
EBOOK_DIR_MAIN = 'Documents'
SUPPORTS_SUB_DIRS = True
class BOEYE_BDX(USBMS):
name = 'BOEYE BDX reader driver'
gui_name = 'BOEYE BDX'
description = _('Communicate with BOEYE BDX serial eBook readers.')
author = 'szboeye'
supported_platforms = ['windows', 'osx', 'linux']
FORMATS = ['epub', 'mobi', 'fb2', 'lit', 'prc', 'pdf', 'rtf', 'txt', 'djvu', 'doc', 'chm', 'html', 'zip', 'pdb']
VENDOR_ID = [0x0085]
PRODUCT_ID = [0x800]
VENDOR_NAME = 'LINUX'
WINDOWS_MAIN_MEM = 'FILE-STOR_GADGET'
WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET'
OSX_MAIN_MEM = 'Linux File-Stor Gadget Media'
OSX_CARD_A_MEM = 'Linux File-Stor Gadget Media'
MAIN_MEMORY_VOLUME_LABEL = 'BOEYE BDX Internal Memory'
STORAGE_CARD_VOLUME_LABEL = 'BOEYE BDX Storage Card'
EBOOK_DIR_MAIN = 'Documents'
EBOOK_DIR_CARD_A = 'Documents'
SUPPORTS_SUB_DIRS = True

View File

@ -34,7 +34,7 @@ if isosx:
) )
gprefs.defaults['action-layout-toolbar'] = ( gprefs.defaults['action-layout-toolbar'] = (
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk', 'Choose Library', 'Donate', None, 'Fetch News', 'Store', 'Save To Disk',
'Connect Share', None, 'Remove Books', 'Connect Share', None, 'Remove Books',
) )
gprefs.defaults['action-layout-toolbar-device'] = ( gprefs.defaults['action-layout-toolbar-device'] = (
@ -48,7 +48,7 @@ else:
gprefs.defaults['action-layout-menubar-device'] = () gprefs.defaults['action-layout-menubar-device'] = ()
gprefs.defaults['action-layout-toolbar'] = ( gprefs.defaults['action-layout-toolbar'] = (
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk', 'Choose Library', 'Donate', None, 'Fetch News', 'Store', 'Save To Disk',
'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences', 'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences',
) )
gprefs.defaults['action-layout-toolbar-device'] = ( gprefs.defaults['action-layout-toolbar-device'] = (

View File

@ -10,7 +10,7 @@ from PyQt4.Qt import QIcon, QMenu, Qt
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.preferences.main import Preferences from calibre.gui2.preferences.main import Preferences
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.constants import DEBUG from calibre.constants import DEBUG, isosx
class PreferencesAction(InterfaceAction): class PreferencesAction(InterfaceAction):
@ -19,7 +19,8 @@ class PreferencesAction(InterfaceAction):
def genesis(self): def genesis(self):
pm = QMenu() pm = QMenu()
pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config) acname = _('Change calibre behavior') if isosx else _('Preferences')
pm.addAction(QIcon(I('config.png')), acname, self.do_config)
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'), pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
self.gui.run_wizard) self.gui.run_wizard)
if not DEBUG: if not DEBUG:

View File

@ -198,7 +198,7 @@ class MetadataSingleDialogBase(ResizableDialog):
ans = self.custom_metadata_widgets ans = self.custom_metadata_widgets
for i in range(len(ans)-1): for i in range(len(ans)-1):
if before is not None and i == 0: if before is not None and i == 0:
pass# Do something pass
if len(ans[i+1].widgets) == 2: if len(ans[i+1].widgets) == 2:
sto(ans[i].widgets[-1], ans[i+1].widgets[1]) sto(ans[i].widgets[-1], ans[i+1].widgets[1])
else: else:
@ -206,7 +206,7 @@ class MetadataSingleDialogBase(ResizableDialog):
for c in range(2, len(ans[i].widgets), 2): for c in range(2, len(ans[i].widgets), 2):
sto(ans[i].widgets[c-1], ans[i].widgets[c+1]) sto(ans[i].widgets[c-1], ans[i].widgets[c+1])
if after is not None: if after is not None:
pass # Do something pass
# }}} # }}}
def do_view_format(self, path, fmt): def do_view_format(self, path, fmt):
@ -728,7 +728,135 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
# }}} # }}}
editors = {'default': MetadataSingleDialog, 'alt1': MetadataSingleDialogAlt1} class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{
cc_two_column = False
one_line_comments_toolbar = True
def do_layout(self):
self.central_widget.clear()
self.labels = []
sto = QWidget.setTabOrder
self.central_widget.tabBar().setVisible(False)
tab0 = QWidget(self)
self.central_widget.addTab(tab0, _("&Metadata"))
l = QGridLayout()
tab0.setLayout(l)
# Basic metadata in col 0
tl = QGridLayout()
gb = QGroupBox(_('Basic metadata'), tab0)
l.addWidget(gb, 0, 0, 1, 1)
gb.setLayout(tl)
self.button_box.addButton(self.fetch_metadata_button,
QDialogButtonBox.ActionRole)
self.config_metadata_button.setToolButtonStyle(Qt.ToolButtonTextOnly)
self.config_metadata_button.setText(_('Configure metadata downloading'))
self.button_box.addButton(self.config_metadata_button,
QDialogButtonBox.ActionRole)
sto(self.button_box, self.title)
def create_row(row, widget, tab_to, button=None, icon=None, span=1):
ql = BuddyLabel(widget)
tl.addWidget(ql, row, 1, 1, 1)
tl.addWidget(widget, row, 2, 1, 1)
if button is not None:
tl.addWidget(button, row, 3, span, 1)
if icon is not None:
button.setIcon(QIcon(I(icon)))
if tab_to is not None:
if button is not None:
sto(widget, button)
sto(button, tab_to)
else:
sto(widget, tab_to)
tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1)
create_row(0, self.title, self.title_sort,
button=self.deduce_title_sort_button, span=2,
icon='auto_author_sort.png')
create_row(1, self.title_sort, self.authors)
create_row(2, self.authors, self.author_sort,
button=self.deduce_author_sort_button,
span=2, icon='auto_author_sort.png')
create_row(3, self.author_sort, self.series)
create_row(4, self.series, self.series_index,
button=self.remove_unused_series_button, icon='trash.png')
create_row(5, self.series_index, self.tags)
create_row(6, self.tags, self.rating, button=self.tags_editor_button)
create_row(7, self.rating, self.pubdate)
create_row(8, self.pubdate, self.publisher,
button=self.pubdate.clear_button, icon='trash.png')
create_row(9, self.publisher, self.timestamp)
create_row(10, self.timestamp, self.identifiers,
button=self.timestamp.clear_button, icon='trash.png')
create_row(11, self.identifiers, self.comments,
button=self.clear_identifiers_button, icon='trash.png')
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
12, 1, 1 ,1)
# Custom metadata in col 1
w = getattr(self, 'custom_metadata_widgets_parent', None)
if w is not None:
gb = QGroupBox(_('Custom metadata'), tab0)
gbl = QVBoxLayout()
gb.setLayout(gbl)
sr = QScrollArea(gb)
sr.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
sr.setWidgetResizable(True)
sr.setBackgroundRole(QPalette.Base)
sr.setFrameStyle(QFrame.NoFrame)
sr.setWidget(w)
gbl.addWidget(sr)
l.addWidget(gb, 0, 1, 1, 1)
sp = QSizePolicy()
sp.setVerticalStretch(10)
sp.setHorizontalPolicy(QSizePolicy.Fixed)
sp.setVerticalPolicy(QSizePolicy.Expanding)
gb.setSizePolicy(sp)
self.set_custom_metadata_tab_order()
# comments span col 0 & 1
w = QGroupBox(_('Comments'), tab0)
sp = QSizePolicy()
sp.setVerticalStretch(10)
sp.setHorizontalPolicy(QSizePolicy.Expanding)
sp.setVerticalPolicy(QSizePolicy.Expanding)
w.setSizePolicy(sp)
lb = QHBoxLayout()
w.setLayout(lb)
lb.addWidget(self.comments)
l.addWidget(w, 1, 0, 1, 2)
# Cover & formats in col 3
gb = QGroupBox(_('Cover'), tab0)
lb = QGridLayout()
gb.setLayout(lb)
lb.addWidget(self.cover, 0, 0, 1, 3, alignment=Qt.AlignCenter)
sto(self.clear_identifiers_button, self.cover.buttons[0])
for i, b in enumerate(self.cover.buttons[:3]):
lb.addWidget(b, 1, i, 1, 1)
sto(b, self.cover.buttons[i+1])
hl = QHBoxLayout()
for b in self.cover.buttons[3:]:
hl.addWidget(b)
sto(self.cover.buttons[-2], self.cover.buttons[-1])
lb.addLayout(hl, 2, 0, 1, 3)
l.addWidget(gb, 0, 2, 1, 1)
l.addWidget(self.formats_manager, 1, 2, 1, 1)
sto(self.cover.buttons[-1], self.formats_manager)
self.formats_manager.formats.setMaximumWidth(10000)
self.formats_manager.formats.setIconSize(QSize(32, 32))
# }}}
editors = {'default': MetadataSingleDialog, 'alt1': MetadataSingleDialogAlt1,
'alt2': MetadataSingleDialogAlt2}
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None, def edit_metadata(db, row_list, current_row, parent=None, view_slot=None,
set_current_callback=None): set_current_callback=None):

View File

@ -61,7 +61,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('bools_are_tristate', db.prefs, restart_required=True) r('bools_are_tristate', db.prefs, restart_required=True)
r = self.register r = self.register
choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1')] choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1'),
(_('All on 1 tab'), 'alt2')]
r('edit_metadata_single_layout', gprefs, choices=choices) r('edit_metadata_single_layout', gprefs, choices=choices)
def initialize(self): def initialize(self):

View File

@ -47,7 +47,7 @@ class StorePlugin(object): # {{{
def __init__(self, gui, name): def __init__(self, gui, name):
from calibre.gui2 import JSONConfig from calibre.gui2 import JSONConfig
self.gui = gui self.gui = gui
self.name = name self.name = name
self.base_plugin = None self.base_plugin = None
@ -79,14 +79,14 @@ class StorePlugin(object): # {{{
return items as a generator. return items as a generator.
Don't be lazy with the search! Load as much data as possible in the Don't be lazy with the search! Load as much data as possible in the
:class:`calibre.gui2.store.search_result.SearchResult` object. :class:`calibre.gui2.store.search_result.SearchResult` object.
However, if data (such as cover_url) However, if data (such as cover_url)
isn't available because the store does not display cover images then it's okay to isn't available because the store does not display cover images then it's okay to
ignore it. ignore it.
At the very least a :class:`calibre.gui2.store.search_result.SearchResult` At the very least a :class:`calibre.gui2.store.search_result.SearchResult`
returned by this function must have the title, author and id. returned by this function must have the title, author and id.
If you have to parse multiple pages to get all of the data then implement If you have to parse multiple pages to get all of the data then implement
:meth:`get_deatils` for retrieving additional information. :meth:`get_deatils` for retrieving additional information.
@ -105,24 +105,24 @@ class StorePlugin(object): # {{{
item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store. item_data is plugin specific and is used in :meth:`open` to open to a specifc place in the store.
''' '''
raise NotImplementedError() raise NotImplementedError()
def get_details(self, search_result, timeout=60): def get_details(self, search_result, timeout=60):
''' '''
Delayed search for information about specific search items. Delayed search for information about specific search items.
Typically, this will be used when certain information such as Typically, this will be used when certain information such as
formats, drm status, cover url are not part of the main search formats, drm status, cover url are not part of the main search
results and the information is on another web page. results and the information is on another web page.
Using this function allows for the main information (title, author) Using this function allows for the main information (title, author)
to be displayed in the search results while other information can to be displayed in the search results while other information can
take extra time to load. Splitting retrieving data that takes longer take extra time to load. Splitting retrieving data that takes longer
to load into a separate function will give the illusion of the search to load into a separate function will give the illusion of the search
being faster. being faster.
:param search_result: A search result that need details set. :param search_result: A search result that need details set.
:param timeout: The maximum amount of time in seconds to spend downloading details. :param timeout: The maximum amount of time in seconds to spend downloading details.
:return: True if the search_result was modified otherwise False :return: True if the search_result was modified otherwise False
''' '''
return False return False
@ -133,30 +133,30 @@ class StorePlugin(object): # {{{
is called to update the caches. It is recommended to call this function is called to update the caches. It is recommended to call this function
from :meth:`open`. Especially if :meth:`open` does anything other than from :meth:`open`. Especially if :meth:`open` does anything other than
open a web page. open a web page.
This function can be called at any time. It is up to the plugin to determine This function can be called at any time. It is up to the plugin to determine
if the cache really does need updating. Unless :param:`force` is True, then if the cache really does need updating. Unless :param:`force` is True, then
the plugin must update the cache. The only time force should be True is if the plugin must update the cache. The only time force should be True is if
this function is called by the plugin's configuration dialog. this function is called by the plugin's configuration dialog.
if :param:`suppress_progress` is False it is safe to assume that this function if :param:`suppress_progress` is False it is safe to assume that this function
is being called from the main GUI thread so it is safe and recommended to use is being called from the main GUI thread so it is safe and recommended to use
a QProgressDialog to display what is happening and allow the user to cancel a QProgressDialog to display what is happening and allow the user to cancel
the operation. if :param:`suppress_progress` is True then run the update the operation. if :param:`suppress_progress` is True then run the update
silently. In this case there is no guarantee what thread is calling this silently. In this case there is no guarantee what thread is calling this
function so no Qt related functionality that requires being run in the main function so no Qt related functionality that requires being run in the main
GUI thread should be run. E.G. Open a QProgressDialog. GUI thread should be run. E.G. Open a QProgressDialog.
:param parent: The parent object to be used by an GUI dialogs. :param parent: The parent object to be used by an GUI dialogs.
:param timeout: The maximum amount of time that should be spent in :param timeout: The maximum amount of time that should be spent in
any given network connection. any given network connection.
:param force: Force updating the cache even if the plugin has determined :param force: Force updating the cache even if the plugin has determined
it is not necessary. it is not necessary.
:param suppress_progress: Should a progress indicator be shown. :param suppress_progress: Should a progress indicator be shown.
:return: True if the cache was updated, False otherwise. :return: True if the cache was updated, False otherwise.
''' '''
return False return False