add_shows.py 24.6 KB
Newer Older
echel0n's avatar
echel0n committed
1
# ##############################################################################
2
3
4
#  Author: echel0n <[email protected]>
#  URL: https://sickrage.ca/
#  Git: https://git.sickrage.ca/SiCKRAGE/sickrage.git
echel0n's avatar
echel0n committed
5
#  -
6
#  This file is part of SiCKRAGE.
echel0n's avatar
echel0n committed
7
#  -
8
9
10
11
#  SiCKRAGE is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
echel0n's avatar
echel0n committed
12
#  -
13
14
15
16
#  SiCKRAGE is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
echel0n's avatar
echel0n committed
17
#  -
18
19
#  You should have received a copy of the GNU General Public License
#  along with SiCKRAGE.  If not, see <http://www.gnu.org/licenses/>.
echel0n's avatar
echel0n committed
20
# ##############################################################################
21
22
23

import os
import re
24
from urllib.parse import unquote_plus, urlencode
25
26
27

from tornado.escape import json_encode
from tornado.httputil import url_concat
28
from tornado.web import authenticated
29
30

import sickrage
31
from sickrage.core.common import Quality, Qualities, EpisodeStatus
32
33
from sickrage.core.enums import SearchFormat, SeriesProviderID
from sickrage.core.helpers import sanitize_file_name, make_dir, chmod_as_parent, checkbox_to_value
34
35
from sickrage.core.helpers.anidb import short_group_names
from sickrage.core.imdb_popular import imdbPopular
36
from sickrage.core.traktapi import TraktAPI
echel0n's avatar
echel0n committed
37
from sickrage.core.tv.show import TVShow
38
from sickrage.core.tv.show.helpers import find_show, get_show_list, find_show_by_location
39
from sickrage.core.webserver.handlers.base import BaseHandler
40
from sickrage.series_providers.helpers import search_series_provider_for_series_id
41
42
43
44
45


def split_extra_show(extra_show):
    if not extra_show:
        return None, None, None, None
46

47
    split_vals = extra_show.split('|')
48

49
    if len(split_vals) < 4:
50
        series_provider_id = split_vals[0]
51
        show_dir = split_vals[1]
52
53
54
        return series_provider_id, show_dir, None, None

    series_provider_id = split_vals[0]
55
    show_dir = split_vals[1]
56
    series_id = split_vals[2]
57
58
    show_name = '|'.join(split_vals[3:])

59
    return series_provider_id, show_dir, series_id, show_name
60
61


62
class HomeAddShowsHandler(BaseHandler):
63
    @authenticated
64
    def get(self, *args, **kwargs):
65
66
67
68
69
70
        return self.render('home/add_shows.mako',
                           title=_('Add Shows'),
                           header=_('Add Shows'),
                           topmenu='home',
                           controller='home',
                           action='add_shows')
71
72


73
class SearchSeriesProviderForShowNameHandler(BaseHandler):
74
    @authenticated
75
    def get(self, *args, **kwargs):
76
        search_term = self.get_argument('search_term')
77
        series_provider_id = self.get_argument('series_provider_id', None)
78

79
80
81
        series_provider_language = self.get_argument('lang', None)
        if not series_provider_language or series_provider_language == 'null':
            series_provider_language = sickrage.app.config.general.series_provider_default_language
82

83
        results = []
84

85
        # search via series name
86
87
        series_provider = sickrage.app.series_providers[SeriesProviderID[series_provider_id]]
        series_results = series_provider.search(query=search_term, language=series_provider_language)
88
89
        if series_results:
            for series in series_results:
90
                if not series.get('name', None):
91
92
                    continue

93
                if not series.get('firstAired', None):
94
95
                    continue

96
                results.append((
97
98
99
100
                    series_provider.name,
                    series_provider_id,
                    series_provider.show_url,
                    int(series['id']),
101
102
                    series['name'],
                    series['firstAired'],
103
104
                    ('', 'disabled')[isinstance(find_show(int(series['id']), SeriesProviderID[series_provider_id]), TVShow)]
                ))
