Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start on changes for changeIndexer page. #9862

Merged
merged 17 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 241 additions & 6 deletions medusa/queues/show_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from medusa.logger.adapters.style import BraceAdapter
from medusa.name_cache import build_name_cache
from medusa.queues import generic_queue
from medusa.tv.series import SaveSeriesException, Series, SeriesIdentifier
from medusa.tv.series import ChangeIndexerException, SaveSeriesException, Series, SeriesIdentifier

from requests import RequestException

Expand All @@ -75,6 +75,7 @@ def __init__(self):
SUBTITLE = 6
REMOVE = 7
SEASON_UPDATE = 8
CHANGE = 9

names = {
REFRESH: 'Refresh',
Expand All @@ -84,6 +85,7 @@ def __init__(self):
SUBTITLE: 'Subtitle',
REMOVE: 'Remove Show',
SEASON_UPDATE: 'Season Update',
CHANGE: 'Change Indexer'
}


Expand All @@ -95,7 +97,9 @@ class ShowQueue(generic_queue.GenericQueue):
ShowQueueActions.SEASON_UPDATE: 'The information on this page is in the process of being updated.',
ShowQueueActions.REFRESH: 'The episodes below are currently being refreshed from disk',
ShowQueueActions.SUBTITLE: 'Currently downloading subtitles for this show',
ShowQueueActions.CHANGE: "This show is in the process of changing it's indexer",
}

