Ejemplo n.º 1
0
    def _patch_episode(episode, data):
        """Patch episode and save the changes to DB."""
        accepted = {}
        ignored = {}
        patches = {
            'status': IntegerField(episode, 'status'),
            'quality': IntegerField(episode, 'quality'),
            'watched': BooleanField(episode, 'watched'),
        }

        for key, value in iter_nested_items(data):
            patch_field = patches.get(key)
            if patch_field and patch_field.patch(episode, value):
                set_nested_value(accepted, key, value)
            else:
                set_nested_value(ignored, key, value)

        # Save patched attributes in db.
        episode.save_to_db()

        if ignored:
            log.warning(
                'Episode patch for {episode} ignored {items!r}',
                {
                    'episode': episode.identifier,
                    'items': ignored
                },
            )

        return accepted
Ejemplo n.º 2
0
    def patch(self, series_slug, path_param=None):
        """Patch series."""
        if not series_slug:
            return self._method_not_allowed('Patching multiple series is not allowed')

        identifier = SeriesIdentifier.from_slug(series_slug)
        if not identifier:
            return self._bad_request('Invalid series identifier')

        series = Series.find_by_identifier(identifier)
        if not series:
            return self._not_found('Series not found')

        data = json_decode(self.request.body)
        indexer_id = data.get('id', {}).get(identifier.indexer.slug)
        if indexer_id is not None and indexer_id != identifier.id:
            return self._bad_request('Conflicting series identifier')

        accepted = {}
        ignored = {}
        patches = {
            'config.aliases': ListField(series, 'aliases'),
            'config.defaultEpisodeStatus': StringField(series, 'default_ep_status_name'),
            'config.dvdOrder': BooleanField(series, 'dvd_order'),
            'config.seasonFolders': BooleanField(series, 'season_folders'),
            'config.anime': BooleanField(series, 'anime'),
            'config.scene': BooleanField(series, 'scene'),
            'config.sports': BooleanField(series, 'sports'),
            'config.paused': BooleanField(series, 'paused'),
            'config.location': StringField(series, '_location'),
            'config.airByDate': BooleanField(series, 'air_by_date'),
            'config.subtitlesEnabled': BooleanField(series, 'subtitles'),
            'config.release.requiredWords': ListField(series, 'release_required_words'),
            'config.release.ignoredWords': ListField(series, 'release_ignore_words'),
            'config.release.blacklist': ListField(series, 'blacklist'),
            'config.release.whitelist': ListField(series, 'whitelist'),
            'language': StringField(series, 'lang'),
            'config.qualities.allowed': ListField(series, 'qualities_allowed'),
            'config.qualities.preferred': ListField(series, 'qualities_preferred'),
            'config.qualities.combined': IntegerField(series, 'quality'),
        }

        for key, value in iter_nested_items(data):
            patch_field = patches.get(key)
            if patch_field and patch_field.patch(series, value):
                set_nested_value(accepted, key, value)
            else:
                set_nested_value(ignored, key, value)

        # Save patched attributes in db.
        series.save_to_db()

        if ignored:
            log.warning('Series patch ignored {items!r}', {'items': ignored})

        self._ok(data=accepted)
