use v6.d; use DB::SQLite; class App::MediaControl::DB { has DB::SQLite $.pool is required; method !db(Callable:D $code) { my $conn = self.pool.db; # we need an explicit LEAVE block because on 2021.10, `will # leave { .finish }` kills precomp LEAVE { .finish with $conn }; $conn.begin; $conn.execute('PRAGMA foreign_keys=true'); KEEP { .commit with $conn }; return $code($conn) with $conn; } method ensure-schema() { return if self!db: { .query( 'SELECT 1 FROM sqlite_schema WHERE type=? AND tbl_name=?', 'table', 'files', ).value.defined }; self!db: { .query(q:to/END/); CREATE TABLE files ( id INTEGER PRIMARY KEY, parent_id INTEGER NULL REFERENCES files(id) ON DELETE CASCADE, path TEXT NOT NULL, name TEXT NOT NULL, is_dir BOOLEAN NOT NULL, watched_time INTEGER NULL, seen BOOLEAN NOT NULL DEFAULT false, UNIQUE (path, name) ) END } } method clear-seen() { self!db: { .query(q:to/END/) UPDATE files SET seen=false END } } method remove-unseen() { self!db: { .query(q:to/END/) DELETE FROM files WHERE seen=false END } } subset Entry of Associative where :( Str:D() :path($)! is copy, Str:D() :name($)!, Bool:D() :is-dir($)!, ); method add-entries(@entries where { .all ~~ Entry }) { my $values = 'VALUES ' ~ ('(?, ?, ?)' xx @entries).join(','); my @binds = @entries.map( -> (:$path is copy, :$name, :$is-dir) { $path ~~ s{$} = '/'; $path ~~ s{^} = '/'; ($path,$name,$is-dir).Slip; }); self!db: { .query(qq:to/END/, |@binds); WITH parent(id,path) AS ( SELECT id, path || name || '/' FROM files ), newrow(path,name,is_dir) AS ( $values ) INSERT INTO files(parent_id,path,name,is_dir,seen) SELECT id, newrow.path, name, is_dir, true FROM newrow LEFT JOIN parent ON parent.path=newrow.path WHERE true ON CONFLICT (path,name) DO UPDATE SET seen=true END } } method remove-entry(Str:D() :$path! is copy, Str:D() :$name!) { $path ~~ s{$} = '/'; $path ~~ s{^} = '/'; note "remove-entry($path,$name)"; self!db: { .query(q:to/END/, :$path, :$name); DELETE FROM files WHERE name=$name AND path=$path END } } multi method get-children-of(Any:U $) { 'IS NULL' } multi method get-children-of(Int:D() $id) { '=$id', $id } proto method get-children-of($) { my ($clause, @binds) = {*}; self!db: { .query(qq:to/END/,|@binds).hashes; SELECT id, name, is_dir FROM files WHERE parent_id $clause ORDER BY name ASC END } } method get-parents-of(Int:D() $id) { self!db: { .query(q:to/END/,$id).hashes.reverse; WITH f(id, parent_id, name) AS ( SELECT id, parent_id, name FROM files WHERE id=$id UNION ALL SELECT files.id, files.parent_id, files.name FROM files JOIN f ON files.id=f.parent_id ) SELECT id, name FROM f END } } method get-entry(Int:D() $id) { self!db: { .query(q:to/END/,$id).hash; SELECT id, path, name, is_dir FROM files WHERE id=$id END } } }