From 81864723c683661abbb977162ad6cc8a9241aaf0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 09:22:40 -0700 Subject: [PATCH 01/18] Fix #7981 (New recipe for Mish's Global Economic Trend Analysis) --- resources/recipes/ecotrend.recipe | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 resources/recipes/ecotrend.recipe diff --git a/resources/recipes/ecotrend.recipe b/resources/recipes/ecotrend.recipe new file mode 100644 index 0000000000..679f190e96 --- /dev/null +++ b/resources/recipes/ecotrend.recipe @@ -0,0 +1,42 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, Darko Miletic ' +''' +globaleconomicanalysis.blogspot.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class GlobalEconomicAnalysis(BasicNewsRecipe): + title = "Mish's Global Economic Trend Analysis" + __author__ = 'Darko Miletic' + description = 'Thoughts on the global economy, housing, gold, silver, interest rates, oil, energy, China, commodities, the dollar, Euro, Renminbi, Yen, inflation, deflation, stagflation, precious metals, emerging markets, and policy decisions that affect the global markets.' + publisher = 'Mike Shedlock' + category = 'news, politics, economy, banking' + oldest_article = 7 + max_articles_per_feed = 200 + no_stylesheets = True + encoding = 'utf8' + use_embedded_content = True + language = 'en' + remove_empty_feeds = True + publication_type = 'blog' + masthead_url = 'http://www.pagina12.com.ar/commons/imgs/logo-home.gif' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif } + img{margin-bottom: 0.4em; display:block} + """ + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + remove_tags = [ + dict(name=['meta','link','iframe','object','embed']) + ,dict(attrs={'class':'blogger-post-footer'}) + ] + remove_attributes=['border'] + + feeds = [(u'Articles', u'http://feeds2.feedburner.com/MishsGlobalEconomicTrendAnalysis')] From eac89c5439c99337dd94618f17cb5ab3827527bd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 09:55:44 -0700 Subject: [PATCH 02/18] Linux binary build: If setting system default locale fails, try setting locale to en_US.UTF-8 instead --- setup/installer/linux/freeze2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup/installer/linux/freeze2.py b/setup/installer/linux/freeze2.py index df2c1d6480..7a2980039e 100644 --- a/setup/installer/linux/freeze2.py +++ b/setup/installer/linux/freeze2.py @@ -318,7 +318,11 @@ class LinuxFreeze(Command): import codecs def set_default_encoding(): - locale.setlocale(locale.LC_ALL, '') + try: + locale.setlocale(locale.LC_ALL, '') + except: + print 'WARNING: Failed to set default libc locale, using en_US' + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') enc = locale.getdefaultlocale()[1] if not enc: enc = locale.nl_langinfo(locale.CODESET) From 2719075963b5d502eba3378dde7d46d8a05ecaf0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 10:07:37 -0700 Subject: [PATCH 03/18] Update Gazet van Antwerpen --- resources/recipes/gva_be.recipe | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/resources/recipes/gva_be.recipe b/resources/recipes/gva_be.recipe index 34c4122394..f42bd23417 100644 --- a/resources/recipes/gva_be.recipe +++ b/resources/recipes/gva_be.recipe @@ -40,13 +40,12 @@ class GazetvanAntwerpen(BasicNewsRecipe): remove_tags_after = dict(name='span', attrs={'class':'author'}) feeds = [ - (u'Overzicht & Blikvanger', u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/overview/overzicht' ) + (u'Binnenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/binnenland' ) + ,(u'Buitenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/buitenland' ) ,(u'Stad & Regio' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/stadenregio' ) ,(u'Economie' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/economie' ) - ,(u'Binnenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/binnenland' ) - ,(u'Buitenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/buitenland' ) ,(u'Media & Cultur' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/mediaencultuur') - ,(u'Wetenschap' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/mediaencultuur') + ,(u'Wetenschap' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/wetenschap' ) ,(u'Sport' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/sport' ) ] From 8ea848312c666cce6e6a71190aa4f834971a1ce1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 10:52:44 -0700 Subject: [PATCH 04/18] Fix #7967 (Specific format actions show non-existent formats) --- setup/installer/linux/freeze2.py | 2 +- src/calibre/gui2/actions/delete.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/setup/installer/linux/freeze2.py b/setup/installer/linux/freeze2.py index 7a2980039e..bd8463b1a7 100644 --- a/setup/installer/linux/freeze2.py +++ b/setup/installer/linux/freeze2.py @@ -321,7 +321,7 @@ class LinuxFreeze(Command): try: locale.setlocale(locale.LC_ALL, '') except: - print 'WARNING: Failed to set default libc locale, using en_US' + print 'WARNING: Failed to set default libc locale, using en_US.UTF-8' locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') enc = locale.getdefaultlocale()[1] if not enc: diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 24dd1d3e5c..27973b5f5b 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -97,10 +97,15 @@ class DeleteAction(InterfaceAction): for action in list(self.delete_menu.actions())[1:]: action.setEnabled(enabled) - def _get_selected_formats(self, msg): + def _get_selected_formats(self, msg, ids): from calibre.gui2.dialogs.select_formats import SelectFormats - fmts = self.gui.library_view.model().db.all_formats() - d = SelectFormats([x.lower() for x in fmts], msg, parent=self.gui) + fmts = set([]) + db = self.gui.library_view.model().db + for x in ids: + fmts_ = db.formats(x, index_is_id=True, verify_formats=False) + if fmts_: + fmts.update(frozenset([x.lower() for x in fmts_.split(',')])) + d = SelectFormats(list(sorted(fmts)), msg, parent=self.gui) if d.exec_() != d.Accepted: return None return d.selected_formats @@ -118,7 +123,7 @@ class DeleteAction(InterfaceAction): if not ids: return fmts = self._get_selected_formats( - _('Choose formats to be deleted')) + _('Choose formats to be deleted'), ids) if not fmts: return for id in ids: @@ -136,7 +141,7 @@ class DeleteAction(InterfaceAction): if not ids: return fmts = self._get_selected_formats( - '

