__init__.py 21.1 KB
Newer Older
echel0n's avatar
echel0n committed
1
# Author: echel0n <[email protected]>
echel0n's avatar
echel0n committed
2
# URL: https://sickrage.ca
echel0n's avatar
echel0n committed
3
#
echel0n's avatar
echel0n committed
4
# This file is part of SickRage.
echel0n's avatar
echel0n committed
5
#
echel0n's avatar
echel0n committed
6
# SickRage is free software: you can redistribute it and/or modify
echel0n's avatar
echel0n committed
7
8
9
10
# 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
11
# SickRage is distributed in the hope that it will be useful,
echel0n's avatar
echel0n committed
12
13
14
15
16
# 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
echel0n's avatar
echel0n committed
17
# along with SickRage.  If not, see <http://www.gnu.org/licenses/>.
echel0n's avatar
echel0n committed
18
19
20

from __future__ import unicode_literals

echel0n's avatar
echel0n committed
21
import datetime
echel0n's avatar
echel0n committed
22
import os
23
import platform
echel0n's avatar
echel0n committed
24
25
26
import re
import shutil
import socket
27
import sys
28
import threading
29
import time
echel0n's avatar
echel0n committed
30
import traceback
31
32
33
import urllib
import urlparse
import uuid
34

35
from apscheduler.schedulers.tornado import TornadoScheduler
echel0n's avatar
echel0n committed
36
from apscheduler.triggers.interval import IntervalTrigger
37
from dateutil import tz
38
from fake_useragent import UserAgent
echel0n's avatar
echel0n committed
39
from keycloak.realm import KeycloakRealm
40
from tornado.ioloop import IOLoop
echel0n's avatar
echel0n committed
41

echel0n's avatar
echel0n committed
42
import adba
echel0n's avatar
echel0n committed
43
import sickrage
44
from sickrage.core.api import API
45
from sickrage.core.caches.name_cache import NameCache
46
from sickrage.core.caches.quicksearch_cache import QuicksearchCache
47
from sickrage.core.common import SD, SKIPPED, WANTED
48
from sickrage.core.config import Config
49
50
from sickrage.core.databases.cache import CacheDB
from sickrage.core.databases.main import MainDB
echel0n's avatar
echel0n committed
51
from sickrage.core.helpers import findCertainShow, generate_secret, makeDir, get_lan_ip, restoreSR, \
52
    getDiskSpaceUsage, getFreeSpace, launch_browser, torrent_webui_url
53
from sickrage.core.helpers.encoding import get_sys_encoding, ek, patch_modules
54
from sickrage.core.logger import Logger
55
from sickrage.core.nameparser.validator import check_force_season_folders
56
from sickrage.core.processors import auto_postprocessor
57
from sickrage.core.processors.auto_postprocessor import AutoPostProcessor
echel0n's avatar
echel0n committed
58
from sickrage.core.queues.event import EventQueue
59
60
61
62
63
from sickrage.core.queues.postprocessor import PostProcessorQueue
from sickrage.core.queues.search import SearchQueue
from sickrage.core.queues.show import ShowQueue
from sickrage.core.searchers.backlog_searcher import BacklogSearcher
from sickrage.core.searchers.daily_searcher import DailySearcher
echel0n's avatar
echel0n committed
64
from sickrage.core.searchers.failed_snatch_searcher import FailedSnatchSearcher
65
66
67
from sickrage.core.searchers.proper_searcher import ProperSearcher
from sickrage.core.searchers.subtitle_searcher import SubtitleSearcher
from sickrage.core.searchers.trakt_searcher import TraktSearcher
68
from sickrage.core.tv.show import TVShow
69
from sickrage.core.ui import Notifications
70
from sickrage.core.updaters.show_updater import ShowUpdater
echel0n's avatar
echel0n committed
71
from sickrage.core.updaters.tz_updater import TimeZoneUpdater
echel0n's avatar
echel0n committed
72
from sickrage.core.upnp import UPNPClient
73
74
from sickrage.core.version_updater import VersionUpdater
from sickrage.core.webserver import WebServer
75
76
from sickrage.metadata import MetadataProviders
from sickrage.notifiers import NotifierProviders
77
from sickrage.providers import SearchProviders
echel0n's avatar
echel0n committed
78

79

80
81
class Core(object):
    def __init__(self):
82
83
        self.started = False
        self.daemon = None
84
        self.io_loop = IOLoop()
85
        self.pid = os.getpid()
86
        self.showlist = []
87

