diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 432efe2abf..66a4fc44ad 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -3,7 +3,7 @@ Make strings safe for use as ASCII filenames, while trying to preserve as much meaning as possible. ''' -import os, errno, time +import os, errno, time, shutil from math import ceil from calibre import sanitize_file_name, isbytestring, force_unicode, prints @@ -277,7 +277,7 @@ def windows_hardlink(src, dest): win32file.CreateHardLink(dest, src) except pywintypes.error as e: msg = u'Creating hardlink from %s to %s failed: %%s' % (src, dest) - raise Exception(msg % e) + raise OSError(msg % e) src_size = os.path.getsize(src) # We open and close dest, to ensure its directory entry is updated # see http://blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx @@ -294,7 +294,19 @@ def windows_hardlink(src, dest): sz = windows_get_size(dest) if sz != src_size: msg = u'Creating hardlink from %s to %s failed: %%s' % (src, dest) - raise Exception(msg % ('hardlink size: %d not the same as source size' % sz)) + raise OSError(msg % ('hardlink size: %d not the same as source size' % sz)) + +def windows_fast_hardlink(src, dest): + import win32file, pywintypes + try: + win32file.CreateHardLink(dest, src) + except pywintypes.error as e: + msg = u'Creating hardlink from %s to %s failed: %%s' % (src, dest) + raise OSError(msg % e) + ssz, dsz = windows_get_size(src), windows_get_size(dest) + if ssz != dsz: + msg = u'Creating hardlink from %s to %s failed: %%s' % (src, dest) + raise OSError(msg % ('hardlink size: %d not the same as source size: %s' % (dsz, ssz))) def windows_nlinks(path): import win32file @@ -527,3 +539,59 @@ def format_permissions(st_mode): if st_mode & stat.S_ISVTX: ans[9] = 't' if (st_mode & stat.S_IXUSR) else 'T' return ''.join(ans) + +def copyfile(src, dest): + shutil.copyfile(src, dest) + try: + shutil.copystat(src, dest) + except Exception: + pass + +def get_hardlink_function(src, dest): + if iswindows: + import win32file + try: + dt = win32file.GetDriveType(dest[:2]) + except Exception: + dt = win32file.DRIVE_UNKNOWN + hardlink = None if dt in (win32file.DRIVE_REMOTE, win32file.DRIVE_CDROM) else windows_fast_hardlink + hardlink = None if src[0].lower() != dest[0].lower() else hardlink + else: + hardlink = os.link + return hardlink + +def copyfile_using_links(path, dest, dest_is_dir=True, filecopyfunc=copyfile): + path, dest = os.path.abspath(path), os.path.abspath(dest) + if dest_is_dir: + dest = os.path.join(dest, os.path.basename(path)) + hardlink = get_hardlink_function(path, dest) + try: + hardlink(path, dest) + except Exception: + filecopyfunc(path, dest) + +def copytree_using_links(path, dest, dest_is_parent=True, filecopyfunc=copyfile): + path, dest = os.path.abspath(path), os.path.abspath(dest) + if dest_is_parent: + dest = os.path.join(dest, os.path.basename(path)) + hardlink = get_hardlink_function(path, dest) + try: + os.makedirs(dest) + except EnvironmentError as e: + if e.errno != errno.EEXIST: + raise + for dirpath, dirnames, filenames in os.walk(path): + base = os.path.relpath(dirpath, path) + dest_base = os.path.join(dest, base) + for dname in dirnames: + try: + os.mkdir(os.path.join(dest_base, dname)) + except EnvironmentError as e: + if e.errno != errno.EEXIST: + raise + for fname in filenames: + src, df = os.path.join(dirpath, fname), os.path.join(dest_base, fname) + try: + hardlink(src, df) + except Exception: + filecopyfunc(src, df)