Ejemplo n.º 3
0
class ConfigHandler(BaseRequestHandler):
    """Config request handler."""

    #: resource name
    name = 'config'
    #: identifier
    identifier = ('identifier', r'\w+')
    #: path param
    path_param = ('path_param', r'\w+')
    #: allowed HTTP methods
    allowed_methods = (
        'GET',
        'PATCH',
    )
    #: patch mapping
    patches = {
        'anonRedirect':
        StringField(app, 'ANON_REDIRECT'),
        'emby.enabled':
        BooleanField(app, 'USE_EMBY'),
        'torrents.authType':
        StringField(app, 'TORRENT_AUTH_TYPE'),
        'torrents.dir':
        StringField(app, 'TORRENT_DIR'),
        'torrents.enabled':
        BooleanField(app, 'USE_TORRENTS'),
        'torrents.highBandwidth':
        StringField(app, 'TORRENT_HIGH_BANDWIDTH'),
        'torrents.host':
        StringField(app, 'TORRENT_HOST'),
        'torrents.label':
        StringField(app, 'TORRENT_LABEL'),
        'torrents.labelAnime':
        StringField(app, 'TORRENT_LABEL_ANIME'),
        'torrents.method':
        StringField(app, 'TORRENT_METHOD'),
        'torrents.password':
        StringField(app, 'TORRENT_PASSWORD'),
        'torrents.path':
        BooleanField(app, 'TORRENT_PATH'),
        'torrents.paused':
        BooleanField(app, 'TORRENT_PAUSED'),
        'torrents.rpcurl':
        StringField(app, 'TORRENT_RPCURL'),
        'torrents.seedLocation':
        StringField(app, 'TORRENT_SEED_LOCATION'),
        'torrents.seedTime':
        StringField(app, 'TORRENT_SEED_TIME'),
        'torrents.username':
        StringField(app, 'TORRENT_USERNAME'),
        'torrents.verifySSL':
        BooleanField(app, 'TORRENT_VERIFY_CERT'),
        'nzb.enabled':
        BooleanField(app, 'USE_NZBS'),
        'nzb.dir':
        StringField(app, 'NZB_DIR'),
        'nzb.method':
        StringField(app, 'NZB_METHOD'),
        'nzb.nzbget.category':
        StringField(app, 'NZBGET_CATEGORY'),
        'nzb.nzbget.categoryAnime':
        StringField(app, 'NZBGET_CATEGORY_ANIME'),
        'nzb.nzbget.categoryAnimeBacklog':
        StringField(app, 'NZBGET_CATEGORY_ANIME_BACKLOG'),
        'nzb.nzbget.categoryBacklog':
        StringField(app, 'NZBGET_CATEGORY_BACKLOG'),
        'nzb.nzbget.host':
        StringField(app, 'NZBGET_HOST'),
        'nzb.nzbget.password':
        StringField(app, 'NZBGET_PASSWORD'),
        'nzb.nzbget.priority':
        StringField(app, 'NZBGET_PRIORITY'),
        'nzb.nzbget.useHttps':
        BooleanField(app, 'NZBGET_USE_HTTPS'),
        'nzb.nzbget.username':
        StringField(app, 'NZBGET_USERNAME'),
        'nzb.sabnzbd.apiKey':
        StringField(app, 'SAB_APIKEY'),
        'nzb.sabnzbd.category':
        StringField(app, 'SAB_CATEGORY'),
        'nzb.sabnzbd.categoryAnime':
        StringField(app, 'SAB_CATEGORY_ANIME'),
        'nzb.sabnzbd.categoryAnimeBacklog':
        StringField(app, 'SAB_CATEGORY_ANIME_BACKLOG'),
        'nzb.sabnzbd.categoryBacklog':
        StringField(app, 'SAB_CATEGORY_BACKLOG'),
        'nzb.sabnzbd.forced':
        BooleanField(app, 'SAB_FORCED'),
        'nzb.sabnzbd.host':
        StringField(app, 'SAB_HOST'),
        'nzb.sabnzbd.password':
        StringField(app, 'SAB_PASSWORD'),
        'nzb.sabnzbd.username':
        StringField(app, 'SAB_USERNAME'),
        'selectedRootIndex':
        IntegerField(app, 'SELECTED_ROOT'),
        'layout.schedule':
        EnumField(app,
                  'COMING_EPS_LAYOUT',
                  ('poster', 'banner', 'list', 'calendar'),
                  default_value='banner',
                  post_processor=layout_schedule_post_processor),
        'layout.history':
        EnumField(app,
                  'HISTORY_LAYOUT', ('compact', 'detailed'),
                  default_value='detailed'),
        'layout.home':
        EnumField(app,
                  'HOME_LAYOUT',
                  ('poster', 'small', 'banner', 'simple', 'coverflow'),
                  default_value='poster'),
        'layout.show.allSeasons':
        BooleanField(app, 'DISPLAY_ALL_SEASONS'),
        'layout.show.specials':
        BooleanField(app, 'DISPLAY_SHOW_SPECIALS'),
        'layout.show.showListOrder':
        ListField(app, 'SHOW_LIST_ORDER'),
        'theme.name':
        StringField(app, 'THEME_NAME', setter=theme_name_setter),
        'backlogOverview.period':
        StringField(app, 'BACKLOG_PERIOD'),
        'backlogOverview.status':
        StringField(app, 'BACKLOG_STATUS'),
        'rootDirs':
        ListField(app, 'ROOT_DIRS'),
        'showDefaults.status':
        EnumField(app, 'STATUS_DEFAULT', (SKIPPED, WANTED, IGNORED), int),
        'showDefaults.statusAfter':
        EnumField(app, 'STATUS_DEFAULT_AFTER', (SKIPPED, WANTED, IGNORED),
                  int),
        'showDefaults.quality':
        IntegerField(app,
                     'QUALITY_DEFAULT',
                     validator=Quality.is_valid_combined_quality),
        'showDefaults.subtitles':
        BooleanField(app,
                     'SUBTITLES_DEFAULT',
                     validator=lambda v: app.USE_SUBTITLES,
                     converter=bool),
        'showDefaults.seasonFolders':
        BooleanField(app,
                     'SEASON_FOLDERS_DEFAULT',
                     validator=season_folders_validator,
                     converter=bool),
        'showDefaults.anime':
        BooleanField(app, 'ANIME_DEFAULT', converter=bool),
        'showDefaults.scene':
        BooleanField(app, 'SCENE_DEFAULT', converter=bool),
        'postProcessing.showDownloadDir':
        StringField(app, 'TV_DOWNLOAD_DIR'),
        'postProcessing.processAutomatically':
        BooleanField(app, 'PROCESS_AUTOMATICALLY'),
        'postProcessing.processMethod':
        StringField(app, 'PROCESS_METHOD'),
        'postProcessing.deleteRarContent':
        BooleanField(app, 'DELRARCONTENTS'),
        'postProcessing.unpack':
        BooleanField(app, 'UNPACK'),
        'postProcessing.noDelete':
        BooleanField(app, 'NO_DELETE'),
        'postProcessing.postponeIfSyncFiles':
        BooleanField(app, 'POSTPONE_IF_SYNC_FILES'),
        'postProcessing.autoPostprocessorFrequency':
        IntegerField(app, 'AUTOPOSTPROCESSOR_FREQUENCY'),
        'postProcessing.airdateEpisodes':
        BooleanField(app, 'AIRDATE_EPISODES'),
        'postProcessing.moveAssociatedFiles':
        BooleanField(app, 'MOVE_ASSOCIATED_FILES'),
        'postProcessing.allowedExtensions':
        ListField(app, 'ALLOWED_EXTENSIONS'),
        'postProcessing.addShowsWithoutDir':
        BooleanField(app, 'ADD_SHOWS_WO_DIR'),
        'postProcessing.createMissingShowDirs':
        BooleanField(app, 'CREATE_MISSING_SHOW_DIRS'),
        'postProcessing.renameEpisodes':
        BooleanField(app, 'RENAME_EPISODES'),
        'postProcessing.postponeIfNoSubs':
        BooleanField(app, 'POSTPONE_IF_NO_SUBS'),
        'postProcessing.nfoRename':
        BooleanField(app, 'NFO_RENAME'),
        'postProcessing.syncFiles':
        ListField(app, 'SYNC_FILES'),
        'postProcessing.fileTimestampTimezone':
        StringField(app, 'FILE_TIMESTAMP_TIMEZONE'),
        'postProcessing.extraScripts':
        ListField(app, 'EXTRA_SCRIPTS'),
        'postProcessing.extraScriptsUrl':
        StringField(app, 'EXTRA_SCRIPTS_URL'),
        'postProcessing.naming.pattern':
        StringField(app, 'NAMING_PATTERN'),
        'postProcessing.naming.enableCustomNamingAnime':
        BooleanField(app, 'NAMING_CUSTOM_ANIME'),
        'postProcessing.naming.enableCustomNamingSports':
        BooleanField(app, 'NAMING_CUSTOM_SPORTS'),
        'postProcessing.naming.enableCustomNamingAirByDate':
        BooleanField(app, 'NAMING_CUSTOM_ABD'),
        'postProcessing.naming.patternSports':
        StringField(app, 'NAMING_SPORTS_PATTERN'),
        'postProcessing.naming.patternAirByDate':
        StringField(app, 'NAMING_ABD_PATTERN'),
        'postProcessing.naming.patternAnime':
        StringField(app, 'NAMING_ANIME_PATTERN'),
        'postProcessing.naming.animeMultiEp':
        IntegerField(app, 'NAMING_ANIME_MULTI_EP'),
        'postProcessing.naming.animeNamingType':
        IntegerField(app, 'NAMING_ANIME'),
        'postProcessing.naming.multiEp':
        IntegerField(app, 'NAMING_MULTI_EP'),
        'postProcessing.naming.stripYear':
        BooleanField(app, 'NAMING_STRIP_YEAR'),
        'search.general.randomizeProviders':
        BooleanField(app, 'RANDOMIZE_PROVIDERS'),
        'search.general.downloadPropers':
        BooleanField(app, 'DOWNLOAD_PROPERS'),
        'search.general.checkPropersInterval':
        StringField(app, 'CHECK_PROPERS_INTERVAL'),
        # 'search.general.propersIntervalLabels': IntegerField(app, 'PROPERS_INTERVAL_LABELS'),
        'search.general.propersSearchDays':
        IntegerField(app, 'PROPERS_SEARCH_DAYS'),
        'search.general.backlogDays':
        IntegerField(app, 'BACKLOG_DAYS'),
        'search.general.backlogFrequency':
        IntegerField(app, 'BACKLOG_FREQUENCY'),
        'search.general.minBacklogFrequency':
        IntegerField(app, 'MIN_BACKLOG_FREQUENCY'),
        'search.general.dailySearchFrequency':
        IntegerField(app, 'DAILYSEARCH_FREQUENCY'),
        'search.general.minDailySearchFrequency':
        IntegerField(app, 'MIN_DAILYSEARCH_FREQUENCY'),
        'search.general.removeFromClient':
        BooleanField(app, 'REMOVE_FROM_CLIENT'),
        'search.general.torrentCheckerFrequency':
        IntegerField(app, 'TORRENT_CHECKER_FREQUENCY'),
        'search.general.minTorrentCheckerFrequency':
        IntegerField(app, 'MIN_TORRENT_CHECKER_FREQUENCY'),
        'search.general.usenetRetention':
        IntegerField(app, 'USENET_RETENTION'),
        'search.general.trackersList':
        ListField(app, 'TRACKERS_LIST'),
        'search.general.allowHighPriority':
        BooleanField(app, 'ALLOW_HIGH_PRIORITY'),
        'search.general.useFailedDownloads':
        BooleanField(app, 'USE_FAILED_DOWNLOADS'),
        'search.general.deleteFailed':
        BooleanField(app, 'DELETE_FAILED'),
        'search.general.cacheTrimming':
        BooleanField(app, 'CACHE_TRIMMING'),
        'search.general.maxCacheAge':
        IntegerField(app, 'MAX_CACHE_AGE'),
        'search.filters.ignored':
        ListField(app, 'IGNORE_WORDS'),
        'search.filters.undesired':
        ListField(app, 'UNDESIRED_WORDS'),
        'search.filters.preferred':
        ListField(app, 'PREFERRED_WORDS'),
        'search.filters.required':
        ListField(app, 'REQUIRE_WORDS'),
        'search.filters.ignoredSubsList':
        ListField(app, 'IGNORED_SUBS_LIST'),
        'search.filters.ignoreUnknownSubs':
        BooleanField(app, 'IGNORE_UND_SUBS'),
    }

    def http_get(self, identifier, path_param=None):
        """Query general configuration.

        :param identifier:
        :param path_param:
        :type path_param: str
        """
        config_sections = DataGenerator.sections()

        if identifier and identifier not in config_sections:
            return self._not_found('Config not found')

        if not identifier:
            config_data = NonEmptyDict()

            for section in config_sections:
                config_data[section] = DataGenerator.get_data(section)

            return self._ok(data=config_data)

        config_data = DataGenerator.get_data(identifier)

        if path_param:
            if path_param not in config_data:
                return self._bad_request(
                    '{key} is a invalid path'.format(key=path_param))

            config_data = config_data[path_param]

        return self._ok(data=config_data)

    def http_patch(self, identifier, *args, **kwargs):
        """Patch general configuration."""
        if not identifier:
            return self._bad_request('Config identifier not specified')

        if identifier != 'main':
            return self._not_found('Config not found')

        data = json_decode(self.request.body)
        accepted = {}
        ignored = {}

        # Remove the metadata providers from the nested items.
        # It's ugly but I don't see a better solution for it right now.
        if data.get('metadata'):
            metadata_providers = data['metadata'].pop('metadataProviders')

            if metadata_providers:
                patch_metadata_providers = MetadataStructureField(
                    app, 'metadata_provider_dict')
                if patch_metadata_providers and patch_metadata_providers.patch(
                        app, metadata_providers):
                    set_nested_value(accepted, 'metadata.metadataProviders',
                                     metadata_providers)
                else:
                    set_nested_value(ignored, 'metadata.metadataProviders',
                                     metadata_providers)

        for key, value in iter_nested_items(data):
            patch_field = self.patches.get(key)
            if patch_field and patch_field.patch(app, value):
                set_nested_value(accepted, key, value)
            else:
                set_nested_value(ignored, key, value)

        if ignored:
            log.warning('Config patch ignored {items!r}', {'items': ignored})

        # Make sure to update the config file after everything is updated
        app.instance.save_config()

        # Push an update to any open Web UIs through the WebSocket
        msg = ws.Message('configUpdated', {
            'section': identifier,
            'config': DataGenerator.get_data(identifier)
        })
        msg.push()

        return self._ok(data=accepted)