88
        self.tz = tz.tzwinlocal() if tz.tzwinlocal else tz.tzlocal()
89

90
91
92
        self.config_file = None
        self.data_dir = None
        self.cache_dir = None
echel0n's avatar
echel0n committed
93
        self.quiet = None
94
95
96
97
98
99
100
        self.no_launch = None
        self.web_port = None
        self.developer = None
        self.debug = None
        self.newest_version = None
        self.newest_version_string = None

echel0n's avatar
echel0n committed
101
102
103
        self.naming_ep_type = ("%(seasonnumber)dx%(episodenumber)02d",
                               "s%(seasonnumber)02de%(episodenumber)02d",
                               "S%(seasonnumber)02dE%(episodenumber)02d",
104
105
                               "%(seasonnumber)02dx%(episodenumber)02d",
                               "S%(seasonnumber)02d E%(episodenumber)02d")
echel0n's avatar
echel0n committed
106
107
108
        self.sports_ep_type = ("%(seasonnumber)dx%(episodenumber)02d",
                               "s%(seasonnumber)02de%(episodenumber)02d",
                               "S%(seasonnumber)02dE%(episodenumber)02d",
109
110
111
                               "%(seasonnumber)02dx%(episodenumber)02d",
                               "S%(seasonnumber)02 dE%(episodenumber)02d")
        self.naming_ep_type_text = ("1x02", "s01e02", "S01E02", "01x02", "S01 E02",)
echel0n's avatar
echel0n committed
112
113
114
115
116
117
118
        self.naming_multi_ep_type = {0: ["-%(episodenumber)02d"] * len(self.naming_ep_type),
                                     1: [" - " + x for x in self.naming_ep_type],
                                     2: [x + "%(episodenumber)02d" for x in ("x", "e", "E", "x")]}
        self.naming_multi_ep_type_text = ("extend", "duplicate", "repeat")
        self.naming_sep_type = (" - ", " ")
        self.naming_sep_type_text = (" - ", "space")

119
120
        self.user_agent = 'SiCKRAGE.CE.1/({};{};{})'.format(platform.system(), platform.release(), str(uuid.uuid1()))
        self.languages = [language for language in os.listdir(sickrage.LOCALE_DIR) if '_' in language]
121
        self.sys_encoding = get_sys_encoding()
122
        self.client_web_urls = {'torrent': '', 'newznab': ''}
echel0n's avatar
echel0n committed
123

124
125
        self.adba_connection = None
        self.notifier_providers = None
echel0n's avatar
echel0n committed
126
        self.metadata_providers = {}
127
        self.search_providers = None
128
129
        self.log = None
        self.config = None
130
131
132
133
134
        self.alerts = None
        self.main_db = None
        self.cache_db = None
        self.scheduler = None
        self.wserver = None
135
        self.google_auth = None
136
137
138
139
        self.name_cache = None
        self.show_queue = None
        self.search_queue = None
        self.postprocessor_queue = None
echel0n's avatar
echel0n committed
140
        self.event_queue = None
141
142
        self.version_updater = None
        self.show_updater = None
echel0n's avatar
echel0n committed
143
        self.tz_updater = None
144
145
146
147
148
149
        self.daily_searcher = None
        self.backlog_searcher = None
        self.proper_searcher = None
        self.trakt_searcher = None
        self.subtitle_searcher = None
        self.auto_postprocessor = None
echel0n's avatar
echel0n committed
150
        self.upnp_client = None
echel0n's avatar
echel0n committed
151
        self.oidc_client = None
152
        self.quicksearch_cache = None
echel0n's avatar
echel0n committed
153

154
    def start(self):
155
        self.started = True
156

157
158
        # thread name
        threading.currentThread().setName('CORE')
159

echel0n's avatar
echel0n committed
160
161
162
        # patch modules with encoding kludge
        patch_modules()

163
        # init core classes
164
165
        self.notifier_providers = NotifierProviders()
        self.metadata_providers = MetadataProviders()
166
167
168
169
170
171
        self.search_providers = SearchProviders()
        self.log = Logger()
        self.config = Config()
        self.alerts = Notifications()
        self.main_db = MainDB()
        self.cache_db = CacheDB()
172
        self.scheduler = TornadoScheduler()
173
174
175
176
177
        self.wserver = WebServer()
        self.name_cache = NameCache()
        self.show_queue = ShowQueue()
        self.search_queue = SearchQueue()
        self.postprocessor_queue = PostProcessorQueue()
echel0n's avatar
echel0n committed
178
        self.event_queue = EventQueue()
