prevent running migrations if is_fresh_install

+ fix: sqlite3.ProgrammingError: Cannot operate on a closed cursor on ProcessAlbumColors()
+ move processing artist images from periodic_scans to Populate
+ bump hash string limit from 7 to 10
+ add last_mod property to database
+ fix: TypeError: '<' not supported between instances of 'int' and 'str' on album page
This commit is contained in:
mungai-njoroge 2023-06-20 16:34:56 +03:00
parent cc6552cb94
commit 1eac009fde
16 changed files with 103 additions and 47 deletions

View File

@ -18,7 +18,6 @@ class SQLiteAlbumMethods:
cur.execute(sql, (albumhash, colors)) cur.execute(sql, (albumhash, colors))
lastrowid = cur.lastrowid lastrowid = cur.lastrowid
cur.close()
return lastrowid return lastrowid

View File

@ -21,7 +21,6 @@ class SQLiteArtistMethods:
""" """
colors = json.dumps(colors) colors = json.dumps(colors)
cur.execute(sql, (artisthash, colors)) cur.execute(sql, (artisthash, colors))
cur.close()
@staticmethod @staticmethod
def get_all_artists(): def get_all_artists():

View File

@ -37,12 +37,13 @@ CREATE TABLE IF NOT EXISTS tracks (
artist text NOT NULL, artist text NOT NULL,
bitrate integer NOT NULL, bitrate integer NOT NULL,
copyright text, copyright text,
date text NOT NULL, date integer NOT NULL,
disc integer NOT NULL, disc integer NOT NULL,
duration integer NOT NULL, duration integer NOT NULL,
filepath text NOT NULL, filepath text NOT NULL,
folder text NOT NULL, folder text NOT NULL,
genre text, genre text,
last_mod float NOT NULL,
title text NOT NULL, title text NOT NULL,
track integer NOT NULL, track integer NOT NULL,
trackhash text NOT NULL, trackhash text NOT NULL,

View File

@ -34,11 +34,12 @@ class SQLiteTrackMethods:
filepath, filepath,
folder, folder,
genre, genre,
last_mod,
title, title,
track, track,
trackhash trackhash
) VALUES(:album, :albumartist, :albumhash, :artist, :bitrate, :copyright, ) VALUES(:album, :albumartist, :albumhash, :artist, :bitrate, :copyright,
:date, :disc, :duration, :filepath, :folder, :genre, :title, :track, :trackhash) :date, :disc, :duration, :filepath, :folder, :genre, :last_mod, :title, :track, :trackhash)
""" """
track = OrderedDict(sorted(track.items())) track = OrderedDict(sorted(track.items()))
@ -83,12 +84,16 @@ class SQLiteTrackMethods:
return None return None
@staticmethod @staticmethod
def remove_track_by_filepath(filepath: str): def remove_tracks_by_filepaths(filepaths: str | list[str]):
""" """
Removes a track from the database using its filepath. Removes a track or tracks from the database using their filepaths.
""" """
if isinstance(filepaths, str):
filepaths = [filepaths]
with SQLiteManager() as cur: with SQLiteManager() as cur:
cur.execute("DELETE FROM tracks WHERE filepath=?", (filepath,)) for filepath in filepaths:
cur.execute("DELETE FROM tracks WHERE filepath=?", (filepath,))
@staticmethod @staticmethod
def remove_tracks_by_folders(folders: set[str]): def remove_tracks_by_folders(folders: set[str]):

View File

@ -79,7 +79,7 @@ class CheckArtistImages:
tqdm( tqdm(
pool.map(self.download_image, artist_store.ArtistStore.artists), pool.map(self.download_image, artist_store.ArtistStore.artists),
total=len(artist_store.ArtistStore.artists), total=len(artist_store.ArtistStore.artists),
desc="Downloading artist images", desc="Downloading missing artist images",
) )
) )

View File

@ -34,7 +34,11 @@ def get_image_colors(image: str, count=1) -> list[str]:
def process_color(item_hash: str, is_album=True): 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() path = (
settings.Paths.get_sm_thumb_path()
if is_album
else settings.Paths.get_artist_img_sm_path()
)
path = Path(path) / (item_hash + ".webp") path = Path(path) / (item_hash + ".webp")
if not path.exists(): if not path.exists():
@ -69,6 +73,8 @@ class ProcessAlbumColors:
color_str = json.dumps(colors) color_str = json.dumps(colors)
db.insert_one_album(cur, album.albumhash, color_str) db.insert_one_album(cur, album.albumhash, color_str)
cur.close()
class ProcessArtistColors: class ProcessArtistColors:
""" """
@ -95,3 +101,5 @@ class ProcessArtistColors:
artist.set_colors(colors) artist.set_colors(colors)
adb.insert_one_artist(cur, artist.artisthash, colors) adb.insert_one_artist(cur, artist.artisthash, colors)
cur.close()

