diff --git a/icons/book.icns b/icons/book.icns new file mode 100644 index 0000000000..ee305dc9dd Binary files /dev/null and b/icons/book.icns differ diff --git a/resources/images/news/michellemalkin_icon.png b/resources/images/news/michellemalkin_icon.png new file mode 100644 index 0000000000..76842ec642 Binary files /dev/null and b/resources/images/news/michellemalkin_icon.png differ diff --git a/resources/recipes/michellemalkin.recipe b/resources/recipes/michellemalkin.recipe new file mode 100644 index 0000000000..e933ed8f1c --- /dev/null +++ b/resources/recipes/michellemalkin.recipe @@ -0,0 +1,49 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, Walt Anthony ' +''' +www.michellemalkin.com +''' +from calibre.web.feeds.news import BasicNewsRecipe + +class MichelleMalkin(BasicNewsRecipe): + title = u'Michelle Malkin' + description = "Michelle Malkin's take on events, a mother, wife, blogger, conservative syndicated columnist, author, and Fox News Channel contributor." + __author__ = 'Walt Anthony' + publisher = 'Michelle Malkin LLC' + category = 'news, politics, USA' + oldest_article = 7 #days + max_articles_per_feed = 50 + summary_length = 150 + language = 'en' + + remove_javascript = True + no_stylesheets = True + + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + , 'linearize_tables' : True + } + + + keep_only_tags = [ + dict(name='div', attrs={'class':'article'}) + ] + + remove_tags = [ + dict(name=['iframe', 'embed', 'object']), + dict(name='div', attrs={'id':['comments', 'commentForm']}), + dict(name='div', attrs={'class':['postCategories', 'comments', 'blogInfo', 'postInfo']}) + + ] + + + feeds = [(u'http://feeds.feedburner.com/michellemalkin/posts')] + + + + def print_version(self, url): + return url + '?print=1' diff --git a/resources/recipes/open_left.recipe b/resources/recipes/open_left.recipe new file mode 100644 index 0000000000..148bb07f13 --- /dev/null +++ b/resources/recipes/open_left.recipe @@ -0,0 +1,22 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class OpenLeft(BasicNewsRecipe): + # Information about the recipe + + title = 'Open Left' + description = 'Progressive American commentary on current events' + category = 'news, commentary' + language = 'en' + __author__ = 'Xanthan Gum' + + # Fetch no article older than seven days + + oldest_article = 7 + + # Fetch no more than 100 articles + + max_articles_per_feed = 100 + + # Fetch the articles from the RSS feed + + feeds = [(u'Articles', u'http://www.openleft.com/rss/rss2.xml')] diff --git a/setup/installer/osx/app/main.py b/setup/installer/osx/app/main.py index 85533717b4..a473281e61 100644 --- a/setup/installer/osx/app/main.py +++ b/setup/installer/osx/app/main.py @@ -266,6 +266,7 @@ class Py2App(object): def get_local_dependencies(self, path_to_lib): for x in self.get_dependencies(path_to_lib): for y in (SW+'/lib/', '/usr/local/lib/', SW+'/qt/lib/', + '/opt/local/lib/', '/Library/Frameworks/Python.framework/', SW+'/freetype/lib/'): if x.startswith(y): if y == '/Library/Frameworks/Python.framework/': @@ -338,8 +339,8 @@ class Py2App(object): c = join(self.build_dir, 'Contents') for x in ('Frameworks', 'MacOS', 'Resources'): os.makedirs(join(c, x)) - x = 'library.icns' - shutil.copyfile(join('icons', x), join(self.resources_dir, x)) + for x in ('library.icns', 'book.icns'): + shutil.copyfile(join('icons', x), join(self.resources_dir, x)) @flush def add_calibre_plugins(self): @@ -355,8 +356,13 @@ class Py2App(object): @flush def create_plist(self): + from calibre.ebooks import BOOK_EXTENSIONS env = dict(**ENV) env['CALIBRE_LAUNCHED_FROM_BUNDLE']='1'; + docs = [{'CFBundleTypeName':'E-book', + 'CFBundleTypeExtensions':list(BOOK_EXTENSIONS), + 'CFBundleTypeRole':'Viewer', + }] pl = dict( CFBundleDevelopmentRegion='English', @@ -367,10 +373,11 @@ class Py2App(object): CFBundlePackageType='APPL', CFBundleSignature='????', CFBundleExecutable='calibre', + CFBundleDocumentTypes=docs, LSMinimumSystemVersion='10.4.2', LSRequiresNativeExecution=True, NSAppleScriptEnabled=False, - NSHumanReadableCopyright='Copyright 2008, Kovid Goyal', + NSHumanReadableCopyright='Copyright 2010, Kovid Goyal', CFBundleGetInfoString=('calibre, an E-book management ' 'application. Visit http://calibre-ebook.com for details.'), CFBundleIconFile='library.icns', @@ -594,6 +601,7 @@ class Py2App(object): if x == 'Info.plist': plist = plistlib.readPlist(join(self.contents_dir, x)) plist['LSUIElement'] = '1' + plist.pop('CFBundleDocumentTypes') plistlib.writePlist(plist, join(cc_dir, x)) else: os.symlink(join('../..', x), diff --git a/src/calibre/devices/prs500/books.py b/src/calibre/devices/prs500/books.py index 07f9310e87..91fcb3255f 100644 --- a/src/calibre/devices/prs500/books.py +++ b/src/calibre/devices/prs500/books.py @@ -274,7 +274,7 @@ class BookList(_BookList): node.setAttribute(attr, attrs[attr]) try: w, h, data = mi.thumbnail - except TypeError: + except: w, h, data = None, None, None if data: diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 122f61e45a..6fba918888 100755 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' Based on ideas from comiclrf created by FangornUK. ''' -import os, shutil, traceback, textwrap, time +import os, shutil, traceback, textwrap, time, codecs from ctypes import byref from Queue import Empty @@ -338,8 +338,9 @@ class ComicInput(InputFormatPlugin): if not os.path.exists('comics.txt'): raise ValueError('%s is not a valid comic collection' %stream.name) - for line in open('comics.txt', - 'rb').read().decode('utf-8').splitlines(): + raw = open('comics.txt', 'rb').read().decode('utf-8') + raw.lstrip(unicode(codecs.BOM_UTF8, "utf8" )) + for line in raw.splitlines(): line = line.strip() if not line: continue diff --git a/src/calibre/ebooks/epub/output.py b/src/calibre/ebooks/epub/output.py index 8e9c9efea9..6e381d5237 100644 --- a/src/calibre/ebooks/epub/output.py +++ b/src/calibre/ebooks/epub/output.py @@ -269,7 +269,7 @@ class EPUBOutput(OutputFormatPlugin): bad = [] for x in XPath('//h:img')(body): src = x.get('src', '').strip() - if src in ('', '#'): + if src in ('', '#') or src.startswith('http:'): bad.append(x) for img in bad: img.getparent().remove(img) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 4f894ce088..4aac84e599 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -573,6 +573,8 @@ class MobiReader(object): attrib[attr] = "%dpx"%int(nval) except: del attrib[attr] + elif val.lower().endswith('%'): + del attrib[attr] elif tag.tag == 'pre': if not tag.text: tag.tag = 'div' diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 34f9f57161..7181c16329 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -3,7 +3,8 @@ __copyright__ = '2008, Kovid Goyal ' """ The GUI """ import os from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \ - QByteArray, QTranslator, QCoreApplication, QThread + QByteArray, QTranslator, QCoreApplication, QThread, \ + QEvent from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ QIcon, QTableView, QApplication, QDialog, QPushButton @@ -524,6 +525,7 @@ class Application(QApplication): def __init__(self, args): qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] QApplication.__init__(self, qargs) + self.file_event_hook = None global gui_thread, qt_app gui_thread = QThread.currentThread() self._translator = None @@ -549,6 +551,15 @@ class Application(QApplication): if set_qt_translator(self._translator): self.installTranslator(self._translator) + def event(self, e): + if callable(self.file_event_hook) and e.type() == QEvent.FileOpen: + path = unicode(e.file()) + if os.access(path, os.R_OK): + self.file_event_hook(path) + return True + else: + return QApplication.event(self, e) + _store_app = None def is_ok_to_use_qt(): diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 5419596334..ace2ac5c7e 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -12,7 +12,7 @@ from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2 import question_dialog, error_dialog, info_dialog from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata import MetaInformation -from calibre.constants import preferred_encoding +from calibre.constants import preferred_encoding, filesystem_encoding class DuplicatesAdder(QThread): @@ -46,6 +46,8 @@ class RecursiveFind(QThread): def run(self): root = os.path.abspath(self.path) self.books = [] + if isinstance(root, unicode): + root = root.encode(filesystem_encoding) try: for dirpath in os.walk(root): if self.canceled: @@ -55,6 +57,8 @@ class RecursiveFind(QThread): self.books += list(self.db.find_books_in_directory(dirpath[0], self.single_book_per_directory)) except Exception, err: + import traceback + traceback.print_exc() try: msg = unicode(err) except: diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index d76d8136db..46342c8a88 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -20,10 +20,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.db = db self.ids = [ db.id(r) for r in rows] self.write_series = False - self.write_rating = False self.changed = False QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync) - QObject.connect(self.rating, SIGNAL('valueChanged(int)'), self.rating_changed) self.tags.update_tags_cache(self.db.all_tags()) self.remove_tags.update_tags_cache(self.db.all_tags()) @@ -99,7 +97,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): aus = unicode(self.author_sort.text()) if aus and self.author_sort.isEnabled(): self.db.set_author_sort(id, aus, notify=False) - if self.write_rating: + if self.rating.value() != -1: self.db.set_rating(id, 2*self.rating.value(), notify=False) pub = unicode(self.publisher.text()) if pub: @@ -134,5 +132,3 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): def series_changed(self): self.write_series = True - def rating_changed(self): - self.write_rating = True diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index 0fdb36b717..1a38568b60 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -96,12 +96,21 @@ QAbstractSpinBox::PlusMinus + + No change + stars + + -1 + 5 + + -1 + diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index cf62508751..fef0853a23 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -2,6 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' import sys, os, time, socket, traceback +from functools import partial from PyQt4.Qt import QCoreApplication, QIcon, QMessageBox @@ -52,10 +53,12 @@ def run_gui(opts, args, actions, listener, app): wizard().exec_() dynamic.set('welcome_wizard_was_run', True) main = Main(listener, opts, actions) + add_filesystem_book = partial(main.add_filesystem_book, allow_device=False) sys.excepthook = main.unhandled_exception if len(args) > 1: args[1] = os.path.abspath(args[1]) - main.add_filesystem_book(args[1]) + add_filesystem_book(args[1]) + app.file_event_hook = add_filesystem_book ret = app.exec_() if getattr(main, 'run_wizard_b4_shutdown', False): from calibre.gui2.wizard import wizard diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 714b2c3a27..7f3ca297fd 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -987,10 +987,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.cover_cache.refresh([cid]) self.library_view.model().current_changed(current_idx, current_idx) - def add_filesystem_book(self, path): + def add_filesystem_book(self, path, allow_device=True): if os.access(path, os.R_OK): books = [os.path.abspath(path)] - to_device = self.stack.currentIndex() != 0 + to_device = allow_device and self.stack.currentIndex() != 0 self._add_books(books, to_device) if to_device: self.status_bar.showMessage(\ @@ -1048,6 +1048,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): if self._adder.critical: det_msg = [] for name, log in self._adder.critical.items(): + if isinstance(name, str): + name = name.decode(filesystem_encoding, 'replace') det_msg.append(name+'\n'+log) warning_dialog(self, _('Failed to read metadata'), _('Failed to read metadata from the following')+':', diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 6b95a4dcaa..9b911754c8 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -514,7 +514,7 @@ class DocumentView(QWebView): mt = guess_type(path)[0] html = open(path, 'rb').read().decode(path.encoding, 'replace') html = EntityDeclarationProcessor(html).processed_html - has_svg = re.search(r'<[:a-z]*svg', html) is not None + has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None if 'xhtml' in mt: html = self.self_closing_pat.sub(self.self_closing_sub, html) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 3dda46864a..a8b3442dfd 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -4,7 +4,7 @@ from collections import namedtuple from datetime import date from xml.sax.saxutils import escape -from calibre import filesystem_encoding, prints +from calibre import filesystem_encoding, prints, strftime from calibre.customize import CatalogPlugin from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString @@ -97,7 +97,7 @@ class CSV_XML(CatalogPlugin): item = ', '.join(item) elif field == 'isbn': # Could be 9, 10 or 13 digits - field = '%s' % re.sub('[\D]','',field) + field = u'%s' % re.sub(r'[\D]','',field) if x < len(fields) - 1: if item is not None: @@ -488,9 +488,6 @@ class EPUB_MOBI(CatalogPlugin): current_step = 0.0 total_steps = 14.0 - # Used to xlate pubdate to friendly format - MONTHS = ['','January', 'February','March','April','May','June', - 'July','August','September','October','November','December'] THUMB_WIDTH = 75 THUMB_HEIGHT = 100 @@ -878,9 +875,7 @@ class EPUB_MOBI(CatalogPlugin): this_title['publisher'] = re.sub('&', '&', record['publisher']) this_title['rating'] = record['rating'] if record['rating'] else 0 - # 2009-11-05 09:29:37 - date_strings = str(record['pubdate']).split("-") - this_title['date'] = '%s %s' % (self.MONTHS[int(date_strings[1])], date_strings[0]) + this_title['date'] = strftime(u'%b %Y', record['pubdate'].timetuple()) this_title['timestamp'] = record['timestamp'] if record['comments']: this_title['description'] = re.sub('&', '&', record['comments']) @@ -1332,11 +1327,11 @@ class EPUB_MOBI(CatalogPlugin): def add_books_to_HTML(this_months_list, dtc): if len(this_months_list): + date_string = strftime(u'%b %Y', current_date.timetuple()) this_months_list = sorted(this_months_list, key=lambda x:(x['title_sort'], x['title_sort'])) this_months_list = sorted(this_months_list, key=lambda x:(x['author_sort'], x['author_sort'])) - # Create a new month anchor pIndexTag = Tag(soup, "p") pIndexTag['class'] = "date_index" @@ -1435,7 +1430,6 @@ class EPUB_MOBI(CatalogPlugin): dtc = 0 current_date = date.fromordinal(1) - current_author = None # Loop through books by date this_months_list = [] @@ -2066,7 +2060,6 @@ class EPUB_MOBI(CatalogPlugin): # add description_preview_count titles # self.authors[0]:friendly [1]:author_sort [2]:book_count current_titles_list = [] - current_author_list = [] master_month_list = [] current_date = self.booksByDate[0]['timestamp'] @@ -2088,6 +2081,7 @@ class EPUB_MOBI(CatalogPlugin): # Add *article* entries for each populated month # master_months_list{}: [0]:titles list [1]:date for books_by_month in master_month_list: + datestr = strftime(u'%b %Y', books_by_month[1].timetuple()) navPointByMonthTag = Tag(soup, 'navPoint') navPointByMonthTag['class'] = "article" navPointByMonthTag['id'] = "%s-%s-ID" % (books_by_month[1].year,books_by_month[1].month ) @@ -2095,8 +2089,7 @@ class EPUB_MOBI(CatalogPlugin): self.playOrder += 1 navLabelTag = Tag(soup, 'navLabel') textTag = Tag(soup, 'text') - textTag.insert(0, NavigableString("Books added in %s %s" % \ - (self.MONTHS[books_by_month[1].month], books_by_month[1].year))) + textTag.insert(0, NavigableString("Books added in " + datestr)) navLabelTag.insert(0, textTag) navPointByMonthTag.insert(0,navLabelTag) contentTag = Tag(soup, 'content') diff --git a/src/calibre/startup.py b/src/calibre/startup.py index 3a761cca10..3e33757f92 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -84,6 +84,7 @@ if not _run_once: return res os.path.abspath = my_abspath + _join = os.path.join def my_join(a, *p): encoding=sys.getfilesystemencoding() diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 5cd90ec49a..113d7dd756 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -558,8 +558,6 @@ class BasicNewsRecipe(Recipe): '--max-recursions', str(self.recursions), '--delay', str(self.delay), ] - if self.encoding is not None: - web2disk_cmdline.extend(['--encoding', self.encoding]) if self.verbose: web2disk_cmdline.append('--verbose') @@ -578,6 +576,7 @@ class BasicNewsRecipe(Recipe): 'preprocess_html', 'remove_tags_after', 'remove_tags_before'): setattr(self.web2disk_options, extra, getattr(self, extra)) self.web2disk_options.postprocess_html = self._postprocess_html + self.web2disk_options.encoding = self.encoding if self.delay > 0: self.simultaneous_downloads = 1 @@ -761,6 +760,7 @@ class BasicNewsRecipe(Recipe): self.report_progress(0, _('Trying to download cover...')) self.download_cover() self.report_progress(0, _('Generating masthead...')) + self.masthead_path = None try: murl = self.get_masthead_url() except: @@ -768,7 +768,7 @@ class BasicNewsRecipe(Recipe): murl = None if murl is not None: self.download_masthead(murl) - else: + if self.masthead_path is None: self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg') self.default_masthead_image(self.masthead_path) @@ -894,7 +894,7 @@ class BasicNewsRecipe(Recipe): ext = '' ext = ext.lower() if ext else 'jpg' mpath = os.path.join(self.output_dir, 'masthead_source.'+ext) - self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg') + outfile = os.path.join(self.output_dir, 'mastheadImage.jpg') if os.access(mu, os.R_OK): with open(mpath, 'wb') as mfile: mfile.write(open(mu, 'rb').read()) @@ -902,20 +902,17 @@ class BasicNewsRecipe(Recipe): with nested(open(mpath, 'wb'), closing(self.browser.open(mu))) as (mfile, r): mfile.write(r.read()) self.report_progress(1, _('Masthead image downloaded')) - self.prepare_masthead_image(mpath, self.masthead_path) + self.prepare_masthead_image(mpath, outfile) + self.masthead_path = outfile if os.path.exists(mpath): os.remove(mpath) def download_masthead(self, url): - br = BasicNewsRecipe.get_browser() try: - br.open(url) self._download_masthead(url) except: self.log.exception("Failed to download supplied masthead_url, synthesizing") - self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg') - self.default_masthead_image(self.masthead_path) def default_cover(self, cover_file): '''