179
180
        self.version_updater = VersionUpdater()
        self.show_updater = ShowUpdater()
echel0n's avatar
echel0n committed
181
        self.tz_updater = TimeZoneUpdater()
182
        self.daily_searcher = DailySearcher()
echel0n's avatar
echel0n committed
183
        self.failed_snatch_searcher = FailedSnatchSearcher()
184
185
186
187
188
        self.backlog_searcher = BacklogSearcher()
        self.proper_searcher = ProperSearcher()
        self.trakt_searcher = TraktSearcher()
        self.subtitle_searcher = SubtitleSearcher()
        self.auto_postprocessor = AutoPostProcessor()
189
        self.upnp_client = UPNPClient()
190
        self.quicksearch_cache = QuicksearchCache()
191

echel0n's avatar
echel0n committed
192
193
194
195
196
        # setup oidc client
        realm = KeycloakRealm(server_url='https://auth.sickrage.ca', realm_name='sickrage')
        self.oidc_client = realm.open_id_connect(client_id='sickrage-app',
                                                 client_secret='5d4710b2-ca70-4d39-b5a3-0705e2c5e703')

197
        # Check if we need to perform a restore first
198
199
        if os.path.exists(os.path.abspath(os.path.join(self.data_dir, 'restore'))):
            success = restoreSR(os.path.abspath(os.path.join(self.data_dir, 'restore')), self.data_dir)
200
201
            print("Restoring SiCKRAGE backup: %s!\n" % ("FAILED", "SUCCESSFUL")[success])
            if success:
202
                shutil.rmtree(os.path.abspath(os.path.join(self.data_dir, 'restore')), ignore_errors=True)
203
204

        # migrate old database file names to new ones
205
206
207
208
        if os.path.isfile(os.path.abspath(os.path.join(self.data_dir, 'sickbeard.db'))):
            if os.path.isfile(os.path.join(self.data_dir, 'sickrage.db')):
                helpers.moveFile(os.path.join(self.data_dir, 'sickrage.db'),
                                 os.path.join(self.data_dir, '{}.bak-{}'
209
210
211
212
                                              .format('sickrage.db',
                                                      datetime.datetime.now().strftime(
                                                          '%Y%m%d_%H%M%S'))))

213
214
            helpers.moveFile(os.path.abspath(os.path.join(self.data_dir, 'sickbeard.db')),
                             os.path.abspath(os.path.join(self.data_dir, 'sickrage.db')))
215

216
        # load config
217
        self.config.load()
218

219
        # set language
220
        self.config.change_gui_lang(self.config.gui_lang)
221

222
        # set socket timeout
223
        socket.setdefaulttimeout(self.config.socket_timeout)
224

225
        # setup logger settings
226
227
        self.log.logSize = self.config.log_size
        self.log.logNr = self.config.log_nr
228
        self.log.logFile = os.path.join(self.data_dir, 'logs', 'sickrage.log')
229
        self.log.debugLogging = self.config.debug
echel0n's avatar
echel0n committed
230
        self.log.consoleLogging = not self.quiet
231
232

        # start logger
233
        self.log.start()
echel0n's avatar
echel0n committed
234

235
        # user agent
236
        if self.config.random_user_agent:
237
            self.user_agent = UserAgent().random
238
239

        urlparse.uses_netloc.append('scgi')
240
        urllib.FancyURLopener.version = self.user_agent
241

242
243
244
        # set torrent client web url
        torrent_webui_url(True)

echel0n's avatar
echel0n committed
245
246
        # Check available space
        try:
247
            total_space, available_space = getFreeSpace(self.data_dir)
echel0n's avatar
echel0n committed
248
            if available_space < 100:
249
250
                self.log.error('Shutting down as SiCKRAGE needs some space to work. You\'ll get corrupted data '
                               'otherwise. Only %sMB left', available_space)
echel0n's avatar
echel0n committed
251
                return
252
        except Exception:
echel0n's avatar
echel0n committed
253
            self.log.error('Failed getting disk space: %s', traceback.format_exc())
echel0n's avatar
echel0n committed
254

255
        # perform database startup actions
256
        for db in [self.main_db, self.cache_db]:
257
258
259
260
261
262
263
264
265
            # initialize database
            db.initialize()

            # check integrity of database
            db.check_integrity()

            # migrate database
            db.migrate()

266
267
268
            # misc database cleanups
            db.cleanup()

269
270
271
            # upgrade database
            db.upgrade()

272
        # compact main database
