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.
'''
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:

View File

@ -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);
}