try hashmap trackstore

This commit is contained in:
cwilvx
2024-07-07 16:52:18 +03:00
parent 2ba5d6c1d7
commit c116957982
8 changed files with 263 additions and 81 deletions
+232 -68
View File
@@ -1,18 +1,99 @@
# from tqdm import tqdm
import itertools
import sys
from typing import Callable
from flask_jwt_extended import current_user
from app.db.libdata import TrackTable
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
# from app.db.sqlite.tracks import SQLiteTrackMethods as trackdb
from app.db.userdata import FavoritesTable
from app.models import Track
from app.utils.bisection import use_bisection
from app.utils.customlist import CustomList
from app.utils.remove_duplicates import remove_duplicates
TRACKS_LOAD_KEY = ""
class TrackGroup:
"""
Tracks grouped under the same trackhash.
"""
def __init__(self, tracks: list[Track]):
self.tracks = tracks
def append(self, track: Track):
"""
Adds a track to the group.
"""
self.tracks.append(track)
def remove(self, track: Track):
"""
Removes a track from the group.
"""
self.tracks.remove(track)
def set_fav_userids(self, userids: set[int]):
"""
Sets the favorite userids.
"""
for track in self.tracks:
track.fav_userids = userids
def get_best(self):
"""
Returns the track with higest bitrate.
"""
return max(self.tracks, key=lambda x: x.bitrate)
def toggle_favorite(self, remove: bool = False):
"""
Adds a track to the favorites.
"""
userids = set(self.tracks[0].fav_userids)
if remove:
userids.remove(current_user["id"])
else:
userids.add(current_user["id"])
for track in self.tracks:
track.fav_userids = userids
def __len__(self):
return len(self.tracks)
class classproperty(property):
"""
A class property decorator.
"""
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class TrackStore:
tracks: list[Track] = CustomList()
# {'trackhash': Track[]}
trackhashmap: dict[str, TrackGroup] = dict()
@classproperty
def tracks(cls) -> list[Track]:
return cls.get_flat_list()
@classmethod
def get_flat_list(cls):
"""
Returns a flat list of all tracks.
"""
return list(
itertools.chain.from_iterable(
[group.tracks for group in cls.trackhashmap.values()]
)
)
@classmethod
def load_all_tracks(cls, instance_key: str):
@@ -24,32 +105,60 @@ class TrackStore:
global TRACKS_LOAD_KEY
TRACKS_LOAD_KEY = instance_key
cls.tracks = CustomList(trackdb.get_all_tracks())
cls.trackhashmap = dict()
tracks = TrackTable.get_all()
favs = favdb.get_fav_tracks()
records = dict()
for fav in favs:
r = records.setdefault(fav[1], set())
r.add(fav[4])
for track in cls.tracks:
# INFO: Load all tracks into the dict store
for track in tracks:
if instance_key != TRACKS_LOAD_KEY:
return
userids = records.get(track.trackhash, set())
track.fav_userids = list(userids)
exists = cls.trackhashmap.get(track.trackhash, None)
if not exists:
cls.trackhashmap[track.trackhash] = TrackGroup([track])
else:
cls.trackhashmap[track.trackhash].append(track)
print("Done!")
# favs = favdb.get_fav_tracks()
favs = FavoritesTable.get_all()
records: dict[str, set[int]] = dict()
# convert records: {trackhash: {userid, userid, ...}}
for fav in favs:
if fav.hash not in records:
# if trackhash not in dict, add it
# and set the value to a set containing the userid
records[fav.hash] = {fav.userid}
# if trackhash is in dict, add the userid to the set
records[fav.hash].add(fav.userid)
for record in records:
if instance_key != TRACKS_LOAD_KEY:
return
group = cls.trackhashmap.get(record, None)
if not group:
continue
group.set_fav_userids(records.get(record, set()))
# print("Done!")
# print(cls.trackhashmap.get("0d6b22c19c").tracks[0].fav_userids)
# sys.exit(0)
@classmethod
def add_track(cls, track: Track):
"""
Adds a single track to the store.
"""
group = cls.trackhashmap.get(track.trackhash, None)
cls.tracks.append(track)
if group:
return group.append(track)
cls.trackhashmap[track.trackhash] = TrackGroup([track])
@classmethod
def add_tracks(cls, tracks: list[Track]):
@@ -57,17 +166,21 @@ class TrackStore:
Adds multiple tracks to the store.
"""
cls.tracks.extend(tracks)
for track in tracks:
cls.add_track(track)
@classmethod
def remove_track_obj(cls, track: Track):
def remove_track(cls, track: Track):
"""
Removes a single track from the store.
"""
try:
cls.tracks.remove(track)
except ValueError:
pass
group = cls.trackhashmap.get(track.trackhash, None)
if group:
group.remove(track)
if len(group) == 0:
del cls.trackhashmap[track.trackhash]
@classmethod
def remove_track_by_filepath(cls, filepath: str):
@@ -75,10 +188,7 @@ class TrackStore:
Removes a track from the store by its filepath.
"""
for track in cls.tracks:
if track.filepath == filepath:
cls.remove_track_obj(track)
break
return cls.remove_tracks_by_filepaths({filepath})
@classmethod
def remove_tracks_by_filepaths(cls, filepaths: set[str]):
@@ -86,47 +196,47 @@ class TrackStore:
Removes multiple tracks from the store by their filepaths.
"""
for track in cls.tracks:
if track.filepath in filepaths:
cls.remove_track_obj(track)
filecount = len(filepaths)
for trackhash in cls.trackhashmap:
group = cls.trackhashmap[trackhash]
for track in group.tracks:
if track.filepath in filepaths:
group.remove(track)
if len(group) == 0:
del cls.trackhashmap[trackhash]
filecount -= 1
if filecount == 0:
break
@classmethod
def count_tracks_by_trackhash(cls, trackhash: str) -> int:
"""
Counts the number of tracks with a specific trackhash.
"""
return sum(1 for track in cls.tracks if track.trackhash == trackhash)
return len(cls.trackhashmap.get(trackhash, []))
@classmethod
def make_track_fav(cls, trackhash: str):
def toggle_favorite(cls, trackhash: str, remove: bool = False):
"""
Adds a track to the favorites.
"""
for track in cls.tracks:
if track.trackhash == trackhash:
if current_user["id"] not in track.fav_userids:
track.fav_userids.append(current_user["id"])
group = cls.trackhashmap.get(trackhash)
if group:
group.toggle_favorite(remove=remove)
@classmethod
def remove_track_from_fav(cls, trackhash: str):
"""
Removes a track from the favorites.
"""
for track in cls.tracks:
if track.trackhash == trackhash:
if current_user["id"] in track.fav_userids:
track.fav_userids.remove(current_user["id"])
@classmethod
def append_track_artists(
cls, albumhash: str, artists: list[str], new_album_title: str
):
tracks = cls.get_tracks_by_albumhash(albumhash)
for track in tracks:
track.add_artists(artists, new_album_title)
return cls.toggle_favorite(trackhash, remove=True)
# ================================================
# ================== GETTERS =====================
@@ -138,16 +248,16 @@ class TrackStore:
Returns a list of tracks by their hashes.
"""
hash_set = set(trackhashes)
set_len = len(hash_set)
tracks = []
for track in cls.tracks:
if track.trackhash in hash_set:
tracks: list[Track] = []
for trackhash in hash_set:
group = cls.trackhashmap.get(trackhash, None)
if group:
track = group.get_best()
tracks.append(track)
if len(tracks) == set_len:
break
# sort the tracks in the order of the given trackhashes
tracks.sort(key=lambda t: trackhashes.index(t.trackhash))
return tracks
@@ -156,32 +266,86 @@ class TrackStore:
def get_tracks_by_filepaths(cls, paths: list[str]) -> list[Track]:
"""
Returns all tracks matching the given paths.
⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔
"""
tracks = sorted(cls.tracks, key=lambda x: x.filepath)
tracks = use_bisection(tracks, "filepath", paths)
return [track for track in tracks if track is not None]
# tracks = sorted(cls.trackhashmap, key=lambda x: x.filepath)
# tracks = use_bisection(tracks, "filepath", paths)
# return [track for track in tracks if track is not None]
# return cls.find_tracks_by(key="filepath", value=paths)
tracks: list[Track] = []
for trackhash in cls.trackhashmap:
group = cls.trackhashmap.get(trackhash)
if not group:
continue
for track in group.tracks:
if track.filepath in paths:
tracks.append(track)
return tracks
@classmethod
def find_tracks_by(
cls,
key: str,
value: str,
predicate: Callable = lambda prop_value, value: prop_value == value,
including_duplicates: bool = False,
):
"""
Find all tracks by a specific key.
"""
tracks: list[Track] = []
for trackhash in cls.trackhashmap:
group = cls.trackhashmap.get(trackhash, None)
if not group:
continue
for track in group.tracks:
prop_value = getattr(track, key)
if predicate(prop_value, value):
tracks.append(track)
if including_duplicates:
return tracks
return remove_duplicates(tracks)
@classmethod
def get_tracks_by_albumhash(cls, album_hash: str) -> list[Track]:
"""
Returns all tracks matching the given album hash.
"""
tracks = [t for t in cls.tracks if t.albumhash == album_hash]
return remove_duplicates(tracks, is_album_tracks=True)
return cls.find_tracks_by(key="albumhash", value=album_hash)
@classmethod
def get_tracks_by_artisthash(cls, artisthash: str):
"""
Returns all tracks matching the given artist. Duplicate tracks are removed.
"""
tracks = [t for t in cls.tracks if artisthash in t.artist_hashes]
tracks = remove_duplicates(tracks)
tracks.sort(key=lambda x: x.last_mod)
return tracks
predicate = lambda artisthashes, artisthash: artisthash in artisthashes
return cls.find_tracks_by(
key="artist_hashes", value=artisthash, predicate=predicate
)
@classmethod
def get_tracks_in_path(cls, path: str):
"""
Returns all tracks in the given path.
"""
return (t for t in cls.tracks if t.folder.startswith(path))
predicate: Callable[[str, str], bool] = (
lambda track_folder, path: track_folder.startswith(path)
)
return cls.find_tracks_by(
key="folder",
value=path,
predicate=predicate,
including_duplicates=True,
)