273
        if self.config.last_db_compact < time.time() - 604800:  # 7 days
274
            self.main_db.compact()
275
            self.config.last_db_compact = int(time.time())
276

277
        # load name cache
278
        self.name_cache.load()
279

echel0n's avatar
echel0n committed
280
281
        # load data for shows from database
        self.load_shows()
282

283
        if self.config.default_page not in ('schedule', 'history', 'IRC'):
284
            self.config.default_page = 'home'
echel0n's avatar
echel0n committed
285

286
        # cleanup cache folder
echel0n's avatar
echel0n committed
287
        for folder in ['mako', 'sessions', 'indexers']:
288
            try:
289
                shutil.rmtree(os.path.join(sickrage.app.cache_dir, folder), ignore_errors=True)
290
291
            except Exception:
                continue
echel0n's avatar
echel0n committed
292

293
        # init anidb connection
294
        if self.config.use_anidb:
295
            def anidb_logger(msg):
296
                return self.log.debug("AniDB: {} ".format(msg))
297

298
            try:
299
                self.adba_connection = adba.Connection(keepAlive=True, log=anidb_logger)
300
                self.adba_connection.auth(self.config.anidb_username, self.config.anidb_password)
301
            except Exception as e:
302
                self.log.warning("AniDB exception msg: %r " % repr(e))
303

304
305
        if self.config.web_port < 21 or self.config.web_port > 65535:
            self.config.web_port = 8081
echel0n's avatar
echel0n committed
306

307
        if not self.config.web_cookie_secret:
echel0n's avatar
echel0n committed
308
            self.config.web_cookie_secret = generate_secret()
echel0n's avatar
echel0n committed
309
310

        # attempt to help prevent users from breaking links by using a bad url
311
312
        if not self.config.anon_redirect.endswith('?'):
            self.config.anon_redirect = ''
313

314
315
        if not re.match(r'\d+\|[^|]+(?:\|[^|]+)*', self.config.root_dirs):
            self.config.root_dirs = ''
316

317
        self.config.naming_force_folders = check_force_season_folders()
318

319
320
        if self.config.nzb_method not in ('blackhole', 'sabnzbd', 'nzbget'):
            self.config.nzb_method = 'blackhole'
321

322
        if self.config.torrent_method not in ('blackhole', 'utorrent', 'transmission', 'deluge', 'deluged',
323
                                              'download_station', 'rtorrent', 'qbittorrent', 'mlnet', 'putio'):
324
325
326
327
328
329
330
331
332
333
334
335
            self.config.torrent_method = 'blackhole'

        if self.config.autopostprocessor_freq < self.config.min_autopostprocessor_freq:
            self.config.autopostprocessor_freq = self.config.min_autopostprocessor_freq
        if self.config.daily_searcher_freq < self.config.min_daily_searcher_freq:
            self.config.daily_searcher_freq = self.config.min_daily_searcher_freq
        if self.config.backlog_searcher_freq < self.config.min_backlog_searcher_freq:
            self.config.backlog_searcher_freq = self.config.min_backlog_searcher_freq
        if self.config.version_updater_freq < self.config.min_version_updater_freq:
            self.config.version_updater_freq = self.config.min_version_updater_freq
        if self.config.subtitle_searcher_freq < self.config.min_subtitle_searcher_freq:
            self.config.subtitle_searcher_freq = self.config.min_subtitle_searcher_freq
echel0n's avatar
echel0n committed
336
337
        if self.config.failed_snatch_age < self.config.min_failed_snatch_age:
            self.config.failed_snatch_age = self.config.min_failed_snatch_age
338
339
340
341
        if self.config.proper_searcher_interval not in ('15m', '45m', '90m', '4h', 'daily'):
            self.config.proper_searcher_interval = 'daily'
        if self.config.showupdate_hour < 0 or self.config.showupdate_hour > 23:
            self.config.showupdate_hour = 0
echel0n's avatar
echel0n committed
342

343
        # add version checker job
344
345
        self.scheduler.add_job(
            self.version_updater.run,
echel0n's avatar
echel0n committed
346
            IntervalTrigger(
echel0n's avatar
echel0n committed
347
                hours=self.config.version_updater_freq,
echel0n's avatar
echel0n committed
348
            ),
349
350
            name=self.version_updater.name,
            id=self.version_updater.name
351
        )
echel0n's avatar
echel0n committed
352

353
        # add network timezones updater job
