diff --git a/resources/recipes/cnetjapan.recipe b/resources/recipes/cnetjapan.recipe index 1058b90401..b57bce5b97 100644 --- a/resources/recipes/cnetjapan.recipe +++ b/resources/recipes/cnetjapan.recipe @@ -11,7 +11,7 @@ class CNetJapan(BasicNewsRecipe): (u'CNet Blog', u'http://feed.japan.cnet.com/rss/blog/index.rdf') ] language = 'ja' - encoding = 'Shift_JIS' + encoding = 'utf-8' remove_javascript = True preprocess_regexps = [ diff --git a/resources/recipes/tyzden.recipe b/resources/recipes/tyzden.recipe new file mode 100644 index 0000000000..c206244ff6 --- /dev/null +++ b/resources/recipes/tyzden.recipe @@ -0,0 +1,80 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2011, Miroslav Vasko zemiak@gmail.com' + +''' +.tyzden, a weekly news magazine (a week old issue) +''' +from calibre import strftime +from calibre.web.feeds.news import BasicNewsRecipe +from datetime import date +import re + +class TyzdenRecipe(BasicNewsRecipe): + __license__ = 'GPL v3' + __author__ = 'zemiak' + language = 'sk' + version = 1 + + publisher = u'www.tyzden.sk' + category = u'Magazine' + description = u'A conservative weekly magazine. The latest free issue' + + today = date.today() + iso = today.isocalendar() + year = iso[0] + weeknum = iso[1] + + if (weeknum > 1): + weeknum -= 1 + + title = u'.tyzden ' + str(weeknum) + '/' + str(year) + + base_url_path = 'http://www.tyzden.sk/casopis/' + str(year) + '/' + str(weeknum) + base_url = base_url_path + '.html' + + oldest_article = 20 + max_articles_per_feed = 100 + remove_javascript = True + + use_embedded_content = False + no_stylesheets = True + + keep_only_tags = [] + keep_only_tags.append(dict(name = 'h1')) + keep_only_tags.append(dict(name = 'div', attrs = {'class': 'text_area top_nofoto'})) + keep_only_tags.append(dict(name = 'div', attrs = {'class': 'text_block'})) + + remove_tags_after = [dict(name = 'div', attrs = {'class': 'text_block'})] + + def find_sections(self): + soup = self.index_to_soup(self.base_url) + # find cover pic + imgdiv = soup.find('div', attrs = {'class': 'foto'}) + if imgdiv is not None: + img = imgdiv.find('img') + if img is not None: + self.cover_url = 'http://www.tyzden.sk/' + img['src'] + # end find cover pic + + for s in soup.findAll('a', attrs={'href': re.compile(r'rubrika/.*')}): + yield (self.tag_to_string(s), s) + + def find_articles(self, soup): + for art in soup.findAllNext('a'): + if (not art['href'].startswith('casopis/')): + break; + + url = art['href'] + title = self.tag_to_string(art) + yield { + 'title': title, 'url':self.base_url_path + '/' + url, 'description':title, + 'date' : strftime('%a, %d %b'), + } + + def parse_index(self): + feeds = [] + for title, soup in self.find_sections(): + feeds.append((title, list(self.find_articles(soup)))) + + return feeds diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 73c930778e..5a82882dfa 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -53,6 +53,9 @@ class ANDROID(USBMS): # LG 0x1004 : { 0x61cc : [0x100] }, + # Archos + 0x0e79 : { 0x1420 : [0x0216]}, + } EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books'] EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to ' @@ -61,18 +64,19 @@ class ANDROID(USBMS): EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(EBOOK_DIR_MAIN) VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER', - 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE'] + 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS'] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE', - 'SGH-T849', '_MB300'] + 'SGH-T849', '_MB300', 'A70S'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', - 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD'] + 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', + 'A70S'] - OSX_MAIN_MEM = 'HTC Android Phone Media' + OSX_MAIN_MEM = 'Android Device Main Memory' - MAIN_MEMORY_VOLUME_LABEL = 'Android Phone Internal Memory' + MAIN_MEMORY_VOLUME_LABEL = 'Android Device Main Memory' SUPPORTS_SUB_DIRS = True diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index 52d1bcc619..dac93fa2e2 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -191,15 +191,15 @@ class PreProcessor(object): blanklines = "\s*(?P<(p|span|div)[^>]*>\s*(<(p|span|div)[^>]*>\s*\s*)\s*){0,3}\s*" line_opening = "<(span|div|p)[^>]*>\s*(<(span|div|p)[^>]*>)?\s*" txt_line_wrap = u"((\u0020|\u0009)*\n){1,4}" - + unwrap_regex = lookahead+line_ending+blanklines+line_opening if format == 'txt': unwrap_regex = lookahead+txt_line_wrap - + unwrap = re.compile(u"%s" % unwrap_regex, re.UNICODE) content = unwrap.sub(' ', content) return content - + def __call__(self, html): self.log("********* Preprocessing HTML *********") diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py index 8c7561f68c..5154373eda 100644 --- a/src/calibre/ebooks/rtf/input.py +++ b/src/calibre/ebooks/rtf/input.py @@ -296,7 +296,7 @@ class RTFInput(InputFormatPlugin): u'

\u00a0

\n'.encode('utf-8'), res) if self.opts.preprocess_html: preprocessor = PreProcessor(self.opts, log=getattr(self, 'log', None)) - res = preprocessor(res) + res = preprocessor(res.decode('utf-8')).encode('utf-8') f.write(res) self.write_inline_css(inline_class, border_styles) stream.seek(0) diff --git a/src/calibre/ebooks/txt/input.py b/src/calibre/ebooks/txt/input.py index 3957391494..aaff8b55c0 100644 --- a/src/calibre/ebooks/txt/input.py +++ b/src/calibre/ebooks/txt/input.py @@ -53,7 +53,7 @@ class TXTInput(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): log.debug('Reading text from file...') - + txt = stream.read() # Get the encoding of the document. if options.input_encoding: @@ -80,7 +80,7 @@ class TXTInput(InputFormatPlugin): # Get length for hyphen removal and punctuation unwrap docanalysis = DocAnalysis('txt', txt) length = docanalysis.line_length(.5) - + if options.formatting_type == 'auto': options.formatting_type = detect_formatting_type(txt) @@ -122,7 +122,7 @@ class TXTInput(InputFormatPlugin): txt = preprocessor.punctuation_unwrap(length, txt, 'txt') flow_size = getattr(options, 'flow_size', 0) - + if options.formatting_type == 'heuristic': html = convert_heuristic(txt, epub_split_size_kb=flow_size) else: diff --git a/src/calibre/ebooks/txt/txtml.py b/src/calibre/ebooks/txt/txtml.py index 786f50824d..00992a8612 100644 --- a/src/calibre/ebooks/txt/txtml.py +++ b/src/calibre/ebooks/txt/txtml.py @@ -218,6 +218,10 @@ class TXTMLizer(object): if tag in SPACE_TAGS: text.append(u' ') + + # Scene breaks. + if tag == 'hr': + text.append('\n\n* * *\n\n') # Process tags that contain text. if hasattr(elem, 'text') and elem.text: diff --git a/src/calibre/gui2/preferences/plugins.py b/src/calibre/gui2/preferences/plugins.py index 2fe2b3bf01..8b4a221f56 100644 --- a/src/calibre/gui2/preferences/plugins.py +++ b/src/calibre/gui2/preferences/plugins.py @@ -15,7 +15,8 @@ from calibre.gui2.preferences.plugins_ui import Ui_Form from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \ disable_plugin, plugin_customization, add_plugin, \ remove_plugin -from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files +from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \ + question_dialog class PluginModel(QAbstractItemModel): # {{{ @@ -132,7 +133,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.toggle_plugin_button.clicked.connect(self.toggle_plugin) self.customize_plugin_button.clicked.connect(self.customize_plugin) self.remove_plugin_button.clicked.connect(self.remove_plugin) - self.button_plugin_browse.clicked.connect(self.find_plugin) self.button_plugin_add.clicked.connect(self.add_plugin) def toggle_plugin(self, *args): @@ -149,23 +149,33 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.modify_plugin(op='remove') def add_plugin(self): - path = unicode(self.plugin_path.text()) - if path and os.access(path, os.R_OK) and path.lower().endswith('.zip'): - add_plugin(path) + path = choose_files(self, 'add a plugin dialog', _('Add plugin'), + filters=[(_('Plugins'), ['zip'])], all_files=False, + select_only_single_file=True) + if not path: + return + path = path[0] + if path and os.access(path, os.R_OK) and path.lower().endswith('.zip'): + if not question_dialog(self, _('Are you sure?'), '

' + \ + _('Installing plugins is a security risk. ' + 'Plugins can contain a virus/malware. ' + 'Only install it if you got it from a trusted source.' + ' Are you sure you want to proceed?'), + show_copy_button=False): + return + plugin = add_plugin(path) self._plugin_model.populate() self._plugin_model.reset() self.changed_signal.emit() - self.plugin_path.setText('') + info_dialog(self, _('Success'), + _('Plugin {0} successfully installed under ' + ' {1} plugins. You may have to restart calibre ' + 'for the plugin to take effect.').format(plugin.name, plugin.type), + show=True) else: error_dialog(self, _('No valid plugin path'), _('%s is not a valid plugin path')%path).exec_() - def find_plugin(self): - path = choose_files(self, 'choose plugin dialog', _('Choose plugin'), - filters=[('Plugins', ['zip'])], all_files=False, - select_only_single_file=True) - if path: - self.plugin_path.setText(path[0]) def modify_plugin(self, op=''): index = self.plugin_view.currentIndex() diff --git a/src/calibre/gui2/preferences/plugins.ui b/src/calibre/gui2/preferences/plugins.ui index 8979867bbc..18f0786a66 100644 --- a/src/calibre/gui2/preferences/plugins.ui +++ b/src/calibre/gui2/preferences/plugins.ui @@ -72,64 +72,10 @@ - - - Add new plugin + + + &Add a new plugin - - - - - - - Plugin &file: - - - plugin_path - - - - - - - - - - ... - - - - :/images/document_open.png:/images/document_open.png - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - &Add - - - - - - diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 0bdd155cbf..798420200a 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -103,7 +103,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.gui_debug = gui_debug acmap = OrderedDict() for action in interface_actions(): - ac = action.load_actual_plugin(self) + try: + ac = action.load_actual_plugin(self) + except: + # Ignore errors in loading user supplied plugins + import traceback + traceback.print_exc() + if ac.plugin_path is None: + raise + ac.plugin_path = action.plugin_path ac.interface_action_base_plugin = action if ac.name in acmap: diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 13469f5622..55abae0392 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -279,7 +279,7 @@ class Document(QWebPage): # {{{ @pyqtSignature("") def init_hyphenate(self): - if self.hyphenate: + if self.hyphenate and getattr(self, 'loaded_lang', ''): self.javascript('do_hyphenation("%s")'%self.loaded_lang) def after_load(self): diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index bd5a1f8ab9..83f19b8711 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -98,9 +98,10 @@ class AumSortedConcatenate(object): def finalize(self): keys = self.ans.keys() - if len(keys) == 0: + l = len(keys) + if l == 0: return 'Unknown:::Unknown' - if len(keys) == 1: + if l == 1: return self.ans[keys[0]] return ':#:'.join([self.ans[v] for v in sorted(keys)])