Ejemplo n.º 4
0
class ConfigHandler(BaseRequestHandler):
    """Config request handler."""

    #: resource name
    name = 'config'
    #: identifier
    identifier = ('identifier', r'\w+')
    #: path param
    path_param = ('path_param', r'\w+')
    #: allowed HTTP methods
    allowed_methods = (
        'GET',
        'PATCH',
    )
    #: patch mapping
    patches = {
        'anonRedirect':
        StringField(app, 'ANON_REDIRECT'),
        'emby.enabled':
        BooleanField(app, 'USE_EMBY'),
        'torrents.enabled':
        BooleanField(app, 'USE_TORRENTS'),
        'torrents.username':
        StringField(app, 'TORRENT_USERNAME'),
        'torrents.password':
        StringField(app, 'TORRENT_PASSWORD'),
        'torrents.label':
        StringField(app, 'TORRENT_LABEL'),
        'torrents.labelAnime':
        StringField(app, 'TORRENT_LABEL_ANIME'),
        'torrents.verifySSL':
        BooleanField(app, 'TORRENT_VERIFY_CERT'),
        'torrents.path':
        BooleanField(app, 'TORRENT_PATH'),
        'selectedRootIndex':
        IntegerField(app, 'SELECTED_ROOT'),
        'layout.schedule':
        EnumField(app,
                  'COMING_EPS_LAYOUT',
                  ('poster', 'banner', 'list', 'calendar'),
                  default_value='banner',
                  post_processor=layout_schedule_post_processor),
        'layout.history':
        EnumField(app,
                  'HISTORY_LAYOUT', ('compact', 'detailed'),
                  default_value='detailed'),
        'layout.home':
        EnumField(app,
                  'HOME_LAYOUT',
                  ('poster', 'small', 'banner', 'simple', 'coverflow'),
                  default_value='poster'),
        'layout.show.allSeasons':
        BooleanField(app, 'DISPLAY_ALL_SEASONS'),
        'layout.show.specials':
        BooleanField(app, 'DISPLAY_SHOW_SPECIALS'),
        'theme.name':
        StringField(app, 'THEME_NAME'),
        'backlogOverview.period':
        StringField(app, 'BACKLOG_PERIOD'),
        'backlogOverview.status':
        StringField(app, 'BACKLOG_STATUS'),
    }

    def get(self, identifier, path_param=None):
        """Query general configuration.

        :param identifier:
        :param path_param:
        :type path_param: str
        """
        if identifier and identifier != 'main':
            return self._not_found('Config not found')

        config_data = NonEmptyDict()
        config_data['anonRedirect'] = app.ANON_REDIRECT
        config_data['animeSplitHome'] = app.ANIME_SPLIT_HOME
        config_data['comingEpsSort'] = app.COMING_EPS_SORT
        config_data['datePreset'] = app.DATE_PRESET
        config_data['fuzzyDating'] = app.FUZZY_DATING
        config_data['themeName'] = app.THEME_NAME
        config_data['posterSortby'] = app.POSTER_SORTBY
        config_data['posterSortdir'] = app.POSTER_SORTDIR
        config_data['rootDirs'] = app.ROOT_DIRS
        config_data['sortArticle'] = app.SORT_ARTICLE
        config_data['timePreset'] = app.TIME_PRESET
        config_data['trimZero'] = app.TRIM_ZERO
        config_data['fanartBackground'] = app.FANART_BACKGROUND
        config_data['fanartBackgroundOpacity'] = float(
            app.FANART_BACKGROUND_OPACITY or 0)
        config_data['branch'] = app.BRANCH
        config_data['commitHash'] = app.CUR_COMMIT_HASH
        config_data['release'] = app.APP_VERSION
        config_data['sslVersion'] = app.OPENSSL_VERSION
        config_data['pythonVersion'] = sys.version
        config_data['databaseVersion'] = NonEmptyDict()
        config_data['databaseVersion']['major'] = app.MAJOR_DB_VERSION
        config_data['databaseVersion']['minor'] = app.MINOR_DB_VERSION
        config_data['os'] = platform.platform()
        config_data['locale'] = '.'.join(
            [text_type(loc or 'Unknown') for loc in app.LOCALE])
        config_data['localUser'] = app.OS_USER or 'Unknown'
        config_data['programDir'] = app.PROG_DIR
        config_data['configFile'] = app.CONFIG_FILE
        config_data['dbFilename'] = db.dbFilename()
        config_data['cacheDir'] = app.CACHE_DIR
        config_data['logDir'] = app.LOG_DIR
        config_data['appArgs'] = app.MY_ARGS
        config_data['webRoot'] = app.WEB_ROOT
        config_data['githubUrl'] = app.GITHUB_IO_URL
        config_data['wikiUrl'] = app.WIKI_URL
        config_data['sourceUrl'] = app.APPLICATION_URL
        config_data['downloadUrl'] = app.DOWNLOAD_URL
        config_data['subtitlesMulti'] = app.SUBTITLES_MULTI
        config_data['namingForceFolders'] = app.NAMING_FORCE_FOLDERS
        config_data['subtitles'] = NonEmptyDict()
        config_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES)
        config_data['kodi'] = NonEmptyDict()
        config_data['kodi']['enabled'] = bool(app.USE_KODI
                                              and app.KODI_UPDATE_LIBRARY)
        config_data['plex'] = NonEmptyDict()
        config_data['plex']['server'] = NonEmptyDict()
        config_data['plex']['server']['enabled'] = bool(app.USE_PLEX_SERVER)
        config_data['plex']['server']['notify'] = NonEmptyDict()
        config_data['plex']['server']['notify']['snatch'] = bool(
            app.PLEX_NOTIFY_ONSNATCH)
        config_data['plex']['server']['notify']['download'] = bool(
            app.PLEX_NOTIFY_ONDOWNLOAD)
        config_data['plex']['server']['notify']['subtitleDownload'] = bool(
            app.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD)

        config_data['plex']['server']['updateLibrary'] = bool(
            app.PLEX_UPDATE_LIBRARY)
        config_data['plex']['server']['host'] = app.PLEX_SERVER_HOST
        config_data['plex']['server']['token'] = app.PLEX_SERVER_TOKEN
        config_data['plex']['server']['username'] = app.PLEX_SERVER_USERNAME
        config_data['plex']['server']['password'] = app.PLEX_SERVER_PASSWORD
        config_data['plex']['client'] = NonEmptyDict()
        config_data['plex']['client']['enabled'] = bool(app.USE_PLEX_CLIENT)
        config_data['plex']['client']['username'] = app.PLEX_CLIENT_USERNAME
        config_data['plex']['client']['password'] = app.PLEX_CLIENT_PASSWORD
        config_data['plex']['client']['host'] = app.PLEX_CLIENT_HOST
        config_data['emby'] = NonEmptyDict()
        config_data['emby']['enabled'] = bool(app.USE_EMBY)
        config_data['torrents'] = NonEmptyDict()
        config_data['torrents']['enabled'] = bool(app.USE_TORRENTS)
        config_data['torrents']['method'] = app.TORRENT_METHOD
        config_data['torrents']['username'] = app.TORRENT_USERNAME
        config_data['torrents']['password'] = app.TORRENT_PASSWORD
        config_data['torrents']['label'] = app.TORRENT_LABEL
        config_data['torrents']['labelAnime'] = app.TORRENT_LABEL_ANIME
        config_data['torrents']['verifySSL'] = app.TORRENT_VERIFY_CERT
        config_data['torrents']['path'] = app.TORRENT_PATH
        config_data['torrents']['seedTime'] = app.TORRENT_SEED_TIME
        config_data['torrents']['paused'] = app.TORRENT_PAUSED
        config_data['torrents']['highBandwidth'] = app.TORRENT_HIGH_BANDWIDTH
        config_data['torrents']['host'] = app.TORRENT_HOST
        config_data['torrents']['rpcurl'] = app.TORRENT_RPCURL
        config_data['torrents']['authType'] = app.TORRENT_AUTH_TYPE
        config_data['nzb'] = NonEmptyDict()
        config_data['nzb']['enabled'] = bool(app.USE_NZBS)
        config_data['nzb']['username'] = app.NZBGET_USERNAME
        config_data['nzb']['password'] = app.NZBGET_PASSWORD
        # app.NZBGET_CATEGORY
        # app.NZBGET_CATEGORY_BACKLOG
        # app.NZBGET_CATEGORY_ANIME
        # app.NZBGET_CATEGORY_ANIME_BACKLOG
        config_data['nzb']['host'] = app.NZBGET_HOST
        config_data['nzb']['priority'] = app.NZBGET_PRIORITY
        config_data['layout'] = NonEmptyDict()
        config_data['layout']['schedule'] = app.COMING_EPS_LAYOUT
        config_data['layout']['history'] = app.HISTORY_LAYOUT
        config_data['layout']['home'] = app.HOME_LAYOUT
        config_data['layout']['show'] = NonEmptyDict()
        config_data['layout']['show']['allSeasons'] = bool(
            app.DISPLAY_ALL_SEASONS)
        config_data['layout']['show']['specials'] = bool(
            app.DISPLAY_SHOW_SPECIALS)
        config_data['selectedRootIndex'] = int(
            app.SELECTED_ROOT
        ) if app.SELECTED_ROOT is not None else -1  # All paths
        config_data['backlogOverview'] = NonEmptyDict()
        config_data['backlogOverview']['period'] = app.BACKLOG_PERIOD
        config_data['backlogOverview']['status'] = app.BACKLOG_STATUS

        if not identifier:
            return self._paginate([config_data])

        if path_param:
            if path_param not in config_data:
                return self._bad_request(
                    '{key} is a invalid path'.format(key=path_param))

            config_data = config_data[path_param]

        return self._ok(data=config_data)

    def patch(self, identifier, *args, **kwargs):
        """Patch general configuration."""
        if not identifier:
            return self._bad_request('Config identifier not specified')

        if identifier != 'main':
            return self._not_found('Config not found')

        data = json_decode(self.request.body)
        accepted = {}
        ignored = {}

        for key, value in iter_nested_items(data):
            patch_field = self.patches.get(key)
            if patch_field and patch_field.patch(app, value):
                set_nested_value(accepted, key, value)
            else:
                set_nested_value(ignored, key, value)

        if ignored:
            log.warning('Config patch ignored %r', ignored)

        # Make sure to update the config file after everything is updated
        app.instance.save_config()
        self._ok(data=accepted)
