feat(lib): add editing functionality, add logging, add categories class
This commit is contained in:
@@ -1,11 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from .components import AudioComponent, AudioUnitType, Component
|
from .components import AudioComponent, AudioUnitType, Component
|
||||||
from .exceptions import MusicAppsLoadError, PluginLoadError, TagsetLoadError
|
from .exceptions import MusicAppsLoadError, PluginLoadError, TagsetLoadError
|
||||||
from .logic import Logic, Plugins, SearchResult
|
from .logic import Logic, Plugins, SearchResult
|
||||||
from .tags import MusicApps, Properties, Tagpool, Tagset
|
from .tags import Category, MusicApps, Properties, Tagpool, Tagset
|
||||||
|
|
||||||
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AudioComponent",
|
"AudioComponent",
|
||||||
"AudioUnitType",
|
"AudioUnitType",
|
||||||
|
"Category",
|
||||||
"Component",
|
"Component",
|
||||||
"Logic",
|
"Logic",
|
||||||
"MusicApps",
|
"MusicApps",
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
from dataclasses import dataclass
|
import logging
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .. import defaults
|
from .. import defaults
|
||||||
from ..exceptions import CannotParseComponentError
|
from ..exceptions import CannotParseComponentError
|
||||||
from ..tags import Tagset
|
from ..tags import Category, MusicApps, Tagset
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AudioUnitType(Enum):
|
class AudioUnitType(Enum):
|
||||||
@@ -62,12 +65,19 @@ class AudioComponent:
|
|||||||
version: int
|
version: int
|
||||||
tags_id: str
|
tags_id: str
|
||||||
tagset: Tagset
|
tagset: Tagset
|
||||||
|
categories: list[Category] = field(default_factory=list)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, data: dict, *, lazy: bool = False, tags_path: Path = defaults.tags_path
|
self,
|
||||||
|
data: dict,
|
||||||
|
*,
|
||||||
|
lazy: bool = False,
|
||||||
|
tags_path: Path = defaults.tags_path,
|
||||||
|
musicapps: MusicApps = None,
|
||||||
):
|
):
|
||||||
self.tags_path = tags_path
|
self.tags_path = tags_path
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
|
self.musicapps = musicapps or MusicApps(tags_path=self.tags_path, lazy=lazy)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.full_name = data.get("name")
|
self.full_name = data.get("name")
|
||||||
@@ -85,17 +95,34 @@ class AudioComponent:
|
|||||||
f"{self.subtype_code.encode('ascii').hex()}-"
|
f"{self.subtype_code.encode('ascii').hex()}-"
|
||||||
f"{self.manufacturer_code.encode('ascii').hex()}"
|
f"{self.manufacturer_code.encode('ascii').hex()}"
|
||||||
)
|
)
|
||||||
|
logger.debug(f"Created AudioComponent {self.full_name} from data")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CannotParseComponentError(f"An error occurred while parsing: {e}")
|
raise CannotParseComponentError(
|
||||||
|
f"An error occurred while parsing: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self) -> "AudioComponent":
|
def load(self) -> "AudioComponent":
|
||||||
|
logger.debug(f"Loading AudioComponent {self.full_name}")
|
||||||
self.tagset = Tagset(self.tags_path / self.tags_id, lazy=self.lazy)
|
self.tagset = Tagset(self.tags_path / self.tags_id, lazy=self.lazy)
|
||||||
|
logger.debug(f"Loaded Tagset for {self.full_name}")
|
||||||
|
self.categories = []
|
||||||
|
for name in self.tagset.tags.keys():
|
||||||
|
try:
|
||||||
|
logger.debug(f"Loading category {name} for {self.full_name}")
|
||||||
|
self.categories.append(
|
||||||
|
Category(name, musicapps=self.musicapps, lazy=self.lazy)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to load category {name} for {self.full_name}: {e}"
|
||||||
|
)
|
||||||
|
logger.debug(f"Loaded {len(self.categories)} categories for {self.full_name}")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other) -> bool:
|
||||||
if not isinstance(other, AudioComponent):
|
if not isinstance(other, AudioComponent):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return self.tags_id == other.tags_id
|
return self.tags_id == other.tags_id
|
||||||
@@ -103,5 +130,42 @@ class AudioComponent:
|
|||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.tags_id)
|
return hash(self.tags_id)
|
||||||
|
|
||||||
|
def set_nickname(self, nickname: str) -> "AudioComponent":
|
||||||
|
self.tagset.set_nickname(nickname)
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_shortname(self, shortname: str) -> "AudioComponent":
|
||||||
|
self.tagset.set_shortname(shortname)
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_categories(self, categories: list[Category]) -> "AudioComponent":
|
||||||
|
self.tagset.set_tags({category.name: "user" for category in categories})
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_to_category(self, category: Category) -> "AudioComponent":
|
||||||
|
self.tagset.add_tag(category.name, "user")
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def remove_from_category(self, category: Category) -> "AudioComponent":
|
||||||
|
self.tagset.remove_tag(category.name)
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_to_category(self, category: Category) -> "AudioComponent":
|
||||||
|
self.tagset.move_to_tag(category.name, "user")
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_to_parents(self) -> "AudioComponent":
|
||||||
|
for category in self.categories:
|
||||||
|
self.tagset.add_tag(category.parent.name, "user")
|
||||||
|
self.tagset.remove_tag(category.name)
|
||||||
|
self.load()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["AudioComponent", "AudioUnitType"]
|
__all__ = ["AudioComponent", "AudioUnitType"]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import plistlib
|
import plistlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -9,8 +10,11 @@ from ..exceptions import (
|
|||||||
NonexistentPlistError,
|
NonexistentPlistError,
|
||||||
OldComponentFormatError,
|
OldComponentFormatError,
|
||||||
)
|
)
|
||||||
|
from ..tags import MusicApps
|
||||||
from .audiocomponent import AudioComponent
|
from .audiocomponent import AudioComponent
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Component:
|
class Component:
|
||||||
@@ -21,16 +25,25 @@ class Component:
|
|||||||
audio_components: list[AudioComponent]
|
audio_components: list[AudioComponent]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, path: Path, *, lazy: bool = False, tags_path: Path = defaults.tags_path
|
self,
|
||||||
|
path: Path,
|
||||||
|
*,
|
||||||
|
lazy: bool = False,
|
||||||
|
tags_path: Path = defaults.tags_path,
|
||||||
|
musicapps: MusicApps = None,
|
||||||
):
|
):
|
||||||
self.path = path if path.suffix == ".component" else Path(f"{path}.component")
|
self.path = path if path.suffix == ".component" else Path(f"{path}.component")
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
self.tags_path = tags_path
|
self.tags_path = tags_path
|
||||||
|
self.musicapps = musicapps or MusicApps(tags_path=self.tags_path, lazy=lazy)
|
||||||
|
logger.debug(f"Created Component from {self.path}")
|
||||||
|
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def _parse_plist(self):
|
def _parse_plist(self):
|
||||||
info_plist_path = self.path / "Contents" / "Info.plist"
|
info_plist_path = self.path / "Contents" / "Info.plist"
|
||||||
|
logger.debug(f"Parsing Info.plist at {info_plist_path}")
|
||||||
if not info_plist_path.exists():
|
if not info_plist_path.exists():
|
||||||
raise NonexistentPlistError(f"Info.plist not found at {info_plist_path}")
|
raise NonexistentPlistError(f"Info.plist not found at {info_plist_path}")
|
||||||
|
|
||||||
@@ -39,25 +52,36 @@ class Component:
|
|||||||
plist_data = plistlib.load(fp)
|
plist_data = plistlib.load(fp)
|
||||||
return plist_data
|
return plist_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CannotParsePlistError(f"An error occurred: {e}")
|
raise CannotParsePlistError(f"An error occurred: {e}") from e
|
||||||
|
|
||||||
def load(self) -> "Component":
|
def load(self) -> "Component":
|
||||||
plist_data = self._parse_plist()
|
plist_data = self._parse_plist()
|
||||||
|
logger.debug(f"Loaded Info.plist for {self.path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.name = self.path.name.removesuffix(".component")
|
self.name = self.path.name.removesuffix(".component")
|
||||||
self.bundle_id = plist_data["CFBundleIdentifier"]
|
self.bundle_id = plist_data.get("CFBundleIdentifier")
|
||||||
self.version = plist_data["CFBundleVersion"]
|
self.version = plist_data.get("CFBundleVersion")
|
||||||
self.short_version = plist_data["CFBundleShortVersionString"]
|
self.short_version = plist_data.get("CFBundleShortVersionString")
|
||||||
|
logger.debug(f"Loaded component info for {self.bundle_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CannotParsePlistError(
|
raise CannotParsePlistError(
|
||||||
f"An error occurred while extracting: {e}"
|
f"An error occurred while extracting: {e}"
|
||||||
) from e
|
) from e
|
||||||
try:
|
try:
|
||||||
|
logger.debug(f"Loading components for {self.bundle_id}")
|
||||||
self.audio_components = [
|
self.audio_components = [
|
||||||
AudioComponent(name, lazy=self.lazy, tags_path=self.tags_path)
|
AudioComponent(
|
||||||
|
name,
|
||||||
|
lazy=self.lazy,
|
||||||
|
tags_path=self.tags_path,
|
||||||
|
musicapps=self.musicapps,
|
||||||
|
)
|
||||||
for name in plist_data["AudioComponents"]
|
for name in plist_data["AudioComponents"]
|
||||||
]
|
]
|
||||||
|
logger.debug(
|
||||||
|
f"Loaded {len(self.audio_components)} components for {self.bundle_id}"
|
||||||
|
)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise OldComponentFormatError(
|
raise OldComponentFormatError(
|
||||||
"This component is in an old format and cannot be loaded"
|
"This component is in an old format and cannot be loaded"
|
||||||
@@ -67,6 +91,7 @@ class Component:
|
|||||||
"An error occurred while loading components"
|
"An error occurred while loading components"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
logger.debug(f"Loaded {self.name} from {self.path}")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
|
|||||||
@@ -30,5 +30,21 @@ class CannotParseTagsetError(TagsetLoadError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TagsetWriteError(TagsetLoadError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MusicAppsLoadError(Exception):
|
class MusicAppsLoadError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MusicAppsWriteError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryValidationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryExistsError(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .. import defaults
|
from .. import defaults
|
||||||
from ..components import Component
|
from ..components import AudioComponent, Component
|
||||||
from ..tags import MusicApps
|
from ..tags import Category, MusicApps
|
||||||
from .plugins import Plugins
|
from .plugins import Plugins
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Logic:
|
class Logic:
|
||||||
musicapps: MusicApps
|
musicapps: MusicApps
|
||||||
plugins: Plugins
|
plugins: Plugins
|
||||||
components: set[Component]
|
components: set[Component]
|
||||||
|
categories: dict[str, Category]
|
||||||
components_path: Path = defaults.components_path
|
components_path: Path = defaults.components_path
|
||||||
tags_path: Path = defaults.tags_path
|
tags_path: Path = defaults.tags_path
|
||||||
|
|
||||||
@@ -33,22 +37,92 @@ class Logic:
|
|||||||
self.musicapps = MusicApps(tags_path=self.tags_path, lazy=lazy)
|
self.musicapps = MusicApps(tags_path=self.tags_path, lazy=lazy)
|
||||||
self.plugins = Plugins()
|
self.plugins = Plugins()
|
||||||
self.components = set()
|
self.components = set()
|
||||||
|
self.categories = {}
|
||||||
|
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
|
|
||||||
|
logger.debug("Created Logic instance")
|
||||||
|
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self.discover_plugins()
|
self.discover_plugins()
|
||||||
|
self.discover_categories()
|
||||||
|
|
||||||
def discover_plugins(self):
|
def discover_plugins(self) -> "Logic":
|
||||||
for component_path in self.components_path.glob("*.component"):
|
for component_path in self.components_path.glob("*.component"):
|
||||||
try:
|
try:
|
||||||
component = Component(component_path, lazy=self.lazy)
|
logger.debug(f"Loading component {component_path}")
|
||||||
|
component = Component(
|
||||||
|
component_path, lazy=self.lazy, musicapps=self.musicapps
|
||||||
|
)
|
||||||
self.components.add(component)
|
self.components.add(component)
|
||||||
|
logger.debug(f"Loading plugins for {component.name}")
|
||||||
for plugin in component.audio_components:
|
for plugin in component.audio_components:
|
||||||
self.plugins.add(plugin, lazy=self.lazy)
|
self.plugins.add(plugin, lazy=self.lazy)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
assert e
|
logger.warning(f"Failed to load component {component_path}: {e}")
|
||||||
continue
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def discover_categories(self) -> "Logic":
|
||||||
|
for category in self.musicapps.tagpool.categories.keys():
|
||||||
|
logger.debug(f"Loading category {category}")
|
||||||
|
self.categories[category] = Category(
|
||||||
|
category, musicapps=self.musicapps, lazy=self.lazy
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def sync_category_plugin_amount(self, category: Category | str) -> "Logic":
|
||||||
|
if isinstance(category, str):
|
||||||
|
category = self.categories[category]
|
||||||
|
logger.debug(f"Syncing plugin amount for {category.name}")
|
||||||
|
category.update_plugin_amount(
|
||||||
|
len(
|
||||||
|
self.plugins.get_by_category(
|
||||||
|
category.name if isinstance(category, Category) else category
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def sync_all_categories_plugin_amount(self) -> "Logic":
|
||||||
|
for category in self.categories.values():
|
||||||
|
self.sync_category_plugin_amount(category)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def search_categories(self, query: str) -> set[Category]:
|
||||||
|
return {
|
||||||
|
category
|
||||||
|
for category in self.categories.values()
|
||||||
|
if query in category.name.lower()
|
||||||
|
}
|
||||||
|
|
||||||
|
def introduce_category(self, name: str) -> Category:
|
||||||
|
return Category.introduce(name, musicapps=self.musicapps, lazy=self.lazy)
|
||||||
|
|
||||||
|
def add_plugins_to_category(
|
||||||
|
self, category: Category, plugins: set[AudioComponent]
|
||||||
|
) -> "Logic":
|
||||||
|
for plugin in plugins:
|
||||||
|
plugin.add_to_category(category)
|
||||||
|
self.sync_category_plugin_amount(category)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def move_plugins_to_category(
|
||||||
|
self, category: Category, plugins: set[AudioComponent]
|
||||||
|
) -> "Logic":
|
||||||
|
for plugin in plugins:
|
||||||
|
plugin.move_to_category(category)
|
||||||
|
self.sync_category_plugin_amount(category)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def remove_plugins_from_category(
|
||||||
|
self, category: Category, plugins: set[AudioComponent]
|
||||||
|
) -> "Logic":
|
||||||
|
for plugin in plugins:
|
||||||
|
plugin.remove_from_category(category)
|
||||||
|
self.sync_category_plugin_amount(category)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Logic"]
|
__all__ = ["Logic"]
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from ..components import AudioComponent, AudioUnitType
|
from ..components import AudioComponent, AudioUnitType
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SearchResult:
|
class SearchResult:
|
||||||
@@ -26,13 +29,16 @@ class Plugins:
|
|||||||
self._by_category: dict[str | None, set[AudioComponent]] = defaultdict(set)
|
self._by_category: dict[str | None, set[AudioComponent]] = defaultdict(set)
|
||||||
|
|
||||||
def add(self, plugin: AudioComponent, *, lazy: bool = False) -> "Plugins":
|
def add(self, plugin: AudioComponent, *, lazy: bool = False) -> "Plugins":
|
||||||
|
logger.debug(f"Adding plugin {plugin.full_name}")
|
||||||
self._plugins.add(plugin)
|
self._plugins.add(plugin)
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self._index_plugin(plugin)
|
self._index_plugin(plugin)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _index_plugin(self, plugin: AudioComponent):
|
def _index_plugin(self, plugin: AudioComponent):
|
||||||
|
logger.debug(f"Indexing plugin {plugin.full_name}")
|
||||||
if plugin.lazy:
|
if plugin.lazy:
|
||||||
|
logger.debug(f"{plugin.full_name} is lazy, loading first")
|
||||||
plugin.load()
|
plugin.load()
|
||||||
plugin.tagset.load()
|
plugin.tagset.load()
|
||||||
|
|
||||||
@@ -48,8 +54,10 @@ class Plugins:
|
|||||||
self._by_category[tag.lower()].add(plugin)
|
self._by_category[tag.lower()].add(plugin)
|
||||||
if not plugin.tagset.tags.keys():
|
if not plugin.tagset.tags.keys():
|
||||||
self._by_category[None].add(plugin)
|
self._by_category[None].add(plugin)
|
||||||
|
logger.debug(f"Indexed plugin {plugin.full_name}")
|
||||||
|
|
||||||
def reindex_all(self):
|
def reindex_all(self):
|
||||||
|
logger.debug("Reindexing all plugins")
|
||||||
self._by_full_name.clear()
|
self._by_full_name.clear()
|
||||||
self._by_manufacturer.clear()
|
self._by_manufacturer.clear()
|
||||||
self._by_name.clear()
|
self._by_name.clear()
|
||||||
@@ -59,9 +67,11 @@ class Plugins:
|
|||||||
self._by_subtype_code.clear()
|
self._by_subtype_code.clear()
|
||||||
self._by_tags_id.clear()
|
self._by_tags_id.clear()
|
||||||
self._by_category.clear()
|
self._by_category.clear()
|
||||||
|
logger.debug("Cleared all indexes")
|
||||||
|
|
||||||
for plugin in self._plugins:
|
for plugin in self._plugins:
|
||||||
self._index_plugin(plugin)
|
self._index_plugin(plugin)
|
||||||
|
logger.debug("Reindexed all plugins")
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
return self._plugins.copy()
|
return self._plugins.copy()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
from .category import Category
|
||||||
from .musicapps import MusicApps, Properties, Tagpool
|
from .musicapps import MusicApps, Properties, Tagpool
|
||||||
from .tagset import Tagset
|
from .tagset import Tagset
|
||||||
|
|
||||||
__all__ = ["MusicApps", "Properties", "Tagpool", "Tagset"]
|
__all__ = ["Category", "MusicApps", "Properties", "Tagpool", "Tagset"]
|
||||||
|
|||||||
159
src/logic_plugin_manager/tags/category.py
Normal file
159
src/logic_plugin_manager/tags/category.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import logging
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from ..exceptions import CategoryExistsError, CategoryValidationError
|
||||||
|
from .musicapps import MusicApps
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Category:
|
||||||
|
name: str
|
||||||
|
musicapps: MusicApps = field(repr=False)
|
||||||
|
is_root: bool
|
||||||
|
plugin_amount: int
|
||||||
|
lazy: bool
|
||||||
|
|
||||||
|
def __init__(self, name: str, *, musicapps: MusicApps = None, lazy: bool = False):
|
||||||
|
self.name = name
|
||||||
|
self.musicapps = musicapps or MusicApps(lazy=lazy)
|
||||||
|
self.is_root = False
|
||||||
|
self.plugin_amount = 0
|
||||||
|
self.lazy = lazy
|
||||||
|
|
||||||
|
if not lazy:
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
logger.debug(f"Validating category {self.name}")
|
||||||
|
if self.name not in self.musicapps.tagpool.categories.keys():
|
||||||
|
raise CategoryValidationError(f"Category {self.name} not found in tagpool")
|
||||||
|
self.plugin_amount = self.musicapps.tagpool.categories[self.name]
|
||||||
|
logger.debug(f"Loaded plugin amount for {self.name} - {self.plugin_amount}")
|
||||||
|
if self.name == "":
|
||||||
|
self.is_root = True
|
||||||
|
logger.debug("This is the root category")
|
||||||
|
return
|
||||||
|
if self.name not in self.musicapps.properties.sorting:
|
||||||
|
raise CategoryValidationError(f"Category {self.name} not found in sorting")
|
||||||
|
logger.debug(f"Valid category {self.name}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def introduce(cls, name: str, *, musicapps: MusicApps = None, lazy: bool = False):
|
||||||
|
logger.debug(f"Introducing category {name}")
|
||||||
|
if musicapps is None:
|
||||||
|
musicapps = MusicApps()
|
||||||
|
try:
|
||||||
|
cls(name, musicapps=musicapps, lazy=lazy)
|
||||||
|
raise CategoryExistsError(f"Category {name} already exists")
|
||||||
|
except CategoryValidationError:
|
||||||
|
logger.debug(f"Category {name} doesn't exist, proceeding")
|
||||||
|
pass
|
||||||
|
|
||||||
|
musicapps.introduce_category(name)
|
||||||
|
logger.debug(f"Introduced category {name}")
|
||||||
|
|
||||||
|
return cls(name, musicapps=musicapps)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self) -> "Category":
|
||||||
|
if self.is_root:
|
||||||
|
return self
|
||||||
|
return self.__class__(
|
||||||
|
":".join(self.name.split(":")[:-1]),
|
||||||
|
musicapps=self.musicapps,
|
||||||
|
lazy=self.lazy,
|
||||||
|
)
|
||||||
|
|
||||||
|
def child(self, name: str) -> "Category":
|
||||||
|
return self.__class__(
|
||||||
|
f"{self.name}:{name}", musicapps=self.musicapps, lazy=self.lazy
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.tagpool.remove_category(self.name)
|
||||||
|
self.musicapps.properties.remove_category(self.name)
|
||||||
|
|
||||||
|
def update_plugin_amount(self, amount: int):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.tagpool.write_category(self.name, amount)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_up(self, steps: int = 1):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_up(self.name, steps)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_down(self, steps: int = 1):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_down(self.name, steps)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to_top(self):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_to_top(self.name)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to_bottom(self):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_to_bottom(self.name)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_before(self, other: "Category"):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_before(self.name, other.name)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_after(self, other: "Category"):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_after(self.name, other.name)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to(self, index: int):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.move_to_index(self.name, index)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def swap(self, other: "Category"):
|
||||||
|
if self.is_root:
|
||||||
|
return
|
||||||
|
self.musicapps.properties.swap(self.name, other.name)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def index(self):
|
||||||
|
return self.musicapps.properties.get_index(self.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def neighbors(self):
|
||||||
|
if self.is_root:
|
||||||
|
return None, None
|
||||||
|
neighbors = self.musicapps.properties.get_neighbors(self.name)
|
||||||
|
if neighbors is None or len(neighbors) != 2:
|
||||||
|
return None, None
|
||||||
|
return (
|
||||||
|
self.__class__(name=neighbors[0], musicapps=self.musicapps),
|
||||||
|
self.__class__(name=neighbors[1], musicapps=self.musicapps),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_first(self):
|
||||||
|
return self.musicapps.properties.is_first(self.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_last(self):
|
||||||
|
return self.musicapps.properties.is_last(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Category"]
|
||||||
@@ -1,20 +1,35 @@
|
|||||||
|
import logging
|
||||||
import plistlib
|
import plistlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .. import defaults
|
from .. import defaults
|
||||||
from ..exceptions import MusicAppsLoadError
|
from ..exceptions import MusicAppsLoadError, MusicAppsWriteError
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _parse_plist(path: Path):
|
def _parse_plist(path: Path):
|
||||||
|
logger.debug(f"Parsing plist at {path}")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
raise MusicAppsLoadError(f"File not found at {path}")
|
raise MusicAppsLoadError(f"File not found at {path}")
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as fp:
|
with open(path, "rb") as fp:
|
||||||
plist_data = plistlib.load(fp)
|
plist_data = plistlib.load(fp)
|
||||||
|
logger.debug(f"Parsed plist for {path}")
|
||||||
return plist_data
|
return plist_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise MusicAppsLoadError(f"An error occurred: {e}")
|
raise MusicAppsLoadError(f"An error occurred: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def _save_plist(path: Path, data: dict):
|
||||||
|
logger.debug(f"Saving plist to {path}")
|
||||||
|
try:
|
||||||
|
with open(path, "wb") as fp:
|
||||||
|
plistlib.dump(data, fp)
|
||||||
|
logger.debug(f"Saved plist to {path}")
|
||||||
|
except Exception as e:
|
||||||
|
raise MusicAppsWriteError(f"An error occurred: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -25,32 +40,263 @@ class Tagpool:
|
|||||||
self.path = tags_path / "MusicApps.tagpool"
|
self.path = tags_path / "MusicApps.tagpool"
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
|
|
||||||
|
logger.debug(f"Created Tagpool from {self.path}")
|
||||||
|
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self) -> "Tagpool":
|
def load(self) -> "Tagpool":
|
||||||
|
logger.debug(f"Loading Tagpool data from {self.path}")
|
||||||
self.categories = _parse_plist(self.path)
|
self.categories = _parse_plist(self.path)
|
||||||
|
logger.debug(f"Loaded Tagpool data from {self.path}")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def write_category(self, name: str, plugin_count: int = 0):
|
||||||
|
self.load()
|
||||||
|
self.categories[name] = plugin_count
|
||||||
|
_save_plist(self.path, self.categories)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def introduce_category(self, name: str):
|
||||||
|
self.load()
|
||||||
|
if name in self.categories:
|
||||||
|
return
|
||||||
|
self.write_category(name)
|
||||||
|
|
||||||
|
def remove_category(self, name: str):
|
||||||
|
self.load()
|
||||||
|
self.categories.pop(name, None)
|
||||||
|
_save_plist(self.path, self.categories)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Properties:
|
class Properties:
|
||||||
sorting: list[str]
|
sorting: list[str]
|
||||||
user_sorted: bool
|
user_sorted: bool
|
||||||
|
__raw_data: dict[str, str | list[str] | bool] = field(repr=False)
|
||||||
|
|
||||||
def __init__(self, tags_path: Path, *, lazy: bool = False):
|
def __init__(self, tags_path: Path, *, lazy: bool = False):
|
||||||
self.path = tags_path / "MusicApps.properties"
|
self.path = tags_path / "MusicApps.properties"
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
|
|
||||||
|
logger.debug(f"Created Properties from {self.path}")
|
||||||
|
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self) -> "Properties":
|
def load(self) -> "Properties":
|
||||||
properties_data = _parse_plist(self.path)
|
logger.debug(f"Loading Properties data from {self.path}")
|
||||||
self.sorting = properties_data.get("sorting", [])
|
self.__raw_data = _parse_plist(self.path)
|
||||||
self.user_sorted = bool(properties_data.get("user_sorted", False))
|
logger.debug(f"Loaded Properties data from {self.path}")
|
||||||
|
|
||||||
|
self.sorting = self.__raw_data.get("sorting", [])
|
||||||
|
self.user_sorted = bool(self.__raw_data.get("user_sorted", False))
|
||||||
|
logger.debug(f"Parsed Properties data from {self.path}")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def introduce_category(self, name: str):
|
||||||
|
self.load()
|
||||||
|
if name in self.sorting:
|
||||||
|
return
|
||||||
|
self.__raw_data["sorting"].append(name)
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def enable_user_sorting(self):
|
||||||
|
self.load()
|
||||||
|
self.__raw_data["user_sorted"] = "property"
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def enable_alphabetical_sorting(self):
|
||||||
|
self.load()
|
||||||
|
del self.__raw_data["user_sorted"]
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def remove_category(self, name: str):
|
||||||
|
self.load()
|
||||||
|
self.__raw_data["sorting"].remove(name)
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_up(self, category: str, steps: int = 1):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found in sorting")
|
||||||
|
|
||||||
|
current_idx = sorting.index(category)
|
||||||
|
new_idx = max(0, current_idx - steps)
|
||||||
|
|
||||||
|
sorting.pop(current_idx)
|
||||||
|
sorting.insert(new_idx, category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_down(self, category: str, steps: int = 1):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found in sorting")
|
||||||
|
|
||||||
|
current_idx = sorting.index(category)
|
||||||
|
new_idx = min(len(sorting) - 1, current_idx + steps)
|
||||||
|
|
||||||
|
sorting.pop(current_idx)
|
||||||
|
sorting.insert(new_idx, category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to_top(self, category: str):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found in sorting")
|
||||||
|
|
||||||
|
sorting.remove(category)
|
||||||
|
sorting.insert(0, category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to_bottom(self, category: str):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found in sorting")
|
||||||
|
|
||||||
|
sorting.remove(category)
|
||||||
|
sorting.append(category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_before(self, category: str, target: str):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found")
|
||||||
|
if target not in sorting:
|
||||||
|
raise ValueError(f"Target category '{target}' not found")
|
||||||
|
|
||||||
|
sorting.remove(category)
|
||||||
|
target_idx = sorting.index(target)
|
||||||
|
sorting.insert(target_idx, category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_after(self, category: str, target: str):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found")
|
||||||
|
if target not in sorting:
|
||||||
|
raise ValueError(f"Target category '{target}' not found")
|
||||||
|
|
||||||
|
sorting.remove(category)
|
||||||
|
target_idx = sorting.index(target)
|
||||||
|
sorting.insert(target_idx + 1, category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to_index(self, category: str, index: int):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category not in sorting:
|
||||||
|
raise ValueError(f"Category '{category}' not found")
|
||||||
|
|
||||||
|
if index < 0:
|
||||||
|
index = len(sorting) + index
|
||||||
|
|
||||||
|
index = max(0, min(len(sorting) - 1, index))
|
||||||
|
|
||||||
|
sorting.remove(category)
|
||||||
|
sorting.insert(index, category)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def swap(self, category1: str, category2: str):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if category1 not in sorting or category2 not in sorting:
|
||||||
|
raise ValueError("Both categories must exist")
|
||||||
|
|
||||||
|
idx1 = sorting.index(category1)
|
||||||
|
idx2 = sorting.index(category2)
|
||||||
|
|
||||||
|
sorting[idx1], sorting[idx2] = sorting[idx2], sorting[idx1]
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def set_order(self, categories: list[str]):
|
||||||
|
self.load()
|
||||||
|
current = set(self.sorting)
|
||||||
|
new = set(categories)
|
||||||
|
|
||||||
|
if new != current:
|
||||||
|
missing = current - new
|
||||||
|
extra = new - current
|
||||||
|
raise ValueError(f"Category mismatch. Missing: {missing}, Extra: {extra}")
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = categories
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def reorder(self, key_func=None, reverse: bool = False):
|
||||||
|
self.load()
|
||||||
|
sorting = self.sorting.copy()
|
||||||
|
|
||||||
|
if key_func is None:
|
||||||
|
sorting.sort(reverse=reverse)
|
||||||
|
else:
|
||||||
|
sorting.sort(key=key_func, reverse=reverse)
|
||||||
|
|
||||||
|
self.__raw_data["sorting"] = sorting
|
||||||
|
_save_plist(self.path, self.__raw_data)
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def get_index(self, category: str) -> int:
|
||||||
|
return self.sorting.index(category)
|
||||||
|
|
||||||
|
def get_at_index(self, index: int) -> str:
|
||||||
|
return self.sorting[index]
|
||||||
|
|
||||||
|
def get_neighbors(self, category: str) -> tuple[str | None, str | None]:
|
||||||
|
idx = self.get_index(category)
|
||||||
|
prev_cat = self.sorting[idx - 1] if idx > 0 else None
|
||||||
|
next_cat = self.sorting[idx + 1] if idx < len(self.sorting) - 1 else None
|
||||||
|
return prev_cat, next_cat
|
||||||
|
|
||||||
|
def is_first(self, category: str) -> bool:
|
||||||
|
return self.get_index(category) == 0
|
||||||
|
|
||||||
|
def is_last(self, category: str) -> bool:
|
||||||
|
return self.get_index(category) == len(self.sorting) - 1
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MusicApps:
|
class MusicApps:
|
||||||
@@ -61,13 +307,24 @@ class MusicApps:
|
|||||||
self.path = tags_path
|
self.path = tags_path
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
|
|
||||||
|
logger.debug(f"Created MusicApps from {self.path}")
|
||||||
|
|
||||||
if not lazy:
|
if not lazy:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self) -> "MusicApps":
|
def load(self) -> "MusicApps":
|
||||||
self.tagpool = Tagpool(self.path, lazy=self.lazy)
|
self.tagpool = Tagpool(self.path, lazy=self.lazy)
|
||||||
self.properties = Properties(self.path, lazy=self.lazy)
|
self.properties = Properties(self.path, lazy=self.lazy)
|
||||||
|
logger.debug(f"Loaded MusicApps from {self.path}")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def introduce_category(self, name: str):
|
||||||
|
self.tagpool.introduce_category(name)
|
||||||
|
self.properties.introduce_category(name)
|
||||||
|
|
||||||
|
def remove_category(self, name: str):
|
||||||
|
self.tagpool.remove_category(name)
|
||||||
|
self.properties.remove_category(name)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["MusicApps", "Properties", "Tagpool"]
|
__all__ = ["MusicApps", "Properties", "Tagpool"]
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import plistlib
|
import plistlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..exceptions import CannotParseTagsetError, NonexistentTagsetError
|
from ..exceptions import (
|
||||||
|
CannotParseTagsetError,
|
||||||
|
NonexistentTagsetError,
|
||||||
|
TagsetWriteError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -11,6 +15,7 @@ class Tagset:
|
|||||||
nickname: str
|
nickname: str
|
||||||
shortname: str
|
shortname: str
|
||||||
tags: dict[str, str]
|
tags: dict[str, str]
|
||||||
|
__raw_data: dict[str, str | dict[str, str]] = field(repr=False)
|
||||||
|
|
||||||
def __init__(self, path: Path, *, lazy: bool = False):
|
def __init__(self, path: Path, *, lazy: bool = False):
|
||||||
self.path = path.with_suffix(".tagset")
|
self.path = path.with_suffix(".tagset")
|
||||||
@@ -27,17 +32,61 @@ class Tagset:
|
|||||||
plist_data = plistlib.load(fp)
|
plist_data = plistlib.load(fp)
|
||||||
return plist_data
|
return plist_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CannotParseTagsetError(f"An error occurred: {e}")
|
raise CannotParseTagsetError(f"An error occurred: {e}") from e
|
||||||
|
|
||||||
|
def _write_plist(self):
|
||||||
|
try:
|
||||||
|
with open(self.path, "wb") as fp:
|
||||||
|
plistlib.dump(self.__raw_data, fp)
|
||||||
|
except Exception as e:
|
||||||
|
raise TagsetWriteError(f"An error occurred: {e}") from e
|
||||||
|
|
||||||
def load(self) -> "Tagset":
|
def load(self) -> "Tagset":
|
||||||
tagset_data = self._parse_plist()
|
self.__raw_data = self._parse_plist()
|
||||||
|
|
||||||
self.tags_id = self.path.name.removesuffix(".tagset")
|
self.tags_id = self.path.name.removesuffix(".tagset")
|
||||||
self.nickname = tagset_data.get("nickname")
|
self.nickname = self.__raw_data.get("nickname")
|
||||||
self.shortname = tagset_data.get("shortname")
|
self.shortname = self.__raw_data.get("shortname")
|
||||||
self.tags = tagset_data.get("tags") or {}
|
self.tags = self.__raw_data.get("tags") or {}
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def set_nickname(self, nickname: str):
|
||||||
|
self.load()
|
||||||
|
self.__raw_data["nickname"] = nickname
|
||||||
|
self._write_plist()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def set_shortname(self, shortname: str):
|
||||||
|
self.load()
|
||||||
|
self.__raw_data["shortname"] = shortname
|
||||||
|
self._write_plist()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def set_tags(self, tags: dict[str, str]):
|
||||||
|
self.load()
|
||||||
|
self.__raw_data["tags"] = tags
|
||||||
|
self._write_plist()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def add_tag(self, tag: str, value: str):
|
||||||
|
self.load()
|
||||||
|
self.tags[tag] = value
|
||||||
|
self._write_plist()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def remove_tag(self, tag: str):
|
||||||
|
self.load()
|
||||||
|
del self.tags[tag]
|
||||||
|
self._write_plist()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def move_to_tag(self, tag: str, value: str):
|
||||||
|
self.load()
|
||||||
|
self.tags.clear()
|
||||||
|
self.tags[tag] = value
|
||||||
|
self._write_plist()
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Tagset"]
|
__all__ = ["Tagset"]
|
||||||
|
|||||||
Reference in New Issue
Block a user