Remove unused lib plex

pull/818/head
Michiel van Baak 4 years ago
parent e815f47e4f
commit 2ec35f61be

@ -1,11 +0,0 @@
import logging
log = logging.getLogger(__name__)
__version__ = '0.7.0'
try:
from plex.client import Plex
except Exception as ex:
log.warn('Unable to import submodules - %s', ex, exc_info=True)

@ -1,116 +0,0 @@
from plex.core.configuration import ConfigurationManager
from plex.core.http import HttpClient
from plex.helpers import has_attribute
from plex.interfaces import construct_map
from plex.interfaces.core.base import InterfaceProxy
from plex.lib.six import add_metaclass
from plex.objects.core.manager import ObjectManager
import logging
import socket
log = logging.getLogger(__name__)
class PlexClient(object):
__interfaces = None
def __init__(self):
# Construct interfaces
self.http = HttpClient(self)
self.configuration = ConfigurationManager()
self.__interfaces = construct_map(self)
# Discover modules
ObjectManager.construct()
@property
def base_url(self):
host = self.configuration.get('server.host', '127.0.0.1')
port = self.configuration.get('server.port', 32400)
return 'http://%s:%s' % (host, port)
def __getitem__(self, path):
parts = path.strip('/').split('/')
cur = self.__interfaces
parameters = []
while parts and type(cur) is dict:
key = parts.pop(0)
if key == '*':
key = None
elif key not in cur:
if None in cur:
parameters.append(key)
cur = cur[None]
continue
return None
cur = cur[key]
while type(cur) is dict:
cur = cur.get(None)
if parts:
parameters.extend(parts)
if parameters:
return InterfaceProxy(cur, parameters)
return cur
def __getattr__(self, name):
interface = self.__interfaces.get(None)
if not interface:
raise Exception("Root interface not found")
return getattr(interface, name)
class PlexMeta(type):
@property
def client(cls):
if cls._client is None:
cls.construct()
return cls._client
def __getattr__(self, name):
if has_attribute(self, name):
return super(PlexMeta, self).__getattribute__(name)
if self.client is None:
self.construct()
return getattr(self.client, name)
def __setattr__(self, name, value):
if has_attribute(self, name):
return super(PlexMeta, self).__setattr__(name, value)
if self.client is None:
self.construct()
setattr(self.client, name, value)
def __getitem__(self, key):
if self.client is None:
self.construct()
return self.client[key]
@add_metaclass(PlexMeta)
class Plex(object):
_client = None
@classmethod
def construct(cls):
cls._client = PlexClient()

@ -1,115 +0,0 @@
class ConfigurationManager(object):
def __init__(self):
self.stack = [
Configuration(self)
]
@property
def current(self):
return self.stack[-1]
@property
def defaults(self):
return self.stack[0]
def authentication(self, token):
return Configuration(self).authentication(token)
def cache(self, **definitions):
return Configuration(self).cache(**definitions)
def client(self, identifier, product, version):
return Configuration(self).client(identifier, product, version)
def device(self, name, system):
return Configuration(self).device(name, system)
def headers(self, headers):
return Configuration(self).headers(headers)
def platform(self, name, version):
return Configuration(self).platform(name, version)
def server(self, host='127.0.0.1', port=32400):
return Configuration(self).server(host, port)
def get(self, key, default=None):
for x in range(len(self.stack) - 1, -1, -1):
value = self.stack[x].get(key)
if value is not None:
return value
return default
def __getitem__(self, key):
return self.get(key)
def __setitem__(self, key, value):
self.current[key] = value
class Configuration(object):
def __init__(self, manager):
self.manager = manager
self.data = {}
def authentication(self, token):
self.data['authentication.token'] = token
return self
def cache(self, **definitions):
for key, value in definitions.items():
self.data['cache.%s' % key] = value
return self
def client(self, identifier, product, version):
self.data['client.identifier'] = identifier
self.data['client.product'] = product
self.data['client.version'] = version
return self
def device(self, name, system):
self.data['device.name'] = name
self.data['device.system'] = system
return self
def headers(self, headers):
self.data['headers'] = headers
return self
def platform(self, name, version):
self.data['platform.name'] = name
self.data['platform.version'] = version
return self
def server(self, host='127.0.0.1', port=32400):
self.data['server.host'] = host
self.data['server.port'] = port
return self
def get(self, key, default=None):
return self.data.get(key, default)
def __enter__(self):
self.manager.stack.append(self)
def __exit__(self, exc_type, exc_val, exc_tb):
item = self.manager.stack.pop()
assert item == self
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value

@ -1,26 +0,0 @@
from threading import Lock
class Context(object):
def __init__(self, **kwargs):
self.kwargs = kwargs
def __getattr__(self, key):
return self.kwargs.get(key)
class ContextStack(object):
def __init__(self):
self._list = []
self._lock = Lock()
def pop(self):
context = self._list.pop()
self._lock.release()
return context
def push(self, **kwargs):
self._lock.acquire()
return self._list.append(Context(**kwargs))

@ -1,105 +0,0 @@
# ExtensionImporter (```flask.exthook```)
# ----------------------------------
# :copyright: (c) 2014 by Armin Ronacher.
# :license: BSD, see LICENSE for more details.
from plex.lib.six import reraise
import os
import sys
class ExtensionImporter(object):
"""This importer redirects imports from this submodule to other locations.
This makes it possible to transition from the old flaskext.name to the
newer flask_name without people having a hard time.
"""
def __init__(self, module_choices, wrapper_module):
self.module_choices = module_choices
self.wrapper_module = wrapper_module
self.prefix = wrapper_module + '.'
self.prefix_cutoff = wrapper_module.count('.') + 1
def __eq__(self, other):
return self.__class__.__module__ == other.__class__.__module__ and \
self.__class__.__name__ == other.__class__.__name__ and \
self.wrapper_module == other.wrapper_module and \
self.module_choices == other.module_choices
def __ne__(self, other):
return not self.__eq__(other)
def install(self):
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
def find_module(self, fullname, path=None):
if fullname.startswith(self.prefix):
return self
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
for path in self.module_choices:
realname = path % modname
try:
__import__(realname)
except ImportError:
exc_type, exc_value, tb = sys.exc_info()
# since we only establish the entry in sys.modules at the
# very this seems to be redundant, but if recursive imports
# happen we will call into the move import a second time.
# On the second invocation we still don't have an entry for
# fullname in sys.modules, but we will end up with the same
# fake module name and that import will succeed since this
# one already has a temporary entry in the modules dict.
# Since this one "succeeded" temporarily that second
# invocation now will have created a fullname entry in
# sys.modules which we have to kill.
sys.modules.pop(fullname, None)
# If it's an important traceback we reraise it, otherwise
# we swallow it and try the next choice. The skipped frame
# is the one from __import__ above which we don't care about
if self.is_important_traceback(realname, tb):
reraise(exc_type, exc_value, tb.tb_next)
continue
module = sys.modules[fullname] = sys.modules[realname]
if '.' not in modname:
setattr(sys.modules[self.wrapper_module], modname, module)
return module
raise ImportError('No module named %s' % fullname)
def is_important_traceback(self, important_module, tb):
"""Walks a traceback's frames and checks if any of the frames
originated in the given important module. If that is the case then we
were able to import the module itself but apparently something went
wrong when the module was imported. (Eg: import of an import failed).
"""
while tb is not None:
if self.is_important_frame(important_module, tb):
return True
tb = tb.tb_next
return False
def is_important_frame(self, important_module, tb):
"""Checks a single frame if it's important."""
g = tb.tb_frame.f_globals
if '__name__' not in g:
return False
module_name = g['__name__']
# Python 2.7 Behavior. Modules are cleaned up late so the
# name shows up properly here. Success!
if module_name == important_module:
return True
# Some python versions will will clean up modules so early that the
# module name at that point is no longer set. Try guessing from
# the filename then.
filename = os.path.abspath(tb.tb_frame.f_code.co_filename)
test_string = os.path.sep + important_module.replace('.', os.path.sep)
return test_string + '.py' in filename or \
test_string + os.path.sep + '__init__.py' in filename