105

106
        return json_encode({'results': results, 'langid': series_provider_language})
107
108


109
class MassAddTableHandler(BaseHandler):
110
    @authenticated
111
    def get(self, *args, **kwargs):
112
        root_dir = self.get_arguments('rootDir')
113

114
        root_dirs = [unquote_plus(x) for x in root_dir]
115

116
117
        if sickrage.app.config.general.root_dirs:
            default_index = int(sickrage.app.config.general.root_dirs.split('|')[0])
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
        else:
            default_index = 0

        if len(root_dirs) > default_index:
            tmp = root_dirs[default_index]
            if tmp in root_dirs:
                root_dirs.remove(tmp)
                root_dirs = [tmp] + root_dirs

        dir_list = []

        for root_dir in root_dirs:
            try:
                file_list = os.listdir(root_dir)
            except Exception:
                continue

            for cur_file in file_list:
                try:
                    cur_path = os.path.normpath(os.path.join(root_dir, cur_file))
                    if not os.path.isdir(cur_path):
                        continue

                    # ignore Synology folders
                    if cur_file.lower() in ['#recycle', '@eadir']:
                        continue

145
                    cur_dir = {'dir': cur_path, 'display_dir': '<b>{}{}</b>{}'.format(os.path.dirname(cur_path), os.sep, os.path.basename(cur_path))}
146
147

                    # see if the folder is in database already
148
149
                    cur_dir['added_already'] = False
                    if find_show_by_location(cur_path):
150
151
152
153
                        cur_dir['added_already'] = True

                    dir_list.append(cur_dir)

154
                    series_id = show_name = series_provider_id = None
155
                    for cur_provider in sickrage.app.metadata_providers.values():
156
                        if all([series_id, show_name, series_provider_id]):
157
158
                            continue

159
                        (series_id, show_name, series_provider_id) = cur_provider.retrieve_show_metadata(cur_path)
160
                        if show_name:
161
162
163
164
165
166
167
168
169
170
                            if not series_provider_id and series_id:
                                for series_provider_id in SeriesProviderID:
                                    result = search_series_provider_for_series_id(series_provider_id, show_name)
                                    if result == series_id:
                                        break
                            elif not series_id and series_provider_id:
                                series_id = search_series_provider_for_series_id(series_provider_id, show_name)

                    cur_dir['existing_info'] = (series_id, show_name, series_provider_id)
                    if series_id and find_show(series_id, series_provider_id):
171
172
173
174
                        cur_dir['added_already'] = True
                except Exception:
                    pass

175
176
177
178
        return self.render('home/mass_add_table.mako',
                           dirList=dir_list,
                           controller='home',
                           action="mass_add_table")
179
180


181
class NewShowHandler(BaseHandler):
182
    @authenticated
183
    def get(self, *args, **kwargs):
184
185
186
187
188
        """
        Display the new show page which collects a tvdb id, folder, and extra options and
        posts them to addNewShow
        """

189
        show_to_add = self.get_argument('show_to_add', None)
190
        other_shows = self.get_arguments('other_shows')
191
        search_string = self.get_argument('search_string', None)
192

193
        series_provider_id, show_dir, series_id, show_name = split_extra_show(show_to_add)
194
195

        use_provided_info = False
196
        if series_id and series_provider_id and show_name:
197
198
            use_provided_info = True

199
        # use the given show_dir for the series provider search if available
200
201
202
203
        default_show_name = show_name or ''
        if not show_dir and search_string:
            default_show_name = search_string
        elif not show_name and show_dir:
204
            default_show_name = re.sub(r' \(\d{4}\)', '', os.path.basename(os.path.normpath(show_dir)).replace('.', ' '))
205

