__init__.py 60 KB
Newer Older
echel0n's avatar
echel0n committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ##############################################################################
#  Author: echel0n <[email protected]>
#  URL: https://sickrage.ca/
#  Git: https://git.sickrage.ca/SiCKRAGE/sickrage.git
#  -
#  This file is part of SiCKRAGE.
#  -
#  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.
#  -
#  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.
#  -
#  You should have received a copy of the GNU General Public License
#  along with SiCKRAGE.  If not, see <http://www.gnu.org/licenses/>.
# ##############################################################################
21

22
import datetime
23
24
25
import glob
import os
import re
26
import shutil
27
import stat
28
import threading
29
30
31
import traceback

import send2trash
32
import sqlalchemy
33
from adba.aniDBAbstracter import Anime
34
from sqlalchemy import orm
echel0n's avatar
echel0n committed
35
from unidecode import unidecode
echel0n's avatar
v8.8.5    
echel0n committed
36
37

import sickrage
38
from sickrage.core.blackandwhitelist import BlackAndWhiteList
echel0n's avatar
echel0n committed
39
from sickrage.core.caches.image_cache import ImageCache
40
41
42
from sickrage.core.common import Quality, Qualities, EpisodeStatus
from sickrage.core.databases.main import MainDB
from sickrage.core.databases.main.schemas import TVShowSchema, IMDbInfoSchema, BlacklistSchema, WhitelistSchema
43
from sickrage.core.enums import SeriesProviderID
44
from sickrage.core.exceptions import ShowNotFoundException, EpisodeNotFoundException, EpisodeDeletedException, MultipleEpisodesInDatabaseException
45
46
from sickrage.core.helpers import list_media_files, is_media_file, try_int, safe_getattr, flatten
from sickrage.core.media.util import series_image, SeriesImageType
47
from sickrage.core.tv.episode import TVEpisode
48
49
50
51
52
53
54
55
56
57
from sickrage.series_providers.exceptions import SeriesProviderAttributeNotFound, SeriesProviderException


class TVShowID(object):
    def __init__(self, series_id, series_provider_id):
        self.series_id = series_id
        self.series_provider_id = series_provider_id

    @property
    def slug(self):
58
        return f'{self.series_id}-{self.series_provider_id.value}'
59
60


61
class TVShow(object):
62
    def __init__(self, series_id, series_provider_id, lang='eng', location=''):
63
        self.lock = threading.Lock()
64
        self._episodes = {}
65
        self.loading_episodes = False
66

67
68
        with sickrage.app.main_db.session() as session:
            try:
69
                query = session.query(MainDB.TVShow).filter_by(series_id=series_id, series_provider_id=series_provider_id).one()
70
71
                self._data_local = query.as_dict()
            except orm.exc.NoResultFound:
72
73
                self._data_local = MainDB.TVShow().as_dict()
                self._data_local.update(**{
74
75
                    'series_id': series_id,
                    'series_provider_id': series_provider_id,
76
77
                    'lang': lang,
                    'location': location
78
                })
79

80
81
82
                self.load_from_series_provider()

        sickrage.app.shows.update({(self.series_id, self.series_provider_id): self})
83

84
85
    @property
    def slug(self):
86
        return f'{self.series_id}-{self.series_provider_id.value}'
87

88
    @property
89
90
    def series_id(self):
        return self._data_local['series_id']
91

92
93
94
    @series_id.setter
    def series_id(self, value):
        self._data_local['series_id'] = value
95
96

    @property
97
98
    def series_provider_id(self):
        return self._data_local['series_provider_id']
99

100
101
102
    @series_provider_id.setter
    def series_provider_id(self, value):
        self._data_local['series_provider_id'] = value
103

104
105
106
107
108
    @property
    def tvdb_id(self):
        if self.series_provider_id == SeriesProviderID.THETVDB:
            return self.series_id

109
110
    @property
    def name(self):
111
        return self._data_local['name']
112
113
114

    @name.setter
    def name(self, value):
115
        self._data_local['name'] = value
116
117
118

    @property
    def location(self):
119
        return self._data_local['location']
120
121
122

    @location.setter
    def location(self, value):
123
        self._data_local['location'] = value
124
125
126

    @property
    def network(self):
127
        return self._data_local['network']
128
129
130

    @network.setter
    def network(self, value):
131
        self._data_local['network'] = value
132
133
134

    @property
    def genre(self):
