[chrome-gnome-shell/feature/connector-update-check: 2/2] Rewritten connector to use Gio.Application/Glib.



commit 974091cc4a7156f568a963f4d346afb8947d69cf
Author: Yuri Konotopov <ykonotopov gmail com>
Date:   Sun Sep 25 13:28:29 2016 +0300

    Rewritten connector to use Gio.Application/Glib.

 connector/chrome-gnome-shell.py |  395 +++++++++++++++++++++------------------
 1 files changed, 209 insertions(+), 186 deletions(-)
---
diff --git a/connector/chrome-gnome-shell.py b/connector/chrome-gnome-shell.py
index 77d8cdf..ec42bff 100755
--- a/connector/chrome-gnome-shell.py
+++ b/connector/chrome-gnome-shell.py
@@ -17,11 +17,10 @@ import json
 import os
 import re
 import requests
+import signal
 import struct
 import sys
 import traceback
-from select import select
-from threading import Lock
 
 CONNECTOR_VERSION      = 7.1
 DEBUG_ENABLED          = False
@@ -30,13 +29,32 @@ SHELL_SCHEMA = "org.gnome.shell"
 ENABLED_EXTENSIONS_KEY = "enabled-extensions"
 EXTENSION_DISABLE_VERSION_CHECK_KEY = "disable-extension-version-validation"
 
-BUFFER_SUPPORTED = hasattr(sys.stdin, 'buffer')
-mainLoop = GLib.MainLoop()
-shellAppearedId = False
-shellSignalId = False
-mutex = Lock()
+# https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host-protocol
+MESSAGE_LENGTH_SIZE = 4
 
-proxy = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SESSION,
+# https://wiki.gnome.org/Projects/GnomeShell/Extensions/UUIDGuidelines
+def isUUID(uuid):
+    return uuid is not None and re.match('[-a-zA-Z0-9@._]+$', uuid) is not None
+
+
+def debug(message):
+    if DEBUG_ENABLED:
+        logError(message)
+
+
+def logError(message):
+    print(message, file=sys.stderr)
+
+
+class ChromeGNOMEShell(Gio.Application):
+    def __init__(self):
+        Gio.Application.__init__(self,
+                                    application_id='org.gnome.chrome-gnome-shell-%s' % os.getppid(),
+                                    flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
+
+        self.shellAppearedId = None
+        self.shellSignalId = None
+        self.proxy = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SESSION,
                                        Gio.DBusProxyFlags.NONE,
                                        None,
                                        'org.gnome.Shell',
@@ -44,149 +62,152 @@ proxy = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SESSION,
                                        'org.gnome.Shell.Extensions',
                                        None)
 
-# https://wiki.gnome.org/Projects/GnomeShell/Extensions/UUIDGuidelines
-def isUUID(uuid):
-    return uuid is not None and re.match('[-a-zA-Z0-9@._]+$', uuid) is not None
+        # Set custom exception hook
+        sys.excepthook = self.default_exception_hook
 
 
-# Helper function that sends a message to the webapp.
-def send_message(response):
-    message = json.dumps(response)
-    message_length = len(message.encode('utf-8'))
+    def default_exception_hook(self, type, value, tb):
+        logError("Uncaught exception of type %s occured" % type)
+        traceback.print_tb(tb)
+        logError("Exception: %s" % value)
 
-    if message_length > 1024*1024:
-        logError('Too long message (%d): "%s"' % (message_length, message))
-        return
+        self.release()
 
-    try:
-        # Write message size.
-        if BUFFER_SUPPORTED:
-            sys.stdout.buffer.write(struct.pack(b'I', message_length))
-        else:
-            sys.stdout.write(struct.pack(b'I', message_length))
+    def do_startup(self):
+        debug('[%d] Startup' % (os.getpid()))
+        Gio.Application.do_startup(self)
 
-        # Write the message itself.
-        sys.stdout.write(message)
-        sys.stdout.flush()
-    except IOError as e:
-        logError('IOError occured: %s' % e.strerror)
-        sys.exit(1)
 
+    def do_shutdown(self):
+        debug('[%d] Shutdown' % (os.getpid()))
+        Gio.Application.do_shutdown(self)
 
-def send_error(message):
-    send_message({'success': False, 'message': message})
+        if self.shellAppearedId:
+            Gio.bus_unwatch_name(self.shellAppearedId)
 