@ -1,59 +0,0 @@
from plex.lib import six
import re
import unicodedata
def flatten(text):
if text is None:
return None
# Normalize `text` to ascii
text = normalize(text)
# Remove special characters
text = re.sub('[^A-Za-z0-9\s]+', '', text)
# Merge duplicate spaces
text = ' '.join(text.split())
# Convert to lower-case
return text.lower()
def normalize(text):
if text is None:
return None
# Normalize unicode characters
if type(text) is six.text_type:
text = unicodedata.normalize('NFKD', text)
# Ensure text is ASCII, ignore unknown characters
text = text.encode('ascii', 'ignore')
# Return decoded `text`
return text.decode('ascii')
def to_iterable(value):
if value is None:
return None
if isinstance(value, (list, tuple)):
return value
return [value]
def synchronized(func):
def wrapper(self, *__args, **__kw):
self._lock.acquire()
try:
return func(self, *__args, **__kw)
finally:
self._lock.release()
wrapper.__name__ = func.__name__
wrapper.__dict__ = func.__dict__
wrapper.__doc__ = func.__doc__
return wrapper

@ -1,151 +0,0 @@
from plex.core.context import ContextStack
from plex.core.helpers import synchronized
from plex.request import PlexRequest
from threading import Condition
import hashlib
import logging
import requests
import socket
log = logging.getLogger(__name__)
class HttpClient(object):
def __init__(self, client):
self.client = client
self.configuration = ContextStack()
self.session = None
# Private
self._lock = Condition()
# Build requests session
self._build()
@property
def cache(self):
return self.client.configuration.get('cache.http')
def configure(self, path=None):
self.configuration.push(base_path=path)
return self
def request(self, method, path=None, params=None, query=None, data=None, credentials=None, **kwargs):
# retrieve configuration
ctx = self.configuration.pop()
if path is not None and type(path) is not str:
# Convert `path` to string (excluding NoneType)
path = str(path)
if ctx.base_path and path:
# Prepend `base_path` to relative `path`s
if not path.startswith('/'):
path = ctx.base_path + '/' + path
elif ctx.base_path:
path = ctx.base_path
elif not path:
path = ''
request = PlexRequest(
self.client,
method=method,
path=path,
params=params,
query=query,
data=data,
credentials=credentials,
**kwargs
)
prepared = request.prepare()
# Try retrieve cached response
response = self._cache_lookup(prepared)
if response:
return response
# TODO retrying requests on 502, 503 errors?
# try:
# response = self.session.send(prepared)
# except socket.gaierror as e:
# code, _ = e
#
# if code != 8:
# raise e
#
# log.warn('Encountered socket.gaierror (code: 8)')
#
# response = self._build().send(prepared)
response = request.request.send()
# Store response in cache
self._cache_store(prepared, response)
return response
def get(self, path=None, params=None, query=None, data=None, **kwargs):
return self.request('GET', path, params, query, data, **kwargs)
def put(self, path=None, params=None, query=None, data=None, **kwargs):
return self.request('PUT', path, params, query, data, **kwargs)
def post(self, path=None, params=None, query=None, data=None, **kwargs):
return self.request('POST', path, params, query, data, **kwargs)
def delete(self, path=None, params=None, query=None, data=None, **kwargs):
return self.request('DELETE', path, params, query, data, **kwargs)
def _build(self):
if self.session:
log.info('Rebuilding session and connection pools...')
# Rebuild the connection pool (old pool has stale connections)
self.session = requests.Session()
return self.session
@synchronized
def _cache_lookup(self, request):
if self.cache is None:
return None
if request.method not in ['GET']:
return None
# Retrieve from cache
return self.cache.get(self._cache_key(request))
@synchronized
def _cache_store(self, request, response):
if self.cache is None:
return None
if request.method not in ['GET']:
return None
# Store in cache
self.cache[self._cache_key(request)] = response
@staticmethod
def _cache_key(request):
raw = ','.join([request.method, request.url])
# Generate MD5 hash of key
m = hashlib.md5()
m.update(raw.encode('utf-8'))
return m.hexdigest()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass

@ -1,54 +0,0 @@
from plex.lib.six import string_types
class idict(dict):
def __init__(self, initial=None):
if initial:
self.update(initial)
def get(self, k, d=None):
if isinstance(k, string_types):
k = k.lower()
if super(idict, self).__contains__(k):
return self[k]
return d
def update(self, E=None, **F):
if E:
if hasattr(E, 'keys'):
# Update with `E` dictionary
for k in E:
self[k] = E[k]
else:
# Update with `E` items
for (k, v) in E:
self[k] = v
# Update with `F` dictionary
for k in F:
self[k] = F[k]
def __contains__(self, k):
if isinstance(k, string_types):
k = k.lower()
return super(idict, self).__contains__(k)
def __delitem__(self, k):
if isinstance(k, string_types):
k = k.lower()
super(idict, self).__delitem__(k)
def __getitem__(self, k):
if isinstance(k, string_types):
k = k.lower()
return super(idict, self).__getitem__(k)
def __setitem__(self, k, value):
if isinstance(k, string_types):
k = k.lower()
super(idict, self).__setitem__(k, value)

@ -1,4 +0,0 @@
from plex.core.extension import ExtensionImporter
importer = ExtensionImporter(['plex_%s'], __name__)
importer.install()

@ -1,6 +0,0 @@
def has_attribute(obj, name):
try:
object.__getattribute__(obj, name)
return True
except AttributeError:
return False

@ -1,81 +0,0 @@
from plex.interfaces.channel import ChannelInterface
from plex.interfaces.library import LibraryInterface
from plex.interfaces.library.metadata import LibraryMetadataInterface
from plex.interfaces.plugin import PluginInterface
from plex.interfaces.plugin.preferences import PluginPreferencesInterface
from plex.interfaces.preferences import PreferencesInterface
from plex.interfaces.root import RootInterface
from plex.interfaces.section import SectionInterface
from plex.interfaces.status import StatusInterface
from plex.interfaces.timeline import TimelineInterface
# TODO automatic interface discovery
INTERFACES = [
RootInterface,
# /
ChannelInterface,
StatusInterface,
# /library
LibraryInterface,
LibraryMetadataInterface,
SectionInterface,
# /:
PreferencesInterface,
TimelineInterface,
# /:/plugins
PluginInterface,
PluginPreferencesInterface
]
def get_interfaces():
for interface in INTERFACES:
if interface.path:
path = interface.path.strip('/')
else:
path = ''
if path:
path = path.split('/')
else:
path = []
yield path, interface
def construct_map(client, d=None, interfaces=None):
if d is None:
d = {}
if interfaces is None:
interfaces = get_interfaces()
for path, interface in interfaces:
if len(path) > 0:
key = path.pop(0)
else:
key = None
if key == '*':
key = None
if len(path) == 0:
d[key] = interface(client)
continue
value = d.get(key, {})
if type(value) is not dict:
value = {None: value}
construct_map(client, value, [(path, interface)])
d[key] = value
return d

@ -1,8 +0,0 @@
from plex.interfaces.core.base import Interface
class ChannelInterface(Interface):
path = 'channels'
def all(self):
raise NotImplementedError()