'+_('Choose formats not to be deleted')) + '

'+_('Choose formats not to be deleted'), ids) if fmts is None: return for id in ids: From f4b7d64708312a8966cede28ccb24430a8456a19 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 11:15:10 -0700 Subject: [PATCH 05/18] E-book viewer: SHow format of current book in the title bar. Fixes #7974 (Viewer - more info in title bar) --- src/calibre/gui2/viewer/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index f7b8a8d401..25f69b1558 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -650,7 +650,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_table_of_contents.setDisabled(not self.iterator.toc) self.current_book_has_toc = bool(self.iterator.toc) self.current_title = title - self.setWindowTitle(self.base_window_title+' - '+title) + self.setWindowTitle(self.base_window_title+' - '+title + + ' [%s]'%os.path.splitext(pathtoebook)[1][1:].upper()) self.pos.setMaximum(sum(self.iterator.pages)) self.pos.setSuffix(' / %d'%sum(self.iterator.pages)) self.vertical_scrollbar.setMinimum(100) From b4d7c5f7a6e8cba8166be30a5ec217e5b8c73084 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 11:21:37 -0700 Subject: [PATCH 06/18] ... --- src/calibre/devices/usbms/device.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index af2948cf82..2c095d6f7b 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -605,8 +605,9 @@ class Device(DeviceConfig, DevicePlugin): main, carda, cardb = self.find_device_nodes() if main is None: - raise DeviceError(_('Unable to detect the %s disk drive. Your ' - ' kernel is probably exporting a deprecated version of SYSFS.') + raise DeviceError(_('Unable to detect the %s disk drive. Either ' + 'the device has already been ejected, or your ' + 'kernel is exporting a deprecated version of SYSFS.') %self.__class__.__name__) self._linux_mount_map = {} From a16708ca72c07bcf5d015440b0172e933f15638c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 11:58:07 -0700 Subject: [PATCH 07/18] Implement #7947 (Bulk grab cover from format) --- src/calibre/gui2/dialogs/metadata_bulk.py | 47 ++++++++++++++++++++++- src/calibre/gui2/dialogs/metadata_bulk.ui | 11 +++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index e0f1f83c73..b875de14e3 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal ' '''Dialog to edit metadata in bulk''' -import re +import re, os from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ pyqtSignal, QDialogButtonBox @@ -12,12 +12,41 @@ from PyQt4 import QtGui from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.ebooks.metadata import string_to_authors, authors_to_string +from calibre.ebooks.metadata.meta import get_metadata from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2 import error_dialog from calibre.gui2.progress_indicator import ProgressIndicator from calibre.utils.config import dynamic from calibre.utils.titlecase import titlecase from calibre.utils.icu import sort_key, capitalize +from calibre.utils.config import prefs +from calibre.utils.magick.draw import identify_data + +def get_cover_data(path): + old = prefs['read_file_metadata'] + if not old: + prefs['read_file_metadata'] = True + cdata = area = None + + try: + mi = get_metadata(open(path, 'rb'), + os.path.splitext(path)[1][1:].lower()) + if mi.cover and os.access(mi.cover, os.R_OK): + cdata = open(mi.cover).read() + elif mi.cover_data[1] is not None: + cdata = mi.cover_data[1] + if cdata: + width, height, fmt = identify_data(cdata) + area = width*height + except: + cdata = area = None + + if old != prefs['read_file_metadata']: + prefs['read_file_metadata'] = old + + return cdata, area + + class MyBlockingBusy(QDialog): @@ -146,6 +175,20 @@ class MyBlockingBusy(QDialog): cdata = calibre_cover(mi.title, mi.format_field('authors')[-1], series_string=series_string) self.db.set_cover(id, cdata) + elif cover_action == 'fromfmt': + fmts = self.db.formats(id, index_is_id=True, verify_formats=False) + if fmts: + covers = [] + for fmt in fmts.split(','): + fmt = self.db.format_abspath(id, fmt, index_is_id=True) + if not fmt: continue + cdata, area = get_cover_data(fmt) + if cdata: + covers.append((cdata, area)) + covers.sort(key=lambda x: x[1]) + if covers: + self.db.set_cover(id, covers[-1][0]) + covers = [] elif self.current_phase == 2: # All of these just affect the DB, so we can tolerate a total rollback if do_auto_author: @@ -700,6 +743,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): cover_action = 'remove' elif self.cover_generate.isChecked(): cover_action = 'generate' + elif self.cover_from_fmt.isChecked(): + cover_action = 'fromfmt' args = (remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, do_autonumber, do_remove_format, remove_format, do_swap_ta, diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index ecb34d8e5b..3f20958d47 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -414,6 +414,13 @@ Future conversion of these books will use the default settings. + + + + Set from &ebook file(s) + + + @@ -686,8 +693,8 @@ nothing should be put between the original text and the inserted text 0 0 - 122 - 38 + 726 + 334 From f595ff2dfc6b4b9177aa4c2248448336da0254e1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 12:01:49 -0700 Subject: [PATCH 08/18] ... --- src/calibre/gui2/dialogs/metadata_bulk.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index b875de14e3..e5292ee755 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -185,10 +185,10 @@ class MyBlockingBusy(QDialog): cdata, area = get_cover_data(fmt) if cdata: covers.append((cdata, area)) - covers.sort(key=lambda x: x[1]) - if covers: - self.db.set_cover(id, covers[-1][0]) - covers = [] + covers.sort(key=lambda x: x[1]) + if covers: + self.db.set_cover(id, covers[-1][0]) + covers = [] elif self.current_phase == 2: # All of these just affect the DB, so we can tolerate a total rollback if do_auto_author: From 6e3d2db96c7958ccaaae78b82b665230931c3940 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 13:20:21 -0700 Subject: [PATCH 09/18] On X11 initialize fontconfig in the GUI thread as Qt also uses fontconfig internally and fontconfig is not thread safe --- src/calibre/gui2/convert/mobi_output.py | 2 +- src/calibre/gui2/widgets.py | 2 +- src/calibre/utils/fonts/__init__.py | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/convert/mobi_output.py b/src/calibre/gui2/convert/mobi_output.py index 14aca24db5..be9c5b4658 100644 --- a/src/calibre/gui2/convert/mobi_output.py +++ b/src/calibre/gui2/convert/mobi_output.py @@ -11,7 +11,6 @@ from PyQt4.Qt import Qt from calibre.gui2.convert.mobi_output_ui import Ui_Form from calibre.gui2.convert import Widget from calibre.gui2.widgets import FontFamilyModel -from calibre.utils.fonts import fontconfig font_family_model = None @@ -28,6 +27,7 @@ class PluginWidget(Widget, Ui_Form): 'mobi_ignore_margins', 'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc'] ) + from calibre.utils.fonts import fontconfig self.db, self.book_id = db, book_id global font_family_model diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 12d64bbbcd..c5ae7fff85 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -19,7 +19,6 @@ from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.constants import isosx from calibre.gui2.filename_pattern_ui import Ui_Form from calibre import fit_image -from calibre.utils.fonts import fontconfig from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.utils.config import prefs, XMLConfig @@ -283,6 +282,7 @@ class FontFamilyModel(QAbstractListModel): def __init__(self, *args): QAbstractListModel.__init__(self, *args) + from calibre.utils.fonts import fontconfig try: self.families = fontconfig.find_font_families() except: diff --git a/src/calibre/utils/fonts/__init__.py b/src/calibre/utils/fonts/__init__.py index 5cab0c4920..3db3a1b285 100644 --- a/src/calibre/utils/fonts/__init__.py +++ b/src/calibre/utils/fonts/__init__.py @@ -7,15 +7,19 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import os, sys -from threading import Thread -from calibre.constants import plugins, iswindows +from calibre.constants import plugins, iswindows, islinux, isfreebsd _fc, _fc_err = plugins['fontconfig'] if _fc is None: raise RuntimeError('Failed to load fontconfig with error:'+_fc_err) +if islinux or isfreebsd: + Thread = object +else: + from threading import Thread + class FontConfig(Thread): def __init__(self): @@ -45,7 +49,8 @@ class FontConfig(Thread): self.failed = True def wait(self): - self.join() + if not (islinux or isfreebsd): + self.join() if self.failed: raise RuntimeError('Failed to initialize fontconfig') @@ -144,7 +149,13 @@ class FontConfig(Thread): return fonts if all else (fonts[0] if fonts else None) fontconfig = FontConfig() -fontconfig.start() +if islinux or isfreebsd: + # On X11 Qt also uses fontconfig, so initialization must happen in the + # main thread. In any case on X11 initializing fontconfig should be very + # fast + fontconfig.run() +else: + fontconfig.start() def test(): from pprint import pprint; From cddf20727770508ffe1116c399a67403b67022a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 14:48:25 -0700 Subject: [PATCH 10/18] Fix #7980 (Security vulnerability in Calibre 0.7.34) --- src/calibre/library/server/content.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 5492c86fa9..8af70d5675 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, os +import re, os, posixpath import cherrypy @@ -88,17 +88,24 @@ class ContentServer(object): def static(self, name): 'Serves static content' name = name.lower() - cherrypy.response.headers['Content-Type'] = { + fname = posixpath.basename(name) + try: + cherrypy.response.headers['Content-Type'] = { 'js' : 'text/javascript', 'css' : 'text/css', 'png' : 'image/png', 'gif' : 'image/gif', 'html' : 'text/html', - '' : 'application/octet-stream', - }[name.rpartition('.')[-1].lower()] + }[fname.rpartition('.')[-1].lower()] + except KeyError: + raise cherrypy.HTTPError(404, '%r not a valid resource type'%name) cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time) - path = P('content_server/'+name) - if not os.path.exists(path): + basedir = os.path.abspath(P('content_server')) + path = os.path.join(basedir, name.replace('/', os.sep)) + path = os.path.abspath(path) + if not path.startswith(basedir): + raise cherrypy.HTTPError(403, 'Access to %s is forbidden'%name) + if not os.path.exists(path) or not os.path.isfile(path): raise cherrypy.HTTPError(404, '%s not found'%name) if self.opts.develop: lm = fromtimestamp(os.stat(path).st_mtime) From c235e3745c7316c18441ed2186215bbb5758e27b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 18:42:49 -0700 Subject: [PATCH 11/18] Comments editor: Add colors --- resources/images/format-fill-color.png | Bin 0 -> 16588 bytes resources/images/format-text-color.png | Bin 0 -> 5106 bytes src/calibre/gui2/comments_editor.py | 34 ++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 resources/images/format-fill-color.png create mode 100644 resources/images/format-text-color.png diff --git a/resources/images/format-fill-color.png b/resources/images/format-fill-color.png new file mode 100644 index 0000000000000000000000000000000000000000..946bead5dae2abe62866d7bb9ae901cfbda262f7 GIT binary patch literal 16588 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

