def request(url, params={}, headers={}, data=None, method=None):
    if params:
        url = "".join([url, "?", urlencode(params)])

    req = urllib2.Request(url)
    if method:
        req.get_method = lambda: method
    req.add_header("User-Agent", USER_AGENT)
    req.add_header("Accept-Encoding", "gzip")
    for k, v in headers.items():
        req.add_header(k, v)
    if data:
        req.add_data(data)
    try:
        with closing(urllib2.urlopen(req)) as response:
            data = response.read()
            if response.headers.get("Content-Encoding", "") == "gzip":
                import zlib
                data = zlib.decompressobj(16 + zlib.MAX_WBITS).decompress(data)
            response.data = data
            response.json = lambda: parse_json(data)
            response.xml = lambda: parse_xml(data)
            return response
    except urllib2.HTTPError, e:
        log.error("http error: %s => %s" % (url, e))
        return None, None
 def _dispatch_delete(self, objectname):
     try:
         self._objects[objectname]._shutdown()
     except Exception:
         _log.error("Error when shutting down the object %s:", type(self._objects[objectname]))
         _log.debug(traceback.format_exc())
     del self._objects[objectname]
 def close(self):
     """
         Close the connection and the socket.
     """
     if self.connection_status == "closed":
         return
     item = {
         'abort': True,
         'event': threading.Event()
     }
     self.write_thread_queue.append(item)
     self.write_thread_semaphore.release()  # notify new item.
     item['event'].wait(1)
     if not item['event'].isSet():
         _log.warning("write thread doesn't process our abort command")
     try:
         self.handler._shutdown()
     except Exception:
         _log.error("Error when shutting down the handler: %s",
                    traceback.format_exc())
     try:
         self._sck.shutdown(socket.SHUT_RDWR)
     except socket.error:
         pass
     self._sck.close()
     self.connection_status = "closed"
 def _dispatch_delete(self, objectname):
     try:
         self._objects[objectname]._shutdown()
     except Exception:
         _log.error("Error when shutting down the object %s:",
                    type(self._objects[objectname]))
         _log.debug(traceback.format_exc())
     del self._objects[objectname]
Exemple #5
0
def start_quasard(**kwargs):
    jsonrpc_failures = 0
    while jsonrpc_enabled() is False:
        jsonrpc_failures += 1
        log.warning(
            "Unable to connect to Kodi's JSON-RPC service, retrying...")
        if jsonrpc_failures > 1:
            time.sleep(5)
            if not jsonrpc_enabled(notify=True):
                log.error(
                    "Unable to reach Kodi's JSON-RPC service, aborting...")
                return False
            else:
                break
        time.sleep(3)

    quasar_dir, quasar_binary = get_quasar_binary()

    if quasar_dir is False or quasar_binary is False:
        return False

    lockfile = os.path.join(ADDON_PATH, ".lockfile")
    if os.path.exists(lockfile):
        log.warning("Existing process found from lockfile, killing...")
        try:
            with open(lockfile) as lf:
                pid = int(lf.read().rstrip(" \t\r\n\0"))
            os.kill(pid, 9)
        except Exception as e:
            log.error(repr(e))

    SW_HIDE = 0
    STARTF_USESHOWWINDOW = 1

    args = [quasar_binary]
    kwargs["cwd"] = quasar_dir

    if PLATFORM["os"] == "windows":
        args[0] = getWindowsShortPath(quasar_binary)
        kwargs["cwd"] = getWindowsShortPath(quasar_dir)
        si = subprocess.STARTUPINFO()
        si.dwFlags = STARTF_USESHOWWINDOW
        si.wShowWindow = SW_HIDE
        clear_fd_inherit_flags()
        kwargs["startupinfo"] = si
    else:
        env = os.environ.copy()
        env["LD_LIBRARY_PATH"] = "%s:%s" % (quasar_dir,
                                            env.get("LD_LIBRARY_PATH", ""))
        kwargs["env"] = env
        kwargs["close_fds"] = True

    return subprocess.Popen(args, **kwargs)
def clear_fd_inherit_flags():
    # Ensure the spawned quasar binary doesn't inherit open files from Kodi
    # which can break things like addon updates. [WINDOWS ONLY]
    from ctypes import windll

    HANDLE_RANGE = xrange(0, 65536)
    HANDLE_FLAG_INHERIT = 1
    FILE_TYPE_DISK = 1

    for hd in HANDLE_RANGE:
        if windll.kernel32.GetFileType(hd) == FILE_TYPE_DISK:
            if not windll.kernel32.SetHandleInformation(hd, HANDLE_FLAG_INHERIT, 0):
                log.error("Error clearing inherit flag, disk file handle %x" % hd)
Exemple #7
0
def clear_fd_inherit_flags():
    # Ensure the spawned quasar binary doesn't inherit open files from Kodi
    # which can break things like addon updates. [WINDOWS ONLY]
    from ctypes import windll

    HANDLE_RANGE = xrange(0, 65536)
    HANDLE_FLAG_INHERIT = 1
    FILE_TYPE_DISK = 1

    for hd in HANDLE_RANGE:
        if windll.kernel32.GetFileType(hd) == FILE_TYPE_DISK:
            if not windll.kernel32.SetHandleInformation(hd, HANDLE_FLAG_INHERIT, 0):
                log.error("Error clearing inherit flag, disk file handle %x" % hd)
 def _send(self, response):
     txtResponse = None
     try:
         txtResponse = json.dumps(response, self)
     except Exception as e:
         _log.error("An unexpected error ocurred when trying to create the message: %r", e)
         response = {"result": None, "error": "InternalServerError: " + repr(e)}
         txtResponse = json.dumps(response, self)
     try:
         self.write(txtResponse)
     except TypeError:
         _log.debug("response was: %r", response)
         raise
Exemple #9
0
def jsonrpc_enabled(notify=False):
    try:
        s = socket.socket()
        s.connect(('127.0.0.1', 9090))
        s.close()
        log.info("Kodi's JSON-RPC service is available, starting up...")
        del s
        return True
    except Exception as e:
        log.error(repr(e))
        if notify:
            dialog = xbmcgui.Dialog()
            dialog.ok("Quasar", getLocalizedString(30199))
    return False
def jsonrpc_enabled(notify=False):
    try:
        s = socket.socket()
        s.connect(('127.0.0.1', 9090))
        s.close()
        log.info("Kodi's JSON-RPC service is available, starting up...")
        del s
        return True
    except Exception as e:
        log.error(repr(e))
        if notify:
            xbmc.executebuiltin("ActivateWindow(ServiceSettings)")
            dialog = xbmcgui.Dialog()
            dialog.ok("Quasar", getLocalizedString(30199))
    return False
 def _send(self, response):
     txtResponse = None
     try:
         txtResponse = json.dumps(response, self)
     except Exception as e:
         _log.error("An unexpected error ocurred when trying to create the message: %r", e)
         response = {
             'result': None,
             'error': "InternalServerError: " + repr(e)
         }
         txtResponse = json.dumps(response, self)
     try:
         self.write(txtResponse)
     except TypeError:
         _log.debug("response was: %r", response)
         raise
