def __init__(self): self.bitrate = None self.alerts = False self.record_type = None self.download_path = None self.remove = False self.refresh = True self.is_search = False self.duplicates = 0 self.time_machine = None self.dl = None self.db = Database() self.api = PlatformAPI()
def __init__(self): logger.debug("Initializing deemix library") self.db = Database() self.dz = Deezer() if config.deemix_path() == "": self.config_dir = localpaths.getConfigFolder() else: self.config_dir = Path(config.deemix_path()) self.dx_settings = LoadSettings(self.config_dir) logger.debug("deemix " + deemix.__version__) logger.debug(f"deemix config path: {self.config_dir}")
def __init__(self, profile_name): self.db = Database() self.profile_name = profile_name self.profile = None
class ProfileConfig: def __init__(self, profile_name): self.db = Database() self.profile_name = profile_name self.profile = None # TODO move this to utils @staticmethod def print_header(message: str = None): print("deemon Profile Editor") if message: print(":: " + message + "\n") else: print("") def edit(self): profile = self.db.get_profile(self.profile_name) self.print_header( f"Configuring '{profile['name']}' (Profile ID: {profile['id']})") modified = 0 for property in profile: if property == "id": continue allowed_opts = config.allowed_values(property) if isinstance(allowed_opts, dict): allowed_opts = [str(x.lower()) for x in allowed_opts.values()] while True: friendly_text = property.replace("_", " ").title() user_input = input( f"{friendly_text} [{profile[property]}]: ").lower() if user_input == "": break # TODO move to function to share with Config.set()? elif user_input == "false" or user_input == "0": user_input = False elif user_input == "true" or user_input == "1": user_input = True elif property == "name" and self.profile_name != user_input: if self.db.get_profile(user_input): print("Name already in use") continue if user_input == "none" and property != "name": user_input = None elif allowed_opts: if user_input not in allowed_opts: print(f"Allowed options: " + ', '.join(str(x) for x in allowed_opts)) continue logger.debug(f"User set {property} to {user_input}") profile[property] = user_input modified += 1 break if modified > 0: user_input = input("\n:: Save these settings? [y|N] ") if user_input.lower() != "y": logger.info("No changes made, exiting...") else: self.db.update_profile(profile) print(f"\nProfile '{profile['name']}' has been updated!") else: print("No changes made, exiting...") def add(self): new_profile = {} profile_config = self.db.get_profile(self.profile_name) if profile_config: return logger.error(f"Profile {self.profile_name} already exists") else: logger.info("Adding new profile: " + self.profile_name) print( "** Any option left blank will fallback to global config **\n") new_profile['name'] = self.profile_name menu = [ { 'setting': 'email', 'type': str, 'text': 'Email address', 'allowed': [] }, { 'setting': 'alerts', 'type': bool, 'text': 'Alerts', 'allowed': config.allowed_values('alerts') }, { 'setting': 'bitrate', 'type': str, 'text': 'Bitrate', 'allowed': config.allowed_values('bitrate').values() }, { 'setting': 'record_type', 'type': str, 'text': 'Record Type', 'allowed': config.allowed_values('record_type') }, { 'setting': 'plex_baseurl', 'type': str, 'text': 'Plex Base URL', 'allowed': [] }, { 'setting': 'plex_token', 'type': str, 'text': 'Plex Token', 'allowed': [] }, { 'setting': 'plex_library', 'type': str, 'text': 'Plex Library', 'allowed': [] }, { 'setting': 'download_path', 'type': str, 'text': 'Download Path', 'allowed': [] }, ] for m in menu: repeat = True while repeat: i = input(m['text'] + ": ") if i == "": new_profile[m['setting']] = None break if not isinstance(i, m['type']): try: i = int(i) except ValueError: print(" - Allowed options: " + ', '.join(str(x) for x in m['allowed'])) continue if len(m['allowed']) > 0: if i not in m['allowed']: print(" - Allowed options: " + ', '.join(str(x) for x in m['allowed'])) continue new_profile[m['setting']] = i break print("\n") i = input(":: Save these settings? [y|N] ") if i.lower() != "y": return logger.info("Operation cancelled. No changes saved.") else: self.db.create_profile(new_profile) logger.debug( f"New profile created with the following configuration: {new_profile}" ) def delete(self): profile_config = self.db.get_profile(self.profile_name) if not profile_config: return logger.error(f"Profile {self.profile_name} not found") if profile_config['id'] == 1: return logger.info("You cannot delete the default profile.") i = input(f":: Remove the profile '{self.profile_name}'? [y|N] ") if i.lower() == "y": self.db.delete_profile(self.profile_name) return logger.info("Profile " + self.profile_name + " deleted.") else: return logger.info("Operation cancelled") def show(self): if not self.profile_name: profile = self.db.get_all_profiles() self.print_header(f"Showing all profiles") else: profile = [self.db.get_profile(self.profile_name)] self.print_header( f"Showing profile '{profile[0]['name']}' (Profile ID: {profile[0]['id']})" ) if len(profile) == 0: return logger.error(f"Profile {self.profile_name} not found") print("{:<10} {:<40} {:<8} {:<8} {:<8} {:<25} " "{:<20} {:<20} {:<20}".format('Name', 'Email', 'Alerts', 'Bitrate', 'Type', 'Plex Base URL', 'Plex Token', 'Plex Library', 'Download Path')) for u in profile: id, name, email, alerts, bitrate, rtype, url, token, \ lib, dl_path = [x if x is not None else '' for x in u.values()] print("{:<10} {:<40} {:<8} {:<8} {:<8} {:<25} " "{:<20} {:<20} {:<20}".format(name, email, alerts, bitrate, rtype, url, token, lib, dl_path)) print("") def clear(self): profile = self.db.get_profile(self.profile_name) self.print_header( f"Configuring '{profile['name']}' (Profile ID: {profile['id']})") if not profile: return logger.error(f"Profile {self.profile_name} not found") for value in profile: if value in ["id", "name"]: continue profile[value] = None self.db.update_profile(profile) logger.info("All values have been cleared.")
def run(whats_new, verbose, profile): """Monitoring and alerting tool for new music releases using the Deezer API. deemon is a free and open source tool. To report issues or to contribute, please visit https://github.com/digitalec/deemon """ global logger global config global db setup_logger(log_level='DEBUG' if verbose else 'INFO', log_file=startup.get_log_file()) logger = logging.getLogger(__name__) logger.debug(f"deemon {__version__}") logger.debug(f"command: \"{' '.join([x for x in sys.argv[1:]])}\"") logger.debug("Python " + platform.python_version()) logger.debug(platform.platform()) logger.debug(f"deemon appdata is located at {startup.get_appdata_dir()}") if whats_new: return startup.get_changelog(__version__) config = Config() db = Database() db.do_upgrade() tid = db.get_next_transaction_id() config.set('tid', tid, validate=False) if profile: profile_config = db.get_profile(profile) if profile_config: LoadProfile(profile_config) else: logger.error(f"Profile {profile} does not exist.") sys.exit(1) else: profile_config = db.get_profile_by_id(1) if profile_config: LoadProfile(profile_config) if not any(x in sys.argv[1:] for x in ['-h', '--help']): last_checked: int = int(db.last_update_check()) next_check: int = last_checked + (config.check_update() * 86400) if config.release_channel() != db.get_release_channel()['value']: # If release_channel has changed, check for latest release logger.debug(f"Release channel changed to '{config.release_channel()}'") db.set_release_channel() last_checked = 0 if time.time() >= next_check or last_checked == 0: logger.info(f"Checking for updates ({config.release_channel()})...") config.set('update_available', 0, False) latest_ver = str(startup.get_latest_version(config.release_channel())) if latest_ver: db.set_latest_version(latest_ver) db.set_last_update_check() new_version = db.get_latest_ver() if parse_version(new_version) > parse_version(__version__): if parse_version(new_version).major > parse_version(__version__).major: config.set('update_available', new_version, False) print("*" * 80) logger.info(f"deemon {parse_version(new_version).major} is available. " f"Please see the release notes before upgrading.") logger.info("Release notes available at: https://github.com/digitalec/deemon/releases") print("*" * 80) else: config.set('update_available', new_version, False) print("*" * 50) logger.info(f"* New version is available: v{__version__} -> v{new_version}") if config.release_channel() == "beta": logger.info("* To upgrade, run `pip install --upgrade --pre deemon`") else: logger.info("* To upgrade, run `pip install --upgrade deemon`") print("*" * 50) print("") config.set("start_time", int(time.time()), False)
import logging from deemon.core.db import Database from deemon.utils import dates logger = logging.getLogger(__name__) db = Database() def view_transactions(): transactions = db.get_transactions() if not transactions: return logger.info("No transactions are available to be rolled back.") for i, transaction in enumerate(transactions, start=1): release_id = [] artist_names = [] playlist_titles = [] for k, v in transaction.items(): if (k == "releases" or k == "playlist_tracks") and transaction[k]: for item in transaction[k]: for key, val in item.items(): if key == "album_id" or key == "track_id": if item[key] not in release_id: release_id.append(item[key]) if k == "monitor" and transaction[k]: if transaction[k] not in artist_names: artist_names = [x['artist_name'] for x in transaction[k]] if k == "playlists" and transaction[k]: if transaction[k] not in playlist_titles:
def __init__(self): self.db = Database()
class Show: def __init__(self): self.db = Database() def monitoring(self, artist: bool = True, query: str = None, export_csv: bool = False, save_path: Union[str, Path] = None, filter: str = None, hide_header: bool = False, is_id: bool = False, backup: Union[str, Path] = None): def csv_output(line: str): if save_path: output_to_file.append(line) else: print(line) output_to_file = [] if backup: export_csv = True filter = "id" hide_header = True save_path = backup if artist: if query: db_result = self.db.get_monitored_artist_by_name(query) else: db_result = self.db.get_all_monitored_artists() if not db_result: if query: return logger.error("Artist not found: " + str(query)) else: return logger.error("No artists are being monitored") else: if query: if is_id: try: query = int(query) except ValueError: return logger.error(f"Invalid Playlist ID - {query}") db_result = self.db.get_monitored_playlist_by_id(query) else: db_result = self.db.get_monitored_playlist_by_name(query) else: db_result = self.db.get_all_monitored_playlists() if not db_result: if query: return logger.error("Playlist/ID not found: " + str(query)) else: return logger.error("No playlists are being monitored") if artist and query: for key, val in db_result.items(): if val == None: db_result[key] = "-" print("{:<10} {:<35} {:<10} {:<10} {:<10} {:<25}".format( 'ID', 'Artist', 'Alerts', 'Bitrate', 'Type', 'Download Path')) print( "{!s:<10} {!s:<35} {!s:<10} {!s:<10} {!s:<10} {!s:<25}".format( db_result['artist_id'], db_result['artist_name'], db_result['alerts'], db_result['bitrate'], db_result['record_type'], db_result['download_path'])) print("") elif not artist and query: for key, val in db_result.items(): if val == None: db_result[key] = "-" print("{:<15} {:<30} {:<50} {:<10} {:<10} {:<25}".format( 'ID', 'Title', 'URL', 'Alerts', 'Bitrate', 'Download Path')) print("{!s:<15} {!s:<30} {!s:<50} {!s:<10} {!s:<10} {!s:<25}". format(db_result['id'], db_result['title'], db_result['url'], db_result['alerts'], db_result['bitrate'], db_result['download_path'])) print("") else: if export_csv or save_path: if artist: if not filter: filter = "name,id,bitrate,alerts,type,path" filter = filter.split(',') logger.debug( f"Generating CSV data using filters: {', '.join(filter)}" ) column_names = [ 'artist_id' if x == 'id' else x for x in filter ] column_names = [ 'artist_name' if x == 'name' else x for x in column_names ] column_names = [ 'record_type' if x == 'type' else x for x in column_names ] column_names = [ 'download_path' if x == 'path' else x for x in column_names ] else: if not filter: filter = "id,title,url,bitrate,alerts,path" filter = filter.split(',') logger.debug( f"Generating CSV data using filters: {', '.join(filter)}" ) column_names = [ 'download_path' if x == 'path' else x for x in filter ] for column in column_names: if not len([x for x in db_result if column in x.keys()]): logger.warning(f"Unknown filter specified: {column}") column_names.remove(column) if not hide_header: csv_output(','.join(filter)) for artist in db_result: filtered_artists = [] for key, value in artist.items(): if value is None: artist[key] = "" for column in column_names: filtered_artists.append(str(artist[column])) if len(filtered_artists): for i, a in enumerate(filtered_artists): if '"' in a: a = a.replace('"', "'") if ',' in a: filtered_artists[i] = f'"{a}"' csv_output(",".join(filtered_artists)) if output_to_file: if Path(save_path).is_dir(): output_filename = Path( save_path / f"{generate_date_filename('deemon_')}.csv") else: output_filename = Path(save_path) with open(output_filename, 'w', encoding="utf-8") as f: for line in output_to_file: if line == output_to_file[-1]: f.writelines(line) break f.writelines(line + "\n") return logger.info("CSV data has been saved to: " + str(output_filename)) return if artist: db_result = [x['artist_name'] for x in db_result] else: db_result = [x['title'] for x in db_result] if len(db_result) < 10: for artist in db_result: print(artist) else: db_result = self.truncate_long_artists(db_result) try: size = os.get_terminal_size() max_cols = (int(size.columns / 30)) except: max_cols = 5 if max_cols > 5: max_cols = 5 while len(db_result) % max_cols != 0: db_result.append(" ") if max_cols >= 5: for a, b, c, d, e in zip(db_result[0::5], db_result[1::5], db_result[2::5], db_result[3::5], db_result[4::5]): print('{:<30}{:<30}{:<30}{:<30}{:<30}'.format( a, b, c, d, e)) elif max_cols >= 4: for a, b, c, d in zip(db_result[0::4], db_result[1::4], db_result[2::4], db_result[3::4]): print('{:<30}{:<30}{:<30}{:<30}'.format(a, b, c, d)) elif max_cols >= 3: for a, b, c in zip(db_result[0::3], db_result[1::3], db_result[2::3]): print('{:<30}{:<30}{:<30}'.format(a, b, c)) elif max_cols >= 2: for a, b in zip(db_result[0::2], db_result[1::2]): print('{:<30}{:<30}'.format(a, b)) else: for a in db_result: print(a) def playlists(self, csv=False): monitored_playlists = self.db.get_all_monitored_playlists() for p in monitored_playlists: print(f"{p[1]} ({p[2]})") @staticmethod def truncate_long_artists(all_artists): for idx, artist in enumerate(all_artists): if len(artist) > 25: all_artists[idx] = artist[:22] + "..." all_artists[idx] = artist return all_artists def releases(self, days, future): if future: future_releases = self.db.get_future_releases() future_release_list = [x for x in future_releases] if len(future_release_list) > 0: logger.info(f"Future releases:") print("") future_release_list.sort(key=lambda x: x['album_release'], reverse=True) for release in future_release_list: print('+ [%-10s] %s - %s' % (release['album_release'], release['artist_name'], release['album_name'])) else: logger.info("No future releases have been detected") else: seconds_per_day = 86400 days_in_seconds = (days * seconds_per_day) now = int(time.time()) back_date = (now - days_in_seconds) releases = self.db.show_new_releases(back_date, now) release_list = [x for x in releases] if len(release_list) > 0: logger.info(f"New releases found within last {days} day(s):") print("") release_list.sort(key=lambda x: x['album_release'], reverse=True) for release in release_list: print('+ [%-10s] %s - %s' % (release['album_release'], release['artist_name'], release['album_name'])) else: logger.info(f"No releases found in the last {days} day(s)")
class DeemixInterface: def __init__(self): logger.debug("Initializing deemix library") self.db = Database() self.dz = Deezer() if config.deemix_path() == "": self.config_dir = localpaths.getConfigFolder() else: self.config_dir = Path(config.deemix_path()) self.dx_settings = LoadSettings(self.config_dir) logger.debug("deemix " + deemix.__version__) logger.debug(f"deemix config path: {self.config_dir}") def download_url(self, url, bitrate, download_path, override_deemix=True): if override_deemix: deemix.generatePlaylistItem = self.generatePlaylistItem if download_path: self.dx_settings['downloadLocation'] = download_path logger.debug(f"deemix download path set to: {self.dx_settings['downloadLocation']}") links = [] for link in url: if ';' in link: for l in link.split(";"): links.append(l) else: links.append(link) for link in links: download_object = generateDownloadObject(self.dz, link, bitrate) if isinstance(download_object, list): for obj in download_object: Downloader(self.dz, obj, self.dx_settings).start() else: Downloader(self.dz, download_object, self.dx_settings).start() def deezer_acct_type(self): user_session = self.dz.get_session()['current_user'] if user_session.get('can_stream_lossless'): logger.debug("Deezer account connected and supports lossless") config.set('deezer_quality', 'lossless', validate=False) elif user_session.get('can_stream_hq'): logger.debug("Deezer account connected and supports high quality") config.set('deezer_quality', 'hq', validate=False) else: logger.warning("Deezer account connected but only supports 128k") config.set('deezer_quality', 'lq', validate=False) def verify_arl(self, arl): if not self.dz.login_via_arl(arl): print("FAILED") logger.debug(f"ARL Failed: {arl}") return False self.deezer_acct_type() print("OK") logger.debug("ARL is valid") return True def login(self): failed_logins = 0 logger.debug("Looking for ARL...") if config.arl(): logger.debug("ARL found in deemon config") print(":: Found ARL in deemon config, checking... ", end="") if self.verify_arl(config.arl()): return True else: logger.error("Unable to login using ARL found in deemon config") failed_logins += 1 else: logger.debug("ARL was not found in deemon config, checking if deemix has it...") if self.config_dir.is_dir(): if Path(self.config_dir / '.arl').is_file(): with open(self.config_dir / '.arl', 'r') as f: arl_from_file = f.readline().rstrip("\n") logger.debug("ARL found in deemix config") print(":: Found ARL in deemix .arl file, checking... ", end="") if self.verify_arl(arl_from_file): return True else: logger.error("Unable to login using ARL found in deemix config directory") failed_logins += 1 else: logger.debug(f"ARL not found in {self.config_dir}") else: logger.error(f"ARL directory {self.config_dir} was not found") if failed_logins > 1: notification = notifier.Notify() notification.expired_arl() else: logger.error("No ARL was found, aborting...") return False def generatePlaylistItem(self, dz, link_id, bitrate, playlistAPI=None, playlistTracksAPI=None): if not playlistAPI: if not str(link_id).isdecimal(): raise InvalidID(f"https://deezer.com/playlist/{link_id}") # Get essential playlist info try: playlistAPI = dz.api.get_playlist(link_id) except APIError: playlistAPI = None # Fallback to gw api if the playlist is private if not playlistAPI: try: userPlaylist = dz.gw.get_playlist_page(link_id) playlistAPI = map_user_playlist(userPlaylist['DATA']) except GWAPIError as e: raise GenerationError(f"https://deezer.com/playlist/{link_id}", str(e)) from e # Check if private playlist and owner if not playlistAPI.get('public', False) and playlistAPI['creator']['id'] != str(self.dz.current_user['id']): logger.warning("You can't download others private playlists.") raise NotYourPrivatePlaylist(f"https://deezer.com/playlist/{link_id}") if not playlistTracksAPI: playlistTracksAPI = dz.gw.get_playlist_tracks(link_id) playlistAPI['various_artist'] = dz.api.get_artist(5080) # Useful for save as compilation totalSize = len(playlistTracksAPI) playlistAPI['nb_tracks'] = totalSize collection = [] for pos, trackAPI in enumerate(playlistTracksAPI, start=1): # # BEGIN DEEMON PATCH # vals = {'track_id': trackAPI['SNG_ID'], 'playlist_id': playlistAPI['id']} sql = "SELECT * FROM 'playlist_tracks' WHERE track_id = :track_id AND playlist_id = :playlist_id" result = self.db.query(sql, vals).fetchone() if result: continue # # END DEEMON PATCH # trackAPI = map_track(trackAPI) if trackAPI['explicit_lyrics']: playlistAPI['explicit'] = True if 'track_token' in trackAPI: del trackAPI['track_token'] trackAPI['position'] = pos collection.append(trackAPI) if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False return Collection({ 'type': 'playlist', 'id': link_id, 'bitrate': bitrate, 'title': playlistAPI['title'], 'artist': playlistAPI['creator']['name'], 'cover': playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg', 'explicit': playlistAPI['explicit'], 'size': totalSize, 'collection': { 'tracks': collection, 'playlistAPI': playlistAPI } })
class Monitor: def __init__(self): self.bitrate = None self.alerts = False self.record_type = None self.download_path = None self.remove = False self.refresh = True self.is_search = False self.duplicates = 0 self.time_machine = None self.dl = None self.db = Database() self.api = PlatformAPI() def set_config(self, bitrate: str, alerts: bool, record_type: str, download_path: Path): self.bitrate = bitrate self.alerts = alerts self.record_type = record_type self.download_path = download_path self.debugger("SetConfig", {'bitrate': bitrate, 'alerts': alerts, 'type': record_type, 'path': download_path}) def set_options(self, remove, dl_all, search): self.remove = True if remove else False self.dl = True if dl_all else False self.is_search = True if search else False self.debugger("SetOptions", {'remove': remove, 'dl': dl_all, 'search': search}) def debugger(self, message: str, payload=None): if config.debug_mode(): if not payload: payload = "" logger.debug(f"DEBUG_MODE: {message} {str(payload)}") def get_best_result(self, api_result) -> list: name = api_result['query'] if self.is_search: logger.debug("Waiting for user input...") prompt = self.prompt_search(name, api_result['results']) if prompt: logger.debug(f"User selected {prompt}") return [prompt] matches = [r for r in api_result['results'] if r['name'].lower() == name.lower()] self.debugger("Matches", matches) if len(matches) == 1: return [matches[0]] elif len(matches) > 1: logger.debug(f"Multiple matches were found for artist \"{api_result['query']}\"") if config.prompt_duplicates(): logger.debug("Waiting for user input...") prompt = self.prompt_search(name, matches) if prompt: logger.debug(f"User selected {prompt}") return [prompt] else: logger.info(f"No selection made, skipping {name}...") return [] else: self.duplicates += 1 return [matches[0]] elif not len(matches): logger.debug(f" [!] No matches were found for artist \"{api_result['query']}\"") if config.prompt_no_matches() and len(api_result['results']): logger.debug("Waiting for user input...") prompt = self.prompt_search(name, api_result['results']) if prompt: logger.debug(f"User selected {prompt}") return [prompt] else: logger.info(f"No selection made, skipping {name}...") return [] else: logger.info(f" [!] Artist {name} not found") return [] def prompt_search(self, value, api_result): menu = search.Search() ask_user = menu.artist_menu(value, api_result, True) if ask_user: return {'id': ask_user['id'], 'name': ask_user['name']} return logger.debug("No artist selected, skipping...") # @performance.timeit def build_artist_query(self, api_result: list): existing = self.db.get_all_monitored_artist_ids() artists_to_add = [] pbar = tqdm(api_result, total=len(api_result), desc="Setting up artists for monitoring...", ascii=" #", bar_format=ui.TQDM_FORMAT) for artist in pbar: if artist is None: continue if artist['id'] in existing: logger.info(f" - Already monitoring {artist['name']}, skipping...") else: artist.update({'bitrate': self.bitrate, 'alerts': self.alerts, 'record_type': self.record_type, 'download_path': self.download_path, 'profile_id': config.profile_id(), 'trans_id': config.transaction_id()}) artists_to_add.append(artist) if len(artists_to_add): logger.debug("New artists have been monitored. Saving changes to the database...") self.db.new_transaction() self.db.fast_monitor(artists_to_add) self.db.commit() return True def build_playlist_query(self, api_result: list): existing = self.db.get_all_monitored_playlist_ids() or [] playlists_to_add = [] pbar = tqdm(api_result, total=len(api_result), desc="Setting up playlists for monitoring...", ascii=" #", bar_format=ui.TQDM_FORMAT) for i, playlist in enumerate(pbar): if not playlist: continue if playlist['id'] in existing: logger.info(f" Already monitoring {playlist['title']}, skipping...") else: playlist.update({'bitrate': self.bitrate, 'alerts': self.alerts, 'download_path': self.download_path, 'profile_id': config.profile_id(), 'trans_id': config.transaction_id()}) playlists_to_add.append(playlist) if len(playlists_to_add): logger.debug("New playlists have been monitored. Saving changes to the database...") self.db.new_transaction() self.db.fast_monitor_playlist(playlists_to_add) self.db.commit() return True def call_refresh(self): refresh = Refresh(self.time_machine, ignore_filters=self.dl) refresh.run() # @performance.timeit def artists(self, names: list) -> None: """ Return list of dictionaries containing each artist """ if self.remove: return self.purge_artists(names=names) self.debugger("SpawningThreads", self.api.max_threads) with ThreadPoolExecutor(max_workers=self.api.max_threads) as ex: api_result = list( tqdm(ex.map(self.api.search_artist, names), total=len(names), desc=f"Fetching artist data for {len(names):,} artist(s), please wait...", ascii=" #", bar_format=ui.TQDM_FORMAT)) select_artist = tqdm(api_result, total=len(api_result), desc="Examining results for best match...", ascii=" #", bar_format=ui.TQDM_FORMAT) to_monitor = [] for artist in select_artist: best_result = self.get_best_result(artist) if best_result: to_monitor.append(best_result) to_process = [item for elem in to_monitor for item in elem if len(item)] if self.build_artist_query(to_process): self.call_refresh() else: print("") logger.info("No new artists have been added, skipping refresh.") # @performance.timeit def artist_ids(self, ids: list): ids = [int(x) for x in ids] if self.remove: return self.purge_artists(ids=ids) self.debugger("SpawningThreads", self.api.max_threads) with ThreadPoolExecutor(max_workers=self.api.max_threads) as ex: api_result = list( tqdm(ex.map(self.api.get_artist_by_id, ids), total=len(ids), desc=f"Fetching artist data for {len(ids):,} artist(s), please wait...", ascii=" #", bar_format=ui.TQDM_FORMAT)) if self.build_artist_query(api_result): self.call_refresh() else: print("") logger.info("No new artists have been added, skipping refresh.") # @performance.timeit def importer(self, import_path: str): if Path(import_path).is_file(): imported_file = dataprocessor.read_file_as_csv(import_path) artist_list = dataprocessor.process_input_file(imported_file) if isinstance(artist_list[0], int): self.artist_ids(artist_list) else: self.artists(artist_list) elif Path(import_path).is_dir(): import_list = [x.relative_to(import_path).name for x in sorted(Path(import_path).iterdir()) if x.is_dir()] if import_list: self.artists(import_list) else: logger.error(f"File or directory not found: {import_path}") return # @performance.timeit def playlists(self, playlists: list): if self.remove: return self.purge_playlists(ids=playlists) ids = [int(x) for x in playlists] self.debugger("SpawningThreads", self.api.max_threads) with ThreadPoolExecutor(max_workers=self.api.max_threads) as ex: api_result = list( tqdm(ex.map(self.api.get_playlist, ids), total=len(ids), desc=f"Fetching playlist data for {len(ids):,} playlist(s), please wait...", ascii=" #", bar_format=ui.TQDM_FORMAT)) if self.build_playlist_query(api_result): self.call_refresh() else: print("") logger.info("No new playlists have been added, skipping refresh.") # @performance.timeit def purge_artists(self, names: list = None, ids: list = None): if names: for n in names: monitored = self.db.get_monitored_artist_by_name(n) if monitored: self.db.remove_monitored_artist(monitored['artist_id']) logger.info(f"No longer monitoring {monitored['artist_name']}") else: logger.info(f"{n} is not being monitored yet") if ids: for i in ids: monitored = self.db.get_monitored_artist_by_id(i) if monitored: self.db.remove_monitored_artist(monitored['artist_id']) logger.info(f"\nNo longer monitoring {monitored['artist_name']}") else: logger.info(f"{i} is not being monitored yet") def purge_playlists(self, titles: list = None, ids: list = None): if ids: for i in ids: monitored = self.db.get_monitored_playlist_by_id(i) if monitored: self.db.remove_monitored_playlists(monitored['id']) logger.info(f"\nNo longer monitoring {monitored['title']}") else: logger.info(f"{i} is not being monitored yet")