135
        return self._data_local['genre']
136
137
138

    @genre.setter
    def genre(self, value):
139
        self._data_local['genre'] = value
140
141
142

    @property
    def overview(self):
143
        return self._data_local['overview']
144
145
146

    @overview.setter
    def overview(self, value):
147
        self._data_local['overview'] = value
148
149
150

    @property
    def classification(self):
151
        return self._data_local['classification']
152
153
154

    @classification.setter
    def classification(self, value):
155
        self._data_local['classification'] = value
156
157
158

    @property
    def runtime(self):
159
        return self._data_local['runtime']
160
161
162

    @runtime.setter
    def runtime(self, value):
163
        self._data_local['runtime'] = value
164
165
166

    @property
    def quality(self):
167
        return self._data_local['quality']
168
169
170

    @quality.setter
    def quality(self, value):
171
        self._data_local['quality'] = value
172
173
174

    @property
    def airs(self):
175
        return self._data_local['airs']
176
177
178

    @airs.setter
    def airs(self, value):
179
        self._data_local['airs'] = value
180
181
182

    @property
    def status(self):
183
        return self._data_local['status']
184
185
186

    @status.setter
    def status(self, value):
187
        self._data_local['status'] = value
188
189
190

    @property
    def flatten_folders(self):
191
        return self._data_local['flatten_folders']
192
193
194

    @flatten_folders.setter
    def flatten_folders(self, value):
195
        self._data_local['flatten_folders'] = value
196
197
198

    @property
    def paused(self):
199
        return self._data_local['paused']
200
201
202

    @paused.setter
    def paused(self, value):
203
        self._data_local['paused'] = value
204

205
206
207
208
209
210
211
212
    @property
    def scene(self):
        return self._data_local['scene']

    @scene.setter
    def scene(self, value):
        self._data_local['scene'] = value

213
214
    @property
    def anime(self):
215
        return self._data_local['anime']
216
217
218

    @anime.setter
    def anime(self, value):
219
        self._data_local['anime'] = value
220
221

    @property
222
223
    def search_format(self):
        return self._data_local['search_format']
224

225
226
227
    @search_format.setter
    def search_format(self, value):
        self._data_local['search_format'] = value
228
229
230

    @property
    def subtitles(self):
231
        return self._data_local['subtitles']
232
233
234

    @subtitles.setter
    def subtitles(self, value):
235
        self._data_local['subtitles'] = value
236
237

    @property
238
239
    def dvd_order(self):
        return self._data_local['dvd_order']
240

241
242
243
    @dvd_order.setter
    def dvd_order(self, value):
        self._data_local['dvd_order'] = value
244
245
246

    @property
    def skip_downloaded(self):
247
        return self._data_local['skip_downloaded']
248
249
250

    @skip_downloaded.setter
    def skip_downloaded(self, value):
251
        self._data_local['skip_downloaded'] = value
252
253
254

    @property
    def startyear(self):
255
        return self._data_local['startyear']
256
257
258

    @startyear.setter
    def startyear(self, value):
259
        self._data_local['startyear'] = value
260
261
262

    @property
    def lang(self):
263
        return self._data_local['lang']
264
265
266

    @lang.setter
    def lang(self, value):
267
        self._data_local['lang'] = value
268
269
270

    @property
    def imdb_id(self):
271
        return self._data_local['imdb_id']
272
273
274

    @imdb_id.setter
    def imdb_id(self, value):
275
        self._data_local['imdb_id'] = value
276
277
278

    @property
    def rls_ignore_words(self):
279
        return self._data_local['rls_ignore_words']
280
281
282

    @rls_ignore_words.setter
    def rls_ignore_words(self, value):
283
        self._data_local['rls_ignore_words'] = value
284
285
286

    @property
    def rls_require_words(self):
287
        return self._data_local['rls_require_words']
288
289
290

    @rls_require_words.setter
    def rls_require_words(self, value):
291
        self._data_local['rls_require_words'] = value
292
293
294

    @property
    def default_ep_status(self):
295
        return self._data_local['default_ep_status']
296
297
298

    @default_ep_status.setter
    def default_ep_status(self, value):
299
        self._data_local['default_ep_status'] = value
300
301
302

    @property
    def sub_use_sr_metadata(self):
303
        return self._data_local['sub_use_sr_metadata']
304
305
306

    @sub_use_sr_metadata.setter
    def sub_use_sr_metadata(self, value):