206
207
208
        provided_series_id = int(series_id or 0)
        provided_series_name = show_name or ''
        provided_series_provider_id = SeriesProviderID[series_provider_id] if series_provider_id else sickrage.app.config.general.series_provider_default
209

210
211
212
213
214
215
        return self.render('home/new_show.mako',
                           enable_anime_options=True,
                           use_provided_info=use_provided_info,
                           default_show_name=default_show_name,
                           other_shows=other_shows,
                           provided_show_dir=show_dir,
216
217
                           provided_series_id=provided_series_id,
                           provided_series_name=provided_series_name,
218
                           provided_series_provider_id=provided_series_provider_id,
219
220
                           series_providers=SeriesProviderID,
                           quality=sickrage.app.config.general.quality_default,
221
222
223
224
225
226
227
228
                           whitelist=[],
                           blacklist=[],
                           groups=[],
                           title=_('New Show'),
                           header=_('New Show'),
                           topmenu='home',
                           controller='home',
                           action="new_show")
229
230


231
class TraktShowsHandler(BaseHandler):
232
    @authenticated
233
    def get(self, *args, **kwargs):
234
235
236
237
238
        """
        Display the new show page which collects a tvdb id, folder, and extra options and
        posts them to addNewShow
        """

239
240
        show_list = self.get_argument('list', 'trending')
        limit = self.get_argument('limit', None) or 10
241

242
        trakt_shows = []
243

244
        shows, black_list = getattr(TraktAPI()['shows'], show_list)(extended="full", limit=int(limit) + len(get_show_list())), False
245
246

        while len(trakt_shows) < int(limit):
247
            trakt_shows += [x for x in shows if 'tvdb' in x.ids and not find_show(int(x.ids['tvdb']))]
248

249
250
251
252
253
254
255
256
257
258
        return self.render('home/trakt_shows.mako',
                           title="Trakt {} Shows".format(show_list.capitalize()),
                           header="Trakt {} Shows".format(show_list.capitalize()),
                           enable_anime_options=False,
                           black_list=black_list,
                           trakt_shows=trakt_shows[:int(limit)],
                           trakt_list=show_list,
                           limit=limit,
                           controller='home',
                           action="trakt_shows")
259
260


261
class PopularShowsHandler(BaseHandler):
262
    @authenticated
263
    def get(self, *args, **kwargs):
264
265
266
        """
        Fetches data from IMDB to show a list of popular shows.
        """
267
        imdb_exception = None
268
269

        try:
270
            popular_shows = imdbPopular().fetch_popular_shows()
271
272
        except Exception as e:
            popular_shows = None
273
            imdb_exception = e
274

275
276
277
278
279
280
281
282
        return self.render('home/imdb_shows.mako',
                           title="IMDB Popular Shows",
                           header="IMDB Popular Shows",
                           popular_shows=popular_shows,
                           imdb_exception=imdb_exception,
                           topmenu="home",
                           controller='home',
                           action="popular_shows")
283
284


285
class AddShowToBlacklistHandler(BaseHandler):
286
    @authenticated
287
    def get(self, *args, **kwargs):
288
        series_id = self.get_argument('series_id')
289

290
291
        data = {'shows': [{'ids': {'tvdb': series_id}}]}
        TraktAPI()["users/me/lists/{list}".format(list=sickrage.app.config.trakt.blacklist_name)].add(data)
292
293
294
        return self.redirect('/home/addShows/trendingShows/')


295
class ExistingShowsHandler(BaseHandler):
296
    @authenticated
297
    def get(self, *args, **kwargs):
298
299
300
        """
        Prints out the page to add existing shows from a root dir
        """
301
302
        return self.render('home/add_existing_shows.mako',
                           enable_anime_options=False,
303
                           quality=sickrage.app.config.general.quality_default,
304
305
306
307
308
                           title=_('Existing Show'),
                           header=_('Existing Show'),
                           topmenu="home",
                           controller='home',
                           action="add_existing_shows")
309
310


