From 1db24f51922f7c8975d1e3d3dbbed1f53283527c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 21 Oct 2012 22:14:31 +0530 Subject: [PATCH] Windows: Check if any of the files of a book are in use before changing the title/author, this (usually) prevents the creation of duplicate files if one of the files is open in another program --- src/calibre/gui2/metadata/basic_widgets.py | 2 +- src/calibre/library/database2.py | 12 ++++++++-- src/calibre/utils/filenames.py | 28 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 36605c7584..0da9b1bcf4 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -270,7 +270,7 @@ class AuthorsEdit(EditWithComplete): import traceback fname = err.filename if err.filename else 'file' error_dialog(self, _('Permission denied'), - _('Could not open %s. Is it being used by another' + _('Could not open "%s". Is it being used by another' ' program?')%fname, det_msg=traceback.format_exc(), show=True) return False diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 5b4f7eec7e..5b2abd0c6b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' The database used to store ebook metadata ''' import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \ - json, uuid, hashlib, copy + json, uuid, hashlib, copy, errno from collections import defaultdict import threading, random from itertools import repeat @@ -30,7 +30,8 @@ from calibre.ptempfile import (PersistentTemporaryFile, base_dir, SpooledTemporaryFile) from calibre.customize.ui import run_plugins_on_import from calibre import isbytestring -from calibre.utils.filenames import ascii_filename, samefile +from calibre.utils.filenames import (ascii_filename, samefile, + windows_is_folder_in_use) from calibre.utils.date import (utcnow, now as nowf, utcfromtimestamp, parse_only_date, UNDEFINED_DATE) from calibre.utils.config import prefs, tweaks, from_json, to_json @@ -649,6 +650,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): spath = os.path.join(self.library_path, *current_path.split('/')) if current_path and os.path.exists(spath): # Migrate existing files + if iswindows: + uf = windows_is_folder_in_use(spath) + if uf is not None: + err = IOError(errno.EACCES, + _('File is open in another process')) + err.filename = uf + raise err cdata = self.cover(id, index_is_id=True) if cdata is not None: with lopen(os.path.join(tpath, 'cover.jpg'), 'wb') as f: diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 65451dab9c..f6455831ba 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -249,4 +249,32 @@ def samefile(src, dst): os.path.normcase(os.path.abspath(dst))) return samestring +def windows_is_file_opened(path): + import win32file, winerror + from pywintypes import error + if isbytestring(path): path = path.decode(filesystem_encoding) + try: + h = win32file.CreateFile(path, win32file.GENERIC_READ, 0, None, + win32file.OPEN_EXISTING, 0, 0) + except error as e: + if getattr(e, 'winerror', 0) == winerror.ERROR_SHARING_VIOLATION: + return True + else: + win32file.CloseHandle(h) + return False + +def windows_is_folder_in_use(path): + ''' + Returns the path to a file that is used in another process in the specified + folder, or None if no such file exists. Note + that this function is not a guarantee. A file may well be opened in the + folder after this function returns. However, it is useful to handle the + common case of a sharing violation gracefully most of the time. + ''' + if isbytestring(path): path = path.decode(filesystem_encoding) + for x in os.listdir(path): + f = os.path.join(path, x) + if windows_is_file_opened(f): + return f + return None