Exemple #12
0
    def _format_exception(self, obj, method, args, kw, exc):
        etype, evalue, etb = exc
        funargs = ", ".join([repr(x) for x in args] +
                            ["%s=%r" % (k, kw[k]) for k in kw])
        if len(funargs) > 40:
            funargs = funargs[:37] + "..."

        _log.error("(%s) In Handler method %s.%s(%s) ",
                   obj.__class__.__module__, obj.__class__.__name__, method,
                   funargs)
        _log.debug("\n".join([
            "%s::%s:%d %s" % (filename, fnname, lineno, srcline) for filename,
            lineno, fnname, srcline in traceback.extract_tb(etb)[1:]
        ]))
        _log.error("Unhandled error: %s: %s", etype.__name__, evalue)
        del etb
        return '%s: %s' % (etype.__name__, evalue)
    def _readn(self):
        """
            Internal function which reads from socket waiting for a newline
        """
        streambuffer = self._buffer
        pos = streambuffer.find(b'\n')
        # _log.debug("read...")
        # retry = 0
        while pos == -1:
            data = b''
            try:
                data = self._sck.recv(2048)
            except IOError as inst:
                _log.debug("Read socket error: IOError%r (timeout: %r)",
                           inst.args, self._sck.gettimeout())
                if inst.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
                    if self._sck.gettimeout() == 0:  # if it was too fast
                        self._sck.settimeout(5)
                        continue
                        # time.sleep(0.5)
                        # retry += 1
                        # if retry < 10:
                        #     _log.debug("Retry %s", retry)
                        #     continue
                # _log.debug(traceback.format_exc(0))
                if inst.errno in self._SOCKET_COMM_ERRORS:
                    raise EofError(len(streambuffer))

                return b''
            except socket.error as inst:
                _log.error("Read socket error: socket.error%r (timeout: %r)",
                           inst.args, self._sck.gettimeout())
                # _log.debug(traceback.format_exc(0))
                return b''
            except:
                raise
            if not data:
                raise EofError(len(streambuffer))
            # _log.debug("readbuf+: %r", data)
            streambuffer += data
            pos = streambuffer.find(b'\n')

        self._buffer = streambuffer[pos + 1:]
        streambuffer = streambuffer[:pos]
        # _log.debug("read: %r", buffer)
        return streambuffer
    def _readn(self):
        """
            Internal function which reads from socket waiting for a newline
        """
        streambuffer = self._buffer
        pos = streambuffer.find(b"\n")
        # _log.debug("read...")
        # retry = 0
        while pos == -1:
            data = b""
            try:
                data = self._sck.recv(2048)
            except IOError as inst:
                _log.debug("Read socket error: IOError%r (timeout: %r)", inst.args, self._sck.gettimeout())
                if inst.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
                    if self._sck.gettimeout() == 0:  # if it was too fast
                        self._sck.settimeout(5)
                        continue
                        # time.sleep(0.5)
                        # retry += 1
                        # if retry < 10:
                        #     _log.debug("Retry %s", retry)
                        #     continue
                # _log.debug(traceback.format_exc(0))
                if inst.errno in self._SOCKET_COMM_ERRORS:
                    raise EofError(len(streambuffer))

                return b""
            except socket.error as inst:
                _log.error("Read socket error: socket.error%r (timeout: %r)", inst.args, self._sck.gettimeout())
                # _log.debug(traceback.format_exc(0))
                return b""
            except:
                raise
            if not data:
                raise EofError(len(streambuffer))
            # _log.debug("readbuf+: %r", data)
            streambuffer += data
            pos = streambuffer.find(b"\n")

        self._buffer = streambuffer[pos + 1 :]
        streambuffer = streambuffer[:pos]
        # _log.debug("read: %r", buffer)
        return streambuffer
    def _format_exception(self, obj, method, args, kw, exc):
        etype, evalue, etb = exc
        funargs = ", ".join([repr(x) for x in args] + ["%s=%r" % (k, kw[k]) for k in kw])
        if len(funargs) > 40:
            funargs = funargs[:37] + "..."

        _log.error(
            "(%s) In Handler method %s.%s(%s) ", obj.__class__.__module__, obj.__class__.__name__, method, funargs
        )
        _log.debug(
            "\n".join(
                [
                    "%s::%s:%d %s" % (filename, fnname, lineno, srcline)
                    for filename, lineno, fnname, srcline in traceback.extract_tb(etb)[1:]
                ]
            )
        )
        _log.error("Unhandled error: %s: %s", etype.__name__, evalue)
        del etb
        return "%s: %s" % (etype.__name__, evalue)
    def setresponse(self, value):
        """
            Method used by Connection instance to tell Request that a Response
            is available to this request.

            Parameters:

            **value**
                Value (JSON decoded) received from socket.
        """
        self.responses.put(value)
        for callback in self.callbacks:
            try:
                callback(self)
            except Exception as exc:
                _log.error("Error on callback: %r", exc)
                _log.debug(traceback.format_exc())

        self.event_response.set()  # helper for threads.
        if self.auto_close:
            self.close()
Exemple #17
0
    def setresponse(self, value):
        """
            Method used by Connection instance to tell Request that a Response
            is available to this request.

            Parameters:

            **value**
                Value (JSON decoded) received from socket.
        """
        self.responses.put(value)
        for callback in self.callbacks:
            try:
                callback(self)
            except Exception as exc:
                _log.error("Error on callback: %r", exc)
                _log.debug(traceback.format_exc())

        self.event_response.set()  # helper for threads.
        if self.auto_close:
            self.close()
 def close(self):
     """
         Close the connection and the socket.
     """
     if self.connection_status == "closed":
         return
     item = {"abort": True, "event": threading.Event()}
     self.write_thread_queue.append(item)
     self.write_thread_semaphore.release()  # notify new item.
     item["event"].wait(1)
     if not item["event"].isSet():
         _log.warning("write thread doesn't process our abort command")
     try:
         self.handler._shutdown()
     except Exception:
         _log.error("Error when shutting down the handler: %s", traceback.format_exc())
     try:
         self._sck.shutdown(socket.SHUT_RDWR)
     except socket.error:
         pass
     self._sck.close()
     self.connection_status = "closed"
Exemple #19
0
def get_quasar_binary():
    binary = "quasar" + (PLATFORM["os"] == "windows" and ".exe" or "")

    binary_dir = os.path.join(ADDON.getAddonInfo("path"), "resources", "bin",
                              "%(os)s_%(arch)s" % PLATFORM)
    if PLATFORM["os"] == "android":
        log.info("Detected binary folder: %s" % binary_dir)
        binary_dir_legacy = binary_dir.replace("/storage/emulated/0",
                                               "/storage/emulated/legacy")
        if os.path.exists(binary_dir_legacy):
            binary_dir = binary_dir_legacy
        log.info("Using binary folder: %s" % binary_dir)
        app_id = android_get_current_appid()
        xbmc_data_path = os.path.join("/data", "data", app_id)
        dest_binary_dir = os.path.join(xbmc_data_path, "files", ADDON_ID,
                                       "bin", "%(os)s_%(arch)s" % PLATFORM)
    else:
        dest_binary_dir = os.path.join(
            xbmc.translatePath(ADDON.getAddonInfo("profile")), "bin",
            "%(os)s_%(arch)s" % PLATFORM)

    try:
        binary_dir = binary_dir.decode("latin1")
        dest_binary_dir = dest_binary_dir.decode("latin1")
    except UnicodeEncodeError:
        log.info("Unable to decode: binary_dir=%s dest_binary_dir=%s" %
                 (repr(binary_dir), repr(dest_binary_dir)))

    binary_path = os.path.join(binary_dir, binary)
    dest_binary_path = os.path.join(dest_binary_dir, binary)

    if not os.path.exists(binary_path):
        notify(ADDON.getLocalizedString(30103).encode('utf-8'))
        system_information()
        return False, False

    if not os.path.exists(dest_binary_path) or get_quasard_checksum(
            dest_binary_path) != get_quasard_checksum(binary_path):
        log.info("Updating quasar daemon...")
        try:
            os.makedirs(dest_binary_dir)
        except OSError:
            pass
        try:
            shutil.rmtree(dest_binary_dir)
        except OSError as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            pass
        try:
            shutil.copytree(binary_dir, dest_binary_dir)
        except OSError as e:
            log.error("Unable to copy to destination path for update: %s" % e)
            notify(ADDON.getLocalizedString(30227).encode('utf-8'))
            system_information()
            pass

    # Clean stale files in the directory, as this can cause headaches on
    # Android when they are unreachable
    dest_files = set(os.listdir(dest_binary_dir))
    orig_files = set(os.listdir(binary_dir))
    log.info("Deleting stale files %s" % (dest_files - orig_files))
    for file_ in (dest_files - orig_files):
        path = os.path.join(dest_binary_dir, file_)
        if os.path.isdir(path):
            shutil.rmtree(path)
        else:
            os.remove(path)

    return dest_binary_dir, ensure_exec_perms(dest_binary_path)
