diff --git a/spotisub/__init__.py b/spotisub/__init__.py index 22ade24..3cdcf35 100644 --- a/spotisub/__init__.py +++ b/spotisub/__init__.py @@ -16,13 +16,13 @@ logging.basicConfig( handlers=[ RotatingFileHandler(os.path.abspath( - os.curdir) + + os.curdir) + "/cache/spotisub.log", maxBytes=1048576, - backupCount=5), + backupCount=5), logging.StreamHandler()], format='%(asctime)s %(levelname)-8s %(message)s', level=int( - os.environ.get(constants.LOG_LEVEL,constants.LOG_LEVEL_DEFAULT_VALUE)), + os.environ.get(constants.LOG_LEVEL, constants.LOG_LEVEL_DEFAULT_VALUE)), datefmt='%Y-%m-%d %H:%M:%S') @@ -37,4 +37,4 @@ configuration_db = SQLAlchemy(spotisub) login = LoginManager(spotisub) login.login_view = 'login' -from spotisub import routes, classes, errors +from spotisub import routes, classes, errors \ No newline at end of file diff --git a/spotisub/database.py b/spotisub/database.py index 574aea5..7148461 100644 --- a/spotisub/database.py +++ b/spotisub/database.py @@ -118,10 +118,10 @@ def __init__(self, dbtype, dbname=''): spotify_song_artist_relation = Table( SPOTIFY_SONG_ARTIST_RELATION, metadata, Column( - 'uuid', - String(36), - primary_key=True, - nullable=False),Column( + 'uuid', + String(36), + primary_key=True, + nullable=False), Column( 'song_relation_uuid', String(36), nullable=False), Column( 'artist_relation_uuid', String(36), nullable=False)) @@ -187,7 +187,8 @@ def __init__(self, dbtype, dbname=''): def create_db_tables(): """Create tables""" dbms.metadata.create_all(dbms.db_engine) - upgrade() + #temp removed, db upgrade will be reimplemented in a future release + #upgrade() def upgrade(): @@ -314,7 +315,7 @@ def insert_song(playlist_info, subsonic_track, else: conn.rollback() return None - + else: conn.rollback() return None @@ -324,6 +325,7 @@ def insert_song(playlist_info, subsonic_track, conn.close() return return_dict + def create_playlist(playlist_info): """Create empty playlist into database""" pl_info = None @@ -344,7 +346,8 @@ def insert_playlist_type(conn, playlist_info): if "uuid" in playlist_info and playlist_info["uuid"] is not None: playlist_info_db = select_playlist_info_by_uuid_with_conn( conn, playlist_info["uuid"]) - if playlist_info_db is None and "name" in playlist_info and playlist_info["name"] is not None: + if playlist_info_db is None and "name" in playlist_info and playlist_info[ + "name"] is not None: playlist_info_db = select_playlist_info_by_name_with_conn( conn, playlist_info["name"]) elif playlist_info_db is None and "import_arg" in playlist_info and playlist_info["import_arg"] is not None: @@ -361,7 +364,7 @@ def insert_playlist_type(conn, playlist_info): subsonic_playlist_id=subsonic_playlist_id_info, subsonic_playlist_name=playlist_info["name"], import_arg=playlist_info["import_arg"], - prefix=playlist_info["prefix"].replace( "\"", "")) + prefix=playlist_info["prefix"].replace("\"", "")) stmt.compile() conn.execute(stmt) logging.info( @@ -377,7 +380,7 @@ def insert_playlist_type(conn, playlist_info): type=playlist_info["type"], subsonic_playlist_id=subsonic_playlist_id_info, subsonic_playlist_name=playlist_info["name"], - prefix=playlist_info["prefix"].replace( "\"", "")) + prefix=playlist_info["prefix"].replace("\"", "")) stmt.compile() conn.execute(stmt) return select_playlist_info_by_uuid_with_conn( @@ -668,7 +671,9 @@ def insert_playlist_relation( conn.execute(stmt) return select_playlist_relation_by_uuid(old_relation.uuid) -def select_playlist_info_by_subsonic_id_with_conn(conn, subsonic_playlist_uuid): + +def select_playlist_info_by_subsonic_id_with_conn( + conn, subsonic_playlist_uuid): """select spotify artists by uuid""" value = None stmt = select( @@ -693,6 +698,7 @@ def select_playlist_info_by_subsonic_id_with_conn(conn, subsonic_playlist_uuid): return value + def select_playlist_info_by_subsonic_id(subsonic_playlist_uuid): """select spotify artists by uuid""" value = None @@ -941,7 +947,7 @@ def count_songs( stmt = stmt.group_by( dbms.subsonic_spotify_relation.c.spotify_song_uuid, - dbms.playlist_info.c.subsonic_playlist_name) + dbms.playlist_info.c.subsonic_playlist_name) stmt = select(func.count()).select_from(stmt.subquery()) @@ -1290,7 +1296,7 @@ def limit_and_order_stmt(stmt, page=None, limit=None, order=None, asc=None): stmt = stmt.limit(limit).offset(page * limit) order_by = [] if order is not None: - order = "case when "+order+" is null then 1 else 0 end, "+order + order = "case when " + order + " is null then 1 else 0 end, " + order if asc: stmt = stmt.order_by(collate(text(order), 'NOCASE')) else: @@ -1311,7 +1317,8 @@ def select_songs_by_artist_uuid( dbms.playlist_info.c.ignored.label('ignored_whole_pl'), dbms.spotify_album.c.name.label('album_name'), dbms.spotify_album.c.ignored.label('spotify_album_ignored'), - dbms.subsonic_spotify_relation.c.uuid.label('subsonic_spotify_relation_uuid'), + dbms.subsonic_spotify_relation.c.uuid.label( + 'subsonic_spotify_relation_uuid'), dbms.subsonic_spotify_relation.c.subsonic_song_id, dbms.playlist_info.c.subsonic_playlist_name, dbms.playlist_info.c.subsonic_playlist_id, @@ -1374,7 +1381,7 @@ def select_count_songs_by_artist_uuid(conn, artist_uuid): stmt = stmt.group_by( dbms.subsonic_spotify_relation.c.spotify_song_uuid, dbms.playlist_info.c.subsonic_playlist_name) - + stmt.compile() stmt = select(func.count()).select_from(stmt.subquery()) @@ -1459,7 +1466,7 @@ def select_count_songs_by_album_uuid(conn, album_uuid): stmt = stmt.group_by( dbms.subsonic_spotify_relation.c.spotify_song_uuid, dbms.playlist_info.c.subsonic_playlist_name) - + stmt.compile() stmt = select(func.count()).select_from(stmt.subquery()) diff --git a/spotisub/generator.py b/spotisub/generator.py index d8be4d8..270dab7 100644 --- a/spotisub/generator.py +++ b/spotisub/generator.py @@ -274,7 +274,8 @@ def show_recommendations_for_artist(uuid): thread.start() thread.join() else: - logging.info("Skipping thread execution becase a full reimport process is running") + logging.info( + "Skipping thread execution becase a full reimport process is running") def show_recommendations_for_artist_run(uuid): @@ -335,7 +336,8 @@ def my_recommendations(uuid): thread.start() thread.join() else: - logging.info("Skipping thread execution becase a full reimport process is running") + logging.info( + "Skipping thread execution becase a full reimport process is running") def my_recommendations_run(uuid): @@ -413,7 +415,8 @@ def get_user_saved_tracks(uuid): thread.start() thread.join() else: - logging.info("Skipping thread execution becase a full reimport process is running") + logging.info( + "Skipping thread execution becase a full reimport process is running") def get_user_saved_tracks_run(uuid): @@ -445,7 +448,8 @@ def get_user_playlists(uuid): thread.start() thread.join() else: - logging.info("Skipping thread execution becase a full reimport process is running") + logging.info( + "Skipping thread execution becase a full reimport process is running") def get_user_playlists_run(uuid, offset=0): @@ -605,7 +609,7 @@ def reimport(uuid): for thread in threading.enumerate(): if (thread.name == playlist_info.type or thread.name.startswith( playlist_info.type)) and thread.is_alive(): - #thread.kill() + # thread.kill() timedelta_sec = timedelta(seconds=30) return playlist_info if playlist_info is not None: @@ -651,6 +655,7 @@ def reimport(uuid): constants.SAVED_GEN_SCHED_DEFAULT_VALUE) return None + def get_tasks(): tasks = [] types = database.select_distinct_type_name() @@ -660,18 +665,24 @@ def get_tasks(): task = {} thread_name = job.id task["type"] = type - task["type_desc"] = "Import " + string.capwords(type.replace("_", " ")) - task["next_execution"] = job.next_run_time.strftime("%d/%m %H:%M:%S") - task["interval"] = str( math.trunc(job.trigger.interval_length / 60 / 60) ) + " hour(s)" + task["type_desc"] = "Import " + \ + string.capwords(type.replace("_", " ")) + task["next_execution"] = job.next_run_time.strftime( + "%d/%m %H:%M:%S") + task["interval"] = str( + math.trunc( + job.trigger.interval_length / 60 / 60)) + " hour(s)" if len(job.args) > 0: - pl_info = database.select_playlist_info_by_uuid(str( job.args[0] )) + pl_info = database.select_playlist_info_by_uuid( + str(job.args[0])) if pl_info is not None: task["args"] = pl_info.subsonic_playlist_name task["uuid"] = job.args[0] - + else: task["args"] = "" - task["running"] = "1" if utils.check_thread_running_by_init_name(thread_name) else "0" + task["running"] = "1" if utils.check_thread_running_by_init_name( + thread_name) else "0" task["id"] = thread_name tasks.append(task) @@ -683,12 +694,14 @@ def get_tasks(): task["uuid"] = "" task["next_execution"] = "Manual" task["interval"] = "" - task["running"] = "1" if utils.check_thread_running_by_name(thread_name) else "0" + task["running"] = "1" if utils.check_thread_running_by_name( + thread_name) else "0" task["id"] = thread_name tasks.append(task) - + return tasks + def poll_playlist(): uuids = [] types = database.select_distinct_type_name() @@ -736,6 +749,7 @@ def scan_library(): scan_artists_top_tracks() scan_user_playlists() + def reimport_all(): """Used to reimport everything""" if not utils.check_thread_running_by_name("reimport_all"): @@ -745,6 +759,7 @@ def reimport_all(): return True return False + def reimport_all_thread(): """Used to reimport everything""" import_all_user_saved_tracks() @@ -753,36 +768,41 @@ def reimport_all_thread(): import_all_artists_recommendations() import_all_artists_top_tracks() + def import_all_user_saved_tracks(): playlist_infos = database.select_playlist_info_by_type( - constants.JOB_ST_ID) + constants.JOB_ST_ID) if len(playlist_infos) > 0: get_user_saved_tracks_run(playlist_infos[0].uuid) + def import_all_my_recommendations(): playlist_infos = database.select_playlist_info_by_type( - constants.JOB_MR_ID) + constants.JOB_MR_ID) if len(playlist_infos) > 0: for playlist_info in playlist_infos: my_recommendations_run(playlist_info.uuid) + def import_all_artists_recommendations(): playlist_infos = database.select_playlist_info_by_type( - constants.JOB_AR_ID) + constants.JOB_AR_ID) if len(playlist_infos) > 0: for playlist_info in playlist_infos: show_recommendations_for_artist_run(playlist_info.uuid) + def import_all_artists_top_tracks(): playlist_infos = database.select_playlist_info_by_type( - constants.JOB_ATT_ID) + constants.JOB_ATT_ID) if len(playlist_infos) > 0: for playlist_info in playlist_infos: artist_top_tracks_run(playlist_info.uuid) + def import_all_user_playlists(): playlist_infos = database.select_playlist_info_by_type( - constants.JOB_UP_ID) + constants.JOB_UP_ID) if len(playlist_infos) > 0: for playlist_info in playlist_infos: get_user_playlists_run(playlist_info.uuid) diff --git a/spotisub/helpers/subsonic_helper.py b/spotisub/helpers/subsonic_helper.py index 9bbba9f..a1a3234 100644 --- a/spotisub/helpers/subsonic_helper.py +++ b/spotisub/helpers/subsonic_helper.py @@ -115,6 +115,7 @@ def load_spotify_object_to_cache(sp, spotify_uri): utils.write_exception() pass + def check_pysonic_connection(): """Return SubsonicOfflineException if pysonic is offline""" if pysonic.ping(): @@ -209,7 +210,7 @@ def add_missing_values_to_track(sp, track): def generate_playlist(playlist_info): """generate empty playlist if not exists""" playlist_info["prefix"] = os.environ.get( - constants.PLAYLIST_PREFIX, constants.PLAYLIST_PREFIX_DEFAULT_VALUE).replace( "\"", "") + constants.PLAYLIST_PREFIX, constants.PLAYLIST_PREFIX_DEFAULT_VALUE).replace("\"", "") return database.create_playlist(playlist_info) @@ -220,17 +221,17 @@ def write_playlist(sp, playlist_info, results): constants.PLAYLIST_PREFIX, constants.PLAYLIST_PREFIX_DEFAULT_VALUE) playlist_id = get_playlist_id_by_name( - playlist_info["prefix"].replace( "\"", "") + playlist_info["name"]) + playlist_info["prefix"].replace("\"", "") + playlist_info["name"]) song_ids = [] old_song_ids = [] if playlist_id is None: check_pysonic_connection().createPlaylist( - name=playlist_info["prefix"].replace( "\"", "") + playlist_info["name"], songIds=[]) + name=playlist_info["prefix"].replace("\"", "") + playlist_info["name"], songIds=[]) logging.info( '(%s) Creating playlist %s', str( threading.current_thread().ident), playlist_info["name"]) playlist_id = get_playlist_id_by_name( - playlist_info["prefix"].replace( "\"", "") + playlist_info["name"]) + playlist_info["prefix"].replace("\"", "") + playlist_info["name"]) database.delete_playlist_relation_by_id(playlist_id) else: old_song_ids = get_playlist_songs_ids_by_id(playlist_id) @@ -275,7 +276,8 @@ def write_playlist(sp, playlist_info, results): if (os.environ.get(constants.SPOTDL_ENABLED, constants.SPOTDL_ENABLED_DEFAULT_VALUE) == "1" and found is False): - if "external_urls" in track and track["external_urls"] is not None and "spotify" in track["external_urls"] and track["external_urls"]["spotify"] is not None: + if "external_urls" in track and track["external_urls"] is not None and "spotify" in track[ + "external_urls"] and track["external_urls"]["spotify"] is not None: is_monitored = True if (os.environ.get(constants.LIDARR_ENABLED, constants.LIDARR_ENABLED_DEFAULT_VALUE) == "1"): @@ -292,7 +294,8 @@ def write_playlist(sp, playlist_info, results): '(%s) This track will be available after ' + 'navidrome rescans your music dir', str(threading.current_thread().ident)) - spotdl_executor.submit(spotdl_helper.download_track, track["external_urls"]["spotify"]) + spotdl_executor.submit( + spotdl_helper.download_track, track["external_urls"]["spotify"]) else: logging.warning( '(%s) Track %s - %s not found in your music library', @@ -861,13 +864,18 @@ def set_ignore(type, uuid, value): elif type == 'playlist': database.update_ignored_playlist(uuid, value) + def download_song(spotipy_helper, uri): sp = spotipy_helper.get_spotipy_client() track = get_spotify_object_from_cache(sp, uri, force=True) - if "external_urls" in track and track["external_urls"] is not None and "spotify" in track["external_urls"] and track["external_urls"]["spotify"] is not None: - spotdl_executor.submit(spotdl_helper.download_track, track["external_urls"]["spotify"]) + if "external_urls" in track and track["external_urls"] is not None and "spotify" in track[ + "external_urls"] and track["external_urls"]["spotify"] is not None: + spotdl_executor.submit( + spotdl_helper.download_track, + track["external_urls"]["spotify"]) return True else: return False + spotify_cache = load_spotify_cache_from_file() diff --git a/spotisub/routes.py b/spotisub/routes.py index e2854a7..a3ac6cd 100644 --- a/spotisub/routes.py +++ b/spotisub/routes.py @@ -51,6 +51,7 @@ tasks_poll_thread = None thread_lock = Lock() + @spotisub.after_request def after_request(response): """Excluding healthcheck endpoint from logging""" @@ -78,10 +79,15 @@ def all_exception_handler(error): api = Api(blueprint, doc='/docs/') spotisub.register_blueprint(blueprint) -socketio = SocketIO(spotisub, async_mode=None, logger=False, engineio_logger=False) +socketio = SocketIO( + spotisub, + async_mode=None, + logger=False, + engineio_logger=False) thread = None thread_lock = Lock() + def get_response_json(data, status): """Generates json response""" r = Response(response=data, status=status, mimetype="application/json") @@ -135,6 +141,7 @@ def overview( asc=asc, sorting_dict=sorting_dict) + @spotisub.route('/reimport_all/') @login_required def reimport_all(uuid=None): @@ -143,16 +150,21 @@ def reimport_all(uuid=None): flash('This import process is already running, please wait for it to finish or restart Spotisub to stop it.') return redirect(url_for('overview')) + @spotisub.route('/reimport//') @login_required def reimport(uuid=None): """Reimport a playlist""" - playlist_info_running = generator.reimport(uuid) + playlist_info_running = generator.reimport(uuid) if playlist_info_running is not None: type = string.capwords(playlist_info_running.type.replace("_", " ")) - flash('A ' + type + ' import process is already running, please wait for it to finish or restart Spotisub to stop it. You can check the status going to System > Tasks.') + flash( + 'A ' + + type + + ' import process is already running, please wait for it to finish or restart Spotisub to stop it. You can check the status going to System > Tasks.') return redirect(url_for('playlist', uuid=uuid)) + @spotisub.route('/download_song//') @login_required def download_song(uuid=None): @@ -173,7 +185,7 @@ def download_song(uuid=None): uri, True), 200) - + @spotisub.route('/') @spotisub.route('/overview_content/') @@ -418,7 +430,7 @@ def logs(): array_lines.append(line.strip()) return render_template('logs.html', title=title, - lines = array_lines) + lines=array_lines) @socketio.event @@ -429,22 +441,33 @@ def connect(): global tasks_poll_thread with thread_lock: if reimport_all_poll_thread is None: - reimport_all_poll_thread = socketio.start_background_task(poll_overview) + reimport_all_poll_thread = socketio.start_background_task( + poll_overview) if playlist_poll_thread is None: - playlist_poll_thread = socketio.start_background_task(poll_playlist) + playlist_poll_thread = socketio.start_background_task( + poll_playlist) if log_poll_thread is None: log_poll_thread = socketio.start_background_task(poll_log) if tasks_poll_thread is None: tasks_poll_thread = socketio.start_background_task(poll_tasks) emit('my_response', {'data': 'Connected', 'count': 0}) + def poll_overview(): with spotisub.test_request_context('/'): while True: if utils.check_thread_running_by_name("reimport_all"): - emit('reimport_all_response', {'data': 'Reimport All job is running', 'status': 1}, namespace='/', broadcast=True) + emit('reimport_all_response', + {'data': 'Reimport All job is running', + 'status': 1}, + namespace='/', + broadcast=True) else: - emit('reimport_all_response', {'data': 'Reimport All job is not running', 'status': 0}, namespace='/', broadcast=True) + emit('reimport_all_response', + {'data': 'Reimport All job is not running', + 'status': 0}, + namespace='/', + broadcast=True) socketio.sleep(5) @@ -453,19 +476,34 @@ def poll_playlist(): while True: uuids = generator.poll_playlist() if len(uuids) > 0: - emit('playlist_response', {'data': 'Playlist import is running', 'uuids': uuids, 'status': 1}, namespace='/', broadcast=True) + emit('playlist_response', + {'data': 'Playlist import is running', + 'uuids': uuids, + 'status': 1}, + namespace='/', + broadcast=True) else: - emit('playlist_response', {'data': 'Playlist import is not running', 'uuids': uuids, 'status': 0}, namespace='/', broadcast=True) + emit('playlist_response', + {'data': 'Playlist import is not running', + 'uuids': uuids, + 'status': 0}, + namespace='/', + broadcast=True) socketio.sleep(5) def poll_tasks(): with spotisub.test_request_context('/'): while True: - emit('tasks_response', generator.get_tasks(), namespace='/', broadcast=True) + emit( + 'tasks_response', + generator.get_tasks(), + namespace='/', + broadcast=True) socketio.sleep(5) -def poll_log(): + +def poll_log(): with spotisub.test_request_context('/'): with open(os.path.abspath(os.curdir) + '/cache/spotisub.log', 'rt') as file: seek = 0 @@ -479,7 +517,9 @@ def poll_log(): if line: if seek != where: sleep_t = None - emit('log_response', {'data': line.strip(), 'status': 1}, namespace='/', broadcast=True) + emit( + 'log_response', { + 'data': line.strip(), 'status': 1}, namespace='/', broadcast=True) else: sleep_t = 0.04 @@ -487,7 +527,8 @@ def poll_log(): if sleep_t: sleep(sleep_t) - + + @spotisub.route('/ignore////') @login_required def ignore(type=None, uuid=None, value=None): diff --git a/spotisub/utils.py b/spotisub/utils.py index a2fed38..1211e10 100644 --- a/spotisub/utils.py +++ b/spotisub/utils.py @@ -160,14 +160,16 @@ def get_sorting_dic(element): if len(element) > 0 and isinstance(element[0], dict): print() + def check_thread_running_by_name(name): for thread in threading.enumerate(): if thread.name == name and thread.is_alive(): return True return False + def check_thread_running_by_init_name(starts_with): for thread in threading.enumerate(): if thread.name.startswith(starts_with) and thread.is_alive(): return True - return False \ No newline at end of file + return False