mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
4b04b8b155
+ fix: file count when you have similar folder names + enforce trailing / on track folder paths
154 lines
4.4 KiB
Python
154 lines
4.4 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
from app.logger import log
|
|
from app.models import Folder, Track
|
|
from app.serializers.track import serialize_tracks
|
|
from app.settings import SUPPORTED_FILES
|
|
from app.utils.wintools import win_replace_slash
|
|
|
|
from app.store.tracks import TrackStore
|
|
|
|
|
|
def create_folder(path: str, trackcount=0, foldercount=0) -> Folder:
|
|
"""
|
|
Creates a folder object from a path.
|
|
"""
|
|
folder = Path(path)
|
|
|
|
return Folder(
|
|
name=folder.name,
|
|
path=win_replace_slash(str(folder)),
|
|
is_sym=folder.is_symlink(),
|
|
trackcount=trackcount,
|
|
foldercount=foldercount,
|
|
)
|
|
|
|
|
|
def get_first_child_from_path(root: str, maybe_child: str):
|
|
"""
|
|
Given a root path and a path, returns the first child from the root path.
|
|
"""
|
|
if not maybe_child.startswith(root) or maybe_child == root:
|
|
return None
|
|
|
|
children = maybe_child.replace(root, "")
|
|
first = Path(children).parts[0]
|
|
|
|
return os.path.join(root, first)
|
|
|
|
def get_folders(paths: list[str]):
|
|
"""
|
|
Filters out folders that don't have any tracks and
|
|
returns a list of folder objects.
|
|
"""
|
|
count_dict = {
|
|
"tracks": {path: 0 for path in paths},
|
|
# folders are immediate children of the root folder
|
|
"folders": {path: set() for path in paths},
|
|
}
|
|
|
|
for track in TrackStore.tracks:
|
|
for path in paths:
|
|
|
|
# a child path should be longer than the root path
|
|
if len(track.folder) >= len(path) and track.folder.startswith(path):
|
|
count_dict["tracks"][path] += 1
|
|
|
|
# counting subfolders
|
|
p = get_first_child_from_path(path, track.folder)
|
|
|
|
if p:
|
|
count_dict["folders"][path].add(p)
|
|
|
|
folders = [
|
|
{
|
|
"path": path,
|
|
"trackcount": count_dict["tracks"][path],
|
|
"foldercount": len(count_dict["folders"][path]),
|
|
}
|
|
for path in paths
|
|
]
|
|
|
|
return [
|
|
create_folder(f["path"], f["trackcount"], f["foldercount"])
|
|
for f in folders
|
|
if f["trackcount"] > 0
|
|
]
|
|
|
|
|
|
class GetFilesAndDirs:
|
|
"""
|
|
Get files and folders from a directory.
|
|
"""
|
|
|
|
def __init__(self, path: str, tracks_only=False) -> None:
|
|
self.path = path
|
|
self.tracks_only = tracks_only
|
|
|
|
def get_files_and_dirs(self, path: str, skip_empty_folders=True):
|
|
"""
|
|
Given a path, returns a list of tracks and folders in that immediate path.
|
|
|
|
Can recursively call itself to skip through empty folders.
|
|
"""
|
|
try:
|
|
entries = os.scandir(path)
|
|
except FileNotFoundError:
|
|
return {
|
|
"path": path,
|
|
"tracks": [],
|
|
"folders": [],
|
|
}
|
|
|
|
dirs, files = [], []
|
|
|
|
for entry in entries:
|
|
ext = os.path.splitext(entry.name)[1].lower()
|
|
|
|
if entry.is_dir() and not entry.name.startswith("."):
|
|
dir = win_replace_slash(entry.path)
|
|
# add a trailing slash to the folder path
|
|
# to avoid matching a folder starting with the same name as the root path
|
|
# eg. .../Music and .../Music VideosI
|
|
dirs.append(os.path.join(dir, ""))
|
|
elif entry.is_file() and ext in SUPPORTED_FILES:
|
|
files.append(win_replace_slash(entry.path))
|
|
|
|
files_ = []
|
|
|
|
for file in files:
|
|
try:
|
|
files_.append(
|
|
{
|
|
"path": file,
|
|
"time": os.path.getmtime(file),
|
|
}
|
|
)
|
|
except OSError as e:
|
|
log.error(e)
|
|
|
|
files_.sort(key=lambda f: f["time"])
|
|
files = [f["path"] for f in files_]
|
|
|
|
tracks = TrackStore.get_tracks_by_filepaths(files)
|
|
|
|
folders = []
|
|
if not self.tracks_only:
|
|
folders = get_folders(dirs)
|
|
|
|
if skip_empty_folders and len(folders) == 1 and len(tracks) == 0:
|
|
# INFO: When we only have one folder and no tracks,
|
|
# skip through empty folders.
|
|
# Call recursively with the first folder in the list.
|
|
return self.get_files_and_dirs(folders[0].path)
|
|
|
|
return {
|
|
"path": path,
|
|
"tracks": serialize_tracks(tracks),
|
|
"folders": folders,
|
|
}
|
|
|
|
def __call__(self):
|
|
return self.get_files_and_dirs(self.path)
|