diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index b541d53b4b..26ac7b8a26 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -180,6 +180,39 @@ class BuildTest(unittest.TestCase): if isinstance(fmt, bytes): fmt = fmt.decode('ascii') self.assertEqual(unicode_type(time.strftime(fmt.replace('%e', '%#d'), t)), x) + tdir = winutil.temp_path() + path = os.path.join(tdir, 'test-create-file.txt') + h = winutil.create_file( + path, winutil.GENERIC_READ | winutil.GENERIC_WRITE, 0, winutil.OPEN_ALWAYS, winutil.FILE_ATTRIBUTE_NORMAL) + winutil.close_handle(h) + self.assertRaises(OSError, winutil.close_handle, h) + winutil.delete_file(path) + self.assertRaises(OSError, winutil.delete_file, path) + self.assertRaises(OSError, winutil.create_file, + os.path.join(path, 'cannot'), winutil.GENERIC_READ, 0, winutil.OPEN_ALWAYS, winutil.FILE_ATTRIBUTE_NORMAL) + sz = 23 + data = os.urandom(sz) + open(path, 'wb').write(data) + h = winutil.create_file( + path, winutil.GENERIC_READ | winutil.GENERIC_WRITE, 0, winutil.OPEN_ALWAYS, winutil.FILE_ATTRIBUTE_NORMAL) + self.assertEqual(winutil.get_file_size(h), sz) + self.assertRaises(OSError, winutil.set_file_pointer, h, 23, 23) + self.assertEqual(winutil.read_file(h), data) + self.assertEqual(winutil.read_file(h), b'') + winutil.set_file_pointer(h, 3) + self.assertEqual(winutil.read_file(h), data[3:]) + winutil.close_handle(h) + self.assertEqual(winutil.nlinks(path), 1) + npath = path + '.2' + winutil.create_hard_link(npath, path) + self.assertEqual(open(npath, 'rb').read(), data) + self.assertEqual(winutil.nlinks(path), 2) + winutil.delete_file(path) + self.assertEqual(winutil.nlinks(npath), 1) + winutil.set_file_attributes(npath, winutil.FILE_ATTRIBUTE_READONLY) + self.assertRaises(OSError, winutil.delete_file, npath) + winutil.set_file_attributes(npath, winutil.FILE_ATTRIBUTE_NORMAL) + winutil.delete_file(npath) def test_sqlite(self): import sqlite3 diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index e7dbbeb317..a717ffacb0 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -219,34 +219,10 @@ def case_preserving_open_file(path, mode='wb', mkdir_mode=0o777): return ans, fpath -def old_windows_get_fileid(path): - # we dont use this anymore as the win32 implementation reads the windows - # registry to convert file times which is slow and breaks on systems with - # registry issues. - import win32file - from pywintypes import error - if isbytestring(path): - path = path.decode(filesystem_encoding) - try: - h = win32file.CreateFileW(path, 0, 0, None, win32file.OPEN_EXISTING, - win32file.FILE_FLAG_BACKUP_SEMANTICS, 0) - try: - data = win32file.GetFileInformationByHandle(h) - finally: - win32file.CloseHandle(h) - except (error, EnvironmentError): - return None - return data[4], data[8], data[9] - - def windows_get_fileid(path): ''' The fileid uniquely identifies actual file contents (it is the same for all hardlinks to a file). Similar to inode number on linux. ''' - try: - get_file_id = plugins['winutil'][0].get_file_id - except AttributeError: - # running from source without updating binary - return old_windows_get_fileid(path) + get_file_id = plugins['winutil'][0].get_file_id if isbytestring(path): path = path.decode(filesystem_encoding) with suppress(OSError): diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index 70d7d158b5..927551739a 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -369,7 +369,14 @@ winutil_strftime(PyObject *self, PyObject *args) return NULL; } -static char winutil_doc[] = "Defines utility methods to interface with windows."; +static PyObject* +winutil_close_handle(PyObject *self, PyObject *pyhandle) { + if (!PyLong_Check(pyhandle)) { PyErr_SetString(PyExc_TypeError, "handle must be an int"); return NULL; } + if (!CloseHandle(PyLong_AsVoidPtr(pyhandle))) return PyErr_SetFromWindowsErr(0); + Py_RETURN_NONE; +} + +static const char winutil_doc[] = "Defines utility methods to interface with windows."; extern PyObject *winutil_add_to_recent_docs(PyObject *self, PyObject *args); extern PyObject *winutil_file_association(PyObject *self, PyObject *args); extern PyObject *winutil_friendly_name(PyObject *self, PyObject *args); @@ -377,6 +384,14 @@ extern PyObject *winutil_notify_associations_changed(PyObject *self, PyObject *a extern PyObject *winutil_move_to_trash(PyObject *self, PyObject *args); extern PyObject *winutil_manage_shortcut(PyObject *self, PyObject *args); extern PyObject *winutil_get_file_id(PyObject *self, PyObject *args); +extern PyObject *winutil_create_file(PyObject *self, PyObject *args); +extern PyObject *winutil_delete_file(PyObject *self, PyObject *args); +extern PyObject *winutil_create_hard_link(PyObject *self, PyObject *args); +extern PyObject *winutil_nlinks(PyObject *self, PyObject *args); +extern PyObject *winutil_set_file_attributes(PyObject *self, PyObject *args); +extern PyObject *winutil_get_file_size(PyObject *self, PyObject *args); +extern PyObject *winutil_set_file_pointer(PyObject *self, PyObject *args); +extern PyObject *winutil_read_file(PyObject *self, PyObject *args); static PyMethodDef winutil_methods[] = { {"special_folder_path", winutil_folder_path, METH_VARARGS, @@ -472,6 +487,42 @@ be a unicode string. Returns unicode strings." "get_file_id(path)\n\nGet the windows file id (volume_num, file_index_high, file_index_low)" }, + {"create_file", (PyCFunction)winutil_create_file, METH_VARARGS, + "create_file(path, desired_access, share_mode, creation_disposition, flags_and_attributes)\n\nWrapper for CreateFile" + }, + + {"get_file_size", (PyCFunction)winutil_get_file_size, METH_O, + "get_file_size(handle)\n\nWrapper for GetFileSizeEx" + }, + + {"set_file_pointer", (PyCFunction)winutil_set_file_pointer, METH_VARARGS, + "set_file_pointer(handle, pos, method=FILE_BEGIN)\n\nWrapper for SetFilePointer" + }, + + {"read_file", (PyCFunction)winutil_read_file, METH_VARARGS, + "set_file_pointer(handle, chunk_size=16KB)\n\nWrapper for ReadFile" + }, + + {"close_handle", (PyCFunction)winutil_close_handle, METH_O, + "close_handle(handle)\n\nWrapper for CloseHandle" + }, + + {"delete_file", (PyCFunction)winutil_delete_file, METH_VARARGS, + "delete_file(path)\n\nWrapper for DeleteFile" + }, + + {"create_hard_link", (PyCFunction)winutil_create_hard_link, METH_VARARGS, + "create_hard_link(path, existing_path)\n\nWrapper for CreateHardLink" + }, + + {"nlinks", (PyCFunction)winutil_nlinks, METH_VARARGS, + "nlinks(path)\n\nReturn the number of hardlinks" + }, + + {"set_file_attributes", (PyCFunction)winutil_set_file_attributes, METH_VARARGS, + "set_file_attributes(path, attrs)\n\nWrapper for SetFileAttributes" + }, + {NULL, NULL, 0, NULL} }; @@ -514,6 +565,27 @@ CALIBRE_MODINIT_FUNC PyInit_winutil(void) { PyModule_AddIntConstant(m, "CSIDL_PROFILE", CSIDL_PROFILE); PyModule_AddIntConstant(m, "CSIDL_STARTUP", CSIDL_STARTUP); PyModule_AddIntConstant(m, "CSIDL_COMMON_STARTUP", CSIDL_COMMON_STARTUP); + PyModule_AddIntConstant(m, "CREATE_NEW", CREATE_NEW); + PyModule_AddIntConstant(m, "CREATE_ALWAYS", CREATE_ALWAYS); + PyModule_AddIntConstant(m, "OPEN_EXISTING", OPEN_EXISTING); + PyModule_AddIntConstant(m, "OPEN_ALWAYS", OPEN_ALWAYS); + PyModule_AddIntConstant(m, "TRUNCATE_EXISTING", TRUNCATE_EXISTING); + PyModule_AddIntConstant(m, "FILE_SHARE_READ", FILE_SHARE_READ); + PyModule_AddIntConstant(m, "FILE_SHARE_WRITE", FILE_SHARE_WRITE); + PyModule_AddIntConstant(m, "FILE_SHARE_DELETE", FILE_SHARE_DELETE); + PyModule_AddIntConstant(m, "FILE_SHARE_VALID_FLAGS", FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE); + PyModule_AddIntConstant(m, "FILE_ATTRIBUTE_READONLY", FILE_ATTRIBUTE_READONLY); + PyModule_AddIntConstant(m, "FILE_ATTRIBUTE_NORMAL", FILE_ATTRIBUTE_NORMAL); + PyModule_AddIntConstant(m, "FILE_ATTRIBUTE_TEMPORARY", FILE_ATTRIBUTE_TEMPORARY); + PyModule_AddIntConstant(m, "FILE_FLAG_DELETE_ON_CLOSE", FILE_FLAG_DELETE_ON_CLOSE); + PyModule_AddIntConstant(m, "FILE_FLAG_SEQUENTIAL_SCAN", FILE_FLAG_SEQUENTIAL_SCAN); + PyModule_AddIntConstant(m, "FILE_FLAG_RANDOM_ACCESS", FILE_FLAG_RANDOM_ACCESS); + PyModule_AddIntConstant(m, "GENERIC_READ", GENERIC_READ); + PyModule_AddIntConstant(m, "GENERIC_WRITE", GENERIC_WRITE); + PyModule_AddIntConstant(m, "DELETE", DELETE); + PyModule_AddIntConstant(m, "FILE_BEGIN", FILE_BEGIN); + PyModule_AddIntConstant(m, "FILE_CURRENT", FILE_CURRENT); + PyModule_AddIntConstant(m, "FILE_END", FILE_END); return m; } diff --git a/src/calibre/utils/windows/winutilpp.cpp b/src/calibre/utils/windows/winutilpp.cpp index 24f88f16dc..f8f02cf538 100644 --- a/src/calibre/utils/windows/winutilpp.cpp +++ b/src/calibre/utils/windows/winutilpp.cpp @@ -131,16 +131,108 @@ py_to_wchar(PyObject *obj, wchar_raii *output) { return 0; } wchar_t *buf = PyUnicode_AsWideCharString(obj, NULL); + if (!buf) { PyErr_NoMemory(); return 0; } output->set_ptr(buf); return 1; } +static inline int +py_to_wchar_no_none(PyObject *obj, wchar_raii *output) { + if (!PyUnicode_Check(obj)) { + PyErr_SetString(PyExc_TypeError, "unicode object expected"); + return 0; + } + wchar_t *buf = PyUnicode_AsWideCharString(obj, NULL); + if (!buf) { PyErr_NoMemory(); return 0; } + output->set_ptr(buf); + return 1; +} + +static PyObject* +set_error_from_file_handle(HANDLE h) { + int error_code = GetLastError(); + wchar_t buf[4096] = {0}; + if (GetFinalPathNameByHandleW(h, buf, 4095, FILE_NAME_OPENED)) { + PyObject *fname = PyUnicode_FromWideChar(buf, -1); + if (fname) { + PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, error_code, fname); + Py_DECREF(fname); + return NULL; + } + } + return PyErr_SetFromWindowsErr(error_code); +} + extern "C" { +PyObject* +winutil_read_file(PyObject *self, PyObject *args) { + unsigned long chunk_size = 16 * 1024; + PyObject *handle; + if (!PyArg_ParseTuple(args, "O!|k", &PyLong_Type, &handle, &chunk_size)) return NULL; + PyObject *ans = PyBytes_FromStringAndSize(NULL, chunk_size); + if (!ans) return PyErr_NoMemory(); + DWORD bytes_read; + if (!ReadFile(PyLong_AsVoidPtr(handle), PyBytes_AS_STRING(ans), chunk_size, &bytes_read, NULL)) { + Py_DECREF(ans); + return set_error_from_file_handle(PyLong_AsVoidPtr(handle)); + } + if (bytes_read < chunk_size) _PyBytes_Resize(&ans, bytes_read); + return ans; +} + + +PyObject* +winutil_get_file_size(PyObject *self, PyObject *pyhandle) { + if (!PyLong_Check(pyhandle)) { PyErr_SetString(PyExc_TypeError, "handle must be an int"); return NULL; } + LARGE_INTEGER ans = {0}; + if (!GetFileSizeEx(PyLong_AsVoidPtr(pyhandle), &ans)) return set_error_from_file_handle(PyLong_AsVoidPtr(pyhandle)); + return PyLong_FromLongLong(ans.QuadPart); +} + +PyObject* +winutil_set_file_pointer(PyObject *self, PyObject *args) { + PyObject *handle; unsigned long move_method = FILE_BEGIN; + LARGE_INTEGER pos = {0}; + if (!PyArg_ParseTuple(args, "O!L|k", &PyLong_Type, &handle, &pos.QuadPart, &move_method)) return NULL; + LARGE_INTEGER ans = {0}; + if (!SetFilePointerEx(PyLong_AsVoidPtr(handle), pos, &ans, move_method)) return set_error_from_file_handle(PyLong_AsVoidPtr(handle)); + return PyLong_FromLongLong(ans.QuadPart); +} + +PyObject* +winutil_create_file(PyObject *self, PyObject *args) { + wchar_raii path; + unsigned long desired_access, share_mode, creation_disposition, flags_and_attributes; + if (!PyArg_ParseTuple(args, "O&kkkk", py_to_wchar_no_none, &path, &desired_access, &share_mode, &creation_disposition, &flags_and_attributes)) return NULL; + HANDLE h = CreateFileW( + path.ptr(), desired_access, share_mode, NULL, creation_disposition, flags_and_attributes, NULL + ); + if (h == INVALID_HANDLE_VALUE) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); + return PyLong_FromVoidPtr(h); +} + +PyObject* +winutil_delete_file(PyObject *self, PyObject *args) { + wchar_raii path; + if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &path)) return NULL; + if (!DeleteFileW(path.ptr())) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); + Py_RETURN_NONE; +} + +PyObject* +winutil_create_hard_link(PyObject *self, PyObject *args) { + wchar_raii path, existing_path; + if (!PyArg_ParseTuple(args, "O&O&", py_to_wchar_no_none, &path, py_to_wchar_no_none, &existing_path)) return NULL; + if (!CreateHardLinkW(path.ptr(), existing_path.ptr(), NULL)) return PyErr_SetExcFromWindowsErrWithFilenameObjects(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0), PyTuple_GET_ITEM(args, 1)); + Py_RETURN_NONE; +} + + PyObject* winutil_get_file_id(PyObject *self, PyObject *args) { wchar_raii path; - if (!PyArg_ParseTuple(args, "O&", py_to_wchar, &path)) return NULL; + if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &path)) return NULL; if (path.ptr()) { handle_raii file_handle(CreateFileW(path.ptr(), 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)); if (!file_handle) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); @@ -153,6 +245,28 @@ winutil_get_file_id(PyObject *self, PyObject *args) { Py_RETURN_NONE; } +PyObject* +winutil_nlinks(PyObject *self, PyObject *args) { + wchar_raii path; + if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &path)) return NULL; + handle_raii file_handle(CreateFileW(path.ptr(), 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)); + if (!file_handle) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); + BY_HANDLE_FILE_INFORMATION info = {0}; + BOOL ok = GetFileInformationByHandle(file_handle.ptr(), &info); + if (!ok) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); + unsigned long ans = info.nNumberOfLinks; + return PyLong_FromUnsignedLong(ans); +} + +PyObject* +winutil_set_file_attributes(PyObject *self, PyObject *args) { + wchar_raii path; unsigned long attrs; + if (!PyArg_ParseTuple(args, "O&k", py_to_wchar_no_none, &path, &attrs)) return NULL; + if (!SetFileAttributes(path.ptr(), attrs)) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); + Py_RETURN_NONE; +} + + PyObject * winutil_add_to_recent_docs(PyObject *self, PyObject *args) { wchar_raii path, app_id;