-def debug(message):
-    if DEBUG_ENABLED:
-        logError(message)
+        if self.shellSignalId:
+            self.proxy.disconnect(self.shellSignalId)
 
 
-def logError(message):
-    print(message, file=sys.stderr)
+    def do_activate(self, app):
+        debug('[%d] Activate' % (os.getpid()))
+        Gio.Application.do_activate(self)
 
 
-def dbus_call_response(method, parameters, resultProperty):
-    try:
-        result = proxy.call_sync(method,
-                                 parameters,
-                                 Gio.DBusCallFlags.NONE,
-                                 -1,
-                                 None)
+    def do_local_command_line(self, arguments):
+        stdin = GLib.IOChannel.unix_new(sys.stdin.fileno())
+        stdin.set_encoding(None)
+        stdin.set_buffered(False)
 
-        send_message({'success': True, resultProperty: result.unpack()[0]})
-    except GLib.GError as e:
-        send_error(e.message)
-
-def check_update(update_url):
-    result = proxy.call_sync("ListExtensions",
-                             None,
-                             Gio.DBusCallFlags.NONE,
-                             -1,
-                             None)
-
-    extensions = result.unpack()[0]
-
-    if extensions:
-        http_request = {
-            'shell_version': proxy.get_cached_property("ShellVersion").unpack(),
-            'installed': {}
-        }
-
-        for uuid in extensions:
-            if isUUID(uuid):
-                try:
-                    http_request['installed'][uuid] = {
-                        'version': int(extensions[uuid]['version'])
-                    }
-                except ValueError:
-                    http_request['installed'][uuid] = {
-                        'version': 1
-                    }
+        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.on_sigint, None)
+        GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.IN, self.on_input, None)
+        GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.HUP, self.on_hup, None)
+        GLib.io_add_watch(stdin, GLib.PRIORITY_DEFAULT, GLib.IOCondition.ERR, self.on_hup, None)
+
+        self.hold()
+
+        return (True, None, 0)
+
+
+    def on_input(self, source, condition, data):
+        debug('[%d] On input' % (os.getpid()))
+        text_length_bytes = source.read(MESSAGE_LENGTH_SIZE)
+
+        if len(text_length_bytes) == 0:
+            debug('[%d] Release condition: %s' % (os.getpid(), str(condition)))
+            self.release()
+            return
+
+        # Unpack message length as 4 byte integer.
+        text_length = struct.unpack(b'i', text_length_bytes)[0]
+
+        # Read the text (JSON object) of the message.
+        text = source.read(text_length).decode('utf-8')
+
+        request = json.loads(text)
+
+        if 'execute' in request:
+            if 'uuid' in request and not isUUID(request['uuid']):
+                return
+
+            self.process_request(request)
 
-        http_request['installed'] = json.dumps(http_request['installed'])
+
+    def on_shell_signal(self, d_bus_proxy, sender_name, signal_name, parameters):
+        if signal_name == 'ExtensionStatusChanged':
+            debug('[%d] Signal: to %s' % (os.getpid(), signal_name))
+            self.send_message({'signal': signal_name, 'parameters': parameters.unpack()})
+            debug('[%d] Signal: from %s' % (os.getpid(), signal_name))
+
+
+    def on_shell_appeared(self, connection, name, name_owner):
+        debug('[%d] Signal: to %s' % (os.getpid(), name))
+        self.send_message({'signal': name})
+        debug('[%d] Signal: from %s' % (os.getpid(), name))
+
+
+    def on_hup(self, source, condition, data):
+        debug('[%d] On hup: %s' % (os.getpid(), str(condition)))
+        self.release()
+
+
+    def on_sigint(self, data):
+        debug('[%d] On sigint' % (os.getpid()))
+        self.release()
+
+
+    # Helper function that sends a message to the webapp.
+    def send_message(self, response):
+        message = json.dumps(response)
+        message_length = len(message.encode('utf-8'))
+
+        if message_length > 1024*1024:
+            logError('Too long message (%d): "%s"' % (message_length, message))
+            return
 
         try:
-            response = requests.get(
-                                    update_url,
-                                    params=http_request,
-                                    timeout=5
-                                    )
-            response.raise_for_status()
-            send_message({
-                         'success': True,
-                         'extensions': extensions,
-                         'upgrade': response.json()}
-                         )
-        except (
-                requests.ConnectionError, requests.HTTPError, requests.Timeout,
-                requests.TooManyRedirects, requests.RequestException, ValueError
-                ) as ex:
-            send_message({'success': False, 'message': str(ex.message) if ('message' in ex) else str(ex)})
-
-
-# Callback that reads messages from the webapp.
-def process_input(user_data):
-    rlist, _, _ = select([sys.stdin], [], [], 1)
-    if rlist:
-        # Read the message length (first 4 bytes).
-        if BUFFER_SUPPORTED:
-            text_length_bytes = sys.stdin.buffer.read(4)
-        else:
-            text_length_bytes = sys.stdin.read(4)
-    else:
-        return True
-
-    if len(text_length_bytes) == 0:
-        mainLoop.quit()
-        return False
-
-    # Unpack message length as 4 byte integer.
-    text_length = struct.unpack(b'i', text_length_bytes)[0]
-
-    # Read the text (JSON object) of the message.
-    if BUFFER_SUPPORTED:
-        text = sys.stdin.buffer.read(text_length).decode('utf-8')
-    else:
-        text = sys.stdin.read(text_length).decode('utf-8')
-
-    request = json.loads(text)
-
-    if 'execute' in request:
-        if 'uuid' in request and not isUUID(request['uuid']):
-            return True
-
-        mutex.acquire()
+            stdout = GLib.IOChannel.unix_new(sys.stdout.fileno())
+            stdout.set_encoding(None)
+            stdout.set_buffered(False)
+
+            stdout.write_chars(struct.pack(b'I', message_length), MESSAGE_LENGTH_SIZE)
+
+            # Write the message itself.
+            stdout.write_chars(message, message_length)
+        except IOError as e:
+            logError('IOError occured: %s' % e.strerror)
+            sys.exit(1)
+
+
+    def send_error(self, message):
+        self.send_message({'success': False, 'message': message})
+
+
+    def dbus_call_response(self, method, parameters, resultProperty):
+        try:
+            result = self.proxy.call_sync(method,
+                                     parameters,
+                                     Gio.DBusCallFlags.NONE,
+                                     -1,
+                                     None)
+
+            self.send_message({'success': True, resultProperty: result.unpack()[0]})
+        except GLib.GError as e:
+            send_error(e.message)
+
+
+    def process_request(self, request):
         debug('[%d] Execute: to %s' % (os.getpid(), request['execute']))
 
         if request['execute'] == 'initialize':
             settings = Gio.Settings.new(SHELL_SCHEMA)
-            shellVersion = proxy.get_cached_property("ShellVersion")
+            shellVersion = self.proxy.get_cached_property("ShellVersion")
             if EXTENSION_DISABLE_VERSION_CHECK_KEY in settings.keys():
                 disableVersionCheck = settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY)
             else:
                 disableVersionCheck = False
 