mSz=;69k8xmJ>5fIHoe{IAkj+Y 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 literal 0 HcmV?d00001 diff --git a/resources/images/format-text-color.png b/resources/images/format-text-color.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec27d559de328c0e62ab3f0240bfd675a0b4630 GIT binary patch literal 5106 zcmVEojiN_!D6pi%*LxhZY((^=okDiZ)44~rCvMUSP{VGmtX$p zz~9-hl@A}6*HTxL)FL0m9K<-p+~^Mx{DS9t-Dl(Ne@EGAAs+7BP9)Y_zKyZWi#+t$ zV~>3}sGW$7%s7q{RaG0N@^Ub>B+$(V7+@-g{Z^(&I>$UuQ!)I53j(EuI(YjQ5}nrx zQdn>$n^%q6uwz94A`%s;TxpWhCXV_SVJ=iv8Dw~`h>>d-7AEeEXG8^YFq zZbbmbn2HMExIf#<(?7%s|6Hb<7jhm;_nZal1b+HYl59e}2E^^RWbh>65kO_@0K$My zv1+l;pNcUi=P?~^l3$1#>QbWXPBpHHbPxN5ptB zujSCgK#z#b0?q+01TH1mO$?6!%B%z?f&T`!IF9r42OoU!&Aex}{^fSmT$17=va^l!a@BH?oFMV^*@ck=RtcZ!oWvY6$hH^P_4N<4YSngf`QqWI^Bd4h%gI|V0i0F0 zJFfuT=3c}AYNyN=*z+ta_hj$ivSrHv@GIa~D^{%dqHEk6O<(=ryT_a3G7!V0BdGE4 zqe)WDJ*1nCk8oI_Y6fO!W*`>h&Dpa#I%Q<-T}9GIr%dVWudC|8B;SCvT<9iKdCQ9AV(_SwWKmKrufpaEi>@^09Xb-Xe{p zx2C2XyG)JOIQ)eAeY5c06#cXIF)*zQM8T2=uu?NI#zc)V7-Kj-X%agYE~LAq1ra&9 zVT^Zd(xfK`Z~iG%=V8p3Xm9@`G|yxA>*WblA1Ehp!}A~_%xQXth~qPq-&|Ycxd#xV zHfrwsd+7h4dCeu$laA8(#(6Zq@D3uWI%|C| zRs`TUPShAfUp&sv`SbB2qa90cW8-rJ(P(#01FC}RA9C-#pU9iY_9g!X8fbLwthznS zXxKgG%{Rv2HYSPB*%u@!fV4r^y~1rd`<&ZopHxTUp9Ebpe7~ffMC5q%5=}*;ymFv+&o zO^Do%@(5_Yo0E1;^0CF!A7O6u4jfb3%^%vf)Al1lZdG^$#u!3`V_^Qh0r%Eztjt?f z-N=1$;Nu1V1_>omZc;smb^KzX=-m(#fj*z}?HZdt;KuOMG;m zAT61r(rxgbY2u<6f5}w^V*wq!_bA#}=sW=Ft$Txpf%AuTER8%aL{bcV5ZGM&uNjFI z0UV3R>vGkY2V8f5TE={8@^KMs*G5^pwy^8{_3{Mp!r(TN>#l3E*#-M+ZchMD4Q^%P zw-$S*c+P+%BX3cco|*du4475|2*6Ge5RJOI_V>kNyUR4`GHqH*fGnM)mk+H?N@#bCt)c#bp5MX50ZiD>k2*+v~F4g`**sK6jhP)Zux zb`te(r6(tn$mr2OF(=*bB;ZgvnXw{(sPCIpR@=Ydbq`Rei+3y>rzc>xfG`R`(xv<@ zs;bf--A6<6ywRpI9^Aq6Mja~>EB{ViGC9glzv~tqemx42G<)|R4V0;qrovVPXztFi zv3^4$nlq>{Qv~46;9$w~9fk<(0ovNy++B%;9ErzI?G({|*F6%z?2e);fCY%hIC*D;p=7$^ItS2F6CJFJ zlq~=e#rN;$s=^&A#S+j1Sg!*Ny6f+id|@IoxRgWqBu%wInBgX`jIyn|FBZ2$A-GPhBz?Hys zI-PJ`cN4(0WRitPj!b&3x!DKe1ui{>h$8ZRR<8UWt5yZg(g?!W49xHf2fj0(v!{~j z_AUn1720vzXeQC$K| z2OLBc)uV_!4Lm_x+i$2$mn(PNamUnTG8sq!_uO+&MQ1$$+|0)5Y0Sq3y_`(1UN-%} zHXrW?_i@WBHq#!@^#O$S>l^9n>Syivte|nn9d}GkrBZWHm*0mY!)a!X|<%a1|8I>5Njc9Bf9=gWiJxKNq^I+%pdg&1Ne zN`!-W>}3gk70O>=!deLo3xx&Hfs61yd_DrqIoWU&Ak*yk=-?@YhiD~qOQKYA-GVUC zLHjvA{uKh}2{J@mYfeY9da> z<<t{0G9!E zHWC3qcqcEDdzQ~QQ7uJ)q93qjOVBw_a_@Y|7JxF(Rw9E$=w`lUnX18rg84fgKUOi+k5l=SnEEr82-4du7i zQt|m;YqrXux3?GH_XBNV;9Axu4PrA`w$BiG8glgn8oqBO3FR$%DYQaX#i<4X0Qh)Y z6g#1FivDB?tNI|909xrp`ANVOe3Wl;m0h=04gm!ajxqpP%9@uH|7XE1$>BKB9}IoV z9S<;>%^YJIu()LVielJED^Hi5M~isnPn-=MF&ghn(RTqbw>%4y7R=ojantdkJ-Hjn z^BDmC7Ukz9rqGAOH)ti$bS0ZqOA!F@PCk7Ceu2xMu()K=RFr_NfY1~HLo8v{v0WVaydpo=;+v^2`gmVhMBCQv1BGJ( zTj>Nk>ELY$t%KtQL*2Va@p%D52TRLap`wx;KYkq7ozRZRif}D!lR*0uZ2WNy^HGJR zpd;3{&Ahsf4hQp#OKI!K6%GcaL9?0B`5iq8#18Zh^gpt{qbV z>y%gwu1~`U6=K4ON1_RsDw(%meY{Z%?(9D#)D}j#z=jA!)P2geR zVS${15qSmhEoJrq{Fy>g%aJS0_u=k)mG+w@JYB&7#{xoG1Sre_Zij+Fyl(^AfE5bg z5O^v3gq&T$o0W5uaF-N$^M_?D0^D-1PW*g;A*vxDWH<_ofV(Mt^M^AV_=;x2{zouM z0N+;XeE1Rsist>yW}Wz1K~PK5U@3&~{!$bFZgaES32Mf%9Kt z9D>~ZutXDSrZykwgaw+xA;>D12YM6DO=fJou3Ym-E-rhiC0U?@JR*AC6g)9Lm>>*FL zVmSzCw21!bN>U{r;6w#%9){5Z*eS)YUR@A{y*J3j&J746fDi?RR$`!SH)IK*4jxIu zr))I^4Op1S5R9nlA90J7Z4)wfM!CzR#JS&T@v_K z_RWU^Az^ETU8;Pc9a;^%U*QrRSrhFS*a+M+di(srq>V(P*GPt|gs@?_E1aO8^6TlfxO#o*&RV{$p^zgz|wE%!vl>~%TjH(uZ=Xv!3 zO@t;Yv{F>H0D!uHbpTr-SfS;LQTV880eGG_C#Ve(Sp@vT zLUuw)RSUrP{R@KHP}NIqCdq zDNFW3L)8gj(xgdqec%5P_SZYwr>gJo?d^TRvfT-#Y6MVMS2r&biQJ&7pJUiMc$Vo0 zzUlk^J&8o(w0nI{E>2qkOrAV>a({pSVi8#kd=yyD`28LQwyEk5fyblK=rcV%J%MLb zPXwVCKz)7vGF83M_x(8{axO57%+ly(zC5vF>?X75!fsXFZ;aWas{0291|9}N8 Date: Mon, 20 Dec 2010 19:51:20 -0700 Subject: [PATCH 12/18] Comments editor completed --- resources/images/format-text-heading.png | Bin 0 -> 965 bytes resources/images/insert-link.png | Bin 0 -> 3696 bytes src/calibre/gui2/comments_editor.py | 87 ++++++++++++++++++++++- 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 resources/images/format-text-heading.png create mode 100644 resources/images/insert-link.png diff --git a/resources/images/format-text-heading.png b/resources/images/format-text-heading.png new file mode 100644 index 0000000000000000000000000000000000000000..970acb7d604b8ce0ea7c852c8a5c7d31f4c27ca3 GIT binary patch literal 965 zcmV;$13LVPP)*G|i&j z6}sZ_D{PREy6p!*c5Q)D350}DNy;x&ZGi=_V8b_5`~ek}K&Xh84QlhS<0LlsUV9wd zsT+ditQcu(*JozVnfscF004bwXXk?u;+E^W;CUWo*^mdMl*IFKd3pIZeY?E7yX#H6 z5hro$_{X4~Q~UbuJ1Wf706+#_yt)7Zr$GXQGP{(5O(Zf>K|G2pcnUr4AIT!J^uO!I zV=OK}X1Dtx2c!#-B6|S|{)7b*0tBW@RE{_Foj*2-1;HE)@NsEts9HvdHM>(k-9`F8>O3~{#uR*KT0veLU zZXnj_SaAIF7+$@81E;5FD69;YW%=19hihNpf_TuGrUChU9tzm~{PnnwQ!5rtC=?0- z<}yyZ+u#4h!B{L7Rn1QXxVSh7qwXL=!$cz5CHgg|=Wxd^FG4Y5A_)off_X%?v~-94 z{i43b?(hQ2p?k*!Vf1vN@DUOfaK{#p9nKf*dlE#}P}q4jF4YC}uAvW` z%_i@P7UZ1nb^`%L475{>6O%C-tL7)?h1hrth(t;PG%@O_h$sQkLK4ON^@S>e=VD?K z0V>+9cAL&?OtDT_AyrwO1dC?G*g_=HUld$NM?d_UKotzXAACLtOYFa3ejJDk1ZY9Q zz^BwCJbv<&gQ$(Wu`L_wTQB*$!~mQa(-G7BwUcdbdwZMjX&u}4I+n$~>p17${*Zi!~0vx{cbW$#4_ct z1Y~tD{4XHA6iW%yZZfQ3Q*Me=S)jGIw|9wZ_Y}!nDpf1k7qQQdTvsyuiHQ9-{TwAi nxPz4vY1)3a%$ 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 O0000 Date: Mon, 20 Dec 2010 19:52:01 -0700 Subject: [PATCH 13/18] ... --- src/calibre/gui2/comments_editor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index a21390bcd0..fb09de984e 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -452,7 +452,7 @@ class Highlighter(QSyntaxHighlighter): # }}} -class Editor(QWidget): +class Editor(QWidget): # {{{ def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -544,6 +544,8 @@ class Editor(QWidget): def code_dirtied(self, *args): self.source_dirty = True +# }}} + if __name__ == '__main__': app = QApplication([]) w = Editor() From 1b36225c7c96f72a43d7948f3079faa09c89ed2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 20:13:22 -0700 Subject: [PATCH 14/18] ... --- src/calibre/gui2/comments_editor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index fb09de984e..5c4e3f4cb0 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -524,7 +524,10 @@ class Editor(QWidget): # {{{ def html(self): def fset(self, v): self.editor.html = v - return property(fget=lambda self:self.editor.html, fset=fset) + def fget(self): + self.tabs.setCurrentIndex(0) + return self.editor.html + return property(fget=fget, fset=fset) def change_tab(self, index): #print 'reloading:', (index and self.wyswyg_dirty) or (not index and From 3bd7e3457a95dd5693ffcd10c882ad9fc17ac69d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 20:28:59 -0700 Subject: [PATCH 15/18] ... --- src/calibre/gui2/comments_editor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 5c4e3f4cb0..e7647a8da3 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -202,6 +202,9 @@ class EditorWidget(QWebView): # {{{ def fget(self): ans = u'' + check = unicode(self.page().mainFrame().toPlainText()).strip() + if not check: + return ans try: raw = unicode(self.page().mainFrame().toHtml()) raw = xml_to_unicode(raw, strip_encoding_pats=True, @@ -534,11 +537,11 @@ class Editor(QWidget): # {{{ # self.source_dirty) if index == 1: # changing to code view if self.wyswyg_dirty: - self.code_edit.setPlainText(self.html) + self.code_edit.setPlainText(self.editor.html) self.wyswyg_dirty = False elif index == 0: #changing to wyswyg if self.source_dirty: - self.html = unicode(self.code_edit.toPlainText()) + self.editor.html = unicode(self.code_edit.toPlainText()) self.source_dirty = False def wyswyg_dirtied(self, *args): From 0968eef89469f15c19ab50f22b8659552cb78714 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 20:34:30 -0700 Subject: [PATCH 16/18] Redesign edit metadata single dialog, using the new WYSWYG comments editor widget --- src/calibre/gui2/dialogs/metadata_single.py | 23 +- src/calibre/gui2/dialogs/metadata_single.ui | 278 ++++++++++---------- 2 files changed, 155 insertions(+), 146 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 7c5a9f95d4..9cb9f7bbbc 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -34,6 +34,7 @@ from calibre.customize.ui import run_plugins_on_import, get_isbndb_key from calibre.gui2.preferences.social import SocialMetadata from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre import strftime +from calibre.library.comments import comments_to_html class CoverFetcher(Thread): # {{{ @@ -195,7 +196,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): _file + _(" is not a valid picture")) d.exec_() else: - self.cover_path.setText(_file) self.cover.setPixmap(pix) self.update_cover_tooltip() self.cover_changed = True @@ -409,7 +409,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): if mi.series_index is not None: self.series_index.setValue(float(mi.series_index)) if mi.comments and mi.comments.strip(): - self.comments.setPlainText(mi.comments) + comments = comments_to_html(mi.comments) + self.comments.html = comments def sync_formats(self): @@ -556,7 +557,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): if rating > 0: self.rating.setValue(int(rating/2.)) comments = self.db.comments(row) - self.comments.setPlainText(comments if comments else '') + if comments and comments.strip(): + comments = comments_to_html(comments) + self.comments.html = comments cover = self.db.cover(row) pubdate = db.pubdate(self.id, index_is_id=True) self.pubdate.setDate(QDate(pubdate.year, pubdate.month, @@ -806,10 +809,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.pubdate.setDate(QDate(dt.year, dt.month, dt.day)) summ = book.comments if summ: - prefix = unicode(self.comments.toPlainText()) + prefix = self.comment.html if prefix: prefix += '\n' - self.comments.setPlainText(prefix + summ) + self.comments.html = prefix + comments_to_html(summ) if book.rating is not None: self.rating.setValue(int(book.rating)) if book.tags: @@ -899,7 +902,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.db.set_series_index(self.id, self.series_index.value(), notify=False, commit=False) self.db.set_comment(self.id, - unicode(self.comments.toPlainText()).strip(), + self.comments.html, notify=False, commit=False) d = self.pubdate.date() d = qt_to_dt(d) @@ -936,16 +939,16 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): QDialog.reject(self, *args) def read_state(self): - wg = dynamic.get('metasingle_window_geometry', None) - ss = dynamic.get('metasingle_splitter_state', None) + wg = dynamic.get('metasingle_window_geometry2', None) + ss = dynamic.get('metasingle_splitter_state2', None) if wg is not None: self.restoreGeometry(wg) if ss is not None: self.splitter.restoreState(ss) def save_state(self): - dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry())) - dynamic.set('metasingle_splitter_state', + dynamic.set('metasingle_window_geometry2', bytes(self.saveGeometry())) + dynamic.set('metasingle_splitter_state2', bytes(self.splitter.saveState())) def break_cycles(self): diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 0355dc0fe6..dfa8c45797 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -6,8 +6,8 @@ 0 0 - 887 - 750 + 994 + 726 @@ -43,8 +43,8 @@ 0 0 - 879 - 711 + 986 + 687 @@ -66,8 +66,8 @@ &Basic metadata - - + + Qt::Horizontal @@ -495,29 +495,132 @@ Using this button to create author sort will change author sort from red to gree + + + + - - - &Comments + + + + 0 + 10 + - - - - - true - - - false + + Book Cover + + + + + + + 0 + 100 + + + + + 6 + + + QLayout::SetMaximumSize + + + 0 + + + + + Change &cover image: + + + cover_button + + + + + + + 6 + + + 0 + + + + + &Browse + + + + :/images/document_open.png:/images/document_open.png + + + + + + + Remove border (if any) from cover + + + T&rim + + + + :/images/trim.png:/images/trim.png + + + + + + + Reset cover to default + + + &Remove + + + + :/images/trash.png:/images/trash.png + + + + + + + + + + + + + Download co&ver + + + + + + + Generate a default cover based on the title and author + + + &Generate cover + + + + + - - + + @@ -546,6 +649,12 @@ Using this button to create author sort will change author sort from red to gree 140 + + + 100 + 0 + + QAbstractItemView::DropOnly @@ -644,129 +753,22 @@ Using this button to create author sort will change author sort from red to gree - + - + 0 10 - Book Cover + &Comments - + + + 0 + - - - - 0 - 100 - - - - - - - - 6 - - - QLayout::SetMaximumSize - - - 0 - - - - - Change &cover image: - - - cover_path - - - - - - - 6 - - - 0 - - - - - true - - - - - - - &Browse - - - - :/images/document_open.png:/images/document_open.png - - - - - - - Remove border (if any) from cover - - - T&rim - - - - :/images/trim.png:/images/trim.png - - - Qt::ToolButtonTextBesideIcon - - - - - - - Reset cover to default - - - ... - - - - :/images/trash.png:/images/trash.png - - - - - - - - - - - - - Download co&ver - - - - - - - Generate a default cover based on the title and author - - - &Generate cover - - - - + @@ -828,6 +830,12 @@ Using this button to create author sort will change author sort from red to gree

calibre/gui2/widgets.h
1 + + Editor + QWidget +
calibre/gui2/comments_editor.h
+ 1 +
title @@ -848,13 +856,11 @@ Using this button to create author sort will change author sort from red to gree date pubdate fetch_metadata_button - comments button_set_cover button_set_metadata formats add_format_button remove_format_button - cover_path cover_button trim_cover_button reset_cover From f5eaa0034d384325b2ee39978ff78e33f2b5d41d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 21:24:34 -0700 Subject: [PATCH 17/18] ... --- src/calibre/gui2/comments_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index e7647a8da3..d19c97e87b 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -127,6 +127,7 @@ class EditorWidget(QWebView): # {{{ (_('Heading') +' 5', 'h5'), (_('Heading') +' 6', 'h6'), (_('Pre-formatted'), 'pre'), + (_('Blockquote'), 'blockquote'), (_('Address'), 'address'), ]: ac = BlockStyleAction(text, name, self) From 4489730bd16942baa7fe59c5ccc35f68b5a9dd08 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Dec 2010 21:54:37 -0700 Subject: [PATCH 18/18] CND and wenxuecity - znjy by Derek Liang --- resources/recipes/cnd.recipe | 67 ++++++++++++++++++++++++ resources/recipes/wenxuecity-znjy.recipe | 62 ++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 resources/recipes/cnd.recipe create mode 100644 resources/recipes/wenxuecity-znjy.recipe diff --git a/resources/recipes/cnd.recipe b/resources/recipes/cnd.recipe new file mode 100644 index 0000000000..0e8206d07a --- /dev/null +++ b/resources/recipes/cnd.recipe @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2010, Derek Liang ' +''' +cnd.org +''' +import re + +from calibre.web.feeds.news import BasicNewsRecipe + +class TheCND(BasicNewsRecipe): + + title = 'CND' + __author__ = 'Derek Liang' + description = '' + INDEX = 'http://cnd.org' + language = 'zh' + conversion_options = {'linearize_tables':True} + + remove_tags_before = dict(name='div', id='articleHead') + remove_tags_after = dict(id='copyright') + remove_tags = [dict(name='table', attrs={'align':'right'}), dict(name='img', attrs={'src':'http://my.cnd.org/images/logo.gif'}), dict(name='hr', attrs={}), dict(name='small', attrs={})] + no_stylesheets = True + + preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')] + + def print_version(self, url): + if url.find('news/article.php') >= 0: + return re.sub("^[^=]*", "http://my.cnd.org/modules/news/print.php?storyid", url) + else: + return re.sub("^[^=]*", "http://my.cnd.org/modules/wfsection/print.php?articleid", url) + + def parse_index(self): + soup = self.index_to_soup(self.INDEX) + + feeds = [] + articles = {} + + for a in soup.findAll('a', attrs={'target':'_cnd'}): + url = a['href'] + if url.find('article.php') < 0 : + continue + if url.startswith('/'): + url = 'http://cnd.org'+url + title = self.tag_to_string(a) + self.log('\tFound article: ', title, 'at', url) + date = a.nextSibling + if (date is not None) and len(date)>2: + if not articles.has_key(date): + articles[date] = [] + articles[date].append({'title':title, 'url':url, 'description': '', 'date':''}) + self.log('\t\tAppend to : ', date) + + self.log('log articles', articles) + mostCurrent = sorted(articles).pop() + self.title = 'CND ' + mostCurrent + + feeds.append((self.title, articles[mostCurrent])) + + return feeds + + def populate_article_metadata(self, article, soup, first): + header = soup.find('h3') + self.log('header: ' + self.tag_to_string(header)) + pass + diff --git a/resources/recipes/wenxuecity-znjy.recipe b/resources/recipes/wenxuecity-znjy.recipe new file mode 100644 index 0000000000..ecce80222e --- /dev/null +++ b/resources/recipes/wenxuecity-znjy.recipe @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2010, Derek Liang ' +''' +wenxuecity.com +''' +import re + +from calibre.web.feeds.news import BasicNewsRecipe + +class TheCND(BasicNewsRecipe): + + title = 'wenxuecity - znjy' + __author__ = 'Derek Liang' + description = '' + INDEX = 'http://bbs.wenxuecity.com/znjy/?elite=1' + language = 'zh' + conversion_options = {'linearize_tables':True} + + remove_tags_before = dict(name='div', id='message') + remove_tags_after = dict(name='div', id='message') + remove_tags = [dict(name='div', id='postmeta'), dict(name='div', id='footer')] + no_stylesheets = True + + preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')] + + def print_version(self, url): + return url + '?print' + + def parse_index(self): + soup = self.index_to_soup(self.INDEX) + + feeds = [] + articles = {} + + for a in soup.findAll('a', attrs={'class':'post'}): + url = a['href'] + if url.startswith('/'): + url = 'http://bbs.wenxuecity.com'+url + title = self.tag_to_string(a) + self.log('\tFound article: ', title, ' at:', url) + dateReg = re.search( '(\d\d?)/(\d\d?)/(\d\d)', self.tag_to_string(a.parent) ) + date = '%(y)s/%(m)02d/%(d)02d' % {'y' : dateReg.group(3), 'm' : int(dateReg.group(1)), 'd' : int(dateReg.group(2)) } + if not articles.has_key(date): + articles[date] = [] + articles[date].append({'title':title, 'url':url, 'description': '', 'date':''}) + self.log('\t\tAppend to : ', date) + + self.log('log articles', articles) + mostCurrent = sorted(articles).pop() + self.title = '文学城 - 子女教育 - ' + mostCurrent + + feeds.append((self.title, articles[mostCurrent])) + + return feeds + + def populate_article_metadata(self, article, soup, first): + header = soup.find('h3') + self.log('header: ' + self.tag_to_string(header)) + pass +