311
class AddShowByIDHandler(BaseHandler):
312
    @authenticated
313
    def get(self, *args, **kwargs):
314
        series_id = self.get_argument('series_id')
315
        show_name = self.get_argument('showName')
316

317
        if re.search(r'tt\d+', series_id):
318
319
320
321
322
            result = sickrage.app.series_providers[SeriesProviderID.THETVDB].search_by_id(series_id)
            if not result:
                return

            series_id = int(result['id'])
323

324
        if find_show(int(series_id), SeriesProviderID.THETVDB):
325
            sickrage.app.log.debug(f"{series_id} already exists in your show library, skipping!")
326
327
328
            return

        location = None
329
330
        if sickrage.app.config.general.root_dirs:
            root_dirs = sickrage.app.config.general.root_dirs.split('|')
331
332
333
334
            location = root_dirs[int(root_dirs[0]) + 1]

        if not location:
            sickrage.app.log.warning("There was an error creating the show, no root directory setting found")
335
            return _('No root directories setup, please go back and add one.')
336

echel0n's avatar
echel0n committed
337
        show_dir = os.path.join(location, sanitize_file_name(show_name))
338

339
        return self.redirect(url_concat("/home/addShows/newShow",
340
341
342
343
344
                                        {'show_to_add': '{series_provider_id}|{show_dir}|{series_id}|{show_name}'.format(
                                            **{'series_provider_id': SeriesProviderID.THETVDB.name,
                                               'show_dir': show_dir,
                                               'series_id': series_id,
                                               'show_name': show_name})}))
345
346


347
class AddNewShowHandler(BaseHandler):
348
    @authenticated
349
    def get(self, *args, **kwargs):
350
351
        return self.redirect("/home/")

352
    @authenticated
353
    def post(self, *args, **kwargs):
354
355
356
357
358
        """
        Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
        provided then it forwards back to newShow, if not it goes to /home.
        """

359
        whichSeries = self.get_argument('whichSeries', None)
360
361
        rootDir = self.get_argument('rootDir', None)
        fullShowPath = self.get_argument('fullShowPath', None)
362
363
        provided_series_name = self.get_argument('providedSeriesName', None)
        series_provider_language = self.get_argument('seriesProviderLanguage', None)
364
365
        defaultStatus = self.get_argument('defaultStatus', None)
        quality_preset = self.get_argument('quality_preset', None)
366
367
        anyQualities = self.get_arguments('anyQualities')
        bestQualities = self.get_arguments('bestQualities')
368
369
370
371
372
        flatten_folders = self.get_argument('flatten_folders', None)
        subtitles = self.get_argument('subtitles', None)
        sub_use_sr_metadata = self.get_argument('sub_use_sr_metadata', None)
        other_shows = self.get_arguments('other_shows')
        skipShow = self.get_argument('skipShow', None)
373
        provided_series_provider_id = self.get_argument('providedSeriesProviderID', None)
374
        anime = self.get_argument('anime', None)
375
        search_format = self.get_argument('search_format', None)
376
        dvd_order = self.get_argument('dvd_order', None)
377
378
379
        blacklist = self.get_argument('blacklist', None)
        whitelist = self.get_argument('whitelist', None)
        defaultStatusAfter = self.get_argument('defaultStatusAfter', None)
380
        scene = self.get_argument('scene', None)
381
382
        skip_downloaded = self.get_argument('skip_downloaded', None)
        add_show_year = self.get_argument('add_show_year', None)
383
384
385

        # if we're skipping then behave accordingly
        if skipShow:
386
            return self.finish_add_show(other_shows)
387

388
389
390
        if not whichSeries:
            return self.redirect("/home/")

391
392
393
394
        # figure out what show we're adding and where
        series_pieces = whichSeries.split('|')
        if (whichSeries and rootDir or whichSeries and fullShowPath) and len(series_pieces) > 1:
            if len(series_pieces) < 6:
