changeset 342:89799148f894

core: use classes and factory to handle platform specific behaviours in a generic way
author Goffi <goffi@goffi.org>
date Sat, 04 Jan 2020 16:24:57 +0100
parents 89b17a841c2f
children 7a171d11eab6
files cagou/__init__.py cagou/core/cagou_main.py cagou/core/platform_/__init__.py cagou/core/platform_/android.py cagou/core/platform_/base.py
diffstat 5 files changed, 188 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/cagou/__init__.py	Fri Jan 03 15:48:59 2020 +0100
+++ b/cagou/__init__.py	Sat Jan 04 16:24:57 2020 +0100
@@ -35,4 +35,5 @@
 
 def run():
     host = G._host = cagou_main.Cagou()
+    G.local_platform = cagou_main.local_platform
     host.run()
--- a/cagou/core/cagou_main.py	Fri Jan 03 15:48:59 2020 +0100
+++ b/cagou/core/cagou_main.py	Sat Jan 04 16:24:57 2020 +0100
@@ -62,8 +62,6 @@
 from kivy.core.window import Window
 from kivy.animation import Animation
 from kivy.metrics import dp
-from kivy import utils as kivy_utils
-from kivy.config import Config as KivyConfig
 from .cagou_widget import CagouWidget
 from .share_widget import ShareWidget
 from . import widgets_handler
@@ -89,16 +87,9 @@
 
 ## platform specific settings ##
 
-if kivy_utils.platform == "android":
-    import socket
-    from .platform_ import android
-    android.init_platform()
-else:
-    # we don't want multi-touch emulation with mouse
-
-    # this option doesn't make sense on Android and cause troubles, so we only activate
-    # it for other platforms (cf. https://github.com/kivy/kivy/issues/6229)
-    KivyConfig.set('input', 'mouse', 'mouse,disable_multitouch')
+from . import platform_
+local_platform = platform_.create()
+local_platform.init_platform()
 
 
 ## General Configuration ##
@@ -318,9 +309,7 @@
     def build(self):
         Window.bind(on_keyboard=self.key_input)
         wid = CagouRootWidget(Label(text="Loading please wait"))
-        if sys.platform == 'android':
-            # we don't want menu on Android
-            wid.root_menus.height = 0
+        local_platform.on_app_build(wid)
         return wid
 
     def showWidget(self):
@@ -340,27 +329,16 @@
 
     def initFrontendState(self):
         """Init state to handle paused/stopped/running on mobile OSes"""
-        if sys.platform == "android":
-            # XXX: we use a separated socket instead of bridge because if we
-            #      try to call a bridge method in on_pause method, the call data
-            #      is not written before the actual pause
-            s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            s.connect(os.path.join(android.SOCKET_DIR, android.SOCKET_FILE))
-            s.sendall(android.STATE_RUNNING)
+        local_platform.on_initFrontendState()
 
     def on_pause(self):
-        self.host.sync = False
-        self._frontend_status_socket.sendall(android.STATE_PAUSED)
-        return True
+        return local_platform.on_pause()
 
     def on_resume(self):
-        self._frontend_status_socket.sendall(android.STATE_RUNNING)
-        self.host.sync = True
+        return local_platform.on_resume()
 
     def on_stop(self):
-        if sys.platform == "android":
-            self._frontend_status_socket.sendall(android.STATE_STOPPED)
-            self._frontend_status_socket.close()
+        return local_platform.on_stop()
 
     def key_input(self, window, key, scancode, codepoint, modifier):
         if key == 27:
@@ -449,8 +427,7 @@
             patches.apply()
             log.warning("SSL certificate validation is disabled, this is unsecure!")
 
-        if sys.platform == 'android':
-            android.host_init(self)
+        local_platform.on_host_init(self)
 
     @property
     def visible_widgets(self):
--- a/cagou/core/platform_/__init__.py	Fri Jan 03 15:48:59 2020 +0100
+++ b/cagou/core/platform_/__init__.py	Sat Jan 04 16:24:57 2020 +0100
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+
+# Cagou: desktop/mobile frontend for Salut à Toi XMPP client
+# Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from kivy import utils as kivy_utils
+
+
+def create():
+    """Factory method to create the platform instance adapted to running one"""
+    if kivy_utils.platform == "android":
+        from .android import Platform
+        return Platform()
+    else:
+        from .base import Platform
+        return Platform()
--- a/cagou/core/platform_/android.py	Fri Jan 03 15:48:59 2020 +0100
+++ b/cagou/core/platform_/android.py	Sat Jan 04 16:24:57 2020 +0100
@@ -17,12 +17,15 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sys
+import os
+import socket
 from jnius import autoclass, cast
 from android import activity
 from sat.core import log as logging
 from cagou.core.constants import Const as C
 from cagou import G
 from kivy.clock import Clock
+from .base import Platform as BasePlatform
 
 
 log = logging.getLogger(__name__)
@@ -41,84 +44,108 @@
 SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
 SOCKET_FILE = ".socket"
 
-# cache for callbacks to run when profile is plugged
-cache = []
+
+class Platform(BasePlatform):
+
+    def __init__(self):
+        super().__init__()
+        # cache for callbacks to run when profile is plugged
+        self.cache = []
+
+    def init_platform(self):
+        # sys.platform is "linux" on android by default
+        # so we change it to allow backend to detect android
+        sys.platform = "android"
+        C.PLUGIN_EXT = 'pyc'
+
+    def on_app_build(self, wid):
+        # we don't want menu on Android
+        wid.root_menus.height = 0
+
+    def on_host_init(self, host):
+        argument = ''
+        service.start(mActivity, argument)
 
