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;
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{<!after '/'>$} = '/';
$path ~~ s{<!before '/'>^} = '/';
($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 parent.id, newrow.path, newrow.name, newrow.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{<!after '/'>$} = '/';
$path ~~ s{<!before '/'>^} = '/';
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
}
}
}