395
396
                sickrage.app.log.error('Unable to add show due to show selection. Not anough arguments: %s' % (repr(series_pieces)))
                sickrage.app.alerts.error(_('Unknown error. Unable to add show due to problem with show selection.'))
397
398
                return self.redirect('/home/addShows/existingShows/')

399
400
            series_provider_id = series_pieces[1]
            series_id = int(series_pieces[3])
401
402
            show_name = series_pieces[4]
        else:
403
404
            series_provider_id = provided_series_provider_id or sickrage.app.config.general.series_provider_default
            series_id = int(whichSeries)
405
406
407
            if fullShowPath:
                show_name = os.path.basename(os.path.normpath(fullShowPath))
            else:
408
                show_name = provided_series_name
409
410
411
412
413

        # use the whole path if it's given, or else append the show name to the root dir to get the full show path
        if fullShowPath:
            show_dir = os.path.normpath(fullShowPath)
        else:
echel0n's avatar
echel0n committed
414
            show_dir = os.path.join(rootDir, sanitize_file_name(show_name))
415
            if add_show_year and not re.match(r'.*\(\d+\)$', show_dir) and re.search(r'\d{4}', series_pieces[5]):
416
417
418
419
420
421
422
423
424
                show_dir = "{} ({})".format(show_dir, re.search(r'\d{4}', series_pieces[5]).group(0))

        # blanket policy - if the dir exists you should have used "add existing show" numbnuts
        if os.path.isdir(show_dir) and not fullShowPath:
            sickrage.app.alerts.error(_("Unable to add show"),
                                      _("Folder ") + show_dir + _(" exists already"))
            return self.redirect('/home/addShows/existingShows/')

        # don't create show dir if config says not to
425
        if sickrage.app.config.general.add_shows_wo_dir:
426
            sickrage.app.log.info("Skipping initial creation of " + show_dir + " due to SiCKRAGE configuation setting")
427
        else:
echel0n's avatar
echel0n committed
428
            dir_exists = make_dir(show_dir)
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
            if not dir_exists:
                sickrage.app.log.warning("Unable to create the folder " + show_dir + ", can't add the show")
                sickrage.app.alerts.error(_("Unable to add show"),
                                          _("Unable to create the folder " +
                                            show_dir + ", can't add the show"))

                # Don't redirect to default page because user wants to see the new show
                return self.redirect("/home/")
            else:
                chmod_as_parent(show_dir)

        if whitelist:
            whitelist = short_group_names(whitelist)
        if blacklist:
            blacklist = short_group_names(blacklist)

445
446
        try:
            new_quality = Qualities[quality_preset.upper()]
447
        except (AttributeError, KeyError):
448
            new_quality = Quality.combine_qualities([Qualities[x.upper()] for x in anyQualities], [Qualities[x.upper()] for x in bestQualities])
449
450

        # add the show
451
452
        sickrage.app.show_queue.add_show(series_provider_id=SeriesProviderID[series_provider_id],
                                         series_id=series_id,
453
                                         showDir=show_dir,
454
455
                                         default_status=EpisodeStatus[defaultStatus],
                                         default_status_after=EpisodeStatus[defaultStatusAfter],
456
                                         quality=new_quality,
457
458
459
460
461
462
463
                                         flatten_folders=checkbox_to_value(flatten_folders),
                                         lang=series_provider_language or sickrage.app.config.general.series_provider_default_language,
                                         subtitles=checkbox_to_value(subtitles),
                                         sub_use_sr_metadata=checkbox_to_value(sub_use_sr_metadata),
                                         anime=checkbox_to_value(anime),
                                         dvd_order=checkbox_to_value(dvd_order),
                                         search_format=SearchFormat[search_format],
464
                                         paused=False,
465
466
                                         blacklist=blacklist,
                                         whitelist=whitelist,
467
468
                                         scene=checkbox_to_value(scene),
                                         skip_downloaded=checkbox_to_value(skip_downloaded))
