def stop(self): """Stop the subproces.""" if self._process and self._process.is_alive(): logger.debug("Terminating process: %s", self._process.name) stop_command = ("stop", None) self.pipe.send(stop_command) self._process.join()
def subprocess(pipe, reuse): # type: (mp.connection, bool) -> None try: # Wait till we receive # commands from the executer while True: # Stop the subprocess # This is required when reusing the subprocess command, data = pipe.recv( ) # type: (str, Tuple[Addon, List[str], LocalRepo, urlparse.SplitResult]) if command == "stop": break # Execute the addon elif command == "execute": addon, deps, cached, url = data # Patch sys.argv to emulate what is expected urllist = [url.scheme, url.netloc, url.path, "", ""] sys.argv = (urlparse.urlunsplit(urllist), 1, "?{}".format(url.query)) try: # Create tesseract to handle kodi module interactions xbmc.session = tesseract = Tesseract(addon, deps, cached, pipe=pipe) tesseract.data.path = url.geturl() # Execute the addon module = addon.entrypoint[1] runpy.run_module(module, run_name="__main__", alter_sys=False) # Addon must have directly raised an error except Exception as e: logger.debug(e, exc_info=True) pipe.send((False, False)) else: # Send back the results from the addon resp = (tesseract.data.succeeded, tesseract.data) pipe.send(resp) # If this subprocess will not be reused then # break from loop to end the process if reuse is False: break except KeyboardInterrupt: pipe.send((False, False)) except Exception as e: logger.error(e, exc_info=True) pipe.send((False, False))
def download(self, dep): # type: (Union[str, Dependency, Addon]) -> Addon if isinstance(dep, str) and dep in self.db: repo, addon = self.db[dep] elif isinstance(dep, (Dependency, Addon)) and dep.id in self.db: repo, addon = self.db[dep.id] # Warn user if we are downloading an older # version than what is required if addon.version < dep.version: warnings.warn( "required version is greater than whats available: {} < {}" .format(addon.version, dep.version), RuntimeWarning) else: raise KeyError("{} not found on remote repo".format(dep)) filename = u"{}-{}.zip".format(addon.id, addon.version) filepath = os.path.join(PACKAGE_DIR, filename) if os.path.exists(filepath): logger.debug("Using cached package: '{}'".format(filename)) else: logger.info("Downloading: '{}'".format(filename)) # Remove old zipfiles before download, if any self.cleanup(addon.id) # Request the addon zipfile from server url_part = "{0}/{1}".format(addon.id, filename) url = "{}/{}".format(repo, url_part) resp = self.session.get(url) # Read and save contents of zipfile to package directory try: with open(filepath, "wb") as stream: for chunk in resp.iter_content(decode_unicode=False): stream.write(chunk) except (OSError, IOError) as e: self.cleanup(addon.id) raise e finally: resp.close() # Remove the old addon directory if exists addon_dir = os.path.join(CACHE_DIR, addon.id) if os.path.exists(addon_dir): shutil.rmtree(addon_dir) self.extract_zip(filepath) self.cached[addon.id] = addon addon.path = addon_dir return addon
def _find_addons(*paths): # type: (str) -> Iterator[Tuple[str, Addon]] """ Search givin paths for kodi addons. Returning a tuple consisting of addon id and Addon object. """ for addons_dir in paths: if os.path.exists(addons_dir): for filename in os.listdir(addons_dir): path = os.path.join(addons_dir, filename, "addon.xml") if os.path.exists(path): addon = Addon.from_file(path) logger.debug("Addon: %s v%s", os.path.join(addons_dir, filename), addon.version) yield addon.id, addon
def process(self): # type: () -> mp.Process if self._process and self._process.is_alive(): logger.debug("Reuseing subprocess: %s", self._process.name) return self._process else: # Create the new process that will execute the addon process = mp.Process(target=subprocess, args=[self.sub_pipe, self.reuse]) process.start() logger.debug("Spawned new subprocess: %s", process.name) if self.reuse: # Save the process for later use self._process = process return process
def update(self): """Check if any cached addon need updating.""" logger.debug("Checking for updates...") for addon in self.cached.values(): if addon.id not in self: # We have a cached addon that no longer exists in the repo warnings.warn( "Cached Addon '{}' no longer available on kodi repo". format(addon.id)) elif addon.version < self.db[addon.id][1].version: self.download(addon) # Create update-check file # with current timestamp timestamp = time.time() with open(self.check_file, "w", encoding="utf8") as stream: json.dump(timestamp, stream)
def __init__(self, cmdargs, cached, display=None): # type: (argparse.Namespace, LocalRepo, Type[displays.BaseDisplay]) -> None self.parent_stack = [] # type: List[KodiData] self.cached = cached self.args = cmdargs # Use custom display object if one is given, else # Use the pretty terminal display if possible, otherwise use the basic non tty display display = display if display else displays.CMDisplay self.display = display(cached, cmdargs) # The process manager self.pm = PManager(cached, self.display.input) # Reverse the list of preselection for faster access self.preselect = list(map(int, cmdargs.preselect)) self.preselect.reverse() # Log the arguments pass to program logger.debug("Command-Line Arguments: %s", vars(cmdargs))