Exemple #20
0
def get_quasar_binary():
    binary = "quasar" + (PLATFORM["os"] == "windows" and ".exe" or "")

    log.info("PLATFORM: %s" % str(PLATFORM))
    binary_dir = os.path.join(ADDON_PATH, "resources", "bin", "%(os)s_%(arch)s" % PLATFORM)
    if PLATFORM["os"] == "android":
        log.info("Detected binary folder: %s" % binary_dir)
        binary_dir_legacy = binary_dir.replace("/storage/emulated/0", "/storage/emulated/legacy")
        if os.path.exists(binary_dir_legacy):
            binary_dir = binary_dir_legacy
        log.info("Using binary folder: %s" % binary_dir)
        app_id = android_get_current_appid()
        xbmc_data_path = os.path.join("/data", "data", app_id)
        
        try:                    #Test if there is any permisions problem
            f = open(os.path.join(xbmc_data_path, "test.txt"), "wb")
            f.write("test")
            f.close()
            os.remove(os.path.join(xbmc_data_path, "test.txt"))
        except:
            xbmc_data_path = ''
        
        if not os.path.exists(xbmc_data_path):
            log.info("%s path does not exist, so using %s as xbmc_data_path" % (xbmc_data_path, xbmc.translatePath("special://xbmcbin/")))
            xbmc_data_path = xbmc.translatePath("special://xbmcbin/")

            try:                    #Test if there is any permisions problem
                f = open(os.path.join(xbmc_data_path, "test.txt"), "wb")
                f.write("test")
                f.close()
                os.remove(os.path.join(xbmc_data_path, "test.txt"))
            except:
                xbmc_data_path = ''
        
        if not os.path.exists(xbmc_data_path):
            log.info("%s path does not exist, so using %s as xbmc_data_path" % (xbmc_data_path, xbmc.translatePath("special://masterprofile/")))
            xbmc_data_path = xbmc.translatePath("special://masterprofile/")
        dest_binary_dir = os.path.join(xbmc_data_path, "files", ADDON_ID, "bin", "%(os)s_%(arch)s" % PLATFORM)
    else:
        dest_binary_dir = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "bin", "%(os)s_%(arch)s" % PLATFORM)

    log.info("Using destination binary folder: %s" % dest_binary_dir)
    binary_path = os.path.join(binary_dir, binary)
    dest_binary_path = os.path.join(dest_binary_dir, binary)

    if not os.path.exists(binary_path):
        notify((getLocalizedString(30103) + " %(os)s_%(arch)s" % PLATFORM), time=7000)
        system_information()
        try:
            log.info("Source directory (%s):\n%s" % (binary_dir, os.listdir(os.path.join(binary_dir, ".."))))
            log.info("Destination directory (%s):\n%s" % (dest_binary_dir, os.listdir(os.path.join(dest_binary_dir, ".."))))
        except Exception:
            pass
        return False, False

    if os.path.isdir(dest_binary_path):
        log.warning("Destination path is a directory, expected previous binary file, removing...")
        try:
            shutil.rmtree(dest_binary_path)
        except Exception as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            return False, False

    if not os.path.exists(dest_binary_path) or get_quasard_checksum(dest_binary_path) != get_quasard_checksum(binary_path):
        log.info("Updating quasar daemon...")
        try:
            os.makedirs(dest_binary_dir)
        except OSError:
            pass
        try:
            shutil.rmtree(dest_binary_dir)
        except Exception as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            pass
        try:
            shutil.copytree(binary_dir, dest_binary_dir)
        except Exception as e:
            log.error("Unable to copy to destination path for update: %s" % e)
            system_information()
            return False, False

    # Clean stale files in the directory, as this can cause headaches on
    # Android when they are unreachable
    dest_files = set(os.listdir(dest_binary_dir))
    orig_files = set(os.listdir(binary_dir))
    log.info("Deleting stale files %s" % (dest_files - orig_files))
    for file_ in (dest_files - orig_files):
        path = os.path.join(dest_binary_dir, file_)
        if os.path.isdir(path):
            shutil.rmtree(path)
        else:
            os.remove(path)

    return dest_binary_dir, ensure_exec_perms(dest_binary_path)