354
        self.scheduler.add_job(
echel0n's avatar
echel0n committed
355
            self.tz_updater.run,
echel0n's avatar
echel0n committed
356
            IntervalTrigger(
echel0n's avatar
echel0n committed
357
                days=1,
echel0n's avatar
echel0n committed
358
            ),
echel0n's avatar
echel0n committed
359
360
            name=self.tz_updater.name,
            id=self.tz_updater.name
361
        )
echel0n's avatar
echel0n committed
362

363
        # add show updater job
364
365
        self.scheduler.add_job(
            self.show_updater.run,
echel0n's avatar
echel0n committed
366
367
            IntervalTrigger(
                days=1,
368
                start_date=datetime.datetime.now().replace(hour=self.config.showupdate_hour)
echel0n's avatar
echel0n committed
369
            ),
370
371
            name=self.show_updater.name,
            id=self.show_updater.name
echel0n's avatar
echel0n committed
372
373
        )

374
        # add daily search job
375
376
        self.scheduler.add_job(
            self.daily_searcher.run,
echel0n's avatar
echel0n committed
377
            IntervalTrigger(
378
                minutes=self.config.daily_searcher_freq,
379
                start_date=datetime.datetime.now() + datetime.timedelta(minutes=4)
echel0n's avatar
echel0n committed
380
            ),
381
382
383
384
            name=self.daily_searcher.name,
            id=self.daily_searcher.name
        )

echel0n's avatar
echel0n committed
385
        # add failed snatch search job
386
        self.scheduler.add_job(
echel0n's avatar
echel0n committed
387
            self.failed_snatch_searcher.run,
388
389
390
391
            IntervalTrigger(
                hours=1,
                start_date=datetime.datetime.now() + datetime.timedelta(minutes=4)
            ),
echel0n's avatar
echel0n committed
392
393
            name=self.failed_snatch_searcher.name,
            id=self.failed_snatch_searcher.name
echel0n's avatar
echel0n committed
394
395
        )

396
        # add backlog search job
397
398
        self.scheduler.add_job(
            self.backlog_searcher.run,
echel0n's avatar
echel0n committed
399
            IntervalTrigger(
400
                minutes=self.config.backlog_searcher_freq,
401
                start_date=datetime.datetime.now() + datetime.timedelta(minutes=30)
echel0n's avatar
echel0n committed
402
            ),
403
404
            name=self.backlog_searcher.name,
            id=self.backlog_searcher.name
echel0n's avatar
echel0n committed
405
406
        )

407
        # add auto-postprocessing job
408
409
        self.scheduler.add_job(
            self.auto_postprocessor.run,
echel0n's avatar
echel0n committed
410
            IntervalTrigger(
411
                minutes=self.config.autopostprocessor_freq
echel0n's avatar
echel0n committed
412
            ),
413
414
            name=self.auto_postprocessor.name,
            id=self.auto_postprocessor.name
echel0n's avatar
echel0n committed
415
416
        )

417
        # add find proper job
418
419
        self.scheduler.add_job(
            self.proper_searcher.run,
echel0n's avatar
echel0n committed
420
            IntervalTrigger(
echel0n's avatar
echel0n committed
421
422
423
424
425
426
427
                minutes={
                    '15m': 15,
                    '45m': 45,
                    '90m': 90,
                    '4h': 4 * 60,
                    'daily': 24 * 60
                }[self.config.proper_searcher_interval]
echel0n's avatar
echel0n committed
428
            ),
429
430
            name=self.proper_searcher.name,
            id=self.proper_searcher.name
echel0n's avatar
echel0n committed
431
432
        )

433
        # add trakt.tv checker job
434
435
        self.scheduler.add_job(
            self.trakt_searcher.run,
echel0n's avatar
echel0n committed
436
437
438
            IntervalTrigger(
                hours=1
            ),
439
440
            name=self.trakt_searcher.name,
            id=self.trakt_searcher.name
echel0n's avatar
echel0n committed
441
442
        )

443
        # add subtitles finder job
444
445
        self.scheduler.add_job(
            self.subtitle_searcher.run,
echel0n's avatar
echel0n committed
446
            IntervalTrigger(
447
                hours=self.config.subtitle_searcher_freq
echel0n's avatar
echel0n committed
448
            ),
449
450
            name=self.subtitle_searcher.name,
            id=self.subtitle_searcher.name
echel0n's avatar
echel0n committed
451
452
        )

453
454
455
456
457
458
459
460
461
462
        # add upnp client job
        self.scheduler.add_job(
            self.upnp_client.run,
            IntervalTrigger(
                seconds=self.upnp_client._nat_portmap_lifetime
            ),
            name=self.upnp_client.name,
            id=self.upnp_client.name
        )