View File

@ -1,10 +1,14 @@
from concurrent.futures import ThreadPoolExecutor import os
from tqdm import tqdm from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor
from requests import ConnectionError as RequestConnectionError
from requests import ReadTimeout
from app import settings from app import settings
from app.db.sqlite.tracks import SQLiteTrackMethods from app.db.sqlite.tracks import SQLiteTrackMethods
from app.db.sqlite.settings import SettingsSQLMethods as sdb from app.db.sqlite.settings import SettingsSQLMethods as sdb
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.lib.artistlib import CheckArtistImages
from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors
from app.lib.taglib import extract_thumb, get_tags from app.lib.taglib import extract_thumb, get_tags
@ -16,9 +20,11 @@ from app.utils.filesystem import run_fast_scandir
from app.store.albums import AlbumStore from app.store.albums import AlbumStore
from app.store.tracks import TrackStore from app.store.tracks import TrackStore
from app.store.artists import ArtistStore from app.store.artists import ArtistStore
from app.utils.network import Ping
get_all_tracks = SQLiteTrackMethods.get_all_tracks get_all_tracks = SQLiteTrackMethods.get_all_tracks
insert_many_tracks = SQLiteTrackMethods.insert_many_tracks insert_many_tracks = SQLiteTrackMethods.insert_many_tracks
remove_tracks_by_filepaths = SQLiteTrackMethods.remove_tracks_by_filepaths
POPULATE_KEY = "" POPULATE_KEY = ""
@ -48,8 +54,8 @@ class Populate:
if len(dirs_to_scan) == 0: if len(dirs_to_scan) == 0:
log.warning( log.warning(
( (
"The root directory is not configured. " "The root directory is not configured. "
+ "Open the app in your webbrowser to configure." + "Open the app in your webbrowser to configure."
) )
) )
return return
@ -65,18 +71,44 @@ class Populate:
for _dir in dirs_to_scan: for _dir in dirs_to_scan:
files.extend(run_fast_scandir(_dir, full=True)[1]) files.extend(run_fast_scandir(_dir, full=True)[1])
self.remove_modified(tracks)
untagged = self.filter_untagged(tracks, files) untagged = self.filter_untagged(tracks, files)
if len(untagged) == 0: if len(untagged) != 0:
log.info("All clear, no unread files.") self.tag_untagged(untagged, key)
return
self.tag_untagged(untagged, key)
ProcessTrackThumbnails() ProcessTrackThumbnails()
ProcessAlbumColors() ProcessAlbumColors()
ProcessArtistColors() ProcessArtistColors()
tried_to_download_new_images = False
if Ping()():
tried_to_download_new_images = True
try:
CheckArtistImages()
except (RequestConnectionError, ReadTimeout):
log.error(
"Internet connection lost. Downloading artist images stopped."
)
else:
log.warning(
f"No internet connection. Downloading artist images halted for {settings.get_scan_sleep_time()} seconds."
)
# Re-process the new artist images.
if tried_to_download_new_images:
ProcessArtistColors()
@staticmethod
def remove_modified(tracks: list[Track]):
modified = [
t.filepath for t in tracks if t.last_mod != os.path.getmtime(t.filepath)
]
TrackStore.remove_tracks_by_filepaths(modified)
remove_tracks_by_filepaths(modified)
@staticmethod @staticmethod
def filter_untagged(tracks: list[Track], files: list[str]): def filter_untagged(tracks: list[Track], files: list[str]):
tagged_files = [t.filepath for t in tracks] tagged_files = [t.filepath for t in tracks]
@ -120,6 +152,7 @@ class Populate:
log.warning("Could not read file: %s", file) log.warning("Could not read file: %s", file)
if len(tagged_tracks) > 0: if len(tagged_tracks) > 0:
log.info("Adding %s tracks to database", len(tagged_tracks))
insert_many_tracks(tagged_tracks) insert_many_tracks(tagged_tracks)
log.info("Added %s/%s tracks", tagged_count, len(untagged)) log.info("Added %s/%s tracks", tagged_count, len(untagged))