queue_mappings = {
ShowQueueActions.REFRESH: 'This show is queued to be refreshed.',
ShowQueueActions.UPDATE: 'This show is queued and awaiting an update.',
Expand Down Expand Up @@ -237,6 +241,12 @@ def addShow(self, indexer, indexer_id, show_dir, **options):

return queue_item_obj

def changeIndexer(self, old_slug, new_slug):
queue_item_obj = QueueItemChangeIndexer(old_slug, new_slug)
self.add_item(queue_item_obj)

return queue_item_obj

def removeShow(self, show, full=False):
if show is None:
raise CantRemoveShowException('Failed removing show: Show does not exist')
Expand Down Expand Up @@ -300,12 +310,234 @@ def _isLoading(self):
isLoading = property(_isLoading)


class QueueItemAdd(ShowQueueItem):
def __init__(self, indexer, indexer_id, show_dir, **options):
class QueueItemChangeIndexer(ShowQueueItem):
"""Queue Item for changing a shows indexer to another."""

def __init__(self, old_slug, new_slug):
"""
Initialize QueueItemChangeIndexer with an old slug and new slug.

Old slug will be used as the currently added show. Which is used to get all show options.
New slug is the to be created show.
"""
self.old_slug = old_slug
self.new_slug = new_slug
self.show_dir = None
self.root_dir = None

self.options = {}
self.old_show = None
self.new_show = None

# this will initialize self.show to None
ShowQueueItem.__init__(self, ShowQueueActions.CHANGE, self.old_show)

# Process add show in priority
self.priority = generic_queue.QueuePriorities.HIGH

def _store_options(self):
self.options = {
'default_status': None,
'quality': {'preferred': self.old_show.qualities_preferred, 'allowed': self.old_show.qualities_allowed},
'season_folders': self.old_show.season_folders,
'lang': self.old_show.lang,
'subtitles': self.old_show.subtitles,
'anime': self.old_show.anime,
'scene': self.old_show.scene,
'paused': self.old_show.paused,
'blacklist': self.old_show.release_groups.blacklist if self.old_show.release_groups else None,
'whitelist': self.old_show.release_groups.whitelist if self.old_show.release_groups else None,
'default_status_after': self.old_show.default_ep_status,
'root_dir': None,
'show_lists': self.old_show.show_lists
}

self.show_dir = self.old_show._location

def run(self):
"""Run QueueItemChangeIndexer queue item."""
step = []

# Small helper, to reduce code for messaging
def message_step(new_step):
step.append(new_step)
ws.Message('QueueItemShow', dict(
step=step, **self.to_json
)).push()

ShowQueueItem.run(self)

def get_show_from_slug(slug):
identifier = SeriesIdentifier.from_slug(slug)
if not identifier:
raise ChangeIndexerException(f'Could not create identifier with slug {slug}')

show = Series.find_by_identifier(identifier)
return show

try:
# Create reference to old show, before starting the remove it.
self.old_show = get_show_from_slug(self.old_slug)

# Store needed options.
self._store_options()

# Start of removing the old show
log.info(
'{id}: Removing {show}',
{'id': self.old_show.series_id, 'show': self.old_show.name}
)
message_step(f'Removing old show {self.old_show.name}')

# Need to first remove the episodes from the Trakt collection, because we need the list of
# Episodes from the db to know which eps to remove.
if app.USE_TRAKT:
message_step('Removing episodes from trakt collection')
try:
app.trakt_checker_scheduler.action.remove_show_trakt_library(self.old_show)
except TraktException as error:
log.warning(
'{id}: Unable to delete show {show} from Trakt.'
' Please remove manually otherwise it will be added again.'
' Error: {error_msg}',
{'id': self.old_show.series_id, 'show': self.old_show.name, 'error_msg': error}
)
except Exception as error:
log.exception('Exception occurred while trying to delete show {show}, error: {error',
{'show': self.old_show.name, 'error': error})

self.old_show.delete_show(full=False)

# Send showRemoved to frontend, so we can remove it from localStorage.
ws.Message('showRemoved', self.old_show.to_json(detailed=False)).push() # Send ws update to client

# Double check to see if the show really has been removed, else bail.
if get_show_from_slug(self.old_slug):
raise ChangeIndexerException(f'Could not create identifier with slug {self.old_slug}')

# Start adding the new show
log.info(
'Starting to add show by {0}',
('show_dir: {0}'.format(self.show_dir)
if self.show_dir else
'New slug: {0}'.format(self.new_slug))
)

self.new_show = Series.from_identifier(SeriesIdentifier.from_slug(self.new_slug))

try:
# Push an update to any open Web UIs through the WebSocket
message_step('load show from {indexer}'.format(indexer=indexerApi(self.new_show.indexer).name))

api = self.new_show.identifier.get_indexer_api(self.options)

if getattr(api[self.new_show.series_id], 'seriesname', None) is None:
log.error(
'Show in {path} has no name on {indexer}, probably searched with the wrong language.',
{'path': self.show_dir, 'indexer': indexerApi(self.new_show.indexer).name}
)

ui.notifications.error(
'Unable to add show',
'Show in {path} has no name on {indexer}, probably the wrong language.'
' Delete .nfo and manually add the correct language.'.format(
path=self.show_dir, indexer=indexerApi(self.new_show.indexer).name)
)
self._finish_early()
raise SaveSeriesException('Indexer is missing a showname in this language: {0!r}')

self.new_show.load_from_indexer(tvapi=api)

message_step('load info from imdb')
self.new_show.load_imdb_info()
except IndexerException as error:
log.warning('Unable to load series from indexer: {0!r}'.format(error))
raise SaveSeriesException('Unable to load series from indexer: {0!r}'.format(error))

try:
message_step('configure show options')
self.new_show.configure(self)
except KeyError as error:
log.error(
'Unable to add show {series_name} due to an error with one of the provided options: {error}',
{'series_name': self.new_show.name, 'error': error}
)
ui.notifications.error(
'Unable to add show {series_name} due to an error with one of the provided options: {error}'.format(
series_name=self.new_show.name, error=error
)
)
raise SaveSeriesException(
'Unable to add show {series_name} due to an error with one of the provided options: {error}'.format(
series_name=self.new_show.name, error=error
))

except Exception as error:
log.error('Error trying to configure show: {0}', error)
log.debug(traceback.format_exc())
raise

app.showList.append(self.new_show)
self.new_show.save_to_db()

try:
message_step('load episodes from {indexer}'.format(indexer=indexerApi(self.new_show.indexer).name))
self.new_show.load_episodes_from_indexer(tvapi=api)
# If we provide a default_status_after through the apiv2 series route options object.
# set it after we've added the episodes.
self.new_show.default_ep_status = self.options['default_status_after'] or app.STATUS_DEFAULT_AFTER

except IndexerException as error:
log.warning('Unable to load series episodes from indexer: {0!r}'.format(error))
raise SaveSeriesException(
'Unable to load series episodes from indexer: {0!r}'.format(error)
)

# show_dir, default_status, quality, season_folders, lang, subtitles, anime,
# scene, paused, blacklist, whitelist, default_status_after, root_dir, show_lists):
message_step('create metadata in show folder')
self.new_show.write_metadata()
self.new_show.update_metadata()
self.new_show.populate_cache()
build_name_cache(self.new_show) # update internal name cache
self.new_show.flush_episodes()
self.new_show.sync_trakt()

message_step('add scene numbering')
self.new_show.add_scene_numbering()

if self.show_dir:
# If a show dir was passed, this was added as an existing show.
# For new shows we shouldn't have any files on disk.
message_step('refresh episodes from disk')
try:
app.show_queue_scheduler.action.refreshShow(self.new_show)
except CantRefreshShowException as error:
log.warning('Unable to rescan episodes from disk: {0!r}'.format(error))

except (ChangeIndexerException, SaveSeriesException) as error:
log.warning('Unable to add series: {0!r}'.format(error))
self.success = False
self._finish_early()
log.debug(traceback.format_exc())

default_status = self.options['default_status'] or app.STATUS_DEFAULT
if statusStrings[default_status] == 'Wanted':
message_step('trigger backlog search')
app.backlog_search_scheduler.action.search_backlog([self.new_show])

self.success = True

ws.Message('showAdded', self.new_show.to_json(detailed=False)).push() # Send ws update to client
message_step('finished')
self.finish()

def _finish_early(self):
if self.new_show is not None:
app.show_queue_scheduler.action.removeShow(self.new_show)
self.finish()


class QueueItemAdd(ShowQueueItem):
def __init__(self, indexer, indexer_id, show_dir, **options):
self.indexer = indexer
self.indexer_id = indexer_id
self.show_dir = ensure_text(show_dir) if show_dir else None
Expand Down Expand Up @@ -491,7 +723,10 @@ def _finish_early(self):


class QueueItemRefresh(ShowQueueItem):
"""QueueItemRefresh class."""

def __init__(self, show=None, force=False):
"""Queue item refresh constructor."""
ShowQueueItem.__init__(self, ShowQueueActions.REFRESH, show)

# do refreshes first because they're quick
Expand All @@ -501,7 +736,7 @@ def __init__(self, show=None, force=False):
self.force = force

def run(self):

"""Run QueueItemRefresh queue item."""
ShowQueueItem.run(self)

log.info(
Expand Down
2 changes: 1 addition & 1 deletion medusa/server/api/v2/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def post(self, series_slug=None, path_param=None):
'paused': data_options.get('paused'),
'blacklist': data_options['release'].get('blacklist', []) if data_options.get('release') else None,
'whitelist': data_options['release'].get('whitelist', []) if data_options.get('release') else None,
'default_status_after': data_options.get('statusAfter'),
'default_status_after': None,
'root_dir': data_options.get('rootDir'),
'show_lists': data_options.get('showLists')
}
Expand Down
35 changes: 35 additions & 0 deletions medusa/server/api/v2/series_change_indexer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# coding=utf-8
"""Request handler for series assets."""
from __future__ import unicode_literals

from medusa import app
from medusa.server.api.v2.base import BaseRequestHandler
from medusa.tv.series import Series, SeriesIdentifier

from tornado.escape import json_decode


class SeriesChangeIndexer(BaseRequestHandler):
"""Change shows indexer."""

#: resource name
name = 'changeindexer'
#: identifier
identifier = None
#: allowed HTTP methods
allowed_methods = ('POST', )

def post(self):
"""Change an existing show's indexer to another."""
data = json_decode(self.request.body)
old_slug = data.get('oldSlug')
new_slug = data.get('newSlug')

identifier = SeriesIdentifier.from_slug(old_slug)
series_obj = Series.find_by_identifier(identifier)
if not series_obj:
return self._not_found(f'Could not find a show to change indexer with slug {old_slug}')

queue_item_obj = app.show_queue_scheduler.action.changeIndexer(old_slug, new_slug)

return self._created(data=queue_item_obj.to_json)
4 changes: 4 additions & 0 deletions medusa/server/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from medusa.server.api.v2.search import SearchHandler
from medusa.server.api.v2.series import SeriesHandler
from medusa.server.api.v2.series_asset import SeriesAssetHandler
from medusa.server.api.v2.series_change_indexer import SeriesChangeIndexer
from medusa.server.api.v2.series_legacy import SeriesLegacyHandler
from medusa.server.api.v2.series_mass_edit import SeriesMassEdit
from medusa.server.api.v2.series_mass_operation import SeriesMassOperation
Expand Down Expand Up @@ -117,6 +118,9 @@ def get_apiv2_handlers(base):
SeriesMassEdit.create_app_handler(base),
# /api/v2/massupdate
SeriesMassOperation.create_app_handler(base),

# /api/v2/series/changeindexer
SeriesChangeIndexer.create_app_handler(base),
# /api/v2/series/tvdb1234/operation
SeriesOperationHandler.create_app_handler(base),
# /api/v2/series/tvdb1234/asset
Expand Down
8 changes: 8 additions & 0 deletions medusa/server/web/manage/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ def episodeStatuses(self, status=None):
"""
return PageTemplate(rh=self, filename='index.mako').render()

def changeIndexer(self):
"""
Render manage/changeIndexer page.

[Converted to VueRouter]
"""
return PageTemplate(rh=self, filename='index.mako').render()

def subtitleMissed(self, whichSubs=None):
"""
Serve manageEpisodeStatus page.
Expand Down
Loading