# -*- coding: utf-8 -*- from collections import defaultdict from urllib.parse import quote from plexapi import log, utils from plexapi.base import PlexObject from plexapi.exceptions import BadRequest, NotFound class Settings(PlexObject): """ Container class for all settings. Allows getting and setting PlexServer settings. Attributes: key (str): '/:/prefs' """ key = '/:/prefs' def __init__(self, server, data, initpath=None): self._settings = {} super(Settings, self).__init__(server, data, initpath) def __getattr__(self, attr): if attr.startswith('_'): try: return self.__dict__[attr] except KeyError: raise AttributeError return self.get(attr).value def __setattr__(self, attr, value): if not attr.startswith('_'): return self.get(attr).set(value) self.__dict__[attr] = value def _loadData(self, data): """ Load attribute values from Plex XML response. """ self._data = data for elem in data: id = utils.lowerFirst(elem.attrib['id']) if id in self._settings: self._settings[id]._loadData(elem) continue self._settings[id] = Setting(self._server, elem, self._initpath) def all(self): """ Returns a list of all :class:`~plexapi.settings.Setting` objects available. """ return [v for id, v in sorted(self._settings.items())] def get(self, id): """ Return the :class:`~plexapi.settings.Setting` object with the specified id. """ id = utils.lowerFirst(id) if id in self._settings: return self._settings[id] raise NotFound(f'Invalid setting id: {id}') def groups(self): """ Returns a dict of lists for all :class:`~plexapi.settings.Setting` objects grouped by setting group. """ groups = defaultdict(list) for setting in self.all(): groups[setting.group].append(setting) return dict(groups) def group(self, group): """ Return a list of all :class:`~plexapi.settings.Setting` objects in the specified group. Parameters: group (str): Group to return all settings. """ return self.groups().get(group, []) def save(self): """ Save any outstanding setting changes to the :class:`~plexapi.server.PlexServer`. This performs a full reload() of Settings after complete. """ params = {} for setting in self.all(): if setting._setValue: log.info('Saving PlexServer setting %s = %s', setting.id, setting._setValue) params[setting.id] = quote(setting._setValue) if not params: raise BadRequest('No setting have been modified.') querystr = '&'.join([f'{k}={v}' for k, v in params.items()]) url = f'{self.key}?{querystr}' self._server.query(url, self._server._session.put) self.reload() class Setting(PlexObject): """ Represents a single Plex setting. Attributes: id (str): Setting id (or name). label (str): Short description of what this setting is. summary (str): Long description of what this setting is. type (str): Setting type (text, int, double, bool). default (str): Default value for this setting. value (str,bool,int,float): Current value for this setting. hidden (bool): True if this is a hidden setting. advanced (bool): True if this is an advanced setting. group (str): Group name this setting is categorized as. enumValues (list,dict): List or dictionary of valid values for this setting. """ _bool_cast = lambda x: bool(x == 'true' or x == '1') _bool_str = lambda x: str(x).lower() TYPES = { 'bool': {'type': bool, 'cast': _bool_cast, 'tostr': _bool_str}, 'double': {'type': float, 'cast': float, 'tostr': str}, 'int': {'type': int, 'cast': int, 'tostr': str}, 'text': {'type': str, 'cast': str, 'tostr': str}, } def _loadData(self, data): """ Load attribute values from Plex XML response. """ self.type = data.attrib.get('type') self.advanced = utils.cast(bool, data.attrib.get('advanced')) self.default = self._cast(data.attrib.get('default')) self.enumValues = self._getEnumValues(data) self.group = data.attrib.get('group') self.hidden = utils.cast(bool, data.attrib.get('hidden')) self.id = data.attrib.get('id') self.label = data.attrib.get('label') self.option = data.attrib.get('option') self.secure = utils.cast(bool, data.attrib.get('secure')) self.summary = data.attrib.get('summary') self.value = self._cast(data.attrib.get('value')) self._setValue = None def _cast(self, value): """ Cast the specific value to the type of this setting. """ if self.type != 'enum': value = utils.cast(self.TYPES.get(self.type)['cast'], value) return value def _getEnumValues(self, data): """ Returns a list or dictionary of values for this setting. """ enumstr = data.attrib.get('enumValues') or data.attrib.get('values') if not enumstr: return None if ':' in enumstr: d = {} for kv in enumstr.split('|'): try: k, v = kv.split(':') d[self._cast(k)] = v except ValueError: d[self._cast(kv)] = kv return d return enumstr.split('|') def set(self, value): """ Set a new value for this setting. NOTE: You must call plex.settings.save() for before any changes to setting values are persisted to the :class:`~plexapi.server.PlexServer`. """ # check a few things up front if not isinstance(value, self.TYPES[self.type]['type']): badtype = type(value).__name__ raise BadRequest(f'Invalid value for {self.id}: a {self.type} is required, not {badtype}') if self.enumValues and value not in self.enumValues: raise BadRequest(f'Invalid value for {self.id}: {value} not in {list(self.enumValues)}') # store value off to the side until we call settings.save() tostr = self.TYPES[self.type]['tostr'] self._setValue = tostr(value) def toUrl(self): """Helper for urls""" return f'{self.id}={self._value or self.value}' @utils.registerPlexObject class Preferences(Setting): """ Represents a single Preferences. Attributes: TAG (str): 'Setting' FILTER (str): 'preferences' """ TAG = 'Setting' FILTER = 'preferences' def _default(self): """ Set the default value for this setting.""" key = f'{self._initpath}/prefs?' url = key + f'{self.id}={self.default}' self._server.query(url, method=self._server._session.put)