From ef4efd57688b3cebe801557a6fe28f9997af04a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 4 Jul 2013 11:11:22 +0530 Subject: [PATCH] Refactor creation of hardlinks on windows After creating the hardlink, open and close the file, to ensure that the directory entry for the file contains the correct file size, see http://blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx --- src/calibre/utils/filenames.py | 45 ++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 8c4b7edfe5..54ce568539 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -70,9 +70,11 @@ def shorten_components_to(length, components, more_to_take=0): else: if x is components[-1]: b, e = os.path.splitext(x) - if e == '.': e = '' + if e == '.': + e = '' r = shorten_component(b, delta)+e - if r.startswith('.'): r = x[0]+r + if r.startswith('.'): + r = x[0]+r else: r = shorten_component(x, delta) r = r.strip() @@ -115,7 +117,7 @@ def is_case_sensitive(path): os.remove(f1) return is_case_sensitive -def case_preserving_open_file(path, mode='wb', mkdir_mode=0777): +def case_preserving_open_file(path, mode='wb', mkdir_mode=0o777): ''' Open the file pointed to by path with the specified mode. If any directories in path do not exist, they are created. Returns the @@ -211,7 +213,8 @@ def samefile_windows(src, dst): handles = [] def get_fileid(x): - if isbytestring(x): x = x.decode(filesystem_encoding) + if isbytestring(x): + x = x.decode(filesystem_encoding) try: h = win32file.CreateFile(x, 0, 0, None, win32file.OPEN_EXISTING, win32file.FILE_FLAG_BACKUP_SEMANTICS, 0) @@ -254,6 +257,24 @@ def samefile(src, dst): os.path.normcase(os.path.abspath(dst))) return samestring +def windows_hardlink(src, dest): + import win32file, pywintypes + msg = u'Creating hardlink from %s to %s failed: %%s' % (src, dest) + try: + win32file.CreateHardLink(dest, src) + except pywintypes.error as e: + raise Exception(msg % e) + # 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 + h = win32file.CreateFile( + dest, 0, win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE | win32file.FILE_SHARE_DELETE, + None, win32file.OPEN_EXISTING, 0, None) + sz = win32file.GetFileSize(h) + win32file.CloseHandle(h) + + if sz != os.path.getsize(src): + raise Exception(msg % ('hardlink size: %d not the same as source size' % sz)) + class WindowsAtomicFolderMove(object): ''' @@ -270,14 +291,16 @@ class WindowsAtomicFolderMove(object): import win32file, winerror from pywintypes import error - if isbytestring(path): path = path.decode(filesystem_encoding) + if isbytestring(path): + path = path.decode(filesystem_encoding) if not os.path.exists(path): return for x in os.listdir(path): f = os.path.normcase(os.path.abspath(os.path.join(path, x))) - if not os.path.isfile(f): continue + if not os.path.isfile(f): + continue try: # Ensure the file is not read-only win32file.SetFileAttributes(f, win32file.FILE_ATTRIBUTE_NORMAL) @@ -315,9 +338,7 @@ class WindowsAtomicFolderMove(object): else: raise ValueError(u'The file %r does not exist'%path) try: - win32file.CreateHardLink(dest, path) - if os.path.getsize(dest) != os.path.getsize(path): - raise Exception('This apparently can happen on network shares. Sigh.') + windows_hardlink(path, dest) return except: pass @@ -355,10 +376,8 @@ class WindowsAtomicFolderMove(object): def hardlink_file(src, dest): if iswindows: - import win32file - win32file.CreateHardLink(dest, src) - if os.path.getsize(dest) != os.path.getsize(src): - raise Exception('This apparently can happen on network shares. Sigh.') + windows_hardlink(src, dest) return os.link(src, dest) +