Exemple #21
0
def call_binary(function, cmd, retry=False, p=None, **kwargs):
    import xbmc
    import xbmcaddon
    import traceback
    import base64
    import requests
    import time
    import json
    import xbmcvfs
    
    # Lets try first the traditional way
    if not p:
        try:
            p = subprocess.Popen(cmd, **kwargs)
            log.info('## Executing Popen.CMD: %s - PID: %s' % (cmd, p.pid))
            return p
        except Exception as e:
            if not PY3:
                e = unicode(str(e), "utf8", errors="replace").encode("utf8")
            elif PY3 and isinstance(e, bytes):
                e = e.decode("utf8")
            log.error('Exception Popen ERROR: %s, %s' % (str(cmd), str(e)))
            
            if PLATFORM["os"] == "android" and ('Errno 13' in str(e) or 'Errno 2' in str(e)):
                p = None
            else:
                return p

    # The traditional way did not work, so most probably we hit the SDK 29 problem
    APP_PARAMS = {
                  'Quasar':  {
                               'ACTIVE': 0, 
                               'USER_ADDON': 'plugin.video.quasar', 
                               'USER_ADDON_STATUS': xbmc.getCondVisibility('System.HasAddon("plugin.video.quasar")'), 
                               'USER_ADDON_USERDATA': os.path.join(xbmc.translatePath('special://masterprofile/'), 
                                            'addon_data', 'plugin.video.quasar'), 
                               'USER_APK': [os.path.join(xbmc.translatePath('special://xbmcbinaddons/'), 
                                            'plugin.video.quasar', 'resources', 'apk', 
                                            'quasar-mobile-assistant.apk')], 
                               'USER_APP': 'com.quasar.quasarmobileassistant', 
                               'USER_APP_CONTROL': 'quasar-mobile-assistant.version',
                               'USER_APP_URL': 'http://127.0.0.1', 
                               'USER_APP_PORT': '66666',
                               'USER_APP_PORT_ALT': '66667'
                              }, 
                  'Alfa':     {
                               'ACTIVE': 1, 
                               'USER_ADDON': 'plugin.video.alfa', 
                               'USER_ADDON_STATUS': xbmc.getCondVisibility('System.HasAddon("plugin.video.alfa")'), 
                               'USER_ADDON_USERDATA': os.path.join(xbmc.translatePath('special://masterprofile/'), 
                                            'addon_data', 'plugin.video.alfa'), 
                               'USER_APK': ['https://github.com/alfa-addon/alfa-repo/raw/master/downloads/assistant/alfa-mobile-assistant.apk',
                                            'https://github.com/alfa-addon/alfa-repo/raw/master/downloads/assistant/alfa-mobile-assistant.version'], 
                               'USER_APP': 'com.alfa.alfamobileassistant', 
                               'USER_APP_CONTROL': 'alfa-mobile-assistant.version',
                               'USER_APP_URL': 'http://127.0.0.1', 
                               'USER_APP_PORT': '48885',
                               'USER_APP_PORT_ALT': '48886'
                              }
                  }
    
    QUASAR_ADDON_SETTING = ADDON
    QUASAR_ADDON_USERDATA = translatePath(QUASAR_ADDON_SETTING.getAddonInfo("profile"))
    if xbmc.getCondVisibility('System.HasAddon("plugin.video.torrest")'):
        TORREST_ADDON = True
    else:
        TORREST_ADDON = False
    USER_APP_URL = ''
    USER_APP_URL_ALT = ''
    USER_ADDON = ''
    USER_ADDON_STATUS = False
    ANDROID_STORAGE = os.getenv('ANDROID_STORAGE')
    if not ANDROID_STORAGE: ANDROID_STORAGE = '/storage'
    USER_APP = ''
    USER_APP_PATH = os.path.join(ANDROID_STORAGE, 'emulated', '0', 'Android', 'data')
    USER_APP_STATUS = False

    for user_addon, user_params in list(APP_PARAMS.items()):
        if not user_params['ACTIVE'] or not user_params['USER_ADDON_STATUS']: continue
        
        if user_addon == 'Quasar':
            try:
                # Quasar add-on and Quasar Assistant installed
                USER_ADDON = user_params['USER_ADDON']
                USER_ADDON_STATUS = True
                if os.path.exists(os.path.join(USER_APP_PATH, user_params['USER_APP'])):
                    USER_APP = user_params['USER_APP']
                    USER_APP_STATUS = True
                    USER_APP_URL = "%s:%s" % (user_params['USER_APP_URL'], user_params['USER_APP_PORT'])
                    USER_APP_URL_ALT = "%s:%s" % (user_params['USER_APP_URL'], user_params['USER_APP_PORT_ALT'])
                if USER_APP_STATUS: break
            except:
                log.error(traceback.format_exc())
        
        if user_addon == 'Alfa':
            try:
                try:
                    # Alfa add-on and Alfa Assistant installed
                    USER_ADDON_SETTING = xbmcaddon.Addon(id="%s" % user_params['USER_ADDON'])
                    USER_ADDON = user_params['USER_ADDON']
                    USER_ADDON_STATUS = True
                    if USER_ADDON_SETTING.getSetting('assistant_mode') == 'este' and \
                            os.path.exists(os.path.join(USER_APP_PATH, user_params['USER_APP'])):
                        USER_APP = user_params['USER_APP']
                        USER_APP_STATUS = True
                        if USER_ADDON_SETTING.getSetting('assistant_custom_address'):
                            USER_APP_URL_base = "http://%s" % USER_ADDON_SETTING.getSetting('assistant_custom_address')
                        else:
                            USER_APP_URL_base = user_params['USER_APP_URL']
                        USER_APP_URL = "%s:%s" % (USER_APP_URL_base, user_params['USER_APP_PORT'])
                        USER_APP_URL_ALT = "%s:%s" % (USER_APP_URL_base, user_params['USER_APP_PORT_ALT'])
                except:
                    # Only Alfa Assistant installed and not Alfa
                    if os.path.exists(os.path.join(USER_APP_PATH, user_params['USER_APP'])):
                        USER_APP_STATUS = True
                if USER_APP_STATUS: break
            except:
                log.error(traceback.format_exc())

    if not USER_APP_STATUS:
        user_params = install_app(APP_PARAMS)
        if not user_params.get('USER_APP', '') or not user_params.get('USER_APP_URL', '') \
                        or not user_params.get('USER_APP_PORT', '') or not user_params.get('USER_APP_PORT_ALT', ''):
            raise ValueError("No app: One MUST be installed")
        else:
            USER_APP_STATUS = True
            USER_APP = user_params['USER_APP']
            USER_APP_URL = "%s:%s" % (user_params['USER_APP_URL'], user_params['USER_APP_PORT'])
            USER_APP_URL_ALT = "%s:%s" % (user_params['USER_APP_URL'], user_params['USER_APP_PORT_ALT'])

    if USER_APP_STATUS:
        try:
            try:
                # Special process for Android 11+: setting download paths in a "free zone"
                os_release = 0
                if PY3:
                    FF = b'\n'
                else:
                    FF = '\n'
                try:
                    for label_a in subprocess.check_output('getprop').split(FF):
                        if PY3 and isinstance(label_a, bytes):
                            label_a = label_a.decode()
                        if 'build.version.release' in label_a:
                            os_release = int(re.findall(':\s*\[(.*?)\]$', label_a, flags=re.DOTALL)[0])
                            break
                except:
                    try:
                        with open(os.environ['ANDROID_ROOT'] + '/build.prop', "r") as f:
                            labels = read(f)
                        for label_a in labels.split():
                            if PY3 and isinstance(label_a, bytes):
                                label_a = label_a.decode()
                            if 'build.version.release' in label_a:
                                os_release = int(re.findall('=(.*?)$', label_a, flags=re.DOTALL)[0])
                                break
                    except:
                        os_release = 10
                
                log.info(os_release)
                # If Android 11+, reset the download & torrents paths to /storage/emulated/0/Download/Kodi/Quasar/...
                if os_release >= 11:
                    if 'userdata/addon_data/plugin.video.' in QUASAR_ADDON_SETTING.getSetting('download_path') \
                                    or not QUASAR_ADDON_SETTING.getSetting('download_path'):
                        download_path = '/storage/emulated/0/Download/Kodi/Quasar/downloads/'
                        QUASAR_ADDON_SETTING.setSetting('download_path', download_path)
                        if not os.path.exists(download_path):
                            res = xbmcvfs.mkdirs(download_path)
                    if 'userdata/addon_data/plugin.video.' in QUASAR_ADDON_SETTING.getSetting('library_path') \
                                    or not QUASAR_ADDON_SETTING.getSetting('library_path'):
                        library_path = '/storage/emulated/0/Download/Kodi/Quasar/Library/'
                        QUASAR_ADDON_SETTING.setSetting('library_path', library_path)
                        if not os.path.exists(library_path):
                            res = xbmcvfs.mkdirs(library_path)

            except:
                log.info('## Assistant checking download_path: ERROR on processing')
                log.info(traceback.format_exc())
            
            """
            Assistant APP acts as a CONSOLE for binaries management in Android 10+ and Kodi 19+
            
            Syntax StartAndroidActivity("USER_APP", "", "function", "cmd|arg| ... |arg|||dict{env} in format |key=value|... "):
                  
                  - cmd: binary name in format '$PWD/lib'binary_name'.so'
                  - 'open':                                                     START the Assitant
                  - 'terminate':                                                CLOSE Assistant
                  - "OpenBinary", "$PWD/libBINARY.so|-port|61235|-settings|/storage/emulated/.../settings.json|||
                                    (kwargs[env]): |key=value|etc=etc":         CALL binary
            
            Syntax Http requests: http://127.0.0.1:48884/command?option=value
                  
                  - /openBinary?cmd=base64OfFullCommand($PWD/libBINARY.so|-port|61235| 
                                    -settings|/storage/emulated/.../settings.json|||
                                    (kwargs[env]): |key=value|etc=etc):         CALL binary
                          - returns: {
                                      "pid": 999,
                                      "output": "Base64encoded",
                                      "error": "Base64encoded",
                                      "startDate": "Base64encoded(2021-12-13 14:00:12)",
                                      "endDate": "Base64encoded([2021-12-13 14:00:12])",    If ended
                                      "retCode": "0|1|number|None"                          None if not ended
                                      "cmd": "Base64encoded(command *args **kwargs)"        Full command as sent to the app
                                      "finalCmd": "Base64encoded($PWD/command *args)"       Actual command executed vy the app
                                      
                  - /openBinary?cmd=base64OfFullCommand([[ANY Android/Linux command: killall libquasar.so (kills all Quasar binaries)]])
                          - returns: {As in /openBinary?cmd}
                  
                  - /getBinaryStatus?pid=999:                                   Request binary STATUS by PID
                          - returns: {As in /openBinary?cmd}
                  
                  - /killBinary?pid=999:                                        Request KILL binary PID
                          - returns: {As in /openBinary?cmd}

                  - /getBinaryList:                                             Return a /getBinaryStatus per binary launched during app session
                          - returns: {As in /openBinary?cmd}
                  - /terminate:                                                 Close Assistant
            """
            
            # Lets start the Assistant app
            separator = '|'
            separator_escaped = '\|'
            separator_kwargs = '|||'
            command = []
            status_code = 0
            cmd_app = ''
            
            url = ''
            url_open = USER_APP_URL + '/openBinary?cmd='
            url_killall = url_open + base64.b64encode(str('killall%slibquasar.so' % separator).encode('utf8')).decode('utf8')
            if isinstance(p, int):
                url_killall = USER_APP_URL + '/killBinary?pid=%s' % p
            cmd_android = 'StartAndroidActivity("%s", "", "%s", "%s")' % (USER_APP, 'open', 'about:blank')
            cmd_android_close = 'StartAndroidActivity("%s", "", "%s", "%s")' % (USER_APP, 'terminate', 'about:blank')
            if TORREST_ADDON:
                time.sleep(10)          # let Torrest starts first
            xbmc.executebuiltin(cmd_android)
            time.sleep(3)

            # Build the command & params
            if isinstance(cmd, list):
                command.append(cmd)
                command.append(kwargs)
                # Convert Args to APP format
                cmd_bis = cmd[:]
                cmd_bis[0] = '$PWD/libquasar.so'
                for args in cmd_bis:
                    cmd_app += args.replace(separator, separator_escaped) + separator
                cmd_app = cmd_app.rstrip(separator)
                # Convert Kwargs to APP format
                if kwargs.get('env', {}):
                    cmd_app += separator_kwargs
                for key, value in list(kwargs.get('env', {}).items()):
                    if key == 'LD_LIBRARY_PATH':
                        # The app will replace $PWD by the binary/lib path
                        value = '$PWD'
                    if key == 'PATH':
                        # The app will replace $PWD by the binary/lib path
                        value = '$PWD:%s' % value
                    cmd_app += '%s=%s%s' % (key.replace(separator, separator_escaped), \
                                value.replace(separator, separator_escaped), separator)
                cmd_app = cmd_app.rstrip(separator)
                command_base64 = base64.b64encode(cmd_app.encode('utf8')).decode('utf8')
            else:
                command_base64 = cmd
                cmd = p.args_
                kwargs = p.kwargs_
                command.append(cmd)
                command.append(kwargs)

            # Launch the Binary
            launch_status = True
            try:
                session = requests.Session()
                # First, cancel existing Binary sessions
                url = url_killall
                log.info('## Killing Quasar from Assistant App: %s' % url)
                resp = session.get(url, timeout=5)
                status_code = resp.status_code
                if status_code != 200:
                    log.info('## ERROR Killing Quasar from Assistant App: %s' % resp.content)
                time.sleep(1)
                # Now lets launch the Binary
                log.info('## Calling Quasar from Assistant App: %s - Retry = %s' % (cmd, retry))
                url = url_open + command_base64
                resp = session.get(url, timeout=5)
            except Exception as e:
                resp = requests.Response()
                resp.status_code = str(e)
            status_code = resp.status_code
            if status_code != 200 and not retry:
                log.error("## Calling/Killing Quasar: Invalid app requests response: %s.  Closing Assistant (1)" % status_code)
                if TORREST_ADDON:
                    time.sleep(10)          # let Torrest starts first
                else:
                    xbmc.executebuiltin(cmd_android_close)
                    time.sleep(5)
                return call_binary(function, cmd, retry=True, **kwargs)
            elif status_code != 200 and retry:
                log.error("## Calling/Killing Quasar: Invalid app requests response: %s.  Closing Assistant (1)" % status_code)
                launch_status = False
                xbmc.executebuiltin(cmd_android_close)
                time.sleep(5)
            try:
                app_response = resp.content
                if launch_status:
                    if PY3 and isinstance(app_response, bytes):
                        app_response = app_response.decode()
                    app_response = re.sub('\n|\r|\t', '', app_response)
                    app_response = json.loads(app_response)
            except:
                status_code = resp.content
                launch_status = False
                log.info("## Calling Quasar: Invalid app response: %s" % resp.content)

            # Simulate the response from subprocess.Popen
            pipeout, pipein = os.pipe()
            class Proc:
                pid = 999999
                stdout = os.fdopen(pipeout, 'rb')
                stdin = os.fdopen(pipein, 'wb')
                stderr = stdout
                returncode = None
                startDate = ''
                endDate = ''
                poll = ''
                terminate = ''
                communicate = ''
                app = USER_APP
                url_app = USER_APP_URL
                url_app_alt = USER_APP_URL_ALT
                cmd_app = command_base64
                finalCmd = ''
                args_ = cmd
                kwargs_ = kwargs
                sess = session
                monitor = xbmc.Monitor()
                binary_time = time.time()
                binary_awake = 0
                torrest = TORREST_ADDON
            
            p = Proc()
            
            def redirect_terminate(p=p, action='killBinary'):
                return binary_stat(p, action)
            def redirect_poll(p=p, action='poll'):
                return binary_stat(p, action)
            def redirect_communicate(p=p, action='communicate'):
                return binary_stat(p, action)
            p.poll = redirect_poll
            p.terminate = redirect_terminate
            p.communicate = redirect_communicate
            
            # If something went wrong on the binary launch, lets return the error so it is recovered from the standard code
            if not launch_status:
                p.returncode = 999
                raise ValueError("No app response:  error code: %s" % status_code)
            
            try:
                p.pid = int(app_response['pid'])
            except:
                raise ValueError("No valid PID returned:  PID code: %s" % resp.content)

            # Handle initial messages
            time.sleep(2)
            if app_response.get('output') or app_response.get('error'):
                res = binary_stat(p, 'poll', app_response=app_response)
            else:
                for x in range(20):
                    res = binary_stat(p, 'poll', init=True)
                    if res: break
                    time.sleep(1)
                else:
                    # Is the binary hung?  Lets restart it
                    return call_binary(function, command_base64, retry=True, kwargs={})

            log.info('## Assistant executing CMD: %s - PID: %s' % (command[0], p.pid))
            #log.warning('## Assistant executing CMD **kwargs: %s' % command[1])
        except:
            log.info('## Assistant ERROR %s in CMD: %s%s' % (status_code, url, command))
            log.error(traceback.format_exc())
            
    return p