-            send_message(
+            self.send_message(
                 {
                     'success': True,
                     'properties': {
@@ -198,25 +219,23 @@ def process_input(user_data):
             )
 
         elif request['execute'] == 'subscribeSignals':
-            global shellAppearedId, shellSignalId
-
-            if not shellAppearedId:
-                shellAppearedId = Gio.bus_watch_name(Gio.BusType.SESSION,
+            if not self.shellAppearedId:
+                self.shellAppearedId = Gio.bus_watch_name(Gio.BusType.SESSION,
                                                      'org.gnome.Shell',
                                                      Gio.BusNameWatcherFlags.NONE,
-                                                     on_shell_appeared,
+                                                     self.on_shell_appeared,
                                                      None)
 
-            if not shellSignalId:
-                shellSignalId = proxy.connect('g-signal', on_shell_signal)
+            if not self.shellSignalId:
+                self.shellSignalId = self.proxy.connect('g-signal', self.on_shell_signal)
 
         elif request['execute'] == 'installExtension':
-            dbus_call_response("InstallRemoteExtension",
+            self.dbus_call_response("InstallRemoteExtension",
                                GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
                                "status")
 
         elif request['execute'] == 'listExtensions':
-            dbus_call_response("ListExtensions", None, "extensions")
+            self.dbus_call_response("ListExtensions", None, "extensions")
 
         elif request['execute'] == 'enableExtension':
             settings = Gio.Settings.new(SHELL_SCHEMA)
@@ -239,10 +258,10 @@ def process_input(user_data):
 
             settings.set_strv(ENABLED_EXTENSIONS_KEY, uuids)
 
-            send_message({'success': True})
+            self.send_message({'success': True})
 
         elif request['execute'] == 'launchExtensionPrefs':
-            proxy.call("LaunchExtensionPrefs",
+            self.proxy.call("LaunchExtensionPrefs",
                        GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
                        Gio.DBusCallFlags.NONE,
                        -1,
@@ -251,17 +270,17 @@ def process_input(user_data):
                        None)
 
         elif request['execute'] == 'getExtensionErrors':
-            dbus_call_response("GetExtensionErrors",
+            self.dbus_call_response("GetExtensionErrors",
                                GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
                                "extensionErrors")
 
         elif request['execute'] == 'getExtensionInfo':
-            dbus_call_response("GetExtensionInfo",
+            self.dbus_call_response("GetExtensionInfo",
                                GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
                                "extensionInfo")
 
         elif request['execute'] == 'uninstallExtension':
-            dbus_call_response("UninstallExtension",
+            self.dbus_call_response("UninstallExtension",
                                GLib.Variant.new_tuple(GLib.Variant.new_string(request['uuid'])),
                                "status")
 
@@ -272,59 +291,63 @@ def process_input(user_data):
 
             check_update(update_url)
 
-        debug('[%d] Execute: from %s' % (os.getpid(), request['execute']))
-        mutex.release()
-
-    return True
-
-
-def on_shell_signal(d_bus_proxy, sender_name, signal_name, parameters):
-    if signal_name == 'ExtensionStatusChanged':
-        mutex.acquire()
-        debug('[%d] Signal: to %s' % (os.getpid(), signal_name))
-        send_message({'signal': signal_name, 'parameters': parameters.unpack()})
-        debug('[%d] Signal: from %s' % (os.getpid(), signal_name))
-        mutex.release()
-
-
-def on_shell_appeared(connection, name, name_owner):
-    mutex.acquire()
-    debug('[%d] Signal: to %s' % (os.getpid(), name))
-    send_message({'signal': name})
-    debug('[%d] Signal: from %s' % (os.getpid(), name))
-    mutex.release()
-
 
-def default_exception_hook(type, value, tb):
-    logError("Uncaught exception of type %s occured" % type)
-    traceback.print_tb(tb)
-    logError("Exception: %s" % value)
 
-    mainLoop.quit()
 
+        debug('[%d] Execute: from %s' % (os.getpid(), request['execute']))
 
-def main():
-    debug('[%d] Startup' % (os.getpid()))
-
-    # Set custom exception hook
-    sys.excepthook = default_exception_hook
+    def check_update(self, update_url):
+        result = self.proxy.call_sync("ListExtensions",
+                                 None,
+                                 Gio.DBusCallFlags.NONE,
+                                 -1,
+                                 None)
 
-    GLib.idle_add(process_input, None)
+        extensions = result.unpack()[0]
+
+        if extensions:
+            http_request = {
+                'shell_version': self.proxy.get_cached_property("ShellVersion").unpack(),
+                'installed': {}
+            }
+
+            for uuid in extensions:
+                if isUUID(uuid):
+                    try:
+                        http_request['installed'][uuid] = {
+                            'version': int(extensions[uuid]['version'])
+                        }
+                    except ValueError:
+                        http_request['installed'][uuid] = {
+                            'version': 1
+                        }
+
+            http_request['installed'] = json.dumps(http_request['installed'])
+
+            try:
+                response = requests.get(
+                                        update_url,
+                                        params=http_request,
+                                        timeout=5
+                                        )
+                response.raise_for_status()
+                self.send_message({
+                             'success': True,
+                             'extensions': extensions,
+                             'upgrade': response.json()}
+                             )
+            except (
+                    requests.ConnectionError, requests.HTTPError, requests.Timeout,
+                    requests.TooManyRedirects, requests.RequestException, ValueError
+                    ) as ex:
+                self.send_message({'success': False, 'message': str(ex.message) if ('message' in ex) else 
str(ex)})
 
-    try:
-        mainLoop.run()
-    except KeyboardInterrupt:
-        mainLoop.quit()
 
-    if shellAppearedId:
-        Gio.bus_unwatch_name(shellAppearedId)
+if __name__ == '__main__':
+    debug('[%d] Main. Use CTRL+D to quit.' % (os.getpid()))
 
-    if shellSignalId:
-        proxy.disconnect(shellSignalId)
+    app = ChromeGNOMEShell()
+    app.register()
+    app.run(sys.argv)
 
     debug('[%d] Quit' % (os.getpid()))
-    sys.exit(0)
-
-
-if __name__ == '__main__':
-    main()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]