From ece8f680c39085eee696fed4282f7e27e9cb768f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 18 Jan 2018 18:14:58 +0530 Subject: [PATCH] Make atomic_rename() work on windows systems with filenames that cannot be encoded using the system codepage --- src/calibre/utils/filenames.py | 31 +++++++++++++++++++++++------ src/calibre/utils/windows/winutil.c | 22 +++++++++++++++----- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 6d18173a8f..afb3f21aae 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -3,12 +3,16 @@ Make strings safe for use as ASCII filenames, while trying to preserve as much meaning as possible. ''' -import os, errno, time, shutil +import errno +import os +import shutil +import time from math import ceil -from calibre import sanitize_file_name, isbytestring, force_unicode, prints -from calibre.constants import (preferred_encoding, iswindows, - filesystem_encoding) +from calibre import force_unicode, isbytestring, prints, sanitize_file_name +from calibre.constants import ( + filesystem_encoding, iswindows, plugins, preferred_encoding +) from calibre.utils.localization import get_udc @@ -476,15 +480,30 @@ def nlinks_file(path): return os.stat(path).st_nlink +if iswindows: + def rename_file(a, b): + move_file = getattr(plugins['winutil'][0], 'move_file', None) + if move_file is None: + import win32file + + def mf_impl(a, b): + win32file.MoveFileEx(a, b, win32file.MOVEFILE_REPLACE_EXISTING|win32file.MOVEFILE_WRITE_THROUGH) + move_file = mf_impl + if isinstance(a, bytes): + a = a.decode('mbcs') + if isinstance(b, bytes): + b = b.decode('mbcs') + move_file(a, b) + + def atomic_rename(oldpath, newpath): '''Replace the file newpath with the file oldpath. Can fail if the files are on different volumes. If succeeds, guaranteed to be atomic. newpath may or may not exist. If it exists, it is replaced. ''' if iswindows: - import win32file for i in xrange(10): try: - win32file.MoveFileEx(oldpath, newpath, win32file.MOVEFILE_REPLACE_EXISTING|win32file.MOVEFILE_WRITE_THROUGH) + rename_file(oldpath, newpath) break except Exception: if i > 8: diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index bcad9f0c60..a1ed8e11ec 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -221,7 +221,7 @@ static PyObject * winutil_get_max_stdio(PyObject *self, PyObject *args) { return Py_BuildValue("i", _getmaxstdio()); } - + static PyObject * winutil_set_max_stdio(PyObject *self, PyObject *args) { int num = 0; @@ -229,7 +229,7 @@ winutil_set_max_stdio(PyObject *self, PyObject *args) { if (_setmaxstdio(num) == -1) return PyErr_SetFromErrno(PyExc_ValueError); Py_RETURN_NONE; } - + static PyObject * winutil_getenv(PyObject *self, PyObject *args) { const wchar_t *q; @@ -239,6 +239,15 @@ winutil_getenv(PyObject *self, PyObject *args) { return PyUnicode_FromWideChar(ans, wcslen(ans)); } +static PyObject* +winutil_move_file(PyObject *self, PyObject *args) { + Py_UNICODE *a, *b; + unsigned int flags = MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH; + if (!PyArg_ParseTuple(args, "uu|I", &a, &b, &flags)) return NULL; + if (!MoveFileExW(a, b, flags)) { PyErr_SetFromWindowsErr(0); return NULL; } + Py_RETURN_NONE; +} + static PyObject * winutil_username(PyObject *self) { wchar_t buf[UNLEN + 1] = {0}; @@ -278,8 +287,8 @@ winutil_localeconv(PyObject *self) { struct lconv *d = localeconv(); #define W(name) #name, d->_W_##name return Py_BuildValue( - "{su su su su su su su su}", - W(decimal_point), W(thousands_sep), W(int_curr_symbol), W(currency_symbol), + "{su su su su su su su su}", + W(decimal_point), W(thousands_sep), W(int_curr_symbol), W(currency_symbol), W(mon_decimal_point), W(mon_thousands_sep), W(positive_sign), W(negative_sign) ); #undef W @@ -459,6 +468,10 @@ be a unicode string. Returns unicode strings." "localeconv()\n\nGet the locale conventions as unicode strings." }, + {"move_file", (PyCFunction)winutil_move_file, METH_VARARGS, + "move_file()\n\nRename the specified file." + }, + {NULL, NULL, 0, NULL} }; @@ -491,4 +504,3 @@ initwinutil(void) { PyModule_AddIntConstant(m, "CSIDL_PROFILE", CSIDL_PROFILE); } -