@ -1,216 +0,0 @@
from plex.lib.six import string_types, StringIO
from plex.lib.six.moves.urllib_parse import urlparse
from functools import wraps
import logging
# Import available parser
PARSER = None
try:
from lxml import etree
PARSER = 'etree.HTMLParser'
except ImportError:
from xml.etree import ElementTree as etree
PARSER = 'etree.XMLParser'
log = logging.getLogger(__name__)
class Helpers(object):
@staticmethod
def get(node, attr):
if PARSER == 'etree.HTMLParser':
return node.get(attr.lower())
return node.get(attr)
@staticmethod
def find(node, tag):
if PARSER == 'etree.HTMLParser':
return node.find(tag.lower())
return node.find(tag)
@staticmethod
def findall(node, tag):
if PARSER == 'etree.HTMLParser':
return node.findall(tag.lower())
return node.findall(tag)
class Interface(object):
helpers = Helpers
path = None
object_map = {}
def __init__(self, client):
self.client = client
def __getitem__(self, name):
if hasattr(self, name):
return getattr(self, name)
raise ValueError('Unknown action "%s" on %s', name, self)
@property
def http(self):
if not self.client:
return None
return self.client.http.configure(self.path)
def parse(self, response, schema):
if response.status_code < 200 or response.status_code >= 300:
return None
try:
root = self.__parse_xml(response.content)
except SyntaxError as ex:
log.error('Unable to parse XML response: %s', ex, exc_info=True, extra={
'data': {
'snippet': self.__error_snippet(response, ex)
}
})
return None
except Exception as ex:
log.error('Unable to parse XML response: %s', ex, exc_info=True)
return None
url = urlparse(response.url)
path = url.path
return self.__construct(self.client, path, root, schema)
@staticmethod
def __parse_xml(content):
if PARSER == 'etree.HTMLParser':
html = etree.fromstring(content, parser=etree.HTMLParser())
assert html.tag == 'html'
bodies = [e for e in html if e.tag == 'body']
assert len(bodies) == 1
body = bodies[0]
assert len(body) == 1
return body[0]
return etree.fromstring(content)
@staticmethod
def __error_snippet(response, ex):
# Retrieve the error line
position = getattr(ex, 'position', None)
if not position or len(position) != 2:
return None
n_line, n_column = position
snippet = None
# Create StringIO stream
stream = StringIO(response.text)
# Iterate over `content` to find `n_line`
for x, l in enumerate(stream):
if x < n_line - 1:
continue
# Line found
snippet = l
break
# Close the stream
stream.close()
if not snippet:
# Couldn't find the line
return None
# Find an attribute value containing `n_column`
start = snippet.find('"', n_column)
end = snippet.find('"', start + 1)
# Trim `snippet` (if attribute value was found)
if start >= 0 and end >= 0:
return snippet[start:end + 1]
return snippet
@classmethod
def __construct(cls, client, path, node, schema):
if not schema:
return None
# Try retrieve schema for `tag`
item = schema.get(node.tag)
if item is None:
raise ValueError('Unknown node with tag "%s"' % node.tag)
if type(item) is dict:
value = cls.helpers.get(node, item.get('_', 'type'))
if value is None:
return None
item = item.get(value)
if item is None:
raise ValueError('Unknown node type "%s"' % value)
descriptor = None
child_schema = None
if type(item) is tuple and len(item) == 2:
descriptor, child_schema = item
else:
descriptor = item
if isinstance(descriptor, string_types):
if descriptor not in cls.object_map:
raise Exception('Unable to find descriptor by name "%s"' % descriptor)
descriptor = cls.object_map.get(descriptor)
if descriptor is None:
raise Exception('Unable to find descriptor')
keys_used, obj = descriptor.construct(client, node, path=path)
# Lazy-construct children
def iter_children():
for child_node in node:
item = cls.__construct(client, path, child_node, child_schema)
if item:
yield item
obj._children = iter_children()
return obj
class InterfaceProxy(object):
def __init__(self, interface, args):
self.interface = interface
self.args = list(args)
def __getattr__(self, name):
value = getattr(self.interface, name)
if not hasattr(value, '__call__'):
return value
@wraps(value)
def wrap(*args, **kwargs):
args = self.args + list(args)
return value(*args, **kwargs)
return wrap

@ -1,104 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class LibraryInterface(Interface):
path = 'library'
def metadata(self, rating_key):
response = self.http.get('metadata', rating_key)
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': {
'album': 'Album',
'artist': 'Artist',
'season': 'Season',
'show': 'Show'
},
'Video': {
'episode': 'Episode',
'clip': 'Clip',
'movie': 'Movie'
},
'Track': 'Track'
}))
}))
def on_deck(self):
response = self.http.get('onDeck')
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Video': {
'movie': 'Movie',
'episode': 'Episode'
}
}))
}))
def recently_added(self):
response = self.http.get('recentlyAdded')
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': {
'album': 'Album',
'season': 'Season'
},
'Video': {
'movie': 'Movie'
}
}))
}))
def sections(self):
response = self.http.get('sections')
return self.parse(response, idict({
'MediaContainer': ('SectionContainer', idict({
'Directory': ('Section', idict({
'Location': 'Location'
}))
}))
}))
#
# Item actions
#
def rate(self, key, rating):
response = self.http.get(
'/:/rate',
query={
'identifier': 'com.plexapp.plugins.library',
'key': key,
'rating': int(round(rating, 0))
}
)
return response.status_code == 200
def scrobble(self, key):
response = self.http.get(
'/:/scrobble',
query={
'identifier': 'com.plexapp.plugins.library',
'key': key
}
)
return response.status_code == 200
def unscrobble(self, key):
response = self.http.get(
'/:/unscrobble',
query={
'identifier': 'com.plexapp.plugins.library',
'key': key
}
)
return response.status_code == 200

@ -1,65 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class LibraryMetadataInterface(Interface):
path = 'library/metadata'
def refresh(self, key):
response = self.http.put(str(key) + "/refresh")
def all_leaves(self, key):
response = self.http.get(key, 'allLeaves')
return self.parse(response, idict({
'MediaContainer': {
'_': 'viewGroup',
'episode': ('ShowLeavesContainer', idict({
'Video': {
'episode': 'Episode'
}
})),
'track': ('ArtistLeavesContainer', idict({
'Track': 'Track'
}))
}
}))
def children(self, key):
response = self.http.get(key, 'children')
return self.parse(response, idict({
'MediaContainer': {
'_': 'viewGroup',
# ---------------------------------------
# Music
# ---------------------------------------
'album': ('ArtistChildrenContainer', idict({
'Directory': {
'album': 'Album'
}
})),
'track': ('AlbumChildrenContainer', idict({
'Track': 'Track'
})),
# ---------------------------------------
# TV
# ---------------------------------------
'season': ('ShowChildrenContainer', idict({
'Directory': {
'season': 'Season'
}
})),
'episode': ('SeasonChildrenContainer', idict({
'Video': {
'episode': 'Episode'
}
}))
}
}))

@ -1,13 +0,0 @@
from plex.interfaces.core.base import Interface
class PluginInterface(Interface):
path = ':/plugins'
def reload_services(self, plugin_id):
response = self.http.get(plugin_id, 'services/reload')
return response.status_code == 200
def restart(self, plugin_id):
response = self.http.get(plugin_id, 'restart')
return response.status_code == 200

@ -1,40 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class PluginPreferencesInterface(Interface):
path = ':/plugins/*/prefs'
def get(self, plugin_id, id=None):
response = self.http.get('/:/plugins/%s/prefs' % plugin_id)
container = self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Setting': 'Setting'
}))
}))
if container is None or id is None:
return container
for setting in container:
if setting.id == id:
return setting
return None
def set(self, plugin_id, id, value):
response = self.http.get('/:/plugins/%s/prefs/set' % plugin_id, query={
id: self.to_setting_value(value, type(value))
})
return response.status_code == 200
def to_setting_value(self, value, value_type=None):
if value is None:
return None
if value_type is bool:
return str(value).lower()
return str(value)

@ -1,40 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class PreferencesInterface(Interface):
path = ':/prefs'
def get(self, id=None):
response = self.http.get()
container = self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Setting': 'Setting'
}))
}))
if container is None or id is None:
return container
for setting in container:
if setting.id == id:
return setting
return None
def set(self, id, value):
response = self.http.put(query={
id: self.to_setting_value(value, type(value))
})
return response.status_code == 200
def to_setting_value(self, value, value_type=None):
if value is None:
return None
if value_type is bool:
return str(value).lower()
return str(value)

@ -1,59 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class RootInterface(Interface):
def detail(self):
response = self.http.get()
return self.parse(response, idict({
'MediaContainer': ('Detail', idict({
'Directory': 'Directory'
}))
}))
def version(self):
detail = self.detail()
if not detail:
return None
return detail.version
def clients(self):
response = self.http.get('clients')
return self.parse(response, idict({
'MediaContainer': ('ClientContainer', idict({
'Server': 'Client'
}))
}))
def players(self):
pass
def servers(self):
response = self.http.get('servers')
return self.parse(response, idict({
'MediaContainer': ('Container', idict({
'Server': 'Server'
}))
}))
def agents(self):
response = self.http.get('system/agents')
return self.parse(response, idict({
'MediaContainer': ('Container', idict({
'Agent': 'Agent'
}))
}))
def primary_agent(self, guid, media_type):
response = self.http.get('/system/agents/%s/config/%s' % (guid, media_type))
return self.parse(response, idict({
'MediaContainer': ('Container', idict({
'Agent': 'Agent'
}))
}))

