diff --git a/resources/recipes/sportsillustrated.recipe b/resources/recipes/sportsillustrated.recipe index dd1df16ac7..f5a7b4c32b 100644 --- a/resources/recipes/sportsillustrated.recipe +++ b/resources/recipes/sportsillustrated.recipe @@ -1,5 +1,5 @@ from calibre.web.feeds.recipes import BasicNewsRecipe -from calibre.ebooks.BeautifulSoup import BeautifulSoup +#from calibre.ebooks.BeautifulSoup import BeautifulSoup from urllib import quote class SportsIllustratedRecipe(BasicNewsRecipe) : @@ -91,7 +91,7 @@ class SportsIllustratedRecipe(BasicNewsRecipe) : # expire : no idea what value to use # All this comes from the Javascript function that redirects to the print version. It's called PT() and is defined in the file 48.js - def preprocess_html(self, soup): + '''def preprocess_html(self, soup): header = soup.find('div', attrs = {'class' : 'siv_artheader'}) homeMadeSoup = BeautifulSoup('') body = homeMadeSoup.body @@ -115,4 +115,5 @@ class SportsIllustratedRecipe(BasicNewsRecipe) : body.append(para) return homeMadeSoup + ''' diff --git a/src/calibre/devices/sne/driver.py b/src/calibre/devices/sne/driver.py index bb8d34c59c..04e5cd0d76 100644 --- a/src/calibre/devices/sne/driver.py +++ b/src/calibre/devices/sne/driver.py @@ -33,6 +33,6 @@ class SNE(USBMS): STORAGE_CARD_VOLUME_LABEL = 'SNE Storage Card' EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Books' - SUPPORTS_SUB_DIRS = True + SUPPORTS_SUB_DIRS = False diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py index 92ac8a2519..d1a6b7c88a 100644 --- a/src/calibre/ebooks/rtf/input.py +++ b/src/calibre/ebooks/rtf/input.py @@ -286,7 +286,6 @@ class RTFInput(InputFormatPlugin): try: xml = self.generate_xml(stream.name) except RtfInvalidCodeException, e: - raise raise ValueError(_('This RTF file has a feature calibre does not ' 'support. Convert it to HTML first and then try it.\n%s')%e) diff --git a/src/calibre/ebooks/rtf2xml/ParseRtf.py b/src/calibre/ebooks/rtf2xml/ParseRtf.py index cdd9a3d088..d673836210 100755 --- a/src/calibre/ebooks/rtf2xml/ParseRtf.py +++ b/src/calibre/ebooks/rtf2xml/ParseRtf.py @@ -226,10 +226,6 @@ class ParseRtf: try: return_value = process_tokens_obj.process_tokens() except InvalidRtfException, msg: - try: - os.remove(self.__temp_file) - except OSError: - pass #Check to see if the file is correctly encoded encode_obj = default_encoding.DefaultEncoding( in_file = self.__temp_file, @@ -241,14 +237,17 @@ class ParseRtf: check_encoding_obj = check_encoding.CheckEncoding( bug_handler = RtfInvalidCodeException, ) - enc = encode_obj.get_codepage() - if enc != 'mac_roman': - enc = 'cp' + enc + enc = 'cp' + encode_obj.get_codepage() + msg = 'Exception in token processing' if check_encoding_obj.check_encoding(self.__file, enc): file_name = self.__file if isinstance(self.__file, str) \ else self.__file.encode('utf-8') msg = 'File %s does not appear to be correctly encoded.\n' % file_name - raise InvalidRtfException, msg + try: + os.remove(self.__temp_file) + except OSError: + pass + raise InvalidRtfException, msg delete_info_obj = delete_info.DeleteInfo( in_file = self.__temp_file, copy = self.__copy, diff --git a/src/calibre/ebooks/rtf2xml/default_encoding.py b/src/calibre/ebooks/rtf2xml/default_encoding.py index 53887e0d90..3ddfbcd321 100755 --- a/src/calibre/ebooks/rtf2xml/default_encoding.py +++ b/src/calibre/ebooks/rtf2xml/default_encoding.py @@ -74,9 +74,6 @@ class DefaultEncoding: if not self.__datafetched: self._encoding() self.__datafetched = True - if self.__platform == 'Macintosh': - code_page = self.__code_page - else: code_page = 'ansicpg' + self.__code_page return self.__platform, code_page, self.__default_num @@ -94,49 +91,60 @@ class DefaultEncoding: def _encoding(self): with open(self.__file, 'r') as read_obj: + cpfound = False if not self.__fetchraw: for line in read_obj: self.__token_info = line[:16] if self.__token_info == 'mi 3: - msg = 'flag problem\n' + msg = 'Flag problem\n' raise self.__bug_handler, msg return True elif self.__token_info in self.__allowable : @@ -173,8 +171,8 @@ class DeleteInfo: Return True for all control words. Return False otherwise. """ - if self.__delete_count == self.__cb_count and self.__token_info ==\ - 'cb33\n + def __collect_tokens_func(self, line): """ Requires: @@ -194,18 +227,19 @@ class Info: att = line[6:16] value = line[20:-1] att_changed = self.__token_dict.get(att) - if att_changed == None: + if att_changed is None: if self.__run_level > 3: - msg = 'no dictionary match for %s\n' % att + msg = 'No dictionary match for %s\n' % att raise self.__bug_handler, msg else: self.__text_string += '<%s>%s' % (att_changed, value) + def __single_field_func(self, line, tag): value = line[20:-1] self.__write_obj.write( - 'mi%s\n' % (tag, tag, value) + 'mi%s\n' % (tag, tag, value) ) + def __after_info_table_func(self, line): """ Requires: @@ -217,6 +251,7 @@ class Info: the file. """ self.__write_obj.write(line) + def fix_info(self): """ Requires: @@ -234,20 +269,15 @@ class Info: information table, simply write the line to the output file. """ self.__initiate_values() - read_obj = open(self.__file, 'r') - self.__write_obj = open(self.__write_to, 'w') - line_to_read = 1 - while line_to_read: - line_to_read = read_obj.readline() - line = line_to_read - self.__token_info = line[:16] - action = self.__state_dict.get(self.__state) - if action == None: - sys.stderr.write('no no matching state in module styles.py\n') - sys.stderr.write(self.__state + '\n') - action(line) - read_obj.close() - self.__write_obj.close() + with open(self.__file, 'r') as read_obj: + with open(self.__write_to, 'wb') as self.__write_obj: + for line in read_obj: + self.__token_info = line[:16] + action = self.__state_dict.get(self.__state) + if action is None: + sys.stderr.write('No matching state in module styles.py\n') + sys.stderr.write(self.__state + '\n') + action(line) copy_obj = copy.Copy(bug_handler = self.__bug_handler) if self.__copy: copy_obj.copy_file(self.__write_to, "info.data") diff --git a/src/calibre/ebooks/rtf2xml/process_tokens.py b/src/calibre/ebooks/rtf2xml/process_tokens.py index 9460af07fc..c6cf124425 100755 --- a/src/calibre/ebooks/rtf2xml/process_tokens.py +++ b/src/calibre/ebooks/rtf2xml/process_tokens.py @@ -70,7 +70,7 @@ class ProcessTokens: ';' : ('mc', ';', self.ms_sub_func), # this must be wrong '-' : ('mc', '-', self.ms_sub_func), - 'line' : ('mi', 'hardline-break', self.hardline_func), #calibre + 'line' : ('mi', 'hardline-break', self.direct_conv_func), #calibre # misc => ml '*' : ('ml', 'asterisk__', self.default_func), ':' : ('ml', 'colon_____', self.default_func), @@ -78,7 +78,6 @@ class ProcessTokens: 'backslash' : ('nu', '\\', self.text_func), 'ob' : ('nu', '{', self.text_func), 'cb' : ('nu', '}', self.text_func), - #'line' : ('nu', ' ', self.text_func), calibre # paragraph formatting => pf 'page' : ('pf', 'page-break', self.default_func), 'par' : ('pf', 'par-end___', self.default_func), @@ -231,11 +230,15 @@ class ProcessTokens: 'trhdr' : ('tb', 'row-header', self.default_func), # preamble => pr # document information => di + # TODO integrate \userprops 'info' : ('di', 'doc-info__', self.default_func), + 'title' : ('di', 'title_____', self.default_func), 'author' : ('di', 'author____', self.default_func), 'operator' : ('di', 'operator__', self.default_func), - 'title' : ('di', 'title_____', self.default_func), + 'manager' : ('di', 'manager___', self.default_func), + 'company' : ('di', 'company___', self.default_func), 'keywords' : ('di', 'keywords__', self.default_func), + 'category' : ('di', 'category__', self.default_func), 'doccomm' : ('di', 'doc-notes_', self.default_func), 'comment' : ('di', 'doc-notes_', self.default_func), 'subject' : ('di', 'subject___', self.default_func), @@ -244,11 +247,19 @@ class ProcessTokens: 'mo' : ('di', 'month_____', self.default_func), 'dy' : ('di', 'day_______', self.default_func), 'min' : ('di', 'minute____', self.default_func), + 'sec' : ('di', 'second____', self.default_func), 'revtim' : ('di', 'revis-time', self.default_func), + 'edmins' : ('di', 'edit-time_', self.default_func), + 'printim' : ('di', 'print-time', self.default_func), + 'buptim' : ('di', 'backuptime', self.default_func), 'nofwords' : ('di', 'num-of-wor', self.default_func), 'nofchars' : ('di', 'num-of-chr', self.default_func), + 'nofcharsws' : ('di', 'numofchrws', self.default_func), 'nofpages' : ('di', 'num-of-pag', self.default_func), - 'edmins' : ('di', 'edit-time_', self.default_func), + 'version' : ('di', 'version___', self.default_func), + 'vern' : ('di', 'intern-ver', self.default_func), + 'hlinkbase' : ('di', 'linkbase__', self.default_func), + 'id' : ('di', 'internalID', self.default_func), # headers and footers => hf 'headerf' : ('hf', 'head-first', self.default_func), 'headerl' : ('hf', 'head-left_', self.default_func), @@ -605,7 +616,7 @@ class ProcessTokens: def ms_sub_func(self, pre, token, num): return 'tx ", input_file) input_file = self.__utf_ud.sub("\\{\\uc0 \g<1>\\}", input_file) #remove \n in bin data @@ -127,7 +131,7 @@ class Tokenize: # this is for older RTF #line = re.sub(self.__par_exp, '\\par ', line) #return filter(lambda x: len(x) > 0, \ - #(self.__remove_line.sub('', x) for x in tokens)) + #(self.__remove_line.sub('', x) for x in tokens)) def __compile_expressions(self): SIMPLE_RPL = { @@ -153,8 +157,6 @@ class Tokenize: # put a backslash in front of to eliminate special cases and # make processing easier "}": "\\}", - # this is for older RTF - r'\\$': '\\par ', } self.__replace_spchar = MReplace(SIMPLE_RPL) #add ;? in case of char following \u @@ -168,10 +170,12 @@ class Tokenize: #why keep backslash whereas \is replaced before? #remove \n from endline char self.__splitexp = re.compile(r"(\\[{}]|\n|\\[^\s\\{}&]+(?:[ \t\r\f\v])?)") + #this is for old RTF + self.__par_exp = re.compile(r'\\\n+') + # self.__par_exp = re.compile(r'\\$') #self.__bin_exp = re.compile(r"\\bin(-?\d{1,8}) {0,1}") #self.__utf_exp = re.compile(r"^\\u(-?\d{3,6})") #self.__splitexp = re.compile(r"(\\[\\{}]|{|}|\n|\\[^\s\\{}&]+(?:\s)?)") - #self.__par_exp = re.compile(r'\\$') #self.__remove_line = re.compile(r'\n+') #self.__mixed_exp = re.compile(r"(\\[a-zA-Z]+\d+)(\D+)") ##self.num_exp = re.compile(r"(\*|:|[a-zA-Z]+)(.*)") @@ -199,7 +203,24 @@ class Tokenize: copy_obj = copy.Copy(bug_handler = self.__bug_handler) if self.__copy: copy_obj.copy_file(self.__write_to, "tokenize.data") + # if self.__out_file: + # self.__file = self.__out_file copy_obj.rename(self.__write_to, self.__file) os.remove(self.__write_to) - #self.__special_tokens = [ '_', '~', "'", '{', '}' ] \ No newline at end of file + #self.__special_tokens = [ '_', '~', "'", '{', '}' ] + +# import sys +# def main(args=sys.argv): + # if len(args) < 1: + # print 'No file' + # return + # file = 'data_tokens.txt' + # if len(args) == 3: + # file = args[2] + # to = Tokenize(args[1], Exception, out_file = file) + # to.tokenize() + + +# if __name__ == '__main__': + # sys.exit(main()) \ No newline at end of file diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index e699551150..6a9becee50 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -505,7 +505,7 @@ class FileDialog(QObject): self.selected_files = [] if mode == QFileDialog.AnyFile: f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, "")) - if f and os.path.exists(f): + if f: self.selected_files.append(f) elif mode == QFileDialog.ExistingFile: f = unicode(QFileDialog.getOpenFileName(parent, title, initial_dir, ftext, "")) diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index d75b0dfa5a..6d3bb539a2 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -28,7 +28,7 @@ class GenerateCatalogAction(InterfaceAction): if not ids: return error_dialog(self.gui, _('No books selected'), - _('No books selected to generate catalog for'), + _('No books selected for catalog generation'), show=True) db = self.gui.library_view.model().db @@ -55,9 +55,9 @@ class GenerateCatalogAction(InterfaceAction): def catalog_generated(self, job): if job.result: - # Search terms nulled catalog results - return error_dialog(self.gui, _('No books found'), - _("No books to catalog\nCheck job details"), + # Error during catalog generation + return error_dialog(self.gui, _('Catalog generation terminated'), + job.result, show=True) if job.failed: return self.gui.job_exception(job) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index c045ccf686..087d40c4eb 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -1144,7 +1144,9 @@ class EPUB_MOBI(CatalogPlugin): def error(self): def fget(self): return self.__error - return property(fget=fget) + def fset(self, val): + self.__error = val + return property(fget=fget,fset=fset) @dynamic_property def generateForKindle(self): def fget(self): @@ -1411,6 +1413,88 @@ class EPUB_MOBI(CatalogPlugin): except: pass + def fetchBooksByAuthor(self): + ''' + Generate a list of titles sorted by author from the database + return = Success + ''' + + self.updateProgressFullStep("Sorting database") + + ''' + # Sort titles case-insensitive, by author + self.booksByAuthor = sorted(self.booksByTitle, + key=lambda x:(x['author_sort'].upper(), x['author_sort'].upper())) + ''' + + self.booksByAuthor = list(self.booksByTitle) + self.booksByAuthor.sort(self.author_compare) + + if False and self.verbose: + self.opts.log.info("fetchBooksByAuthor(): %d books" % len(self.booksByAuthor)) + self.opts.log.info(" %-30s %-20s %s" % ('title', 'series', 'series_index')) + for title in self.booksByAuthor: + self.opts.log.info((u" %-30s %-20s%5s " % \ + (title['title'][:30], + title['series'][:20] if title['series'] else '', + title['series_index'], + )).encode('utf-8')) + raise SystemExit + + # Build the unique_authors set from existing data + authors = [(record['author'], record['author_sort'].capitalize()) for record in self.booksByAuthor] + + # authors[] contains a list of all book authors, with multiple entries for multiple books by author + # authors[]: (([0]:friendly [1]:sort)) + # unique_authors[]: (([0]:friendly [1]:sort [2]:book_count)) + books_by_current_author = 0 + current_author = authors[0] + multiple_authors = False + unique_authors = [] + for (i,author) in enumerate(authors): + if author != current_author: + # Note that current_author and author are tuples: (friendly, sort) + multiple_authors = True + + if author != current_author and i: + # Warn, exit if friendly matches previous, but sort doesn't + if author[0] == current_author[0]: + error_msg = _(''' +\n*** Metadata error *** +Inconsistent Author Sort values for Author '{0}', unable to continue building catalog. +Select all books by '{0}', apply correct Author Sort value in Edit Metadata dialog, +then rebuild the catalog.\n''').format(author[0]) + + self.opts.log.warn(error_msg) + self.error = error_msg + return False + + # New author, save the previous author/sort/count + unique_authors.append((current_author[0], icu_title(current_author[1]), + books_by_current_author)) + current_author = author + books_by_current_author = 1 + elif i==0 and len(authors) == 1: + # Allow for single-book lists + unique_authors.append((current_author[0], icu_title(current_author[1]), + books_by_current_author)) + else: + books_by_current_author += 1 + else: + # Add final author to list or single-author dataset + if (current_author == author and len(authors) > 1) or not multiple_authors: + unique_authors.append((current_author[0], icu_title(current_author[1]), + books_by_current_author)) + + if False and self.verbose: + self.opts.log.info("\nfetchBooksByauthor(): %d unique authors" % len(unique_authors)) + for author in unique_authors: + self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20], + author[2])).encode('utf-8')) + + self.authors = unique_authors + return True + def fetchBooksByTitle(self): self.updateProgressFullStep("Fetching database") @@ -1562,90 +1646,9 @@ class EPUB_MOBI(CatalogPlugin): title['title_sort'][0:40])).decode('mac-roman')) return True else: + self.error = _("No books found to catalog.\nCheck 'Excluded books' criteria in E-book options.") return False - def fetchBooksByAuthor(self): - ''' - Generate a list of titles sorted by author from the database - return = Success - ''' - - self.updateProgressFullStep("Sorting database") - - ''' - # Sort titles case-insensitive, by author - self.booksByAuthor = sorted(self.booksByTitle, - key=lambda x:(x['author_sort'].upper(), x['author_sort'].upper())) - ''' - - self.booksByAuthor = list(self.booksByTitle) - self.booksByAuthor.sort(self.author_compare) - - if False and self.verbose: - self.opts.log.info("fetchBooksByAuthor(): %d books" % len(self.booksByAuthor)) - self.opts.log.info(" %-30s %-20s %s" % ('title', 'series', 'series_index')) - for title in self.booksByAuthor: - self.opts.log.info((u" %-30s %-20s%5s " % \ - (title['title'][:30], - title['series'][:20] if title['series'] else '', - title['series_index'], - )).encode('utf-8')) - raise SystemExit - - # Build the unique_authors set from existing data - authors = [(record['author'], record['author_sort'].capitalize()) for record in self.booksByAuthor] - - # authors[] contains a list of all book authors, with multiple entries for multiple books by author - # authors[]: (([0]:friendly [1]:sort)) - # unique_authors[]: (([0]:friendly [1]:sort [2]:book_count)) - books_by_current_author = 0 - current_author = authors[0] - multiple_authors = False - unique_authors = [] - for (i,author) in enumerate(authors): - if author != current_author: - # Note that current_author and author are tuples: (friendly, sort) - multiple_authors = True - - if author != current_author and i: - # Warn, exit if friendly matches previous, but sort doesn't - if author[0] == current_author[0]: - error_msg = _(''' -\n*** Metadata error *** -Inconsistent Author Sort values for Author '{0}', unable to continue building catalog. -Select all books by '{0}', apply correct Author Sort value in Edit Metadata dialog, -then rebuild the catalog. -*** Terminating catalog generation ***\n''').format(author[0]) - - self.opts.log.warn(error_msg) - return False - - # New author, save the previous author/sort/count - unique_authors.append((current_author[0], icu_title(current_author[1]), - books_by_current_author)) - current_author = author - books_by_current_author = 1 - elif i==0 and len(authors) == 1: - # Allow for single-book lists - unique_authors.append((current_author[0], icu_title(current_author[1]), - books_by_current_author)) - else: - books_by_current_author += 1 - else: - # Add final author to list or single-author dataset - if (current_author == author and len(authors) > 1) or not multiple_authors: - unique_authors.append((current_author[0], icu_title(current_author[1]), - books_by_current_author)) - - if False and self.verbose: - self.opts.log.info("\nfetchBooksByauthor(): %d unique authors" % len(unique_authors)) - for author in unique_authors: - self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20], - author[2])).encode('utf-8')) - - self.authors = unique_authors - return True - def fetchBookmarks(self): ''' Collect bookmarks for catalog entries @@ -5069,6 +5072,8 @@ then rebuild the catalog. abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() - return 0 + # returns to gui2.actions.catalog:catalog_generated() + return None else: - return 1 + # returns to gui2.actions.catalog:catalog_generated() + return catalog.error diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 43891a64c4..31f5e73689 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -693,8 +693,12 @@ def command_catalog(args, dbpath): } with plugin: - plugin.run(args[1], opts, get_db(dbpath, opts)) - return 0 + ret = plugin.run(args[1], opts, get_db(dbpath, opts)) + if ret is None: + ret = 0 + else: + ret = 1 + return ret # end of GR additions diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index df094347b8..33593e93fe 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -690,10 +690,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): mi = Metadata(None) aut_list = row[fm['au_map']] - if not aut_list: - aut_list = [] + if aut_list: + aut_list = [p.split(':::') for p in aut_list.split(':#:') if p] else: - aut_list = [p.split(':::') for p in aut_list.split(':#:')] + aut_list = [] aum = [] aus = {} for (author, author_sort) in aut_list: diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 2c0d2a6173..0e8c101620 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -437,6 +437,15 @@ My antivirus program claims |app| is a virus/trojan? Your antivirus program is wrong. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing |app|, disable it temporarily, install |app| and then re-enable it. +How do I backup |app|? +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The most important thing to backup is the |app| library folder, that contains all your books and metadata. This is the folder you chose for your |app| library when you ran |app| for the first time. You can get the path to the library folder by clicking the |app| icon on the main toolbar. You must backup this complete folder with all its files and sub-folders. + +You can switch |app| to using a backed up library folder by simply clicking the |app| icon on the toolbar and choosing your backup library folder. + +If you want to backup the |app| configuration/plugins, you have to backup the config directory. You can find this config directory via :guilabel:`Preferences->Miscellaneous`. Note that restoring configuration directories is not officially supported, but should work in most cases. Just copy the contents of the backup directory into the current configuration directory to restore. + How do I use purchased EPUB books with |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Most purchased EPUB books have `DRM `_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your e-book reader. First, you must authorize your reader on a windows machine with Adobe Digital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. When you purchase an epub book from a website, you will get an ".acsm" file. This file should be opened with Adobe Digital Editions, which will then download the actual ".epub" e-book. The e-book file will be stored in the folder "My Digital Editions", from where you can add it to |app|.