def dump_version(ret): try: p = platform.platform() except: p = "Could not detect" try: log.info("""Python version: %s dist: %s linux_distribution: %s system: %s machine: %s platform: %s uname: %s version: %s mac_ver: %s """ % ( sys.version.split('\n'), str(platform.dist()), linux_distribution(), platform.system(), platform.machine(), p, platform.uname(), platform.version(), platform.mac_ver() )) except: if ret is not None: log.info("Cannot write detection info. Ret: %s" % (repr(ret))) pass
def Reset(self): for i in self._objects: try: self._objects[i].hide() except: pass log.info("Resetting RPC objects...") self._objects = {}
def wait_for_abortRequested(proc, monitor): monitor.closing.wait() log.info("projectxd: exiting projectxd daemon") try: if proc is not None: proc.terminate() except OSError: pass # Process already exited, nothing to terminate log.info("projectxd: projectxd daemon exited")
def server_thread(): try: bjsonrpc.bjsonrpc_options['threaded'] = True s = bjsonrpc.createserver(host="0.0.0.0", port=JSONRPC_EXT_PORT, handler_factory=projectxRPCServer) log.info("projectx: starting jsonrpc service") s.serve() log.info("projectx: exiting jsonrpc service") except Exception: import traceback map(log.error, traceback.format_exc().split("\n")) raise
def system_information(): build = xbmc.getInfoLabel("System.BuildVersion") log.info("System information: %(os)s_%(arch)s %(version)s" % PLATFORM) log.info("Kodi build version: %s" % build) log.info("OS type: %s" % platform.system()) log.info("uname: %s" % repr(platform.uname())) return PLATFORM
def doDownload(): dbid = getDbId() mediatype = getMediaType() xbmcgui.Dialog().notification(ADDON.getLocalizedString(32009), sys.listitem.getLabel(), xbmcgui.NOTIFICATION_INFO, 3000) log.info("Downloading for: DBID=%s, MediaType=%s" % (dbid, mediatype)) url = "plugin://plugin.video.projectx/context/%s/%s/download" % (mediatype, dbid) log.info("Starting projectx with: %s" % url) xbmc.Player().play(url)
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("projectx", getLocalizedString(30199)) return False
def GetCurrentView(self): skinPath = xbmc.translatePath('special://skin/') xml = os.path.join(skinPath, 'addon.xml') f = xbmcvfs.File(xml) read = f.read() f.close() try: src = re.search('defaultresolution="([^"]+)', read, re.DOTALL).group(1) except: src = re.search('<res.+?folder="([^"]+)', read, re.DOTALL).group(1) src = os.path.join(skinPath, src, 'MyVideoNav.xml') f = xbmcvfs.File(src) read = f.read() f.close() match = re.search('<views>([^<]+)', read, re.DOTALL) if match: views = match.group(1) log.info("Skin's ViewModes: %s" % views) for view in views.split(','): if xbmc.getInfoLabel('Control.GetLabel(%s)' % view): return view
def run(): # Make sure the XBMC jsonrpc server is started. xbmc.startServer(xbmc.SERVER_JSONRPCSERVER, True) # Make the monitor monitor = projectxMonitor() threads = [ threading.Thread(target=server_thread), # JSONRPC thread ] if not ONLY_CLIENT and PLATFORM["fork"]: threads.append( threading.Thread(target=projectxd_thread, args=[monitor])) # projectxd thread for t in threads: t.daemon = True t.start() # XBMC loop while not monitor.abortRequested(): xbmc.sleep(1000) log.info("projectx: exiting projectxd")
def get_platform(): try: binary_platform = ADDON.getSetting("binary_platform") except: binary_platform = "auto" pass build = xbmc.getInfoLabel("System.BuildVersion") kodi_version = int(build.split()[0][:2]) if binary_platform and "auto" not in binary_platform.lower(): custom = binary_platform.split('_') if len(custom) > 1: return { "os": custom[0], "arch": custom[1], "fork": True, "version": "", "kodi": kodi_version, "build": build } ret = { "auto_arch": sys.maxsize > 2 ** 32 and "64-bit" or "32-bit", "arch": sys.maxsize > 2 ** 32 and "x64" or "x86", "os": "", "version": "", "kodi": kodi_version, "build": build, "fork": True, "machine": "", "system": "", "platform": "" } try: ret["os"] = platform.release() except: pass try: ret["machine"] = platform.machine() except: # Default 'machine' for Android can be 'arm' if xbmc.getCondVisibility("system.platform.android"): ret["machine"] = "arm" pass try: ret["system"] = platform.system() except: pass try: ret["platform"] = platform.platform() except: pass if xbmc.getCondVisibility("system.platform.android"): ret["os"] = "android" if "arm" in ret["machine"].lower() or "aarch" in ret["machine"].lower(): ret["arch"] = "arm" if "64" in ret["machine"] and ret["auto_arch"] == "64-bit": ret["arch"] = "arm64" elif xbmc.getCondVisibility("system.platform.linux"): ret["os"] = "linux" if "aarch" in ret["machine"].lower() or "arm64" in ret["machine"].lower(): if xbmc.getCondVisibility("system.platform.linux.raspberrypi"): ret["arch"] = "armv7" elif ret["auto_arch"] == "32-bit": ret["arch"] = "armv7" elif ret["auto_arch"] == "64-bit": ret["arch"] = "arm64" # elif platform.architecture()[0].startswith("32"): # ret["arch"] = "armv6" else: ret["arch"] = "armv7" elif "armv7" in ret["machine"]: ret["arch"] = "armv7" elif "arm" in ret["machine"]: cpuarch = "" if "aarch" in ret["machine"].lower() or "arm" in ret["machine"].lower(): info = cpuinfo() for proc in info.keys(): log.info("CPU: %s=%s" % (proc, info[proc])) model = "" if "model name" in info[proc]: model = info[proc]["model name"].lower() elif "Processor" in info[proc]: model = info[proc]["Processor"].lower() if model: log.info("Exploring model: %s" % model) if "aarch" in model or "arm64" in model or "v8l" in model: cpuarch = "arm64" elif "armv7" in model or "v7l" in model: cpuarch = "armv7" break if cpuarch: log.info("Using CPU info arch: %s" % cpuarch) ret["arch"] = cpuarch else: ret["arch"] = "armv6" elif xbmc.getCondVisibility("system.platform.xbox"): ret["os"] = "windows" ret["arch"] = "x64" ret["fork"] = False elif xbmc.getCondVisibility("system.platform.windows"): ret["os"] = "windows" if ret["machine"].endswith('64'): ret["arch"] = "x64" elif ret["system"] == "Darwin": ret["os"] = "darwin" ret["arch"] = "x64" if "AppleTV" in ret["platform"]: ret["os"] = "ios" ret["arch"] = "armv7" ret["fork"] = False if "64bit" in ret["platform"]: ret["arch"] = "arm64" elif xbmc.getCondVisibility("system.platform.ios"): ret["os"] = "ios" ret["arch"] = "armv7" ret["fork"] = False if "64bit" in ret["platform"]: ret["arch"] = "arm64" # elif xbmc.getCondVisibility("system.platform.osx"): # ret["os"] = "darwin" # ret["arch"] = "x64" # elif xbmc.getCondVisibility("system.platform.ios"): # ret["os"] = "ios" # ret["arch"] = "armv7" return ret
def get_projectx_binary(): global binary_platform binary_platform = get_platform() binary = "projectx" + (binary_platform["os"] == "windows" and ".exe" or "") binary_dir = os.path.join(ADDON_PATH, "resources", "bin", "%(os)s_%(arch)s" % binary_platform) if binary_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 changed binary folder for Android: %s" % binary_dir) app_id = android_get_current_appid() xbmc_data_path = os.path.join("/data", "data", app_id) 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/") 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" % binary_platform) else: dest_binary_dir = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")), "bin", "%(os)s_%(arch)s" % binary_platform) binary_path = os.path.join(binary_dir, binary) dest_binary_path = os.path.join(dest_binary_dir, binary) log.info("Binary detection. Source: %s, Destination: %s" % (binary_path, dest_binary_path)) if not os.path.exists(binary_path): # notify((getLocalizedString(30103) + " %(os)s_%(arch)s" % PLATFORM), time=7000) dialog_ok("LOCALIZE[30347];;" + "%(os)s_%(arch)s" % binary_platform) 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 not os.path.exists(binary_path) or get_projectxd_checksum(dest_binary_path) != get_projectxd_checksum(binary_path) or not filecmp.cmp(dest_binary_path, binary_path, shallow=True): log.info("Updating projectx 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) log.info("Binary detection: [ Source: %s, Destination: %s ]" % (binary_path, dest_binary_path)) return dest_binary_dir, ensure_exec_perms(dest_binary_path)
def projectxd_thread(monitor): crash_count = 0 try: monitor_abort = xbmc.Monitor() # For Kodi >= 14 while not monitor_abort.abortRequested(): log.info("projectxd: starting projectxd") proc = None if hasSubprocess: proc = start_projectxd(stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if not proc: break else: log.info("projectxd: current system is unable to run the binary") break threading.Thread(target=wait_for_abortRequested, args=[proc, monitor]).start() if not hasSubprocess: break if binary_platform["os"] == "windows": while proc.poll() is None: log.info(proc.stdout.readline()) else: # Kodi hangs on some Android (sigh...) systems when doing a blocking # read. We count on the fact that projectx daemon flushes its log # output on \n, creating a pretty clean output import fcntl import select fd = proc.stdout.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) while proc.poll() is None: try: to_read, _, _ = select.select([proc.stdout], [], []) for ro in to_read: line = ro.readline() if line == "": # write end is closed break log.info(line) except IOError: time.sleep(1) # nothing to read, sleep if proc.returncode == 0 or proc.returncode == -9 or monitor_abort.abortRequested(): break if proc.returncode == 5: notify(getLocalizedString(30332), time=3000) else: crash_count += 1 notify(getLocalizedString(30100), time=3000) xbmc.executebuiltin("Dialog.Close(all, true)") system_information() time.sleep(5) if crash_count >= 3: notify(getLocalizedString(30110), time=3000) break except Exception as e: import traceback map(log.error, traceback.format_exc().split("\n")) notify("%s: %s" % (getLocalizedString(30226), repr(e).encode('utf-8'))) raise
def start_projectxd(**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) projectx_dir, projectx_binary = get_projectx_binary() log.info("Binary dir: %s, item: %s " % (projectx_dir, projectx_binary)) if projectx_dir is False or projectx_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 OSError as e: if e.errno != 3: # Ignore: OSError: [Errno 3] No such process log.error(repr(e)) except Exception as e: log.error(repr(e)) if binary_platform["os"] == "windows": try: library_lockfile = os.path.join(xbmc.translatePath(ADDON.getAddonInfo("profile")).decode('utf-8'), "library.db.lock") log.warning("Removing library.db.lock file at %s ..." % library_lockfile) os.remove(library_lockfile) except Exception as e: log.error(repr(e)) SW_HIDE = 0 STARTF_USESHOWWINDOW = 1 args = [projectx_binary] kwargs["cwd"] = projectx_dir if binary_platform["os"] == "windows": args[0] = getWindowsShortPath(projectx_binary) kwargs["cwd"] = getWindowsShortPath(projectx_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" % (projectx_dir, env.get("LD_LIBRARY_PATH", "")) kwargs["env"] = env kwargs["close_fds"] = True wait_counter = 1 log.debug("Checking for visible") 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 log.info("projectxd: start args: %s, kw: %s" % (args, kwargs)) if hasSubprocess: return subprocess.Popen(args, **kwargs) return False
def DialogProgressBG_Cleanup(self): log.info("Cleaning up dialogs") for hwnd in self._objects: if isinstance(self._objects[hwnd], xbmcgui.DialogProgressBG): self._objects[hwnd].close()