@ -1,69 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class SectionInterface(Interface):
path = 'library/sections'
def all(self, key):
response = self.http.get(key, 'all')
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': {
'artist': 'Artist',
'show': 'Show'
},
'Video': {
'movie': 'Movie'
}
}))
}))
def recently_added(self, key):
response = self.http.get(key, 'recentlyAdded')
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': {
'artist': 'Artist',
'show': 'Show'
},
'Video': {
'movie': 'Movie',
'episode': 'Episode',
'clip': 'Clip',
}
}))
}))
def first_character(self, key, character=None):
if character:
response = self.http.get(key, ['firstCharacter', character])
# somehow plex wrongly returns items of other libraries when character is #
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': {
'album': 'Album',
'artist': 'Artist',
'season': 'Season',
'show': 'Show'
},
'Video': {
'episode': 'Episode',
'clip': 'Clip',
'movie': 'Movie'
},
'Track': 'Track'
}))
}))
response = self.http.get(key, 'firstCharacter')
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': 'Directory'
}))
}))

@ -1,21 +0,0 @@
from plex.core.idict import idict
from plex.interfaces.core.base import Interface
class StatusInterface(Interface):
path = 'status'
def sessions(self):
response = self.http.get('sessions')
return self.parse(response, idict({
'MediaContainer': ('SessionContainer', idict({
'Track': 'Track',
'Video': {
'episode': 'Episode',
'clip': 'Clip',
'movie': 'Movie'
}
}))
}))

@ -1,36 +0,0 @@
from plex.interfaces.core.base import Interface
TIMELINE_STATES = [
'buffering',
'paused',
'playing',
'stopped'
]
class TimelineInterface(Interface):
path = ':/timeline'
def update(self, rating_key, state, time, duration, key=None, play_queue_item_id=None):
if not rating_key:
raise ValueError('Invalid "rating_key" parameter')
if time is None or duration is None:
raise ValueError('"time" and "duration" parameters are required')
if state not in TIMELINE_STATES:
raise ValueError('Unknown "state"')
response = self.http.get(query=[
('ratingKey', rating_key),
('state', state),
('time', time),
('duration', duration),
# Optional parameters
('key', key),
('playQueueItemID', play_queue_item_id)
])
return response and response.status_code == 200