Exemple #22
0
def start_quasard(**kwargs):
    jsonrpc_failures = 0
    while jsonrpc_enabled() is False:
        jsonrpc_failures += 1
        log.warning("Unable to connect to Kodi's JSON-RPC service, retrying...")
        if jsonrpc_failures > 1:
            time.sleep(5)
            if not jsonrpc_enabled(notify=True):
                log.error("Unable to reach Kodi's JSON-RPC service, aborting...")
                return False
            else:
                break
        time.sleep(3)

    quasar_dir, quasar_binary = get_quasar_binary()

    if quasar_dir is False or quasar_binary is False:
        return False

    lockfile = os.path.join(ADDON_PATH, ".lockfile")
    if os.path.exists(lockfile):
        log.warning("Existing process found from lockfile, killing...")
        try:
            with open(lockfile) as lf:
                pid = int(lf.read().rstrip(" \t\r\n\0"))
            os.kill(pid, 9)
        except Exception as e:
            log.error(repr(e))

        if PLATFORM["os"] == "windows":
            log.warning("Removing library.db.lock file...")
            try:
                library_lockfile = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "library.db.lock")
                os.remove(library_lockfile)
            except Exception as e:
                log.error(repr(e))

    SW_HIDE = 0
    STARTF_USESHOWWINDOW = 1

    args = [quasar_binary]
    kwargs["cwd"] = quasar_dir

    if PLATFORM["os"] == "windows":
        args[0] = getWindowsShortPath(quasar_binary)
        kwargs["cwd"] = getWindowsShortPath(quasar_dir)
        si = subprocess.STARTUPINFO()
        si.dwFlags = STARTF_USESHOWWINDOW
        si.wShowWindow = SW_HIDE
        clear_fd_inherit_flags()
        kwargs["startupinfo"] = si
    else:
        env = os.environ.copy()
        env["LD_LIBRARY_PATH"] = "%s:%s" % (quasar_dir, env.get("LD_LIBRARY_PATH", ""))
        kwargs["env"] = env
        kwargs["close_fds"] = True

    wait_counter = 1
    while xbmc.getCondVisibility('Window.IsVisible(10140)') or xbmc.getCondVisibility('Window.IsActive(10140)'):
        if wait_counter == 1:
            log.info('Add-on settings currently opened, waiting before starting...')
        if wait_counter > 300:
            break
        time.sleep(1)
        wait_counter += 1

    return subprocess.Popen(args, **kwargs)
