From 4f757e989f30733853672d452666fd9c91b55423 Mon Sep 17 00:00:00 2001 From: mungai-njoroge Date: Wed, 27 Sep 2023 17:18:52 +0300 Subject: [PATCH] redesign progressbars + hide some progressbars + rewrite telemetry into a class + remove obsolete start info logs + update contributing.md to include contributing.md + send posthog event in a bg thread + related side effects --- .github/contributing.md | 4 +- app/api/artist.py | 10 ++++- app/arg_handler.py | 60 +++++++++++++------------ app/db/sqlite/settings.py | 2 - app/lib/artistlib.py | 2 +- app/lib/colorlib.py | 2 +- app/lib/populate.py | 2 +- app/lib/trackslib.py | 3 +- app/logger.py | 1 + app/models/album.py | 6 +-- app/print_help.py | 8 ++-- app/settings.py | 11 ++--- app/start_info_logger.py | 26 ++--------- app/store/albums.py | 4 +- app/store/artists.py | 7 +-- app/store/tracks.py | 4 +- app/telemetry.py | 94 ++++++++++++++++++++++----------------- app/utils/progressbar.py | 16 +++++++ app/utils/threading.py | 3 +- manage.py | 9 +++- 20 files changed, 144 insertions(+), 130 deletions(-) create mode 100644 app/utils/progressbar.py diff --git a/.github/contributing.md b/.github/contributing.md index 19a5a79..36fef4c 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -39,7 +39,9 @@ cd swingmusic poetry install ``` -Run the server. You can use a different port if you have another Swing Music instance running on the default port `1970`. +You need a LastFM API key which you can get on the [API accounts page](https://www.last.fm/api/accounts). Then set it as an environment variable under the name: `LASTFM_API_KEY`. + +Finally, run the server. You can use a different port if you have another Swing Music instance running on port `1970`. ```sh poetry run python manage.py --port 1980 diff --git a/app/api/artist.py b/app/api/artist.py index d6979a9..19f7b74 100644 --- a/app/api/artist.py +++ b/app/api/artist.py @@ -15,17 +15,23 @@ from app.serializers.track import serialize_tracks from app.store.albums import AlbumStore from app.store.artists import ArtistStore from app.store.tracks import TrackStore -from app import telemetry +from app.telemetry import Telemetry +from app.utils.threading import background api = Blueprint("artist", __name__, url_prefix="/") +@background +def send_event(): + Telemetry.send_artist_visited() + + @api.route("/artist/", methods=["GET"]) def get_artist(artisthash: str): """ Get artist data. """ - telemetry.send_artist_visited() + send_event() limit = request.args.get("limit") if limit is None: diff --git a/app/arg_handler.py b/app/arg_handler.py index 3eac229..18c8f4e 100644 --- a/app/arg_handler.py +++ b/app/arg_handler.py @@ -34,6 +34,9 @@ class HandleArgs: Runs Pyinstaller. """ + if ALLARGS.build not in ARGS: + return + if settings.IS_BUILD: print("Catch me if you can! 😆💬") sys.exit(0) @@ -49,37 +52,36 @@ class HandleArgs: log.error("ERROR: POSTHOG_API_KEY not set in environment") sys.exit(0) - if ALLARGS.build in ARGS: - try: - with open("./app/configs.py", "w", encoding="utf-8") as file: - # copy the api keys to the config file - line1 = f'LASTFM_API_KEY = "{lastfm_key}"\n' - line2 = f'POSTHOG_API_KEY = "{posthog_key}"\n' - file.write(line1) - file.write(line2) + try: + with open("./app/configs.py", "w", encoding="utf-8") as file: + # copy the api keys to the config file + line1 = f'LASTFM_API_KEY = "{lastfm_key}"\n' + line2 = f'POSTHOG_API_KEY = "{posthog_key}"\n' + file.write(line1) + file.write(line2) - bundler.run( - [ - "manage.py", - "--onefile", - "--name", - "swingmusic", - "--clean", - f"--add-data=assets:assets", - f"--add-data=client:client", - f"--icon=assets/logo-fill.ico", - "-y", - ] - ) - finally: - # revert and remove the api keys for dev mode - with open("./app/configs.py", "w", encoding="utf-8") as file: - line1 = "LASTFM_API_KEY = ''\n" - line2 = "POSTHOG_API_KEY = ''\n" - file.write(line1) - file.write(line2) + bundler.run( + [ + "manage.py", + "--onefile", + "--name", + "swingmusic", + "--clean", + f"--add-data=assets:assets", + f"--add-data=client:client", + f"--icon=assets/logo-fill.ico", + "-y", + ] + ) + finally: + # revert and remove the api keys for dev mode + with open("./app/configs.py", "w", encoding="utf-8") as file: + line1 = "LASTFM_API_KEY = ''\n" + line2 = "POSTHOG_API_KEY = ''\n" + file.write(line1) + file.write(line2) - sys.exit(0) + sys.exit(0) @staticmethod def handle_port(): diff --git a/app/db/sqlite/settings.py b/app/db/sqlite/settings.py index a6dce95..a597d1b 100644 --- a/app/db/sqlite/settings.py +++ b/app/db/sqlite/settings.py @@ -28,8 +28,6 @@ class SettingsSQLMethods: if settings is None: return [] - # print - # omit id, root_dirs, and exclude_dirs return settings[3:] diff --git a/app/lib/artistlib.py b/app/lib/artistlib.py index d247ca8..1c9bfd9 100644 --- a/app/lib/artistlib.py +++ b/app/lib/artistlib.py @@ -8,12 +8,12 @@ import requests from PIL import Image, UnidentifiedImageError from requests.exceptions import ConnectionError as RequestConnectionError from requests.exceptions import ReadTimeout -from tqdm import tqdm from app import settings from app.models import Album, Artist, Track from app.store import artists as artist_store from app.utils.hashing import create_hash +from app.utils.progressbar import tqdm CHECK_ARTIST_IMAGES_KEY = "" diff --git a/app/lib/colorlib.py b/app/lib/colorlib.py index 4039080..17dd5c4 100644 --- a/app/lib/colorlib.py +++ b/app/lib/colorlib.py @@ -6,7 +6,6 @@ import json from pathlib import Path import colorgram -from tqdm import tqdm from app import settings from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb @@ -17,6 +16,7 @@ from app.store.artists import ArtistStore from app.store.albums import AlbumStore from app.logger import log from app.lib.errors import PopulateCancelledError +from app.utils.progressbar import tqdm PROCESS_ALBUM_COLORS_KEY = "" PROCESS_ARTIST_COLORS_KEY = "" diff --git a/app/lib/populate.py b/app/lib/populate.py index 2d144f7..a2db9e9 100644 --- a/app/lib/populate.py +++ b/app/lib/populate.py @@ -5,7 +5,6 @@ from typing import Generator from requests import ConnectionError as RequestConnectionError from requests import ReadTimeout -from tqdm import tqdm from app import settings from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb @@ -28,6 +27,7 @@ from app.store.artists import ArtistStore from app.store.tracks import TrackStore from app.utils.filesystem import run_fast_scandir from app.utils.network import has_connection +from app.utils.progressbar import tqdm get_all_tracks = SQLiteTrackMethods.get_all_tracks insert_many_tracks = SQLiteTrackMethods.insert_many_tracks diff --git a/app/lib/trackslib.py b/app/lib/trackslib.py index 6bb1a30..99a66b4 100644 --- a/app/lib/trackslib.py +++ b/app/lib/trackslib.py @@ -3,11 +3,10 @@ This library contains all the functions related to tracks. """ import os -from tqdm import tqdm from app.db.sqlite.tracks import SQLiteTrackMethods as tdb from app.store.tracks import TrackStore - +from app.utils.progressbar import tqdm def validate_tracks() -> None: """ diff --git a/app/logger.py b/app/logger.py index 8139128..3df7cd7 100644 --- a/app/logger.py +++ b/app/logger.py @@ -2,6 +2,7 @@ Logger module """ +from app.settings import IS_BUILD import logging diff --git a/app/models/album.py b/app/models/album.py index 5a9969b..993c563 100644 --- a/app/models/album.py +++ b/app/models/album.py @@ -104,7 +104,7 @@ class Album: """ keywords = ["motion picture", "soundtrack"] for keyword in keywords: - if keyword in self.title.lower(): + if keyword in self.og_title.lower(): return True return False @@ -145,7 +145,7 @@ class Album: """ keywords = ["live from", "live at", "live in", "live on", "mtv unplugged"] for keyword in keywords: - if keyword in self.title.lower(): + if keyword in self.og_title.lower(): return True return False @@ -165,7 +165,7 @@ class Album: show_albums_as_singles = get_flag(SessionVarKeys.SHOW_ALBUMS_AS_SINGLES) for keyword in keywords: - if keyword in self.title.lower(): + if keyword in self.og_title.lower(): self.is_single = True return diff --git a/app/print_help.py b/app/print_help.py index a1c44b6..7839334 100644 --- a/app/print_help.py +++ b/app/print_help.py @@ -13,7 +13,7 @@ help_args_list = [ [ "--scan-interval", "-psi", - "Set the periodic scan interval in seconds. Default is 600 seconds (10 minutes)", + "Set the scan interval in seconds. Default 600s (10 minutes)", ], [ "--build", @@ -26,9 +26,7 @@ HELP_MESSAGE = f""" Swing Music is a beautiful, self-hosted music player for your local audio files. Like a cooler Spotify ... but bring your own music. -Usage: swingmusic [options] +Usage: swingmusic [options] [args] -{tabulate(help_args_list, headers=["Option", "Short", "Description"], tablefmt="markdown", maxcolwidths=[None, None, 44])} +{tabulate(help_args_list, headers=["Option", "Short", "Description"], tablefmt="simple_grid", maxcolwidths=[None, None, 40])} """ - -"80s, 90s, the noughties and today" diff --git a/app/settings.py b/app/settings.py index 0ec4f55..228c250 100644 --- a/app/settings.py +++ b/app/settings.py @@ -6,7 +6,6 @@ import sys from typing import Any from app import configs -from app.logger import log join = os.path.join @@ -241,14 +240,10 @@ class Keys: cls.LASTFM_API = configs.LASTFM_API_KEY cls.POSTHOG_API_KEY = configs.POSTHOG_API_KEY - cls.verify_exists() + cls.verify_keys() @classmethod - def verify_exists(cls): + def verify_keys(cls): if not cls.LASTFM_API: - log.error("ERROR: LASTFM_API_KEY not set in environment") - sys.exit(0) - - if not cls.POSTHOG_API_KEY: - log.error("ERROR: POSTHOG_API_KEY not set in environment") + print("ERROR: LASTFM_API_KEY not set in environment") sys.exit(0) diff --git a/app/start_info_logger.py b/app/start_info_logger.py index 3260f63..5baa311 100644 --- a/app/start_info_logger.py +++ b/app/start_info_logger.py @@ -1,7 +1,6 @@ import os -from app.settings import (FLASKVARS, TCOLOR, Paths, Release, SessionVarKeys, - get_flag) +from app.settings import FLASKVARS, TCOLOR, Paths, Release from app.utils.network import get_ip @@ -25,27 +24,8 @@ def log_startup_info(): f"➤ {TCOLOR.OKGREEN}http://{address}:{FLASKVARS.get_flask_port()}{TCOLOR.ENDC}" ) - print(lines) - print("\n") + print(lines+"\n") - to_print = [ - [ - "Extract featured artists from titles", - get_flag(SessionVarKeys.EXTRACT_FEAT) - ], - [ - "Remove prod. from titles", - get_flag(SessionVarKeys.REMOVE_PROD) - ] - ] - - for item in to_print: - print( - f"{item[0]}: {TCOLOR.FAIL}{item[1]}{TCOLOR.ENDC}" - ) - - print( - f"{TCOLOR.YELLOW}Data folder: {Paths.get_app_dir()}{TCOLOR.ENDC}" - ) + print(f"{TCOLOR.YELLOW}Data folder: {Paths.get_app_dir()}{TCOLOR.ENDC}") print("\n") diff --git a/app/store/albums.py b/app/store/albums.py index 7a3c7a5..14cd793 100644 --- a/app/store/albums.py +++ b/app/store/albums.py @@ -1,13 +1,13 @@ import json import random -from tqdm import tqdm from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb from app.models import Album, Track from ..utils.hashing import create_hash from .tracks import TrackStore +from app.utils.progressbar import tqdm ALBUM_LOAD_KEY = "" @@ -49,7 +49,7 @@ class AlbumStore: db_albums: list[tuple] = aldb.get_all_albums() - for album in tqdm(db_albums, desc="Mapping album colors"): + for album in db_albums: albumhash = album[1] colors = json.loads(album[2]) diff --git a/app/store/artists.py b/app/store/artists.py index 8d57d2a..f93991a 100644 --- a/app/store/artists.py +++ b/app/store/artists.py @@ -1,11 +1,10 @@ import json -from tqdm import tqdm - from app.db.sqlite.artistcolors import SQLiteArtistMethods as ardb from app.lib.artistlib import get_all_artists from app.models import Artist from app.utils.bisection import UseBisection +from app.utils.progressbar import tqdm from .albums import AlbumStore from .tracks import TrackStore @@ -26,9 +25,7 @@ class ArtistStore: cls.artists = get_all_artists(TrackStore.tracks, AlbumStore.albums) - # db_artists: list[tuple] = list(ardb.get_all_artists()) - - for artist in tqdm(ardb.get_all_artists(), desc="Loading artists"): + for artist in ardb.get_all_artists(): if instance_key != ARTIST_LOAD_KEY: return diff --git a/app/store/tracks.py b/app/store/tracks.py index 8828828..ca7e3cb 100644 --- a/app/store/tracks.py +++ b/app/store/tracks.py @@ -1,10 +1,11 @@ -from tqdm import tqdm +# from tqdm import tqdm from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb from app.db.sqlite.tracks import SQLiteTrackMethods as tdb from app.models import Track from app.utils.bisection import UseBisection from app.utils.remove_duplicates import remove_duplicates +from app.utils.progressbar import tqdm TRACKS_LOAD_KEY = "" @@ -25,6 +26,7 @@ class TrackStore: fav_hashes = favdb.get_fav_tracks() fav_hashes = " ".join([t[1] for t in fav_hashes]) + print("\n") # adds space between progress bars and startup info for track in tqdm(cls.tracks, desc="Loading tracks"): if instance_key != TRACKS_LOAD_KEY: return diff --git a/app/telemetry.py b/app/telemetry.py index dfdab68..c726fe5 100644 --- a/app/telemetry.py +++ b/app/telemetry.py @@ -2,55 +2,69 @@ import sys import uuid as UUID from posthog import Posthog -from app.settings import Paths, Keys + +from app.logger import log +from app.settings import Keys, Paths, Release from app.utils.hashing import create_hash from app.utils.network import has_connection -from app.logger import log -USER_ID = "" - -try: - posthog = Posthog( - project_api_key=Keys.POSTHOG_API_KEY, - host="https://app.posthog.com", - disable_geoip=False, - timeout=30, - ) -except AssertionError: - log.error("ERROR: POSTHOG_API_KEY not set in environment") - sys.exit(0) - - -def create_userid(): +class Telemetry: """ - Creates a unique user id for the user and saves it to a file. + Handles sending telemetry data to posthog. """ - uuid_path = Paths.get_app_dir() + "/userid.txt" - global USER_ID - try: - with open(uuid_path, "r") as f: - USER_ID = f.read().strip() - except FileNotFoundError: - uuid = str(UUID.uuid4()) - USER_ID = "user_" + create_hash(uuid, limit=15) + user_id = "" + off = False - with open(uuid_path, "w") as f: - f.write(USER_ID) + @classmethod + def init(cls) -> None: + try: + cls.posthog = Posthog( + project_api_key=Keys.POSTHOG_API_KEY, + host="https://app.posthog.com", + disable_geoip=False, + ) + cls.create_userid() + except AssertionError: + cls.disable_telemetry() -def send_event(event: str): - """ - Sends an event to posthog. - """ - global USER_ID - if has_connection(): - posthog.capture(USER_ID, event=f"v1.3.0-{event}") + @classmethod + def create_userid(cls): + """ + Creates a unique user id for the user and saves it to a file. + """ + uuid_path = Paths.get_app_dir() + "/userid.txt" + try: + with open(uuid_path, "r") as f: + cls.user_id = f.read().strip() + except FileNotFoundError: + uuid = str(UUID.uuid4()) + cls.user_id = "user_" + create_hash(uuid, limit=15) -def send_artist_visited(): - """ - Sends an event to posthog when an artist page is visited. - """ - send_event("artist-page-visited") + with open(uuid_path, "w") as f: + f.write(cls.user_id) + + @classmethod + def disable_telemetry(cls): + cls.off = True + + @classmethod + def send_event(cls, event: str): + """ + Sends an event to posthog. + """ + if cls.off: + return + + if has_connection(): + cls.posthog.capture(cls.user_id, event=f"v{Release.APP_VERSION}-{event}") + + @classmethod + def send_artist_visited(cls): + """ + Sends an event to posthog when an artist page is visited. + """ + cls.send_event("artist-page-visited") diff --git a/app/utils/progressbar.py b/app/utils/progressbar.py new file mode 100644 index 0000000..682a919 --- /dev/null +++ b/app/utils/progressbar.py @@ -0,0 +1,16 @@ +from tqdm import tqdm as _tqdm + + +def tqdm(*args, **kwargs): + """ + Wrapper for tqdm that sets globals. + """ + bar_format = "{percentage:3.0f}%|{bar:45}|{n_fmt}/{total_fmt}{desc}" + kwargs["bar_format"] = bar_format + + if "desc" in kwargs: + print(f'INFO|{kwargs["desc"].capitalize()} ...') + kwargs["desc"] = "" + + + return _tqdm(*args, **kwargs) diff --git a/app/utils/threading.py b/app/utils/threading.py index 11ec9e0..5f66f91 100644 --- a/app/utils/threading.py +++ b/app/utils/threading.py @@ -3,8 +3,7 @@ import threading def background(func): """ - a threading decorator - use @background above the function you want to run in the background + Runs the decorated function in a background thread. """ def background_func(*a, **kw): diff --git a/manage.py b/manage.py index 35596bd..ffbc541 100644 --- a/manage.py +++ b/manage.py @@ -8,7 +8,7 @@ import os import setproctitle from flask import request -from app import telemetry +from app.telemetry import Telemetry from app.api import create_api from app.arg_handler import HandleArgs from app.lib.watchdogg import Watcher as WatchDog @@ -79,13 +79,18 @@ def start_watchdog(): WatchDog().run() +@background +def init_telemetry(): + Telemetry.init() + + def run_swingmusic(): Keys.load() HandleArgs() log_startup_info() bg_run_setup() start_watchdog() - telemetry.create_userid() + init_telemetry() setproctitle.setproctitle( f"swingmusic - {FLASKVARS.FLASK_HOST}:{FLASKVARS.FLASK_PORT}"