307
        self._data_local['sub_use_sr_metadata'] = value
308
309
310

    @property
    def notify_list(self):
311
        return self._data_local['notify_list']
312
313
314

    @notify_list.setter
    def notify_list(self, value):
315
        self._data_local['notify_list'] = value
316
317
318

    @property
    def search_delay(self):
319
        return self._data_local['search_delay']
320
321
322

    @search_delay.setter
    def search_delay(self, value):
323
        self._data_local['search_delay'] = value
324

325
326
    @property
    def scene_exceptions(self):
327
328
329
        if self._data_local['scene_exceptions']:
            return list(filter(None, self._data_local['scene_exceptions'].split(',')))
        return []
330
331
332
333
334

    @scene_exceptions.setter
    def scene_exceptions(self, value):
        self._data_local['scene_exceptions'] = ','.join(value)

335
336
    @property
    def last_update(self):
337
        return self._data_local['last_update']
338
339
340

    @last_update.setter
    def last_update(self, value):
341
        self._data_local['last_update'] = value
342
343
344

    @property
    def last_refresh(self):
345
        return self._data_local['last_refresh']
346
347
348

    @last_refresh.setter
    def last_refresh(self, value):
349
        self._data_local['last_refresh'] = value
350
351
352

    @property
    def last_backlog_search(self):
353
        return self._data_local['last_backlog_search']
354
355
356

    @last_backlog_search.setter
    def last_backlog_search(self, value):
357
        self._data_local['last_backlog_search'] = value
358
359
360

    @property
    def last_proper_search(self):
361
        return self._data_local['last_proper_search']
362
363
364

    @last_proper_search.setter
    def last_proper_search(self, value):
365
        self._data_local['last_proper_search'] = value
366

367
368
369
370
371
372
373
374
    @property
    def last_scene_exceptions_refresh(self):
        return self._data_local['last_scene_exceptions_refresh']

    @last_scene_exceptions_refresh.setter
    def last_scene_exceptions_refresh(self, value):
        self._data_local['last_scene_exceptions_refresh'] = value

375
376
377
378
379
380
381
382
    @property
    def last_xem_refresh(self):
        return self._data_local['last_xem_refresh']

    @last_xem_refresh.setter
    def last_xem_refresh(self, value):
        self._data_local['last_xem_refresh'] = value

383
384
385
386
    @property
    def series_provider(self):
        return sickrage.app.series_providers[self.series_provider_id]

387
    @property
388
    def episodes(self):
389
        if not self._episodes:
390
            with sickrage.app.main_db.session() as session:
391
                for x in session.query(MainDB.TVEpisode).filter_by(series_id=self.series_id, series_provider_id=self.series_provider_id):
392
                    self._episodes[x.episode_id] = TVEpisode(series_id=x.series_id, series_provider_id=x.series_provider_id, season=x.season, episode=x.episode)
393
        return list(self._episodes.values())
394
395
396

    @property
    def imdb_info(self):
397
        with sickrage.app.main_db.session() as session:
398
            return session.query(MainDB.IMDbInfo).filter_by(series_id=self.series_id).one_or_none()
399

400
401
    @property
    def is_anime(self):
echel0n's avatar
echel0n committed
402
        return int(self.anime) > 0
403

404
405
406
    @property
    def airs_next(self):
        _airs_next = datetime.date.min
407
408

        with sickrage.app.main_db.session() as session:
409
410
411
            query = session.query(
                MainDB.TVEpisode
            ).filter_by(
412
                series_id=self.series_id, series_provider_id=self.series_provider_id
413
414
415
            ).filter(
                MainDB.TVEpisode.season > 0,
                MainDB.TVEpisode.airdate >= datetime.date.today(),
416
                MainDB.TVEpisode.status.in_([EpisodeStatus.UNAIRED, EpisodeStatus.WANTED])
417
418
419
420
421
422
            ).order_by(
                MainDB.TVEpisode.airdate
            ).first()

            if query:
                _airs_next = query.airdate
423

424
425
426
427
428
        return _airs_next

    @property
    def airs_prev(self):
        _airs_prev = datetime.date.min
429
430

        with sickrage.app.main_db.session() as session:
431
432
433
            query = session.query(
                MainDB.TVEpisode
            ).filter_by(
434
                series_id=self.series_id, series_provider_id=self.series_provider_id
435
436
437
            ).filter(
                MainDB.TVEpisode.season > 0,
                MainDB.TVEpisode.airdate < datetime.date.today(),
438
                MainDB.TVEpisode.status != EpisodeStatus.UNAIRED
439
440
441
442
443
444
            ).order_by(
                sqlalchemy.desc(MainDB.TVEpisode.airdate)
            ).first()

            if query:
                _airs_prev = query.airdate
445

446
447
        return _airs_prev

448
449
    @property
    def episodes_unaired(self):
450
        with sickrage.app.main_db.session() as session:
451
452
453
            query = session.query(
                MainDB.TVEpisode.season, MainDB.TVEpisode.status
            ).filter_by(
454
                series_id=self.series_id, series_provider_id=self.series_provider_id
455
            ).filter(
456
                MainDB.TVEpisode.status == EpisodeStatus.UNAIRED
457
458
            )

459
            if not sickrage.app.config.gui.display_show_specials:
460
                query = query.filter(MainDB.TVEpisode.season > 0)
461

462
        return query.count()
463

464
465
    @property
    def episodes_snatched(self):
466
        with sickrage.app.main_db.session() as session:
467
468
469
            query = session.query(
                MainDB.TVEpisode.season, MainDB.TVEpisode.status
            ).filter_by(
470
                series_id=self.series_id, series_provider_id=self.series_provider_id
471
            ).filter(
472
473
                MainDB.TVEpisode.status.in_(flatten([EpisodeStatus.composites(EpisodeStatus.SNATCHED), EpisodeStatus.composites(EpisodeStatus.SNATCHED_BEST),
                                                     EpisodeStatus.composites(EpisodeStatus.SNATCHED_PROPER)]))
474
475
            )

476
            if not sickrage.app.config.gui.display_show_specials:
477
                query = query.filter(MainDB.TVEpisode.season > 0)
478

479
        return query.count()
480
481
482

    @property
    def episodes_downloaded(self):
483
        with sickrage.app.main_db.session() as session:
484
485
486
            query = session.query(
                MainDB.TVEpisode.season, MainDB.TVEpisode.status
            ).filter_by(
487
                series_id=self.series_id, series_provider_id=self.series_provider_id
488
            ).filter(
489
                MainDB.TVEpisode.status.in_(flatten([EpisodeStatus.composites(EpisodeStatus.DOWNLOADED), EpisodeStatus.composites(EpisodeStatus.ARCHIVED)]))
490
            )
491

492
            if not sickrage.app.config.gui.display_show_specials:
493
                query = query.filter(MainDB.TVEpisode.season > 0)
494

495
        return query.count()
496
497

    @property
498
    def episodes_special(self):
499
        with sickrage.app.main_db.session() as session:
500
501
502
            query = session.query(
                MainDB.TVEpisode.season
            ).filter_by(
503
                series_id=self.series_id, series_provider_id=self.series_provider_id
504
505
506
            ).filter(
                MainDB.TVEpisode.season == 0
            )
507

508
        return query.count()
509
510

    @property
511
    def episodes_total(self):
512
        with sickrage.app.main_db.session() as session:
513
514
515
            query = session.query(
                MainDB.TVEpisode.season, MainDB.TVEpisode.status
            ).filter_by(
516
                series_id=self.series_id, series_provider_id=self.series_provider_id
517
            ).filter(
518
                MainDB.TVEpisode.status != EpisodeStatus.UNAIRED
519
            )
520

521
            if not sickrage.app.config.gui.display_show_specials:
522
                query = query.filter(MainDB.TVEpisode.season > 0)
523

524
        return query.count()
525

526
527
528
529
530
531
532
    @property
    def new_episodes(self):
        cur_date = datetime.date.today()
        cur_date += datetime.timedelta(days=1)
        cur_time = datetime.datetime.now(sickrage.app.tz)

        new_episodes = []
533
        for episode_object in self.episodes:
534
            if episode_object.status != EpisodeStatus.UNAIRED or episode_object.season == 0 or not episode_object.airdate > datetime.date.min:
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
                continue

            air_date = episode_object.airdate
            air_date += datetime.timedelta(days=episode_object.show.search_delay)
            if not cur_date >= air_date:
                continue

            if episode_object.show.airs and episode_object.show.network:
                # This is how you assure it is always converted to local time
                air_time = sickrage.app.tz_updater.parse_date_time(episode_object.airdate,
                                                                   episode_object.show.airs,
                                                                   episode_object.show.network).astimezone(sickrage.app.tz)

                # filter out any episodes that haven't started airing yet,
                # but set them to the default status while they are airing
                # so they are snatched faster
                if air_time > cur_time:
                    continue

            new_episodes += [episode_object]

        return new_episodes