def get_quasar_binary():
    binary = "quasar" + (PLATFORM["os"] == "windows" and ".exe" or "")

    binary_dir = os.path.join(ADDON.getAddonInfo("path"), "resources", "bin", "%(os)s_%(arch)s" % PLATFORM)
    if PLATFORM["os"] == "android":
        log.info("Detected binary folder: %s" % binary_dir)
        binary_dir_legacy = binary_dir.replace("/storage/emulated/0", "/storage/emulated/legacy")
        if os.path.exists(binary_dir_legacy):
            binary_dir = binary_dir_legacy
        log.info("Using binary folder: %s" % binary_dir)
        app_id = android_get_current_appid()
        xbmc_data_path = os.path.join("/data", "data", app_id)
        dest_binary_dir = os.path.join(xbmc_data_path, "files", ADDON_ID, "bin", "%(os)s_%(arch)s" % PLATFORM)
    else:
        dest_binary_dir = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")), "bin", "%(os)s_%(arch)s" % PLATFORM)

    try:
        binary_dir = binary_dir.decode("latin1")
        dest_binary_dir = dest_binary_dir.decode("latin1")
    except UnicodeEncodeError:
        log.info("Unable to decode: binary_dir=%s dest_binary_dir=%s" % (repr(binary_dir), repr(dest_binary_dir)))

    binary_path = os.path.join(binary_dir, binary)
    dest_binary_path = os.path.join(dest_binary_dir, binary)

    if not os.path.exists(binary_path):
        notify(ADDON.getLocalizedString(30103).encode('utf-8'))
        system_information()
        return False, False

    if not os.path.exists(dest_binary_path) or get_quasard_checksum(dest_binary_path) != get_quasard_checksum(binary_path):
        log.info("Updating quasar daemon...")
        try:
            os.makedirs(dest_binary_dir)
        except OSError:
            pass
        try:
            shutil.rmtree(dest_binary_dir)
        except OSError as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            pass
        try:
            shutil.copytree(binary_dir, dest_binary_dir)
        except OSError as e:
            log.error("Unable to copy to destination path for update: %s" % e)
            notify(ADDON.getLocalizedString(30227).encode('utf-8'))
            system_information()
            pass

    # Clean stale files in the directory, as this can cause headaches on
    # Android when they are unreachable
    dest_files = set(os.listdir(dest_binary_dir))
    orig_files = set(os.listdir(binary_dir))
    log.info("Deleting stale files %s" % (dest_files - orig_files))
    for file_ in (dest_files - orig_files):
        path = os.path.join(dest_binary_dir, file_)
        if os.path.isdir(path):
            shutil.rmtree(path)
        else:
            os.remove(path)

    return dest_binary_dir, ensure_exec_perms(dest_binary_path)
Exemple #24
0
def get_quasar_binary():
    binary = "quasar" + (PLATFORM["os"] == "windows" and ".exe" or "")

    log.info("PLATFORM: %s" % str(PLATFORM))
    binary_dir = os.path.join(ADDON_PATH, "resources", "bin", "%(os)s_%(arch)s" % PLATFORM)
    if PLATFORM["os"] == "android":
        log.info("Detected binary folder: %s" % binary_dir)
        binary_dir_legacy = binary_dir.replace("/storage/emulated/0", "/storage/emulated/legacy")
        if os.path.exists(binary_dir_legacy):
            binary_dir = binary_dir_legacy
        app_id = android_get_current_appid()
        xbmc_data_path = translatePath("special://xbmcbin/").replace('user/0', 'data').replace('cache/apk/assets', 'files/quasar')
        log.info("Trying binary Kodi folder: %s" % xbmc_data_path)
        
        try:                        #Test if there is any permisions problem
            if not os.path.exists(xbmc_data_path):
                os.makedirs(xbmc_data_path)
        except Exception as e:
            log.info("ERROR %s in binary Kodi folder: %s" % (str(e), xbmc_data_path))
        
        if not os.path.exists(xbmc_data_path):
            xbmc_data_path = translatePath("special://xbmcbin/").replace('cache/apk/assets', 'files/quasar')
            log.info("Trying alternative binary Kodi folder: %s" % xbmc_data_path)

            try:                    #Test if there is any permisions problem
                if not os.path.exists(xbmc_data_path):
                    os.makedirs(xbmc_data_path)
            except Exception as e:
                log.info("ERROR %s in alternative binary Kodi folder: %s" % (str(e), xbmc_data_path))

        dest_binary_dir = xbmc_data_path
    else:
        if not PY3:
            dest_binary_dir = os.path.join(translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "bin", "%(os)s_%(arch)s" % PLATFORM)
        else:
            dest_binary_dir = os.path.join(translatePath(ADDON.getAddonInfo("profile")), "bin", "%(os)s_%(arch)s" % PLATFORM)
    
    if PY3 and isinstance(dest_binary_dir, bytes):
        dest_binary_dir = dest_binary_dir.decode("utf8")
    log.info("Using destination binary folder: %s" % dest_binary_dir)
    binary_path = os.path.join(binary_dir, binary)
    dest_binary_path = os.path.join(dest_binary_dir, binary)

    if not os.path.exists(binary_path):
        notify((getLocalizedString(30103) + " %(os)s_%(arch)s" % PLATFORM), time=7000)
        system_information()
        try:
            log.info("Source directory (%s):\n%s" % (binary_dir, os.listdir(os.path.join(binary_dir, ".."))))
            log.info("Destination directory (%s):\n%s" % (dest_binary_dir, os.listdir(os.path.join(dest_binary_dir, ".."))))
        except Exception:
            pass
            #return False, False

    if os.path.isdir(dest_binary_path):
        log.warning("Destination path is a directory, expected previous binary file, removing...")
        try:
            shutil.rmtree(dest_binary_path)
        except Exception as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            #return False, False

    if not os.path.exists(dest_binary_path) or get_quasard_checksum(dest_binary_path) != get_quasard_checksum(binary_path):
        log.info("Updating quasar daemon...")
        try:
            os.makedirs(dest_binary_dir)
        except OSError:
            pass
        try:
            shutil.rmtree(dest_binary_dir)
        except Exception as e:
            log.error("Unable to remove destination path for update: %s" % e)
            system_information()
            #return False, False
        try:
            shutil.copytree(binary_dir, dest_binary_dir)
        except Exception as e:
            log.error("Unable to copy to destination path for update: %s" % e)
            system_information()
            #return False, False

    # Clean stale files in the directory, as this can cause headaches on
    # Android when they are unreachable
    if os.path.exists(dest_binary_dir):
        try:
            dest_files = set(os.listdir(dest_binary_dir))
            orig_files = set(os.listdir(binary_dir))
            log.info("Deleting stale files %s" % (dest_files - orig_files))
            for file_ in (dest_files - orig_files):
                path = os.path.join(dest_binary_dir, file_)
                if os.path.isdir(path):
                    shutil.rmtree(path)
                else:
                    os.remove(path)
        except:
            pass

    return dest_binary_dir, ensure_exec_perms(dest_binary_path)