@ -1,762 +0,0 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2014 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import absolute_import
import functools
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.8.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result) # Invokes __set__.
# This is a bit ugly, but it avoids running this again.
delattr(obj.__class__, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
def __getattr__(self, attr):
_module = self._resolve()
value = getattr(_module, attr)
setattr(self, attr, value)
return value
class _LazyModule(types.ModuleType):
def __init__(self, name):
super(_LazyModule, self).__init__(name)
self.__doc__ = self.__class__.__doc__
def __dir__(self):
attrs = ["__doc__", "__name__"]
attrs += [attr.name for attr in self._moved_attributes]
return attrs
# Subclasses should override this
_moved_attributes = []
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _SixMetaPathImporter(object):
"""
A meta path importer to import six.moves and its submodules.
This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""
def __init__(self, six_module_name):
self.name = six_module_name
self.known_modules = {}
def _add_module(self, mod, *fullnames):
for fullname in fullnames:
self.known_modules[self.name + "." + fullname] = mod
def _get_module(self, fullname):
return self.known_modules[self.name + "." + fullname]
def find_module(self, fullname, path=None):
if fullname in self.known_modules:
return self
return None
def __get_module(self, fullname):
try:
return self.known_modules[fullname]
except KeyError:
raise ImportError("This loader does not know module " + fullname)
def load_module(self, fullname):
try:
# in case of a reload
return sys.modules[fullname]
except KeyError:
pass
mod = self.__get_module(fullname)
if isinstance(mod, MovedModule):
mod = mod._resolve()
else:
mod.__loader__ = self
sys.modules[fullname] = mod
return mod
def is_package(self, fullname):
"""
Return true, if the named module is a package.
We need this method to get correct spec objects with
Python 3.4 (see PEP451)
"""
return hasattr(self.__get_module(fullname), "__path__")
def get_code(self, fullname):
"""Return None
Required, if is_package is implemented"""
self.__get_module(fullname) # eventually raises ImportError
return None
get_source = get_code # same as get_code
_importer = _SixMetaPathImporter(__name__)
class _MovedItems(_LazyModule):
"""Lazy loading of moved objects"""
__path__ = [] # mark as package
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("intern", "__builtin__", "sys"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("UserDict", "UserDict", "collections"),
MovedAttribute("UserList", "UserList", "collections"),
MovedAttribute("UserString", "UserString", "collections"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("_thread", "thread", "_thread"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
if isinstance(attr, MovedModule):
_importer._add_module(attr, "moves." + attr.name)
del attr
_MovedItems._moved_attributes = _moved_attributes
moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")
class Module_six_moves_urllib_parse(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
_urllib_parse_moved_attributes = [
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
MovedAttribute("quote", "urllib", "urllib.parse"),
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
"moves.urllib_parse", "moves.urllib.parse")
class Module_six_moves_urllib_error(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_error"""
_urllib_error_moved_attributes = [
MovedAttribute("URLError", "urllib2", "urllib.error"),
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
"moves.urllib_error", "moves.urllib.error")
class Module_six_moves_urllib_request(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_request"""
_urllib_request_moved_attributes = [
MovedAttribute("urlopen", "urllib2", "urllib.request"),
MovedAttribute("install_opener", "urllib2", "urllib.request"),
MovedAttribute("build_opener", "urllib2", "urllib.request"),
MovedAttribute("pathname2url", "urllib", "urllib.request"),
MovedAttribute("url2pathname", "urllib", "urllib.request"),
MovedAttribute("getproxies", "urllib", "urllib.request"),
MovedAttribute("Request", "urllib2", "urllib.request"),
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
"moves.urllib_request", "moves.urllib.request")
class Module_six_moves_urllib_response(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_response"""
_urllib_response_moved_attributes = [
MovedAttribute("addbase", "urllib", "urllib.response"),
MovedAttribute("addclosehook", "urllib", "urllib.response"),
MovedAttribute("addinfo", "urllib", "urllib.response"),
MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
"moves.urllib_response", "moves.urllib.response")
class Module_six_moves_urllib_robotparser(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
_urllib_robotparser_moved_attributes = [
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
"moves.urllib_robotparser", "moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
__path__ = [] # mark as package
parse = _importer._get_module("moves.urllib_parse")
error = _importer._get_module("moves.urllib_error")
request = _importer._get_module("moves.urllib_request")
response = _importer._get_module("moves.urllib_response")
robotparser = _importer._get_module("moves.urllib_robotparser")
def __dir__(self):
return ['parse', 'error', 'request', 'response', 'robotparser']
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
"moves.urllib")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
if PY3:
def iterkeys(d, **kw):
return iter(d.keys(**kw))
def itervalues(d, **kw):
return iter(d.values(**kw))
def iteritems(d, **kw):
return iter(d.items(**kw))
def iterlists(d, **kw):
return iter(d.lists(**kw))
else:
def iterkeys(d, **kw):
return iter(d.iterkeys(**kw))
def itervalues(d, **kw):
return iter(d.itervalues(**kw))
def iteritems(d, **kw):
return iter(d.iteritems(**kw))
def iterlists(d, **kw):
return iter(d.iterlists(**kw))
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
"Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
"Return an iterator over the (key, [values]) pairs of a dictionary.")
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
# Workaround for standalone backslash
def u(s):
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
def iterbytes(buf):
return (ord(byte) for byte in buf)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
isinstance(data, unicode) and
fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(fp.encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
if sys.version_info[0:2] < (3, 4):
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
def wrapper(f):
f = functools.wraps(wrapped)(f)
f.__wrapped__ = wrapped
return f
return wrapper
else:
wraps = functools.wraps
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
# Complete the moves implementation.
# This code is at the end of this module to speed up module loading.
# Turn this module into a package.
__path__ = [] # required for PEP 302 and PEP 451
__package__ = __name__ # see PEP 366 @ReservedAssignment
if globals().get("__spec__") is not None:
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
# Remove other six meta path importers, since they cause problems. This can
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
# this for some reason.)
if sys.meta_path:
for i, importer in enumerate(sys.meta_path):
# Here's some real nastiness: Another "instance" of the six module might
# be floating around. Therefore, we can't use isinstance() to check for
# the six meta path importer, since the other six instance will have
# inserted an importer with different class.
if (type(importer).__name__ == "_SixMetaPathImporter" and
importer.name == __name__):
del sys.meta_path[i]
break
del i, importer
# Finally, add the importer to the meta path import hook.
sys.meta_path.append(_importer)

@ -1,29 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class MediaType(Descriptor):
name = Property
media_type = Property("mediaType", type=int)
@classmethod
def from_node(cls, client, node):
items = []
for t in cls.helpers.findall(node, 'MediaType'):
_, obj = MediaType.construct(client, t, child=True)
items.append(obj)
return [], items
class Agent(Descriptor):
name = Property
enabled = Property(type=int)
identifier = Property
primary = Property(type=int)
has_prefs = Property("hasPrefs", type=int)
has_attribution = Property("hasAttribution", type=int)
media_types = Property(resolver=lambda: MediaType.from_node)

@ -1,32 +0,0 @@
from plex.core.helpers import to_iterable
from plex.objects.container import Container
from plex.objects.core.base import Property
from plex.objects.server import Server
class Client(Server):
product = Property
device_class = Property('deviceClass')
protocol = Property
protocol_version = Property('protocolVersion', type=int)
protocol_capabilities = Property('protocolCapabilities')
class ClientContainer(Container):
filter_passes = lambda _, allowed, value: allowed is None or value in allowed
def filter(self, identifiers=None):
identifiers = to_iterable(identifiers)
for client in self:
if not self.filter_passes(identifiers, client.machine_identifier):
continue
yield client
def get(self, identifier):
for item in self.filter(identifier):
return item
return None

@ -1,7 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Container(Descriptor):
size = Property(type=int)
updated_at = Property('updatedAt', int)

@ -1,168 +0,0 @@
from plex.lib.six import add_metaclass
from plex.interfaces.core.base import Interface
import logging
import traceback
import types
log = logging.getLogger(__name__)
class Property(object):
helpers = Interface.helpers
def __init__(self, name=None, type=None, resolver=None):
self.name = name
self.type = type
self.resolver = resolver
def value(self, client, key, node, keys_used):
if self.resolver is not None:
return self.value_func(client, node, keys_used)
return self.value_node(key, node, keys_used)
def value_node(self, key, node, keys_used):
value = self.helpers.get(node, key)
keys_used.append(key.lower())
if value is None:
return None
return self.value_convert(value)
def value_convert(self, value):
if not self.type:
return value
types = self.type if type(self.type) is list else [self.type]
result = value
for target_type in types:
try:
result = target_type(result)
except:
return None
return result
def value_func(self, client, node, keys_used):
func = self.resolver()
try:
keys, value = func(client, node)
keys_used.extend([k.lower() for k in keys])
return value
except Exception as ex:
log.warn('Exception in value function (%s): %s - %s', func, ex, traceback.format_exc())
return None
class DescriptorMeta(type):
def __init__(self, name, bases, attrs):
super(DescriptorMeta, self).__init__(name, bases, attrs)
Interface.object_map[self.__name__] = self
@add_metaclass(DescriptorMeta)
class Descriptor(Interface):
attribute_map = None
def __init__(self, client, path):
super(Descriptor, self).__init__(client)
self.path = path
self._children = None
@classmethod
def properties(cls):
keys = [k for k in dir(cls) if not k.startswith('_')]
#log.debug('%s - keys: %s', self, keys)
for key in keys:
if key.startswith('_'):
continue
value = getattr(cls, key)
if value is Property:
yield key, Property(key)
elif isinstance(value, Property):
yield key, value
@classmethod
def construct(cls, client, node, attribute_map=None, path=None, child=False):
if node is None:
return [], None
keys_available = [k.lower() for k in node.keys()]
keys_used = []
if attribute_map is None:
attribute_map = cls.attribute_map or {}
require_map = attribute_map.get('*') != '*'
# Determine path from object "key"
key = cls.helpers.get(node, 'key')
if key is not None:
path = key[:key.rfind('/')]
# Construct object
obj = cls(client, path)
#log.debug('%s - Properties: %s', cls.__name__, list(obj.properties()))
for key, prop in cls.properties():
node_key = prop.name or key
if attribute_map:
if node_key in attribute_map:
node_key = attribute_map.get(node_key)
elif require_map:
setattr(obj, key, None)
continue
#log.debug('%s - Found property "%s"', cls.__name__, key)
setattr(obj, key, prop.value(client, node_key, node, keys_used))
# Post-fill transformation
obj.__transform__()
# Look for omitted keys
omitted = list(set(keys_available) - set(keys_used))
omitted.sort()
if omitted and not child:
log.warn('%s - Omitted attributes: %s', cls.__name__, ', '.join(omitted))
return keys_used, obj
def __transform__(self):
pass
def __iter__(self):
return self._children or []
def __getstate__(self):
data = self.__dict__
def build():
for key, value in data.items():
if isinstance(value, types.GeneratorType):
value = list(value)
if key in ['client']:
continue
yield key, value
return dict(build())
class DescriptorMixin(Descriptor):
pass

@ -1,89 +0,0 @@
import inspect
import logging
import os
log = logging.getLogger(__name__)
UNC_PREFIX = '\\\\?\\'
class ObjectManager(object):
base_dir = None
objects_dir = None
objects_map = {}
ignore_files = [
'__init__.py'
]
ignore_paths = [
'plex\\objects\\core\\base.py',
'plex\\objects\\core\\manager.py'
]
@classmethod
def discover(cls):
cls.objects_dir = os.path.join(cls.base_dir, 'plex', 'objects')
# Walk plex/objects directory
for current, directories, files in os.walk(cls.objects_dir):
# Iterate files, yield valid paths
for filename in files:
if not filename.endswith('.py'):
continue
# Ensure filename is not in ignore list
if filename in cls.ignore_files:
continue
path = os.path.join(current, filename)
# Ensure path is not in ignore list
if not all([not path.endswith(p) for p in cls.ignore_paths]):
continue
# Remove UNC prefix (if it exists)
if path.startswith(UNC_PREFIX):
path = path[len(UNC_PREFIX):]
path = os.path.relpath(path, cls.base_dir)
name = os.path.splitext(path)[0].replace(os.path.sep, '.')
yield path, name
@classmethod
def load(cls):
for path, name in cls.discover():
try:
mod = __import__(name, fromlist=['*'])
except Exception as ex:
log.warn('Unable to import "%s" - %s', name, ex)
continue
# Get classes in module
classes = [
(key, getattr(mod, key)) for key in dir(mod)
if not key.startswith('_')
]
# Filter to module-specific classes
classes = [
(key, value) for (key, value) in classes
if inspect.isclass(value) and value.__module__ == name
]
yield classes
@classmethod
def construct(cls):
log.debug('Loading descriptors...')
cls.base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../', '..'))
# Load modules, find descriptor classes
for classes in cls.load():
# Update object map
for key, value in classes:
cls.objects_map[key] = value
log.debug('Loaded %s descriptors (%s)', len(cls.objects_map), ', '.join(sorted(cls.objects_map.keys())))

@ -1,62 +0,0 @@
from plex.objects.core.base import Descriptor, Property
from plex.objects.container import Container
class Detail(Container):
myplex = Property(resolver=lambda: Detail.construct_myplex)
transcoder = Property(resolver=lambda: Detail.construct_transcoder)
friendly_name = Property('friendlyName')
machine_identifier = Property('machineIdentifier')
version = Property
platform = Property
platform_version = Property('platformVersion')
allow_camera_upload = Property('allowCameraUpload', [int, bool])
allow_channel_access = Property('allowChannelAccess', [int, bool])
allow_sync = Property('allowSync', [int, bool])
certificate = Property(type=[int, bool])
multiuser = Property(type=[int, bool])
sync = Property(type=[int, bool])
start_state = Property('startState')
silverlight = Property('silverlightInstalled', [int, bool])
soundflower = Property('soundflowerInstalled', [int, bool])
flash = Property('flashInstalled', [int, bool])
webkit = Property(type=[int, bool])
cookie_parameters = Property('requestParametersInCookie', [int, bool])
@staticmethod
def construct_myplex(client, node):
return MyPlexDetail.construct(client, node, child=True)
@staticmethod
def construct_transcoder(client, node):
return TranscoderDetail.construct(client, node, child=True)
class MyPlexDetail(Descriptor):
enabled = Property('myPlex', type=bool)
username = Property('myPlexUsername')
mapping_state = Property('myPlexMappingState')
signin_state = Property('myPlexSigninState')
subscription = Property('myPlexSubscription', [int, bool])
class TranscoderDetail(Descriptor):
audio = Property('transcoderAudio', [int, bool])
video = Property('transcoderVideo', [int, bool])
video_bitrates = Property('transcoderVideoBitrates')
video_qualities = Property('transcoderVideoQualities')
video_resolutions = Property('transcoderVideoResolutions')
active_video_sessions = Property('transcoderActiveVideoSessions', int)

@ -1,16 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Directory(Descriptor):
key = Property
type = Property
title = Property
size = Property
art = Property
thumb = Property
allow_sync = Property('allowSync', bool)
updated_at = Property('updatedAt', int)

@ -1,80 +0,0 @@
from plex.core.helpers import flatten, to_iterable
from plex.objects.core.base import Property
from plex.objects.container import Container
from plex.objects.library.section import Section
class MediaContainer(Container):
section = Property(resolver=lambda: MediaContainer.construct_section)
title1 = Property
title2 = Property
identifier = Property
art = Property
thumb = Property
view_group = Property('viewGroup')
view_mode = Property('viewMode', int)
media_tag_prefix = Property('mediaTagPrefix')
media_tag_version = Property('mediaTagVersion')
size = Property('size', int)
total_size = Property('totalSize', int)
allow_sync = Property('allowSync', bool)
mixed_parents = Property('mixedParents', bool)
no_cache = Property('nocache', bool)
sort_asc = Property('sortAsc', bool)
@staticmethod
def construct_section(client, node):
attribute_map = {
'key': 'librarySectionID',
'uuid': 'librarySectionUUID',
'title': 'librarySectionTitle'
}
return Section.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(MediaContainer, self).__iter__():
item.section = self.section
yield item
class ChildrenContainer(MediaContainer):
pass
class LeavesContainer(MediaContainer):
pass
class SectionContainer(MediaContainer):
filter_passes = lambda _, allowed, value: allowed is None or value in allowed
def filter(self, types=None, keys=None, titles=None):
types = to_iterable(types)
keys = to_iterable(keys)
titles = to_iterable(titles)
if titles:
# Flatten titles
titles = [flatten(x) for x in titles]
for section in self:
if not self.filter_passes(types, section.type):
continue
if not self.filter_passes(keys, section.key):
continue
if not self.filter_passes(titles, flatten(section.title)):
continue
yield section

@ -1,10 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Country(Descriptor):
id = Property(type=int)
tag = Property
@classmethod
def from_node(cls, client, node):
return cls.construct(client, cls.helpers.find(node, 'Country'), child=True)

@ -1,10 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Director(Descriptor):
id = Property(type=int)
tag = Property
@classmethod
def from_node(cls, client, node):
return cls.construct(client, cls.helpers.find(node, 'Director'), child=True)

@ -1,17 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Genre(Descriptor):
id = Property(type=int)
tag = Property
@classmethod
def from_node(cls, client, node):
items = []
for genre in cls.helpers.findall(node, 'Genre'):
_, obj = Genre.construct(client, genre, child=True)
items.append(obj)
return [], items

@ -1,20 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Role(Descriptor):
id = Property(type=int)
tag = Property
role = Property
thumb = Property
@classmethod
def from_node(cls, client, node):
items = []
for genre in cls.helpers.findall(node, 'Role'):
_, obj = Role.construct(client, genre, child=True)
items.append(obj)
return [], items

@ -1,17 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Writer(Descriptor):
id = Property(type=int)
tag = Property
@classmethod
def from_node(cls, client, node):
items = []
for genre in cls.helpers.findall(node, 'Writer'):
_, obj = Writer.construct(client, genre, child=True)
items.append(obj)
return [], items

@ -1,6 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Location(Descriptor):
id = Property
path = Property

@ -1,39 +0,0 @@
from plex.objects.core.base import Descriptor, Property
from plex.objects.library.part import Part
class Media(Descriptor):
parts = Property(resolver=lambda: Part.from_node)
id = Property(type=int)
video_codec = Property('videoCodec')
video_frame_rate = Property('videoFrameRate')
video_resolution = Property('videoResolution')
audio_channels = Property('audioChannels', type=int)
audio_codec = Property('audioCodec')
container = Property
width = Property(type=int)
height = Property(type=int)
aspect_ratio = Property('aspectRatio', type=float)
bitrate = Property(type=int)
duration = Property(type=int)
#@classmethod
#def from_node(cls, client, node):
# return cls.construct(client, cls.helpers.find(node, 'Media'), child=True)
@classmethod
def from_node(cls, client, node):
items = []
for genre in cls.helpers.findall(node, 'Media'):
_, obj = Media.construct(client, genre, child=True)
items.append(obj)
return [], items

@ -1,68 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.directory import Directory
from plex.objects.library.container import ChildrenContainer
from plex.objects.library.extra.genre import Genre
from plex.objects.library.metadata.base import Metadata
from plex.objects.library.metadata.artist import Artist
from plex.objects.mixins.rate import RateMixin
class Album(Directory, Metadata, RateMixin):
artist = Property(resolver=lambda: Album.construct_artist)
genres = Property(resolver=lambda: Genre.from_node)
index = Property(type=int)
year = Property(type=int)
originally_available_at = Property('originallyAvailableAt')
track_count = Property('leafCount', int)
viewed_track_count = Property('viewedLeafCount', int)
def children(self):
return self.client['library/metadata'].children(self.rating_key)
@staticmethod
def construct_artist(client, node):
attribute_map = {
'key': 'parentKey',
'ratingKey': 'parentRatingKey',
'title': 'parentTitle',
'thumb': 'parentThumb'
}
return Artist.construct(client, node, attribute_map, child=True)
class AlbumChildrenContainer(ChildrenContainer):
artist = Property(resolver=lambda: AlbumChildrenContainer.construct_artist)
album = Property(resolver=lambda: AlbumChildrenContainer.construct_album)
key = Property
@staticmethod
def construct_artist(client, node):
attribute_map = {
'title': 'grandparentTitle'
}
return Artist.construct(client, node, attribute_map, child=True)
@staticmethod
def construct_album(client, node):
attribute_map = {
'index': 'parentIndex',
'title': 'parentTitle',
'year' : 'parentYear'
}
return Album.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(ChildrenContainer, self).__iter__():
item.artist = self.artist
item.album = self.album
yield item

@ -1,58 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.directory import Directory
from plex.objects.library.container import LeavesContainer, ChildrenContainer
from plex.objects.library.metadata.base import Metadata
from plex.objects.mixins.rate import RateMixin
class Artist(Directory, Metadata, RateMixin):
index = Property(type=int)
def all_leaves(self):
return self.client['library/metadata'].all_leaves(self.rating_key)
def children(self):
return self.client['library/metadata'].children(self.rating_key)
class ArtistChildrenContainer(ChildrenContainer):
artist = Property(resolver=lambda: ArtistChildrenContainer.construct_artist)
key = Property
summary = Property
@staticmethod
def construct_artist(client, node):
attribute_map = {
'index': 'parentIndex',
'title': 'parentTitle'
}
return Artist.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(ChildrenContainer, self).__iter__():
item.artist = self.artist
yield item
class ArtistLeavesContainer(LeavesContainer):
artist = Property(resolver=lambda: ArtistLeavesContainer.construct_artist)
key = Property
@staticmethod
def construct_artist(client, node):
attribute_map = {
'index': 'parentIndex',
'title': 'parentTitle'
}
return Artist.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(LeavesContainer, self).__iter__():
item.artist = self.artist
yield item

@ -1,38 +0,0 @@
from plex.objects.core.base import Descriptor, Property
from plex.objects.library.section import Section
class Metadata(Descriptor):
section = Property(resolver=lambda: Metadata.construct_section)
# somehow section doesn't resolve on onDeck, add key manually
section_key = Property('librarySectionID')
key = Property
guid = Property
rating_key = Property('ratingKey')
extra_key = Property('primaryExtraKey')
title = Property
title_sort = Property('titleSort')
title_original = Property('originalTitle')
summary = Property
thumb = Property
source_title = Property('sourceTitle')
added_at = Property('addedAt', int)
last_viewed_at = Property('lastViewedAt', int)
@staticmethod
def construct_section(client, node):
attribute_map = {
'key': 'librarySectionID',
'uuid': 'librarySectionUUID',
'title': 'librarySectionTitle'
}
return Section.construct(client, node, attribute_map, child=True)

@ -1,9 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.library.metadata.base import Metadata
from plex.objects.library.video import Video
class Clip(Video, Metadata):
extra_type = Property('extraType', type=int)
index = Property(type=int)

@ -1,48 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.library.metadata.season import Season
from plex.objects.library.metadata.show import Show
from plex.objects.library.metadata.base import Metadata
from plex.objects.library.video import Video
from plex.objects.mixins.rate import RateMixin
from plex.objects.mixins.scrobble import ScrobbleMixin
class Episode(Video, Metadata, RateMixin, ScrobbleMixin):
show = Property(resolver=lambda: Episode.construct_show)
season = Property(resolver=lambda: Episode.construct_season)
index = Property(type=int)
studio = Property
audience_rating = Property('audienceRating', float)
content_rating = Property('contentRating')
year = Property(type=int)
originally_available_at = Property('originallyAvailableAt')
@staticmethod
def construct_show(client, node):
attribute_map = {
'key': 'grandparentKey',
'ratingKey': 'grandparentRatingKey',
'title': 'grandparentTitle',
'art': 'grandparentArt',
'theme': 'grandparentTheme',
'thumb': 'grandparentThumb'
}
return Show.construct(client, node, attribute_map, child=True)
@staticmethod
def construct_season(client, node):
attribute_map = {
'index': 'parentIndex',
'key': 'parentKey',
'ratingKey': 'parentRatingKey',
'thumb': 'parentThumb'
}
return Season.construct(client, node, attribute_map, child=True)

@ -1,22 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.library.extra.country import Country
from plex.objects.library.extra.genre import Genre
from plex.objects.library.extra.role import Role
from plex.objects.library.metadata.base import Metadata
from plex.objects.library.video import Video
from plex.objects.mixins.rate import RateMixin
from plex.objects.mixins.scrobble import ScrobbleMixin
class Movie(Video, Metadata, RateMixin, ScrobbleMixin):
country = Property(resolver=lambda: Country.from_node)
genres = Property(resolver=lambda: Genre.from_node)
roles = Property(resolver=lambda: Role.from_node)
studio = Property
content_rating = Property('contentRating')
year = Property(type=int)
originally_available_at = Property('originallyAvailableAt')
tagline = Property

@ -1,78 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.library.container import ChildrenContainer
from plex.objects.library.metadata.show import Show
from plex.objects.library.metadata.base import Metadata
from plex.objects.library.video import Directory
class Season(Directory, Metadata):
show = Property(resolver=lambda: Season.construct_show)
index = Property(type=int)
banner = Property
theme = Property
year = Property(type=int)
episode_count = Property('leafCount', int)
viewed_episode_count = Property('viewedLeafCount', int)
view_count = Property('viewCount', type=int)
def children(self):
return self.client['library/metadata'].children(self.rating_key)
@staticmethod
def construct_show(client, node):
attribute_map = {
'index' : 'parentIndex',
'key' : 'parentKey',
'ratingKey': 'parentRatingKey',
'title' : 'parentTitle',
'summary' : 'parentSummary',
'thumb' : 'parentThumb',
'theme' : 'parentTheme'
}
return Show.construct(client, node, attribute_map, child=True)
class SeasonChildrenContainer(ChildrenContainer):
show = Property(resolver=lambda: SeasonChildrenContainer.construct_show)
season = Property(resolver=lambda: SeasonChildrenContainer.construct_season)
key = Property
banner = Property
theme = Property
@staticmethod
def construct_show(client, node):
attribute_map = {
'title' : 'grandparentTitle',
'contentRating': 'grandparentContentRating',
'studio' : 'grandparentStudio',
'theme' : 'grandparentTheme'
}
return Show.construct(client, node, attribute_map, child=True)
@staticmethod
def construct_season(client, node):
attribute_map = {
'index': 'parentIndex',
'title': 'parentTitle'
}
return Season.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(ChildrenContainer, self).__iter__():
item.show = self.show
item.season = self.season
yield item

@ -1,85 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.directory import Directory
from plex.objects.library.container import LeavesContainer, ChildrenContainer
from plex.objects.library.metadata.base import Metadata
from plex.objects.mixins.rate import RateMixin
class Show(Directory, Metadata, RateMixin):
index = Property(type=int)
duration = Property(type=int)
studio = Property
content_rating = Property('contentRating')
banner = Property
theme = Property
year = Property(type=int)
originally_available_at = Property('originallyAvailableAt')
season_count = Property('childCount', int)
episode_count = Property('leafCount', int)
viewed_episode_count = Property('viewedLeafCount', int)
view_count = Property('viewCount', int)
def all_leaves(self):
return self.client['library/metadata'].all_leaves(self.rating_key)
def children(self):
return self.client['library/metadata'].children(self.rating_key)
class ShowChildrenContainer(ChildrenContainer):
show = Property(resolver=lambda: ShowLeavesContainer.construct_show)
key = Property
summary = Property
banner = Property
theme = Property
@staticmethod
def construct_show(client, node):
attribute_map = {
'index': 'parentIndex',
'title': 'parentTitle',
'year' : 'parentYear'
}
return Show.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(ChildrenContainer, self).__iter__():
item.show = self.show
yield item
class ShowLeavesContainer(LeavesContainer):
show = Property(resolver=lambda: ShowLeavesContainer.construct_show)
key = Property
banner = Property
theme = Property
@staticmethod
def construct_show(client, node):
attribute_map = {
'index': 'parentIndex',
'title': 'parentTitle',
'year' : 'parentYear'
}
return Show.construct(client, node, attribute_map, child=True)
def __iter__(self):
for item in super(LeavesContainer, self).__iter__():
item.show = self.show
yield item

@ -1,47 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.directory import Directory
from plex.objects.library.metadata.album import Album
from plex.objects.library.metadata.artist import Artist
from plex.objects.library.metadata.base import Metadata
from plex.objects.mixins.scrobble import ScrobbleMixin
from plex.objects.mixins.session import SessionMixin
class Track(Directory, Metadata, SessionMixin, ScrobbleMixin):
artist = Property(resolver=lambda: Track.construct_artist)
album = Property(resolver=lambda: Track.construct_album)
index = Property(type=int)
view_count = Property('viewCount', type=int)
view_offset = Property('viewOffset', type=int)
duration = Property(type=int)
@staticmethod
def construct_artist(client, node):
attribute_map = {
'key': 'grandparentKey',
'ratingKey': 'grandparentRatingKey',
'title': 'grandparentTitle',
'thumb': 'grandparentThumb'
}
return Artist.construct(client, node, attribute_map, child=True)
@staticmethod
def construct_album(client, node):
attribute_map = {
'index': 'parentIndex',
'key': 'parentKey',
'ratingKey': 'parentRatingKey',
'title': 'parentTitle',
'year': 'parentYear',
'thumb': 'parentThumb'
}
return Album.construct(client, node, attribute_map, child=True)

@ -1,26 +0,0 @@
from plex.objects.core.base import Descriptor, Property
from plex.objects.library.stream import Stream
class Part(Descriptor):
streams = Property(resolver=lambda: Stream.from_node)
id = Property(type=int)
key = Property
file = Property
container = Property
duration = Property(type=int)
size = Property(type=int)
@classmethod
def from_node(cls, client, node):
items = []
for genre in cls.helpers.findall(node, 'Part'):
_, obj = Part.construct(client, genre, child=True)
items.append(obj)
return [], items

@ -1,37 +0,0 @@
from plex.core.idict import idict
from plex.objects.core.base import Property
from plex.objects.directory import Directory
class Section(Directory):
uuid = Property
filters = Property(type=bool)
refreshing = Property(type=bool)
agent = Property
scanner = Property
language = Property
composite = Property
type = Property
created_at = Property('createdAt', int)
def __transform__(self):
self.path = '/library/sections/%s' % self.key
def all(self):
response = self.http.get('all')
return self.parse(response, idict({
'MediaContainer': ('MediaContainer', idict({
'Directory': {
'artist': 'Artist',
'show': 'Show'
},
'Video': {
'movie': 'Movie'
}
}))
}))

@ -1,56 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Stream(Descriptor):
id = Property(type=int)
index = Property(type=int)
stream_key = Property('key')
stream_type = Property('streamType', type=int)
selected = Property(type=bool)
forced = Property(type=bool)
default = Property(type=bool)
title = Property
duration = Property(type=int)
codec = Property
codec_id = Property('codecID')
bit_depth = Property('bitDepth', type=int)
chroma_subsampling = Property('chromaSubsampling')
color_space = Property('colorSpace')
width = Property(type=int)
height = Property(type=int)
bitrate = Property(type=int)
bitrate_mode = Property('bitrateMode')
channels = Property(type=int)
sampling_rate = Property('samplingRate', type=int)
frame_rate = Property('frameRate')
profile = Property
scan_type = Property('scanType')
language = Property('language')
language_code = Property('languageCode')
bvop = Property(type=int)
gmc = Property(type=int)
level = Property(type=int)
qpel = Property(type=int)
@classmethod
def from_node(cls, client, node):
items = []
for genre in cls.helpers.findall(node, 'Stream'):
_, obj = Stream.construct(client, genre, child=True)
items.append(obj)
return [], items

@ -1,22 +0,0 @@
from plex.objects.core.base import Property
from plex.objects.directory import Directory
from plex.objects.library.extra.director import Director
from plex.objects.library.extra.writer import Writer
from plex.objects.library.media import Media
from plex.objects.mixins.session import SessionMixin
class Video(Directory, SessionMixin):
director = Property(resolver=lambda: Director.from_node)
media = Property(resolver=lambda: Media.from_node)
writers = Property(resolver=lambda: Writer.from_node)
view_count = Property('viewCount', type=int)
view_offset = Property('viewOffset', type=int)
chapter_source = Property('chapterSource')
duration = Property(type=int)
@property
def seen(self):
return self.view_count and self.view_count >= 1

@ -1,10 +0,0 @@
from plex import Plex
from plex.objects.core.base import Property, DescriptorMixin
class RateMixin(DescriptorMixin):
rating = Property(type=float)
user_rating = Property('userRating', type=float)
def rate(self, value):
return Plex['library'].rate(self.rating_key, value)

@ -1,7 +0,0 @@
from plex import Plex
from plex.objects.core.base import DescriptorMixin
class ScrobbleMixin(DescriptorMixin):
def scrobble(self):
return Plex['library'].scrobble(self.rating_key)

@ -1,32 +0,0 @@
from plex.objects.core.base import Descriptor, Property, DescriptorMixin
from plex.objects.player import Player
from plex.objects.transcode_session import TranscodeSession
from plex.objects.user import User
class SessionMixin(DescriptorMixin):
session = Property(resolver=lambda: SessionMixin.construct_session)
@staticmethod
def construct_session(client, node):
return Session.construct(client, node, child=True)
class Session(Descriptor):
key = Property('sessionKey', int)
user = Property(resolver=lambda: Session.construct_user)
player = Property(resolver=lambda: Session.construct_player)
transcode_session = Property(resolver=lambda: Session.construct_transcode_session)
@classmethod
def construct_user(cls, client, node):
return User.construct(client, cls.helpers.find(node, 'User'), child=True)
@classmethod
def construct_player(cls, client, node):
return Player.construct(client, cls.helpers.find(node, 'Player'), child=True)
@classmethod
def construct_transcode_session(cls, client, node):
return TranscodeSession.construct(client, cls.helpers.find(node, 'TranscodeSession'), child=True)

@ -1,11 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Player(Descriptor):
title = Property
machine_identifier = Property('machineIdentifier')
state = Property
platform = Property
product = Property

@ -1,12 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Server(Descriptor):
name = Property
host = Property
address = Property
port = Property(type=int)
machine_identifier = Property('machineIdentifier')
version = Property

@ -1,21 +0,0 @@
from plex.core.helpers import to_iterable
from plex.objects.library.container import MediaContainer
class SessionContainer(MediaContainer):
filter_passes = lambda _, allowed, value: allowed is None or value in allowed
def filter(self, keys=None):
keys = to_iterable(keys)
for item in self:
if not self.filter_passes(keys, item.session.key):
continue
yield item
def get(self, key):
for item in self.filter(key):
return item
return None

@ -1,53 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class Setting(Descriptor):
id = Property
label = Property
summary = Property
type = Property
group = Property
value = Property(resolver=lambda: Setting.parse_value)
default = Property(resolver=lambda: Setting.parse_default)
options = Property('enumValues', resolver=lambda: Setting.parse_options)
hidden = Property(type=[int, bool])
advanced = Property(type=[int, bool])
@classmethod
def parse_value(cls, client, node):
type = cls.helpers.get(node, 'type')
value = cls.helpers.get(node, 'value')
return ['value'], Setting.convert(type, value)
@classmethod
def parse_default(cls, client, node):
type = cls.helpers.get(node, 'type')
default = cls.helpers.get(node, 'default')
return ['default'], Setting.convert(type, default)
@classmethod
def parse_options(cls, client, node):
value = cls.helpers.get(node, 'enumValues')
if not value:
return [], None
return ['enumValues'], [
tuple(option.split(':', 2)) for option in value.split('|')
]
@staticmethod
def convert(type, value):
if type == 'bool':
value = value.lower()
value = value == 'true'
elif type == 'int':
value = int(value)
return value

@ -1,24 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class TranscodeSession(Descriptor):
key = Property
progress = Property(type=float)
speed = Property(type=float)
duration = Property(type=int)
protocol = Property
throttled = Property(type=int) # TODO this needs to cast: str -> int -> bool
container = Property('container')
video_codec = Property('videoCodec')
video_decision = Property('videoDecision')
audio_codec = Property('audioCodec')
audio_channels = Property('audioChannels', int)
audio_decision = Property('audioDecision')
width = Property(type=int)
height = Property(type=int)

@ -1,8 +0,0 @@
from plex.objects.core.base import Descriptor, Property
class User(Descriptor):
id = Property(type=int)
title = Property
thumb = Property

@ -1,121 +0,0 @@
from plex.lib.six.moves.urllib_parse import urlencode
from requests import Request
import json
class PlexRequest(object):
def __init__(self, client, **kwargs):
self.client = client
self.kwargs = kwargs
self.request = None
# Parsed Attributes
self.path = None
self.params = None
self.data = None
self.headers = None
self.method = None
def prepare(self):
self.request = Request()
self.transform_parameters()
self.request.url = self.construct_url()
self.request.data = self.transform_data()
self.request.headers = self.transform_headers()
self.request.method = self.transform_method()
return self.request.prepare()
def construct_url(self):
"""Construct a full plex request URI, with `params`."""
path = [self.path]
path.extend([str(x) for x in self.params])
url = self.client.base_url + '/'.join(x for x in path if x)
query = self.kwargs.get('query')
if query:
# Dict -> List
if type(query) is dict:
query = query.items()
# Remove items with `None` value
query = [
(k, v) for (k, v) in query
if v is not None
]
# Encode query, append to URL
url += '?' + urlencode(query)
return url
def transform_parameters(self):
# Transform `path`
self.path = self.kwargs.get('path')
if not self.path.startswith('/'):
self.path = '/' + self.path
if self.path.endswith('/'):
self.path = self.path[:-1]
# Transform `params` into list
self.params = self.kwargs.get('params') or []
if type(self.params) is not list:
self.params = [self.params]
def transform_data(self):
self.data = self.kwargs.get('data')
if self.data is None:
return None
return json.dumps(self.data)
def transform_headers(self):
self.headers = self.kwargs.get('headers') or {}
# Authentication
self.headers['X-Plex-Token'] = self.client.configuration['authentication.token']
# Client
self.headers['X-Plex-Client-Identifier'] = self.client.configuration['client.identifier']
self.headers['X-Plex-Product'] = self.client.configuration['client.product']
self.headers['X-Plex-Version'] = self.client.configuration['client.version']
# Device
self.headers['X-Device'] = self.client.configuration['device.system']
self.headers['X-Device-Name'] = self.client.configuration['device.name']
# Platform
self.headers['X-Platform'] = self.client.configuration['platform.name']
self.headers['X-Platform-Version'] = self.client.configuration['platform.version']
# Update with extra headers from configuration
c_headers = self.client.configuration['headers']
if c_headers:
self.headers.update(c_headers)
# Only return headers with valid values
return dict([
(k, v) for (k, v) in self.headers.items()
if v is not None
])
def transform_method(self):
self.method = self.kwargs.get('method')
# Pick `method` (if not provided)
if not self.method:
self.method = 'POST' if self.data else 'GET'
return self.method

@ -1,17 +0,0 @@
import jsonpickle
class Serializer(object):
@classmethod
def encode(cls, value):
return jsonpickle.encode(value)
@classmethod
def decode(cls, value, client=None):
try:
result = jsonpickle.decode(value)
result.client = client
return result
except:
return None
Loading…
Cancel
Save