+        activity.bind(on_new_intent=self.on_new_intent)
+        self.cache.append((self.on_new_intent, mActivity.getIntent()))
+        host.addListener('profilePlugged', self.onProfilePlugged)
+
+    def on_initFrontendState(self):
+        # XXX: we use a separated socket instead of bridge because if we
+        #      try to call a bridge method in on_pause method, the call data
+        #      is not written before the actual pause
+        s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE))
+        s.sendall(STATE_RUNNING)
+
+    def onProfilePlugged(self, profile):
+        log.debug("ANDROID profilePlugged")
+        for method, *args in self.cache:
+            method(*args)
+        del self.cache
+        G.host.removeListener("profilePlugged", self.onProfilePlugged)
+
+    def on_pause(self):
+        G.host.sync = False
+        self._frontend_status_socket.sendall(STATE_PAUSED)
+        return True
 
-def on_new_intent(intent):
-    log.debug("on_new_intent")
-    Intent = autoclass('android.content.Intent')
-    action = intent.getAction();
-    intent_type = intent.getType();
-    if action == "android.intent.action.SEND":
-        # we have receiving data to share, we parse the intent data
-        # and show the share widget
-        data = {}
-        text = intent.getStringExtra(Intent.EXTRA_TEXT)
-        if text is not None:
-            data['text'] = text
-        item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
-        if item is not None:
-            uri = cast('android.net.Uri', item)
-            data['uri'] = uri.toString()
-            path = getPathFromUri(uri)
-            if path is not None:
-                data['path'] = path
+    def on_resume(self):
+        self._frontend_status_socket.sendall(STATE_RUNNING)
+        G.host.sync = True
+
+    def on_stop(self):
+        self._frontend_status_socket.sendall(STATE_STOPPED)
+        self._frontend_status_socket.close()
+
+    def getPathFromUri(self, uri):
+        cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
+        if cursor is None:
+            return uri.getPath()
         else:
+            cursor.moveToFirst()
+            # FIXME: using DATA is not recommended (and DATA is deprecated)
+            # we should read directly the file with
+            # ContentResolver#openFileDescriptor(Uri, String)
+            col_idx = cursor.getColumnIndex(DATA);
+            if col_idx == -1:
+                return uri.getPath()
+            return cursor.getString(col_idx)
+
+    def on_new_intent(self, intent):
+        log.debug("on_new_intent")
+        Intent = autoclass('android.content.Intent')
+        action = intent.getAction();
+        intent_type = intent.getType();
+        if action == "android.intent.action.SEND":
+            # we have receiving data to share, we parse the intent data
+            # and show the share widget
+            data = {}
+            text = intent.getStringExtra(Intent.EXTRA_TEXT)
+            if text is not None:
+                data['text'] = text
+            item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
+            if item is not None:
+                uri = cast('android.net.Uri', item)
+                data['uri'] = uri.toString()
+                path = self.getPathFromUri(uri)
+                if path is not None:
+                    data['path'] = path
+            else:
+                uri = None
+                path = None
+
+            Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0)
+        else:
+            text = None
             uri = None
             path = None
 
-        Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0)
-    else:
-        text = None
-        uri = None
-        path = None
-
-    msg = (f"NEW INTENT RECEIVED\n"
-           f"type: {intent_type}\n"
-           f"action: {action}\n"
-           f"text: {text}\n"
-           f"uri: {uri}\n"
-           f"path: {path}")
-
-    log.debug(msg)
-
-
-def onProfilePlugged(profile):
-    log.debug("ANDROID profilePlugged")
-    global cache
-    for method, *args in cache:
-        method(*args)
-    del cache
-    G.host.removeListener("profilePlugged", onProfilePlugged)
-
+        msg = (f"NEW INTENT RECEIVED\n"
+               f"type: {intent_type}\n"
+               f"action: {action}\n"
+               f"text: {text}\n"
+               f"uri: {uri}\n"
+               f"path: {path}")
 
-def init_platform():
-    # sys.platform is "linux" on android by default
-    # so we change it to allow backend to detect android
-    sys.platform = "android"
-    C.PLUGIN_EXT = 'pyc'
-
-
-def host_init(host):
-    argument = ''
-    service.start(mActivity, argument)
-
-    activity.bind(on_new_intent=on_new_intent)
-    cache.append((on_new_intent, mActivity.getIntent()))
-    host.addListener('profilePlugged', onProfilePlugged)
-
-
-def getPathFromUri(uri):
-    cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
-    if cursor is None:
-        return uri.getPath()
-    else:
-        cursor.moveToFirst()
-        # FIXME: using DATA is not recommended (and DATA is deprecated)
-        # we should read directly the file with
-        # ContentResolver#openFileDescriptor(Uri, String)
-        col_idx = cursor.getColumnIndex(DATA);
-        if col_idx == -1:
-            return uri.getPath()
-        return cursor.getString(col_idx)
+        log.debug(msg)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cagou/core/platform_/base.py	Sat Jan 04 16:24:57 2020 +0100
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+# Cagou: desktop/mobile frontend for Salut à Toi XMPP client
+# Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from kivy.config import Config as KivyConfig
+
+
+class Platform:
+    """Base class to handle platform specific behaviours"""
+
+    def init_platform(self):
+        # we don't want multi-touch emulation with mouse
+
+        # this option doesn't make sense on Android and cause troubles, so we only activate
+        # it for other platforms (cf. https://github.com/kivy/kivy/issues/6229)
+        KivyConfig.set('input', 'mouse', 'mouse,disable_multitouch')
+
+    def on_app_build(self, wid):
+        pass
+
+    def on_host_init(self, host):
+        pass
+
+    def on_initFrontendState(self):
+        pass
+
+    def on_pause(self):
+        pass
+
+    def on_resume(self):
+        pass
+
+    def on_stop(self):
+        pass