def __init__(self, f, *args, **kwargs): self.batchname = f.__name__ self.__function = f self.__functionArgs = args self.__functionKwargs = kwargs try: for item in inspect.stack(): if progressive_load.loaders.has_key(item[3]): self.loadername = item[3] progressive_load.loaders[self.loadername].batches.append(self) return raise except: PMS.Log("(Framework) ERROR: Unable to find a progressive loader for the batch named '%s'" % self.batchname) return
def __save(): global __prefs global __prefsPath Thread.Lock("Framework.Prefs", addToLog=False) try: userPrefs = XML.Element("PluginPreferences") for pref in __prefs: if pref.has_key("value"): userPrefs.append(XML.Element(pref["id"], pref["value"])) f = open(__prefsPath, "w") f.write(XML.StringFromElement(userPrefs)) f.close() PMS.Log("(Framework) Saved the user preferences file") finally: Thread.Unlock("Framework.Prefs", addToLog=False)
def __call__(self, f): self.name = f.__name__ if progressive_load.loaders.has_key(self.name): self = progressive_load.loaders[self.name] else: PMS.Log("(Framework) Started a new progressive loader named '%s'" % self.name) self.__function = f self.last_fetch_time = Datetime.Now() progressive_load.loaders[self.name] = self Thread.Create(progressive_load.__runLoader, self) i = 0 while i < 10 and len(self.container) == 0: Thread.Sleep(0.1) return self.getContainer()
def __save(): global __saveScheduled Thread.Lock("Framework.HTTPCache", addToLog=False) try: # Save the cache Data.__pickle("%s/HTTPCache" % Data.__dataPath, __cache) # Save the cookie jar if __cookieJar is not None: __cookieJar.save("%s/HTTPCookies" % Data.__dataPath) finally: __saveScheduled = False Thread.Unlock("Framework.HTTPCache", addToLog=False) PMS.Log("(Framework) Saved shared HTTP data")
def __init__(self, f, *args, **kwargs): self.taskname = f.__name__ self.__function = f self.__args = args self.__kwargs = kwargs try: for item in inspect.stack(): if parallel.tasksets.has_key(item[3]): self.tasksetname = item[3] parallel.tasksets[self.tasksetname].append(self) return raise except: PMS.Log("(Framework) ERROR: Unable to find a task set for the task named '%s'" % self.taskname) return
def __checkFrameworkCompatibility(): try: lastFrameworkVersion = Dict.Get("Framework.LastCompatibilityVersion") if lastFrameworkVersion != PMS.FrameworkCompatibilityVersion: raise return True except: PMS.Log( "(Framework) Data stored by an earlier framework version has been removed due to incompatibilities." ) if os.path.exists(Prefs.__prefsPath): os.unlink(Prefs.__prefsPath) if os.path.exists(Database.__databasePath): os.unlink(Database.__databasePath) shutil.rmtree(Data.__dataPath) os.makedirs(Data.__dataItemPath) Dict.__loadDefaults() Dict.__save(addToLog=False) return False
def __init__(self, key, title, prompt, subtitle=None, summary=None, thumb=None, art=None, **kwargs): PMS.Log( "(Framework) WARNING: SearchDirectoryItem is deprecated. Use InputDirectoryItem instead." ) InputDirectoryItem.__init__(self, key=key, title=title, prompt=prompt, subtitle=subtitle, summary=summary, thumb=thumb, art=art, **kwargs)
def __autoUpdateCachedPages(): global __cache for url in __cache: item = __cache[url] if item.has_key("UpdateTime"): # Check whether the page would expire before the next cache update is triggered if item["UpdateTime"] < (Datetime.Now() + Datetime.Delta( seconds=__autoUpdateCacheTime)): if item.has_key("Headers"): headers = item["Headers"] else: headers = {} PMS.Log( "(Framework) Automatically updating the cached copy of %s" % url) Request(url, headers=headers, cacheTime=item["CacheTime"], autoUpdate=True, encoding=item["Encoding"], errors=item["Errors"], addToLog=False)
def SetDefaultLocale(loc="en-us"): global defaultLangDict global defaultCountryDict global __defaultLocale global CurrentLocale if loc == __defaultLocale: return __defaultLocale = loc pos = __defaultLocale.find("-") if pos > -1: lang = __defaultLocale[:pos] langPath = os.path.join(Plugin.__bundlePath, "Contents/Strings/%s.json" % lang) if os.path.exists(langPath): f = open(langPath, "r") defaultLangDict = JSON.ObjectFromString(f.read()) f.close() PMS.Log("(Framework) Loaded %s strings" % lang) else: PMS.Log("(Framework) Couldn't find %s strings" % lang) locPath = os.path.join(Plugin.__bundlePath, "Contents/Strings/%s.json" % __defaultLocale) if os.path.exists(locPath): f = open(locPath, "r") defaultCountryDict = JSON.ObjectFromString(f.read()) f.close() PMS.Log("(Framework) Loaded %s strings" % __defaultLocale) else: PMS.Log("(Framework) Couldn't find %s strings" % __defaultLocale) else: langPath = os.path.join(Plugin.__bundlePath, "Contents/Strings/%s.json" % __defaultLocale) if os.path.exists(langPath): f = open(langPath, "r") defaultLangDict = JSON.ObjectFromString(f.read()) f.close() PMS.Log("(Framework) Loaded %s strings" % __defaultLocale) else: PMS.Log("(Framework) Couldn't find %s strings" % __defaultLocale) if CurrentLocale == None: CurrentLocale = loc
def __loadLocale(loc): global CurrentLocale if loc == CurrentLocale: return None global langDict global countryDict pos = loc.find("-") if pos > -1: lang = loc[:pos] langPath = os.path.join(Plugin.__bundlePath, "Contents/Strings/%s.json" % lang) if os.path.exists(langPath): f = open(langPath, "r") langDict = JSON.ObjectFromString(f.read()) f.close() PMS.Log("(Framework) Loaded %s strings" % lang) else: PMS.Log("(Framework) Couldn't find %s strings" % lang) locPath = os.path.join(Plugin.__bundlePath, "Contents/Strings/%s.json" % loc) if os.path.exists(locPath): f = open(locPath, "r") countryDict = JSON.ObjectFromString(f.read()) f.close() PMS.Log("(Framework) Loaded %s strings" % loc) else: PMS.Log("(Framework) Couldn't find %s strings" % loc) else: langPath = os.path.join(Plugin.__bundlePath, "Contents/Strings/%s.json" % loc) if os.path.exists(langPath): f = open(langPath, "r") langDict = JSON.ObjectFromString(f.read()) f.close() PMS.Log("(Framework) Loaded %s strings" % loc) else: PMS.Log("(Framework) Couldn't find %s strings" % loc) CurrentLocale = loc
def __scanModules(): global __modBundled global __modLibs for path, dirs, files in os.walk( os.path.join(__bundlePath, "Contents/Code")): for d in dirs: __modBundled.append(d) for f in files: if f.endswith(".py"): if f != "__init__.py": __modBundled.append(f[:-3]) elif not f.endswith(".pyc"): PMS.Log( "(Framework) WARNING: Non-code file named '%s' found inside the Code directory. Future framework versions will abort at this point." % f) for path, dirs, files in os.walk(os.path.join(sys.path[0], "../Libraries")): for d in dirs: __modLibs.append(d) for f in files: if f != "__init__.py" and f.endswith(".py"): __modLibs.append(f[:-3])
def SaveObject(itemName, obj): __pickle(__path(itemName), obj) PMS.Log("(Framework) Saved the data item named '%s' as an object" % itemName)
def Save(itemName, data): __write(__path(itemName), data) PMS.Log("(Framework) Saved the data item named '%s'" % itemName)
def __rollback(): global __changed __db.rollback() PMS.Log("(Framework) Rolled back the database") __changed = False
def __commit(): global __changed __db.commit() PMS.Log("(Framework) Committed the database") __changed = False
def LoadShared(itemName, binary=False): data = __load("%s/%s" % (__sharedResourcePath, itemName), binary) if data is not None: PMS.Log("(Framework) Loaded shared resource named '%s'" % itemName) return data
def __exit(): if Database.__db is not None: Database.__db.close() PMS.Log("(Framework) Plug-in stopped") sys.exit()
def __run(_bundlePath): # # Initializes the framework, verifies the plug-in & extracts information, then enters a # run loop for handling requests. # global Identifier global Debug global __bundlePath global __pluginModule global __logFilePath global __requestHandlers global LastPrefix FirstRun = False random.seed() # Set up the support file paths pmsPath = "%s/Library/Application Support/Plex Media Server" % os.environ[ "HOME"] supportFilesPath = "%s/Plug-in Support" % pmsPath frameworkSupportFilesPath = "%s/Framework Support" % pmsPath logFilesPath = "%s/Library/Logs/PMS Plugin Logs" % os.environ["HOME"] # Make sure framework directories exist def checkpath(path): try: if not os.path.exists(path): os.makedirs(path) except: pass checkpath("%s/Preferences" % supportFilesPath) checkpath("%s/Databases" % supportFilesPath) checkpath(logFilesPath) checkpath(frameworkSupportFilesPath) # Set the bundle path __bundlePath = _bundlePath.rstrip('/') # Add the bundle path to the system path, including any libraries if os.path.isdir("%s/Contents" % __bundlePath): sys.path.append("%s/Contents" % __bundlePath) if os.path.isdir("%s/Contents/Libraries" % __bundlePath): sys.path.append("%s/Contents/Libraries" % __bundlePath) else: print "Couldn't find bundle directory" return None # Open the Info.plist file f = open("%s/Contents/Info.plist" % __bundlePath, "r") infoplist = XML.ElementFromString(f.read()) f.close() if infoplist is None: print "Couldn't load Info.plist file from plug-in" return # Get the plug-in identifier Identifier = infoplist.xpath( '//key[text()="CFBundleIdentifier"]//following-sibling::string/text()' )[0] if Identifier is None: print "Invalid Info.plist file in plug-in" return None # Set up the log file __logFilePath = "%s/%s.log" % (logFilesPath, Identifier) if os.path.exists(__logFilePath): if os.path.exists("%s.old" % __logFilePath): os.remove("%s.old" % __logFilePath) os.rename(__logFilePath, "%s.old" % __logFilePath) # Now we can start logging PMS.Log("(Framework) Bundle verification complete", False) # Check whether debugging is enabled try: _debug = infoplist.xpath( '//key[text()="PlexPluginDebug"]//following-sibling::string/text()' )[0] if _debug == "1": Debug = True PMS.Log("(Framework) Debugging is enabled") except: pass # Log the system encoding (set during bootstrap) PMS.Log("(Framework) Default encoding is " + sys.getdefaultencoding()) # Set up framework paths Prefs.__prefsPath = "%s/Preferences/%s.xml" % (supportFilesPath, Identifier) Data.__dataPath = "%s/Data/%s" % (supportFilesPath, Identifier) Data.__dataItemPath = "%s/DataItems" % Data.__dataPath if not os.path.isdir(Data.__dataItemPath): FirstRun = True os.makedirs(Data.__dataItemPath) Resource.__resourcePath = "%s/Contents/Resources" % __bundlePath Helper.__helperPath = "%s/Contents/Helpers" % __bundlePath Resource.__sharedResourcePath = "%s/Plug-ins/Framework.bundle/Contents/Resources/Versions/1/Resources" % pmsPath Database.__databasePath = "%s/Databases/%s.db" % (supportFilesPath, Identifier) os.chdir(Data.__dataItemPath) Locale.SetDefaultLocale() PMS.Log("(Framework) Configured framework modules") # Attempt to import the plug-in module - if debugging is enabled, don't catch exceptions if Debug: import Code as _plugin PMS.Log("(Framework) Imported plug-in module") else: try: import Code as _plugin PMS.Log("(Framework) Imported plug-in module") except ImportError: PMS.Log("(Framework) Couldn't import plug-in from bundle") __exit() return # Load the list of trusted plug-ins _trusted = [] try: _trustedJSON = Resource.LoadShared("trust.json") if _trustedJSON: _trusted = JSON.ObjectFromString(_trustedJSON) except: pass # Populate the permission lists __setupPermissionLists() # Register the plug-in with the framework __pluginModule = _plugin # Check the imported module to make sure nothing untoward is happening! if Identifier in _trusted: PMS.Log("(Framework) Plug-in is trusted, skipping module check") else: __scanModules() _allowed = [] for n in PMS.__dict__: if n[0] != "_": if type(PMS.__dict__[n]).__name__ == "module": _allowed.append(n) for n in __modWhitelist: _allowed.append(n) __checkModule(_plugin, _allowed) PMS.Log("(Framework) Checked module imports") # Initialize the framework modules Dict.__load() if not FirstRun: __checkFrameworkCompatibility() Prefs.__load() HTTP.__loadCookieJar() HTTP.__loadCache() PMS.Log("(Framework) Initialized framework modules") # Call the plug-in's Start method PMS.Log("(Framework) Attempting to start the plug-in...") __call(__pluginModule.Start) PMS.Log("(Framework) Plug-in started", False) # Start timers __startCacheManager(firstRun=FirstRun) PMS.Log("(Framework) Entering run loop") # Enter a run loop to handle requests while True: try: # Read the input path = raw_input() path = path.lstrip("GET ").strip() LastPrefix = None # Read headers headers = {} stop = False while stop == False: line = raw_input() if len(line) == 1: stop = True else: split = string.split(line.strip(), ":", maxsplit=1) if len(split) == 2: headers[split[0].strip()] = split[1].strip() # Set the locale if headers.has_key("X-Plex-Language"): loc = headers["X-Plex-Language"].lower() Locale.__loadLocale(loc) # Set the version if headers.has_key("X-Plex-Version"): Client.__setVersion(headers["X-Plex-Version"]) # Extract arguments kwargs = {} mpath = path if path.find("?") >= 0: parts = path.split("?") mpath = parts[0] args = parts[1].split("&") for arg in args: kwarg = arg.split("=") if len(kwarg) == 2: name = urllib.unquote(kwarg[0]) value = urllib.unquote(kwarg[1]) kwargs[name] = value if mpath[-1] == "/": mpath = mpath[:-1] # Split the path into components and decode. pathNouns = path.split('/') pathNouns = [urllib.unquote(p) for p in pathNouns] # If no input was given, return an error if len(pathNouns) <= 1: __return("%s\r\n\r\n" % PMS.Error['BadRequest']) # Otherwise, attempt to handle the request else: result = None pathNouns.pop(0) count = len(pathNouns) if pathNouns[-1] == "": pathNouns.pop(len(pathNouns) - 1) PMS.Log("(Framework) Handling request : %s" % path, False) # Check for a management request if pathNouns[0] == ":": result = __handlePMSRequest(pathNouns, path, **kwargs) else: handler = None isPrefixHandler = False # See if there's a prefix handler available for key in __prefixHandlers: if mpath.count(key, 0, len(key)) == 1: LastPrefix = key if mpath in __prefixHandlers: handler = __prefixHandlers[mpath]["handler"] isPrefixHandler = True else: # Check each request handler to see if it handles the current prefix popped = False for key in __requestHandlers: if handler is None: if path.count(key, 0, len(key)) == 1: # Remove the prefix from the path keyNounCount = len(key.split('/')) - 1 for i in range(keyNounCount): pathNouns.pop(0) count = count - keyNounCount # Find the request handler handler = __requestHandlers[key]["handler"] LastPrefix = key popped = True # If no path request handler was found, make sure we still pop the prefix so internal requests work for key in __prefixHandlers: if popped == False: if mpath.count(key, 0, len(key)) == 1: keyNounCount = len(key.split('/')) - 1 for i in range(keyNounCount): pathNouns.pop(0) popped = True # Check whether we should handle the request internally handled = False if count > 0: if pathNouns[0] == ":": handled = True result = __handleInternalRequest( pathNouns, path, **kwargs) # Check if the App Store has flagged the plug-in as broken if os.path.exists( os.path.join(frameworkSupportFilesPath, "%s.broken" % Identifier)): #TODO: Localise this bit, use message from the App Store if available handled = True result = PMS.Objects.MessageContainer( "Please try again later", "This plug-in is currently unavailable") PMS.Log("(Framework) Plug-in is flagged as broken") # If the request hasn't been handled, and we have a valid request handler, call it else: if not handled and handler is not None: if isPrefixHandler: result = handler(**kwargs) else: result = handler(pathNouns, path, **kwargs) # If the request wasn't handled, return an error if result == None: PMS.Log("(Framework) Request not handled by plug-in", False) response = "%s\r\n\r\n" % PMS.Error['NotFound'] # If the plugin returned an error, return it to PMS elif result in PMS.Error.values(): PMS.Log( "(Framework) Plug-in returned an error : %s" % result, False) response = "%s\r\n" % result # Otherwise, check if a valid object was returned, and return the result elif __objectManager.ObjectHasBase(result, Objects.Object): PMS.Log("(Framework) Response OK") resultStr = result.Content() resultStatus = result.Status() resultHeaders = result.Headers() if resultStr is not None: resultLen = len(resultStr) if resultLen > 0: resultHeaders += "Content-Length: %i\r\n" % resultLen resultStr = "\r\n%s" % resultStr else: resultStr = "" response = str("%s\r\n%s" % (resultStatus, resultHeaders)) + str( resultStr) + str("\r\n") __return(response) # If a KeyboardInterrupt (SIGINT) is raised, stop the plugin except KeyboardInterrupt: # Save data & exit __saveData() __exit() except EOFError: # Save data & exit __saveData() __exit() # If another exception is raised, deal with the problem except: __except() __return("%s\r\n\r\n" % PMS.Error['InternalError']) # Make sure the plugin's data is saved finally: __saveData()
def ConnectRoute_inner(f): if f.__name__ not in controllers: controllers[f.__name__] = f routemap.connect(None, path, controller=f.__name__, action=action, **kwargs) PMS.Log("(Framework) Created a new route: %s => %s" % (path, f.__name__)) return f
def SetTimeout(timeout): socket.setdefaulttimeout(timeout) PMS.Log("(Framework) Set the default socket timeout to %.1f seconds" % timeout)
def Request(url, values=None, headers={}, cacheTime=None, autoUpdate=False, encoding=None, errors=None, addToLog=True): global __cache global __saveScheduled now = Datetime.Now() # If no cache time is given, use the default if cacheTime is None: cacheTime = __cacheTime # Attempt to return a cached copy, fetching again if an exception occurs try: # Make sure we don't cache POST requests if values == None and cacheTime > 0: if __cache.has_key(url): cachedAt = __cache[url]["CheckTime"] expiresAt = cachedAt + Datetime.Delta(seconds=cacheTime) if Datetime.Now() < expiresAt: if addToLog: PMS.Log( "(Framework) Loaded %s from the cache (expires at %s)" % (url, expiresAt)) return __cache[url]["Content"] except: if addToLog: PMS.Log( "(Framework) Couldn't load %s from the cache, attempting to fetch again." ) # Try to fetch the page from the server try: # Encode the values data = None if values is not None: data = urllib.urlencode(values) h = __headers.copy() for header in headers: h[header] = headers[header] request = urllib2.Request(url, data, h) f = urllib2.urlopen(request) response = f.read() # If the response is gzipped, unzip it if f.headers.get('Content-Encoding') == "gzip": if addToLog: PMS.Log("(Framework) Received gzipped response from %s" % url) stream = StringIO.StringIO(response) gzipper = gzip.GzipFile(fileobj=stream) response = gzipper.read() else: if addToLog: PMS.Log("(Framework) Received response from %s" % url) # Try to decode the response if manually specified try: if not (encoding is None and errors is None): if encoding is None: encoding = "utf8" if errors is None: errors = "strict" response = str( response.decode(encoding, errors).encode("utf8", errors)) except: if addToLog: PMS.Log( "(Framework) Unable to decode response from '%s' with codec %s" % (url, encoding)) # Handle common errors except urllib2.HTTPError: PMS.Log("(Framework) HTTPError when requesting '%s'" % url) return None except urllib2.URLError: PMS.Log("(Framework) URLError when requesting '%s'" % url) return None Thread.Lock("Framework.HTTPCache", addToLog=False) try: # Cache the data if required if response is not None and cacheTime > 0: item = {} item["Content"] = response item["CheckTime"] = Datetime.Now() if autoUpdate: item["UpdateTime"] = Datetime.Now() + Datetime.Delta( seconds=cacheTime) item["CacheTime"] = cacheTime item["Headers"] = headers item["Encoding"] = encoding item["Errors"] = errors __cache[url] = item if addToLog: PMS.Log("(Framework) Cached response from %s" % url) if not __saveScheduled: Thread.CreateTimer(5, __save) __saveScheduled = True finally: Thread.Unlock("Framework.HTTPCache", addToLog=False) # Return the data return response
def __startTimedThread(function, *args, **kwargs): if function.__name__[0] != "_": PMS.Log( "(Framework) Started a new thread named '%s' after a timed interval" % function.__name__) function(*args, **kwargs)