Wrap the basic win32 file apis

Want to eliminate use of pywin32 gradually
This commit is contained in:
Kovid Goyal 2020-10-08 09:45:16 +05:30
parent 003a22b47c
commit 0341bf40db
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 222 additions and 27 deletions

View File

@ -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

View File

@ -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):

View File

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

View File

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