Use winutil for the file dialog helper

This commit is contained in:
Kovid Goyal 2020-10-15 21:08:59 +05:30
parent e48db37839
commit c6ca0a95db
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -10,7 +10,6 @@ import sys
from threading import Thread
from uuid import uuid4
from PyQt5.Qt import QEventLoop, Qt, pyqtSignal
from polyglot.builtins import filter, string_or_bytes, unicode_type
@ -90,22 +89,15 @@ class Helper(Thread):
self.callback = callback
self.data = data
self.daemon = True
self.rc = 0
self.rc = 1
self.stdoutdata = self.stderrdata = b''
def run(self):
self.stdoutdata, self.stderrdata = self.process.communicate(b''.join(self.data))
self.rc = self.process.wait()
self.callback()
class Loop(QEventLoop):
dialog_closed = pyqtSignal()
def __init__(self):
QEventLoop.__init__(self)
self.dialog_closed.connect(self.exit, type=Qt.QueuedConnection)
try:
self.stdoutdata, self.stderrdata = self.process.communicate(b''.join(self.data))
self.rc = self.process.wait()
finally:
self.callback()
def process_path(x):
@ -178,8 +170,20 @@ def run_file_dialog(
app_uid = app_uid or current_app_uid
if app_uid:
data.append(serialize_string('APP_UID', app_uid))
from PyQt5.Qt import QEventLoop, Qt, pyqtSignal
class Loop(QEventLoop):
dialog_closed = pyqtSignal()
def __init__(self):
QEventLoop.__init__(self)
self.dialog_closed.connect(self.exit, type=Qt.QueuedConnection)
loop = Loop()
server = PipeServer(pipename)
server.start()
with sanitize_env_vars():
h = Helper(subprocess.Popen(
[HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE),
@ -293,79 +297,43 @@ def choose_save_file(window, name, title, filters=[], all_files=True, initial_pa
class PipeServer(Thread):
def __init__(self, pipename):
Thread.__init__(self, name='PipeServer')
self.daemon = True
import win32pipe, win32api, win32con
FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
PIPE_REJECT_REMOTE_CLIENTS = 0x00000008
self.pipe_handle = win32pipe.CreateNamedPipe(
pipename, win32pipe.PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_READMODE_BYTE | win32pipe.PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
1, 8192, 8192, 0, None)
win32api.SetHandleInformation(self.pipe_handle, win32con.HANDLE_FLAG_INHERIT, 0)
Thread.__init__(self, name='PipeServer', daemon=True)
from calibre_extensions import winutil
self.client_connected = False
self.pipe_handle = winutil.create_named_pipe(
pipename, winutil.PIPE_ACCESS_INBOUND | winutil.FILE_FLAG_FIRST_PIPE_INSTANCE,
winutil.PIPE_TYPE_BYTE | winutil.PIPE_READMODE_BYTE | winutil.PIPE_WAIT | winutil.PIPE_REJECT_REMOTE_CLIENTS,
1, 8192, 8192, 0)
winutil.set_handle_information(self.pipe_handle, winutil.HANDLE_FLAG_INHERIT, 0)
self.err_msg = None
self.data = b''
self.start()
def run(self):
import win32pipe, win32file, winerror, win32api
def as_unicode(err):
try:
self.err_msg = unicode_type(err)
except Exception:
self.err_msg = repr(err)
from calibre_extensions import winutil
try:
try:
rc = win32pipe.ConnectNamedPipe(self.pipe_handle)
winutil.connect_named_pipe(self.pipe_handle)
except Exception as err:
as_unicode(err)
return
if rc != 0:
self.err_msg = 'Failed to connect to client over named pipe: 0x%x' % rc
self.err_msg = f'ConnectNamedPipe failed: {err}'
return
self.client_connected = True
while True:
try:
hr, data = win32file.ReadFile(self.pipe_handle, 1024 * 50, None)
except Exception as err:
if getattr(err, 'winerror', None) == winerror.ERROR_BROKEN_PIPE:
data = winutil.read_file(self.pipe_handle, 64 * 1024)
except OSError as err:
if err.winerror == winutil.ERROR_BROKEN_PIPE:
break # pipe was closed at the other end
as_unicode(err)
break
if hr not in (winerror.ERROR_MORE_DATA, 0):
self.err_msg = 'ReadFile on pipe failed with hr=%d' % hr
break
self.err_msg = f'ReadFile on pipe failed: {err}'
if not data:
break
self.data += data
finally:
win32api.CloseHandle(self.pipe_handle)
self.pipe_handle = None
def test(helper=HELPER):
pipename = '\\\\.\\pipe\\%s' % uuid4()
echo = '\U0001f431 Hello world!'
secret = os.urandom(32).replace(b'\0', b' ')
data = serialize_string('PIPENAME', pipename) + serialize_string('ECHO', echo) + serialize_secret(secret)
server = PipeServer(pipename)
p = subprocess.Popen([helper], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate(data)
if p.wait() != 0:
raise Exception('File dialog failed: ' + stdout.decode('utf-8') + ' ' + stderr.decode('utf-8'))
if server.err_msg is not None:
raise RuntimeError(server.err_msg)
server.join(2)
parts = list(filter(None, server.data.split(b'\0')))
if parts[0] != secret:
raise RuntimeError('Did not get back secret: %r != %r' % (secret, parts[0]))
q = parts[1].decode('utf-8')
if q != echo:
raise RuntimeError('Unexpected response: %r' % server.data)
if __name__ == '__main__':
choose_save_file(None, 'xxx', 'yyy')
test(sys.argv[-1])
from calibre.gui2 import Application
app = Application([])
print(choose_save_file(None, 'xxx', 'yyy'))
del app