package AniDB::Datastore; use 5.024; use experimental 'signatures'; use Moo; use Path::Tiny; use Try::Tiny; use JSON::MaybeXS qw(encode_json decode_json); use namespace::clean; has database => ( is => 'ro', required => 1 ); has dbh => ( is => 'ro', lazy => 1 ); sub _build_dbh($self) { require DBI; return DBI->connect( 'dbi:SQLite:dbname='.$self->database, '', '', { AutoCommit => 1, RaiseError => 1, PrintError => 0, }, ); } sub BUILD($self) { try { $self->dbh->selectall_arrayref('SELECT * FROM path_info LIMIT 1'); } catch { if (/\bno such table\b/) { my @sql = do { local $/ = ";\n\n"; }; $self->dbh->do($_) for @sql; } else { die $_; } }; } sub has_changed($self,$path,$stat) { my $path_info = $self->path_info_for($path); # if we don't know about the file, it's definitely changed! return 1 unless $path_info; return ( $path_info->{size} == $stat->size && $path_info->{mtime} == $stat->mtime ); } sub rename($self,$path,$new_path) { $self->dbh->do( q{UPDATE path_info SET name=? WHERE name=?}, $path->stringify,$new_path->stringify, ); } sub full_info_for($self,$path) { my $path_info = $self->path_info_for($path); my $episode_info = $self->episode_info_for( $path_info, ); my $anime_info = $self->anime_info_for( $episode_info, ); return { path => $path_info, episode => $episode_info, anime => $anime_info, }; } sub path_info_for($self,$path) { my $ret = $self->dbh->selectall_arrayref( qr{SELECT * FROM path_info WHERE name=?}, { Slice => {} }, $path->stringify, ); return $ret->[0]; } sub update_path_info($self,$path,$path_info) { my @binds = (@{$path_info}{qw(size mtime hash)}, $path->stringify); return $self->dbh->do( qr{UPDATE path_info SET size=?, mtime=?, hash=? WHERE name=?}, {}, @binds, ) or $self->dbh->do( qr{INSERT INTO path_info(size,mtime,hash,name) VALUES (?,?,?,?)}, {}, @binds, ); } sub episode_info_for($self,$args) { my $ret = $self->dbh->selectall_arrayref( qr{SELECT json FROM episode_info WHERE hash=? AND size=?}, { }, @{$args}{qw(hash size)}, ); return unless $ret && $ret->[0]; return decode_json($ret->[0][0]); } sub update_episode_info($self,$path_info, $episode_info) { my $json = encode_json($episode_info); my @binds = ($json, @{$path_info}{qw(size hash)}); return $self->dbh->do( qr{UPDATE episode_info SET json=? WHERE size=? AND hash=?}, {}, @binds, ) or $self->dbh->do( qr{INSERT INTO episode_info(json,size,hash) VALUES (?,?,?)}, {}, @binds, ); } sub anime_info_for($self,$args) { my $ret = $self->dbh->selectall_arrayref( qr{SELECT json FROM anime_info WHERE aid=?}, { }, @{$args}{qw(aid)}, ); return unless $ret && $ret->[0]; return decode_json($ret->[0][0]); } sub update_anime_info($self,$episode_info, $anime_info) { my $json = encode_json($anime_info); my @binds = ($json, $episode_info->{eid}); return $self->dbh->do( qr{UPDATE anime_info SET json=? WHERE eid=?}, {}, @binds, ) or $self->dbh->do( qr{INSERT INTO anime_info(json,eid) VALUES (?,?)}, {}, @binds, ); } 1; __DATA__ CREATE TABLE path_info ( name TEXT PRIMARY KEY, mtime INT NOT NULL, size INT NOT NULL, hash TEXT NOT NULL ); CREATE TABLE episode_info ( size INT NOT NULL, hash TEXT NOT NULL, json TEST NOT NULL, PRIMARY KEY (size,hash) ); CREATE TABLE anime_info ( eid INT PRIMARY KEY, json TEST NOT NULL );