Skip to content

Commit

Permalink
Add new release toggling (#3)
Browse files Browse the repository at this point in the history
* model 1

* simplify

* cli = fixes
  • Loading branch information
azuline authored Oct 20, 2023
1 parent 39c82b0 commit e10fb4f
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 32 deletions.
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def seeded_cache(config: Config) -> None:
f"""\
INSERT INTO releases
(id , source_path , cover_image_path , datafile_mtime, virtual_dirname, title , release_type, release_year, multidisc, new , formatted_artists)
VALUES ('r1', '{dirpaths[0]}', null , '999' , 'r1' , 'Release 1', 'album' , 2023 , false , true , 'Techno Man;Bass Man')
VALUES ('r1', '{dirpaths[0]}', null , '999' , 'r1' , 'Release 1', 'album' , 2023 , false , false , 'Techno Man;Bass Man')
, ('r2', '{dirpaths[1]}', '{imagepaths[0]}', '999' , 'r2' , 'Release 2', 'album' , 2021 , false , false, 'Violin Woman feat. Conductor Woman');
INSERT INTO releases_genres
Expand Down
13 changes: 12 additions & 1 deletion rose/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
edit_collage_in_editor,
)
from rose.config import Config
from rose.releases import dump_releases
from rose.releases import dump_releases, toggle_release_new
from rose.virtualfs import mount_virtualfs, unmount_virtualfs
from rose.watcher import start_watchdog

Expand Down Expand Up @@ -102,6 +102,17 @@ def print1(ctx: Context) -> None:
print(dump_releases(ctx.config))


@releases.command()
@click.argument("release", type=str, nargs=1)
@click.pass_obj
def toggle_new(ctx: Context, release: str) -> None:
"""
Toggle whether a release is new. Accepts a release's UUID or virtual fs dirname (both are
accepted).
"""
toggle_release_new(ctx.config, release)


@cli.group()
def collages() -> None:
"""Manage collages."""
Expand Down
18 changes: 11 additions & 7 deletions rose/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,8 @@ def update_cache_for_releases(
release_virtual_dirname += " [" + ";".join(release.genres) + "]"
if release.labels:
release_virtual_dirname += " {" + ";".join(release.labels) + "}"
# Reimplement this once we have new toggling.
# if release.new:
# release_virtual_dirname += " +NEW!+"
if release.new:
release_virtual_dirname = "[NEW] " + release_virtual_dirname
release_virtual_dirname = sanitize_filename(release_virtual_dirname)
# And in case of a name collision, add an extra number at the end. Iterate to
# find the first unused number.
Expand Down Expand Up @@ -1203,12 +1202,12 @@ def list_collages(c: Config) -> Iterator[str]:
yield row["name"]


def list_collage_releases(c: Config, collage_name: str) -> Iterator[tuple[int, str, Path]]:
"""Returns tuples of (position, release_virtual_dirname, release_source_path)."""
def list_collage_releases(c: Config, collage_name: str) -> Iterator[tuple[int, str, Path, bool]]:
"""Returns tuples of (position, release_virtual_dirname, release_source_path, release_new)."""
with connect(c) as conn:
cursor = conn.execute(
"""
SELECT cr.position, r.virtual_dirname, r.source_path
SELECT cr.position, r.virtual_dirname, r.source_path, r.new
FROM collages_releases cr
JOIN releases r ON r.id = cr.release_id
WHERE cr.collage_name = ?
Expand All @@ -1217,7 +1216,12 @@ def list_collage_releases(c: Config, collage_name: str) -> Iterator[tuple[int, s
(collage_name,),
)
for row in cursor:
yield (row["position"], row["virtual_dirname"], Path(row["source_path"]))
yield (
row["position"],
row["virtual_dirname"],
Path(row["source_path"]),
bool(row["new"]),
)


def release_exists(c: Config, virtual_dirname: str) -> Path | None:
Expand Down
12 changes: 6 additions & 6 deletions rose/cache_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ def test_list_releases(config: Config) -> None:
type="album",
year=2023,
multidisc=False,
new=True,
new=False,
genres=["Deep House", "Techno"],
labels=["Silk Music"],
artists=[
Expand Down Expand Up @@ -421,7 +421,7 @@ def test_list_releases(config: Config) -> None:
type="album",
year=2023,
multidisc=False,
new=True,
new=False,
genres=["Deep House", "Techno"],
labels=["Silk Music"],
artists=[
Expand All @@ -444,7 +444,7 @@ def test_list_releases(config: Config) -> None:
type="album",
year=2023,
multidisc=False,
new=True,
new=False,
genres=["Deep House", "Techno"],
labels=["Silk Music"],
artists=[
Expand All @@ -467,7 +467,7 @@ def test_list_releases(config: Config) -> None:
type="album",
year=2023,
multidisc=False,
new=True,
new=False,
genres=["Deep House", "Techno"],
labels=["Silk Music"],
artists=[
Expand Down Expand Up @@ -522,8 +522,8 @@ def test_list_collages(config: Config) -> None:
def test_list_collage_releases(config: Config) -> None:
releases = list(list_collage_releases(config, "Rose Gold"))
assert set(releases) == {
(0, "r1", config.music_source_dir / "r1"),
(1, "r2", config.music_source_dir / "r2"),
(0, "r1", config.music_source_dir / "r1", False),
(1, "r2", config.music_source_dir / "r2", False),
}
releases = list(list_collage_releases(config, "Ruby Red"))
assert releases == []
Expand Down
2 changes: 1 addition & 1 deletion rose/collages.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def dump_collages(c: Config) -> str:
collage_names = list(list_collages(c))
for name in collage_names:
out[name] = []
for pos, virtual_dirname, _ in list_collage_releases(c, name):
for pos, virtual_dirname, _, _ in list_collage_releases(c, name):
out[name].append({"position": pos, "release": virtual_dirname})
return json.dumps(out)

Expand Down
26 changes: 26 additions & 0 deletions rose/releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
from pathlib import Path
from typing import Any

import tomli_w
import tomllib
from send2trash import send2trash

from rose.cache import (
STORED_DATA_FILE_REGEX,
get_release_id_from_virtual_dirname,
get_release_source_path_from_id,
get_release_virtual_dirname_from_id,
list_releases,
update_cache_evict_nonexistent_releases,
update_cache_for_collages,
update_cache_for_releases,
)
from rose.common import RoseError, valid_uuid
from rose.config import Config
Expand Down Expand Up @@ -49,6 +53,28 @@ def delete_release(c: Config, release_id_or_virtual_dirname: str) -> None:
update_cache_for_collages(c, None, force=True)


def toggle_release_new(c: Config, release_id_or_virtual_dirname: str) -> None:
release_id, release_dirname = resolve_release_ids(c, release_id_or_virtual_dirname)
source_path = get_release_source_path_from_id(c, release_id)
if source_path is None:
logger.debug(f"Failed to lookup source path for release {release_id} ({release_dirname})")
return None

for f in source_path.iterdir():
if not STORED_DATA_FILE_REGEX.match(f.name):
continue

with f.open("rb") as fp:
data = tomllib.load(fp)
data["new"] = not data["new"]
with f.open("wb") as fp:
tomli_w.dump(data, fp)
update_cache_for_releases(c, [source_path], force=True)
return

logger.critical(f"Failed to find .rose.toml in {source_path}")


def resolve_release_ids(c: Config, release_id_or_virtual_dirname: str) -> tuple[str, str]:
if valid_uuid(release_id_or_virtual_dirname):
uuid = release_id_or_virtual_dirname
Expand Down
29 changes: 29 additions & 0 deletions rose/releases_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import shutil

import pytest
import tomllib

from conftest import TEST_RELEASE_1
from rose.cache import connect, update_cache
Expand All @@ -10,6 +11,7 @@
delete_release,
dump_releases,
resolve_release_ids,
toggle_release_new,
)


Expand All @@ -30,6 +32,33 @@ def test_delete_release(config: Config) -> None:
assert cursor.fetchone()[0] == 0


def test_toggle_release_new(config: Config) -> None:
shutil.copytree(TEST_RELEASE_1, config.music_source_dir / TEST_RELEASE_1.name)
update_cache(config)
with connect(config) as conn:
cursor = conn.execute("SELECT id, virtual_dirname FROM releases")
release_id = cursor.fetchone()["id"]
datafile = config.music_source_dir / TEST_RELEASE_1.name / f".rose.{release_id}.toml"

# Set not new.
toggle_release_new(config, release_id)
with datafile.open("rb") as fp:
data = tomllib.load(fp)
assert data["new"] is False
with connect(config) as conn:
cursor = conn.execute("SELECT virtual_dirname FROM releases")
assert not cursor.fetchone()["virtual_dirname"].startswith("[NEW] ")

# Set new.
toggle_release_new(config, release_id)
with datafile.open("rb") as fp:
data = tomllib.load(fp)
assert data["new"] is True
with connect(config) as conn:
cursor = conn.execute("SELECT virtual_dirname FROM releases")
assert cursor.fetchone()["virtual_dirname"].startswith("[NEW] ")


def test_resolve_release_ids(config: Config) -> None:
shutil.copytree(TEST_RELEASE_1, config.music_source_dir / TEST_RELEASE_1.name)
update_cache(config)
Expand Down
51 changes: 37 additions & 14 deletions rose/virtualfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
)
from rose.common import sanitize_filename
from rose.config import Config
from rose.releases import ReleaseDoesNotExistError, delete_release
from rose.releases import ReleaseDoesNotExistError, delete_release, toggle_release_new

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -149,11 +149,11 @@ def readdir(self, path: str, _: int) -> Iterator[str]:
sanitized_genre_filter=p.genre,
sanitized_label_filter=p.label,
):
yield release.virtual_dirname
self.getattr_cache[path + "/" + release.virtual_dirname] = (
time.time(),
("dir", release.source_path),
)
v = release.virtual_dirname
if release.new:
v = "[NEW] " + v
yield v
self.getattr_cache[path + "/" + v] = (time.time(), ("dir", release.source_path))
elif p.view == "Artists":
for artist in list_artists(self.config):
if artist in self.hide_artists_set:
Expand All @@ -175,10 +175,12 @@ def readdir(self, path: str, _: int) -> Iterator[str]:
elif p.view == "Collages" and p.collage:
releases = list(list_collage_releases(self.config, p.collage))
pad_size = max(len(str(r[0])) for r in releases)
for idx, virtual_dirname, source_dir in releases:
dirname = f"{str(idx).zfill(pad_size)}. {virtual_dirname}"
yield dirname
self.getattr_cache[path + "/" + dirname] = (time.time(), ("dir", source_dir))
for idx, v, source_dir, new in releases:
if new:
v = f"[NEW] {v}"
v = f"{str(idx).zfill(pad_size)}. {v}"
yield v
self.getattr_cache[path + "/" + v] = (time.time(), ("dir", source_dir))
elif p.view == "Collages":
# Don't need to sanitize because the collage names come from filenames.
for collage in list_collages(self.config):
Expand Down Expand Up @@ -236,6 +238,7 @@ def release(self, path: str, fh: int) -> None:
def mkdir(self, path: str, mode: int) -> None:
logger.debug(f"Received mkdir for {path=} {mode=}")
p = parse_virtual_path(path)
logger.debug(f"Parsed mkdir path as {p}")

# Possible actions:
# 1. Add a release to an existing collage.
Expand All @@ -258,6 +261,7 @@ def mkdir(self, path: str, mode: int) -> None:
def rmdir(self, path: str) -> None:
logger.debug(f"Received rmdir for {path=}")
p = parse_virtual_path(path)
logger.debug(f"Parsed rmdir path as {p}")

# Possible actions:
# 1. Delete a release from an existing collage.
Expand All @@ -277,12 +281,28 @@ def rmdir(self, path: str) -> None:
def rename(self, old: str, new: str) -> None:
logger.debug(f"Received rename for {old=} {new=}")
op = parse_virtual_path(old)
logger.debug(f"Parsed rename old path as {op}")
np = parse_virtual_path(new)
logger.debug(f"Parsed rename new path as {np}")

# Possible actions:
# 1. Rename a collage
if op.view == "Collages" and np.view == "Collages":
if op.collage and np.collage and not op.release and not np.release:
# 1. Rename a collage.
# 2. Toggle a release's new status.
if (
(op.release and np.release)
and op.release.removeprefix("[NEW] ") == np.release.removeprefix("[NEW] ")
and (not op.file and not np.file)
):
if op.release.startswith("[NEW] ") != np.release.startswith("[NEW] "):
toggle_release_new(self.config, op.release)
else:
raise fuse.FuseOSError(errno.EACCES)
elif op.view == "Collages" and np.view == "Collages":
if (
(op.collage and np.collage)
and op.collage != np.collage
and (not op.release and not np.release)
):
rename_collage(self.config, op.collage, np.collage)
else:
raise fuse.FuseOSError(errno.EACCES)
Expand Down Expand Up @@ -397,7 +417,10 @@ def parse_virtual_path(path: str) -> ParsedPath:
return ParsedPath(view="Collages", collage=parts[1], release=rm_position(parts[2]))
if len(parts) == 4:
return ParsedPath(
view="Collages", collage=parts[1], release=rm_position(parts[2]), file=parts[3]
view="Collages",
collage=parts[1],
release=rm_position(parts[2]),
file=parts[3],
)
raise fuse.FuseOSError(errno.ENOENT)

Expand Down
14 changes: 14 additions & 0 deletions rose/virtualfs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ def test_virtual_filesystem_collage_actions(config: Config) -> None:
assert not (src / "!collages" / "New Jeans.toml").exists()


def test_virtual_filesystem_toggle_new(config: Config, source_dir: Path) -> None:
dirname = "NewJeans - 1990. I Love NewJeans [K-Pop;R&B] {A Cool Label}"
root = config.fuse_mount_dir
with startfs(config):
(root / "Releases" / dirname).rename(root / "Releases" / f"[NEW] {dirname}")
assert (root / "Releases" / f"[NEW] {dirname}").is_dir()
assert not (root / "Releases" / dirname).exists()
(root / "Releases" / f"[NEW] {dirname}").rename(root / "Releases" / dirname)
assert (root / "Releases" / dirname).is_dir()
assert not (root / "Releases" / f"[NEW] {dirname}").exists()
with pytest.raises(OSError): # noqa: PT011
(root / "Releases" / dirname).rename(root / "Releases" / "lalala")


@pytest.mark.usefixtures("seeded_cache")
def test_virtual_filesystem_hide_values(config: Config) -> None:
new_config = Config(
Expand Down
2 changes: 1 addition & 1 deletion testdata/cache/Test Release 2/.rose.ilovecarly.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
new = true
new = false
2 changes: 1 addition & 1 deletion testdata/cache/Test Release 3/.rose.ilovenewjeans.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
new = true
new = false

0 comments on commit e10fb4f

Please sign in to comment.