View File

@ -64,17 +64,18 @@ def extract_thumb(filepath: str, webp_path: str) -> bool:
return False return False
def extract_date(date_str: str | None, filepath: str) -> int: def extract_date(date_str: str | None, timestamp: float) -> int:
try: try:
return int(date_str.split("-")[0]) return int(date_str.split("-")[0])
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
# TODO: USE FILEPATH LAST-MOD DATE instead of current date print(datetime.datetime.fromtimestamp(timestamp).year)
return datetime.date.today().today().year return datetime.datetime.fromtimestamp(timestamp).year
def get_tags(filepath: str): def get_tags(filepath: str):
filetype = filepath.split(".")[-1] filetype = filepath.split(".")[-1]
filename = (filepath.split("/")[-1]).replace(f".{filetype}", "") filename = (filepath.split("/")[-1]).replace(f".{filetype}", "")
last_mod = os.path.getmtime(filepath)
try: try:
tags = TinyTag.get(filepath) tags = TinyTag.get(filepath)
@ -141,9 +142,10 @@ def get_tags(filepath: str):
tags.image = f"{tags.albumhash}.webp" tags.image = f"{tags.albumhash}.webp"
tags.folder = win_replace_slash(os.path.dirname(filepath)) tags.folder = win_replace_slash(os.path.dirname(filepath))
tags.date = extract_date(tags.year, filepath) tags.date = extract_date(tags.year, last_mod)
tags.filepath = win_replace_slash(filepath) tags.filepath = win_replace_slash(filepath)
tags.filetype = filetype tags.filetype = filetype
tags.last_mod = last_mod
tags = tags.__dict__ tags = tags.__dict__

View File

@ -16,4 +16,4 @@ def validate_tracks() -> None:
for track in tqdm(TrackStore.tracks, desc="Checking for deleted tracks"): for track in tqdm(TrackStore.tracks, desc="Checking for deleted tracks"):
if not os.path.exists(track.filepath): if not os.path.exists(track.filepath):
TrackStore.tracks.remove(track) TrackStore.tracks.remove(track)
tdb.remove_track_by_filepath(track.filepath) tdb.remove_tracks_by_filepaths(track.filepath)

View File

@ -8,18 +8,16 @@ import time
from watchdog.events import PatternMatchingEventHandler from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer from watchdog.observers import Observer
from app.logger import log
from app.lib.taglib import get_tags
from app.models import Artist, Track
from app import settings from app import settings
from app.db.sqlite.settings import SettingsSQLMethods as sdb
from app.db.sqlite.tracks import SQLiteManager from app.db.sqlite.tracks import SQLiteManager
from app.db.sqlite.tracks import SQLiteTrackMethods as db from app.db.sqlite.tracks import SQLiteTrackMethods as db
from app.db.sqlite.settings import SettingsSQLMethods as sdb from app.lib.taglib import get_tags
from app.logger import log
from app.store.tracks import TrackStore from app.models import Artist, Track
from app.store.albums import AlbumStore from app.store.albums import AlbumStore
from app.store.artists import ArtistStore from app.store.artists import ArtistStore
from app.store.tracks import TrackStore
class Watcher: class Watcher:
@ -133,7 +131,7 @@ def add_track(filepath: str) -> None:
""" """
# remove the track if it already exists # remove the track if it already exists
TrackStore.remove_track_by_filepath(filepath) TrackStore.remove_track_by_filepath(filepath)
db.remove_track_by_filepath(filepath) db.remove_tracks_by_filepaths(filepath)
# add the track to the database and store. # add the track to the database and store.
tags = get_tags(filepath) tags = get_tags(filepath)
@ -142,7 +140,6 @@ def add_track(filepath: str) -> None:
return return
with SQLiteManager() as cur: with SQLiteManager() as cur:
db.remove_track_by_filepath(tags["filepath"])
db.insert_one_track(tags, cur) db.insert_one_track(tags, cur)
track = Track(**tags) track = Track(**tags)
@ -168,7 +165,7 @@ def remove_track(filepath: str) -> None:
except IndexError: except IndexError:
return return
db.remove_track_by_filepath(filepath) db.remove_tracks_by_filepaths(filepath)
TrackStore.remove_track_by_filepath(filepath) TrackStore.remove_track_by_filepath(filepath)
empty_album = TrackStore.count_tracks_by_hash(track.albumhash) > 0 empty_album = TrackStore.count_tracks_by_hash(track.albumhash) > 0

