From b8edfe539437fdc9b29b0fc281917daa63c3c13e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 13:44:06 -0600 Subject: [PATCH 01/16] ... --- src/calibre/library/database2.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 93320166b7..1a2eef2c81 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1130,7 +1130,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def set_authors(self, id, authors, notify=True, commit=True): ''' - `authors`: A list of authors. + Note that even if commit is False, the db will still be committed to + because this causes the location of files to change + + :param authors: A list of authors. ''' if not authors: authors = [_('Unknown')] @@ -1167,6 +1170,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.notify('metadata', [id]) def set_title(self, id, title, notify=True, commit=True): + ''' + Note that even if commit is False, the db will still be committed to + because this causes the location of files to change + ''' if not title: return if not isinstance(title, unicode): From 06173bccebb0d8b3df497d0d0df030c1710aa80b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 13:45:34 -0600 Subject: [PATCH 02/16] Second beta --- src/calibre/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 334406e01b..91c114359c 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = 'calibre' -__version__ = '0.7.900' +__version__ = '0.7.901' __author__ = "Kovid Goyal " import re From 8c74a347d773281b92bafba41662b66af366f789 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 14:56:52 -0600 Subject: [PATCH 03/16] Fix #6899 (Updated recipe for Danas) --- resources/recipes/danas.recipe | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/recipes/danas.recipe b/resources/recipes/danas.recipe index 3543acd684..1e0e319334 100644 --- a/resources/recipes/danas.recipe +++ b/resources/recipes/danas.recipe @@ -49,7 +49,11 @@ class Danas(BasicNewsRecipe): , 'language' : language } - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] + preprocess_regexps = [ + (re.compile(u'\u0110'), lambda match: u'\u00D0') + ,(re.compile(u'\u201c'), lambda match: '"') + ,(re.compile(u'\u201e'), lambda match: '"') + ] keep_only_tags = [dict(name='div', attrs={'id':'left'})] remove_tags = [ From 63f02aa91beaa330a45d8c572cb6e092832b6cb0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 15:19:14 -0600 Subject: [PATCH 04/16] Fix regression in get_metadata for books with no formats --- src/calibre/library/database2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 01d46083b2..3e7b932808 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -590,7 +590,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): mi.pubdate = self.pubdate(idx, index_is_id=index_is_id) mi.uuid = self.uuid(idx, index_is_id=index_is_id) mi.title_sort = self.title_sort(idx, index_is_id=index_is_id) - mi.formats = self.formats(idx, index_is_id=index_is_id).split(',') + mi.formats = self.formats(idx, index_is_id=index_is_id) + if hasattr(mi.formats, 'split'): + mi.formats = mi.formats.split(',') + else: + mi.formats = None tags = self.tags(idx, index_is_id=index_is_id) if tags: mi.tags = [i.strip() for i in tags.split(',')] From d225fd9ff5c08083d79238f489cd72abbef8b61d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 15:22:00 -0600 Subject: [PATCH 05/16] Allow --reinitialize-db to use an SQL dump from elsewhere --- src/calibre/debug.py | 47 ++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/calibre/debug.py b/src/calibre/debug.py index 8a2097ddd1..8cc125b118 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -36,13 +36,17 @@ Run an embedded python interpreter. 'plugin code.') parser.add_option('--reinitialize-db', default=None, help='Re-initialize the sqlite calibre database at the ' - 'specified path. Useful to recover from db corruption.') + 'specified path. Useful to recover from db corruption.' + ' You can also specify the path to an SQL dump which ' + 'will be used instead of trying to dump the database.' + ' This can be useful when dumping fails, but dumping ' + 'with sqlite3 works.') parser.add_option('-p', '--py-console', help='Run python console', default=False, action='store_true') return parser -def reinit_db(dbpath, callback=None): +def reinit_db(dbpath, callback=None, sql_dump=None): if not os.path.exists(dbpath): raise ValueError(dbpath + ' does not exist') from calibre.library.sqlite import connect @@ -52,26 +56,32 @@ def reinit_db(dbpath, callback=None): uv = conn.get('PRAGMA user_version;', all=False) conn.execute('PRAGMA writable_schema=ON') conn.commit() - sql_lines = conn.dump() + if sql_dump is None: + sql_lines = conn.dump() + else: + sql_lines = open(sql_dump, 'rb').read() conn.close() dest = dbpath + '.tmp' try: with closing(connect(dest, False)) as nconn: nconn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)') nconn.commit() - if callable(callback): - callback(len(sql_lines), True) - for i, line in enumerate(sql_lines): - try: - nconn.execute(line) - except: - import traceback - prints('SQL line %r failed with error:'%line) - prints(traceback.format_exc()) - continue - finally: - if callable(callback): - callback(i, False) + if sql_dump is None: + if callable(callback): + callback(len(sql_lines), True) + for i, line in enumerate(sql_lines): + try: + nconn.execute(line) + except: + import traceback + prints('SQL line %r failed with error:'%line) + prints(traceback.format_exc()) + continue + finally: + if callable(callback): + callback(i, False) + else: + nconn.executescript(sql_lines) nconn.execute('pragma user_version=%d'%int(uv)) nconn.commit() os.remove(dbpath) @@ -170,7 +180,10 @@ def main(args=sys.argv): prints('CALIBRE_EXTENSIONS_PATH='+sys.extensions_location) prints('CALIBRE_PYTHON_PATH='+os.pathsep.join(sys.path)) elif opts.reinitialize_db is not None: - reinit_db(opts.reinitialize_db) + sql_dump = None + if len(args) > 1 and os.access(args[-1], os.R_OK): + sql_dump = args[-1] + reinit_db(opts.reinitialize_db, sql_dump=sql_dump) else: from calibre import ipython ipython() From 0e8017ade69c0ad0f3d374db381f6170b175e21f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 16:45:42 -0600 Subject: [PATCH 06/16] News download: Rationalize cover processing. Fixes #6852 (ebook-convert ieeespectrum.recipe .mobi crashes) --- src/calibre/utils/magick/draw.py | 8 ++--- src/calibre/web/feeds/news.py | 50 +++++++++++++++----------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index ed9e3d3d83..dcf9d7b671 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -60,15 +60,15 @@ def identify(path): data = open(path, 'rb').read() return identify_data(data) -def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0, - border_color='#ffffff'): +def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0, + border_color='#ffffff', fmt='jpg'): img = Image() - img.open(path_to_image) + img.load(img_data) lwidth, lheight = img.size canvas = create_canvas(lwidth+left+right, lheight+top+bottom, border_color) canvas.compose(img, left, top) - canvas.save(path_to_image) + return canvas.export(fmt) def create_text_wand(font_size, font_path=None): if font_path is None: diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index a140dfbf05..d1e7866198 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -7,7 +7,7 @@ Defines various abstract base classes that can be subclassed to create powerful __docformat__ = "restructuredtext en" -import os, time, traceback, re, urlparse, sys +import os, time, traceback, re, urlparse, sys, cStringIO from collections import defaultdict from functools import partial from contextlib import nested, closing @@ -27,6 +27,7 @@ from calibre.web.fetch.simple import RecursiveFetcher from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import now as nowf +from calibre.utils.magick.draw import save_cover_data_to, add_borders_to_image class LoginFailed(ValueError): pass @@ -948,38 +949,36 @@ class BasicNewsRecipe(Recipe): try: cu = self.get_cover_url() except Exception, err: - cu = None self.log.error(_('Could not download cover: %s')%str(err)) self.log.debug(traceback.format_exc()) - if cu is not None: - ext = cu.split('/')[-1].rpartition('.')[-1] - if '?' in ext: - ext = '' - ext = ext.lower() if ext and '/' not in ext else 'jpg' - cpath = os.path.join(self.output_dir, 'cover.'+ext) + else: + cdata = None if os.access(cu, os.R_OK): - with open(cpath, 'wb') as cfile: - cfile.write(open(cu, 'rb').read()) + cdata = open(cu, 'rb').read() else: self.report_progress(1, _('Downloading cover from %s')%cu) - with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): - cfile.write(r.read()) - if self.cover_margins[0] or self.cover_margins[1]: - from calibre.utils.magick.draw import add_borders_to_image - add_borders_to_image(cpath, - left=self.cover_margins[0],right=self.cover_margins[0], - top=self.cover_margins[1],bottom=self.cover_margins[1], - border_color=self.cover_margins[2]) - if ext.lower() == 'pdf': + with closing(self.browser.open(cu)) as r: + cdata = r.read() + if not cdata: + return + ext = cu.split('/')[-1].rpartition('.')[-1].lower().strip() + if ext == 'pdf': from calibre.ebooks.metadata.pdf import get_metadata - stream = open(cpath, 'rb') + stream = cStringIO.StringIO(cdata) + cdata = None mi = get_metadata(stream) - cpath = None if mi.cover_data and mi.cover_data[1]: - cpath = os.path.join(self.output_dir, - 'cover.'+mi.cover_data[0]) - with open(cpath, 'wb') as f: - f.write(mi.cover_data[1]) + cdata = mi.cover_data[1] + if not cdata: + return + if self.cover_margins[0] or self.cover_margins[1]: + cdata = add_borders_to_image(cdata, + left=self.cover_margins[0],right=self.cover_margins[0], + top=self.cover_margins[1],bottom=self.cover_margins[1], + border_color=self.cover_margins[2]) + + cpath = os.path.join(self.output_dir, 'cover.jpg') + save_cover_data_to(cdata, cpath) self.cover_path = cpath def download_cover(self): @@ -1422,7 +1421,6 @@ class CalibrePeriodical(BasicNewsRecipe): return br def download(self): - import cStringIO self.log('Fetching downloaded recipe') try: raw = self.browser.open_novisit( From b958545a8af34885c7053e14f752fed2ba548627 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 16:47:28 -0600 Subject: [PATCH 07/16] ... --- src/calibre/ebooks/mobi/writer.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 5d5de7b153..23f92d1fd2 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -1574,14 +1574,15 @@ class MobiWriter(object): id = unicode(oeb.metadata.cover[0]) item = oeb.manifest.ids[id] href = item.href - index = self._images[href] - 1 - exth.write(pack('>III', 0xc9, 0x0c, index)) - exth.write(pack('>III', 0xcb, 0x0c, 0)) - nrecs += 2 - index = self._add_thumbnail(item) - if index is not None: - exth.write(pack('>III', 0xca, 0x0c, index - 1)) - nrecs += 1 + if href in self._images: + index = self._images[href] - 1 + exth.write(pack('>III', 0xc9, 0x0c, index)) + exth.write(pack('>III', 0xcb, 0x0c, 0)) + nrecs += 2 + index = self._add_thumbnail(item) + if index is not None: + exth.write(pack('>III', 0xca, 0x0c, index - 1)) + nrecs += 1 exth = exth.getvalue() trail = len(exth) % 4 From 62c652869a30d136278356557db397b7417c06bc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 17:13:22 -0600 Subject: [PATCH 08/16] Fix #5900 (Alex reader from spring design is reconized as N516 and send books to wrong location) --- src/calibre/devices/hanvon/driver.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/calibre/devices/hanvon/driver.py b/src/calibre/devices/hanvon/driver.py index 75728a94ea..6291864b86 100644 --- a/src/calibre/devices/hanvon/driver.py +++ b/src/calibre/devices/hanvon/driver.py @@ -11,6 +11,10 @@ import re from calibre.devices.usbms.driver import USBMS +def is_alex(device_info): + return device_info[3] == u'Linux 2.6.28 with pxa3xx_u2d' and \ + device_info[4] == u'Seleucia Disk' + class N516(USBMS): name = 'N516 driver' @@ -34,6 +38,9 @@ class N516(USBMS): EBOOK_DIR_MAIN = 'e_book' SUPPORTS_SUB_DIRS = True + def can_handle(self, device_info, debug=False): + return not is_alex(device_info) + class THEBOOK(N516): name = 'The Book driver' gui_name = 'The Book' @@ -61,6 +68,9 @@ class ALEX(N516): EBOOK_DIR_MAIN = 'eBooks' SUPPORTS_SUB_DIRS = True + def can_handle(self, device_info, debug=False): + return is_alex(device_info) + class AZBOOKA(ALEX): name = 'Azbooka driver' @@ -74,6 +84,9 @@ class AZBOOKA(ALEX): EBOOK_DIR_MAIN = '' + def can_handle(self, device_info, debug=False): + return not is_alex(device_info) + class EB511(USBMS): name = 'Elonex EB 511 driver' From 9656798e3881757f06bacb8bacee860a42f3be00 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 19:00:50 -0600 Subject: [PATCH 09/16] Fix #6522 (Cannot recognize my Nexus One with 2.2 Cyanogen Root) --- src/calibre/devices/android/driver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 7a451112c0..c9c0827759 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -29,7 +29,9 @@ class ANDROID(USBMS): # Sony Ericsson 0xfce : { 0xd12e : [0x0100]}, - 0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]}, + # Google + 0x18d1 : { 0x4e11 : [0x0100, 0x226, 0x227], 0x4e12: [0x0100, 0x226, + 0x227]}, # Samsung 0x04e8 : { 0x681d : [0x0222, 0x0400], From 78490d39bd5f98eb7f6f19f62a9ff451862b08aa Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 21 Sep 2010 22:17:50 -0300 Subject: [PATCH 10/16] Add support for setting the ReadStatus to Read and correctly deal with empty collections --- src/calibre/devices/kobo/driver.py | 120 +++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 31 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 1171b74f5c..104553b675 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -98,6 +98,8 @@ class KOBO(USBMS): if readstatus == 1: playlist_map[lpath]= "Im_Reading" + elif readstatus == 2: + playlist_map[lpath]= "Read" path = self.normalize_path(path) # print "Normalized FileName: " + path @@ -441,43 +443,99 @@ class KOBO(USBMS): connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite') cursor = connection.cursor() - # Reset Im_Reading list in the database - if oncard == 'carda': - query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID like \'file:///mnt/sd/%\'' - elif oncard != 'carda' and oncard != 'cardb': - query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID not like \'file:///mnt/sd/%\'' + + if collections: + # Process any collections that exist + for category, books in collections.items(): + if category == 'Im_Reading': + # Reset Im_Reading list in the database + if oncard == 'carda': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID like \'file:///mnt/sd/%\'' + elif oncard != 'carda' and oncard != 'cardb': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID not like \'file:///mnt/sd/%\'' - try: - cursor.execute (query) - except: - debug_print('Database Exception: Unable to reset Im_Reading list') - raise - else: -# debug_print('Commit: Reset Im_Reading list') - connection.commit() - - for category, books in collections.items(): - if category == 'Im_Reading': - for book in books: -# debug_print('Title:', book.title, 'lpath:', book.path) - book.device_collections = ['Im_Reading'] - - extension = os.path.splitext(book.path)[1] - ContentType = self.get_content_type_from_extension(extension) - - ContentID = self.contentid_from_path(book.path, ContentType) - datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) - - t = (datelastread,ContentID,) - try: - cursor.execute('update content set ReadStatus=1,FirstTimeReading=\'false\',DateLastRead=? where BookID is Null and ContentID = ?', t) + cursor.execute (query) except: - debug_print('Database Exception: Unable create Im_Reading list') + debug_print('Database Exception: Unable to reset Im_Reading list') raise else: +# debug_print('Commit: Reset Im_Reading list') connection.commit() - # debug_print('Database: Commit create Im_Reading list') + + for book in books: +# debug_print('Title:', book.title, 'lpath:', book.path) + book.device_collections = ['Im_Reading'] + + extension = os.path.splitext(book.path)[1] + ContentType = self.get_content_type_from_extension(extension) + + ContentID = self.contentid_from_path(book.path, ContentType) + datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) + + t = (datelastread,ContentID,) + + try: + cursor.execute('update content set ReadStatus=1,FirstTimeReading=\'false\',DateLastRead=? where BookID is Null and ContentID = ?', t) + except: + debug_print('Database Exception: Unable create Im_Reading list') + raise + else: + connection.commit() + # debug_print('Database: Commit create Im_Reading list') + if category == 'Read': + # Reset Im_Reading list in the database + if oncard == 'carda': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID like \'file:///mnt/sd/%\'' + elif oncard != 'carda' and oncard != 'cardb': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID not like \'file:///mnt/sd/%\'' + + try: + cursor.execute (query) + except: + debug_print('Database Exception: Unable to reset Im_Reading list') + raise + else: +# debug_print('Commit: Reset Im_Reading list') + connection.commit() + + for book in books: +# debug_print('Title:', book.title, 'lpath:', book.path) + book.device_collections = ['Read'] + + extension = os.path.splitext(book.path)[1] + ContentType = self.get_content_type_from_extension(extension) + + ContentID = self.contentid_from_path(book.path, ContentType) +# datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) + + t = (ContentID,) + + try: + cursor.execute('update content set ReadStatus=2,FirstTimeReading=\'true\' where BookID is Null and ContentID = ?', t) + except: + debug_print('Database Exception: Unable set book as Rinished') + raise + else: + connection.commit() +# debug_print('Database: Commit set ReadStatus as Finished') + else: # No collections + # Since no collections exist the ReadStatus needs to be reset to 0 (Unread) + print "Reseting ReadStatus to 0" + # Reset Im_Reading list in the database + if oncard == 'carda': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\'' + elif oncard != 'carda' and oncard != 'cardb': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\'' + + try: + cursor.execute (query) + except: + debug_print('Database Exception: Unable to reset Im_Reading list') + raise + else: +# debug_print('Commit: Reset Im_Reading list') + connection.commit() cursor.close() connection.close() From 7f472f742ece9ada0736690c440dc2e9da30cc74 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 21:05:23 -0600 Subject: [PATCH 11/16] Styling cleanups --- src/calibre/utils/pyconsole/formatter.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py index 9409007ec6..6e7d982a82 100644 --- a/src/calibre/utils/pyconsole/formatter.py +++ b/src/calibre/utils/pyconsole/formatter.py @@ -8,18 +8,20 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import QTextCharFormat, QFont, QBrush, QColor from pygments.formatter import Formatter as PF -from pygments.token import Token, Generic +from pygments.token import Token, Generic, string_to_tokentype class Formatter(object): - def __init__(self, prompt, continuation, **options): + def __init__(self, prompt, continuation, style='default'): if len(prompt) != len(continuation): raise ValueError('%r does not have the same length as %r' % (prompt, continuation)) self.prompt, self.continuation = prompt, continuation + self.set_style(style) - pf = PF(**options) + def set_style(self, style): + pf = PF(style=style) self.styles = {} self.normal = self.base_fmt() self.background_color = pf.style.background_color @@ -27,6 +29,7 @@ class Formatter(object): for ttype, ndef in pf.style: fmt = self.base_fmt() + fmt.setProperty(fmt.UserProperty, str(ttype)) if ndef['color']: fmt.setForeground(QBrush(QColor('#%s'%ndef['color']))) fmt.setUnderlineColor(QColor('#%s'%ndef['color'])) @@ -49,6 +52,14 @@ class Formatter(object): QTextEdit { color: %s; background-color: %s } '''%(self.color, self.background_color) + def get_fmt(self, token): + if type(token) != type(Token.Generic): + token = string_to_tokentype(token) + fmt = self.styles.get(token, None) + if fmt is None: + fmt = self.base_fmt() + fmt.setProperty(fmt.UserProperty, str(token)) + return fmt def base_fmt(self): fmt = QTextCharFormat() @@ -59,7 +70,7 @@ class Formatter(object): cursor.insertText(raw, self.normal) def render_syntax_error(self, tb, cursor): - fmt = self.styles[Token.Error] + fmt = self.get_fmt(Token.Error) cursor.insertText(tb, fmt) def render(self, tokens, cursor): @@ -84,7 +95,9 @@ class Formatter(object): def render_prompt(self, is_continuation, cursor): pr = self.continuation if is_continuation else self.prompt - fmt = self.styles[Generic.Prompt] + fmt = self.get_fmt(Generic.Prompt) + if fmt is None: + fmt = self.base_fmt() cursor.insertText(pr, fmt) From f12f69ef5edc3c9395abc1862f62f2405245fe2d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 21:18:32 -0600 Subject: [PATCH 12/16] superesportes by Luciano Furtado. Fixes #405 (New news feed) --- resources/recipes/superesportes.recipe | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 resources/recipes/superesportes.recipe diff --git a/resources/recipes/superesportes.recipe b/resources/recipes/superesportes.recipe new file mode 100644 index 0000000000..49289f188d --- /dev/null +++ b/resources/recipes/superesportes.recipe @@ -0,0 +1,79 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, Luciano Furtado ' +''' +www.superesportes.com.br +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class SuperEsportesRecipe(BasicNewsRecipe): + + title = u'www.superesportes.com.br' + description = u'Superesportes - Notícias do esporte no Brasil e no mundo' + __author__ = 'Luciano Furtado' + language = 'pt' + category = 'esportes, Brasil' + no_stylesheets = True + oldest_article = 7 + + use_embedded_content=0 + max_articles_per_feed = 10 + cover_url = 'http://imgs.mg.superesportes.com.br/superesportes_logo.png' + + extra_css = 'div.info_noticias h1 { font-size: 100% }' + + + + remove_tags = [ + dict(name='div',attrs={'class':'topo'}), + dict(name='div',attrs={'class':'rodape'}), + dict(name='div',attrs={'class':'navegacao'}), + dict(name='div',attrs={'class':'lateral2'}), + dict(name='div',attrs={'class':'leia_mais'}), + dict(name='div',attrs={'id':'comentar'}), + dict(name='div',attrs={'id':'vrumelc_noticia'}), + dict(name='div',attrs={'class':'compartilhe'}), + dict(name='div',attrs={'class':'linha_noticias'}), + dict(name='div',attrs={'class':'botoes_noticias'}), + dict(name='div',attrs={'class':'barra_time bg_time'}), + ] + + + + def parse_index(self): + feeds = [] + sections = [ + (u'Atletico', 'http://www.df.superesportes.com.br/futebol/atletico-mg/capa_atletico_mg/index.shtml'), + (u'Botafogo', 'http://www.df.superesportes.com.br/futebol/botafogo/capa_botafogo/index.shtml'), + (u'Corinthinas', 'http://www.df.superesportes.com.br/futebol/corinthians/capa_corinthians/index.shtml'), + (u'Cruzeiro', 'http://www.df.superesportes.com.br/futebol/cruzeiro/capa_cruzeiro/index.shtml'), + (u'Flamengo', 'http://www.df.superesportes.com.br/futebol/flamengo/capa_flamengo/index.shtml'), + (u'Fluminense', 'http://www.df.superesportes.com.br/futebol/fluminense/capa_fluminense/index.shtml'), + (u'Palmeiras', 'http://www.df.superesportes.com.br/futebol/palmeiras/capa_palmeiras/index.shtml'), + (u'Santos', 'http://www.df.superesportes.com.br/futebol/santos/capa_santos/index.shtml'), + (u'São Paulo', 'http://www.df.superesportes.com.br/futebol/sao-paulo/capa_sao_paulo/index.shtml'), + (u'Vasco', 'http://www.df.superesportes.com.br/futebol/vasco/capa_vasco/index.shtml'), + ] + + + for section, url in sections: + current_articles = [] + + soup = self.index_to_soup(url) + latestNews = soup.find(name='ul',attrs={'class': 'lista_ultimas_noticias'}) + + for li_tag in latestNews.findAll(name='li'): + a_tag = li_tag.find('a', href= True) + if a_tag is None: + continue + title = self.tag_to_string(a_tag) + url = a_tag.get('href', False) + self.log("\n\nFound title: " + title + "\nUrl: " + url + "\nSection: " + section) + current_articles.append({'title': title, 'url': url, 'description': title, 'date':''}) + + if current_articles: + feeds.append((section, current_articles)) + + + return feeds + From 35dba964c6241d5600d6aeba4bfcf27d104ee8f9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 21:46:56 -0600 Subject: [PATCH 13/16] Theming support for console via right click menu --- src/calibre/utils/pyconsole/__init__.py | 14 +++++--- src/calibre/utils/pyconsole/console.py | 44 +++++++++++++++++++++--- src/calibre/utils/pyconsole/formatter.py | 4 --- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py index 0dfa9398e1..06a7011132 100644 --- a/src/calibre/utils/pyconsole/__init__.py +++ b/src/calibre/utils/pyconsole/__init__.py @@ -8,14 +8,18 @@ __docformat__ = 'restructuredtext en' import sys from calibre import prints as prints_ -from calibre.utils.config import Config, StringConfig +from calibre.utils.config import Config, ConfigProxy -def console_config(defaults=None): - desc=_('Settings to control the calibre content server') - c = Config('console', desc) if defaults is None else StringConfig(defaults, desc) +def console_config(): + desc='Settings to control the calibre console' + c = Config('console', desc) - c.add_opt('--theme', default='default', help='The color theme') + c.add_opt('theme', default='default', help='The color theme') + + return c + +prefs = ConfigProxy(console_config()) def prints(*args, **kwargs): diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py index f741562f03..b0ecce0cb3 100644 --- a/src/calibre/utils/pyconsole/console.py +++ b/src/calibre/utils/pyconsole/console.py @@ -6,16 +6,18 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' import sys, textwrap, traceback, StringIO +from functools import partial from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \ - QCoreApplication + QCoreApplication, QColor, QPalette, QMenu, QActionGroup from pygments.lexers import PythonLexer, PythonTracebackLexer +from pygments.styles import get_all_styles from calibre.constants import __appname__, __version__ from calibre.utils.pyconsole.formatter import Formatter from calibre.utils.pyconsole.repl import Interpreter, DummyFile -from calibre.utils.pyconsole import prints +from calibre.utils.pyconsole import prints, prefs from calibre.gui2 import error_dialog class EditBlock(object): # {{{ @@ -47,6 +49,28 @@ class Prepender(object): # {{{ self.console.cursor_pos = self.opos # }}} +class ThemeMenu(QMenu): + + def __init__(self, parent): + QMenu.__init__(self, _('Choose theme (needs restart)')) + parent.addMenu(self) + self.group = QActionGroup(self) + current = prefs['theme'] + alls = list(sorted(get_all_styles())) + if current not in alls: + current = prefs['theme'] = 'default' + self.actions = [] + for style in alls: + ac = self.group.addAction(style) + if current == style: + ac.setChecked(True) + self.actions.append(ac) + ac.triggered.connect(partial(self.set_theme, style)) + self.addAction(ac) + + def set_theme(self, style, *args): + prefs['theme'] = style + class Console(QTextEdit): @@ -99,8 +123,16 @@ class Console(QTextEdit): self.doc.setMaximumBlockCount(10000) self.lexer = PythonLexer(ensurenl=False) self.tb_lexer = PythonTracebackLexer() - self.formatter = Formatter(prompt, continuation, style='default') - self.setStyleSheet(self.formatter.stylesheet) + + self.context_menu = cm = QMenu(self) # {{{ + cm.theme = ThemeMenu(cm) + # }}} + + self.formatter = Formatter(prompt, continuation, style=prefs['theme']) + p = QPalette() + p.setColor(p.Base, QColor(self.formatter.background_color)) + p.setColor(p.Text, QColor(self.formatter.color)) + self.setPalette(p) self.key_dispatcher = { # {{{ Qt.Key_Enter : self.enter_pressed, @@ -127,6 +159,10 @@ class Console(QTextEdit): sys.excepthook = self.unhandled_exception + def contextMenuEvent(self, event): + self.context_menu.popup(event.globalPos()) + event.accept() + # Prompt management {{{ diff --git a/src/calibre/utils/pyconsole/formatter.py b/src/calibre/utils/pyconsole/formatter.py index 6e7d982a82..17360fecb3 100644 --- a/src/calibre/utils/pyconsole/formatter.py +++ b/src/calibre/utils/pyconsole/formatter.py @@ -48,10 +48,6 @@ class Formatter(object): self.styles[ttype] = fmt - self.stylesheet = ''' - QTextEdit { color: %s; background-color: %s } - '''%(self.color, self.background_color) - def get_fmt(self, token): if type(token) != type(Token.Generic): token = string_to_tokentype(token) From e7adf45c01b21a584158728de903837d79f82298 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 22:21:36 -0600 Subject: [PATCH 14/16] Restart and context menu added to console --- src/calibre/utils/pyconsole/__init__.py | 2 +- src/calibre/utils/pyconsole/console.py | 5 +++-- src/calibre/utils/pyconsole/main.py | 23 ++++++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/pyconsole/__init__.py b/src/calibre/utils/pyconsole/__init__.py index 06a7011132..32eb926143 100644 --- a/src/calibre/utils/pyconsole/__init__.py +++ b/src/calibre/utils/pyconsole/__init__.py @@ -15,7 +15,7 @@ def console_config(): desc='Settings to control the calibre console' c = Config('console', desc) - c.add_opt('theme', default='default', help='The color theme') + c.add_opt('theme', default='native', help='The color theme') return c diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py index b0ecce0cb3..164cf4e2ca 100644 --- a/src/calibre/utils/pyconsole/console.py +++ b/src/calibre/utils/pyconsole/console.py @@ -49,7 +49,7 @@ class Prepender(object): # {{{ self.console.cursor_pos = self.opos # }}} -class ThemeMenu(QMenu): +class ThemeMenu(QMenu): # {{{ def __init__(self, parent): QMenu.__init__(self, _('Choose theme (needs restart)')) @@ -62,6 +62,7 @@ class ThemeMenu(QMenu): self.actions = [] for style in alls: ac = self.group.addAction(style) + ac.setCheckable(True) if current == style: ac.setChecked(True) self.actions.append(ac) @@ -71,6 +72,7 @@ class ThemeMenu(QMenu): def set_theme(self, style, *args): prefs['theme'] = style +# }}} class Console(QTextEdit): @@ -163,7 +165,6 @@ class Console(QTextEdit): self.context_menu.popup(event.globalPos()) event.accept() - # Prompt management {{{ @dynamic_property diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py index f098ce2ee2..a5a4b42266 100644 --- a/src/calibre/utils/pyconsole/main.py +++ b/src/calibre/utils/pyconsole/main.py @@ -9,7 +9,7 @@ __version__ = '0.1.0' from functools import partial from PyQt4.Qt import QDialog, QToolBar, QStatusBar, QLabel, QFont, Qt, \ - QApplication, QIcon, QVBoxLayout + QApplication, QIcon, QVBoxLayout, QAction from calibre.constants import __appname__, __version__ from calibre.utils.pyconsole.console import Console @@ -19,8 +19,9 @@ class MainWindow(QDialog): def __init__(self, default_status_msg=_('Welcome to') + ' ' + __appname__+' console', parent=None): - QDialog.__init__(self, parent) + + self.restart_requested = False self.l = QVBoxLayout() self.setLayout(self.l) @@ -51,14 +52,26 @@ class MainWindow(QDialog): self.setWindowTitle(__appname__ + ' console') self.setWindowIcon(QIcon(I('console.png'))) + self.restart_action = QAction(_('Restart'), self) + self.restart_action.setShortcut(_('Ctrl+R')) + self.addAction(self.restart_action) + self.restart_action.triggered.connect(self.restart) + self.console.context_menu.addAction(self.restart_action) + + def restart(self): + self.restart_requested = True + self.reject() def main(): QApplication.setApplicationName(__appname__+' console') QApplication.setOrganizationName('Kovid Goyal') app = QApplication([]) - m = MainWindow() - m.show() - app.exec_() + app + while True: + m = MainWindow() + m.exec_() + if not m.restart_requested: + break if __name__ == '__main__': From a41f481ee7d3a72b727c1f6b2d98ae10443cb0d2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 22:28:49 -0600 Subject: [PATCH 15/16] Backspace and delete now work in console --- src/calibre/utils/pyconsole/console.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py index 164cf4e2ca..aa0ff84d77 100644 --- a/src/calibre/utils/pyconsole/console.py +++ b/src/calibre/utils/pyconsole/console.py @@ -143,6 +143,8 @@ class Console(QTextEdit): Qt.Key_End : self.end_pressed, Qt.Key_Left : self.left_pressed, Qt.Key_Right : self.right_pressed, + Qt.Key_Backspace : self.backspace_pressed, + Qt.Key_Delete : self.delete_pressed, } # }}} motd = textwrap.dedent('''\ @@ -327,6 +329,22 @@ class Console(QTextEdit): self.setTextCursor(c) self.ensureCursorVisible() + def backspace_pressed(self): + lineno, pos = self.cursor_pos + if lineno < 0: return + if pos > self.prompt_len: + self.cursor.deletePreviousChar() + elif lineno > 0: + c = self.cursor + c.movePosition(c.Up) + c.movePosition(c.EndOfLine) + self.setTextCursor(c) + self.ensureCursorVisible() + + def delete_pressed(self): + self.cursor.deleteChar() + self.ensureCursorVisible() + def right_pressed(self): lineno, pos = self.cursor_pos if lineno < 0: return From fdd839af7cafa964b26cc9481cc55a7d5b65b16a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Sep 2010 22:46:54 -0600 Subject: [PATCH 16/16] Ctrl+Home and Ctrl+End now work --- src/calibre/utils/pyconsole/console.py | 23 ++++++++++++++++------- src/calibre/utils/pyconsole/main.py | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/pyconsole/console.py b/src/calibre/utils/pyconsole/console.py index aa0ff84d77..81169140cd 100644 --- a/src/calibre/utils/pyconsole/console.py +++ b/src/calibre/utils/pyconsole/console.py @@ -9,7 +9,7 @@ import sys, textwrap, traceback, StringIO from functools import partial from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \ - QCoreApplication, QColor, QPalette, QMenu, QActionGroup + QApplication, QColor, QPalette, QMenu, QActionGroup from pygments.lexers import PythonLexer, PythonTracebackLexer from pygments.styles import get_all_styles @@ -278,7 +278,7 @@ class Console(QTextEdit): except: prints(tb, end='') self.ensureCursorVisible() - QCoreApplication.processEvents() + QApplication.processEvents() def show_output(self, raw): def do_show(): @@ -296,7 +296,7 @@ class Console(QTextEdit): else: do_show() self.ensureCursorVisible() - QCoreApplication.processEvents() + QApplication.processEvents() # }}} @@ -360,14 +360,23 @@ class Console(QTextEdit): def home_pressed(self): if self.prompt_frame is not None: - c = self.cursor - c.movePosition(c.StartOfLine) - c.movePosition(c.NextCharacter, n=self.prompt_len) - self.setTextCursor(c) + mods = QApplication.keyboardModifiers() + ctrl = bool(int(mods & Qt.CTRL)) + if ctrl: + self.cursor_pos = (0, self.prompt_len) + else: + c = self.cursor + c.movePosition(c.StartOfLine) + c.movePosition(c.NextCharacter, n=self.prompt_len) + self.setTextCursor(c) self.ensureCursorVisible() def end_pressed(self): if self.prompt_frame is not None: + mods = QApplication.keyboardModifiers() + ctrl = bool(int(mods & Qt.CTRL)) + if ctrl: + self.cursor_pos = (len(list(self.prompt()))-1, self.prompt_len) c = self.cursor c.movePosition(c.EndOfLine) self.setTextCursor(c) diff --git a/src/calibre/utils/pyconsole/main.py b/src/calibre/utils/pyconsole/main.py index a5a4b42266..a708ca1652 100644 --- a/src/calibre/utils/pyconsole/main.py +++ b/src/calibre/utils/pyconsole/main.py @@ -52,7 +52,7 @@ class MainWindow(QDialog): self.setWindowTitle(__appname__ + ' console') self.setWindowIcon(QIcon(I('console.png'))) - self.restart_action = QAction(_('Restart'), self) + self.restart_action = QAction(_('Restart console'), self) self.restart_action.setShortcut(_('Ctrl+R')) self.addAction(self.restart_action) self.restart_action.triggered.connect(self.restart)