Esempio n. 1
0
    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)
Esempio n. 2
0
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
Esempio n. 3
0
    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)
Esempio n. 4
0
    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.')
Esempio n. 5
0
    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.')
Esempio n. 6
0
    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
Esempio n. 8
0
    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
Esempio n. 9
0
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
Esempio n. 10
0
    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
Esempio n. 11
0
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
Esempio n. 13
0
    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)
Esempio n. 14
0
    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'
        )
Esempio n. 15
0
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
Esempio n. 16
0
    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)
Esempio n. 17
0
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
Esempio n. 18
0
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
Esempio n. 19
0
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
Esempio n. 20
0
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
Esempio n. 21
0
    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
Esempio n. 22
0
    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
Esempio n. 23
0
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"))
Esempio n. 24
0
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
Esempio n. 25
0
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
Esempio n. 26
0
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
Esempio n. 27
0
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"))
Esempio n. 28
0
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
Esempio n. 29
0
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
Esempio n. 30
0
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
Esempio n. 31
0
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
Esempio n. 32
0
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
Esempio n. 33
0
    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)
Esempio n. 34
0
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
Esempio n. 35
0
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
Esempio n. 36
0
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
Esempio n. 37
0
    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)
Esempio n. 38
0
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
Esempio n. 39
0
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
Esempio n. 40
0
    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)
Esempio n. 41
0
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
Esempio n. 42
0
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
Esempio n. 43
0
 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)
     )
Esempio n. 44
0
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
Esempio n. 45
0
 def build_status(cls, current, title, callback=None):
     return DirectoryObject(
         key=callback,
         title=pad_title(_('%s - Status') % title),
         summary=cls.build_status_summary(current)
     )