Move various win API calls into native code

Faster, more robust. Should fix #1728196
This commit is contained in:
Kovid Goyal 2017-10-28 13:07:38 +05:30
parent 7357c7d7da
commit b6312f1f12
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 126 additions and 32 deletions

View File

@ -163,7 +163,7 @@
"name": "winutil", "name": "winutil",
"only": "windows", "only": "windows",
"sources": "calibre/utils/windows/winutil.c", "sources": "calibre/utils/windows/winutil.c",
"libraries": "shell32 wininet", "libraries": "shell32 wininet advapi32",
"cflags": "/X" "cflags": "/X"
}, },
{ {

View File

@ -281,6 +281,10 @@ def get_portable_base():
def get_unicode_windows_env_var(name): def get_unicode_windows_env_var(name):
winutil = plugins['winutil'][0]
getenv = getattr(winutil, 'getenv', None)
if getenv is not None:
return getenv(unicode(name))
import ctypes import ctypes
name = unicode(name) name = unicode(name)
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0) n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
@ -293,19 +297,26 @@ def get_unicode_windows_env_var(name):
def get_windows_username(): def get_windows_username():
''' '''
Return the user name of the currently loggen in user as a unicode string. Return the user name of the currently logged in user as a unicode string.
Note that usernames on windows are case insensitive, the case of the value Note that usernames on windows are case insensitive, the case of the value
returned depends on what the user typed into the login box at login time. returned depends on what the user typed into the login box at login time.
''' '''
winutil = plugins['winutil'][0]
username = getattr(winutil, 'username', None)
if username is not None:
return username()
import ctypes import ctypes
from ctypes import wintypes
try: try:
advapi32 = ctypes.windll.advapi32 advapi32 = ctypes.windll.advapi32
GetUserName = getattr(advapi32, u'GetUserNameW') GetUserName = getattr(advapi32, u'GetUserNameW')
GetUserName.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)]
GetUserName.restype = wintypes.BOOL
except AttributeError: except AttributeError:
pass pass
else: else:
buf = ctypes.create_unicode_buffer(257) buf = ctypes.create_unicode_buffer(257)
n = ctypes.c_int(257) n = wintypes.DWORD(257)
if GetUserName(buf, ctypes.byref(n)): if GetUserName(buf, ctypes.byref(n)):
return buf.value return buf.value
@ -313,6 +324,10 @@ def get_windows_username():
def get_windows_temp_path(): def get_windows_temp_path():
winutil = plugins['winutil'][0]
temp_path = getattr(winutil, 'temp_path', None)
if temp_path is not None:
return temp_path()
import ctypes import ctypes
n = ctypes.windll.kernel32.GetTempPathW(0, None) n = ctypes.windll.kernel32.GetTempPathW(0, None)
if n == 0: if n == 0:
@ -324,6 +339,10 @@ def get_windows_temp_path():
def get_windows_user_locale_name(): def get_windows_user_locale_name():
winutil = plugins['winutil'][0]
locale_name = getattr(winutil, 'locale_name', None)
if locale_name is not None:
return locale_name()
import ctypes import ctypes
k32 = ctypes.windll.kernel32 k32 = ctypes.windll.kernel32
n = 255 n = 255
@ -334,32 +353,17 @@ def get_windows_user_locale_name():
return u'_'.join(buf.value.split(u'-')[:2]) return u'_'.join(buf.value.split(u'-')[:2])
number_formats = None
def get_windows_number_formats(): def get_windows_number_formats():
# This can be changed to use localeconv() once we switch to Visual Studio ans = getattr(get_windows_number_formats, 'ans', None)
# 2015 as localeconv() in that version has unicode variants for all strings. if ans is None:
global number_formats winutil = plugins['winutil'][0]
if number_formats is None: localeconv = getattr(winutil, 'localeconv', None)
import ctypes if localeconv is not None:
from ctypes.wintypes import DWORD d = localeconv()
k32 = ctypes.windll.kernel32 thousands_sep, decimal_point = d['thousands_sep'], d['decimal_point']
n = 25 else:
buf = ctypes.create_unicode_buffer(u'\0'*n) from locale import localeconv
k32.GetNumberFormatEx.argtypes = [ctypes.c_wchar_p, DWORD, ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int] d = localeconv()
k32.GetNumberFormatEx.restype = ctypes.c_int thousands_sep, decimal_point = d['thousands_sep'].decode('mbcs'), d['decimal_point'].decode('mbcs')
if k32.GetNumberFormatEx(None, 0, u'123456.7', None, buf, n) == 0: ans = get_windows_number_formats.ans = thousands_sep, decimal_point
raise ctypes.WinError() return ans
src = buf.value
thousands_sep, decimal_point = u',.'
idx = src.find(u'6')
if idx > -1 and src[idx+1] != u'7':
decimal_point = src[idx+1]
src = src[:idx]
for c in src:
if c not in u'123456':
thousands_sep = c
break
number_formats = (thousands_sep, decimal_point)
return number_formats