View File

@ -24,6 +24,10 @@ def apply_migrations():
userdb_version = MigrationManager.get_userdatadb_postinit_version() userdb_version = MigrationManager.get_userdatadb_postinit_version()
maindb_version = MigrationManager.get_maindb_postinit_version() maindb_version = MigrationManager.get_maindb_postinit_version()
# No migrations to run
if userdb_version == 0 and maindb_version == 0:
return
for migration in main_db_migrations: for migration in main_db_migrations:
if migration.version > maindb_version: if migration.version > maindb_version:
log.info("Running new MAIN-DB post-init migration: %s", migration.name) log.info("Running new MAIN-DB post-init migration: %s", migration.name)

View File

@ -28,6 +28,10 @@ def run_preinit_migrations():
except OperationalError: except OperationalError:
userdb_version = 0 userdb_version = 0
# No migrations to run
if userdb_version == 0:
return
for migration in all_preinits: for migration in all_preinits:
if migration.version > userdb_version: if migration.version > userdb_version:
log.warn("Running new pre-init migration: %s", migration.name) log.warn("Running new pre-init migration: %s", migration.name)

View File

@ -19,12 +19,13 @@ class Track:
artist: str | list[ArtistMinimal] artist: str | list[ArtistMinimal]
bitrate: int bitrate: int
copyright: str copyright: str
date: str date: int
disc: int disc: int
duration: int duration: int
filepath: str filepath: str
folder: str folder: str
genre: str | list[str] genre: str | list[str]
last_mod: float
title: str title: str
track: int track: int
trackhash: str trackhash: str

View File

@ -2,11 +2,8 @@
This module contains functions for the server This module contains functions for the server
""" """
import time import time
from requests import ReadTimeout
from requests import ConnectionError as RequestConnectionError
from app.logger import log from app.logger import log
from app.lib.artistlib import CheckArtistImages
from app.lib.populate import Populate, PopulateCancelledError from app.lib.populate import Populate, PopulateCancelledError
from app.utils.generators import get_random_str from app.utils.generators import get_random_str
@ -30,13 +27,5 @@ def run_periodic_scans():
except PopulateCancelledError: except PopulateCancelledError:
pass pass
if Ping()():
try:
CheckArtistImages()
except (RequestConnectionError, ReadTimeout):
log.error(
"Internet connection lost. Downloading artist images stopped."
)
sleep_time = get_scan_sleep_time() sleep_time = get_scan_sleep_time()
time.sleep(sleep_time) time.sleep(sleep_time)

View File

@ -52,6 +52,18 @@ class TrackStore:
cls.tracks.remove(track) cls.tracks.remove(track)
break break
@classmethod
def remove_tracks_by_filepaths(cls, filepaths: list[str]):
"""
Removes multiple tracks from the store by their filepaths.
"""
paths_str = "~".join(filepaths)
for track in cls.tracks:
if track.filepath in paths_str:
cls.tracks.remove(track)
@classmethod @classmethod
def remove_tracks_by_dir_except(cls, dirs: list[str]): def remove_tracks_by_dir_except(cls, dirs: list[str]):
"""Removes all tracks not in the root directories.""" """Removes all tracks not in the root directories."""
@ -98,7 +110,9 @@ class TrackStore:
track.is_favorite = False track.is_favorite = False
@classmethod @classmethod
def append_track_artists(cls, albumhash: str, artists: list[str], new_album_title: str): def append_track_artists(
cls, albumhash: str, artists: list[str], new_album_title: str
):
tracks = cls.get_tracks_by_albumhash(albumhash) tracks = cls.get_tracks_by_albumhash(albumhash)
for track in tracks: for track in tracks:

View File

@ -3,7 +3,7 @@ import hashlib
from unidecode import unidecode from unidecode import unidecode
def create_hash(*args: str, decode=False, limit=7) -> str: def create_hash(*args: str, decode=False, limit=10) -> str:
""" """
Creates a simple hash for an album Creates a simple hash for an album
""" """
@ -19,7 +19,7 @@ def create_hash(*args: str, decode=False, limit=7) -> str:
return str_[-limit:] return str_[-limit:]
def create_folder_hash(*args: str, limit=7) -> str: def create_folder_hash(*args: str, limit=10) -> str:
""" """
Creates a simple hash for an album Creates a simple hash for an album
""" """