469
470
471

        sickrage.app.alerts.message(_('Adding Show'), _('Adding the specified show into ') + show_dir)

472
        return self.finish_add_show(other_shows)
473

474
    def finish_add_show(self, other_shows):
475
476
477
478
479
480
        # if there are no extra shows then go home
        if not other_shows:
            return self.redirect('/home/')

        # peel off the next one
        next_show_dir = other_shows[0]
481
        rest_of_show_dirs = other_shows[1:]
482
483

        # go to add the next show
484
        return self.redirect("/home/addShows/newShow?" + urlencode({'show_to_add': next_show_dir, 'other_shows': rest_of_show_dirs}, True))
485

486

487
class AddExistingShowsHandler(BaseHandler):
488
    @authenticated
489
    def post(self, *args, **kwargs):
490
491
492
493
        """
        Receives a dir list and add them. Adds the ones with given TVDB IDs first, then forwards
        along to the newShow page.
        """
494
495
        shows_to_add = self.get_arguments('shows_to_add')
        prompt_for_settings = self.get_argument('promptForSettings')
496
497
498
499
500
501

        # grab a list of other shows to add, if provided
        shows_to_add = [unquote_plus(x) for x in shows_to_add]

        prompt_for_settings = checkbox_to_value(prompt_for_settings)

502
        series_id_given = []
503
        dirs_only = []
504
        # separate all the ones with series id
505
506
507
508
        for cur_dir in shows_to_add:
            split_vals = cur_dir.split('|')
            if split_vals:
                if len(split_vals) > 2:
509
510
511
                    series_provider_id, show_dir, series_id, show_name = split_extra_show(cur_dir)
                    if all([show_dir, series_id, show_name]):
                        series_id_given.append((series_provider_id, show_dir, int(series_id), show_name))
512
513
514
515
516
517
518
                else:
                    dirs_only.append(cur_dir)
            else:
                dirs_only.append(cur_dir)

        # if they want me to prompt for settings then I will just carry on to the newShow page
        if prompt_for_settings and shows_to_add:
519
            return self.redirect("/home/addShows/newShow?" + urlencode({'show_to_add': shows_to_add[0], 'other_shows': shows_to_add[1:]}, True))
520
521
522

        # if they don't want me to prompt for settings then I can just add all the nfo shows now
        num_added = 0
523
524
        for cur_show in series_id_given:
            series_provider_id, show_dir, series_id, show_name = cur_show
525

526
            if series_provider_id is not None and series_id is not None:
527
                # add the show
528
                sickrage.app.show_queue.add_show(SeriesProviderID[series_provider_id],
529
                                                 series_id,
530
                                                 show_dir,
531
532
533
534
535
536
537
538
539
                                                 default_status=sickrage.app.config.general.status_default,
                                                 quality=sickrage.app.config.general.quality_default,
                                                 flatten_folders=sickrage.app.config.general.flatten_folders_default,
                                                 subtitles=sickrage.app.config.subtitles.default,
                                                 anime=sickrage.app.config.general.anime_default,
                                                 search_format=sickrage.app.config.general.search_format_default,
                                                 default_status_after=sickrage.app.config.general.status_default_after,
                                                 scene=sickrage.app.config.general.scene_default,
                                                 skip_downloaded=sickrage.app.config.general.skip_downloaded_default)
540
541
542
543
                num_added += 1

        if num_added:
            sickrage.app.alerts.message(_("Shows Added"),
544
                                        _("Automatically added ") + str(num_added) + _(" from their existing metadata files"))
545
546
547
548
549
550

        # if we're done then go home
        if not dirs_only:
            return self.redirect('/home/')

        # for the remaining shows we need to prompt for each one, so forward this on to the newShow page
551
        return self.redirect("/home/addShows/newShow?" + urlencode({'show_to_add': dirs_only[0], 'other_shows': dirs_only[1:]}, True))