def start_quasard(**kwargs):
    jsonrpc_failures = 0
    while jsonrpc_enabled() is False:
        jsonrpc_failures += 1
        log.warning("Unable to connect to Kodi's JSON-RPC service, retrying...")
        if jsonrpc_failures > 1:
            time.sleep(5)
            if not jsonrpc_enabled(notify=True):
                log.error("Unable to reach Kodi's JSON-RPC service, aborting...")
                return False
            else:
                break
        time.sleep(3)

    quasar_dir, quasar_binary = get_quasar_binary()

    if quasar_dir is False or quasar_binary is False:
        return False

    lockfile = os.path.join(ADDON_PATH, ".lockfile")
    if os.path.exists(lockfile):
        log.warning("Existing process found from lockfile, killing...")
        try:
            with open(lockfile) as lf:
                pid = int(lf.read().rstrip(" \t\r\n\0"))
            os.kill(pid, 9)
        except Exception as e:
            log.error(repr(e))

        if PLATFORM["os"] == "windows":
            log.warning("Removing library.db.lock file...")
            try:
                library_lockfile = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "library.db.lock")
                os.remove(library_lockfile)
            except Exception as e:
                log.error(repr(e))

    SW_HIDE = 0
    STARTF_USESHOWWINDOW = 1

    args = [quasar_binary]
    kwargs["cwd"] = quasar_dir

    if PLATFORM["os"] == "windows":
        args[0] = getWindowsShortPath(quasar_binary)
        kwargs["cwd"] = getWindowsShortPath(quasar_dir)
        si = subprocess.STARTUPINFO()
        si.dwFlags = STARTF_USESHOWWINDOW
        si.wShowWindow = SW_HIDE
        clear_fd_inherit_flags()
        kwargs["startupinfo"] = si
    else:
        env = os.environ.copy()
        env["LD_LIBRARY_PATH"] = "%s:%s" % (quasar_dir, env.get("LD_LIBRARY_PATH", ""))
        kwargs["env"] = env
        kwargs["close_fds"] = True

    wait_counter = 1
    while xbmc.getCondVisibility('Window.IsVisible(10140)'):
        if wait_counter == 1:
            log.info('Add-on settings currently opened, waiting before starting...')
        if wait_counter > 300:
            break
        time.sleep(1)

    return subprocess.Popen(args, **kwargs)
Exemple #26
0
def binary_stat(p, action, retry=False, init=False, app_response={}):
    if init: log.info('## Binary_stat: action: %s; PID: %s; retry: %s; init: %s; awake: %s; app_r: %s' % \
                    (action, p.pid, retry, init, p.binary_awake, app_response))
    import traceback
    import base64
    import requests
    import json
    import time
    import xbmc
    
    try:
        if action in ['poll', 'communicate']:
            url = p.url_app + '/getBinaryStatus?pid=%s&flushAfterRead=true' % str(p.pid)
            url_alt = p.url_app_alt + '/getBinaryStatus?pid=%s&flushAfterRead=true' % str(p.pid)

        if action == 'killBinary':
            url = p.url_app + '/killBinary?pid=%s' % str(p.pid)
            url_alt = p.url_app_alt + '/killBinary?pid=%s' % str(p.pid)

        url_close = p.url_app + '/terminate'
        cmd_android = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'open', 'about:blank')
        cmd_android_quit = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'quit', 'about:blank')
        cmd_android_close = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'terminate', 'about:blank')
        cmd_android_permissions = 'StartAndroidActivity("%s", "", "%s", "%s")' % (p.app, 'checkPermissions', 'about:blank')

        finished = False
        retry_req = False
        retry_app = False
        stdout_acum = ''
        stderr_acum = ''
        msg = ''
        binary_awake = 0
        binary_awake_safe = 300*1000
        while not finished:
            if not app_response.get('retCode', 0) >= 999:
                try:
                    resp = p.sess.get(url, timeout=5)
                except Exception as e:
                    resp = requests.Response()
                    resp.status_code = str(e)
                
                if resp.status_code != 200 and not retry_req:
                    if action == 'killBinary' or p.monitor.abortRequested():
                        app_response = {'pid': p.pid, 'retCode': 998}
                    else:
                        log.error("## Binary_stat: Invalid app requests response for PID: %s: %s - retry: %s - awake: %s" % \
                                    (p.pid, resp.status_code, retry_req, p.binary_awake))
                        retry_req = True
                        url = url_alt
                        msg += str(resp.status_code)
                        stdout_acum += str(resp.status_code)
                        xbmc.executebuiltin(cmd_android)
                        binary_awake = (int(time.time()) - int(p.binary_time)) * 1000 - binary_awake_safe
                        if binary_awake < binary_awake_safe:
                            binary_awake = 0
                        log.info('## Time.awake: %s; binary_awake: %s; p.binary_awake: %s' % \
                                    ((int(time.time()) - int(p.binary_time))*1000, binary_awake, p.binary_awake))
                        time.sleep(5)
                        continue
                if resp.status_code != 200 and retry_req and app_response.get('retCode', 0) != 999:
                    log.error("## Binary_stat: Invalid app requests response for PID: %s: %s - retry: %s - awake: %s.  Closing Assistant" % \
                                    (p.pid, resp.status_code, retry_req, p.binary_awake))
                    msg += str(resp.status_code)
                    stdout_acum += str(resp.status_code)
                    app_response = {'pid': p.pid, 'retCode': 999}
                    if p.torrest:
                        time.sleep(10)      # let Torrest recover first
                    else:
                        xbmc.executebuiltin(cmd_android_close)
                        time.sleep(5)
                        xbmc.executebuiltin(cmd_android)
                        if binary_awake > binary_awake_safe:
                            if p.binary_awake:
                                if binary_awake < p.binary_awake: p.binary_awake = binary_awake
                            else:
                                p.binary_awake = binary_awake
                                time.sleep(5)
                                log.info('## Time.awake: %s; binary_awake: %s; p.binary_awake: %s' % \
                                            ((int(time.time()) - int(p.binary_time))*1000, binary_awake, p.binary_awake))
                                try:
                                    if not 'awakingInterval' in url: 
                                        url += '&awakingInterval=%s' % p.binary_awake
                                        resp = p.sess.get(url, timeout=5)
                                except:
                                    pass
                                time.sleep(1)
                                continue
                        time.sleep(5)
                        continue

                if resp.status_code == 200:
                    try:
                        app_response = resp.content
                        if PY3 and isinstance(app_response, bytes):
                            app_response = app_response.decode()
                        app_response_save = app_response
                        app_response = re.sub('\n|\r|\t', '', app_response)
                        app_response = json.loads(app_response)
                        test_json = app_response["pid"]
                    except:
                        status_code = resp.content
                        log.error("## Binary_stat: Invalid app response for PID: %s: %s - retry: %s - awake: %s" % \
                                    (p.pid, resp.content, retry_app, p.binary_awake))
                        if retry_app:
                            app_response = {'pid': p.pid}
                            app_response['retCode'] = 999
                            msg += app_response_save
                            stdout_acum += app_response_save
                        else:
                            retry_app = True
                            app_response = {}
                            if not p.torrest:
                                if not 'awakingInterval' in url and (binary_awake > 0 or p.binary_awake > 0):
                                    if p.binary_awake:
                                        if binary_awake and binary_awake < p.binary_awake: p.binary_awake = binary_awake
                                    else:
                                        p.binary_awake = binary_awake
                                    url += '&awakingInterval=%s' % p.binary_awake
                                    log.info('## Time.awake: %s; binary_awake: %s; p.binary_awake: %s' % \
                                        ((int(time.time()) - int(p.binary_time))*1000, binary_awake, p.binary_awake))
                            time.sleep(1)
                            continue
                        
            if app_response.get("pid", 0):
                if app_response.get('output'):
                    stdout_acum += base64.b64decode(app_response['output']).decode('utf-8')
                    msg += base64.b64decode(app_response['output']).decode('utf-8')
                if app_response.get('error'): 
                    stderr_acum += base64.b64decode(app_response['error']).decode('utf-8')
                    msg += base64.b64decode(app_response['error']).decode('utf-8')
                if app_response.get('startDate'): 
                    p.startDate = base64.b64decode(app_response['startDate']).decode('utf-8')
                if app_response.get('endDate'): 
                    p.endDate = base64.b64decode(app_response['endDate']).decode('utf-8')
                if app_response.get('cmd'): 
                    p.cmd_app = base64.b64decode(app_response['cmd']).decode('utf-8')
                if app_response.get('finalCmd'): 
                    p.finalCmd = base64.b64decode(app_response['finalCmd']).decode('utf-8')

                # If still app permissions not allowed, give it a retry
                if 'permission denied' in msg:
                    notify('Accept Assitant permissions', time=15000)
                    time.sleep(5)
                    xbmc.executebuiltin(cmd_android_permissions)
                    time.sleep(10)
                    xbmc.executebuiltin(cmd_android_quit)
                    time.sleep(5)
                
                if msg:
                    try:
                        for line in msg.split('\n'):
                            line += '\n'
                            if PY3 and not isinstance(line, (bytes, bytearray)):
                                line = line.encode('utf-8')
                            p.stdin.write(line)
                            p.stdin.flush()
                    except:
                        pass
            
            p.returncode = None
            if action == 'killBinary' and not app_response.get('retCode', ''):
                app_response['retCode'] = 137
            if app_response.get('retCode', '') or action == 'killBinary' or \
                            (action == 'communicate' and app_response.get('retCode', '') != ''):
                try:
                    p.stdin.flush()
                    p.stdin.close()
                except:
                    pass
                try:
                    p.returncode = int(app_response['retCode'])
                except:
                    p.returncode = app_response['retCode']
                
            if action == 'communicate' and p.returncode is not None:
                log.info("## Binary_stat: communicate Quasar: %s - Returncode: %s" % (p.pid, p.returncode))
                return stdout_acum, stderr_acum
            
            elif action == 'poll':
                if init and msg:
                    #log.warning('## Binary_stat: Quasar initial response: %s' % msg)
                    return True
                return p.returncode
            
            elif action == 'killBinary':
                log.info("## Binary_stat: killBinary Quasar: %s - Returncode: %s" % (p.pid, p.returncode))
                try:
                    if p.monitor.abortRequested():
                        if not p.torrest:
                            try:
                                resp_t = p.sess.get(url_close, timeout=1)
                            except:
                                pass
                    elif p.returncode == 998:
                        if not p.torrest:
                            xbmc.executebuiltin(cmd_android_close)
                        time.sleep(10)
                except:
                    logging.info(traceback.format_exc())
                    time.sleep(1)
                    if not p.torrest:
                        xbmc.executebuiltin(cmd_android_close)
                    time.sleep(2)
                return p
            
            time.sleep(5)
            msg = ''
            app_response = {}

    except:
        log.error(traceback.format_exc())
    return None
