def get_rows(self): try: return self._cursor.execute(''.join(self._select_movies)) except sqlite3.DatabaseError as de: LOG.debug(de.message) raise PlexCleanerException("Unabled to fetch database rows {0}".format(de.message), severity=logging.ERROR)
def backup_database(db): backup_time = datetime.now().strftime('.%Y%m%d-%H%M') backup = os.path.join(os.path.expanduser('~'), ''.join([os.path.basename(db), backup_time, '.bak'])) try: LOG.info("Creating backup for Plex database at {0}".format(backup)) shutil.copy(db, backup) return True except (IOError, OSError) as oe: log_error(oe.errno, backup) raise PlexCleanerException('Unable to create database backup', severity=logging.ERROR)
def __init__(self, db): self.library = [] self.library_paths = [] self.effective_size = 0 self.has_missing_file = False for row in db.get_rows(): movie = Movie(*row) self._update_library(movie) LOG.info("There are {0} different media source".format(len(self.library_paths))) LOG.info("Library size is {0:0.3f} gigabyte".format(self.effective_size * self._B_TO_GB))
def copy_jacket(src, dst, skip): try: if os.path.isfile(dst) and skip: LOG.debug("Jacket '{0}' already exist, skip.".format(dst)) return False shutil.copy(src, dst) return True except (IOError, OSError) as oe: log_error(oe.errno, dst) return False
def __init__(self, metadata_home='/var/lib/plexmediaserver', database_override=None, database_name='com.plexapp.plugins.library.db'): sqlite = sqlite3.sqlite_version_info[:2] if sqlite < (3, 7): raise PlexCleanerException("SQLite bindings are not up to date " "(requires 3.7 current is {0}.{1})".format(*sqlite), severity=logging.ERROR) db = os.path.join(metadata_home, self._database_path, database_name) try: if database_override: db = database_override LOG.debug("User database override {0}".format(db)) LOG.info("Reading Plex database located at {0}".format(db)) self.filename = db self._connection = sqlite3.connect(db) self._cursor = self._connection.cursor() self._cursor.execute('ANALYZE') except sqlite3.OperationalError as oe: LOG.debug(oe) raise PlexCleanerException('Could not connect to Plex database', severity=logging.ERROR) except sqlite3.DatabaseError as de: LOG.debug(de.message) raise PlexCleanerException('Could not open Plex database (check permissions)', severity=logging.ERROR)
def move_media(src, dst, interrupt=False): try: LOG.debug(u"Copy file '{0}' to '{1}'".format(src, dst)) if os.path.isfile(dst): LOG.debug(u"File '{0}' already exist, will override if not the same file.".format(src)) shutil.move(src, dst) return True except (IOError, OSError) as oe: log_error(oe.errno, dst) if interrupt: raise PlexCleanerException('Media movie move error occurred (file missing)', severity=logging.CRITICAL)
def create_dir(dst): try: LOG.debug("Creating directory '{0}'.".format(dst)) os.mkdir(dst) return True except OSError as e: if e.errno == errno.EEXIST: LOG.debug("Directory '{0}' already exist.".format(dst)) return False raise PlexCleanerException("Unable to create directory '{0}' check permissions".format(dst), severity=logging.ERROR)
def create_dir(dst): try: LOG.debug("Creating directory '{0}'.".format(dst)) os.mkdir(dst) return True except OSError as e: if e.errno == errno.EEXIST: LOG.debug("Directory '{0}' already exist.".format(dst)) return False raise PlexCleanerException( "Unable to create directory '{0}' check permissions".format(dst), severity=logging.ERROR)
def _update_library(self, movie): if int(movie.count) > 1: LOG.warning("Movie {0} has duplicate file. Will not process.".format(movie.original_file)) return False self.library.append(movie) if movie.library_path not in self.library_paths: self.library_paths.append(movie.library_path) if movie.exist and movie.matched: # Movie might be in the database but it might be absent in the filesystem self.effective_size += movie.size if not movie.exist: self.has_missing_file = True LOG.warning("The file {0} is missing from the library".format(movie.original_file))
def move_media(src, dst, interrupt=False): try: LOG.debug(u"Copy file '{0}' to '{1}'".format(src, dst)) if os.path.isfile(dst): LOG.debug( u"File '{0}' already exist, will override if not the same file." .format(src)) shutil.move(src, dst) return True except (IOError, OSError) as oe: log_error(oe.errno, dst) if interrupt: raise PlexCleanerException( 'Media movie move error occurred (file missing)', severity=logging.CRITICAL)
def log_error(err, dst): if err == errno.EACCES: LOG.error(u"Not enough permission on: {0}".format(dst)) elif err == errno.ENOSPC: LOG.error(u"Not enough space on destination: {0}".format(os.path.dirname(dst))) elif err == errno.ENOENT: LOG.error(u"Unable to locate source file to copy to {0}".format(dst)) else: LOG.error(u"Unknown error occurred while executing operation to destination: {0}".format(os.path.dirname(dst)))
def log_error(err, dst): if err == errno.EACCES: LOG.error(u"Not enough permission on: {0}".format(dst)) elif err == errno.ENOSPC: LOG.error(u"Not enough space on destination: {0}".format( os.path.dirname(dst))) elif err == errno.ENOENT: LOG.error(u"Unable to locate source file to copy to {0}".format(dst)) else: LOG.error( u"Unknown error occurred while executing operation to destination: {0}" .format(os.path.dirname(dst)))
def update_row(self, mid, value): LOG.debug("Updating movie '{0}' with '{1}'".format(mid, value)) self._cursor.execute(self._update_movie, (value, mid)) self._uncommited = True
def clean(config): LOG.setLevel(logging.getLevelName(config.log_level)) try: if config.update and is_plex_running(): raise PlexCleanerException('Should not update database if Plex is running', severity=logging.ERROR) with database.Database(metadata_home=config.plex_home, database_override=config.database_override) as db: if config.database_backup: backup_database(db.filename) library = Library(db) if not len(library): raise PlexCleanerException('Library is empty', severity=logging.WARNING) if library.has_missing_file and config.interrupt: raise PlexCleanerException('Missing media file on the filesystem', severity=logging.WARNING) if config.export: LOG.info("Will consolidate library in: '{0}'".format(config.export)) has_permission([config.export]) space = get_free_fs_space(config.export) if library.effective_size > space: raise PlexCleanerException('Remaining space on the target filesystem is not enough to export the ' "library {0} Bytes > {1} Bytes".format(library.effective_size, space), severity=logging.CRITICAL) else: has_permission(library.library_paths) for movie in library: LOG.info(u"Processing: '{0}'".format(movie.basename)) if movie.matched: new_path = movie.get_correct_absolute_path(override=config.export) create_dir(new_path) jacket = os.path.join(new_path, config.jacket) copy_jacket(movie.get_metadata_jacket(metadata_home=config.plex_home), jacket, config.skip_jacket) # TODO: Copy SRT to library moved = move_media(movie.original_file, movie.get_correct_absolute_file(override=config.export), config.interrupt) if not moved: LOG.info("{0} was not moved to {1}".format(movie.correct_title, new_path)) elif config.update and movie.need_update(override=config.export): update_database(db, movie) else: LOG.info(u"Movie '{0}' was not matched in Plex".format(movie.basename)) except PlexCleanerException: LOG.warning('PlexCleaner did not process media library.') sys.exit(1) except KeyboardInterrupt: LOG.info('bye') sys.exit(0)
def update_database(db, m): filename = m.get_correct_absolute_file() db.update_row(m.mid, filename) LOG.debug("Updating movie '{0}' with path '{1}'".format(m.correct_title, filename)) return True
def update_many_row(self, values): LOG.debug("Updating {0} movies".format(len(values))) self._cursor.executemany(self._update_movie, values) self._uncommited = True
def commit(self): LOG.debug('Commiting last changes to database.') self._connection.commit() self._uncommited = False
def rollback(self): LOG.debug('Rollback last changes to database.') self._connection.rollback() self._uncommited = False
def clean(config): LOG.setLevel(logging.getLevelName(config.log_level)) try: if config.update and is_plex_running(): raise PlexCleanerException( 'Should not update database if Plex is running', severity=logging.ERROR) with database.Database( metadata_home=config.plex_home, database_override=config.database_override) as db: if config.database_backup: backup_database(db.filename) library = Library(db) if not len(library): raise PlexCleanerException('Library is empty', severity=logging.WARNING) if library.has_missing_file and config.interrupt: raise PlexCleanerException( 'Missing media file on the filesystem', severity=logging.WARNING) if config.export: LOG.info("Will consolidate library in: '{0}'".format( config.export)) has_permission([config.export]) space = get_free_fs_space(config.export) if library.effective_size > space: raise PlexCleanerException( 'Remaining space on the target filesystem is not enough to export the ' "library {0} Bytes > {1} Bytes".format( library.effective_size, space), severity=logging.CRITICAL) else: has_permission(library.library_paths) for movie in library: LOG.info(u"Processing: '{0}'".format(movie.basename)) if movie.matched: new_path = movie.get_correct_absolute_path( override=config.export) create_dir(new_path) jacket = os.path.join(new_path, config.jacket) copy_jacket( movie.get_metadata_jacket( metadata_home=config.plex_home), jacket, config.skip_jacket) # TODO: Copy SRT to library moved = move_media( movie.original_file, movie.get_correct_absolute_file( override=config.export), config.interrupt) if not moved: LOG.info("{0} was not moved to {1}".format( movie.correct_title, new_path)) elif config.update and movie.need_update( override=config.export): update_database(db, movie) else: LOG.info(u"Movie '{0}' was not matched in Plex".format( movie.basename)) except PlexCleanerException: LOG.warning('PlexCleaner did not process media library.') sys.exit(1) except KeyboardInterrupt: LOG.info('bye') sys.exit(0)
def update_database(db, m): filename = m.get_correct_absolute_file() db.update_row(m.mid, filename) LOG.debug("Updating movie '{0}' with path '{1}'".format( m.correct_title, filename)) return True