View File

@ -121,8 +121,20 @@ class BuildTest(unittest.TestCase):
def test_winutil(self): def test_winutil(self):
from calibre.constants import plugins from calibre.constants import plugins
winutil = plugins['winutil'][0] winutil = plugins['winutil'][0]
def au(x, name):
self.assertTrue(isinstance(x, unicode), name + '() did not return a unicode string')
for x in winutil.argv(): for x in winutil.argv():
self.assertTrue(isinstance(x, unicode), 'argv() not returning unicode string') au(x, 'argv')
for x in 'username temp_path locale_name'.split():
au(getattr(winutil, x)(), x)
d = winutil.localeconv()
au(d['thousands_sep'], 'localeconv')
au(d['decimal_point'], 'localeconv')
for k, v in d.iteritems():
au(v, k)
for k in os.environ.keys():
au(winutil.getenv(unicode(k)), 'getenv-' + k)
def test_sqlite(self): def test_sqlite(self):
import sqlite3 import sqlite3

View File

@ -42,6 +42,8 @@ wherever possible in this module.
#define UNICODE #define UNICODE
#include <Windows.h> #include <Windows.h>
#include <Wininet.h> #include <Wininet.h>
#include <LMcons.h>
#include <locale.h>
#include <Python.h> #include <Python.h>
#include <structseq.h> #include <structseq.h>
#include <timefuncs.h> #include <timefuncs.h>
@ -228,6 +230,62 @@ winutil_set_max_stdio(PyObject *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject *
winutil_getenv(PyObject *self, PyObject *args) {
const wchar_t *q;
if (!PyArg_ParseTuple(args, "u", &q)) return NULL;
wchar_t *ans = _wgetenv(q);
if (ans == NULL) Py_RETURN_NONE;
return PyUnicode_FromWideChar(ans, wcslen(ans));
}
static PyObject *
winutil_username(PyObject *self) {
wchar_t buf[UNLEN + 1] = {0};
DWORD sz = sizeof(buf)/sizeof(buf[0]);
if (!GetUserName(buf, &sz)) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return PyUnicode_FromWideChar(buf, wcslen(buf));
}
static PyObject *
winutil_temp_path(PyObject *self) {
wchar_t buf[MAX_PATH + 1] = {0};
DWORD sz = sizeof(buf)/sizeof(buf[0]);
if (!GetTempPath(sz, buf)) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return PyUnicode_FromWideChar(buf, wcslen(buf));
}
static PyObject *
winutil_locale_name(PyObject *self) {
wchar_t buf[LOCALE_NAME_MAX_LENGTH + 1] = {0};
if (!GetUserDefaultLocaleName(buf, sizeof(buf)/sizeof(buf[0]))) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return PyUnicode_FromWideChar(buf, wcslen(buf));
}
static PyObject *
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),
W(mon_decimal_point), W(mon_thousands_sep), W(positive_sign), W(negative_sign)
);
#undef W
}
static PyObject * static PyObject *
winutil_strftime(PyObject *self, PyObject *args) winutil_strftime(PyObject *self, PyObject *args)
{ {
@ -381,6 +439,26 @@ be a unicode string. Returns unicode strings."
"setmaxstdio(num)\n\nSet the maximum number of open file handles." "setmaxstdio(num)\n\nSet the maximum number of open file handles."
}, },
{"getenv", (PyCFunction)winutil_getenv, METH_VARARGS,
"getenv(name)\n\nGet the value of the specified env var as a unicode string."
},
{"username", (PyCFunction)winutil_username, METH_NOARGS,
"username()\n\nGet the current username as a unicode string."
},
{"temp_path", (PyCFunction)winutil_temp_path, METH_NOARGS,
"temp_path()\n\nGet the current temporary dir as a unicode string."
},
{"locale_name", (PyCFunction)winutil_locale_name, METH_NOARGS,
"locale_name()\n\nGet the current locale name as a unicode string."
},
{"localeconv", (PyCFunction)winutil_localeconv, METH_NOARGS,
"localeconv()\n\nGet the locale conventions as unicode strings."
},
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}
}; };