Ejemplo n.º 5
0
class ConfigHandler(BaseRequestHandler):
    """Config request handler."""

    #: resource name
    name = 'config'
    #: identifier
    identifier = ('identifier', r'\w+')
    #: path param
    path_param = ('path_param', r'\w+')
    #: allowed HTTP methods
    allowed_methods = (
        'GET',
        'PATCH',
    )
    #: patch mapping
    patches = {
        'anonRedirect':
        StringField(app, 'ANON_REDIRECT'),
        'emby.enabled':
        BooleanField(app, 'USE_EMBY'),
        'torrents.authType':
        StringField(app, 'TORRENT_AUTH_TYPE'),
        'torrents.dir':
        StringField(app, 'TORRENT_DIR'),
        'torrents.enabled':
        BooleanField(app, 'USE_TORRENTS'),
        'torrents.highBandwidth':
        StringField(app, 'TORRENT_HIGH_BANDWIDTH'),
        'torrents.host':
        StringField(app, 'TORRENT_HOST'),
        'torrents.label':
        StringField(app, 'TORRENT_LABEL'),
        'torrents.labelAnime':
        StringField(app, 'TORRENT_LABEL_ANIME'),
        'torrents.method':
        StringField(app, 'TORRENT_METHOD'),
        'torrents.password':
        StringField(app, 'TORRENT_PASSWORD'),
        'torrents.path':
        BooleanField(app, 'TORRENT_PATH'),
        'torrents.paused':
        BooleanField(app, 'TORRENT_PAUSED'),
        'torrents.rpcurl':
        StringField(app, 'TORRENT_RPCURL'),
        'torrents.seedLocation':
        StringField(app, 'TORRENT_SEED_LOCATION'),
        'torrents.seedTime':
        StringField(app, 'TORRENT_SEED_TIME'),
        'torrents.username':
        StringField(app, 'TORRENT_USERNAME'),
        'torrents.verifySSL':
        BooleanField(app, 'TORRENT_VERIFY_CERT'),
        'nzb.enabled':
        BooleanField(app, 'USE_NZBS'),
        'nzb.dir':
        StringField(app, 'NZB_DIR'),
        'nzb.method':
        StringField(app, 'NZB_METHOD'),
        'nzb.nzbget.category':
        StringField(app, 'NZBGET_CATEGORY'),
        'nzb.nzbget.categoryAnime':
        StringField(app, 'NZBGET_CATEGORY_ANIME'),
        'nzb.nzbget.categoryAnimeBacklog':
        StringField(app, 'NZBGET_CATEGORY_ANIME_BACKLOG'),
        'nzb.nzbget.categoryBacklog':
        StringField(app, 'NZBGET_CATEGORY_BACKLOG'),
        'nzb.nzbget.host':
        StringField(app, 'NZBGET_HOST'),
        'nzb.nzbget.password':
        StringField(app, 'NZBGET_PASSWORD'),
        'nzb.nzbget.priority':
        StringField(app, 'NZBGET_PRIORITY'),
        'nzb.nzbget.useHttps':
        BooleanField(app, 'NZBGET_USE_HTTPS'),
        'nzb.nzbget.username':
        StringField(app, 'NZBGET_USERNAME'),
        'nzb.sabnzbd.apiKey':
        StringField(app, 'SAB_APIKEY'),
        'nzb.sabnzbd.category':
        StringField(app, 'SAB_CATEGORY'),
        'nzb.sabnzbd.categoryAnime':
        StringField(app, 'SAB_CATEGORY_ANIME'),
        'nzb.sabnzbd.categoryAnimeBacklog':
        StringField(app, 'SAB_CATEGORY_ANIME_BACKLOG'),
        'nzb.sabnzbd.categoryBacklog':
        StringField(app, 'SAB_CATEGORY_BACKLOG'),
        'nzb.sabnzbd.forced':
        BooleanField(app, 'SAB_FORCED'),
        'nzb.sabnzbd.host':
        StringField(app, 'SAB_HOST'),
        'nzb.sabnzbd.password':
        StringField(app, 'SAB_PASSWORD'),
        'nzb.sabnzbd.username':
        StringField(app, 'SAB_USERNAME'),
        'selectedRootIndex':
        IntegerField(app, 'SELECTED_ROOT'),
        'layout.schedule':
        EnumField(app,
                  'COMING_EPS_LAYOUT',
                  ('poster', 'banner', 'list', 'calendar'),
                  default_value='banner',
                  post_processor=layout_schedule_post_processor),
        'layout.history':
        EnumField(app,
                  'HISTORY_LAYOUT', ('compact', 'detailed'),
                  default_value='detailed'),
        'layout.home':
        EnumField(app,
                  'HOME_LAYOUT',
                  ('poster', 'small', 'banner', 'simple', 'coverflow'),
                  default_value='poster'),
        'layout.show.allSeasons':
        BooleanField(app, 'DISPLAY_ALL_SEASONS'),
        'layout.show.specials':
        BooleanField(app, 'DISPLAY_SHOW_SPECIALS'),
        'layout.show.showListOrder':
        ListField(app, 'SHOW_LIST_ORDER'),
        'theme.name':
        StringField(app, 'THEME_NAME', setter=theme_name_setter),
        'backlogOverview.period':
        StringField(app, 'BACKLOG_PERIOD'),
        'backlogOverview.status':
        StringField(app, 'BACKLOG_STATUS'),
        'rootDirs':
        ListField(app, 'ROOT_DIRS'),
    }

    def get(self, identifier, path_param=None):
        """Query general configuration.

        :param identifier:
        :param path_param:
        :type path_param: str
        """
        config_sections = DataGenerator.sections()

        if identifier and identifier not in config_sections:
            return self._not_found('Config not found')

        if not identifier:
            config_data = NonEmptyDict()

            for section in config_sections:
                config_data[section] = DataGenerator.get_data(section)

            return self._ok(data=config_data)

        config_data = DataGenerator.get_data(identifier)

        if path_param:
            if path_param not in config_data:
                return self._bad_request(
                    '{key} is a invalid path'.format(key=path_param))

            config_data = config_data[path_param]

        return self._ok(data=config_data)

    def patch(self, identifier, *args, **kwargs):
        """Patch general configuration."""
        if not identifier:
            return self._bad_request('Config identifier not specified')

        if identifier != 'main':
            return self._not_found('Config not found')

        data = json_decode(self.request.body)
        accepted = {}
        ignored = {}

        for key, value in iter_nested_items(data):
            patch_field = self.patches.get(key)
            if patch_field and patch_field.patch(app, value):
                set_nested_value(accepted, key, value)
            else:
                set_nested_value(ignored, key, value)

        if ignored:
            log.warning('Config patch ignored {items!r}', {'items': ignored})

        # Make sure to update the config file after everything is updated
        app.instance.save_config()

        # Push an update to any open Web UIs through the WebSocket
        msg = ws.Message('configUpdated', {
            'section': identifier,
            'config': DataGenerator.get_data(identifier)
        })
        msg.push()

        self._ok(data=accepted)