From 07bdc1c76a0916dd6f4ba86b463646dae9e1ad99 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Wed, 28 Nov 2018 13:18:57 +0100 Subject: [PATCH 1/4] Fixed changing location for show. --- medusa/server/api/v2/series.py | 2 +- medusa/tv/series.py | 71 +++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/medusa/server/api/v2/series.py b/medusa/server/api/v2/series.py index 7e3ae4ef6a..011f8ff2ff 100644 --- a/medusa/server/api/v2/series.py +++ b/medusa/server/api/v2/series.py @@ -130,7 +130,7 @@ def http_patch(self, series_slug, path_param=None): 'config.scene': BooleanField(series, 'scene'), 'config.sports': BooleanField(series, 'sports'), 'config.paused': BooleanField(series, 'paused'), - 'config.location': StringField(series, '_location'), + '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'), diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 7e992d942b..6c67afc5b4 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -55,6 +55,7 @@ ) from medusa.helper.exceptions import ( AnidbAdbaConnectionException, + CantRefreshShowException, CantRemoveShowException, EpisodeDeletedException, EpisodeNotFoundException, @@ -370,36 +371,68 @@ def raw_location(self): @property def location(self): """Get the show location.""" - # no directory check needed if missing - # show directories are created during post-processing - if app.CREATE_MISSING_SHOW_DIRS or self.is_location_valid(): - return self._location - raise ShowDirectoryNotFoundException(u'Show folder does not exist.') - - @property - def indexer_name(self): - """Return the indexer name identifier. Example: tvdb.""" - return indexerConfig[self.indexer].get('identifier') - - @property - def indexer_slug(self): - """Return the slug name of the series. Example: tvdb1234.""" - return indexer_id_to_slug(self.indexer, self.series_id) + return self._location @location.setter def location(self, value): + old_location = os.path.normpath(self._location) + new_location = os.path.normpath(value) + log.debug( u'{indexer} {id}: Setting location: {location}', { 'indexer': indexerApi(self.indexer).name, 'id': self.series_id, - 'location': value, + 'location': new_location, } ) + + if new_location == old_location: + return + # Don't validate directory if user wants to add shows without creating a dir if app.ADD_SHOWS_WO_DIR or self.is_location_valid(value): - self._location = value - else: - raise ShowDirectoryNotFoundException(u'Invalid show folder!') + self._location = new_location + return + + changed_location = True + log.info('Changing show location to: {new}', {'new': new_location}) + if not os.path.isdir(new_location): + if app.CREATE_MISSING_SHOW_DIRS: + log.info(u"Show directory doesn't exist, creating it") + try: + os.mkdir(new_location) + except OSError as error: + changed_location = False + log.warning(u"Unable to create the show directory '{location}'. Error: {msg}", + {'location': new_location, 'msg': error}) + else: + log.info(u'New show directory created') + helpers.chmod_as_parent(new_location) + else: + changed_location = False + log.warning("New location '{location}' does not exist. " + "Enable setting '(Config - Postprocessing) Create missing show dirs'", {'location': new_location}) + + # Save new location only if we changed it + if changed_location: + self._location = new_location + + if changed_location and os.path.isdir(new_location): + try: + app.show_queue_scheduler.action.refreshShow(self) + except CantRefreshShowException as error: + log.warning("Unable to refresh show '{show}'. Error: {error}", + {'show': self.name, 'error': error.message}) + + @property + def indexer_name(self): + """Return the indexer name identifier. Example: tvdb.""" + return indexerConfig[self.indexer].get('identifier') + + @property + def indexer_slug(self): + """Return the slug name of the series. Example: tvdb1234.""" + return indexer_id_to_slug(self.indexer, self.series_id) @property def current_qualities(self): From b8d1f74f6f0fa4f99a138bec469a395c077dbd47 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Wed, 28 Nov 2018 13:37:56 +0100 Subject: [PATCH 2/4] Added property validate_location to handle the ShowDirectoryNotFoundExceptions. --- CHANGELOG.md | 1 + medusa/metadata/generic.py | 18 +++++++++--------- medusa/metadata/kodi.py | 2 +- medusa/metadata/media_browser.py | 12 ++++++------ medusa/metadata/wdtv.py | 6 +++--- medusa/post_processor.py | 2 +- medusa/server/api/v1/core.py | 4 ++-- medusa/server/web/home/handler.py | 8 ++++---- medusa/show_queue.py | 2 +- medusa/tv/series.py | 8 ++++++++ 10 files changed, 36 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f143930b..02697ddfbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Fixed adding anime release group when adding show ([#5749](https://github.com/pymedusa/Medusa/pull/5749)) - Fixed Pushover debug log causing BraceException ([#5759](https://github.com/pymedusa/Medusa/pull/5759)) - Fixed torrent method Downloadstation not selected after restart ([#5761](https://github.com/pymedusa/Medusa/pull/5761)) +- Fixed changing show location, should now also utilise the option 'CREATE_MISSING_SHOW_DIRS' ([#5795](https://github.com/pymedusa/Medusa/pull/5795)) ----- diff --git a/medusa/metadata/generic.py b/medusa/metadata/generic.py index dc1ab5b004..87e8771081 100644 --- a/medusa/metadata/generic.py +++ b/medusa/metadata/generic.py @@ -159,19 +159,19 @@ def _has_season_all_banner(self, show_obj): return self._check_exists(self.get_season_all_banner_path(show_obj)) def get_show_file_path(self, show_obj): - return os.path.join(show_obj.location, self._show_metadata_filename) + return os.path.join(show_obj.validate_location, self._show_metadata_filename) def get_episode_file_path(self, ep_obj): - return replace_extension(ep_obj.location, self._ep_nfo_extension) + return replace_extension(ep_obj.validate_location, self._ep_nfo_extension) def get_fanart_path(self, show_obj): - return os.path.join(show_obj.location, self.fanart_name) + return os.path.join(show_obj.validate_location, self.fanart_name) def get_poster_path(self, show_obj): - return os.path.join(show_obj.location, self.poster_name) + return os.path.join(show_obj.validate_location, self.poster_name) def get_banner_path(self, show_obj): - return os.path.join(show_obj.location, self.banner_name) + return os.path.join(show_obj.validate_location, self.banner_name) @staticmethod def get_episode_thumb_path(ep_obj): @@ -208,7 +208,7 @@ def get_season_poster_path(show_obj, season): else: season_poster_filename = u'season' + str(season).zfill(2) - return os.path.join(show_obj.location, season_poster_filename + u'-poster.jpg') + return os.path.join(show_obj.validate_location, season_poster_filename + u'-poster.jpg') @staticmethod def get_season_banner_path(show_obj, season): @@ -225,13 +225,13 @@ def get_season_banner_path(show_obj, season): else: season_banner_filename = u'season' + str(season).zfill(2) - return os.path.join(show_obj.location, season_banner_filename + u'-banner.jpg') + return os.path.join(show_obj.validate_location, season_banner_filename + u'-banner.jpg') def get_season_all_poster_path(self, show_obj): - return os.path.join(show_obj.location, self.season_all_poster_name) + return os.path.join(show_obj.validate_location, self.season_all_poster_name) def get_season_all_banner_path(self, show_obj): - return os.path.join(show_obj.location, self.season_all_banner_name) + return os.path.join(show_obj.validate_location, self.season_all_banner_name) # pylint: disable=unused-argument,no-self-use def _show_data(self, show_obj): diff --git a/medusa/metadata/kodi.py b/medusa/metadata/kodi.py index 9605643636..fd920f229a 100644 --- a/medusa/metadata/kodi.py +++ b/medusa/metadata/kodi.py @@ -104,7 +104,7 @@ def get_season_poster_path(show_obj, season): else: season_poster_filename = 'season' + text_type(season).zfill(2) - return os.path.join(show_obj.location, season_poster_filename + '.tbn') + return os.path.join(show_obj.validate_location, season_poster_filename + '.tbn') # present a standard "interface" from the module diff --git a/medusa/metadata/media_browser.py b/medusa/metadata/media_browser.py index abf23e90b5..371ad2c63b 100644 --- a/medusa/metadata/media_browser.py +++ b/medusa/metadata/media_browser.py @@ -138,8 +138,8 @@ def get_season_poster_path(show_obj, season): If no season folder exists, None is returned """ - dir_list = [x for x in os.listdir(show_obj.location) if - os.path.isdir(os.path.join(show_obj.location, x))] + dir_list = [x for x in os.listdir(show_obj.validate_location) if + os.path.isdir(os.path.join(show_obj.validate_location, x))] season_dir_regex = ur'^Season\s+(\d+)$' @@ -170,7 +170,7 @@ def get_season_poster_path(show_obj, season): log.debug(u'Using {path}/folder.jpg as season directory for season {number}', {u'path': season_dir, u'number': season}) - return os.path.join(show_obj.location, season_dir, u'folder.jpg') + return os.path.join(show_obj.validate_location, season_dir, u'folder.jpg') @staticmethod def get_season_banner_path(show_obj, season): @@ -180,8 +180,8 @@ def get_season_banner_path(show_obj, season): If no season folder exists, None is returned """ - dir_list = [x for x in os.listdir(show_obj.location) if - os.path.isdir(os.path.join(show_obj.location, x))] + dir_list = [x for x in os.listdir(show_obj.validate_location) if + os.path.isdir(os.path.join(show_obj.validate_location, x))] season_dir_regex = ur'^Season\s+(\d+)$' @@ -212,7 +212,7 @@ def get_season_banner_path(show_obj, season): log.debug(u'Using {path}/banner.jpg as season directory for season {number}', {u'path': season_dir, u'number': season}) - return os.path.join(show_obj.location, season_dir, u'banner.jpg') + return os.path.join(show_obj.validate_location, season_dir, u'banner.jpg') def _show_data(self, show_obj): """ diff --git a/medusa/metadata/wdtv.py b/medusa/metadata/wdtv.py index 3f3c03fa90..c17feac597 100644 --- a/medusa/metadata/wdtv.py +++ b/medusa/metadata/wdtv.py @@ -130,8 +130,8 @@ def get_season_poster_path(show_obj, season): If no season folder exists, None is returned """ - dir_list = [x for x in os.listdir(show_obj.location) if - os.path.isdir(os.path.join(show_obj.location, x))] + dir_list = [x for x in os.listdir(show_obj.validate_location) if + os.path.isdir(os.path.join(show_obj.validate_location, x))] season_dir_regex = r'^Season\s+(\d+)$' @@ -159,7 +159,7 @@ def get_season_poster_path(show_obj, season): log.debug('Using {location}/folder.jpg as season dir for season {number}', {'location': season_dir, 'number': season}) - return os.path.join(show_obj.location, season_dir, 'folder.jpg') + return os.path.join(show_obj.validate_location, season_dir, 'folder.jpg') def _ep_data(self, ep_obj): """ diff --git a/medusa/post_processor.py b/medusa/post_processor.py index 095cd84628..83cfb3e300 100644 --- a/medusa/post_processor.py +++ b/medusa/post_processor.py @@ -1203,7 +1203,7 @@ def process(self): # find the destination folder try: proper_path = ep_obj.proper_path() - proper_absolute_path = os.path.join(ep_obj.series.location, proper_path) + proper_absolute_path = os.path.join(ep_obj.series.validate_location, proper_path) dest_path = os.path.dirname(proper_absolute_path) except ShowDirectoryNotFoundException: raise EpisodePostProcessingFailedException(u"Unable to post-process an episode if the show dir '{0}' " diff --git a/medusa/server/api/v1/core.py b/medusa/server/api/v1/core.py index 65375fa12b..fadf673700 100644 --- a/medusa/server/api/v1/core.py +++ b/medusa/server/api/v1/core.py @@ -734,7 +734,7 @@ def run(self): # absolute vs relative vs broken show_path = None try: - show_path = show_obj.location + show_path = show_obj.validate_location except ShowDirectoryNotFoundException: pass @@ -1903,7 +1903,7 @@ def run(self): show_dict['quality_details'] = {'initial': any_qualities, 'archive': best_qualities} try: - show_dict['location'] = show_obj.location + show_dict['location'] = show_obj.validate_location except ShowDirectoryNotFoundException: show_dict['location'] = '' diff --git a/medusa/server/web/home/handler.py b/medusa/server/web/home/handler.py index b8ba3599c7..3a1ac9db2b 100644 --- a/medusa/server/web/home/handler.py +++ b/medusa/server/web/home/handler.py @@ -823,7 +823,7 @@ def displayShow(self, indexername=None, seriesid=None, ): }] try: - show_loc = (series_obj.location, True) + show_loc = (series_obj.validate_location, True) except ShowDirectoryNotFoundException: show_loc = (series_obj._location, False) # pylint: disable=protected-access @@ -1146,7 +1146,7 @@ def snatchSelection(self, indexername, seriesid, season=None, episode=None, manu }] try: - show_loc = (series_obj.location, True) + show_loc = (series_obj.validate_location, True) except ShowDirectoryNotFoundException: show_loc = (series_obj._location, False) # pylint: disable=protected-access @@ -2016,7 +2016,7 @@ def testRename(self, indexername=None, seriesid=None): return self._genericMessage('Error', 'Show not in show list') try: - series_obj.location # @UnusedVariable + series_obj.validate_location # @UnusedVariable except ShowDirectoryNotFoundException: return self._genericMessage('Error', 'Can\'t rename episodes when the show dir is missing.') @@ -2051,7 +2051,7 @@ def doRename(self, indexername=None, seriesid=None, eps=None): return self._genericMessage('Error', error_message) try: - series_obj.location # @UnusedVariable + series_obj.validate_location # @UnusedVariable except ShowDirectoryNotFoundException: return self._genericMessage('Error', 'Can\'t rename episodes when the show dir is missing.') diff --git a/medusa/show_queue.py b/medusa/show_queue.py index c68418d793..6589ba5538 100644 --- a/medusa/show_queue.py +++ b/medusa/show_queue.py @@ -716,7 +716,7 @@ def run(self): ) try: - self.show.location + self.show.validate_location except ShowDirectoryNotFoundException: log.warning( "Can't perform rename on {series_name} when the show dir is missing.", diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 6c67afc5b4..4fb9428d19 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -368,6 +368,13 @@ def raw_location(self): """Get the raw show location, unvalidated.""" return self._location + @property + def validate_location(self): + """Legacy call to location with a validation when ADD_SHOWS_WO_DIR is set.""" + if app.CREATE_MISSING_SHOW_DIRS or self.is_location_valid(): + return self._location + raise ShowDirectoryNotFoundException(u'Show folder does not exist.') + @property def location(self): """Get the show location.""" @@ -1714,6 +1721,7 @@ def delete_show(self, full=False): # remove entire show folder if full: try: + _ = self.validate_location # Let's get the exception out of the way asap. log.info(u'{id}: Attempt to {action} show folder {location}', {'id': self.series_id, 'action': action, 'location': self.location}) # check first the read-only attribute From ba28f2fae4537f6e6c3691bc6c36663768c9feb7 Mon Sep 17 00:00:00 2001 From: P0psicles Date: Wed, 28 Nov 2018 13:40:08 +0100 Subject: [PATCH 3/4] Remove raw_location, as it's not needed anymore. --- medusa/clients/torrent/utorrent_client.py | 2 +- medusa/post_processor.py | 2 +- medusa/tv/episode.py | 2 +- medusa/tv/series.py | 21 ++++++++------------- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/medusa/clients/torrent/utorrent_client.py b/medusa/clients/torrent/utorrent_client.py index eecaff5103..7b84f525a5 100644 --- a/medusa/clients/torrent/utorrent_client.py +++ b/medusa/clients/torrent/utorrent_client.py @@ -26,7 +26,7 @@ def get_torrent_subfolder(result): root_dirs = app.ROOT_DIRS if root_dirs: root_location = root_dirs[int(root_dirs[0]) + 1] - torrent_path = result.series.raw_location + torrent_path = result.series.location if root_dirs and root_location != torrent_path: # Subfolder is under root, but possibly not directly under diff --git a/medusa/post_processor.py b/medusa/post_processor.py index 83cfb3e300..b8bbcad23b 100644 --- a/medusa/post_processor.py +++ b/medusa/post_processor.py @@ -1207,7 +1207,7 @@ def process(self): dest_path = os.path.dirname(proper_absolute_path) except ShowDirectoryNotFoundException: raise EpisodePostProcessingFailedException(u"Unable to post-process an episode if the show dir '{0}' " - u"doesn't exist, quitting".format(ep_obj.series.raw_location)) + u"doesn't exist, quitting".format(ep_obj.series.location)) self.log(u'Destination folder for this episode: {0}'.format(dest_path), logger.DEBUG) diff --git a/medusa/tv/episode.py b/medusa/tv/episode.py index f08ed41b0b..322fcb4810 100644 --- a/medusa/tv/episode.py +++ b/medusa/tv/episode.py @@ -864,7 +864,7 @@ def load_from_indexer(self, season=None, episode=None, tvapi=None, cached_season '{id}: {series} episode statuses unchanged. Location is missing: {location}', { 'id': self.series.series_id, 'series': self.series.name, - 'location': self.series.raw_location, + 'location': self.series.location, } ) return diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 4fb9428d19..a899f3808b 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -363,11 +363,6 @@ def network_logo_name(self): """Get the network logo name.""" return self.network.replace(u'\u00C9', 'e').replace(u'\u00E9', 'e').replace(' ', '-').lower() - @property - def raw_location(self): - """Get the raw show location, unvalidated.""" - return self._location - @property def validate_location(self): """Legacy call to location with a validation when ADD_SHOWS_WO_DIR is set.""" @@ -470,7 +465,7 @@ def default_ep_status_name(self, status_name): @property def size(self): """Size of the show on disk.""" - return helpers.get_size(self.raw_location) + return helpers.get_size(self.location) def show_size(self, pretty=False): """ @@ -1742,17 +1737,17 @@ def delete_show(self, full=False): shutil.rmtree(self.location) log.info(u'{id}: {action} show folder {location}', - {'id': self.series_id, 'action': action, 'location': self.raw_location}) + {'id': self.series_id, 'action': action, 'location': self.location}) except ShowDirectoryNotFoundException: log.warning(u'{id}: Show folder {location} does not exist. No need to {action}', - {'id': self.series_id, 'action': action, 'location': self.raw_location}) + {'id': self.series_id, 'action': action, 'location': self.location}) except OSError as error: log.warning( u'{id}: Unable to {action} {location}. Error: {error_msg}', { 'id': self.series_id, 'action': action, - 'location': self.raw_location, + 'location': self.location, 'error_msg': ex(error), } ) @@ -1924,7 +1919,7 @@ def save_to_db(self): control_value_dict = {'indexer': self.indexer, 'indexer_id': self.series_id} new_value_dict = {'show_name': self.name, - 'location': self.raw_location, # skip location validation + 'location': self.location, # skip location validation 'network': self.network, 'genre': self.genre, 'classification': self.classification, @@ -1973,7 +1968,7 @@ def __str__(self): to_return += 'indexerid: ' + str(self.series_id) + '\n' to_return += 'indexer: ' + str(self.indexer) + '\n' to_return += 'name: ' + self.name + '\n' - to_return += 'location: ' + self.raw_location + '\n' # skip location validation + to_return += 'location: ' + self.location + '\n' # skip location validation if self.network: to_return += 'network: ' + self.network + '\n' if self.airs: @@ -2000,7 +1995,7 @@ def __unicode__(self): to_return += u'indexerid: {0}\n'.format(self.series_id) to_return += u'indexer: {0}\n'.format(self.indexer) to_return += u'name: {0}\n'.format(self.name) - to_return += u'location: {0}\n'.format(self.raw_location) # skip location validation + to_return += u'location: {0}\n'.format(self.location) # skip location validation if self.network: to_return += u'network: {0}\n'.format(self.network) if self.airs: @@ -2060,7 +2055,7 @@ def to_json(self, detailed=True, fetch=False): data['country_codes'] = self.imdb_countries # e.g. ['it', 'fr'] data['plot'] = self.plot or self.imdb_plot data['config'] = {} - data['config']['location'] = self.raw_location + data['config']['location'] = self.location data['config']['qualities'] = {} data['config']['qualities']['allowed'] = self.qualities_allowed data['config']['qualities']['preferred'] = self.qualities_preferred From 91dd8949065185d8e9eedc3b2f6180ba0deccd18 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 12 Jan 2019 13:57:38 +0100 Subject: [PATCH 4/4] flake8 --- medusa/tv/series.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/medusa/tv/series.py b/medusa/tv/series.py index 194e024f59..1beddb1d69 100644 --- a/medusa/tv/series.py +++ b/medusa/tv/series.py @@ -1720,7 +1720,7 @@ def delete_show(self, full=False): # remove entire show folder if full: try: - _ = self.validate_location # Let's get the exception out of the way asap. + self.validate_location # Let's get the exception out of the way asap. log.info(u'{id}: Attempt to {action} show folder {location}', {'id': self.series_id, 'action': action, 'location': self.location}) # check first the read-only attribute