558
559
560
    @property
    def total_size(self):
        _total_size = 0
561
        _related_episodes = set()
562
        for episode_object in self.episodes:
563
564
            [_related_episodes.add(related_episode.episode_id) for related_episode in episode_object.related_episodes]
            if episode_object.episode_id not in _related_episodes:
565
                _total_size += episode_object.file_size
566
567
        return _total_size

568
569
    @property
    def network_logo_name(self):
echel0n's avatar
echel0n committed
570
        return unidecode(self.network).lower()
571

572
    @property
573
574
    def release_groups(self):
        if self.is_anime:
575
            return BlackAndWhiteList(self.series_id, self.series_provider_id)
576

577
578
    @property
    def poster(self):
579
        return series_image(self.series_id, self.series_provider_id, SeriesImageType.POSTER).url
580
581
582

    @property
    def banner(self):
583
        return series_image(self.series_id, self.series_provider_id, SeriesImageType.BANNER).url
584
585
586
587
588
589
590
591
592
593
594
595
596

    @property
    def allowed_qualities(self):
        allowed_qualities, __ = Quality.split_quality(self.quality)
        return allowed_qualities

    @property
    def preferred_qualities(self):
        __, preferred_qualities = Quality.split_quality(self.quality)
        return preferred_qualities

    @property
    def show_queue_status(self):
597
        if sickrage.app.show_queue.is_being_added(self.series_id):
598
599
600
601
            return {
                'action': 'ADD',
                'message': _('This show is in the process of being downloaded - the info below is incomplete.')
            }
602
        elif sickrage.app.show_queue.is_being_removed(self.series_id):
603
604
605
606
            return {
                'action': 'REMOVE',
                'message': _('This show is in the process of being removed.')
            }
607
        elif sickrage.app.show_queue.is_being_updated(self.series_id):
608
609
610
611
            return {
                'action': 'UPDATE',
                'message': _('The information on this page is in the process of being updated.')
            }
612
        elif sickrage.app.show_queue.is_being_refreshed(self.series_id):
613
614
615
616
            return {
                'action': 'REFRESH',
                'message': _('The episodes below are currently being refreshed from disk')
            }
617
        elif sickrage.app.show_queue.is_being_subtitled(self.series_id):
618
619
620
621
            return {
                'action': 'SUBTITLE',
                'message': _('Currently downloading subtitles for this show')
            }
622
        elif sickrage.app.show_queue.is_queued_to_refresh(self.series_id):
623
624
625
626
            return {
                'action': 'REFRESH_QUEUE',
                'message': _('This show is queued to be refreshed.')
            }
627
        elif sickrage.app.show_queue.is_queued_to_update(self.series_id):
628
629
630
631
            return {
                'action': 'UPDATE_QUEUE',
                'message': _('This show is queued and awaiting an update.')
            }
632
        elif sickrage.app.show_queue.is_queued_to_subtitle(self.series_id):
633
634
635
636
637
            return {
                'action': 'SUBTITLE_QUEUE',
                'message': _('This show is queued and awaiting subtitles download.')
            }

638
639
640
641
642
643
644
645
646
647
        return {}

    @property
    def is_loading(self):
        return self.show_queue_status.get('action') == 'ADD'

    @property
    def is_removing(self):
        return self.show_queue_status.get('action') == 'REMOVE'

648
649
650
651
    @property
    def is_loading_episodes(self):
        return self.loading_episodes

652
    def save(self):
653
        with self.lock, sickrage.app.main_db.session() as session:
654
            sickrage.app.log.debug("{0:d}: Saving to database: {1}".format(self.series_id, self.name))
655
656

            try:
657
                query = session.query(MainDB.TVShow).filter_by(series_id=self.series_id, series_provider_id=self.series_provider_id).one()
658
                query.update(**self._data_local)
659
660
661
662
            except orm.exc.NoResultFound:
                session.add(MainDB.TVShow(**self._data_local))
            finally:
                session.commit()
663

664
    def delete(self):
665
        with self.lock, sickrage.app.main_db.session() as session:
666
            session.query(MainDB.TVShow).filter_by(series_id=self.series_id, series_provider_id=self.series_provider_id).delete()
667
            session.commit()
668

669
    def flush_episodes(self):
670
        self._episodes.clear()
671

672
673
    def load_from_series_provider(self, cache=True):
        sickrage.app.log.debug(str(self.series_id) + ": Loading show info from " + self.series_provider.name)
echel0n's avatar
echel0n committed
674

675
        series_provider_language = self.lang or sickrage.app.config.general.series_provider_default_language
676
677
        series_info = self.series_provider.get_series_info(self.series_id, language=series_provider_language, enable_cache=cache)
        if not series_info:
678
            raise SeriesProviderException
echel0n's avatar
echel0n committed
679

680
        try:
681
            self.name = series_info['name'].strip()
682
        except AttributeError:
683
            raise SeriesProviderAttributeNotFound("Found %s, but attribute 'name' was empty." % self.series_id)
echel0n's avatar
echel0n committed
684

685
686
687
688
689
690
        self.overview = safe_getattr(series_info, 'overview', self.overview)
        # self.classification = safe_getattr(series_info, 'classification', self.classification)
        self.genre = safe_getattr(series_info, 'genre', self.genre)
        self.network = safe_getattr(series_info, 'network', self.network)
        self.runtime = try_int(safe_getattr(series_info, 'runtime', self.runtime))
        self.imdb_id = safe_getattr(series_info, 'imdbId', self.imdb_id)
echel0n's avatar
echel0n committed
691

692
        try:
693
            self.airs = f"{safe_getattr(series_info, 'airDay')} {safe_getattr(series_info, 'airTime')}"
694
695
        except:
            self.airs = ''
echel0n's avatar
echel0n committed
696

697
        try:
698
            self.startyear = try_int(str(safe_getattr(series_info, 'firstAired') or datetime.date.min).split('-')[0])
699
700
        except:
            self.startyear = 0
echel0n's avatar
echel0n committed
701

702
        self.status = safe_getattr(series_info, 'status', self.status)
703

704
        self.save()
echel0n's avatar
echel0n committed
705

706
    def load_episodes_from_series_provider(self, cache=True):
707
708
        scanned_eps = {}

709
710
        self.loading_episodes = True

711
        sickrage.app.log.debug(str(self.series_id) + ": Loading all episodes from " + self.series_provider.name + "..")
712
713
714

        # flush episodes from cache so we can reload from database
        self.flush_episodes()
715

716
717
718
        series_provider_language = self.lang or sickrage.app.config.general.series_provider_default_language
        series_info = self.series_provider.get_series_info(self.series_id, language=series_provider_language, enable_cache=cache)
        if not series_info:
719
            self.loading_episodes = False
720
            raise SeriesProviderException
721

722
        for season in series_info:
723
            scanned_eps[season] = {}
724
            for episode in series_info[season]:
725
726
727
728
729
                # need some examples of wtf episode 0 means to decide if we want it or not
                if episode == 0:
                    continue

                try:
730
                    episode_obj = self.get_episode(season, episode)
731
                except EpisodeNotFoundException:
732
                    continue
733
734
                else:
                    try:
735
                        episode_obj.load_from_series_provider(season, episode)
736
                        episode_obj.save()
737
738
739
                    except EpisodeDeletedException:
                        sickrage.app.log.info("The episode was deleted, skipping the rest of the load")
                        continue
740
741
742
743

                scanned_eps[season][episode] = True

        # Done updating save last update date
744
        self.last_update = datetime.datetime.now()
745

746
        self.save()
747

748
749
        self.loading_episodes = False

750
        return scanned_eps
echel0n's avatar
echel0n committed
751

752
    def get_episode(self, season=None, episode=None, absolute_number=None, location=None, no_create=False):
753
        try:
754
            if season is None and episode is None and absolute_number is not None:
755
                with sickrage.app.main_db.session() as session:
756
757
                    query = session.query(MainDB.TVEpisode).filter_by(series_id=self.series_id, series_provider_id=self.series_provider_id,
                                                                      absolute_number=absolute_number).one()
758
759
760
                    sickrage.app.log.debug("Found episode by absolute_number %s which is S%02dE%02d" % (absolute_number, query.season, query.episode))
                    season = query.season
                    episode = query.episode
761

762
            for tv_episode in self.episodes:
763
764
765
                if tv_episode.season == season and tv_episode.episode == episode:
                    return tv_episode
            else:
766
                if no_create:
767
                    return None
768
769
770
771
772
773
774
775

                tv_episode = TVEpisode(series_id=self.series_id,
                                       series_provider_id=self.series_provider_id,
                                       season=season,
                                       episode=episode,
                                       location=location or '')

                self._episodes[tv_episode.episode_id] = tv_episode
776
                return tv_episode
777
778
        except orm.exc.MultipleResultsFound:
            if absolute_number is not None:
779
                sickrage.app.log.debug("Multiple entries for absolute number: " + str(absolute_number) + " in show: " + self.name + " found ")
780
781
782
783
784
            raise MultipleEpisodesInDatabaseException
        except orm.exc.NoResultFound:
            if absolute_number is not None:
                sickrage.app.log.debug("No entries for absolute number: " + str(absolute_number) + " in show: " + self.name + " found.")
            raise EpisodeNotFoundException
785

786
787
788
789
790
    def delete_episode(self, season, episode, full=False):
        episode_object = self.get_episode(season, episode, no_create=True)
        if not episode_object:
            return

791
792
        data = sickrage.app.notification_providers['trakt'].trakt_episode_data_generate([(episode_object.season, episode_object.episode)])
        if sickrage.app.config.trakt.enable and sickrage.app.config.trakt.sync_watchlist and data:
793
            sickrage.app.log.debug("Deleting episode from Trakt")
794
            sickrage.app.notification_providers['trakt'].update_watchlist(self, data_episode=data, update="remove")
795
796
797
798
799
800
801
802
803
804
805
806

        if full and os.path.isfile(episode_object.location):
            sickrage.app.log.info('Attempt to delete episode file %s' % episode_object.location)

            try:
                os.remove(episode_object.location)
            except OSError as e:
                sickrage.app.log.warning('Unable to delete episode file %s: %s / %s' % (episode_object.location, repr(e), str(e)))

        # delete episode from show episode cache
        sickrage.app.log.debug("Deleting %s S%02dE%02d from the shows episode cache" % (self.name, episode_object.season or 0, episode_object.episode or 0))
        try:
807
            del self._episodes[episode_object.episode_id]
808
809
810
811
812
813
814
815
816
        except KeyError:
            pass

        # delete episode from database
        sickrage.app.log.debug("Deleting %s S%02dE%02d from the DB" % (self.name, episode_object.season or 0, episode_object.episode or 0))
        episode_object.delete()

        raise EpisodeDeletedException()

817
    def write_show_nfo(self, force=False):
818
819
        result = False

820
        if not os.path.isdir(self.location):
821
            sickrage.app.log.info(str(self.series_id) + ": Show dir doesn't exist, skipping NFO generation")
822
823
            return False

824
        sickrage.app.log.debug(str(self.series_id) + ": Writing NFOs for show")
825
        for cur_provider in sickrage.app.metadata_providers.values():
826
            result = cur_provider.create_show_metadata(self, force) or result
827
828
829

        return result

830
    def write_metadata(self, show_only=False, force=False):
831
        if not os.path.isdir(self.location):
832
            sickrage.app.log.info(str(self.series_id) + ": Show dir doesn't exist, skipping NFO generation")
833
834
            return

835
        self.get_images()
836

837
        self.write_show_nfo(force)
838
839

        if not show_only:
840
            self.write_episode_nfos(force)
841
            self.update_episode_video_metadata()
842

843
    def write_episode_nfos(self, force=False):
844
        if not os.path.isdir(self.location):
845
            sickrage.app.log.info(str(self.series_id) + ": Show dir doesn't exist, skipping NFO generation")
846
847
            return

848
        sickrage.app.log.debug(str(self.series_id) + ": Writing NFOs for all episodes")
849

850
        for episode_obj in self.episodes:
851
            if episode_obj.location == '':
852
853
                continue

854
            sickrage.app.log.debug(str(self.series_id) + ": Retrieving/creating episode S%02dE%02d" % (episode_obj.season or 0, episode_obj.episode or 0))
echel0n's avatar
echel0n committed
855

856
            episode_obj.create_meta_files(force)
857

858
859
    def update_episode_video_metadata(self):
        if not os.path.isdir(self.location):
860
            sickrage.app.log.info(str(self.series_id) + ": Show dir doesn't exist, skipping video metadata updating")