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 threading import Thread
from uuid import uuid4 from uuid import uuid4
from PyQt5.Qt import QEventLoop, Qt, pyqtSignal
from polyglot.builtins import filter, string_or_bytes, unicode_type from polyglot.builtins import filter, string_or_bytes, unicode_type
@ -90,22 +89,15 @@ class Helper(Thread):
self.callback = callback self.callback = callback
self.data = data self.data = data
self.daemon = True self.daemon = True
self.rc = 0 self.rc = 1
self.stdoutdata = self.stderrdata = b'' self.stdoutdata = self.stderrdata = b''
def run(self): def run(self):
self.stdoutdata, self.stderrdata = self.process.communicate(b''.join(self.data)) try:
self.rc = self.process.wait() self.stdoutdata, self.stderrdata = self.process.communicate(b''.join(self.data))
self.callback() self.rc = self.process.wait()
finally:
self.callback()
class Loop(QEventLoop):
dialog_closed = pyqtSignal()
def __init__(self):
QEventLoop.__init__(self)
self.dialog_closed.connect(self.exit, type=Qt.QueuedConnection)
def process_path(x): def process_path(x):
@ -178,8 +170,20 @@ def run_file_dialog(
app_uid = app_uid or current_app_uid app_uid = app_uid or current_app_uid
if app_uid: if app_uid:
data.append(serialize_string('APP_UID', 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() loop = Loop()
server = PipeServer(pipename) server = PipeServer(pipename)
server.start()
with sanitize_env_vars(): with sanitize_env_vars():
h = Helper(subprocess.Popen( h = Helper(subprocess.Popen(
[HELPER], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE), [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): class PipeServer(Thread):
def __init__(self, pipename): def __init__(self, pipename):
Thread.__init__(self, name='PipeServer') Thread.__init__(self, name='PipeServer', daemon=True)
self.daemon = True from calibre_extensions import winutil
import win32pipe, win32api, win32con self.client_connected = False
FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 self.pipe_handle = winutil.create_named_pipe(
PIPE_REJECT_REMOTE_CLIENTS = 0x00000008 pipename, winutil.PIPE_ACCESS_INBOUND | winutil.FILE_FLAG_FIRST_PIPE_INSTANCE,
self.pipe_handle = win32pipe.CreateNamedPipe( winutil.PIPE_TYPE_BYTE | winutil.PIPE_READMODE_BYTE | winutil.PIPE_WAIT | winutil.PIPE_REJECT_REMOTE_CLIENTS,
pipename, win32pipe.PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE, 1, 8192, 8192, 0)
win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_READMODE_BYTE | win32pipe.PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, winutil.set_handle_information(self.pipe_handle, winutil.HANDLE_FLAG_INHERIT, 0)
1, 8192, 8192, 0, None)
win32api.SetHandleInformation(self.pipe_handle, win32con.HANDLE_FLAG_INHERIT, 0)
self.err_msg = None self.err_msg = None
self.data = b'' self.data = b''
self.start()
def run(self): def run(self):
import win32pipe, win32file, winerror, win32api from calibre_extensions import winutil
def as_unicode(err):
try:
self.err_msg = unicode_type(err)
except Exception:
self.err_msg = repr(err)
try: try:
try: try:
rc = win32pipe.ConnectNamedPipe(self.pipe_handle) winutil.connect_named_pipe(self.pipe_handle)
except Exception as err: except Exception as err:
as_unicode(err) self.err_msg = f'ConnectNamedPipe failed: {err}'
return
if rc != 0:
self.err_msg = 'Failed to connect to client over named pipe: 0x%x' % rc
return return
self.client_connected = True
while True: while True:
try: try:
hr, data = win32file.ReadFile(self.pipe_handle, 1024 * 50, None) data = winutil.read_file(self.pipe_handle, 64 * 1024)
except Exception as err: except OSError as err:
if getattr(err, 'winerror', None) == winerror.ERROR_BROKEN_PIPE: if err.winerror == winutil.ERROR_BROKEN_PIPE:
break # pipe was closed at the other end break # pipe was closed at the other end
as_unicode(err) self.err_msg = f'ReadFile on pipe failed: {err}'
break
if hr not in (winerror.ERROR_MORE_DATA, 0):
self.err_msg = 'ReadFile on pipe failed with hr=%d' % hr
break
if not data: if not data:
break break
self.data += data self.data += data
finally: finally:
win32api.CloseHandle(self.pipe_handle)
self.pipe_handle = None 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__': if __name__ == '__main__':
choose_save_file(None, 'xxx', 'yyy') from calibre.gui2 import Application
test(sys.argv[-1]) app = Application([])
print(choose_save_file(None, 'xxx', 'yyy'))
del app