emmental/emmental/db/libraries.py

150 lines
5.0 KiB
Python

# Copyright 2022 (c) Anna Schumaker
"""A custom Gio.ListModel for working with libraries."""
import pathlib
import sqlite3
from gi.repository import GObject
from .. import path
from . import idle
from . import playlist
from . import tagger
class Library(playlist.Playlist):
"""Our custom Library with path and enabled properties."""
libraryid = GObject.Property(type=int)
path = GObject.Property(type=GObject.TYPE_PYOBJECT)
enabled = GObject.Property(type=bool, default=True)
deleting = GObject.Property(type=bool, default=False)
queue = GObject.Property(type=idle.Queue)
readdir = GObject.Property(type=GObject.TYPE_PYOBJECT)
tagger = GObject.Property(type=GObject.TYPE_PYOBJECT)
online = GObject.Property(type=bool, default=False)
def __init__(self, **kwargs):
"""Initialize our Library object."""
super().__init__(queue=idle.Queue(), **kwargs)
self.scan()
def __queue_delete(self) -> bool:
self.table.delete(self)
return True
def __queue_tracks(self) -> bool:
if (files := self.readdir.poll_result()) is None:
self.__stop_thread("readdir")
self.queue.push(self.__stop_thread, "tagger")
return True
self.queue.push_many(self.__tag_track, [(f,) for f in files])
return False
def __tag_track(self, path: pathlib.Path) -> bool:
if self.tagger.ready.is_set():
(file, tags) = self.tagger.get_result(self.table.sql)
if file is None:
self.tagger.tag_file(path)
else:
return True
return False
def __scan_library(self) -> bool:
self.readdir = path.readdir_async(self.path)
if self.readdir is not None:
self.online = True
self.queue.push(self.__queue_tracks)
self.tagger = tagger.Thread()
return True
def __stop_thread(self, thread_name: str) -> bool:
if (thread := self.get_property(thread_name)) is not None:
thread.stop()
self.set_property(thread_name, None)
return True
def do_update(self, column: str) -> bool:
"""Update a Library playlist."""
match column:
case "readdir" | "tagger": pass
case "online": self.table.notify_online(self)
case _: return super().do_update(column)
return True
def delete(self) -> bool:
"""Delete this Library."""
if self.deleting is False:
self.stop()
self.deleting = True
self.queue.push(self.__queue_delete)
return True
return False
def scan(self) -> None:
"""Scan the Library."""
if not self.queue.running:
self.queue.push(self.__scan_library)
def stop(self) -> None:
"""Stop this Library's background work."""
self.__stop_thread("readdir")
self.__stop_thread("tagger")
self.queue.cancel()
@property
def primary_key(self) -> int:
"""Get this library's primary key."""
return self.libraryid
class Table(playlist.Table):
"""Our Library ListModel."""
def do_construct(self, **kwargs) -> Library:
"""Construct a new library."""
return Library(**kwargs)
def do_sql_delete(self, library: Library) -> sqlite3.Cursor:
"""Delete a library."""
return self.sql("DELETE FROM libraries WHERE libraryid=?",
library.libraryid)
def do_sql_insert(self, path: pathlib.Path) -> sqlite3.Cursor:
"""Create a new library."""
if cur := self.sql("INSERT INTO libraries (path) VALUES (?)", path):
return self.sql("SELECT * FROM libraries_view WHERE libraryid=?",
cur.lastrowid)
def do_sql_glob(self, glob: str) -> sqlite3.Cursor:
"""Search for libraries matching the search text."""
return self.sql("""SELECT libraryid FROM libraries_view
WHERE name GLOB ?""", glob)
def do_sql_select_all(self) -> sqlite3.Cursor:
"""Load libraries from the database."""
return self.sql("SELECT * FROM libraries_view")
def do_sql_select_one(self, path: pathlib.Path) -> sqlite3.Cursor:
"""Look up a library by path."""
return self.sql("SELECT libraryid FROM libraries WHERE path=?", path)
def do_sql_update(self, library: Library, column: str, newval) -> bool:
"""Update a Library playlist."""
return self.sql(f"UPDATE libraries SET {column}=? WHERE rowid=?",
newval, library.libraryid)
def notify_online(self, library: Library) -> None:
"""Notify that a library's online status has changed."""
if not library.online or self.loaded:
self.emit("library-online", library)
def stop(self) -> None:
"""Stop any background work."""
for library in self.store:
library.stop()
super().stop()
@GObject.Signal(arg_types=(Library,))
def library_online(self, library: Library) -> None:
"""Signal that a library online status has changed."""