Exemple #27
0
def install_app(APP_PARAMS):
    import traceback
    import requests
    import xbmc
    import time
    
    try:
        user_params = {}
        apk_OK = False
        ANDROID_STORAGE = os.getenv('ANDROID_STORAGE')
        if not ANDROID_STORAGE: ANDROID_STORAGE = '/storage'
        LOCAL_DOWNLOAD_PATH = os.path.join(ANDROID_STORAGE, 'emulated', '0', 'Download')
        USER_APP_PATH = os.path.join(ANDROID_STORAGE, 'emulated', '0', 'Android', 'data')

        for user_addon, user_params in list(APP_PARAMS.items()):
            if not user_params['ACTIVE']: continue
                
            for apk_path in user_params['USER_APK']:
                if apk_path.endswith('.apk'):
                    download_path = LOCAL_DOWNLOAD_PATH
                elif user_params['USER_ADDON_STATUS']:
                    download_path = user_params['USER_ADDON_USERDATA']
                else:
                    continue
                
                if apk_path.startswith('http'):
                    try:
                        apk_body = requests.get(apk_path, timeout=10)
                    except Exception as e:
                        apk_body = requests.Response()
                        apk_body.status_code = str(e)
                    if apk_body.status_code != 200:
                        log.error("## Install_app: Invalid app requests response: %s" % (apk_body.status_code))
                        apk_OK = False
                        continue
                    with open(os.path.join(download_path, \
                            os.path.basename(apk_path)), "wb") as f:
                        f.write(apk_body.content)
                    apk_OK = True
                
                else:
                    if os.path.exists(apk_path):
                        shutil.copy(apk_path, download_path)
                        apk_OK = True
                    else:
                        continue
                if not apk_OK:
                    break
            
            if apk_OK:
                log.info("## Install_app: Installing the APK from: %s" % LOCAL_DOWNLOAD_PATH)
                notify('Install your Assistant %s from folder %s' % \
                            (os.path.basename(user_params['USER_APK'][0]), \
                            LOCAL_DOWNLOAD_PATH))
                cmd_android = 'StartAndroidActivity("%s", "", "%s", "%s")' % (user_params['USER_APP'], 'open', 'about:blank')
                cmd_android_permissions = 'StartAndroidActivity("%s", "", "%s", "%s")' % (user_params['USER_APP'], 'checkPermissions', 'about:blank')
                cmd_android_close = 'StartAndroidActivity("%s", "", "%s", "%s")' % (user_params['USER_APP'], 'terminate', 'about:blank')
                xbmc.executebuiltin(cmd_android)
                time.sleep(1)
                
                # Lets give the user 5 minutes to install the app an retry automatically
                for x in range(300):
                    if os.path.exists(os.path.join(USER_APP_PATH, user_params['USER_APP'])):
                        log.info("## Install_app: APP installed: %s" % user_params['USER_APP'])
                        notify('Accept Assistant permissions')
                        log.info("## Install_app: Requesting permissions: %s" % user_params['USER_APP'])
                        time.sleep(5)
                        xbmc.executebuiltin(cmd_android_permissions)
                        time.sleep(15)
                        log.info("## Install_app: closing APP: %s" % user_params['USER_APP'])
                        notify('Accept Assistant permissions')
                        xbmc.executebuiltin(cmd_android_close)
                        log.info("## Install_app: APP closed: %s" % user_params['USER_APP'])
                        time.sleep(10)
                        return user_params
                    
                    xbmc.executebuiltin(cmd_android)
                    time.sleep(1)
                break
    
    except:
        log.info(traceback.format_exc())
        user_params = {}
    return user_params