implement CLI password recovery (hacky :omg:)

+ rewrite migrations logic
+ rename encode_password to hash_password
+ update image sizes (add medium size)
+ rename image endpoints
This commit is contained in:
cwilvx
2024-05-11 21:26:03 +03:00
parent 1e857c1e89
commit b40f05cc7c
21 changed files with 306 additions and 152 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ client
logs.txt
*.spec
TODO.md
# TODO.md
testdata.py
test.py
nohup.out
+2
View File
@@ -0,0 +1,2 @@
- Fix migrations!
- Use total length instead of release version length
+6 -5
View File
@@ -14,7 +14,7 @@ from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
from app.utils.auth import check_password, encode_password
from app.utils.auth import check_password, hash_password
from app.config import UserConfig
bp_tag = Tag(name="Auth", description="Authentication stuff")
@@ -113,7 +113,7 @@ def update_profile(body: UpdateProfileBody):
user["roles"] = json.dumps(body.roles)
if user["password"]:
user["password"] = encode_password(user["password"])
user["password"] = hash_password(user["password"])
# remove empty values
clean_user = {k: v for k, v in user.items() if v}
@@ -132,7 +132,7 @@ def create_user(body: UpdateProfileBody):
user = {
"username": body.username,
"password": encode_password(body.password),
"password": hash_password(body.password),
"roles": json.dumps([]),
}
@@ -232,7 +232,9 @@ def get_all_users(query: GetAllUsersQuery):
users = authdb.get_all_users()
is_admin = current_user and "admin" in current_user["roles"]
settings['enableGuest'] = [user for user in users if user.username == "guest"].__len__() > 0
settings["enableGuest"] = [
user for user in users if user.username == "guest"
].__len__() > 0
# if user is admin, also return settings
if is_admin:
@@ -252,7 +254,6 @@ def get_all_users(query: GetAllUsersQuery):
):
return res
# remove guest user
# if not settings["enableGuest"]:
# users = [user for user in users if user.username != "guest"]
+59 -39
View File
@@ -13,6 +13,9 @@ api = APIBlueprint("imgserver", __name__, url_prefix="/img", abp_tags=[bp_tag])
def send_fallback_img(filename: str = "default.webp"):
"""
Returns the fallback image from the assets folder.
"""
folder = Paths.get_assets_path()
img = Path(folder) / filename
@@ -22,6 +25,18 @@ def send_fallback_img(filename: str = "default.webp"):
return send_from_directory(folder, filename)
def send_file_or_fallback(folder: str, filename: str, fallback: str = "default.webp"):
"""
Returns the file from the folder or the fallback image.
"""
fpath = Path(folder) / filename
if fpath.exists():
return send_from_directory(folder, filename)
return send_fallback_img(fallback)
class ImagePath(BaseModel):
imgpath: str = Field(
description="The image filename",
@@ -43,62 +58,72 @@ class ImagePath(BaseModel):
# return send_fallback_img()
@api.get("/t/<imgpath>")
# TRACK THUMBNAILS
@api.get("/thumbnail/<imgpath>")
def send_lg_thumbnail(path: ImagePath):
"""
Get large thumbnail (500 x 500)
"""
folder = Paths.get_lg_thumb_path()
fpath = Path(folder) / path.imgpath
if fpath.exists():
return send_from_directory(folder, path.imgpath)
return send_fallback_img()
return send_file_or_fallback(folder, path.imgpath)
@api.get("/t/s/<imgpath>")
@api.get("/thumbnail/xsmall/<imgpath>")
def send_xsm_thumbnail(path: ImagePath):
"""
Get extra small thumbnail (64px)
"""
folder = Paths.get_xsm_thumb_path()
return send_file_or_fallback(folder, path.imgpath)
@api.get("/thumbnail/small/<imgpath>")
def send_sm_thumbnail(path: ImagePath):
"""
Get small thumbnail (64 x 64)
Get small thumbnail (96px)
"""
folder = Paths.get_sm_thumb_path()
fpath = Path(folder) / path.imgpath
if fpath.exists():
return send_from_directory(folder, path.imgpath)
return send_fallback_img()
return send_file_or_fallback(folder, path.imgpath)
@api.get("/a/<imgpath>")
@api.get("/thumbnail/medium/<imgpath>")
def send_md_thumbnail(path: ImagePath):
"""
Get medium thumbnail (256px)
"""
folder = Paths.get_md_thumb_path()
return send_file_or_fallback(folder, path.imgpath)
# ARTISTS
@api.get("/artist/<imgpath>")
def send_lg_artist_image(path: ImagePath):
"""
Get large artist image (500 x 500)
"""
folder = Paths.get_artist_img_lg_path()
fpath = Path(folder) / path.imgpath
if fpath.exists():
return send_from_directory(folder, path.imgpath)
return send_fallback_img("artist.webp")
folder = Paths.get_lg_artist_img_path()
return send_file_or_fallback(folder, path.imgpath, "artist.webp")
@api.get("/a/s/<imgpath>")
@api.get("/artist/small/<imgpath>")
def send_sm_artist_image(path: ImagePath):
"""
Get small artist image (64 x 64)
Get small artist image (128)
"""
folder = Paths.get_artist_img_sm_path()
fpath = Path(folder) / path.imgpath
if fpath.exists():
return send_from_directory(folder, path.imgpath)
return send_fallback_img("artist.webp")
folder = Paths.get_sm_artist_img_path()
return send_file_or_fallback(folder, path.imgpath, "artist.webp")
@api.get("/artist/medium/<imgpath>")
def send_md_artist_image(path: ImagePath):
"""
Get medium artist image (256px)
"""
folder = Paths.get_md_artist_img_path()
return send_file_or_fallback(folder, path.imgpath, "artist.webp")
# PLAYLISTS
class PlaylistImagePath(BaseModel):
imgpath: str = Field(
description="The image path",
@@ -106,7 +131,7 @@ class PlaylistImagePath(BaseModel):
)
@api.get("/p/<imgpath>")
@api.get("/playlist/<imgpath>")
def send_playlist_image(path: PlaylistImagePath):
"""
Get playlist image
@@ -114,9 +139,4 @@ def send_playlist_image(path: PlaylistImagePath):
Images are constructed as '{playlist_id}.webp'
"""
folder = Paths.get_playlist_img_path()
fpath = Path(folder) / path.imgpath
if fpath.exists():
return send_from_directory(folder, path.imgpath)
return send_fallback_img("playlist.svg")
return send_file_or_fallback(folder, path.imgpath, "playlist.svg")
+2 -1
View File
@@ -26,6 +26,7 @@ api = APIBlueprint("playlists", __name__, url_prefix="/playlists", abp_tags=[tag
PL = SQLitePlaylistMethods
class SendAllPlaylistsQuery(BaseModel):
no_images: bool = Field(False, description="Whether to include images")
@@ -410,7 +411,7 @@ def save_item_as_playlist(body: SavePlaylistAsItemBody):
filename = itemhash + ".webp"
base_path = (
Paths.get_artist_img_lg_path()
Paths.get_lg_artist_img_path()
if itemtype == "artist"
else Paths.get_lg_thumb_path()
)
+55 -8
View File
@@ -2,6 +2,7 @@
Handles arguments passed to the program.
"""
from getpass import getpass
import os.path
import sys
@@ -10,27 +11,37 @@ import PyInstaller.__main__ as bundler
from app import settings
from app.logger import log
from app.print_help import HELP_MESSAGE
from app.utils.auth import hash_password
from app.utils.paths import getFlaskOpenApiPath
from app.utils.xdg_utils import get_xdg_config_dir
from app.utils.wintools import is_windows
from app.db.sqlite.auth import SQLiteAuthMethods as authdb
ALLARGS = settings.ALLARGS
ARGS = sys.argv[1:]
class ProcessArgs:
"""
Processes the arguments passed to the program.
"""
def __init__(self) -> None:
# resolve config path
self.handle_config_path() # 1
# handles that exit
self.handle_password_recovery()
self.handle_build()
self.handle_host()
self.handle_port()
self.handle_config_path()
self.handle_periodic_scan()
self.handle_periodic_scan_interval()
self.handle_help()
self.handle_version()
# non-exiting handles
self.handle_host()
self.handle_port()
self.handle_periodic_scan()
self.handle_periodic_scan_interval()
@staticmethod
def handle_build():
"""
@@ -57,12 +68,13 @@ class ProcessArgs:
value = settings.Info.get(key)
if not value:
log.error(f"WARNING: {key} not set in environment")
log.error(f"WARNING: {key} not resolved. Exiting ...")
sys.exit(0)
lines.append(f'{key} = "{value}"\n')
try:
# write the info to the config file
with open("./app/configs.py", "w", encoding="utf-8") as file:
# copy the api keys to the config file
file.writelines(lines)
@@ -189,3 +201,38 @@ class ProcessArgs:
f"COMMIT#: {settings.Info.GIT_CURRENT_BRANCH}/{settings.Info.GIT_LATEST_COMMIT_HASH}"
)
sys.exit(0)
@staticmethod
def handle_password_recovery():
if ALLARGS.pswd in ARGS:
print("SWING MUSIC v2.0.0 ")
print("PASSWORD RECOVERY \n")
username: str = ""
password: str = ""
# collect username
try:
username = input("Enter username: ")
except KeyboardInterrupt:
print("\nOperation cancelled! Exiting ...")
sys.exit(0)
username = username.strip()
user = authdb.get_user_by_username(username)
if not user:
print(f"User {username} not found")
sys.exit(0)
# collect password
try:
password = getpass("Enter new password: ")
except KeyboardInterrupt:
print("\nOperation cancelled! Exiting ...")
sys.exit(0)
password = hash_password(password)
user = authdb.update_user({"id": user.id, "password": password})
sys.exit(0)
+5 -4
View File
@@ -1,6 +1,6 @@
import json
from app.models.user import User
from app.utils.auth import encode_password
from app.utils.auth import hash_password
from app.db.sqlite.utils import SQLiteManager
@@ -44,7 +44,7 @@ class SQLiteAuthMethods:
"""
user = {
"username": "admin",
"password": encode_password("admin"),
"password": hash_password("admin"),
"roles": json.dumps(["admin"]),
}
return SQLiteAuthMethods.insert_user(user)
@@ -56,7 +56,7 @@ class SQLiteAuthMethods:
"""
user = {
"username": "guest",
"password": encode_password("guest"),
"password": hash_password("guest"),
"roles": json.dumps(["guest"]),
}
@@ -67,7 +67,7 @@ class SQLiteAuthMethods:
"""
Update a user in the database.
:param user: A dict with the username, password and roles.
:param user: A dict with the user id and the fields to update. Ommited fields will not be updated.
"""
# get all user dict keys
keys = list(user.keys())
@@ -75,6 +75,7 @@ class SQLiteAuthMethods:
{', '.join([f"{key} = :{key}" for key in keys if key != 'id'])}
WHERE id = :id
"""
print(sql, user)
with SQLiteManager(userdata_db=True) as cur:
cur.execute(sql, user)
+4 -4
View File
@@ -7,9 +7,9 @@ from app.db.sqlite.utils import SQLiteManager
class MigrationManager:
@staticmethod
def get_version() -> int:
def get_index() -> int:
"""
Returns the latest userdata database version.
Returns the latest databases migrations index.
"""
sql = "SELECT * FROM dbmigrations"
with SQLiteManager() as cur:
@@ -21,9 +21,9 @@ class MigrationManager:
# 👇 Setters 👇
@staticmethod
def set_version(version: int):
def set_index(version: int):
"""
Sets the userdata pre-init database version.
Updates the databases migrations index.
"""
sql = "UPDATE dbmigrations SET version = ? WHERE id = 1"
with SQLiteManager() as cur:
+27 -11
View File
@@ -55,13 +55,22 @@ def get_artist_image_link(artist: str):
# TODO: Move network calls to utils/network.py
class DownloadImage:
def __init__(self, url: str, name: str) -> None:
sm_path = Path(settings.Paths.get_artist_img_sm_path()) / name
lg_path = Path(settings.Paths.get_artist_img_lg_path()) / name
img = self.download(url)
if img is not None:
self.save_img(img, sm_path, lg_path)
if img is None:
return
sm_path = Path(settings.Paths.get_sm_artist_img_path()) / name
lg_path = Path(settings.Paths.get_lg_artist_img_path()) / name
md_path = Path(settings.Paths.get_md_artist_img_path()) / name
entries = [
(lg_path, None), # save in the original size
(sm_path, settings.Defaults.SM_ARTIST_IMG_SIZE),
(md_path, settings.Defaults.MD_ARTIST_IMG_SIZE),
]
self.save_img(img, entries)
@staticmethod
def download(url: str) -> Image.Image | None:
@@ -74,14 +83,21 @@ class DownloadImage:
return None
@staticmethod
def save_img(img: Image.Image, sm_path: Path, lg_path: Path):
def save_img(img: Image.Image, entries: list[tuple[Path, int | None]]):
"""
Saves the image to the destinations.
"""
img.save(lg_path, format="webp")
ratio = img.width / img.height
for entry in entries:
path, size = entry
sm_size = settings.Defaults.SM_ARTIST_IMG_SIZE
img.resize((sm_size, sm_size), Image.ANTIALIAS).save(sm_path, format="webp")
if size is None:
img.save(path, format="webp")
continue
img.resize((size, int(size / ratio)), Image.ANTIALIAS).save(
path, format="webp"
)
class CheckArtistImages:
@@ -90,7 +106,7 @@ class CheckArtistImages:
CHECK_ARTIST_IMAGES_KEY = instance_key
# read all files in the artist image folder
path = settings.Paths.get_artist_img_sm_path()
path = settings.Paths.get_sm_artist_img_path()
processed = "".join(os.listdir(path)).replace("webp", "")
# filter out artists that already have an image
@@ -126,7 +142,7 @@ class CheckArtistImages:
return
img_path = (
Path(settings.Paths.get_artist_img_sm_path()) / f"{artist.artisthash}.webp"
Path(settings.Paths.get_sm_artist_img_path()) / f"{artist.artisthash}.webp"
)
if img_path.exists():
+1 -1
View File
@@ -42,7 +42,7 @@ def process_color(item_hash: str, is_album=True):
path = (
settings.Paths.get_sm_thumb_path()
if is_album
else settings.Paths.get_artist_img_sm_path()
else settings.Paths.get_sm_artist_img_path()
)
path = Path(path) / (item_hash + ".webp")
+10 -8
View File
@@ -33,20 +33,22 @@ def extract_thumb(filepath: str, webp_path: str, overwrite=False) -> bool:
"""
lg_img_path = os.path.join(Paths.get_lg_thumb_path(), webp_path)
sm_img_path = os.path.join(Paths.get_sm_thumb_path(), webp_path)
xms_img_path = os.path.join(Paths.get_xsm_thumb_path(), webp_path)
md_img_path = os.path.join(Paths.get_md_thumb_path(), webp_path)
tsize = Defaults.THUMB_SIZE
sm_tsize = Defaults.SM_THUMB_SIZE
images = [
(lg_img_path, Defaults.LG_THUMB_SIZE),
(sm_img_path, Defaults.SM_THUMB_SIZE),
(xms_img_path, Defaults.XSM_THUMB_SIZE),
(md_img_path, Defaults.MD_THUMB_SIZE),
]
def save_image(img: Image.Image):
width, height = img.size
ratio = width / height
img.resize((tsize, int(tsize / ratio)), Image.ANTIALIAS).save(
lg_img_path, "webp"
)
img.resize((sm_tsize, int(sm_tsize / ratio)), Image.ANTIALIAS).save(
sm_img_path, "webp"
)
for path, size in images:
img.resize((size, int(size / ratio)), Image.ANTIALIAS).save(path, "webp")
if not overwrite and os.path.exists(sm_img_path):
img_size = os.path.getsize(sm_img_path)
+23 -18
View File
@@ -2,12 +2,6 @@
Migrations module.
Reads and applies the latest database migrations.
PLEASE NOTE: OLDER MIGRATIONS CAN NEVER BE DELETED.
ONLY MODIFY OLD MIGRATIONS FOR BUG FIXES OR ENHANCEMENTS ONLY
[TRY NOT TO MODIFY BEHAVIOR, UNLESS YOU KNOW WHAT YOU'RE DOING].
PS: Fuck that! Do what you want.
"""
from app.db.sqlite.migrations import MigrationManager
@@ -33,21 +27,32 @@ migrations: list[list[Migration]] = [
def apply_migrations():
"""
Applies the latest database migrations.
The length of all the migrations is stored in the database
and used to check for new migrations. When the length of the
migrations list is larger than the number stored in the db,
migrations past that index are applied and the new length
is stored as the new migration index.
"""
version = MigrationManager.get_version()
index = MigrationManager.get_index()
all_migrations = [migration for sublist in migrations for migration in sublist]
if version != len(migrations):
# INFO: Apply new migrations
for migration in migrations[version:]:
for m in migration:
to_apply: list[Migration] = []
# if index is from old release,
# get migrations from the "migrations" list
if index < 3:
_migrations = migrations[index:]
to_apply = [migration for sublist in _migrations for migration in sublist]
else:
to_apply = all_migrations[index:]
for migration in to_apply:
try:
m.migrate()
log.info("Applied migration: %s", m.__name__)
migration.migrate()
log.info("Applied migration: %s", migration.__name__)
except:
log.error("Failed to run migration: %s", m.__name__)
log.error("Failed to run migration: %s", migration.__name__)
print("Migrations applied successfully.")
print("Current migration version: ", len(migrations))
# bump migration version
MigrationManager.set_version(len(migrations))
MigrationManager.set_index(len(all_migrations))
+16
View File
@@ -58,3 +58,19 @@ class DeleteOriginalThumbnails(Migration):
if os.path.exists(og_imgpath):
shutil.rmtree(og_imgpath)
class DeleteOriginalThumbnailsa(Migration):
"""
Original thumbnails are too large and are not needed.
"""
# TODO: Implement this migration
@staticmethod
def migrate():
imgpath = Paths.get_thumbs_path()
og_imgpath = os.path.join(imgpath, "original")
if os.path.exists(og_imgpath):
shutil.rmtree(og_imgpath)
+6
View File
@@ -13,6 +13,12 @@ from app.logger import log
def run_periodic_scans():
"""
Runs periodic scans.
Periodic scans are checks that run every few minutes
in the background to do stuff like:
- checking for new music
- delete deleted entries
- downloading artist images, and other data.
"""
# ValidateAlbumThumbs()
# ValidatePlaylistThumbs()
+8 -5
View File
@@ -1,4 +1,4 @@
from app.settings import ALLARGS
from app.settings import ALLARGS, Info
from tabulate import tabulate
args = ALLARGS
@@ -10,6 +10,7 @@ help_args_list = [
["--port", "", "Set the port"],
["--config", "", "Set the config path"],
["--no-periodic-scan", "-nps", "Disable periodic scan"],
["--pswd", "", "Recover a password"],
[
"--scan-interval",
"-psi",
@@ -23,10 +24,12 @@ help_args_list = [
]
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.
Swing Music v{Info.SWINGMUSIC_APP_VERSION}
Usage: swingmusic [options] [args]
A beautiful, self-hosted music player for your local audio files.
Like Spotify ... but bring your own music.
{tabulate(help_args_list, headers=["Option", "Short", "Description"], tablefmt="simple_grid", maxcolwidths=[None, None, 40])}
Usage: ./swingmusic [options] [args]
{tabulate(help_args_list, headers=["Option", "Alias", "Description"], tablefmt="psql", maxcolwidths=[None, None, 40])}
"""
+41 -17
View File
@@ -45,22 +45,24 @@ class Paths:
def get_img_path(cls):
return join(cls.get_app_dir(), "images")
# ARTISTS
@classmethod
def get_artist_img_path(cls):
return join(cls.get_img_path(), "artists")
@classmethod
def get_artist_img_sm_path(cls):
def get_sm_artist_img_path(cls):
return join(cls.get_artist_img_path(), "small")
@classmethod
def get_artist_img_lg_path(cls):
return join(cls.get_artist_img_path(), "large")
def get_md_artist_img_path(cls):
return join(cls.get_artist_img_path(), "medium")
@classmethod
def get_playlist_img_path(cls):
return join(cls.get_img_path(), "playlists")
def get_lg_artist_img_path(cls):
return join(cls.get_artist_img_path(), "large")
# TRACK THUMBNAILS
@classmethod
def get_thumbs_path(cls):
return join(cls.get_img_path(), "thumbnails")
@@ -69,10 +71,23 @@ class Paths:
def get_sm_thumb_path(cls):
return join(cls.get_thumbs_path(), "small")
@classmethod
def get_xsm_thumb_path(cls):
return join(cls.get_thumbs_path(), "xsmall")
@classmethod
def get_md_thumb_path(cls):
return join(cls.get_thumbs_path(), "medium")
@classmethod
def get_lg_thumb_path(cls):
return join(cls.get_thumbs_path(), "large")
# OTHERS
@classmethod
def get_playlist_img_path(cls):
return join(cls.get_img_path(), "playlists")
@classmethod
def get_assets_path(cls):
return join(Paths.get_app_dir(), "assets")
@@ -92,12 +107,25 @@ class Paths:
# defaults
class Defaults:
THUMB_SIZE = 512
SM_THUMB_SIZE = 128
"""
Contains default values for various settings.
XSM_THUMB_SIZE: extra small thumbnail size for web client tracklist
SM_THUMB_SIZE: small thumbnail size for android client tracklist
MD_THUMB_SIZE: medium thumbnail size for web client album cards
LG_THUMB_SIZE: large thumbnail size for web client now playing album art
NOTE: LG_ARTIST_IMG_SIZE is not defined as the images are saved in the original size (500px)
"""
XSM_THUMB_SIZE = 64
SM_THUMB_SIZE = 96
MD_THUMB_SIZE = 256
LG_THUMB_SIZE = 512
SM_ARTIST_IMG_SIZE = 128
"""
The size of extracted images in pixels
"""
MD_ARTIST_IMG_SIZE = 256
HASH_LENGTH = 10
API_ALBUMHASH = "bfe300e966"
API_ARTISTHASH = "cae59f1fc5"
@@ -105,7 +133,6 @@ class Defaults:
API_ALBUMNAME = "The Goat"
API_ARTISTNAME = "Polo G"
API_TRACKNAME = "Martin & Gina"
API_CARD_LIMIT = 6
@@ -162,6 +189,8 @@ class ALLARGS:
host = "--host"
config = "--config"
pswd = "--pswd"
show_feat = ("--show-feat", "-sf")
show_prod = ("--show-prod", "-sp")
dont_clean_albums = ("--no-clean-albums", "-nca")
@@ -275,6 +304,7 @@ class Info:
NOTE: This class initially written to load keys when running in build mode.
TODO: Remove this class entirely, and implement functionality where needed.
"""
SWINGMUSIC_APP_VERSION = os.environ.get("SWINGMUSIC_APP_VERSION")
GIT_LATEST_COMMIT_HASH = "<unset>"
GIT_CURRENT_BRANCH = "<unset>"
@@ -289,12 +319,6 @@ class Info:
cls.GIT_LATEST_COMMIT_HASH = getLatestCommitHash()
cls.GIT_CURRENT_BRANCH = getCurrentBranch()
cls.verify_keys()
@classmethod
def verify_keys(cls):
pass
@classmethod
def get(cls, key: str):
return getattr(cls, key, None)
+8
View File
@@ -14,6 +14,9 @@ from app.config import UserConfig
def run_setup():
"""
Creates the config directory, runs migrations, and loads settings.
"""
create_config_dir()
# setup config file
@@ -32,6 +35,11 @@ def run_setup():
# settings table is empty
pass
def load_into_mem():
"""
Load all tracks, albums, and artists into memory.
"""
instance_key = get_random_str()
# INFO: Load all tracks, albums, and artists into memory
+16 -14
View File
@@ -51,28 +51,28 @@ def create_config_dir() -> None:
"""
Creates the config directory if it doesn't exist.
"""
thumb_path = os.path.join("images", "thumbnails")
small_thumb_path = os.path.join(thumb_path, "small")
large_thumb_path = os.path.join(thumb_path, "large")
sm_thumb_path = settings.Paths.get_sm_thumb_path()
lg_thumb_path = settings.Paths.get_lg_thumb_path()
md_thumb_path = settings.Paths.get_md_thumb_path()
xsm_thumb_path = settings.Paths.get_xsm_thumb_path()
artist_img_path = os.path.join("images", "artists")
small_artist_img_path = os.path.join(artist_img_path, "small")
large_artist_img_path = os.path.join(artist_img_path, "large")
small_artist_img_path = settings.Paths.get_sm_artist_img_path()
md_artist_img_path = settings.Paths.get_md_artist_img_path()
large_artist_img_path = settings.Paths.get_lg_artist_img_path()
playlist_img_path = os.path.join("images", "playlists")
dirs = [
"", # creates the config folder
"images",
"plugins",
sm_thumb_path,
lg_thumb_path,
md_thumb_path,
xsm_thumb_path,
"plugins/lyrics",
thumb_path,
small_thumb_path,
large_thumb_path,
artist_img_path,
playlist_img_path,
md_artist_img_path,
small_artist_img_path,
large_artist_img_path,
playlist_img_path,
]
for _dir in dirs:
@@ -80,7 +80,9 @@ def create_config_dir() -> None:
exists = os.path.exists(path)
if not exists:
os.makedirs(path)
# exist_ok=True to create parent directories if they don't exist
os.makedirs(path, exist_ok=True)
os.chmod(path, 0o755)
# copy assets to the app directory
CopyFiles()
+1 -1
View File
@@ -10,7 +10,7 @@ def log_startup_info():
# os.system("cls" if os.name == "nt" else "echo -e \\\\033c")
print(lines)
print(f"{TCOLOR.HEADER}SwingMusic {Info.SWINGMUSIC_APP_VERSION} {TCOLOR.ENDC}")
print(f"{TCOLOR.HEADER}Swing Music v{Info.SWINGMUSIC_APP_VERSION} {TCOLOR.ENDC}")
adresses = [FLASKVARS.get_flask_host()]
+8 -8
View File
@@ -4,13 +4,13 @@ import hashlib
from app.config import UserConfig
def encode_password(password: str) -> str:
def hash_password(password: str) -> str:
"""
This function encodes the given password.
Hashes the given password using sha256 algorithm and the user id as salt.
:param password: The password to encode.
:param password: The password to hash.
:return: The encoded password.
:return: The hashed password.
"""
return hashlib.pbkdf2_hmac(
@@ -18,14 +18,14 @@ def encode_password(password: str) -> str:
).hex()
def check_password(password: str, encoded: str) -> bool:
def check_password(password: str, hashed: str) -> bool:
"""
This function checks if the given password matches the encoded password.
This function checks if the given password matches the hashed password.
:param password: The password to check.
:param encoded: The encoded password.
:param hashed: The hashed password.
:return: Whether the password matches.
"""
return hmac.compare_digest(encode_password(password), encoded)
return hmac.compare_digest(hash_password(password), hashed)
+4 -4
View File
@@ -25,7 +25,7 @@ from app.lib.watchdogg import Watcher as WatchDog
from app.periodic_scan import run_periodic_scans
from app.plugins.register import register_plugins
from app.settings import FLASKVARS, TCOLOR, Info
from app.setup import run_setup
from app.setup import load_into_mem, run_setup
from app.start_info_logger import log_startup_info
from app.utils.filesystem import get_home_res_path
from app.utils.paths import getClientFilesExtensions
@@ -47,10 +47,9 @@ mimetypes.add_type("application/manifest+json", ".webmanifest")
werkzeug = logging.getLogger("werkzeug")
werkzeug.setLevel(logging.ERROR)
# Background tasks
@background
def bg_run_setup() -> None:
def bg_run_setup():
run_periodic_scans()
@@ -71,9 +70,10 @@ def run_swingmusic():
# Setup function calls
Info.load()
ProcessArgs()
run_setup()
Info.load()
load_into_mem()
run_swingmusic()