From ab15a9c3f8e3718192026ad543968892d589a97e Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 14 Aug 2011 19:35:07 -0400 Subject: [PATCH 1/4] Fix bug #826038: Scene break character pattern not saved. --- src/calibre/gui2/convert/heuristics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/convert/heuristics.py b/src/calibre/gui2/convert/heuristics.py index 5e7e4aa506..67774bb2b3 100644 --- a/src/calibre/gui2/convert/heuristics.py +++ b/src/calibre/gui2/convert/heuristics.py @@ -72,10 +72,11 @@ class HeuristicsWidget(Widget, Ui_Form): return True def load_histories(self): + val = unicode(self.opt_replace_scene_breaks.currentText()) + self.opt_replace_scene_breaks.clear() self.opt_replace_scene_breaks.lineEdit().setText('') - val = unicode(self.opt_replace_scene_breaks.currentText()) rssb_hist = gprefs.get('replace_scene_breaks_history', self.rssb_defaults) if val in rssb_hist: del rssb_hist[rssb_hist.index(val)] From 482fadeb7b7890dc59c9ddab0c69fdbef5fc5678 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 14 Aug 2011 17:45:38 -0600 Subject: [PATCH 2/4] ... --- src/calibre/gui2/preferences/adding.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/adding.ui b/src/calibre/gui2/preferences/adding.ui index 4a0d01be73..dae050b7ea 100644 --- a/src/calibre/gui2/preferences/adding.ui +++ b/src/calibre/gui2/preferences/adding.ui @@ -130,7 +130,7 @@ Author matching is exact. - When &copying books from one library to another, preserve the date + When using the "&Copy to library" action to copy books between libraries, preserve the date From a61f1387b09e1a9ea36ac1150d8b7c9a64b7eedd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 14 Aug 2011 19:03:21 -0600 Subject: [PATCH 3/4] Fix #825706 (Private bug) --- src/calibre/devices/usbms/cli.py | 8 +-- src/calibre/devices/usbms/driver.py | 4 +- src/calibre/utils/filenames.py | 85 ++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/calibre/devices/usbms/cli.py b/src/calibre/devices/usbms/cli.py index 1554d6fce0..4ff9efef8b 100644 --- a/src/calibre/devices/usbms/cli.py +++ b/src/calibre/devices/usbms/cli.py @@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en' import os, shutil, time from calibre.devices.errors import PathError +from calibre.utils.filenames import case_preserving_open_file class File(object): @@ -46,10 +47,8 @@ class CLI(object): path = os.path.join(path, infile.name) if not replace_file and os.path.exists(path): raise PathError('File already exists: ' + path) - d = os.path.dirname(path) - if not os.path.exists(d): - os.makedirs(d) - with open(path, 'w+b') as dest: + dest, actual_path = case_preserving_open_file(path) + with dest: try: shutil.copyfileobj(infile, dest) except IOError: @@ -62,6 +61,7 @@ class CLI(object): #if not check_transfer(infile, dest): raise Exception('Transfer failed') if close: infile.close() + return actual_path def munge_path(self, path): if path.startswith('/') and not (path.startswith(self._main_prefix) or \ diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 89531ec057..e09876081b 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -258,10 +258,10 @@ class USBMS(CLI, Device): for i, infile in enumerate(files): mdata, fname = metadata.next(), names.next() filepath = self.normalize_path(self.create_upload_path(path, mdata, fname)) - paths.append(filepath) if not hasattr(infile, 'read'): infile = self.normalize_path(infile) - self.put_file(infile, filepath, replace_file=True) + filepath = self.put_file(infile, filepath, replace_file=True) + paths.append(filepath) try: self.upload_cover(os.path.dirname(filepath), os.path.splitext(os.path.basename(filepath))[0], diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 8c6daa5adf..61a0b8e398 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -3,11 +3,12 @@ Make strings safe for use as ASCII filenames, while trying to preserve as much meaning as possible. ''' -import os +import os, errno from math import ceil -from calibre import sanitize_file_name -from calibre.constants import preferred_encoding, iswindows +from calibre import sanitize_file_name, isbytestring, force_unicode +from calibre.constants import (preferred_encoding, iswindows, + filesystem_encoding) from calibre.utils.localization import get_udc def ascii_text(orig): @@ -114,3 +115,81 @@ def is_case_sensitive(path): os.remove(f1) return is_case_sensitive +def case_preserving_open_file(path, mode='wb', mkdir_mode=0777): + ''' + Open the file pointed to by path with the specified mode. If any + directories in path do not exist, they are created. Returns the + opened file object and the path to the opened file object. This path is + guaranteed to have the same case as the on disk path. For case insensitive + filesystems, the returned path may be different from the passed in path. + The returned path is always unicode and always an absolute path. + + If mode is None, then this function assumes that path points to a directory + and return the path to the directory as the file object. + + mkdir_mode specifies the mode with which any missing directories in path + are created. + ''' + if isbytestring(path): + path = path.decode(filesystem_encoding) + + path = os.path.abspath(path) + + sep = force_unicode(os.sep, 'ascii') + + if path.endswith(sep): + path = path[:-1] + if not path: + raise ValueError('Path must not point to root') + + components = path.split(sep) + if not components: + raise ValueError('Invalid path: %r'%path) + + cpath = sep + if iswindows: + # Always upper case the drive letter and add a trailing slash so that + # the first os.listdir works correctly + cpath = components[0].upper() + sep + + # Create all the directories in path, putting the on disk case version of + # the directory into cpath + dirs = components[1:] if mode is None else components[1:-1] + for comp in dirs: + cdir = os.path.join(cpath, comp) + try: + os.mkdir(cdir, mkdir_mode) + except OSError as e: + if e.errno != errno.EEXIST: + if not os.path.exists(cdir): + # Check for exists again, as we could have got a permission + # denied error + raise + # This component already exists, ensure the case is correct + cl = comp.lower() + candidates = [c for c in os.listdir(cpath) if c.lower() == cl] + if len(candidates) == 1: + cdir = os.path.join(cpath, candidates[0]) + # else: We are on a case sensitive file system so cdir must already + # be correct + cpath = cdir + + if mode is None: + ans = fpath = cpath + else: + fname = components[-1] + ans = open(os.path.join(cpath, fname), mode) + # Ensure file and all its metadata is written to disk so that subsequent + # listdir() has file name in it. I don't know if this is actually + # necessary, but given the diversity of platforms, best to be safe. + ans.flush() + os.fsync(ans.fileno()) + + cl = fname.lower() + candidates = [c for c in os.listdir(cpath) if c.lower() == cl] + if len(candidates) == 1: + fpath = os.path.join(cpath, candidates[0]) + else: + fpath = ans.name + return ans, fpath + From 916af1da07393f442d683c5d86377afc082271ae Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Sun, 14 Aug 2011 18:11:43 -0700 Subject: [PATCH 4/4] Added djv, xps, and oxps to list of known book formats. --- src/calibre/ebooks/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py index 50ad2b0b50..c2e338ea10 100644 --- a/src/calibre/ebooks/__init__.py +++ b/src/calibre/ebooks/__init__.py @@ -28,8 +28,9 @@ class ParserError(ValueError): BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'txtz', 'text', 'htm', 'xhtm', 'html', 'htmlz', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc', - 'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip', - 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb'] + 'epub', 'fb2', 'djv', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip', + 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb', + 'xps', 'oxps'] class HTMLRenderer(object):