def _processUpgrade(connection, upgradeClass): instance = upgradeClass(connection) logger.log(u'Checking %s database upgrade' % prettyName(upgradeClass.__name__), logger.DEBUG) if not instance.test(): logger.log(u'Database upgrade required: %s' % prettyName(upgradeClass.__name__), logger.MESSAGE) try: instance.execute() except sqlite3.DatabaseError as e: # attemping to restore previous DB backup and perform upgrade try: instance.execute() except: result = connection.select('SELECT db_version FROM db_version') if result: version = int(result[0]['db_version']) # close db before attempting restore connection.close() if restoreDatabase(connection.filename, version): logger.log_error_and_exit(u'Successfully restored database version: %s' % version) else: logger.log_error_and_exit(u'Failed to restore database version: %s' % version) logger.log('%s upgrade completed' % upgradeClass.__name__, logger.DEBUG) else: logger.log('%s upgrade not required' % upgradeClass.__name__, logger.DEBUG) for upgradeSubClass in upgradeClass.__subclasses__(): _processUpgrade(connection, upgradeSubClass)
def migrate_config(self): """ Calls each successive migration until the config is the same version as SG expects """ if self.config_version > self.expected_config_version: logger.log_error_and_exit( u'Your config version (%s) has been incremented past what this version of SickGear supports (%s).\n' 'If you have used other forks or a newer version of SickGear, your config file may be unusable due to ' 'their modifications.' % (self.config_version, self.expected_config_version)) sickbeard.CONFIG_VERSION = self.config_version while self.config_version < self.expected_config_version: next_version = self.config_version + 1 if next_version in self.migration_names: migration_name = ': %s' % self.migration_names[next_version] else: migration_name = '' logger.log(u'Backing up config before upgrade') if not helpers.backupVersionedFile(sickbeard.CONFIG_FILE, self.config_version): logger.log_error_and_exit(u'Config backup failed, abort upgrading config') else: logger.log(u'Proceeding with upgrade') # do the migration, expect a method named _migrate_v<num> logger.log(u'Migrating config up to version %s %s' % (next_version, migration_name)) getattr(self, '_migrate_v%s' % next_version)() self.config_version = next_version # save new config after migration sickbeard.CONFIG_VERSION = self.config_version logger.log(u'Saving config file to disk') sickbeard.save_config()
def daemonize(): try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: print "fork() failed" sys.exit(1) os.chdir(sickbeard.PROG_DIR) os.setsid() # Make sure I can read my own files and shut out others prev = os.umask(0) os.umask(prev and int('077', 8)) try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: print "fork() failed" sys.exit(1) # Write pid if sickbeard.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID: " + pid + " to " + str(sickbeard.PIDFILE)) try: file(sickbeard.PIDFILE, 'w').write("%s\n" % pid) except IOError, e: logger.log_error_and_exit(u"Unable to write PID file: " + sickbeard.PIDFILE + " Error: " + str(e.strerror) + " [" + str(e.errno) + "]")
def restoreDatabase(version): logger.log(u"Restoring database before trying upgrade again") if not sickbeard.helpers.restoreVersionedFile(dbFilename(suffix='v' + str(version)), version): logger.log_error_and_exit(u"Database restore failed, abort upgrading database") return False else: return True
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=no-member,protected-access # An object is accessed for a non-existent member. # Access to a protected member of a client class # Make a non-session-leader child process try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as error_message: sys.stderr.write("fork #1 failed: %d (%s)\n" % (error_message.errno, error_message.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX # https://github.com/SickRage/sickrage-issues/issues/2969 # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920 # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html # Previous code simply set the umask to whatever it was because it was ANDing instead of OR-ing # Daemons traditionally run with umask 0 anyways and this should not have repercussions os.umask(0) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as error_message: sys.stderr.write("fork #2 failed: %d (%s)\n" % (error_message.errno, error_message.strerror)) sys.exit(1) # Write pid if self.create_pid: pid = os.getpid() logger.log("Writing PID: %s to %s" % (pid, self.pid_file)) try: with io.open(self.pid_file, "w") as f_pid: f_pid.write("%s\n" % pid) except EnvironmentError as error_message: logger.log_error_and_exit( "Unable to write PID file: %s Error: %s [%s]" % (self.pid_file, error_message.strerror, error_message.errno) ) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, "devnull", "/dev/null") stdin = file(devnull) stdout = file(devnull, "a+") stderr = file(devnull, "a+") os.dup2(stdin.fileno(), getattr(sys.stdin, "device", sys.stdin).fileno()) os.dup2(stdout.fileno(), getattr(sys.stdout, "device", sys.stdout).fileno()) os.dup2(stderr.fileno(), getattr(sys.stderr, "device", sys.stderr).fileno())
def execute(self): if not self.hasTable("tv_shows") and not self.hasTable("db_version"): queries = [ "CREATE TABLE db_version (db_version INTEGER);", "CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT);", "CREATE TABLE info (last_backlog NUMERIC, last_tvdb NUMERIC);", "CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, tvdbid NUMERIC, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT);", "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, tvr_id NUMERIC, tvr_name TEXT, air_by_date NUMERIC, lang TEXT, last_update_tvdb NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT);", "CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes (showid,airdate);", "CREATE INDEX idx_showid ON tv_episodes (showid);", "CREATE UNIQUE INDEX idx_tvdb_id ON tv_shows (tvdb_id);", "INSERT INTO db_version (db_version) VALUES (15);" ] for query in queries: self.connection.action(query) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit(u"Your database version (" + str(cur_db_version) + ") is too old to migrate from what this version of Sick Beard supports (" + \ str(MIN_DB_VERSION) + ").\n" + \ "Upgrade using a previous version (tag) build 496 to build 501 of Sick Beard first or remove database file to begin fresh." ) if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit(u"Your database version (" + str(cur_db_version) + ") has been incremented past what this version of Sick Beard supports (" + \ str(MAX_DB_VERSION) + ").\n" + \ "If you have used other forks of Sick Beard, your database may be unusable due to their modifications." )
def migrate_config(self): """ Calls each successive migration until the config is the same version as SB expects """ if self.config_version > self.expected_config_version: logger.log_error_and_exit(u"Your config version (" + str(self.config_version) + ") has been incremented past what this version of Sick Beard supports (" + str(self.expected_config_version) + ").\n" + \ "If you have used other forks or a newer version of Sick Beard, your config file may be unusable due to their modifications.") sickbeard.CONFIG_VERSION = self.config_version while self.config_version < self.expected_config_version: next_version = self.config_version + 1 if next_version in self.migration_names: migration_name = ': ' + self.migration_names[next_version] else: migration_name = '' logger.log(u"Backing up config before upgrade") if not helpers.backupVersionedFile(sickbeard.CONFIG_FILE, self.config_version): logger.log_error_and_exit(u"Config backup failed, abort upgrading config") else: logger.log(u"Proceeding with upgrade") # do the migration, expect a method named _migrate_v<num> logger.log(u"Migrating config up to version " + str(next_version) + migration_name) getattr(self, '_migrate_v' + str(next_version))() self.config_version = next_version # save new config after migration sickbeard.CONFIG_VERSION = self.config_version logger.log(u"Saving config file to disk") sickbeard.save_config()
def backupDatabase(version): logger.log(u"Backing up database before upgrade") if not helpers.backupVersionedFile(db.dbFilename(), version): logger.log_error_and_exit( u"Database backup failed, abort upgrading database") else: logger.log(u"Proceeding with upgrade")
def daemonize(): try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: print "fork() failed" sys.exit(1) os.chdir(sickbeard.PROG_DIR) os.setsid() # Make sure I can read my own files and shut out others prev= os.umask(0) os.umask(prev and int('077',8)) try: pid = os.fork() if pid > 0: sys.exit(0) except OSError: print "fork() failed" sys.exit(1) # Write pid if sickbeard.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID: " + pid + " to " + str(sickbeard.PIDFILE)) try: file(sickbeard.PIDFILE, 'w').write("%s\n" % pid) except IOError, e: logger.log_error_and_exit( u"Unable to write PID file: " + sickbeard.PIDFILE + " Error: " + str(e.strerror) + " [" + str( e.errno) + "]")
def backupDatabase(version): logger.log(u"Backing up database before upgrade") if not helpers.backupVersionedFile(db.dbFilename(), version): logger.log_error_and_exit(u"Database backup failed, abort upgrading database") else: logger.log(u"Proceeding with upgrade")
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=protected-access # An object is accessed for a non-existent member. # Access to a protected member of a client class # Make a non-session-leader child process try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as error: sys.stderr.write('fork #1 failed: {error_num}: {error_message}\n'.format (error_num=error.errno, error_message=error.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX # https://github.com/SickRage/SickRage/issues/2969 # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920 # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html # Previous code simply set the umask to whatever it was because it was ANDing instead of OR-ing # Daemons traditionally run with umask 0 anyways and this should not have repercussions os.umask(0) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as error: sys.stderr.write('fork #2 failed: Error {error_num}: {error_message}\n'.format (error_num=error.errno, error_message=error.strerror)) sys.exit(1) # Write pid if self.create_pid: pid = os.getpid() logger.log('Writing PID: {pid} to {filename}'.format(pid=pid, filename=self.pid_file)) try: with io.open(self.pid_file, 'w') as f_pid: f_pid.write('{0}\n'.format(pid)) except EnvironmentError as error: logger.log_error_and_exit('Unable to write PID file: {filename} Error {error_num}: {error_message}'.format (filename=self.pid_file, error_num=error.errno, error_message=error.strerror)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull) stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), getattr(sys.stdin, 'device', sys.stdin).fileno()) os.dup2(stdout.fileno(), getattr(sys.stdout, 'device', sys.stdout).fileno()) os.dup2(stderr.fileno(), getattr(sys.stderr, 'device', sys.stderr).fileno())
def restoreDatabase(filename, version): logger.log(u'Restoring database before trying upgrade again') if not sickbeard.helpers.restoreVersionedFile(dbFilename(filename=filename, suffix='v%s' % version), version): logger.log_error_and_exit(u'Database restore failed, abort upgrading database') return False else: return True
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=protected-access # An object is accessed for a non-existent member. # Access to a protected member of a client class # Make a non-session-leader child process try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as error: sys.stderr.write('fork #1 failed: {error_num}: {error_message}\n'.format (error_num=error.errno, error_message=error.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX # https://github.com/SickRage/sickrage-issues/issues/2969 # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920 # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html # Previous code simply set the umask to whatever it was because it was ANDing instead of OR-ing # Daemons traditionally run with umask 0 anyways and this should not have repercussions os.umask(0) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as error: sys.stderr.write('fork #2 failed: Error {error_num}: {error_message}\n'.format (error_num=error.errno, error_message=error.strerror)) sys.exit(1) # Write pid if self.create_pid: pid = os.getpid() logger.log('Writing PID: {pid} to {filename}'.format(pid=pid, filename=self.pid_file)) try: with io.open(self.pid_file, 'w') as f_pid: f_pid.write('{0!s}\n'.format(pid)) except EnvironmentError as error: logger.log_error_and_exit('Unable to write PID file: {filename} Error {error_num}: {error_message}'.format (filename=self.pid_file, error_num=error.errno, error_message=error.strerror)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull) stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), getattr(sys.stdin, 'device', sys.stdin).fileno()) os.dup2(stdout.fileno(), getattr(sys.stdout, 'device', sys.stdout).fileno()) os.dup2(stderr.fileno(), getattr(sys.stderr, 'device', sys.stderr).fileno())
def execute(self): if not self.hasTable("tv_shows") and not self.hasTable("db_version"): queries = [ "CREATE TABLE db_version (db_version INTEGER);", "CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT, source TEXT);", "CREATE TABLE info (last_backlog NUMERIC, last_tvdb NUMERIC);", "CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, tvdbid NUMERIC, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT);", "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, location TEXT, show_name TEXT, tvdb_id NUMERIC, network TEXT, genre TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, tvr_id NUMERIC, tvr_name TEXT, air_by_date NUMERIC, lang TEXT, last_update_tvdb NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT, skip_notices NUMERIC);", "CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes (showid,airdate);", "CREATE INDEX idx_showid ON tv_episodes (showid);", "CREATE UNIQUE INDEX idx_tvdb_id ON tv_shows (tvdb_id);", "INSERT INTO db_version (db_version) VALUES (18);" ] for query in queries: self.connection.action(query) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit(u"Your database version (" + str(cur_db_version) + ") is too old to migrate from what this version of Sick Beard supports (" + \ str(MIN_DB_VERSION) + ").\n" + \ "Upgrade using a previous version (tag) build 496 to build 501 of Sick Beard first or remove database file to begin fresh." ) if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit(u"Your database version (" + str(cur_db_version) + ") has been incremented past what this version of Sick Beard supports (" + \ str(MAX_DB_VERSION) + ").\n" + \ "If you have used other forks of Sick Beard, your database may be unusable due to their modifications." )
def execute(self): db.backup_database('sickbeard.db', self.checkDBVersion()) if not self.hasTable('tv_shows') and not self.hasTable('db_version'): queries = [ 'CREATE TABLE db_version (db_version INTEGER);', 'CREATE TABLE history (' 'action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC,' ' quality NUMERIC, resource TEXT, provider TEXT);', 'CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT,' ' year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT,' ' certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC);', 'CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC);', 'CREATE TABLE scene_numbering(indexer TEXT, indexer_id INTEGER,' ' season INTEGER, episode INTEGER,scene_season INTEGER, scene_episode INTEGER,' ' PRIMARY KEY(indexer_id, season, episode));', 'CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer NUMERIC,' ' show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC,' ' quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC,' ' startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT,' ' imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC, archive_firstmatch NUMERIC,' ' rls_require_words TEXT, rls_ignore_words TEXT, sports NUMERIC);', 'CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC,' ' indexerid NUMERIC, indexer NUMERIC, name TEXT, season NUMERIC, episode NUMERIC,' ' description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC,' ' location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC,' ' subtitles_lastsearch TIMESTAMP, is_proper NUMERIC, scene_season NUMERIC, scene_episode NUMERIC);', 'CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id);', 'CREATE INDEX idx_showid ON tv_episodes (showid);', 'CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate);', 'CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate);', 'CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate);', 'CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate);', 'INSERT INTO db_version (db_version) VALUES (31);' ] for query in queries: self.connection.action(query) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit( u'Your database version (' + str(cur_db_version) + ') is too old to migrate from what this version of SickGear supports (' + str(MIN_DB_VERSION) + ').' + '\n' + 'Upgrade using a previous version (tag) build 496 to build 501 of SickGear first or' ' remove database file to begin fresh.') if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit( u'Your database version (' + str(cur_db_version) + ') has been incremented past what this version of SickGear supports (' + str(MAX_DB_VERSION) + ').' + '\n' + 'If you have used other forks of SickGear,' ' your database may be unusable due to their modifications.' ) return self.checkDBVersion()
def backup_database(filename, version): logger.log(u'Backing up database before upgrade') if not sickbeard.helpers.backupVersionedFile(dbFilename(filename), version): logger.log_error_and_exit( u'Database backup failed, abort upgrading database') else: logger.log(u'Proceeding with upgrade')
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=no-member,protected-access # An object is accessed for a non-existent member. # Access to a protected member of a client class # Make a non-session-leader child process try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as e: sys.stderr.write(u"fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX # https://github.com/SickRage/sickrage-issues/issues/2969 # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920 # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html # Previous code simply set the umask to whatever it was because it was ANDing instead of ORring # Daemons traditionally run with umask 0 anyways and this should not have repercussions os.umask(0) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as e: sys.stderr.write(u"fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # Write pid if self.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID: " + pid + " to " + str(self.PIDFILE)) try: file(self.PIDFILE, 'w').write("%s\n" % pid) except IOError as e: logger.log_error_and_exit( u"Unable to write PID file: " + self.PIDFILE + " Error: " + str(e.strerror) + " [" + str( e.errno) + "]") # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull, 'r') stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), getattr(sys.stdin, 'device', sys.stdin).fileno()) os.dup2(stdout.fileno(), getattr(sys.stdout, 'device', sys.stdout).fileno()) os.dup2(stderr.fileno(), getattr(sys.stderr, 'device', sys.stderr).fileno())
def restoreDatabase(filename, version): logger.log(u'Restoring database before trying upgrade again') if not sickbeard.helpers.restoreVersionedFile( dbFilename(filename=filename, suffix='v%s' % version), version): logger.log_error_and_exit( u'Database restore failed, abort upgrading database') return False else: return True
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=E1101 # Make a non-session-leader child process try: pid = os.fork() # only available in UNIX if 0 != pid: self.exit(0) except OSError as er: sys.stderr.write('fork #1 failed: %d (%s)\n' % (er.errno, er.strerror)) sys.exit(1) os.setsid() # only available in UNIX # Make sure I can read my own files and shut out others prev = os.umask(0) os.umask(prev and int('077', 8)) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # only available in UNIX if 0 != pid: self.exit(0) except OSError as er: sys.stderr.write('fork #2 failed: %d (%s)\n' % (er.errno, er.strerror)) sys.exit(1) # Write pid if self.create_pid: pid = str(os.getpid()) logger.log(u'Writing PID: %s to %s' % (pid, self.pid_file)) try: os.fdopen( os.open(self.pid_file, os.O_CREAT | os.O_WRONLY, 0o644), 'w').write('%s\n' % pid) except (BaseException, Exception) as er: logger.log_error_and_exit( 'Unable to write PID file: %s Error: %s [%s]' % (self.pid_file, er.strerror, er.errno)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = open(devnull, 'r') stdout = open(devnull, 'a+') stderr = open(devnull, 'a+') os.dup2(stdin.fileno(), sys.stdin.fileno()) os.dup2(stdout.fileno(), sys.stdout.fileno()) os.dup2(stderr.fileno(), sys.stderr.fileno())
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=E1101 # Make a non-session-leader child process try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as e: sys.stderr.write('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX # Make sure I can read my own files and shut out others prev = os.umask(0) os.umask(prev and int('077', 8)) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as e: sys.stderr.write('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror)) sys.exit(1) # Write pid if self.CREATEPID: pid = str(os.getpid()) logger.log(u'Writing PID: %s to %s' % (pid, self.PIDFILE)) try: open(self.PIDFILE, 'w').write('%s\n' % pid) except IOError as e: logger.log_error_and_exit( u'Unable to write PID file: %s Error: %s [%s]' % (self.PIDFILE, e.strerror, e.errno)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = open(devnull, 'r') stdout = open(devnull, 'a+') stderr = open(devnull, 'a+') os.dup2(stdin.fileno(), sys.stdin.fileno()) os.dup2(stdout.fileno(), sys.stdout.fileno()) os.dup2(stderr.fileno(), sys.stderr.fileno())
def restoreDatabase(version): """ Restores a database to a previous version (backup file of version must still exist) :param version: Version to restore to :return: True if restore succeeds, False if it fails """ logger.log(u"Restoring database before trying upgrade again") if not sickbeard.helpers.restoreVersionedFile(dbFilename(suffix='v' + str(version)), version): logger.log_error_and_exit(u"Database restore failed, abort upgrading database") return False else: return True
def restoreDatabase(version): """ Restores a database to a previous version (backup file of version must still exist) :param version: Version to restore to :return: True if restore succeeds, False if it fails """ logger.log("Restoring database before trying upgrade again") if not sickbeard.helpers.restoreVersionedFile(dbFilename(suffix='v' + str(version)), version): logger.log_error_and_exit("Database restore failed, abort upgrading database") return False else: return True
def execute(self): db.backup_database('sickbeard.db', self.checkDBVersion()) if not self.hasTable('tv_shows') and not self.hasTable('db_version'): queries = [ # original sick beard tables 'CREATE TABLE db_version (db_version INTEGER);', 'CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT, version NUMERIC)', 'CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC)', 'CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer NUMERIC, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC, scene_season NUMERIC, scene_episode NUMERIC, absolute_number NUMERIC, scene_absolute_number NUMERIC, version NUMERIC, release_group TEXT, trakt_watched NUMERIC)', 'CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer NUMERIC, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC, archive_firstmatch NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT, sports NUMERIC, anime NUMERIC, scene NUMERIC, overview TEXT, tag TEXT)', 'CREATE INDEX idx_showid ON tv_episodes (showid)', 'CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes (showid,airdate)', 'CREATE TABLE blacklist (show_id INTEGER, range TEXT, keyword TEXT)', 'CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC, PRIMARY KEY (indexer_id, indexer))', 'CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)', 'CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER, absolute_number NUMERIC, scene_absolute_number NUMERIC, PRIMARY KEY (indexer_id, season, episode))', 'CREATE TABLE whitelist (show_id INTEGER, range TEXT, keyword TEXT)', 'CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER)', 'CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id)', 'CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate)', 'CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate)', 'CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate)', 'INSERT INTO db_version (db_version) VALUES (20003)' ] for query in queries: self.connection.action(query) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit(u'Your database version (' + str(cur_db_version) + ') is too old to migrate from what this version of SickGear supports (' + str(MIN_DB_VERSION) + ').' + "\n" + 'Upgrade using a previous version (tag) build 496 to build 501 of SickGear first or remove database file to begin fresh.' ) if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit(u'Your database version (' + str(cur_db_version) + ') has been incremented past what this version of SickGear supports (' + str(MAX_DB_VERSION) + ').' + "\n" + 'If you have used other forks of SickGear, your database may be unusable due to their modifications.' ) return self.checkDBVersion()
def _processUpgrade(connection, upgradeClass): instance = upgradeClass(connection) logger.log( u'Checking %s database upgrade' % prettyName(upgradeClass.__name__), logger.DEBUG) if not instance.test(): connection.is_upgrading = True connection.upgrade_log( getattr(upgradeClass, 'pretty_name', None) or prettyName(upgradeClass.__name__)) logger.log( u'Database upgrade required: %s' % prettyName(upgradeClass.__name__), logger.MESSAGE) try: instance.execute() except sqlite3.DatabaseError as e: # attempting to restore previous DB backup and perform upgrade try: instance.execute() except: result = connection.select('SELECT db_version FROM db_version') if result: version = int(result[0]['db_version']) # close db before attempting restore connection.close() if restoreDatabase(connection.filename, version): logger.log_error_and_exit( u'Successfully restored database version: %s' % version) else: logger.log_error_and_exit( u'Failed to restore database version: %s' % version) logger.log('%s upgrade completed' % upgradeClass.__name__, logger.DEBUG) else: logger.log('%s upgrade not required' % upgradeClass.__name__, logger.DEBUG) for upgradeSubClass in upgradeClass.__subclasses__(): _processUpgrade(connection, upgradeSubClass)
def execute(self): if not self.hasTable("tv_shows") and not self.hasTable("db_version"): queries = [ "CREATE TABLE db_version (db_version INTEGER);", "CREATE TABLE history (action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT)", "CREATE TABLE imdb_info (indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC)", "CREATE TABLE info (last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC)", "CREATE TABLE scene_numbering(indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER,scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY(indexer_id, season, episode))", "CREATE TABLE tv_shows (show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer TEXT, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC, archive_firstmatch NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT, sports NUMERIC);", "CREATE TABLE tv_episodes (episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer TEXT, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC, scene_season NUMERIC, scene_episode NUMERIC);", "CREATE UNIQUE INDEX idx_indexer_id ON tv_shows (indexer_id)", "CREATE INDEX idx_showid ON tv_episodes (showid);", "CREATE INDEX idx_sta_epi_air ON tv_episodes (status,episode, airdate);", "CREATE INDEX idx_sta_epi_sta_air ON tv_episodes (season,episode, status, airdate);", "CREATE INDEX idx_status ON tv_episodes (status,season,episode,airdate);", "CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid,airdate)", "INSERT INTO db_version (db_version) VALUES (31);", ] for query in queries: self.connection.action(query) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit( u"Your database version (" + str(cur_db_version) + ") is too old to migrate from what this version of SickRage supports (" + str(MIN_DB_VERSION) + ").\n" + "Upgrade using a previous version (tag) build 496 to build 501 of SickRage first or remove database file to begin fresh." ) if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit( u"Your database version (" + str(cur_db_version) + ") has been incremented past what this version of SickRage supports (" + str(MAX_DB_VERSION) + ").\n" + "If you have used other forks of SickRage, your database may be unusable due to their modifications." )
def execute(self): if not self.hasTable("tv_shows") and not self.hasTable("db_version"): queries = [ "CREATE TABLE db_version(db_version INTEGER);", "CREATE TABLE history(action NUMERIC, date NUMERIC, showid NUMERIC, season NUMERIC, episode NUMERIC, quality NUMERIC, resource TEXT, provider TEXT, version NUMERIC DEFAULT -1);", "CREATE TABLE imdb_info(indexer_id INTEGER PRIMARY KEY, imdb_id TEXT, title TEXT, year NUMERIC, akas TEXT, runtimes NUMERIC, genres TEXT, countries TEXT, country_codes TEXT, certificates TEXT, rating TEXT, votes INTEGER, last_update NUMERIC);", "CREATE TABLE info(last_backlog NUMERIC, last_indexer NUMERIC, last_proper_search NUMERIC);", "CREATE TABLE scene_numbering(indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER, absolute_number NUMERIC, scene_absolute_number NUMERIC, PRIMARY KEY(indexer_id, season, episode));", "CREATE TABLE tv_shows(show_id INTEGER PRIMARY KEY, indexer_id NUMERIC, indexer NUMERIC, show_name TEXT, location TEXT, network TEXT, genre TEXT, classification TEXT, runtime NUMERIC, quality NUMERIC, airs TEXT, status TEXT, flatten_folders NUMERIC, paused NUMERIC, startyear NUMERIC, air_by_date NUMERIC, lang TEXT, subtitles NUMERIC, notify_list TEXT, imdb_id TEXT, last_update_indexer NUMERIC, dvdorder NUMERIC, archive_firstmatch NUMERIC, rls_require_words TEXT, rls_ignore_words TEXT, sports NUMERIC, anime NUMERIC, scene NUMERIC, default_ep_status NUMERIC DEFAULT -1);", "CREATE TABLE tv_episodes(episode_id INTEGER PRIMARY KEY, showid NUMERIC, indexerid NUMERIC, indexer TEXT, name TEXT, season NUMERIC, episode NUMERIC, description TEXT, airdate NUMERIC, hasnfo NUMERIC, hastbn NUMERIC, status NUMERIC, location TEXT, file_size NUMERIC, release_name TEXT, subtitles TEXT, subtitles_searchcount NUMERIC, subtitles_lastsearch TIMESTAMP, is_proper NUMERIC, scene_season NUMERIC, scene_episode NUMERIC, absolute_number NUMERIC, scene_absolute_number NUMERIC, version NUMERIC DEFAULT -1, release_group TEXT);", "CREATE TABLE blacklist (show_id INTEGER, range TEXT, keyword TEXT);", "CREATE TABLE whitelist (show_id INTEGER, range TEXT, keyword TEXT);", "CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER);", "CREATE TABLE indexer_mapping (indexer_id INTEGER, indexer NUMERIC, mindexer_id INTEGER, mindexer NUMERIC, PRIMARY KEY (indexer_id, indexer));", "CREATE UNIQUE INDEX idx_indexer_id ON tv_shows(indexer_id);", "CREATE INDEX idx_showid ON tv_episodes(showid);", "CREATE INDEX idx_sta_epi_air ON tv_episodes(status, episode, airdate);", "CREATE INDEX idx_sta_epi_sta_air ON tv_episodes(season, episode, status, airdate);", "CREATE INDEX idx_status ON tv_episodes(status,season,episode,airdate);", "CREATE INDEX idx_tv_episodes_showid_airdate ON tv_episodes(showid, airdate);", "INSERT INTO db_version(db_version) VALUES (42);" ] for query in queries: self.connection.action(query) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit(u"Your database version (" + str( cur_db_version) + ") is too old to migrate from what this version of SickRage supports (" + \ str(MIN_DB_VERSION) + ").\n" + \ "Upgrade using a previous version (tag) build 496 to build 501 of SickRage first or remove database file to begin fresh." ) if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit(u"Your database version (" + str( cur_db_version) + ") has been incremented past what this version of SickRage supports (" + \ str(MAX_DB_VERSION) + ").\n" + \ "If you have used other forks of SickRage, your database may be unusable due to their modifications." )
def execute(self): if not self.hasTable("lastUpdate") and not self.hasTable("db_version"): queries = [ ("CREATE TABLE lastUpdate (provider TEXT, time NUMERIC);", ), ("CREATE TABLE lastSearch (provider TEXT, time NUMERIC);", ), ("CREATE TABLE db_version (db_version INTEGER);", ), ("CREATE TABLE scene_exceptions (exception_id INTEGER PRIMARY KEY, indexer_id INTEGER KEY, show_name TEXT, season NUMERIC, custom NUMERIC);", ), ("CREATE TABLE scene_names (indexer_id INTEGER, name TEXT);", ), ("CREATE TABLE network_timezones (network_name TEXT PRIMARY KEY, timezone TEXT);", ), ("CREATE TABLE scene_exceptions_refresh (list TEXT PRIMARY KEY, last_refreshed INTEGER);", ), ("INSERT INTO db_version (db_version) VALUES (6)", ), ] for query in queries: if len(query) == 1: self.connection.action(query[0]) else: self.connection.action(query[0], query[1:]) else: cur_db_version = self.checkDBVersion() if cur_db_version < MIN_DB_VERSION: logger.log_error_and_exit(u"Your cache database version (" + str(cur_db_version) + ") is too old to migrate from what this version of Sick Beard supports (" + \ str(MIN_DB_VERSION) + ").\n" + \ "Upgrade using a previous version (tag) build 496 to build 501 of Sick Beard first or remove database file to begin fresh." ) if cur_db_version > MAX_DB_VERSION: logger.log_error_and_exit(u"Your cache database version (" + str(cur_db_version) + ") has been incremented past what this version of Sick Beard supports (" + \ str(MAX_DB_VERSION) + ").\n" + \ "If you have used other forks of Sick Beard, your database may be unusable due to their modifications." )
try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # Write pid if sickbeard.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID: " + pid + " to " + str(sickbeard.PIDFILE)) try: file(sickbeard.PIDFILE, 'w').write("%s\n" % pid) except IOError, e: logger.log_error_and_exit(u"Unable to write PID file: " + sickbeard.PIDFILE + " Error: " + str(e.strerror) + " [" + str(e.errno) + "]") # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull, 'r') stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), sys.stdin.fileno()) os.dup2(stdout.fileno(), sys.stdout.fileno()) os.dup2(stderr.fileno(), sys.stderr.fileno()) def help_message():
pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError, e: sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # Write pid if sickbeard.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID: " + pid + " to " + str(sickbeard.PIDFILE)) try: file(sickbeard.PIDFILE, 'w').write("%s\n" % pid) except IOError, e: logger.log_error_and_exit(u"Unable to write PID file: " + sickbeard.PIDFILE + " Error: " + str(e.strerror) + " [" + str(e.errno) + "]") # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull, 'r') stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), sys.stdin.fileno()) os.dup2(stdout.fileno(), sys.stdout.fileno()) os.dup2(stderr.fileno(), sys.stderr.fileno())
def start(self): # do some preliminary stuff sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__)) sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME) sickbeard.DATA_DIR = sickbeard.PROG_DIR sickbeard.MY_ARGS = sys.argv[1:] sickbeard.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, '') except (locale.Error, IOError): pass try: sickbeard.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # For OSes that are poorly configured I'll just randomly force UTF-8 if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): sickbeard.SYS_ENCODING = 'UTF-8' if not hasattr(sys, 'setdefaultencoding'): moves.reload_module(sys) try: # pylint: disable=E1101 # On non-unicode builds this raises an AttributeError, if encoding type is not valid it throws a LookupError sys.setdefaultencoding(sickbeard.SYS_ENCODING) except (StandardError, Exception): print('Sorry, you MUST add the SickGear folder to the PYTHONPATH environment variable') print('or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING) sys.exit(1) # Need console logging for SickBeard.py and SickBeard-console.exe self.console_logging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0) # Rename the main thread threading.currentThread().name = 'MAIN' try: opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::', ['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'systemd', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize']) # @UnusedVariable except getopt.GetoptError: sys.exit(self.help_message()) for o, a in opts: # Prints help message if o in ('-h', '--help'): sys.exit(self.help_message()) # For now we'll just silence the logging if o in ('-q', '--quiet'): self.console_logging = False # Should we update (from indexer) all shows in the DB right away? if o in ('-f', '--forceupdate'): self.force_update = True # Suppress launching web browser # Needed for OSes without default browser assigned # Prevent duplicate browser window when restarting in the app if o in ('--nolaunch',): self.no_launch = True # Override default/configured port if o in ('-p', '--port'): try: self.forced_port = int(a) except ValueError: sys.exit('Port: %s is not a number. Exiting.' % a) # Run as a double forked daemon if o in ('-d', '--daemon'): self.run_as_daemon = True # When running as daemon disable console_logging and don't start browser self.console_logging = False self.no_launch = True if 'win32' == sys.platform: self.run_as_daemon = False # Run as a systemd service if o in ('-s', '--systemd') and 'win32' != sys.platform: self.run_as_systemd = True self.run_as_daemon = False self.console_logging = False self.no_launch = True # Write a pidfile if requested if o in ('--pidfile',): self.create_pid = True self.pid_file = str(a) # If the pidfile already exists, sickbeard may still be running, so exit if os.path.exists(self.pid_file): sys.exit('PID file: %s already exists. Exiting.' % self.pid_file) # Specify folder to load the config file from if o in ('--config',): sickbeard.CONFIG_FILE = os.path.abspath(a) # Specify folder to use as the data dir if o in ('--datadir',): sickbeard.DATA_DIR = os.path.abspath(a) # Prevent resizing of the banner/posters even if PIL is installed if o in ('--noresize',): sickbeard.NO_RESIZE = True # The pidfile is only useful in daemon mode, make sure we can write the file properly if self.create_pid: if self.run_as_daemon: pid_dir = os.path.dirname(self.pid_file) if not os.access(pid_dir, os.F_OK): sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir) if not os.access(pid_dir, os.W_OK): sys.exit(u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir) else: if self.console_logging: print(u'Not running in daemon mode. PID file creation disabled') self.create_pid = False # If they don't specify a config file then put it in the data dir if not sickbeard.CONFIG_FILE: sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini') # Make sure that we can create the data dir if not os.access(sickbeard.DATA_DIR, os.F_OK): try: os.makedirs(sickbeard.DATA_DIR, 0o744) except os.error: sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the data dir if not os.access(sickbeard.DATA_DIR, os.W_OK): sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the config file if not os.access(sickbeard.CONFIG_FILE, os.W_OK): if os.path.isfile(sickbeard.CONFIG_FILE): sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE) elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK): sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting' % os.path.dirname(sickbeard.CONFIG_FILE)) os.chdir(sickbeard.DATA_DIR) if self.console_logging: print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE) # Load the config and publish it to the sickbeard package if not os.path.isfile(sickbeard.CONFIG_FILE): print(u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE) sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) try: stack_size = int(sickbeard.CFG['General']['stack_size']) except (StandardError, Exception): stack_size = None if stack_size: try: threading.stack_size(stack_size) except (StandardError, Exception) as er: print('Stack Size %s not set: %s' % (stack_size, er.message)) # check all db versions for d, min_v, max_v, base_v, mo in [ ('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION, sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'), ('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION, sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'), ('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION, sickbeard.mainDB.TEST_BASE_VERSION, 'MainDb') ]: cur_db_version = db.DBConnection(d).checkDBVersion() # handling of standalone TEST db versions if cur_db_version >= 100000 and cur_db_version != max_v: print('Your [%s] database version (%s) is a test db version and doesn\'t match SickGear required ' 'version (%s), downgrading to production db' % (d, cur_db_version, max_v)) self.execute_rollback(mo, max_v) cur_db_version = db.DBConnection(d).checkDBVersion() if cur_db_version >= 100000: print(u'Rollback to production failed.') sys.exit(u'If you have used other forks, your database may be unusable due to their changes') if 100000 <= max_v and None is not base_v: max_v = base_v # set max_v to the needed base production db for test_db print(u'Rollback to production of [%s] successful.' % d) # handling of production db versions if 0 < cur_db_version < 100000: if cur_db_version < min_v: print(u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear' % (d, cur_db_version)) sys.exit(u'Upgrade using a previous version of SG first,' + u' or start with no database file to begin fresh') if cur_db_version > max_v: print(u'Your [%s] database version (%s) has been incremented past' u' what this version of SickGear supports. Trying to rollback now. Please wait...' % (d, cur_db_version)) self.execute_rollback(mo, max_v) if db.DBConnection(d).checkDBVersion() > max_v: print(u'Rollback failed.') sys.exit(u'If you have used other forks, your database may be unusable due to their changes') print(u'Rollback of [%s] successful.' % d) # free memory global rollback_loaded rollback_loaded = None # Initialize the config and our threads sickbeard.initialize(console_logging=self.console_logging) if self.run_as_daemon: self.daemonize() # Get PID sickbeard.PID = os.getpid() if self.forced_port: logger.log(u'Forcing web server to port %s' % self.forced_port) self.start_port = self.forced_port else: self.start_port = sickbeard.WEB_PORT if sickbeard.WEB_LOG: self.log_dir = sickbeard.LOG_DIR else: self.log_dir = None # sickbeard.WEB_HOST is available as a configuration value in various # places but is not configurable. It is supported here for historic reasons. if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0': self.webhost = sickbeard.WEB_HOST else: self.webhost = (('0.0.0.0', '::')[sickbeard.WEB_IPV6], '')[sickbeard.WEB_IPV64] # web server options self.web_options = dict( host=self.webhost, port=int(self.start_port), web_root=sickbeard.WEB_ROOT, data_root=os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), log_dir=self.log_dir, username=sickbeard.WEB_USERNAME, password=sickbeard.WEB_PASSWORD, handle_reverse_proxy=sickbeard.HANDLE_REVERSE_PROXY, enable_https=False, https_cert=None, https_key=None, ) if sickbeard.ENABLE_HTTPS: self.web_options.update(dict( enable_https=sickbeard.ENABLE_HTTPS, https_cert=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), https_key=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY) )) # start web server try: # used to check if existing SG instances have been started sickbeard.helpers.wait_for_free_port( sickbeard.WEB_IPV6 and '::1' or self.web_options['host'], self.web_options['port']) self.webserver = WebServer(self.web_options) self.webserver.start() except (StandardError, Exception): logger.log(u'Unable to start web server, is something else running on port %d?' % self.start_port, logger.ERROR) if self.run_as_systemd: self.exit(0) if sickbeard.LAUNCH_BROWSER and not self.no_launch: logger.log(u'Launching browser and exiting', logger.ERROR) sickbeard.launch_browser(self.start_port) self.exit(1) # Check if we need to perform a restore first restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore') if os.path.exists(restore_dir): if self.restore(restore_dir, sickbeard.DATA_DIR): logger.log(u'Restore successful...') else: logger.log_error_and_exit(u'Restore FAILED!') # Build from the DB to start with self.load_shows_from_db() # Fire up all our threads sickbeard.start() # Build internal name cache name_cache.buildNameCache() # refresh network timezones network_timezones.update_network_dict() # load all ids from xem startup_background_tasks = threading.Thread(name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids) startup_background_tasks.start() # check history snatched_proper update if not db.DBConnection().has_flag('history_snatch_proper'): # noinspection PyUnresolvedReferences history_snatched_proper_task = threading.Thread(name='UPGRADE-HISTORY-ACTION', target=sickbeard.history.history_snatched_proper_fix) history_snatched_proper_task.start() if sickbeard.USE_FAILED_DOWNLOADS: failed_history.remove_old_history() # Start an update if we're supposed to if self.force_update or sickbeard.UPDATE_SHOWS_ON_START: sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable # Launch browser if sickbeard.LAUNCH_BROWSER and not self.no_launch: sickbeard.launch_browser(self.start_port) # main loop while True: time.sleep(1)
def backup_database(filename, version): logger.log(u'Backing up database before upgrade') if not sickbeard.helpers.backupVersionedFile(dbFilename(filename), version): logger.log_error_and_exit(u'Database backup failed, abort upgrading database') else: logger.log(u'Proceeding with upgrade')
def MigrationCode(myDB): schema = { 0: sickbeard.mainDB.InitialSchema, 9: sickbeard.mainDB.AddSizeAndSceneNameFields, 10: sickbeard.mainDB.RenameSeasonFolders, 11: sickbeard.mainDB.Add1080pAndRawHDQualities, 12: sickbeard.mainDB.AddShowidTvdbidIndex, 13: sickbeard.mainDB.AddLastUpdateTVDB, 14: sickbeard.mainDB.AddDBIncreaseTo15, 15: sickbeard.mainDB.AddIMDbInfo, 16: sickbeard.mainDB.AddProperNamingSupport, 17: sickbeard.mainDB.AddEmailSubscriptionTable, 18: sickbeard.mainDB.AddProperSearch, 19: sickbeard.mainDB.AddDvdOrderOption, 20: sickbeard.mainDB.AddSubtitlesSupport, 21: sickbeard.mainDB.ConvertTVShowsToIndexerScheme, 22: sickbeard.mainDB.ConvertTVEpisodesToIndexerScheme, 23: sickbeard.mainDB.ConvertIMDBInfoToIndexerScheme, 24: sickbeard.mainDB.ConvertInfoToIndexerScheme, 25: sickbeard.mainDB.AddArchiveFirstMatchOption, 26: sickbeard.mainDB.AddSceneNumbering, 27: sickbeard.mainDB.ConvertIndexerToInteger, 28: sickbeard.mainDB.AddRequireAndIgnoreWords, 29: sickbeard.mainDB.AddSportsOption, 30: sickbeard.mainDB.AddSceneNumberingToTvEpisodes, 31: sickbeard.mainDB.AddAnimeTVShow, 32: sickbeard.mainDB.AddAbsoluteNumbering, 33: sickbeard.mainDB.AddSceneAbsoluteNumbering, 34: sickbeard.mainDB.AddAnimeBlacklistWhitelist, 35: sickbeard.mainDB.AddSceneAbsoluteNumbering2, 36: sickbeard.mainDB.AddXemRefresh, 37: sickbeard.mainDB.AddSceneToTvShows, 38: sickbeard.mainDB.AddIndexerMapping, 39: sickbeard.mainDB.AddVersionToTvEpisodes, 40: sickbeard.mainDB.BumpDatabaseVersion, 41: sickbeard.mainDB.Migrate41, 42: sickbeard.mainDB.Migrate41, 43: sickbeard.mainDB.Migrate41, 44: sickbeard.mainDB.Migrate41, 4301: sickbeard.mainDB.Migrate4301, 5816: sickbeard.mainDB.MigrateUpstream, 5817: sickbeard.mainDB.MigrateUpstream, 5818: sickbeard.mainDB.MigrateUpstream, 10000: sickbeard.mainDB.SickGearDatabaseVersion, 10001: sickbeard.mainDB.RemoveDefaultEpStatusFromTvShows, 20000: sickbeard.mainDB.DBIncreaseTo20001, 20001: sickbeard.mainDB.AddTvShowOverview, 20002: sickbeard.mainDB.AddTvShowTags, # 20002: sickbeard.mainDB.AddCoolSickGearFeature3, } db_version = myDB.checkDBVersion() logger.log(u'Detected database version: v%s' % db_version, logger.DEBUG) if not (db_version in schema): if db_version == sickbeard.mainDB.MAX_DB_VERSION: logger.log(u'Database schema is up-to-date, no upgrade required') elif db_version < 10000: logger.log_error_and_exit(u'SickGear does not currently support upgrading from this database version') else: logger.log_error_and_exit(u'Invalid database version') else: while db_version < sickbeard.mainDB.MAX_DB_VERSION: try: update = schema[db_version](myDB) db_version = update.execute() except Exception as e: myDB.close() logger.log(u'Failed to update database with error: %s attempting recovery...' % ex(e), logger.ERROR) if restoreDatabase(myDB.filename, db_version): # initialize the main SB database logger.log_error_and_exit(u'Successfully restored database version: %s' % db_version) else: logger.log_error_and_exit(u'Failed to restore database version: %s' % db_version)
def start(self): # do some preliminary stuff sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__)) sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME) sickbeard.DATA_DIR = sickbeard.PROG_DIR sickbeard.MY_ARGS = sys.argv[1:] sickbeard.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, '') except (locale.Error, IOError): pass try: sickbeard.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # For OSes that are poorly configured I'll just randomly force UTF-8 if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ( 'ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): sickbeard.SYS_ENCODING = 'UTF-8' if not hasattr(sys, 'setdefaultencoding'): moves.reload_module(sys) if PY2: try: # On non-unicode builds this raises an AttributeError, # if encoding type is not valid it throws a LookupError # noinspection PyUnresolvedReferences sys.setdefaultencoding(sickbeard.SYS_ENCODING) except (BaseException, Exception): print( 'Sorry, you MUST add the SickGear folder to the PYTHONPATH environment variable' ) print( 'or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING) sys.exit(1) # Need console logging for sickgear.py and SickBeard-console.exe self.console_logging = (not hasattr( sys, 'frozen')) or (0 < sickbeard.MY_NAME.lower().find('-console')) # Rename the main thread threading.currentThread().name = 'MAIN' try: opts, args = getopt.getopt(sys.argv[1:], 'hfqdsp::', [ 'help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'systemd', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize' ]) except getopt.GetoptError: sys.exit(self.help_message()) for o, a in opts: # Prints help message if o in ('-h', '--help'): sys.exit(self.help_message()) # For now we'll just silence the logging if o in ('-q', '--quiet'): self.console_logging = False # Should we update (from indexer) all shows in the DB right away? if o in ('-f', '--forceupdate'): self.force_update = True # Suppress launching web browser # Needed for OSes without default browser assigned # Prevent duplicate browser window when restarting in the app if o in ('--nolaunch', ): self.no_launch = True # Override default/configured port if o in ('-p', '--port'): try: self.forced_port = int(a) except ValueError: sys.exit('Port: %s is not a number. Exiting.' % a) # Run as a double forked daemon if o in ('-d', '--daemon'): self.run_as_daemon = True # When running as daemon disable console_logging and don't start browser self.console_logging = False self.no_launch = True if 'win32' == sys.platform: self.run_as_daemon = False # Run as a systemd service if o in ('-s', '--systemd') and 'win32' != sys.platform: self.run_as_systemd = True self.run_as_daemon = False self.console_logging = False self.no_launch = True # Write a pidfile if requested if o in ('--pidfile', ): self.create_pid = True self.pid_file = str(a) # If the pidfile already exists, sickbeard may still be running, so exit if os.path.exists(self.pid_file): sys.exit('PID file: %s already exists. Exiting.' % self.pid_file) # Specify folder to load the config file from if o in ('--config', ): sickbeard.CONFIG_FILE = os.path.abspath(a) # Specify folder to use as the data dir if o in ('--datadir', ): sickbeard.DATA_DIR = os.path.abspath(a) # Prevent resizing of the banner/posters even if PIL is installed if o in ('--noresize', ): sickbeard.NO_RESIZE = True # The pidfile is only useful in daemon mode, make sure we can write the file properly if self.create_pid: if self.run_as_daemon: pid_dir = os.path.dirname(self.pid_file) if not os.access(pid_dir, os.F_OK): sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir) if not os.access(pid_dir, os.W_OK): sys.exit( u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir) else: if self.console_logging: print( u'Not running in daemon mode. PID file creation disabled' ) self.create_pid = False # If they don't specify a config file then put it in the data dir if not sickbeard.CONFIG_FILE: sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini') # Make sure that we can create the data dir if not os.access(sickbeard.DATA_DIR, os.F_OK): try: os.makedirs(sickbeard.DATA_DIR, 0o744) except os.error: sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the data dir if not os.access(sickbeard.DATA_DIR, os.W_OK): sys.exit( u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the config file if not os.access(sickbeard.CONFIG_FILE, os.W_OK): if os.path.isfile(sickbeard.CONFIG_FILE): sys.exit( u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE) elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK): sys.exit( u'Config file directory: %s must be writeable (write permissions). Exiting' % os.path.dirname(sickbeard.CONFIG_FILE)) os.chdir(sickbeard.DATA_DIR) if self.console_logging: print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE) # Load the config and publish it to the sickbeard package if not os.path.isfile(sickbeard.CONFIG_FILE): print(u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE) sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) try: stack_size = int(sickbeard.CFG['General']['stack_size']) except (BaseException, Exception): stack_size = None if stack_size: try: threading.stack_size(stack_size) except (BaseException, Exception) as er: print('Stack Size %s not set: %s' % (stack_size, ex(er))) if self.run_as_daemon: self.daemonize() # Get PID sickbeard.PID = os.getpid() # Initialize the config sickbeard.initialize(console_logging=self.console_logging) if self.forced_port: logger.log(u'Forcing web server to port %s' % self.forced_port) self.start_port = self.forced_port else: self.start_port = sickbeard.WEB_PORT if sickbeard.WEB_LOG: self.log_dir = sickbeard.LOG_DIR else: self.log_dir = None # sickbeard.WEB_HOST is available as a configuration value in various # places but is not configurable. It is supported here for historic reasons. if sickbeard.WEB_HOST and '0.0.0.0' != sickbeard.WEB_HOST: self.webhost = sickbeard.WEB_HOST else: self.webhost = (('0.0.0.0', '::')[sickbeard.WEB_IPV6], '')[sickbeard.WEB_IPV64] # web server options self.web_options = dict( host=self.webhost, port=int(self.start_port), web_root=sickbeard.WEB_ROOT, data_root=os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), log_dir=self.log_dir, username=sickbeard.WEB_USERNAME, password=sickbeard.WEB_PASSWORD, handle_reverse_proxy=sickbeard.HANDLE_REVERSE_PROXY, enable_https=False, https_cert=None, https_key=None, ) if sickbeard.ENABLE_HTTPS: self.web_options.update( dict(enable_https=sickbeard.ENABLE_HTTPS, https_cert=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), https_key=os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY))) # start web server try: # used to check if existing SG instances have been started sickbeard.helpers.wait_for_free_port( sickbeard.WEB_IPV6 and '::1' or self.web_options['host'], self.web_options['port']) self.webserver = WebServer(options=self.web_options) self.webserver.start() # wait for server thread to be started self.webserver.wait_server_start() sickbeard.started = True except (BaseException, Exception): logger.log( u'Unable to start web server, is something else running on port %d?' % self.start_port, logger.ERROR) if self.run_as_systemd: self.exit(0) if sickbeard.LAUNCH_BROWSER and not self.no_launch: logger.log(u'Launching browser and exiting', logger.ERROR) sickbeard.launch_browser(self.start_port) self.exit(1) # Launch browser if sickbeard.LAUNCH_BROWSER and not self.no_launch: sickbeard.launch_browser(self.start_port) # check all db versions for d, min_v, max_v, base_v, mo in [ ('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION, sickbeard.failed_db.TEST_BASE_VERSION, 'FailedDb'), ('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION, sickbeard.cache_db.TEST_BASE_VERSION, 'CacheDb'), ('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION, sickbeard.mainDB.TEST_BASE_VERSION, 'MainDb') ]: cur_db_version = db.DBConnection(d).checkDBVersion() # handling of standalone TEST db versions load_msg = 'Downgrading %s to production version' % d if 100000 <= cur_db_version != max_v: sickbeard.classes.loading_msg.set_msg_progress( load_msg, 'Rollback') print( 'Your [%s] database version (%s) is a test db version and doesn\'t match SickGear required ' 'version (%s), downgrading to production db' % (d, cur_db_version, max_v)) self.execute_rollback(mo, max_v, load_msg) cur_db_version = db.DBConnection(d).checkDBVersion() if 100000 <= cur_db_version: print(u'Rollback to production failed.') sys.exit( u'If you have used other forks, your database may be unusable due to their changes' ) if 100000 <= max_v and None is not base_v: max_v = base_v # set max_v to the needed base production db for test_db print(u'Rollback to production of [%s] successful.' % d) sickbeard.classes.loading_msg.set_msg_progress( load_msg, 'Finished') # handling of production version higher then current base of test db if isinstance(base_v, integer_types ) and max_v >= 100000 > cur_db_version > base_v: sickbeard.classes.loading_msg.set_msg_progress( load_msg, 'Rollback') print( 'Your [%s] database version (%s) is a db version and doesn\'t match SickGear required ' 'version (%s), downgrading to production base db' % (d, cur_db_version, max_v)) self.execute_rollback(mo, base_v, load_msg) cur_db_version = db.DBConnection(d).checkDBVersion() if 100000 <= cur_db_version: print(u'Rollback to production base failed.') sys.exit( u'If you have used other forks, your database may be unusable due to their changes' ) if 100000 <= max_v and None is not base_v: max_v = base_v # set max_v to the needed base production db for test_db print(u'Rollback to production base of [%s] successful.' % d) sickbeard.classes.loading_msg.set_msg_progress( load_msg, 'Finished') # handling of production db versions if 0 < cur_db_version < 100000: if cur_db_version < min_v: print( u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear' % (d, cur_db_version)) sys.exit(u'Upgrade using a previous version of SG first,' + u' or start with no database file to begin fresh') if cur_db_version > max_v: sickbeard.classes.loading_msg.set_msg_progress( load_msg, 'Rollback') print( u'Your [%s] database version (%s) has been incremented past' u' what this version of SickGear supports. Trying to rollback now. Please wait...' % (d, cur_db_version)) self.execute_rollback(mo, max_v, load_msg) if db.DBConnection(d).checkDBVersion() > max_v: print(u'Rollback failed.') sys.exit( u'If you have used other forks, your database may be unusable due to their changes' ) print(u'Rollback of [%s] successful.' % d) sickbeard.classes.loading_msg.set_msg_progress( load_msg, 'Finished') # migrate the config if it needs it from sickbeard.config import ConfigMigrator migrator = ConfigMigrator(sickbeard.CFG) if migrator.config_version > migrator.expected_config_version: self.execute_rollback('ConfigFile', migrator.expected_config_version, 'Downgrading config.ini') migrator = ConfigMigrator(sickbeard.CFG) migrator.migrate_config() # free memory global rollback_loaded rollback_loaded = None sickbeard.classes.loading_msg.message = 'Init SickGear' # Initialize the threads and other stuff sickbeard.initialize(console_logging=self.console_logging) # Check if we need to perform a restore first restore_dir = os.path.join(sickbeard.DATA_DIR, 'restore') if os.path.exists(restore_dir): sickbeard.classes.loading_msg.message = 'Restoring files' if self.restore(restore_dir, sickbeard.DATA_DIR): logger.log(u'Restore successful...') else: logger.log_error_and_exit(u'Restore FAILED!') # Build from the DB to start with sickbeard.classes.loading_msg.message = 'Loading shows from db' self.load_shows_from_db() # Fire up all our threads sickbeard.classes.loading_msg.message = 'Starting threads' sickbeard.start() # Build internal name cache sickbeard.classes.loading_msg.message = 'Build name cache' name_cache.buildNameCache() # refresh network timezones sickbeard.classes.loading_msg.message = 'Checking network timezones' network_timezones.update_network_dict() # load all ids from xem sickbeard.classes.loading_msg.message = 'Loading xem data' startup_background_tasks = threading.Thread( name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids) startup_background_tasks.start() sickbeard.classes.loading_msg.message = 'Checking history' # check history snatched_proper update if not db.DBConnection().has_flag('history_snatch_proper'): # noinspection PyUnresolvedReferences history_snatched_proper_task = threading.Thread( name='UPGRADE-HISTORY-ACTION', target=sickbeard.history.history_snatched_proper_fix) history_snatched_proper_task.start() if not db.DBConnection().has_flag('kodi_nfo_default_removed'): sickbeard.metadata.kodi.remove_default_attr() if sickbeard.USE_FAILED_DOWNLOADS: failed_history.remove_old_history() # Start an update if we're supposed to if self.force_update or sickbeard.UPDATE_SHOWS_ON_START: sickbeard.classes.loading_msg.message = 'Starting a forced show update' sickbeard.showUpdateScheduler.action.run() sickbeard.classes.loading_msg.message = 'Switching to default web server' time.sleep(2) self.webserver.switch_handlers() # # Launch browser # if sickbeard.LAUNCH_BROWSER and not self.no_launch: # sickbeard.launch_browser(self.start_port) # main loop while True: time.sleep(1)
def daemonize(self): """ Fork off as a daemon """ # pylint: disable=E1101,W0212 # An object is accessed for a non-existent member. # Access to a protected member of a client class # Make a non-session-leader child process try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as e: sys.stderr.write(u"fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) os.setsid() # @UndefinedVariable - only available in UNIX # https://github.com/SiCKRAGETV/sickrage-issues/issues/2969 # http://www.microhowto.info/howto/cause_a_process_to_become_a_daemon_in_c.html#idp23920 # https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch06s08.html # Previous code simply set the umask to whatever it was because it was ANDing instead of ORring # Daemons traditionally run with umask 0 anyways and this should not have repercussions os.umask(0) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError as e: sys.stderr.write(u"fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(1) # Write pid if self.CREATEPID: pid = str(os.getpid()) logger.log(u"Writing PID: " + pid + " to " + str(self.PIDFILE)) try: file(self.PIDFILE, 'w').write("%s\n" % pid) except IOError as e: logger.log_error_and_exit(u"Unable to write PID file: " + self.PIDFILE + " Error: " + str(e.strerror) + " [" + str(e.errno) + "]") # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull, 'r') stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), getattr(sys.stdin, 'device', sys.stdin).fileno()) os.dup2(stdout.fileno(), getattr(sys.stdout, 'device', sys.stdout).fileno()) os.dup2(stderr.fileno(), getattr(sys.stderr, 'device', sys.stderr).fileno())
def start(self): # do some preliminary stuff sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__)) sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME) sickbeard.DATA_DIR = sickbeard.PROG_DIR sickbeard.MY_ARGS = sys.argv[1:] sickbeard.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, '') except (locale.Error, IOError): pass try: sickbeard.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # For OSes that are poorly configured I'll just randomly force UTF-8 if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ( 'ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): sickbeard.SYS_ENCODING = 'UTF-8' if not hasattr(sys, 'setdefaultencoding'): moves.reload_module(sys) try: # pylint: disable=E1101 # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError sys.setdefaultencoding(sickbeard.SYS_ENCODING) except: print( 'Sorry, you MUST add the SickGear folder to the PYTHONPATH environment variable' ) print( 'or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING) sys.exit(1) # Need console logging for SickBeard.py and SickBeard-console.exe self.consoleLogging = (not hasattr( sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0) # Rename the main thread threading.currentThread().name = 'MAIN' try: opts, args = getopt.getopt(sys.argv[1:], 'hfqdp::', [ 'help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize' ]) # @UnusedVariable except getopt.GetoptError: sys.exit(self.help_message()) for o, a in opts: # Prints help message if o in ('-h', '--help'): sys.exit(self.help_message()) # For now we'll just silence the logging if o in ('-q', '--quiet'): self.consoleLogging = False # Should we update (from indexer) all shows in the DB right away? if o in ('-f', '--forceupdate'): self.forceUpdate = True # Suppress launching web browser # Needed for OSes without default browser assigned # Prevent duplicate browser window when restarting in the app if o in ('--nolaunch', ): self.noLaunch = True # Override default/configured port if o in ('-p', '--port'): try: self.forcedPort = int(a) except ValueError: sys.exit('Port: %s is not a number. Exiting.' % a) # Run as a double forked daemon if o in ('-d', '--daemon'): self.runAsDaemon = True # When running as daemon disable consoleLogging and don't start browser self.consoleLogging = False self.noLaunch = True if sys.platform == 'win32': self.runAsDaemon = False # Write a pidfile if requested if o in ('--pidfile', ): self.CREATEPID = True self.PIDFILE = str(a) # If the pidfile already exists, sickbeard may still be running, so exit if os.path.exists(self.PIDFILE): sys.exit('PID file: %s already exists. Exiting.' % self.PIDFILE) # Specify folder to load the config file from if o in ('--config', ): sickbeard.CONFIG_FILE = os.path.abspath(a) # Specify folder to use as the data dir if o in ('--datadir', ): sickbeard.DATA_DIR = os.path.abspath(a) # Prevent resizing of the banner/posters even if PIL is installed if o in ('--noresize', ): sickbeard.NO_RESIZE = True # The pidfile is only useful in daemon mode, make sure we can write the file properly if self.CREATEPID: if self.runAsDaemon: pid_dir = os.path.dirname(self.PIDFILE) if not os.access(pid_dir, os.F_OK): sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir) if not os.access(pid_dir, os.W_OK): sys.exit( u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir) else: if self.consoleLogging: print( u'Not running in daemon mode. PID file creation disabled' ) self.CREATEPID = False # If they don't specify a config file then put it in the data dir if not sickbeard.CONFIG_FILE: sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini') # Make sure that we can create the data dir if not os.access(sickbeard.DATA_DIR, os.F_OK): try: os.makedirs(sickbeard.DATA_DIR, 0o744) except os.error: sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the data dir if not os.access(sickbeard.DATA_DIR, os.W_OK): sys.exit( u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the config file if not os.access(sickbeard.CONFIG_FILE, os.W_OK): if os.path.isfile(sickbeard.CONFIG_FILE): sys.exit( u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE) elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK): sys.exit( u'Config file directory: %s must be writeable (write permissions). Exiting' % os.path.dirname(sickbeard.CONFIG_FILE)) os.chdir(sickbeard.DATA_DIR) if self.consoleLogging: print(u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE) # Load the config and publish it to the sickbeard package if not os.path.isfile(sickbeard.CONFIG_FILE): print(u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE) sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) # check all db versions for d, min_v, max_v, mo in [ ('failed.db', sickbeard.failed_db.MIN_DB_VERSION, sickbeard.failed_db.MAX_DB_VERSION, 'FailedDb'), ('cache.db', sickbeard.cache_db.MIN_DB_VERSION, sickbeard.cache_db.MAX_DB_VERSION, 'CacheDb'), ('sickbeard.db', sickbeard.mainDB.MIN_DB_VERSION, sickbeard.mainDB.MAX_DB_VERSION, 'MainDb') ]: cur_db_version = db.DBConnection(d).checkDBVersion() if cur_db_version > 0: if cur_db_version < min_v: print( u'Your [%s] database version (%s) is too old to migrate from with this version of SickGear' % (d, cur_db_version)) sys.exit(u'Upgrade using a previous version of SG first,' + u' or start with no database file to begin fresh') if cur_db_version > max_v: print( u'Your [%s] database version (%s) has been incremented past' u' what this version of SickGear supports. Trying to rollback now. Please wait...' % (d, cur_db_version)) try: rollback_loaded = db.get_rollback_module() if None is not rollback_loaded: rollback_loaded.__dict__[mo]().run(max_v) else: print( u'ERROR: Could not download Rollback Module.') except (StandardError, Exception): pass if db.DBConnection(d).checkDBVersion() > max_v: print(u'Rollback failed.') sys.exit( u'If you have used other forks, your database may be unusable due to their changes' ) print(u'Rollback of [%s] successful.' % d) # Initialize the config and our threads sickbeard.initialize(consoleLogging=self.consoleLogging) if self.runAsDaemon: self.daemonize() # Get PID sickbeard.PID = os.getpid() if self.forcedPort: logger.log(u'Forcing web server to port %s' % self.forcedPort) self.startPort = self.forcedPort else: self.startPort = sickbeard.WEB_PORT if sickbeard.WEB_LOG: self.log_dir = sickbeard.LOG_DIR else: self.log_dir = None # sickbeard.WEB_HOST is available as a configuration value in various # places but is not configurable. It is supported here for historic reasons. if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0': self.webhost = sickbeard.WEB_HOST else: if sickbeard.WEB_IPV6: self.webhost = '::' else: self.webhost = '0.0.0.0' # web server options self.web_options = { 'port': int(self.startPort), 'host': self.webhost, 'data_root': os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), 'web_root': sickbeard.WEB_ROOT, 'log_dir': self.log_dir, 'username': sickbeard.WEB_USERNAME, 'password': sickbeard.WEB_PASSWORD, 'enable_https': sickbeard.ENABLE_HTTPS, 'handle_reverse_proxy': sickbeard.HANDLE_REVERSE_PROXY, 'https_cert': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), 'https_key': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY), } # start web server try: # used to check if existing SG instances have been started sickbeard.helpers.wait_for_free_port(self.web_options['host'], self.web_options['port']) self.webserver = WebServer(self.web_options) self.webserver.start() except Exception: logger.log( u'Unable to start web server, is something else running on port %d?' % self.startPort, logger.ERROR) if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon: logger.log(u'Launching browser and exiting', logger.ERROR) sickbeard.launch_browser(self.startPort) os._exit(1) # Check if we need to perform a restore first restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore') if os.path.exists(restoreDir): if self.restore(restoreDir, sickbeard.DATA_DIR): logger.log(u'Restore successful...') else: logger.log_error_and_exit(u'Restore FAILED!') # Build from the DB to start with self.loadShowsFromDB() # Fire up all our threads sickbeard.start() # Build internal name cache name_cache.buildNameCache() # refresh network timezones network_timezones.update_network_dict() # load all ids from xem startup_background_tasks = threading.Thread( name='FETCH-XEMDATA', target=sickbeard.scene_exceptions.get_xem_ids) startup_background_tasks.start() # sure, why not? if sickbeard.USE_FAILED_DOWNLOADS: failed_history.trimHistory() # Start an update if we're supposed to if self.forceUpdate or sickbeard.UPDATE_SHOWS_ON_START: sickbeard.showUpdateScheduler.action.run( force=True) # @UndefinedVariable # Launch browser if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon): sickbeard.launch_browser(self.startPort) # main loop while True: time.sleep(1)
def MigrationCode(myDB): schema = { 0: sickbeard.mainDB.InitialSchema, 9: sickbeard.mainDB.AddSizeAndSceneNameFields, 10: sickbeard.mainDB.RenameSeasonFolders, 11: sickbeard.mainDB.Add1080pAndRawHDQualities, 12: sickbeard.mainDB.AddShowidTvdbidIndex, 13: sickbeard.mainDB.AddLastUpdateTVDB, 14: sickbeard.mainDB.AddDBIncreaseTo15, 15: sickbeard.mainDB.AddIMDbInfo, 16: sickbeard.mainDB.AddProperNamingSupport, 17: sickbeard.mainDB.AddEmailSubscriptionTable, 18: sickbeard.mainDB.AddProperSearch, 19: sickbeard.mainDB.AddDvdOrderOption, 20: sickbeard.mainDB.AddSubtitlesSupport, 21: sickbeard.mainDB.ConvertTVShowsToIndexerScheme, 22: sickbeard.mainDB.ConvertTVEpisodesToIndexerScheme, 23: sickbeard.mainDB.ConvertIMDBInfoToIndexerScheme, 24: sickbeard.mainDB.ConvertInfoToIndexerScheme, 25: sickbeard.mainDB.AddArchiveFirstMatchOption, 26: sickbeard.mainDB.AddSceneNumbering, 27: sickbeard.mainDB.ConvertIndexerToInteger, 28: sickbeard.mainDB.AddRequireAndIgnoreWords, 29: sickbeard.mainDB.AddSportsOption, 30: sickbeard.mainDB.AddSceneNumberingToTvEpisodes, 31: sickbeard.mainDB.AddAnimeTVShow, 32: sickbeard.mainDB.AddAbsoluteNumbering, 33: sickbeard.mainDB.AddSceneAbsoluteNumbering, 34: sickbeard.mainDB.AddAnimeBlacklistWhitelist, 35: sickbeard.mainDB.AddSceneAbsoluteNumbering2, 36: sickbeard.mainDB.AddXemRefresh, 37: sickbeard.mainDB.AddSceneToTvShows, 38: sickbeard.mainDB.AddIndexerMapping, 39: sickbeard.mainDB.AddVersionToTvEpisodes, 40: sickbeard.mainDB.BumpDatabaseVersion, 41: sickbeard.mainDB.Migrate41, 42: sickbeard.mainDB.Migrate41, 43: sickbeard.mainDB.Migrate43, 44: sickbeard.mainDB.Migrate43, 4301: sickbeard.mainDB.Migrate4301, 4302: sickbeard.mainDB.Migrate4302, 4400: sickbeard.mainDB.Migrate4302, 5816: sickbeard.mainDB.MigrateUpstream, 5817: sickbeard.mainDB.MigrateUpstream, 5818: sickbeard.mainDB.MigrateUpstream, 10000: sickbeard.mainDB.SickGearDatabaseVersion, 10001: sickbeard.mainDB.RemoveDefaultEpStatusFromTvShows, 10002: sickbeard.mainDB.RemoveMinorDBVersion, 10003: sickbeard.mainDB.RemoveMetadataSub, 20000: sickbeard.mainDB.DBIncreaseTo20001, 20001: sickbeard.mainDB.AddTvShowOverview, 20002: sickbeard.mainDB.AddTvShowTags, 20003: sickbeard.mainDB.ChangeMapIndexer, 20004: sickbeard.mainDB.AddShowNotFoundCounter, 20005: sickbeard.mainDB.AddFlagTable, 20006: sickbeard.mainDB.DBIncreaseTo20007, 20007: sickbeard.mainDB.AddWebdlTypesTable, 20008: sickbeard.mainDB.AddWatched, 20009: sickbeard.mainDB.AddPrune, # 20002: sickbeard.mainDB.AddCoolSickGearFeature3, } db_version = myDB.checkDBVersion() logger.log(u'Detected database version: v%s' % db_version, logger.DEBUG) if not (db_version in schema): if db_version == sickbeard.mainDB.MAX_DB_VERSION: logger.log(u'Database schema is up-to-date, no upgrade required') elif db_version < 10000: logger.log_error_and_exit( u'SickGear does not currently support upgrading from this database version' ) else: logger.log_error_and_exit(u'Invalid database version') else: myDB.upgrade_log('Upgrading') while db_version < sickbeard.mainDB.MAX_DB_VERSION: if None is schema[ db_version]: # skip placeholders used when multi PRs are updating DB db_version += 1 continue try: update = schema[db_version](myDB) db_version = update.execute() except Exception as e: myDB.close() logger.log( u'Failed to update database with error: %s attempting recovery...' % ex(e), logger.ERROR) if restoreDatabase(myDB.filename, db_version): # initialize the main SB database logger.log_error_and_exit( u'Successfully restored database version: %s' % db_version) else: logger.log_error_and_exit( u'Failed to restore database version: %s' % db_version) myDB.upgrade_log('Finished')
try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: os._exit(0) except OSError, e: sys.stderr.write('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror)) sys.exit(1) # Write pid if self.CREATEPID: pid = str(os.getpid()) logger.log(u'Writing PID: %s to %s' % (pid, self.PIDFILE)) try: file(self.PIDFILE, 'w').write('%s\n' % pid) except IOError, e: logger.log_error_and_exit( u'Unable to write PID file: %s Error: %s [%s]' % (self.PIDFILE, e.strerror, e.errno)) # Redirect all output sys.stdout.flush() sys.stderr.flush() devnull = getattr(os, 'devnull', '/dev/null') stdin = file(devnull, 'r') stdout = file(devnull, 'a+') stderr = file(devnull, 'a+') os.dup2(stdin.fileno(), sys.stdin.fileno()) os.dup2(stdout.fileno(), sys.stdout.fileno()) os.dup2(stderr.fileno(), sys.stderr.fileno()) @staticmethod def remove_pid_file(PIDFILE):
def start(self): # do some preliminary stuff sickbeard.MY_FULLNAME = os.path.normpath(os.path.abspath(__file__)) sickbeard.MY_NAME = os.path.basename(sickbeard.MY_FULLNAME) sickbeard.PROG_DIR = os.path.dirname(sickbeard.MY_FULLNAME) sickbeard.DATA_DIR = sickbeard.PROG_DIR sickbeard.MY_ARGS = sys.argv[1:] sickbeard.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, '') except (locale.Error, IOError): pass try: sickbeard.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # For OSes that are poorly configured I'll just randomly force UTF-8 if not sickbeard.SYS_ENCODING or sickbeard.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): sickbeard.SYS_ENCODING = 'UTF-8' if not hasattr(sys, 'setdefaultencoding'): reload(sys) try: # pylint: disable=E1101 # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError sys.setdefaultencoding(sickbeard.SYS_ENCODING) except: print 'Sorry, you MUST add the SickGear folder to the PYTHONPATH environment variable' print 'or find another way to force Python to use %s for string encoding.' % sickbeard.SYS_ENCODING sys.exit(1) # Need console logging for SickBeard.py and SickBeard-console.exe self.consoleLogging = (not hasattr(sys, 'frozen')) or (sickbeard.MY_NAME.lower().find('-console') > 0) # Rename the main thread threading.currentThread().name = 'MAIN' try: opts, args = getopt.getopt(sys.argv[1:], 'hfqdp::', ['help', 'forceupdate', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=', 'datadir=', 'config=', 'noresize']) # @UnusedVariable except getopt.GetoptError: sys.exit(self.help_message()) for o, a in opts: # Prints help message if o in ('-h', '--help'): sys.exit(self.help_message()) # For now we'll just silence the logging if o in ('-q', '--quiet'): self.consoleLogging = False # Should we update (from indexer) all shows in the DB right away? if o in ('-f', '--forceupdate'): self.forceUpdate = True # Suppress launching web browser # Needed for OSes without default browser assigned # Prevent duplicate browser window when restarting in the app if o in ('--nolaunch',): self.noLaunch = True # Override default/configured port if o in ('-p', '--port'): try: self.forcedPort = int(a) except ValueError: sys.exit('Port: %s is not a number. Exiting.' % a) # Run as a double forked daemon if o in ('-d', '--daemon'): self.runAsDaemon = True # When running as daemon disable consoleLogging and don't start browser self.consoleLogging = False self.noLaunch = True if sys.platform == 'win32': self.runAsDaemon = False # Write a pidfile if requested if o in ('--pidfile',): self.CREATEPID = True self.PIDFILE = str(a) # If the pidfile already exists, sickbeard may still be running, so exit if os.path.exists(self.PIDFILE): sys.exit('PID file: %s already exists. Exiting.' % self.PIDFILE) # Specify folder to load the config file from if o in ('--config',): sickbeard.CONFIG_FILE = os.path.abspath(a) # Specify folder to use as the data dir if o in ('--datadir',): sickbeard.DATA_DIR = os.path.abspath(a) # Prevent resizing of the banner/posters even if PIL is installed if o in ('--noresize',): sickbeard.NO_RESIZE = True # The pidfile is only useful in daemon mode, make sure we can write the file properly if self.CREATEPID: if self.runAsDaemon: pid_dir = os.path.dirname(self.PIDFILE) if not os.access(pid_dir, os.F_OK): sys.exit(u"PID dir: %s doesn't exist. Exiting." % pid_dir) if not os.access(pid_dir, os.W_OK): sys.exit(u'PID dir: %s must be writable (write permissions). Exiting.' % pid_dir) else: if self.consoleLogging: print u'Not running in daemon mode. PID file creation disabled' self.CREATEPID = False # If they don't specify a config file then put it in the data dir if not sickbeard.CONFIG_FILE: sickbeard.CONFIG_FILE = os.path.join(sickbeard.DATA_DIR, 'config.ini') # Make sure that we can create the data dir if not os.access(sickbeard.DATA_DIR, os.F_OK): try: os.makedirs(sickbeard.DATA_DIR, 0744) except os.error: sys.exit(u'Unable to create data directory: %s Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the data dir if not os.access(sickbeard.DATA_DIR, os.W_OK): sys.exit(u'Data directory: %s must be writable (write permissions). Exiting.' % sickbeard.DATA_DIR) # Make sure we can write to the config file if not os.access(sickbeard.CONFIG_FILE, os.W_OK): if os.path.isfile(sickbeard.CONFIG_FILE): sys.exit(u'Config file: %s must be writeable (write permissions). Exiting.' % sickbeard.CONFIG_FILE) elif not os.access(os.path.dirname(sickbeard.CONFIG_FILE), os.W_OK): sys.exit(u'Config file directory: %s must be writeable (write permissions). Exiting' % os.path.dirname(sickbeard.CONFIG_FILE)) os.chdir(sickbeard.DATA_DIR) if self.consoleLogging: print u'Starting up SickGear from %s' % sickbeard.CONFIG_FILE # Load the config and publish it to the sickbeard package if not os.path.isfile(sickbeard.CONFIG_FILE): print u'Unable to find "%s", all settings will be default!' % sickbeard.CONFIG_FILE sickbeard.CFG = ConfigObj(sickbeard.CONFIG_FILE) CUR_DB_VERSION = db.DBConnection().checkDBVersion() if CUR_DB_VERSION > 0: if CUR_DB_VERSION < MIN_DB_VERSION: print u'Your database version (%s) is too old to migrate from with this version of SickGear' \ % CUR_DB_VERSION sys.exit(u'Upgrade using a previous version of SG first, or start with no database file to begin fresh') if CUR_DB_VERSION > MAX_DB_VERSION: print u'Your database version (%s) has been incremented past what this version of SickGear supports' \ % CUR_DB_VERSION sys.exit( u'If you have used other forks of SG, your database may be unusable due to their modifications') # Initialize the config and our threads sickbeard.initialize(consoleLogging=self.consoleLogging) if self.runAsDaemon: self.daemonize() # Get PID sickbeard.PID = os.getpid() if self.forcedPort: logger.log(u'Forcing web server to port %s' % self.forcedPort) self.startPort = self.forcedPort else: self.startPort = sickbeard.WEB_PORT if sickbeard.WEB_LOG: self.log_dir = sickbeard.LOG_DIR else: self.log_dir = None # sickbeard.WEB_HOST is available as a configuration value in various # places but is not configurable. It is supported here for historic reasons. if sickbeard.WEB_HOST and sickbeard.WEB_HOST != '0.0.0.0': self.webhost = sickbeard.WEB_HOST else: if sickbeard.WEB_IPV6: self.webhost = '::' else: self.webhost = '0.0.0.0' # web server options self.web_options = { 'port': int(self.startPort), 'host': self.webhost, 'data_root': os.path.join(sickbeard.PROG_DIR, 'gui', sickbeard.GUI_NAME), 'web_root': sickbeard.WEB_ROOT, 'log_dir': self.log_dir, 'username': sickbeard.WEB_USERNAME, 'password': sickbeard.WEB_PASSWORD, 'enable_https': sickbeard.ENABLE_HTTPS, 'handle_reverse_proxy': sickbeard.HANDLE_REVERSE_PROXY, 'https_cert': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_CERT), 'https_key': os.path.join(sickbeard.PROG_DIR, sickbeard.HTTPS_KEY), } # start web server try: # used to check if existing SG instances have been started sickbeard.helpers.wait_for_free_port(self.web_options['host'], self.web_options['port']) self.webserver = WebServer(self.web_options) self.webserver.start() except Exception: logger.log(u'Unable to start web server, is something else running on port %d?' % self.startPort, logger.ERROR) if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon: logger.log(u'Launching browser and exiting', logger.ERROR) sickbeard.launchBrowser(self.startPort) os._exit(1) # Check if we need to perform a restore first restoreDir = os.path.join(sickbeard.DATA_DIR, 'restore') if os.path.exists(restoreDir): if self.restore(restoreDir, sickbeard.DATA_DIR): logger.log(u'Restore successful...') else: logger.log_error_and_exit(u'Restore FAILED!') # Build from the DB to start with self.loadShowsFromDB() # Fire up all our threads sickbeard.start() # Build internal name cache name_cache.buildNameCache() # refresh network timezones network_timezones.update_network_dict() # sure, why not? if sickbeard.USE_FAILED_DOWNLOADS: failed_history.trimHistory() # Start an update if we're supposed to if self.forceUpdate or sickbeard.UPDATE_SHOWS_ON_START: sickbeard.showUpdateScheduler.action.run(force=True) # @UndefinedVariable # Launch browser if sickbeard.LAUNCH_BROWSER and not (self.noLaunch or self.runAsDaemon): sickbeard.launchBrowser(self.startPort) # main loop while True: time.sleep(1)