Make atomic_rename() work on windows systems with filenames that cannot be encoded using the system codepage

This commit is contained in:
Kovid Goyal 2018-01-18 18:14:58 +05:30
parent f258b3d53e
commit ece8f680c3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 42 additions and 11 deletions

View File

@ -3,12 +3,16 @@ Make strings safe for use as ASCII filenames, while trying to preserve as much
meaning as possible. meaning as possible.
''' '''
import os, errno, time, shutil import errno
import os
import shutil
import time
from math import ceil from math import ceil
from calibre import sanitize_file_name, isbytestring, force_unicode, prints from calibre import force_unicode, isbytestring, prints, sanitize_file_name
from calibre.constants import (preferred_encoding, iswindows, from calibre.constants import (
filesystem_encoding) filesystem_encoding, iswindows, plugins, preferred_encoding
)
from calibre.utils.localization import get_udc from calibre.utils.localization import get_udc
@ -476,15 +480,30 @@ def nlinks_file(path):
return os.stat(path).st_nlink 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): def atomic_rename(oldpath, newpath):
'''Replace the file newpath with the file oldpath. Can fail if the files '''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 are on different volumes. If succeeds, guaranteed to be atomic. newpath may
or may not exist. If it exists, it is replaced. ''' or may not exist. If it exists, it is replaced. '''
if iswindows: if iswindows:
import win32file
for i in xrange(10): for i in xrange(10):
try: try:
win32file.MoveFileEx(oldpath, newpath, win32file.MOVEFILE_REPLACE_EXISTING|win32file.MOVEFILE_WRITE_THROUGH) rename_file(oldpath, newpath)
break break
except Exception: except Exception:
if i > 8: if i > 8:

View File

@ -221,7 +221,7 @@ static PyObject *
winutil_get_max_stdio(PyObject *self, PyObject *args) { winutil_get_max_stdio(PyObject *self, PyObject *args) {
return Py_BuildValue("i", _getmaxstdio()); return Py_BuildValue("i", _getmaxstdio());
} }
static PyObject * static PyObject *
winutil_set_max_stdio(PyObject *self, PyObject *args) { winutil_set_max_stdio(PyObject *self, PyObject *args) {
int num = 0; int num = 0;
@ -229,7 +229,7 @@ winutil_set_max_stdio(PyObject *self, PyObject *args) {
if (_setmaxstdio(num) == -1) return PyErr_SetFromErrno(PyExc_ValueError); if (_setmaxstdio(num) == -1) return PyErr_SetFromErrno(PyExc_ValueError);
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject * static PyObject *
winutil_getenv(PyObject *self, PyObject *args) { winutil_getenv(PyObject *self, PyObject *args) {
const wchar_t *q; const wchar_t *q;
@ -239,6 +239,15 @@ winutil_getenv(PyObject *self, PyObject *args) {
return PyUnicode_FromWideChar(ans, wcslen(ans)); 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 * static PyObject *
winutil_username(PyObject *self) { winutil_username(PyObject *self) {
wchar_t buf[UNLEN + 1] = {0}; wchar_t buf[UNLEN + 1] = {0};
@ -278,8 +287,8 @@ winutil_localeconv(PyObject *self) {
struct lconv *d = localeconv(); struct lconv *d = localeconv();
#define W(name) #name, d->_W_##name #define W(name) #name, d->_W_##name
return Py_BuildValue( return Py_BuildValue(
"{su su su su su su su su}", "{su su su su su su su su}",
W(decimal_point), W(thousands_sep), W(int_curr_symbol), W(currency_symbol), 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) W(mon_decimal_point), W(mon_thousands_sep), W(positive_sign), W(negative_sign)
); );
#undef W #undef W
@ -459,6 +468,10 @@ be a unicode string. Returns unicode strings."
"localeconv()\n\nGet the locale conventions as 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} {NULL, NULL, 0, NULL}
}; };
@ -491,4 +504,3 @@ initwinutil(void) {
PyModule_AddIntConstant(m, "CSIDL_PROFILE", CSIDL_PROFILE); PyModule_AddIntConstant(m, "CSIDL_PROFILE", CSIDL_PROFILE);
} }