463
        # start scheduler service
464
        self.scheduler.start()
465

echel0n's avatar
echel0n committed
466
        # start queue's
467
468
469
        self.search_queue.start()
        self.show_queue.start()
        self.postprocessor_queue.start()
echel0n's avatar
echel0n committed
470
        self.event_queue.start()
echel0n's avatar
echel0n committed
471

472
        # start webserver
473
        self.wserver.start()
474

echel0n's avatar
echel0n committed
475
476
477
478
        # fire off startup events
        self.event_queue.fire_event(self.version_updater.run)
        self.event_queue.fire_event(self.tz_updater.run)

echel0n's avatar
echel0n committed
479
480
481
        # start ioloop
        self.io_loop.start()

482
    def shutdown(self, restart=False):
483
        if self.started:
484
            self.log.info('SiCKRAGE IS SHUTTING DOWN!!!')
485

echel0n's avatar
echel0n committed
486
            # shutdown webserver
echel0n's avatar
echel0n committed
487
488
            if self.wserver:
                self.wserver.shutdown()
echel0n's avatar
echel0n committed
489

echel0n's avatar
echel0n committed
490
            # shutdown show queue
491
492
493
494
            if self.show_queue:
                self.log.debug("Shutting down show queue")
                self.show_queue.shutdown()
                del self.show_queue
echel0n's avatar
echel0n committed
495

echel0n's avatar
echel0n committed
496
            # shutdown search queue
497
498
499
500
            if self.search_queue:
                self.log.debug("Shutting down search queue")
                self.search_queue.shutdown()
                del self.search_queue
501

echel0n's avatar
echel0n committed
502
            # shutdown post-processor queue
503
504
505
506
            if self.postprocessor_queue:
                self.log.debug("Shutting down post-processor queue")
                self.postprocessor_queue.shutdown()
                del self.postprocessor_queue
echel0n's avatar
echel0n committed
507

echel0n's avatar
echel0n committed
508
509
510
511
512
513
            # shutdown event queue
            if self.event_queue:
                self.log.debug("Shutting down event queue")
                self.event_queue.shutdown()
                del self.event_queue

echel0n's avatar
echel0n committed
514
            # log out of ADBA
515
516
517
            if self.adba_connection:
                self.log.debug("Shutting down ANIDB connection")
                self.adba_connection.stop()
echel0n's avatar
echel0n committed
518

echel0n's avatar
echel0n committed
519
520
            # save all show and config settings
            self.save_all()
521

522
523
524
525
526
            # close databases
            for db in [self.main_db, self.cache_db]:
                if db.opened:
                    self.log.debug("Shutting down {} database connection".format(db.name))
                    db.close()
527

echel0n's avatar
echel0n committed
528
            # shutdown logging
529
530
            if self.log:
                self.log.close()
531

echel0n's avatar
echel0n committed
532
533
        if restart:
            os.execl(sys.executable, sys.executable, *sys.argv)
534
535

        if sickrage.app.daemon:
536
            sickrage.app.daemon.stop()
echel0n's avatar
echel0n committed
537

538
        self.started = False
539

echel0n's avatar
echel0n committed
540
541
        self.io_loop.stop()

echel0n's avatar
echel0n committed
542
543
    def save_all(self):
        # write all shows
544
        self.log.info("Saving all shows to the database")
545
        for show in self.showlist:
546
            try:
547
                show.saveToDB()
548
            except Exception:
549
                continue
echel0n's avatar
echel0n committed
550
551

        # save config
552
        self.config.save()
553

echel0n's avatar
echel0n committed
554
555
    def load_shows(self):
        """
556
        Populates the showlist and quicksearch cache with shows and episodes from the database
echel0n's avatar
echel0n committed
557
558
        """

559
560
        self.quicksearch_cache.load()

561
        for dbData in self.main_db.all('tv_shows'):
562
563
            show = TVShow(int(dbData['indexer']), int(dbData['indexer_id']))

echel0n's avatar
echel0n committed
564
            try:
565
566
567
                self.log.debug("Loading data for show: [{}]".format(show.name))
                self.showlist.append(show)
                self.quicksearch_cache.add_show(show.indexerid)
echel0n's avatar
echel0n committed
568
            except Exception as e:
echel0n's avatar
echel0n committed
569
                self.log.debug("Show error in [%s]: %s" % (show.location, str(e)))