def build_since(status): since = datetime.utcnow() - status.latest.ended_at if since.seconds < 1: return _('Last run just a moment ago') return _('Last run %s') % human(since, precision=1)
class PinOption(SimpleOption): type = 'string' group = (_('Authentication'), ) label = _('Authentication PIN') preference = 'pin' def on_database_changed(self, value, account=None): # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account): if not value: # Ignore empty PIN field return None # Retrieve administrator account trakt_account = TraktAccountManager.get(TraktAccount.account == account) # Update administrator authorization if not TraktAccountManager.update.from_pin(trakt_account, value): log.warn('Unable to update account') return None return value
def build(cls, account, mode, section=None): status = SyncResult.get_latest(account, mode, section).first() if status is None or status.latest is None: return _('Not run yet.') # Build status fragments fragments = [] if status.latest.ended_at: # Build "Last run [...] ago" fragment fragments.append(cls.build_since(status)) if status.latest.started_at: # Build "taking [...] seconds" fragment fragments.append(cls.build_elapsed(status)) # Build result fragment (success, errors) fragments.append(cls.build_result(status)) # Merge fragments if len(fragments): return ', '.join(fragments) + '.' return _('Not run yet.')
def build_elapsed(status): elapsed = status.latest.ended_at - status.latest.started_at if elapsed.seconds < 1: return _('taking less than a second') return _('taking %s') % human(elapsed, precision=1, past_tense='%s')
def ListMessages(days=14, version='latest', viewed=False, *args, **kwargs): # Cast `viewed` to boolean if type(viewed) is str: if viewed == 'None': viewed = None else: viewed = viewed == 'True' # Retrieve messages messages = list( List(days=try_convert(days, int), version=version, viewed=viewed).order_by(Message.last_logged_at.desc()).limit(50)) total_messages = List( days=try_convert(days, int), version=version, ).count() # Construct container oc = ObjectContainer(title2=_("Messages")) if viewed is False and len(messages) > 1: oc.add( DirectoryObject(key=Callback(DismissMessages), title=pad_title(_("Dismiss all")))) for m in messages: if m.type is None or\ m.summary is None: continue thumb = None if m.type == Message.Type.Exception: thumb = R("icon-exception-viewed.png") if m.viewed else R( "icon-exception.png") elif m.type == Message.Type.Info: thumb = R("icon-notification-viewed.png") if m.viewed else R( "icon-notification.png") elif m.type in ERROR_TYPES: thumb = R("icon-error-viewed.png") if m.viewed else R( "icon-error.png") oc.add( DirectoryObject( key=Callback(ViewMessage, error_id=m.id), title=pad_title('[%s] %s' % (Message.Type.title(m.type), m.summary)), thumb=thumb)) # Append "View All" button if len(messages) != 50 and len(messages) < total_messages: oc.add( DirectoryObject(key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("View All")))) return oc
def build_status_summary(current): summary = _('Working') # Estimated time remaining remaining_seconds = current.progress.remaining_seconds if remaining_seconds is not None: summary += _(', %.02f seconds remaining') % remaining_seconds return summary
class SyncListsPersonalPlaylistsOption(SimpleOption): key = 'sync.lists.personal.playlists' type = 'boolean' default = True group = (_('Sync - Lists (Beta)'), _('Personal')) label = _('Create playlists in plex') description = _("Create playlists in Plex if they don't already exist.") order = 311
class SyncListsWatchlistPlaylistsOption(SimpleOption): key = 'sync.lists.watchlist.playlists' type = 'boolean' default = True group = (_('Sync - Lists (Beta)'), _('Watchlist')) label = _('Create playlist in plex') description = _("Create playlist in Plex if it doesn't already exist.") order = 321
def ViewMessage(error_id, *args, **kwargs): # Retrieve message from database message = MessageManager.get.by_id(error_id) # Update `last_viewed_at` field message.last_viewed_at = datetime.utcnow() message.save() # Parse request headers web_client = Request.Headers.get('X-Plex-Product', '').lower() == 'plex web' # Build objects oc = ObjectContainer( title2='[%s] %s' % (Message.Type.title(message.type), Trim(message.summary))) if message.type == Message.Type.Exception: # Display exception samples for e in message.exceptions.order_by( Exception.timestamp.desc()).limit(50): since = datetime.utcnow() - e.timestamp callback = Callback(ViewMessage, error_id=error_id) if web_client: # Display exception traceback in Plex/Web callback = Callback(ViewException, exception_id=e.id) oc.add( DirectoryObject( key=callback, title=pad_title( '[%s] %s: %s' % (human(since, precision=1), e.type, e.message)), thumb=R("icon-exception.png"))) elif message.type in [ Message.Type.Info, Message.Type.Warning, Message.Type.Error, Message.Type.Critical ]: # Display message code oc.add( DirectoryObject(key='', title=pad_title(_('Code: %s') % hex(message.code)))) # Display message description if message.description: oc.add( DirectoryObject(key='', title=pad_title( _('Description: %s') % message.description))) return oc
def title(cls, value): if cls.__titles__ is None: # Build titles map cls.__titles__ = { cls.Full: _('Full'), cls.Pull: _('Pull'), cls.Push: _('Push'), cls.FastPull: _('Quick Pull') } return cls.__titles__.get(value)
def build_elapsed(status): elapsed = status.latest.ended_at - status.latest.started_at if elapsed.seconds < 1: return _('taking less than a second') return _('taking %s') % human( elapsed, precision=1, past_tense='%s' )
def AboutMenu(*args, **kwargs): oc = ObjectContainer(title2=_("About")) oc.add( DirectoryObject(key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("Messages")))) oc.add( DirectoryObject(key=Callback(AboutMenu), title=pad_title(_("Version: %s") % PLUGIN_VERSION))) return oc
def title(cls, value): if cls.__titles__ is None: # Build titles map cls.__titles__ = { cls.All: _('All'), cls.Movies: _('Movies'), cls.Shows: _('Shows'), cls.Seasons: _('Seasons'), cls.Episodes: _('Episodes'), } return cls.__titles__.get(value)
class SyncActionOption(SimpleOption): key = 'sync.action.mode' type = 'enum' choices = ACTION_MODE_LABELS_BY_KEY default = None scope = 'server' group = (_('Advanced'), _('Sync')) label = _('Action') description = Description(_("Action to perform during syncs."), [ (_("Update"), _("Update Trakt.tv and Plex with any changes")), (_("Log"), _("Don't perform any updates, just display changes in the plugin log file" )) ]) order = 120 @property def value(self): value = super(SyncActionOption, self).value if value is None: return SyncActionMode.Update return value
def AboutMenu(*args, **kwargs): oc = ObjectContainer( title2=_("About") ) oc.add(DirectoryObject( key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("Messages")) )) oc.add(DirectoryObject( key=Callback(AboutMenu), title=pad_title(_("Version: %s") % PLUGIN_VERSION) )) return oc
def ViewMessage(error_id, *args, **kwargs): # Retrieve message from database message = MessageManager.get.by_id(error_id) # Update `last_viewed_at` field message.last_viewed_at = datetime.utcnow() message.save() # Parse request headers web_client = Request.Headers.get('X-Plex-Product', '').lower() == 'plex web' # Build objects oc = ObjectContainer( title2='[%s] %s' % (Message.Type.title(message.type), Trim(message.summary)) ) if message.type == Message.Type.Exception: # Display exception samples for e in message.exceptions.order_by(Exception.timestamp.desc()).limit(50): since = datetime.utcnow() - e.timestamp callback = Callback(ViewMessage, error_id=error_id) if web_client: # Display exception traceback in Plex/Web callback = Callback(ViewException, exception_id=e.id) oc.add(DirectoryObject( key=callback, title=pad_title('[%s] %s: %s' % (human(since, precision=1), e.type, e.message)), thumb=R("icon-exception.png") )) elif message.type in [Message.Type.Info, Message.Type.Warning, Message.Type.Error, Message.Type.Critical]: # Display message code oc.add(DirectoryObject( key='', title=pad_title(_('Code: %s') % hex(message.code)) )) # Display message description if message.description: oc.add(DirectoryObject( key='', title=pad_title(_('Description: %s') % message.description) )) return oc
class MatcherOption(SimpleOption): key = 'matcher.mode' type = 'enum' choices = MATCHER_LABELS_BY_KEY default = MatcherMode.PlexExtended scope = 'server' group = (_('Advanced'), _('Matcher')) label = _('Mode') description = Description( _("Matcher to use for episode identification."), [ (_("Plex"), _( "Use the episode identifier provided by Plex" )), (_("Plex Extended"), _( "Use [Caper](https://github.com/fuzeman/caper) to parse episode filenames for an " "identifier *(provides better support for multi-episode files)*" )) ] ) order = 110 preference = 'matcher' def on_changed(self, value, account=None): # Update matcher configuration extended = value == MatcherMode.PlexExtended plex_metadata.Matcher.caper_enabled = extended plex_metadata.Matcher.extend_enabled = extended log.debug('Configured matcher, extended: %r', extended) def on_database_changed(self, value, account=None): if value not in MATCHER_IDS_BY_KEY: log.warn('Unknown value: %r', value) return # Map `value` to plex preference value = MATCHER_IDS_BY_KEY[value] # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): if value not in MATCHER_KEYS_BY_LABEL: log.warn('Unknown value: %r', value) return # Map plex `value` value = MATCHER_KEYS_BY_LABEL[value] # Update database self.update(value, emit=False) return value
def build_result(cls, status): if status.latest.success: return _('was successful') message = _('failed') # Resolve errors errors = list(status.latest.get_errors()) if len(errors) > 1: # Multiple errors message += _(' (%d errors, %s)') % (len(errors), cls.format_error(errors[0])) elif len(errors) == 1: # Single error message += _(' (%s)') % cls.format_error(errors[0]) return message
def build_result(status): if status.latest.success: return _('was successful') message = _('failed') # Resolve errors errors = list(status.latest.get_errors()) if len(errors) > 1: # Multiple errors message += _(' (%d errors, %s)') % (len(errors), errors[0].summary) elif len(errors) == 1: # Single error message += _(' (%s)') % errors[0].summary return message
def ValidatePrefs(): # Retrieve plex token token_plex = AccountMigration.get_token(Request.Headers) # Retrieve current activity mode last_activity_mode = Preferences.get('activity.mode') if Request.Headers.get('X-Disable-Preference-Migration', '0') == '0': # Run account migration am = AccountMigration() am.run(token_plex) # Migrate server preferences Preferences.migrate() # Try migrate administrator preferences try: Preferences.initialize(account=1) Preferences.migrate(account=1) except Account.DoesNotExist: log.debug( 'Unable to migrate administrator preferences, no account found' ) else: log.debug('Ignoring preference migration (disabled by header)') # Restart if activity_mode has changed if Preferences.get('activity.mode') != last_activity_mode or Prefs[ 'language'] != Dict['language']: log.info('Restart required to apply changes, restarting plugin...') def restart(): # Delay until after `ValidatePrefs` returns time.sleep(3) # Restart plugin Plex[':/plugins'].restart(PLUGIN_IDENTIFIER) spawn(restart) return MessageContainer(_("Success"), _("Success")) # Fire configuration changed callback spawn(Main.on_configuration_changed) return MessageContainer(_("Success"), _("Success"))
class ActivityOption(SimpleOption): key = 'activity.mode' type = 'enum' choices = ACTIVITY_LABELS_BY_KEY default = ActivityMode.Automatic scope = 'server' group = (_('Advanced'), _('Activity')) label = _('Method') description = Description(_("Method used to retrieve watching activity"), [ (_("Automatic"), _("Automatically determine available activity method")), (_("WebSocket"), _("Retrieve watching activity from the Plex notification channel")), (_("Logging"), _("Parse \"Plex Media Server.log\" for watching activity *(higher CPU + Disk IO usage, may block system sleep)*" )) ]) order = 100 preference = 'activity_mode' def on_database_changed(self, value, account=None): if value not in ACTIVITY_IDS_BY_KEY: log.warn('Unknown value: %r', value) return # Map `value` to plex preference value = ACTIVITY_IDS_BY_KEY[value] # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): if value not in ACTIVITY_KEYS_BY_LABEL: log.warn('Unknown value: %r', value) return # Map plex `value` value = ACTIVITY_KEYS_BY_LABEL[value] # Update database self.update(value, emit=False) return value
class BackupMaintenanceIntervalOption(SchedulerOption): key = 'backup.interval' type = 'enum' choices = INTERVAL_LABELS_BY_KEY default = SyncInterval.D1 scope = 'server' group = (_('Backups'), ) label = _('Maintenance Interval') description = _( "Interval to perform backup maintenance tasks, this involves:\n" " - Compressing backups for the previous month and year\n" " - Deleting backups for the previous period by these rules:\n" " - 4 backups per day\n" " - 14 backups per week\n" " - 28 backups per month") order = 10
def MainMenu(*args, **kwargs): oc = ObjectContainer(no_cache=True) # # Messages # m_count, m_type = MessageStatus(viewed=False) if m_count > 0: oc.add(DirectoryObject( key=Callback(ListMessages, viewed=False), title=_("Messages (%s)") % locale.format("%d", m_count, grouping=True), thumb=R("icon-%s.png" % m_type) )) # # Sync # oc.add(DirectoryObject( key=Callback(ControlsMenu if Accounts.count() == 1 else AccountsMenu), title=_("Sync"), summary=_("Synchronize your libraries with Trakt.tv"), thumb=R("icon-sync.png") )) # # About # oc.add(DirectoryObject( key=Callback(AboutMenu), title=_("About"), thumb=R("icon-about.png") )) oc.add(PrefsObject( title=_("Preferences"), thumb=R("icon-preferences.png") )) return oc
def ValidatePrefs(): # Retrieve plex token token_plex = AccountMigration.get_token(Request.Headers) # Retrieve current activity mode last_activity_mode = Preferences.get("activity.mode") if Request.Headers.get("X-Disable-Preference-Migration", "0") == "0": # Run account migration am = AccountMigration() am.run(token_plex) # Migrate server preferences Preferences.migrate() # Try migrate administrator preferences try: Preferences.initialize(account=1) Preferences.migrate(account=1) except Account.DoesNotExist: log.debug("Unable to migrate administrator preferences, no account found") else: log.debug("Ignoring preference migration (disabled by header)") # Restart if activity_mode has changed if RestartRequired(last_activity_mode): log.info("Restart required to apply changes, restarting plugin...") def restart(): # Delay until after `ValidatePrefs` returns time.sleep(3) # Restart plugin Plex[":/plugins"].restart(PLUGIN_IDENTIFIER) spawn(restart, daemon=True) return MessageContainer(_("Success"), _("Success")) # Fire configuration changed callback spawn(Main.on_configuration_changed, daemon=True) return MessageContainer(_("Success"), _("Success"))
class ScrobbleDuplicationPeriodOption(SimpleOption): key = 'scrobble.duplication_period' type = 'enum' choices = DUPLICATION_PERIOD_LABELS_BY_KEY default = ScrobbleDuplicationPeriod.H6 scope = 'server' group = (_('Advanced'), _('Scrobble')) label = _('Ignore duplicates for') description = _( "Duplicate scrobbles for a movie or episode are ignored for the defined period.\n" "\n" "This can help reduce the chances of unintended history duplication if you resume watching something that " "has already reached 80% progress (so has already been scrobbled).") order = 115 preference = 'scrobble_duplication_period' def on_database_changed(self, value, account=None): if value not in DUPLICATION_PERIOD_IDS_BY_KEY: log.warn('Unknown value: %r', value) return # Map `value` to plex preference value = DUPLICATION_PERIOD_IDS_BY_KEY[value] # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): if value not in DUPLICATION_PERIOD_KEYS_BY_LABEL: log.warn('Unknown value: %r', value) return # Map plex `value` value = DUPLICATION_PERIOD_KEYS_BY_LABEL[value] # Update database self.update(value, account, emit=False) return value
def MainMenu(): oc = ObjectContainer(no_cache=True) # # Messages # m_count, m_type = MessageStatus(viewed=False) if m_count > 0: oc.add(DirectoryObject( key=Callback(ListMessages, viewed=False), title=_("Messages (%s)") % locale.format("%d", m_count, grouping=True), thumb=R("icon-%s.png" % m_type) )) # # Sync # oc.add(DirectoryObject( key=Callback(ControlsMenu if Accounts.count() == 1 else AccountsMenu), title=_("Sync"), summary=_("Synchronize your libraries with Trakt.tv"), thumb=R("icon-sync.png") )) # # About # oc.add(DirectoryObject( key=Callback(AboutMenu), title=_("About"), thumb=R("icon-about.png") )) oc.add(PrefsObject( title=_("Preferences"), thumb=R("icon-preferences.png") )) return oc
class SyncRatingsConflictOption(SimpleOption): key = 'sync.ratings.conflict' type = 'enum' choices = RESOLUTION_LABELS_BY_KEY default = SyncConflictResolution.Latest group = (_('Sync'), _('Ratings')) label = _('Conflict resolution') description = Description( _("Rating to use when a conflict exists between Plex and your Trakt.tv profile." ), [(_("Latest"), _("Use the most recent rating")), (_("Trakt"), _("Use the rating from your Trakt.tv profile")), (_("Plex"), _("Use the rating from Plex"))]) order = 211 preference = 'sync_ratings_conflict' def on_database_changed(self, value, account=None): if value not in RESOLUTION_IDS_BY_KEY: log.warn('Unknown value: %r', value) return # Map `value` to plex preference value = RESOLUTION_IDS_BY_KEY[value] # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): if value not in RESOLUTION_KEYS_BY_LABEL: log.warn('Unknown value: %r', value) return # Map plex `value` value = RESOLUTION_KEYS_BY_LABEL[value] # Update database self.update(value, account, emit=False) return value
class ApiOption(SimpleOption): key = 'api.enabled' type = 'boolean' default = None scope = 'server' group = (_('API'),) label = _('Enabled') description = _( "Enables the plugin administration API, disabling this option will block access to the configuration site." ) order = 200 @property def value(self): value = super(ApiOption, self).value if value is None: return True return value
class SyncIdleDelayOption(SimpleOption): key = 'sync.idle_delay' type = 'enum' choices = IDLE_DELAY_LABELS_BY_KEY default = SyncIdleDelay.M30 scope = 'server' group = (_('Advanced'), _('Sync - Triggers')) label = _('Idle delay') description = _( "Wait time before the server is considered idle after media stops being streamed." ) order = 131 preference = 'sync_idle_delay' def on_database_changed(self, value, account=None): if value not in IDLE_DELAY_IDS_BY_KEY: log.warn('Unknown value: %r', value) return # Map `value` to plex preference value = IDLE_DELAY_IDS_BY_KEY[value] # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): if value not in IDLE_DELAY_KEYS_BY_LABEL: log.warn('Unknown value: %r', value) return # Map plex `value` value = IDLE_DELAY_KEYS_BY_LABEL[value] # Update database self.update(value, account, emit=False) return value
def title(cls, value): if cls.__titles__ is None: cls.__titles__ = { MessageType.Generic: None, MessageType.Exception: _("Exception"), MessageType.Info: _("Info"), MessageType.Warning: _("Warning"), MessageType.Error: _("Error"), MessageType.Critical: _("Critical"), MessageType.Trakt: _("Trakt.tv"), MessageType.Plex: _("Plex.tv"), MessageType.Sentry: _("Sentry") } return cls.__titles__.get(value)
class SyncLibraryUpdateOption(SimpleOption): key = 'sync.library_update' type = 'boolean' default = False group = (_('Sync'), _('Triggers')) label = _('After library updates') description = _( "Automatically trigger a \"Full\" sync after your Plex libraries are updated." ) order = 251 preference = 'sync_run_library' def on_database_changed(self, value, account=None): # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): # Update database self.update(value, account, emit=False) return value
class SyncProfilerOption(SimpleOption): key = 'sync.profiler' type = 'enum' choices = PROFILER_MODE_LABELS_BY_KEY default = SyncProfilerMode.Disabled scope = 'server' group = (_('Advanced'), _('Sync')) label = _('Profiler') description = Description( _("Profiler to use for performance analysis during syncs"), [(_("Basic"), _("Basic per-method elapsed time reports *([elapsed.py](https://github.com/fuzeman/elapsed.py))*" )), (_("Disabled"), _("Disable sync profiling"))]) order = 121
class SyncCleanCollectionOption(SimpleOption): key = 'sync.collection.clean' type = 'boolean' default = False group = (_('Sync'), _('Collection')) label = _('Clean collection') description = _( "Remove movies and episodes from your Trakt.tv collection that are unable to be found in your Plex libraries." ) order = 231 preference = 'sync_clean_collection' def on_database_changed(self, value, account=None): # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): # Update database self.update(value, account, emit=False) return value
class SyncIdleDeferOption(SimpleOption): key = 'sync.idle_defer' type = 'boolean' default = True scope = 'server' group = (_('Advanced'), _('Sync - Triggers')) label = _('Defer until server is idle') description = _( "Defer automatic syncs until the server isn't streaming any media." ) order = 130 preference = 'sync_idle_defer' def on_database_changed(self, value, account=None): # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): # Update database self.update(value, account, emit=False) return value
class ScrobbleOption(SimpleOption): key = 'scrobble.enabled' type = 'boolean' default = True group = (_('Scrobble'),) label = _('Enabled') description = _( "Send your watching activity to Trakt.tv in real-time - this will update the \"currently watching\" status on " "your profile and mark items as watched when they reach 80% progress." ) order = 100 preference = 'start_scrobble' def on_database_changed(self, value, account=None): # Update preference return self._update_preference(value, account) def on_plex_changed(self, value, account=None): # Update database self.update(value, account, emit=False) return value
def title(cls, value): if cls.__titles__ is None: # Build titles map cls.__titles__ = { cls.All: _('All'), cls.Collection: _('Collection'), cls.Playback: _('Playback'), cls.Ratings: _('Ratings'), cls.Watched: _('Watched'), cls.Watchlist: _('Watchlist') } return cls.__titles__.get(value)
def AccountsMenu(refresh=None, *args, **kwargs): oc = ObjectContainer( title2=_("Accounts"), no_cache=True ) # Active sync status Active.create( oc, callback=Callback(AccountsMenu, refresh=timestamp()), ) # Accounts for account in Accounts.list(): oc.add(DirectoryObject( key=Callback(ControlsMenu, account_id=account.id), title=account.name, art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts), thumb=function_path('Thumb.png', account_id=account.id, refresh=account.refreshed_ts) )) return oc
def ControlsMenu(account_id=1, title=None, message=None, refresh=None, message_only=False, *args, **kwargs): account = AccountManager.get(Account.id == account_id) # Build sync controls menu oc = ObjectContainer( title2=_("Sync (%s)") % account.name, no_cache=True, art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts) ) # Start result message if title and message: oc.add(DirectoryObject( key=Callback(ControlsMenu, account_id=account.id, refresh=timestamp()), title=pad_title(title), summary=message )) if message_only: return oc # Active sync status Active.create( oc, callback=Callback(ControlsMenu, account_id=account.id, refresh=timestamp()), account=account ) # # Full # oc.add(DirectoryObject( key=Trigger.callback(Synchronize, account), title=pad_title(SyncMode.title(SyncMode.Full)), summary=Status.build(account, SyncMode.Full), thumb=R("icon-sync.png"), art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts) )) # # Pull # oc.add(DirectoryObject( key=Trigger.callback(Pull, account), title=pad_title(_('%s from Trakt.tv') % SyncMode.title(SyncMode.Pull)), summary=Status.build(account, SyncMode.Pull), thumb=R("icon-sync_down.png"), art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts) )) oc.add(DirectoryObject( key=Trigger.callback(FastPull, account), title=pad_title(_('%s from Trakt.tv') % SyncMode.title(SyncMode.FastPull)), summary=Status.build(account, SyncMode.FastPull), thumb=R("icon-sync_down.png"), art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts) )) # # Push # p_account = account.plex try: # Retrieve account libraries/sections with p_account.authorization(): sections = Plex['library'].sections() except Exception as ex: # Build message if p_account is None: message = _("Plex account hasn't been authenticated") else: message = str(ex.message or ex) # Redirect to error message log.warn('Unable to retrieve account libraries/sections: %s', message, exc_info=True) return redirect('/sync', account_id=account_id, title=_('Error'), message=message, message_only=True ) section_keys = [] f_allow, f_deny = Filters.get('filter_sections') for section in sections.filter(['show', 'movie'], titles=f_allow): oc.add(DirectoryObject( key=Trigger.callback(Push, account, section), title=pad_title(_('%s "%s" to Trakt.tv') % (SyncMode.title(SyncMode.Push), section.title)), summary=Status.build(account, SyncMode.Push, section.key), thumb=R("icon-sync_up.png"), art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts) )) section_keys.append(section.key) if len(section_keys) > 1: oc.add(DirectoryObject( key=Trigger.callback(Push, account), title=pad_title(_('%s all to Trakt.tv') % SyncMode.title(SyncMode.Push)), summary=Status.build(account, SyncMode.Push), thumb=R("icon-sync_up.png"), art=function_path('Cover.png', account_id=account.id, refresh=account.refreshed_ts) )) return oc
def build_cancel(cls, current, title): return DirectoryObject( key=Callback(Cancel, account_id=current.account.id, id=current.id), title=pad_title(_('%s - Cancel') % title) )
def ListMessages(days=14, version='latest', viewed=False, *args, **kwargs): # Cast `viewed` to boolean if type(viewed) is str: if viewed == 'None': viewed = None else: viewed = viewed == 'True' # Retrieve messages messages = list(List( days=try_convert(days, int), version=version, viewed=viewed ).order_by( Message.last_logged_at.desc() ).limit(50)) total_messages = List( days=try_convert(days, int), version=version, ).count() # Construct container oc = ObjectContainer( title2=_("Messages") ) # Add "Dismiss All" button if viewed is False and len(messages) > 1: oc.add(DirectoryObject( key=Callback(DismissMessages), title=pad_title(_("Dismiss all")) )) # Add interface messages for record in InterfaceMessages.records: # Pick object thumb if record.level >= logging.WARNING: thumb = R("icon-error.png") else: thumb = R("icon-notification.png") # Add object oc.add(DirectoryObject( key=PLUGIN_PREFIX + '/messages/list', title=pad_title('[%s] %s' % (logging.getLevelName(record.level).capitalize(), record.message)), thumb=thumb )) # Add stored messages for m in messages: if m.type is None or\ m.summary is None: continue # Pick thumb if m.type == Message.Type.Exception: thumb = R("icon-exception-viewed.png") if m.viewed else R("icon-exception.png") elif m.type == Message.Type.Info: thumb = R("icon-notification-viewed.png") if m.viewed else R("icon-notification.png") elif m.type in CONNECTION_TYPES: thumb = R("icon-connection-viewed.png") if m.viewed else R("icon-connection.png") else: thumb = R("icon-error-viewed.png") if m.viewed else R("icon-error.png") # Add object oc.add(DirectoryObject( key=Callback(ViewMessage, error_id=m.id), title=pad_title('[%s] %s' % (Message.Type.title(m.type), m.summary)), thumb=thumb )) # Append "View All" button if len(messages) != 50 and len(messages) < total_messages: oc.add(DirectoryObject( key=Callback(ListMessages, days=None, viewed=None), title=pad_title(_("View All")) )) return oc
def build_status(cls, current, title, callback=None): return DirectoryObject( key=callback, title=pad_title(_('%s - Status') % title), summary=cls.build_status_summary(current) )