def refreshManualConnections(self): manualConnections = self.getManualConnections() if not manualConnections: return util.LOG("Refreshing {0} manual connections".format( len(manualConnections))) for conn in manualConnections: # Default to http, as the server will need to be signed in for https to work, # so the client should too. We'd also have to allow hostname entry, instead of # IP address for the cert to validate. proto = "http" port = conn.port or "32400" serverAddress = "{0}://{1}:{2}".format(proto, conn.connection, port) request = http.HttpRequest(serverAddress + "/identity") context = request.createRequestContext( "manual_connections", callback.Callable(self.onManualConnectionsResponse)) context.serverAddress = serverAddress context.address = conn.connection context.proto = proto context.port = port context.timeout = 10000 plexapp.APP.startRequest(request, context)
def testReachability(self, server, allowFallback=False): # Check if we will allow the connection test. If this is a fallback connection, # then we will defer it until we "allowFallback" (test insecure connections # after secure tests have completed and failed). Insecure connections will be # tested if the policy "always" allows them, or if set to "same_network" and # the current connection is local and server has (publicAddressMatches=1). allowConnectionTest = not self.isFallback if not allowConnectionTest: insecurePolicy = plexapp.INTERFACE.getPreference("allow_insecure") if insecurePolicy == "always" or (insecurePolicy == "same_network" and server.sameNetwork and self.isLocal): allowConnectionTest = allowFallback server.hasFallback = not allowConnectionTest util.LOG('{0} for {1}'.format( allowConnectionTest and "Continuing with insecure connection testing" or "Insecure connection testing is deferred", server)) else: util.LOG( "Insecure connections not allowed. Ignore insecure connection test for {0}" .format(server)) self.state = self.STATE_INSECURE callable = callback.Callable(server.onReachabilityResult, [self], random.randint(0, 256)) callable.deferCall() return True if allowConnectionTest: if not self.isSecure and ( not allowFallback and server.hasSecureConnections() or server.activeConnection and server.activeConnection.state != self.STATE_REACHABLE and server.activeConnection.isSecure): util.DEBUG_LOG("Invalid insecure connection test in progress") self.request = http.HttpRequest(self.buildUrl(server, "/")) context = self.request.createRequestContext( "reachability", callback.Callable(self.onReachabilityResponse)) context.server = server context.timeout = 10000 util.addPlexHeaders(self.request, server.getToken()) self.hasPendingRequest = plexapp.APP.startRequest( self.request, context) return True return False
def validateToken(self, token, switchUser=False): self.authToken = token self.switchUser = switchUser request = myplexrequest.MyPlexRequest("/users/sign_in.xml") context = request.createRequestContext("sign_in", callback.Callable(self.onAccountResponse)) context.timeout = self.isOffline and 1000 or 10000 plexapp.APP.startRequest(request, context, {})
def removeItem(self, item): request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id) + "/items/" + item.get("playQueueItemID", "-1"), "DELETE") self.addRequestOptions(request) context = request.createRequestContext( "delete", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def __init__(self): signalsmixin.SignalsMixin.__init__(self) # obj.Append(ListenersMixin()) self.serversByUuid = {} self.selectedServer = None self.transcodeServer = None self.channelServer = None self.deferReachabilityTimer = None self.startSelectedServerSearch() self.loadState() plexapp.APP.on("change:user", callback.Callable(self.onAccountChange)) plexapp.APP.on("change:allow_insecure", callback.Callable(self.onSecurityChange)) plexapp.APP.on("change:manual_connections", callback.Callable(self.onManualConnectionChange))
def createRequestContext(self, requestType, callback_=None): context = RequestContext() context.requestType = requestType if callback_: context.callback = callback.Callable(self.onResponse) context.completionCallback = callback_ context.callbackCtx = callback_.context return context
def addItem(self, item, addNext=False, excludeSeedItem=False): request = plexrequest.PlexRequest(self.server, "/playQueues/" + str(self.id), "PUT") request.addParam("uri", item.getItemUri()) request.addParam("next", addNext and "1" or "0") request.addParam("excludeSeedItem", excludeSeedItem and "1" or "0") self.addRequestOptions(request) context = request.createRequestContext( "add", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def sendTimelineToServer(self, timelineType, timeline, time): if not hasattr(timeline.item, 'getServer') or not timeline.item.getServer(): return serverTimeline = self.getServerTimeline(timelineType) # Only send timeline if it's the first, item changes, playstate changes or timer pops itemsEqual = timeline.item and serverTimeline.item and timeline.item.ratingKey == serverTimeline.item.ratingKey if itemsEqual and timeline.state == serverTimeline.state and not serverTimeline.isExpired( ): return serverTimeline.reset() serverTimeline.item = timeline.item serverTimeline.state = timeline.state # Ignore sending timelines for multi part media with no duration obj = timeline.choice if obj and obj.part and obj.part.duration.asInt( ) == 0 and obj.media.parts and len(obj.media.parts) > 1: util.WARN_LOG( "Timeline not supported: the current part doesn't have a valid duration" ) return # It's possible with timers and in player seeking for the time to be greater than the # duration, which causes a 400, so in that case we'll set the time to the duration. duration = timeline.item.duration.asInt() or timeline.duration if time > duration: time = duration params = util.AttributeDict() params["time"] = time params["duration"] = duration params["state"] = timeline.state params["guid"] = timeline.item.guid params["ratingKey"] = timeline.item.ratingKey params["url"] = timeline.item.url params["key"] = timeline.item.key params["containerKey"] = timeline.item.container.address if timeline.playQueue: params["playQueueItemID"] = timeline.playQueue.selectedId path = "/:/timeline" for paramKey in params: if params[paramKey]: path = http.addUrlParam( path, paramKey + "=" + urllib.quote(str(params[paramKey]))) request = plexrequest.PlexRequest(timeline.item.getServer(), path) context = request.createRequestContext( "timelineUpdate", callback.Callable(self.onTimelineResponse)) context.playQueue = timeline.playQueue plexapp.APP.startRequest(request, context)
def createPlayQueueForId(id, server=None, contentType=None): obj = PlayQueue(server, contentType) obj.id = id request = plexrequest.PlexRequest(server, "/playQueues/" + str(id)) request.addParam("own", "1") obj.addRequestOptions(request) context = request.createRequestContext("own", callback.Callable(obj.onResponse)) plexapp.APP.startRequest(request, context) return obj
def refreshResources(self, force=False): if force: plexapp.SERVERMANAGER.resetLastTest() request = myplexrequest.MyPlexRequest("/pms/resources") context = request.createRequestContext("resources", callback.Callable(self.onResourcesResponse)) context.timeout = plexapp.ACCOUNT.isOffline and 1000 or 10000 if plexapp.ACCOUNT.isSecure: request.addParam("includeHttps", "1") plexapp.APP.startRequest(request, context)
def moveItem(self, item, after): if after: query = "?after=" + after.get("playQueueItemID", "-1") else: query = "" request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id) + "/items/" + item.get("playQueueItemID", "-1") + "/move" + query, "PUT") self.addRequestOptions(request) context = request.createRequestContext( "move", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def deferUpdateReachability(self, addTimer=True, logInfo=True): if addTimer and not self.deferReachabilityTimer: self.deferReachabilityTimer = plexapp.createTimer( 1000, callback.Callable(self.onDeferUpdateReachabilityTimer), repeat=True) plexapp.APP.addTimer(self.deferReachabilityTimer) else: if self.deferReachabilityTimer: self.deferReachabilityTimer.reset() if self.deferReachabilityTimer and logInfo: util.LOG( 'Defer update reachability for all devices a few seconds: GDMactive={0}' .format(gdm.DISCOVERY.isActive()))
def setShuffle(self, shuffle=None): if shuffle is None: shuffle = not self.isShuffled if self.isShuffled == shuffle: return if shuffle: command = "/shuffle" else: command = "/unshuffle" # Don't change self.isShuffled, it'll be set in OnResponse if all goes well request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id) + command, "PUT") self.addRequestOptions(request) context = request.createRequestContext( "shuffle", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def startRequest(self, request, context, body=None, contentType=None): context.request = request started = request.startAsync(body=body, contentType=contentType, context=context) if started: requestID = context.request.getIdentity() self.pendingRequests[requestID] = context if context.timeout: request.timer = createTimer( context.timeout, callback.Callable(self.onRequestTimeout, forcedArgs=[context])) self.addTimer(request.timer) elif context.callback: context.callback(None, context) return started
def loadState(self): # Look for the new JSON serialization. If it's not there, look for the # old token and Plex Pass values. plexapp.APP.addInitializer("myplex") jstring = plexapp.INTERFACE.getRegistry("MyPlexAccount", None, "myplex") if jstring: try: obj = json.loads(jstring) except: util.ERROR() obj = None if obj: self.ID = obj.get('ID') or self.ID self.title = obj.get('title') or self.title self.username = obj.get('username') or self.username self.email = obj.get('email') or self.email self.authToken = obj.get('authToken') or self.authToken self.pin = obj.get('pin') or self.pin self.isPlexPass = obj.get('isPlexPass') or self.isPlexPass self.isManaged = obj.get('isManaged') or self.isManaged self.isAdmin = obj.get('isAdmin') or self.isAdmin self.isSecure = obj.get('isSecure') or self.isSecure self.isProtected = bool(obj.get('pin')) self.adminHasPlexPass = obj.get( 'adminHasPlexPass') or self.adminHasPlexPass if self.authToken: request = myplexrequest.MyPlexRequest("/users/account") context = request.createRequestContext( "account", callback.Callable(self.onAccountResponse)) plexapp.APP.startRequest(request, context) else: plexapp.APP.clearInitializer("myplex")
def refresh(self, force=True, delay=False, wait=False): # Ignore refreshing local PQs if self.isLocal(): return if wait: self.responded = False self.initialized = False # We refresh our play queue if the caller insists or if we only have a # portion of our play queue loaded. In particular, this means that we don't # refresh the play queue if we're asked to refresh because a new track is # being played but we have the entire album loaded already. if force or self.isWindowed(): if delay: # We occasionally want to refresh the PQ in response to moving to a # new item and starting playback, but if we refresh immediately: # we probably end up refreshing before PMS realizes we've moved on. # There's no great solution, but delaying our refresh by just a few # seconds makes us much more likely to get an accurate window (and # accurate selected IDs) from PMS. if not self.refreshTimer: self.refreshTimer = plexapp.createTimer( 5000, self.onRefreshTimer) plexapp.APP.addTimer(self.refreshTimer) else: request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id)) self.addRequestOptions(request) context = request.createRequestContext( "refresh", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context) if wait: return self.waitForInitialization()
def createRemotePlayQueue(item, contentType, options, args): util.DEBUG_LOG('Creating remote playQueue request...') obj = PlayQueue(item.getServer(), contentType, options) # The item's URI is made up of the library section UUID, a descriptor of # the item type (item or directory), and the item's path, URL-encoded. uri = "library://" + item.getLibrarySectionUuid() + "/" itemType = item.isDirectory() and "directory" or "item" path = None if not options.key: # if item.onDeck and len(item.onDeck) > 0: # options.key = item.onDeck[0].getAbsolutePath("key") # el if not item.isDirectory(): options.key = item.get("key") # If we're asked to play unwatched, ignore the option unless we are unwatched. options.unwatched = options.unwatched and item.isUnwatched() # TODO(schuyler): Until we build postplay, we're not allowed to queue containers for episodes. if item.type == "episode": options.context = options.CONTEXT_SELF elif item.type == "movie": if not options.extrasPrefixCount and not options.resume: options.extrasPrefixCount = plexapp.INTERFACE.getPreference( "cinema_trailers", 0) # How exactly to construct the item URI depends on the metadata type, though # whenever possible we simply use /library/metadata/:id. if item.isLibraryItem() and not item.isLibraryPQ: path = "/library/metadata/" + item.ratingKey else: path = item.getAbsolutePath("key") if options.context == options.CONTEXT_SELF: # If the context is specifically for just this item,: just use the # item's key and get out. pass elif item.type == "playlist": path = None uri = item.get("ratingKey") options.isPlaylist = True elif item.type == "track": # TODO(rob): Is there ever a time the container address is wrong? If we # expect to play a single track,: use options.CONTEXT_SELF. path = item.container.address or "/library/metadata/" + item.get( "parentRatingKey", "") itemType = "directory" elif item.isPhotoOrDirectoryItem(): if item.type == "photoalbum" or item.parentKey: path = item.getParentPath(item.type == "photoalbum" and "key" or "parentKey") itemType = "item" elif item.isDirectory(): path = item.getAbsolutePath("key") else: path = item.container.address itemType = "directory" options.key = item.getAbsolutePath("key") elif item.type == "episode": path = "/library/metadata/" + item.get("grandparentRatingKey", "") itemType = "directory" options.key = item.getAbsolutePath("key") # elif item.type == "show": # path = "/library/metadata/" + item.get("ratingKey", "") if path: if args: path += util.joinArgs(args) util.DEBUG_LOG("playQueue path: " + str(path)) if "/search" not in path: # Convert a few params to the PQ spec convert = {'type': "sourceType", 'unwatchedLeaves': "unwatched"} for key in convert: regex = re.compile("(?i)([?&])" + key + "=") path = regex.sub("\1" + convert[key] + "=", path) util.DEBUG_LOG("playQueue path: " + str(path)) uri = uri + itemType + "/" + urllib.quote_plus(path) util.DEBUG_LOG("playQueue uri: " + str(uri)) # Create the PQ request request = plexrequest.PlexRequest(obj.server, "/playQueues") request.addParam(not options.isPlaylist and "uri" or "playlistID", uri) request.addParam("type", contentType) # request.addParam('X-Plex-Client-Identifier', plexapp.INTERFACE.getGlobal('clientIdentifier')) # Add options we pass once during PQ creation if options.shuffle: request.addParam("shuffle", "1") options.key = None else: request.addParam("shuffle", "0") if options.key: request.addParam("key", options.key) # Add options we pass every time querying PQs obj.addRequestOptions(request) util.DEBUG_LOG('Initial playQueue request started...') context = request.createRequestContext("create", callback.Callable(obj.onResponse)) plexapp.APP.startRequest(request, context, body='') return obj