diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index 26ac7b8a26..8f1dcde6ff 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -147,6 +147,7 @@ class BuildTest(unittest.TestCase): @unittest.skipUnless(iswindows, 'winutil is windows only') def test_winutil(self): + import tempfile from calibre.constants import plugins from calibre import strftime winutil = plugins['winutil'][0] @@ -213,6 +214,41 @@ class BuildTest(unittest.TestCase): self.assertRaises(OSError, winutil.delete_file, npath) winutil.set_file_attributes(npath, winutil.FILE_ATTRIBUTE_NORMAL) winutil.delete_file(npath) + self.assertGreater(min(winutil.get_disk_free_space(None)), 0) + open(path, 'wb').close() + open(npath, 'wb').close() + winutil.move_file(path, npath, winutil.MOVEFILE_WRITE_THROUGH | winutil.MOVEFILE_REPLACE_EXISTING) + self.assertFalse(os.path.exists(path)) + os.remove(npath) + dpath = tempfile.mkdtemp(dir=os.path.dirname(path)) + dh = winutil.create_file( + dpath, winutil.FILE_LIST_DIRECTORY, winutil.FILE_SHARE_READ, winutil.OPEN_EXISTING, winutil.FILE_FLAG_BACKUP_SEMANTICS, + ) + from threading import Thread + events = [] + + def read_changes(): + buffer = b'0' * 8192 + events.extend(winutil.read_directory_changes( + dh, buffer, True, + winutil.FILE_NOTIFY_CHANGE_FILE_NAME | + winutil.FILE_NOTIFY_CHANGE_DIR_NAME | + winutil.FILE_NOTIFY_CHANGE_ATTRIBUTES | + winutil.FILE_NOTIFY_CHANGE_SIZE | + winutil.FILE_NOTIFY_CHANGE_LAST_WRITE | + winutil.FILE_NOTIFY_CHANGE_SECURITY + )) + t = Thread(target=read_changes, daemon=True) + t.start() + testp = os.path.join(dpath, 'test') + open(testp, 'w').close() + t.join(2) + self.assertTrue(events) + for actions, path in events: + self.assertEqual(os.path.join(dpath, path), testp) + winutil.close_handle(dh) + os.remove(testp) + os.rmdir(dpath) def test_sqlite(self): import sqlite3 diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c index 927551739a..3861287bf5 100644 --- a/src/calibre/utils/windows/winutil.c +++ b/src/calibre/utils/windows/winutil.c @@ -225,15 +225,6 @@ winutil_set_max_stdio(PyObject *self, PyObject *args) { Py_RETURN_NONE; } -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}; @@ -392,6 +383,9 @@ 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); +extern PyObject *winutil_get_disk_free_space(PyObject *self, PyObject *args); +extern PyObject *winutil_move_file(PyObject *self, PyObject *args); +extern PyObject *winutil_read_directory_changes(PyObject *self, PyObject *args); static PyMethodDef winutil_methods[] = { {"special_folder_path", winutil_folder_path, METH_VARARGS, @@ -503,6 +497,10 @@ be a unicode string. Returns unicode strings." "set_file_pointer(handle, chunk_size=16KB)\n\nWrapper for ReadFile" }, + {"get_disk_free_space", (PyCFunction)winutil_get_disk_free_space, METH_VARARGS, + "get_disk_free_space(path)\n\nWrapper for GetDiskFreeSpaceEx" + }, + {"close_handle", (PyCFunction)winutil_close_handle, METH_O, "close_handle(handle)\n\nWrapper for CloseHandle" }, @@ -523,6 +521,14 @@ be a unicode string. Returns unicode strings." "set_file_attributes(path, attrs)\n\nWrapper for SetFileAttributes" }, + {"move_file", (PyCFunction)winutil_move_file, METH_VARARGS, + "move_file(a, b, flags)\n\nWrapper for MoveFileEx" + }, + + {"read_directory_changes", (PyCFunction)winutil_read_directory_changes, METH_VARARGS, + "read_directory_changes(handle, buffer, subtree, flags)\n\nWrapper for ReadDirectoryChangesW" + }, + {NULL, NULL, 0, NULL} }; @@ -586,6 +592,27 @@ CALIBRE_MODINIT_FUNC PyInit_winutil(void) { PyModule_AddIntConstant(m, "FILE_BEGIN", FILE_BEGIN); PyModule_AddIntConstant(m, "FILE_CURRENT", FILE_CURRENT); PyModule_AddIntConstant(m, "FILE_END", FILE_END); + PyModule_AddIntConstant(m, "MOVEFILE_COPY_ALLOWED", MOVEFILE_COPY_ALLOWED); + PyModule_AddIntConstant(m, "MOVEFILE_CREATE_HARDLINK", MOVEFILE_CREATE_HARDLINK); + PyModule_AddIntConstant(m, "MOVEFILE_DELAY_UNTIL_REBOOT", MOVEFILE_DELAY_UNTIL_REBOOT); + PyModule_AddIntConstant(m, "MOVEFILE_FAIL_IF_NOT_TRACKABLE", MOVEFILE_FAIL_IF_NOT_TRACKABLE); + PyModule_AddIntConstant(m, "MOVEFILE_REPLACE_EXISTING", MOVEFILE_REPLACE_EXISTING); + PyModule_AddIntConstant(m, "MOVEFILE_WRITE_THROUGH", MOVEFILE_WRITE_THROUGH); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_FILE_NAME", FILE_NOTIFY_CHANGE_FILE_NAME); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_DIR_NAME", FILE_NOTIFY_CHANGE_DIR_NAME); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_ATTRIBUTES", FILE_NOTIFY_CHANGE_ATTRIBUTES); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_SIZE", FILE_NOTIFY_CHANGE_SIZE); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_LAST_WRITE", FILE_NOTIFY_CHANGE_LAST_WRITE); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_LAST_ACCESS", FILE_NOTIFY_CHANGE_LAST_ACCESS); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_CREATION", FILE_NOTIFY_CHANGE_CREATION); + PyModule_AddIntConstant(m, "FILE_NOTIFY_CHANGE_SECURITY", FILE_NOTIFY_CHANGE_SECURITY); + PyModule_AddIntConstant(m, "FILE_ACTION_ADDED", FILE_ACTION_ADDED); + PyModule_AddIntConstant(m, "FILE_ACTION_REMOVED", FILE_ACTION_REMOVED); + PyModule_AddIntConstant(m, "FILE_ACTION_MODIFIED", FILE_ACTION_MODIFIED); + PyModule_AddIntConstant(m, "FILE_ACTION_RENAMED_OLD_NAME", FILE_ACTION_RENAMED_OLD_NAME); + PyModule_AddIntConstant(m, "FILE_ACTION_RENAMED_NEW_NAME", FILE_ACTION_RENAMED_NEW_NAME); + PyModule_AddIntConstant(m, "FILE_LIST_DIRECTORY", FILE_LIST_DIRECTORY); + PyModule_AddIntConstant(m, "FILE_FLAG_BACKUP_SEMANTICS", FILE_FLAG_BACKUP_SEMANTICS); return m; } diff --git a/src/calibre/utils/windows/winutilpp.cpp b/src/calibre/utils/windows/winutilpp.cpp index f8f02cf538..124b036e86 100644 --- a/src/calibre/utils/windows/winutilpp.cpp +++ b/src/calibre/utils/windows/winutilpp.cpp @@ -165,6 +165,25 @@ set_error_from_file_handle(HANDLE h) { extern "C" { +PyObject* +winutil_move_file(PyObject *self, PyObject *args) { + wchar_raii a, b; + unsigned int flags = MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH; + if (!PyArg_ParseTuple(args, "O&O&|I", py_to_wchar_no_none, &a, py_to_wchar_no_none, &b, &flags)) return NULL; + if (!MoveFileExW(a.ptr(), b.ptr(), flags)) + return PyErr_SetExcFromWindowsErrWithFilenameObjects(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0), PyTuple_GET_ITEM(args, 1)); + Py_RETURN_NONE; +} + +PyObject* +winutil_get_disk_free_space(PyObject *self, PyObject *args) { + wchar_raii path; + if (!PyArg_ParseTuple(args, "O&", py_to_wchar, &path)) return NULL; + ULARGE_INTEGER bytes_available_to_caller, total_bytes, total_free_bytes; + if (!GetDiskFreeSpaceEx(path.ptr(), &bytes_available_to_caller, &total_bytes, &total_free_bytes)) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, PyTuple_GET_ITEM(args, 0)); + return Py_BuildValue("KKK", bytes_available_to_caller.QuadPart, total_bytes.QuadPart, total_free_bytes.QuadPart); +} + PyObject* winutil_read_file(PyObject *self, PyObject *args) { unsigned long chunk_size = 16 * 1024; @@ -181,6 +200,38 @@ winutil_read_file(PyObject *self, PyObject *args) { return ans; } +PyObject* +winutil_read_directory_changes(PyObject *self, PyObject *args) { + PyObject *buffer, *handle; int watch_subtree; unsigned long filter; + if (!PyArg_ParseTuple(args, "O!O!pk", &PyLong_Type, &handle, &PyBytes_Type, &buffer, &watch_subtree, &filter)) return NULL; + DWORD bytes_returned; + BOOL ok; + Py_BEGIN_ALLOW_THREADS; + ok = ReadDirectoryChangesW(PyLong_AsVoidPtr(handle), PyBytes_AS_STRING(buffer), (DWORD)PyBytes_GET_SIZE(buffer), watch_subtree, filter, &bytes_returned, NULL, NULL); + Py_END_ALLOW_THREADS; + if (!ok) return set_error_from_file_handle(PyLong_AsVoidPtr(handle)); + PFILE_NOTIFY_INFORMATION p; + size_t offset = 0; + PyObject *ans = PyList_New(0); + if (!ans) return NULL; + if (bytes_returned) { + do { + p = (PFILE_NOTIFY_INFORMATION)(PyBytes_AS_STRING(buffer) + offset); + offset += p->NextEntryOffset; + if (p->FileNameLength) { + PyObject *temp = Py_BuildValue("ku#", p->Action, p->FileName, p->FileNameLength / sizeof(wchar_t)); + if (!temp) { Py_DECREF(ans); return NULL; } + int ret = PyList_Append(ans, temp); + Py_DECREF(temp); + if (ret != 0) { Py_DECREF(ans); return NULL; } + } + } while(p->NextEntryOffset); + } else { + Py_CLEAR(ans); + PyErr_SetString(PyExc_OverflowError, "the change events buffer overflowed, something has changed"); + } + return ans; +} PyObject* winutil_get_file_size(PyObject *self, PyObject *pyhandle) {