Skip to content

Commit

Permalink
templating
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed May 6, 2024
1 parent 3f88f5f commit a58fe4a
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 5 deletions.
14 changes: 13 additions & 1 deletion rose/releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
)
from rose.common import Artist, ArtistMapping, RoseError, RoseExpectedError
from rose.config import Config
from rose.rule_parser import MetadataAction, MetadataMatcher
from rose.rule_parser import ALL_TAGS, MetadataAction, MetadataMatcher
from rose.rules import (
execute_metadata_actions,
fast_search_for_matching_releases,
Expand Down Expand Up @@ -450,6 +450,18 @@ def edit_release(


def find_releases_matching_rule(c: Config, matcher: MetadataMatcher) -> list[Release]:
# Implement optimizations for common lookups. Only applies to strict lookups.
# TODO: Morning
if matcher.pattern.pattern.startswith("^") and matcher.pattern.pattern.endswith("$"):
if matcher.tags == ALL_TAGS["artist"]:
pass
if matcher.tags == ["genre"]:
pass
if matcher.tags == ["label"]:
pass
if matcher.tags == ["descriptor"]:
pass

release_ids = [x.id for x in fast_search_for_matching_releases(c, matcher)]
releases = list_releases(c, release_ids)
return filter_release_false_positives_using_read_cache(matcher, releases)
Expand Down
27 changes: 25 additions & 2 deletions rose/rule_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from __future__ import annotations

from collections.abc import Sequence
import io
import logging
import re
Expand Down Expand Up @@ -76,9 +77,11 @@ def __str__(self) -> str:
"label",
]

ExpandableTag = Tag | Literal["artist", "trackartist", "releaseartist"]

# Map of a tag to its "resolved" tags. Most tags simply resolve to themselves; however, we let
# certain tags be aliases for multiple other tags, purely for convenience.
ALL_TAGS: dict[str, list[Tag]] = {
ALL_TAGS: dict[ExpandableTag, list[Tag]] = {
"tracktitle": ["tracktitle"],
"trackartist": [
"trackartist[main]",
Expand Down Expand Up @@ -275,7 +278,7 @@ def __str__(self) -> str:
return r


@dataclass
@dataclass()
class MetadataMatcher:
# Tags to test against the pattern. If any tags match the pattern, the action will be ran
# against the track.
Expand All @@ -289,6 +292,13 @@ def __str__(self) -> str:
r += str(self.pattern)
return r

def __init__(self, tags: Sequence[ExpandableTag], pattern: MatcherPattern) -> None:
_tags: set[Tag] = set()
for t in tags:
_tags.update(ALL_TAGS[t])
self.tags = list(_tags)
self.pattern = pattern

@classmethod
def parse(cls, raw: str, *, rule_name: str = "matcher") -> MetadataMatcher:
idx = 0
Expand Down Expand Up @@ -375,6 +385,19 @@ class MetadataAction:
# upon.
pattern: MatcherPattern | None = None

def __init__(
self,
behavior: ReplaceAction | SedAction | SplitAction | AddAction | DeleteAction,
tags: Sequence[ExpandableTag],
pattern: MatcherPattern | None = None,
) -> None:
self.behavior = behavior
_tags: set[Tag] = set()
for t in tags:
_tags.update(ALL_TAGS[t])
self.tags = list(_tags)
self.pattern = pattern

def __str__(self) -> str:
r = ""
r += stringify_tags(self.tags)
Expand Down
14 changes: 13 additions & 1 deletion rose/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)
from rose.common import RoseExpectedError
from rose.config import Config
from rose.rule_parser import MetadataAction, MetadataMatcher
from rose.rule_parser import ALL_TAGS, MetadataAction, MetadataMatcher
from rose.rules import (
execute_metadata_actions,
fast_search_for_matching_tracks,
Expand All @@ -29,6 +29,18 @@ class TrackDoesNotExistError(RoseExpectedError):


def find_tracks_matching_rule(c: Config, matcher: MetadataMatcher) -> list[Track]:
# Implement optimizations for common lookups. Only applies to strict lookups.
# TODO: Morning
if matcher.pattern.pattern.startswith("^") and matcher.pattern.pattern.endswith("$"):
if matcher.tags == ALL_TAGS["artist"]:
pass
if matcher.tags == ["genre"]:
pass
if matcher.tags == ["label"]:
pass
if matcher.tags == ["descriptor"]:
pass

track_ids = [t.id for t in fast_search_for_matching_tracks(c, matcher)]
tracks = list_tracks(c, track_ids)
return filter_track_false_positives_using_read_cache(matcher, tracks)
Expand Down
74 changes: 73 additions & 1 deletion rose_vfs/virtualfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@
)
from rose.cache import (
list_releases_delete_this,
release_within_collage,
)
from rose.rule_parser import MatcherPattern, MetadataMatcher
from rose.tracks import find_tracks_matching_rule

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -160,6 +163,9 @@ def get(self, key: K, default: T) -> V | T:
return default


ALL_TRACKS = "!All Tracks"


@dataclass(frozen=True, slots=True)
class VirtualPath:
view: (
Expand All @@ -184,6 +190,9 @@ class VirtualPath:
label: str | None = None
collage: str | None = None
playlist: str | None = None
# Release may be set to `ALL_TRACKS`, in which case it is never attempted to be resolved to a
# release. Instead, it is treated as a special directory. There may be name conflicts; I don't
# care.
release: str | None = None
file: str | None = None

Expand Down Expand Up @@ -421,7 +430,7 @@ def list_release_paths(
directory names.
"""
# For collision number generation.
seen: set[str] = set()
seen: set[str] = {ALL_TRACKS}
prefix_pad_size = len(str(len(releases)))
for idx, release in enumerate(releases):
# Determine the proper template.
Expand Down Expand Up @@ -954,6 +963,14 @@ def getattr(self, p: VirtualPath) -> dict[str, Any]:
if p.collage:
if not get_collage(self.config, p.collage):
raise llfuse.FUSEError(errno.ENOENT)
if p.release == ALL_TRACKS:
if not p.file:
return self.stat("dir")
if (track := get_track(self.config, self._get_track_id(p))) and (
release_within_collage(self.config, track.release.id, p.collage)
):
return self.stat("file", track.source_path)
raise llfuse.FUSEError(errno.ENOENT)
if p.release:
return self._getattr_release(p)
return self.stat("dir")
Expand All @@ -963,6 +980,14 @@ def getattr(self, p: VirtualPath) -> dict[str, Any]:
la = self.sanitizer.unsanitize(p.label, p.label_parent)
if not label_exists(self.config, la) or not self.can_show.label(la):
raise llfuse.FUSEError(errno.ENOENT)
if p.release == ALL_TRACKS:
if not p.file:
return self.stat("dir")
if (track := get_track(self.config, self._get_track_id(p))) and (
p.label in track.release.labels
):
return self.stat("file", track.source_path)
raise llfuse.FUSEError(errno.ENOENT)
if p.release:
return self._getattr_release(p)
return self.stat("dir")
Expand All @@ -972,6 +997,14 @@ def getattr(self, p: VirtualPath) -> dict[str, Any]:
d = self.sanitizer.unsanitize(p.descriptor, p.descriptor_parent)
if not descriptor_exists(self.config, d) or not self.can_show.descriptor(d):
raise llfuse.FUSEError(errno.ENOENT)
if p.release == ALL_TRACKS:
if not p.file:
return self.stat("dir")
if (track := get_track(self.config, self._get_track_id(p))) and (
p.descriptor in track.release.descriptors
):
return self.stat("file", track.source_path)
raise llfuse.FUSEError(errno.ENOENT)
if p.release:
return self._getattr_release(p)
return self.stat("dir")
Expand All @@ -981,6 +1014,17 @@ def getattr(self, p: VirtualPath) -> dict[str, Any]:
g = self.sanitizer.unsanitize(p.genre, p.genre_parent)
if not genre_exists(self.config, g) or not self.can_show.genre(g):
raise llfuse.FUSEError(errno.ENOENT)
if p.release == ALL_TRACKS:
if not p.file:
return self.stat("dir")
if (track := get_track(self.config, self._get_track_id(p))) and (
p.genre in track.release.genres
or p.genre in track.release.parent_genres
or p.genre in track.release.secondary_genres
or p.genre in track.release.parent_secondary_genres
):
return self.stat("file", track.source_path)
raise llfuse.FUSEError(errno.ENOENT)
if p.release:
return self._getattr_release(p)
return self.stat("dir")
Expand All @@ -990,6 +1034,16 @@ def getattr(self, p: VirtualPath) -> dict[str, Any]:
a = self.sanitizer.unsanitize(p.artist, p.artist_parent)
if not artist_exists(self.config, a) or not self.can_show.artist(a):
raise llfuse.FUSEError(errno.ENOENT)
if p.release == ALL_TRACKS:
if not p.file:
return self.stat("dir")
if (track := get_track(self.config, self._get_track_id(p))) and any(
p.artist == a.name
for _, artists in track.release.releaseartists.items()
for a in artists
):
return self.stat("file", track.source_path)
raise llfuse.FUSEError(errno.ENOENT)
if p.release:
return self._getattr_release(p)
return self.stat("dir")
Expand Down Expand Up @@ -1034,6 +1088,22 @@ def readdir(self, p: VirtualPath) -> Iterator[tuple[str, dict[str, Any]]]:
]
return

if p.release == ALL_TRACKS:
if p.artist:
matcher = MetadataMatcher(tags=["artist"], pattern=MatcherPattern(f"^{p.artist}$"))
if p.genre:
matcher = MetadataMatcher(tags=["genre"], pattern=MatcherPattern(f"^{p.genre}$"))
if p.descriptor:
matcher = MetadataMatcher(
tags=["descriptor"], pattern=MatcherPattern(f"^{p.descriptor}$")
)
if p.label:
matcher = MetadataMatcher(tags=["label"], pattern=MatcherPattern(f"^{p.label}$"))
tracks = find_tracks_matching_rule(self.config, matcher)
for trk, vname in self.vnames.list_track_paths(p, tracks):
yield vname, self.stat("file", trk.source_path)
return

if p.release:
if (release_id := self.vnames.lookup_release(p)) and (
release := get_release(self.config, release_id)
Expand Down Expand Up @@ -1066,6 +1136,7 @@ def readdir(self, p: VirtualPath) -> Iterator[tuple[str, dict[str, Any]]]:
# fmt: on
for rls, vname in self.vnames.list_release_paths(p, releases):
yield vname, self.stat("dir", rls.source_path)
yield ALL_TRACKS, self.stat("dir")
return

if p.view == "Artists":
Expand Down Expand Up @@ -1106,6 +1177,7 @@ def readdir(self, p: VirtualPath) -> Iterator[tuple[str, dict[str, Any]]]:
releases = get_collage_releases(self.config, p.collage)
for rls, vname in self.vnames.list_release_paths(p, releases):
yield vname, self.stat("dir", rls.source_path)
yield ALL_TRACKS, self.stat("dir")
return

if p.view == "Collages":
Expand Down

0 comments on commit a58fe4a

Please sign in to comment.