resources/lib/Library.py
File `Library.py` has 720 lines of code (exceeds 250 allowed). Consider refactoring.# pylint: skip-file# -*- coding: utf-8 -*-# Module: LibraryExporter# Created on: 13.01.2017 import osimport reimport timeimport threadingimport xbmcimport xbmcguiimport xbmcvfsimport requestsfrom resources.lib.utils import noopfrom resources.lib.KodiHelper import KodiHelpertry: import cPickle as pickleDo not use bare 'except'except: import pickle `Library` has 29 functions (exceeds 20 allowed). Consider refactoring.class Library(object): """Exports Netflix shows & movies to a local library folder""" series_label = 'shows' """str: Label to identify shows""" movies_label = 'movies' """str: Label to identify movies""" metadata_label = 'metadata' """str: Label to identify metadata""" imagecache_label = 'imagecache' """str: Label to identify imagecache""" db_filename = 'lib.ndb' """str: (File)Name of the store for the database dump that contains all shows/movies added to the library""" def __init__(self, nx_common): """ Takes the instances & configuration options needed to drive the plugin Parameters ---------- root_folder : :obj:`str` Cookie location library_settings : :obj:`str` User data cache location library_db_path : :obj:`str` User data cache location log_fn : :obj:`fn` optional log function """ enable_custom_folder = nx_common.get_setting('enablelibraryfolder') self.nx_common = nx_common self.kodi_helper = None self.base_data_path = nx_common.data_path self.enable_custom_library_folder = enable_custom_folderLine too long (81 > 79 characters) self.custom_library_folder = nx_common.get_setting('customlibraryfolder') self.db_filepath = os.path.join(self.base_data_path, self.db_filename) self.log = nx_common.log # check for local library folder & set up the paths if self.enable_custom_library_folder != 'true': lib_path = self.base_data_path else: lib_path = self.custom_library_folder self.movie_path = os.path.join(lib_path, self.movies_label) self.tvshow_path = os.path.join(lib_path, self.series_label) self.metadata_path = os.path.join(lib_path, self.metadata_label) self.imagecache_path = os.path.join(lib_path, self.imagecache_label) # check if we need to setup the base folder structure & do so if needed self.setup_local_netflix_library(source={ self.movies_label: self.movie_path, self.series_label: self.tvshow_path, self.metadata_label: self.metadata_path, self.imagecache_label: self.imagecache_path }) # load the local db self.db = self._load_local_db(filename=self.db_filepath) def set_kodi_helper(self, kodi_helper): self.kodi_helper = kodi_helper def setup_local_netflix_library(self, source): """Sets up the basic directories Parameters ---------- source : :obj:`dict` of :obj:`str` Dicitionary with directories to be created """ for label in source: exists = xbmcvfs.exists( path=self.nx_common.check_folder_path(source[label])) if not exists: xbmcvfs.mkdir(source[label]) def write_strm_file(self, path, url, title_player): """Writes the stream file that Kodi can use to integrate it into the DB Parameters ---------- path : :obj:`str` Filepath of the file to be created url : :obj:`str` Stream url title_player : :obj:`str` Video fallback title for m3u """ if isinstance(path, str): logpath = path.decode('ascii', 'ignore').encode('ascii') elif isinstance(path, unicode): logpath = path.encode('ascii', 'ignore') self.log('Writing {}'.format(logpath)) f = xbmcvfs.File(path, 'w') f.write('#EXTINF:-1,'+title_player.encode('utf-8')+'\n') f.write(url) f.close() self.log('Successfully wrote {}'.format(logpath)) Similar blocks of code found in 2 locations. Consider refactoring. def write_metadata_file(self, video_id, content): """Writes the metadata file that caches grabbed content from netflix Parameters ---------- video_id : :obj:`str` ID of video content : Unchanged metadata from netflix """ meta_file = os.path.join(self.metadata_path, video_id+'.meta') if not xbmcvfs.exists(meta_file): f = xbmcvfs.File(meta_file, 'wb') f.write(bytearray(pickle.dumps(content, protocol=2))) f.close() Similar blocks of code found in 2 locations. Consider refactoring. def read_metadata_file(self, video_id): """Reads the metadata file that caches grabbed content from netflix Parameters ---------- video_id : :obj:`str` ID of video content : Unchanged metadata from cache file """ meta_file = os.path.join(self.metadata_path, str(video_id)+'.meta') if xbmcvfs.exists(meta_file): f = xbmcvfs.File(meta_file, 'rb') content = f.read() f.close() meta_data = pickle.loads(content) return meta_data return Similar blocks of code found in 2 locations. Consider refactoring. def read_artdata_file(self, video_id): """Reads the artdata file that caches grabbed content from netflix Parameters ---------- video_id : :obj:`str` ID of video content : Unchanged artdata from cache file """ meta_file = os.path.join(self.metadata_path, str(video_id)+'.art') if xbmcvfs.exists(meta_file): f = xbmcvfs.File(meta_file, 'rb') content = f.read() f.close() meta_data = pickle.loads(content) return meta_data return Similar blocks of code found in 2 locations. Consider refactoring. def write_artdata_file(self, video_id, content): """Writes the art data file that caches grabbed content from netflix Parameters ---------- video_id : :obj:`str` ID of video content : Unchanged artdata from netflix """ meta_file = os.path.join(self.metadata_path, video_id+'.art') if not xbmcvfs.exists(meta_file): f = xbmcvfs.File(meta_file, 'wb') f.write(bytearray(pickle.dumps(content, protocol=2))) f.close() def _load_local_db(self, filename): """Loads the local db file and parses it, creates one if not existent Parameters ---------- filename : :obj:`str` Filepath of db file Returns ------- :obj:`dict` Parsed contents of the db file """ # if the db doesn't exist, create it if not os.path.isfile(filename): data = {self.movies_label: {}, self.series_label: {}} self.log('Setup local library DB') self._update_local_db(filename=filename, db=data) return data with open(filename, 'rb') as f: data = pickle.load(f) if data: return data else: return {} def _update_local_db(self, filename, db): """Updates the local db file with new data Parameters ---------- filename : :obj:`str` Filepath of db file db : :obj:`dict` Database contents Returns ------- bool Update has been successfully executed """ if not os.path.isdir(os.path.dirname(filename)): return False with open(filename, 'wb') as f: f.truncate() pickle.dump(db, f, protocol=2) return True def movie_exists(self, title, year): """Checks if a movie is already present in the local DB Parameters ---------- title : :obj:`str` Title of the movie year : :obj:`int` Release year of the movie Returns ------- bool Movie exists in DB """ title = re.sub(r'[?|$|!|:|#]', r'', title) movie_meta = '%s (%d)' % (title, year) return movie_meta in self.db[self.movies_label] def show_exists(self, title): """Checks if a show is present in the local DB Parameters ---------- title : :obj:`str` Title of the show Returns ------- bool Show exists in DB """ title = re.sub(r'[?|$|!|:|#]', r'', title) show_meta = '%s' % (title) return show_meta in self.db[self.series_label] def season_exists(self, title, season): """Checks if a season is present in the local DB Parameters ---------- title : :obj:`str` Title of the show season : :obj:`int` Season sequence number Returns ------- bool Season of show exists in DB """ title = re.sub(r'[?|$|!|:|#]', r'', title) if self.show_exists(title) is False: return False show_entry = self.db[self.series_label][title] return season in show_entry['seasons'] def episode_exists(self, title, season, episode): """Checks if an episode if a show is present in the local DB Parameters ---------- title : :obj:`str` Title of the show season : :obj:`int` Season sequence number episode : :obj:`int` Episode sequence number Returns ------- bool Episode of show exists in DB """ title = re.sub(r'[?|$|!|:|#]', r'', title) if self.show_exists(title) is False: return False show_entry = self.db[self.series_label][title] episode_entry = 'S%02dE%02d' % (season, episode) return episode_entry in show_entry['episodes'] Function `add_movie` has 5 arguments (exceeds 4 allowed). Consider refactoring. def add_movie(self, title, alt_title, year, video_id, build_url): """Adds a movie to the local db, generates & persists the strm file Parameters ---------- title : :obj:`str` Title of the show alt_title : :obj:`str` Alternative title given by the user year : :obj:`int` Release year of the show video_id : :obj:`str` ID of the video to be played build_url : :obj:`fn` Function to generate the stream url """ title = re.sub(r'[?|$|!|:|#]', r'', title) movie_meta = '%s (%d)' % (title, year) folder = re.sub(r'[?|$|!|:|#]', r'', alt_title) dirname = self.nx_common.check_folder_path( path=os.path.join(self.movie_path, folder)) filename = os.path.join(dirname, movie_meta + '.strm') progress = xbmcgui.DialogProgress() progress.create(self.kodi_helper.get_local_string(650), movie_meta) if xbmcvfs.exists(filename): return if not xbmcvfs.exists(dirname): xbmcvfs.mkdirs(dirname) if self.movie_exists(title=title, year=year) is False: progress.update(50) time.sleep(0.5) self.db[self.movies_label][movie_meta] = {'alt_title': alt_title} self._update_local_db(filename=self.db_filepath, db=self.db) url = build_url({'action': 'play_video', 'video_id': video_id}) self.write_strm_file(path=filename, url=url, title_player=movie_meta) progress.update(100) time.sleep(1)Cyclomatic complexity is too high in method add_show. (9) progress.close() Function `add_show` has 6 arguments (exceeds 4 allowed). Consider refactoring.
Function `add_show` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring. def add_show(self, netflix_id, title, alt_title, episodes, build_url, in_background=False): """Adds a show to the local db, generates & persists the strm files Note: Can also used to store complete seasons or single episodes, it all depends on what is present in the episodes dictionary Parameters ---------- title : :obj:`str` Title of the show alt_title : :obj:`str` Alternative title given by the user episodes : :obj:`dict` of :obj:`dict` Episodes that need to be added build_url : :obj:`fn` Function to generate the stream url """ title = re.sub(r'[?|$|!|:|#]', r'', title) show_meta = '%s' % (title) folder = re.sub(r'[?|$|!|:|#]', r'', alt_title.encode('utf-8')) show_dir = self.nx_common.check_folder_path( path=os.path.join(self.tvshow_path, folder)) progress = self._create_progress_dialog(in_background) progress.create(self.kodi_helper.get_local_string(650), show_meta) if not xbmcvfs.exists(show_dir): self.log('Created show folder {}'.format(show_dir)) xbmcvfs.mkdirs(show_dir) if self.show_exists(title) is False: self.log('Show does not exists, adding entry to internal library') self.db[self.series_label][show_meta] = { 'netflix_id': netflix_id, 'seasons': [], 'episodes': [], 'alt_title': alt_title} else: self.log('Show is present in internal library: {}' .format(self.db[self.series_label][show_meta])) if 'netflix_id' not in self.db[self.series_label][show_meta]: self.db[self.series_label][show_meta]['netflix_id'] = netflix_id self._update_local_db(filename=self.db_filepath, db=self.db) self.log('Added missing netflix_id={} for {} to internal library.' .format(netflix_id, title.encode('utf-8')), xbmc.LOGNOTICE) episodes = [episode for episode in episodes if not self.episode_exists(title, episode['season'], episode['episode'])] self.log('Episodes to export: {}'.format(episodes)) if len(episodes) == 0: self.log('No episodes to export, exiting') return False step = round(100.0 / len(episodes), 1) percent = step for episode in episodes: desc = self.kodi_helper.get_local_string(20373) + ': ' desc += str(episode.get('season')) long_desc = self.kodi_helper.get_local_string(20359) + ': ' long_desc += str(episode.get('episode')) progress.update( percent=int(percent), line1=show_meta, line2=desc, line3=long_desc) self._add_episode( show_dir=show_dir, title=title, season=episode.get('season'), episode=episode.get('episode'), video_id=episode.get('id'), build_url=build_url) percent += step time.sleep(0.05) self._update_local_db(filename=self.db_filepath, db=self.db) time.sleep(1) progress.close() if in_background: self.kodi_helper.dialogs.show_episodes_added_notify( title, len(episodes), self.kodi_helper.icon) return show_dir def _create_progress_dialog(self, is_noop): if is_noop: class NoopDialog(): def create(self, title, subtitle): return noop() def update(self, **kwargs): return noop() def close(self): return noop() return NoopDialog() return xbmcgui.DialogProgress() Function `_add_episode` has 7 arguments (exceeds 4 allowed). Consider refactoring.
Line too long (82 > 79 characters) def _add_episode(self, title, show_dir, season, episode, video_id, build_url): """ Adds a single episode to the local DB, generates & persists the strm file Parameters ---------- title : :obj:`str` Title of the show show_dir : :obj:`str` Directory that holds the stream files for that show season : :obj:`int` Season sequence number episode : :obj:`int` Episode sequence number video_id : :obj:`str` ID of the video to be played build_url : :obj:`fn` Function to generate the stream url """ season = int(season) episode = int(episode) title = re.sub(r'[?|$|!|:|#]', r'', title) self.log('Adding S{}E{} (id={}) of {} (dest={})' .format(season, episode, video_id, title.encode('utf-8'), show_dir)) # add season if self.season_exists(title=title, season=season) is False: self.log( 'Season {} does not exist, adding entry to internal library.' .format(season)) self.db[self.series_label][title]['seasons'].append(season) # add episode episode_meta = 'S%02dE%02d' % (season, episode) episode_exists = self.episode_exists( title=title, season=season, episode=episode) if episode_exists is False: self.log( 'S{}E{} does not exist, adding entry to internal library.' .format(season, episode)) self.db[self.series_label][title]['episodes'].append(episode_meta) # create strm file filename = episode_meta + '.strm' filepath = os.path.join(show_dir, filename) if xbmcvfs.exists(filepath): self.log('strm file {} already exists, not writing it' .format(filepath)) return url = build_url({'action': 'play_video', 'video_id': video_id}) self.write_strm_file( path=filepath, url=url, title_player=title + ' - ' + episode_meta) def remove_movie(self, title, year): """Removes the DB entry & the strm file for the movie given Parameters ---------- title : :obj:`str` Title of the movie year : :obj:`int` Release year of the movie Returns ------- bool Delete successfull """ title = re.sub(r'[?|$|!|:|#]', r'', title) movie_meta = '%s (%d)' % (title, year) folder = re.sub( pattern=r'[?|$|!|:|#]', repl=r'', string=self.db[self.movies_label][movie_meta]['alt_title']) progress = xbmcgui.DialogProgress() progress.create(self.kodi_helper.get_local_string(1210), movie_meta) progress.update(50) time.sleep(0.5) del self.db[self.movies_label][movie_meta] self._update_local_db(filename=self.db_filepath, db=self.db) dirname = self.nx_common.check_folder_path( path=os.path.join(self.movie_path, folder)) filename = os.path.join(self.movie_path, folder, movie_meta + '.strm') if xbmcvfs.exists(dirname): xbmcvfs.delete(filename) xbmcvfs.rmdir(dirname) return True return False time.sleep(1) progress.close() def remove_show(self, title): """Removes the DB entry & the strm files for the show given Parameters ---------- title : :obj:`str` Title of the show Returns ------- bool Delete successfull """ title = re.sub(r'[?|$|!|:|#]', r'', title) label = self.series_label rep_str = self.db[label][title]['alt_title'].encode('utf-8') folder = re.sub( pattern=r'[?|$|!|:|#]', repl=r'', string=rep_str) progress = xbmcgui.DialogProgress() progress.create(self.kodi_helper.get_local_string(1210), title) time.sleep(0.5) del self.db[self.series_label][title] self._update_local_db(filename=self.db_filepath, db=self.db) show_dir = self.nx_common.check_folder_path( path=os.path.join(self.tvshow_path, folder)) if xbmcvfs.exists(show_dir): show_files = xbmcvfs.listdir(show_dir)[1] episode_count_total = len(show_files) step = round(100.0 / episode_count_total, 1) percent = 100 - step for filename in show_files: progress.update(int(percent)) xbmcvfs.delete(os.path.join(show_dir, filename)) percent = percent - step time.sleep(0.05) xbmcvfs.rmdir(show_dir) return True return False time.sleep(1)Cyclomatic complexity is too high in method remove_season. (8) progress.close() Function `remove_season` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring. def remove_season(self, title, season): """Removes the DB entry & the strm files for a season of a show given Parameters ---------- title : :obj:`str` Title of the show season : :obj:`int` Season sequence number Returns ------- bool Delete successfull """ title = re.sub(r'[?|$|!|:|#]', r'', title.encode('utf-8')) season = int(season) season_list = [] episodes_list = [] show_meta = '%s' % (title)Similar blocks of code found in 2 locations. Consider refactoring. for season_entry in self.db[self.series_label][show_meta]['seasons']: if season_entry != season: season_list.append(season_entry) self.db[self.series_label][show_meta]['seasons'] = season_list alt_title = self.db[self.series_label][show_meta]['alt_title'] show_dir = self.nx_common.check_folder_path( path=os.path.join(self.tvshow_path, alt_title)) if xbmcvfs.exists(show_dir):Line too long (108 > 79 characters) show_files = [f for f in xbmcvfs.listdir(show_dir) if xbmcvfs.exists(os.path.join(show_dir, f))] for filename in show_files: if 'S%02dE' % (season) in filename: xbmcvfs.delete(os.path.join(show_dir, filename)) else: episodes_list.append(filename.replace('.strm', '')) self.db[self.series_label][show_meta]['episodes'] = episodes_list self._update_local_db(filename=self.db_filepath, db=self.db) return True def remove_episode(self, title, season, episode): """Removes the DB entry & the strm files for an episode of a show given Parameters ---------- title : :obj:`str` Title of the show season : :obj:`int` Season sequence number episode : :obj:`int` Episode sequence number Returns ------- bool Delete successfull """ title = re.sub(r'[?|$|!|:|#]', r'', title.encode('utf-8')) episodes_list = [] show_meta = '%s' % (title) episode_meta = 'S%02dE%02d' % (season, episode) alt_title = self.db[self.series_label][show_meta]['alt_title'] show_dir = self.nx_common.check_folder_path( path=os.path.join(self.tvshow_path, alt_title)) if xbmcvfs.exists(os.path.join(show_dir, episode_meta + '.strm')): xbmcvfs.delete(os.path.join(show_dir, episode_meta + '.strm'))Similar blocks of code found in 2 locations. Consider refactoring. for episode_entry in self.db[self.series_label][show_meta]['episodes']: if episode_meta != episode_entry: episodes_list.append(episode_entry) self.db[self.series_label][show_meta]['episodes'] = episodes_list self._update_local_db(filename=self.db_filepath, db=self.db) return True def list_exported_media(self): """Return List of exported movies Returns ------- obj:`dict` Contents of export folder """ movies = (['', '']) shows = (['', '']) movie_path = self.movie_path tvshow_path = self.tvshow_path if xbmcvfs.exists(self.nx_common.check_folder_path(movie_path)): movies = xbmcvfs.listdir(movie_path) if xbmcvfs.exists(self.nx_common.check_folder_path(tvshow_path)): shows = xbmcvfs.listdir(tvshow_path) return movies + shows def list_exported_shows(self): return self.db[self.series_label] def get_exported_movie_year(self, title): """Return year of given exported movie Returns ------- obj:`int` year of given movie """ year = '0000' folder = self.nx_common.check_folder_path( path=os.path.join(self.movie_path, title)) if xbmcvfs.exists(folder): file = xbmcvfs.listdir(folder)Identical blocks of code found in 2 locations. Consider refactoring. year = str(file[1]).split('(', 1)[1].split(')', 1)[0]Cyclomatic complexity is too high in method updatedb_from_exported. (9) return int(year) Function `updatedb_from_exported` has a Cognitive Complexity of 21 (exceeds 5 allowed). Consider refactoring. def updatedb_from_exported(self): """Adds movies and shows from exported media to the local db Returns ------- bool Process finished """ tv_show_path = self.tvshow_path db_filepath = self.db_filepath if xbmcvfs.exists(self.nx_common.check_folder_path(self.movie_path)): movies = xbmcvfs.listdir(self.movie_path) for video in movies[0]: folder = os.path.join(self.movie_path, video) file = xbmcvfs.listdir(folder)Identical blocks of code found in 2 locations. Consider refactoring. year = int(str(file[1]).split("(", 1)[1].split(")", 1)[0]) alt_title = unicode(video.decode('utf-8')) title = unicode(video.decode('utf-8')) movie_meta = '%s (%d)' % (title, year) if self.movie_exists(title=title, year=year) is False: self.db[self.movies_label][movie_meta] = { 'alt_title': alt_title} self._update_local_db(filename=db_filepath, db=self.db) if xbmcvfs.exists(self.nx_common.check_folder_path(tv_show_path)): shows = xbmcvfs.listdir(tv_show_path) for video in shows[0]: show_dir = os.path.join(tv_show_path, video) title = unicode(video.decode('utf-8')) alt_title = unicode(video.decode('utf-8')) show_meta = '%s' % (title) if self.show_exists(title) is False: self.db[self.series_label][show_meta] = { 'seasons': [], 'episodes': [], 'alt_title': alt_title} episodes = xbmcvfs.listdir(show_dir) for episode in episodes[1]: file = str(episode).split(".")[0] season = int(str(file).split("S")[1].split("E")[0]) episode = int(str(file).split("E")[1]) episode_meta = 'S%02dE%02d' % (season, episode) episode_exists = self.episode_exists( title=title, season=season, episode=episode)Avoid deeply nested control flow statements. if episode_exists is False:Line too long (94 > 79 characters) self.db[self.series_label][title]['episodes'].append(episode_meta) self._update_local_db( filename=self.db_filepath, db=self.db) return True def download_image_file(self, title, url): """Writes thumb image which is shown in exported Parameters ---------- title : :obj:`str` Filename based on title url : :obj:`str` Image url Returns ------- bool Download triggered """ title = re.sub(r'[?|$|!|:|#]', r'', title.decode('utf-8')) imgfile = title + '.jpg' file = os.path.join(self.imagecache_path, imgfile) folder_movies = self.nx_common.check_folder_path( path=os.path.join(self.movie_path, title)) folder_tvshows = self.nx_common.check_folder_path( path=os.path.join(self.tvshow_path, title)) file_exists = xbmcvfs.exists(file) folder_exists = xbmcvfs.exists(folder_movies) tv_shows_folder = xbmcvfs.exists(folder_tvshows) if not file_exists and (folder_exists or tv_shows_folder): thread = threading.Thread(target=self.fetch_url, args=(url, file)) thread.start() return True def fetch_url(self, url, file): f = xbmcvfs.File(file, 'wb') f.write(requests.get(url).content) f.write(url) f.close() def get_previewimage(self, title): """Load thumb image which is shown in exported Parameters ---------- title : :obj:`str` Filename based on title url : :obj:`str` Image url Returns ------- obj:`int` image of given title if exists """ title = re.sub(r'[?|$|!|:|#]', r'', title) imgfile = title + '.jpg' file = os.path.join(self.imagecache_path, imgfile) if xbmcvfs.exists(file): return file return ""