diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/App/BorgRestore/DB.pm | 185 | ||||
-rw-r--r-- | lib/App/BorgRestore/Helper.pm | 22 | ||||
-rw-r--r-- | lib/App/BorgRestore/Settings.pm | 33 |
3 files changed, 240 insertions, 0 deletions
diff --git a/lib/App/BorgRestore/DB.pm b/lib/App/BorgRestore/DB.pm new file mode 100644 index 0000000..d4e61df --- /dev/null +++ b/lib/App/BorgRestore/DB.pm @@ -0,0 +1,185 @@ +package App::BorgRestore::DB; +use v5.10; +use strict; +use warnings; + +use App::BorgRestore::Helper; + +use Data::Dumper; +use DBI; + +sub new { + my $class = shift; + my $db_path = shift; + + my $self = {}; + bless $self, $class; + + $self->_open_db($db_path); + + return $self; +} + +sub _open_db { + my $self = shift; + my $dbfile = shift; + + $self->{dbh} = DBI->connect("dbi:SQLite:dbname=$dbfile","","", {RaiseError => 1, Taint => 1}); + $self->{dbh}->do("PRAGMA cache_size=-1024000"); + $self->{dbh}->do("PRAGMA strict=ON"); +} + +sub initialize_db { + my $self = shift; + + $self->{dbh}->do('create table `files` (`path` text, primary key (`path`)) without rowid;'); + $self->{dbh}->do('create table `archives` (`archive_name` text unique);'); +} + +sub get_archive_names { + my $self = shift; + + my @ret; + + my $st = $self->{dbh}->prepare("select `archive_name` from `archives`;"); + $st->execute(); + while (my $result = $st->fetchrow_hashref) { + push @ret, $result->{archive_name}; + } + return \@ret; +} + +sub get_archive_row_count { + my $self = shift; + + my $st = $self->{dbh}->prepare("select count(*) count from `files`;"); + $st->execute(); + my $result = $st->fetchrow_hashref; + return $result->{count}; +} + +sub add_archive_name { + my $self = shift; + my $archive = shift; + + $archive = App::BorgRestore::Helper::untaint_archive_name($archive); + + my $st = $self->{dbh}->prepare('insert into `archives` (`archive_name`) values (?);'); + $st->execute($archive); + + $self->_add_column_to_table("files", $archive); +} + +sub _add_column_to_table { + my $self = shift; + my $table = shift; + my $column = shift; + + my $st = $self->{dbh}->prepare('alter table `'.$table.'` add column `'._prefix_archive_id($column).'` integer;'); + $st->execute(); +} + +sub remove_archive { + my $self = shift; + my $archive = shift; + + $archive = App::BorgRestore::Helper::untaint_archive_name($archive); + + my $archive_id = $self->get_archive_id($archive); + + my @keep_archives = grep {$_ ne $archive;} @{$self->get_archive_names()}; + + $self->{dbh}->do('create table `files_new` (`path` text, primary key (`path`)) without rowid;'); + for my $archive (@keep_archives) { + $self->_add_column_to_table("files_new", $archive); + } + + my @columns_to_copy = map {'`'._prefix_archive_id($_).'`'} @keep_archives; + @columns_to_copy = ('`path`', @columns_to_copy); + $self->{dbh}->do('insert into `files_new` select '.join(',', @columns_to_copy).' from files'); + + $self->{dbh}->do('drop table `files`'); + + $self->{dbh}->do('alter table `files_new` rename to `files`'); + + my $st = $self->{dbh}->prepare('delete from `archives` where `archive_name` = ?;'); + $st->execute($archive); +} + +sub _prefix_archive_id { + my $archive = shift; + + $archive = App::BorgRestore::Helper::untaint_archive_name($archive); + + return 'timestamp-'.$archive; +} + +sub get_archive_id { + my $self = shift; + my $archive = shift; + + return _prefix_archive_id($archive); +} + +sub get_archives_for_path { + my $self = shift; + my $path = shift; + + my $st = $self->{dbh}->prepare('select * from `files` where `path` = ?;'); + $st->execute(App::BorgRestore::Helper::untaint($path, qr(.*))); + + my @ret; + + my $result = $st->fetchrow_hashref; + my $archives = $self->get_archive_names(); + + for my $archive (@$archives) { + my $archive_id = $self->get_archive_id($archive); + my $timestamp = $result->{$archive_id}; + + push @ret, { + modification_time => $timestamp, + archive => $archive, + }; + } + + return \@ret; +} + + +sub add_path { + my $self = shift; + my $archive_id = shift; + my $path = shift; + my $time = shift; + + my $st = $self->{dbh}->prepare_cached('insert or ignore into `files` (`path`, `'.$archive_id.'`) + values(?, ?)'); + $st->execute($path, $time); + + $st = $self->{dbh}->prepare_cached('update files set `'.$archive_id.'` = ? where `path` = ?'); + $st->execute($time, $path); +} + +sub begin_work { + my $self = shift; + + $self->{dbh}->begin_work(); +} + +sub commit { + my $self = shift; + + $self->{dbh}->commit(); +} + +sub vacuum { + my $self = shift; + + $self->{dbh}->do("vacuum"); +} + + +1; + +__END__ diff --git a/lib/App/BorgRestore/Helper.pm b/lib/App/BorgRestore/Helper.pm new file mode 100644 index 0000000..b4d52e5 --- /dev/null +++ b/lib/App/BorgRestore/Helper.pm @@ -0,0 +1,22 @@ +package App::BorgRestore::Helper; +use v5.10; +use strict; +use warnings; + + +sub untaint { + my $data = shift; + my $regex = shift; + + $data =~ m/^($regex)$/ or die "Failed to untaint: $data"; + return $1; +} + +sub untaint_archive_name { + my $archive = shift; + return untaint($archive, qr([a-zA-Z0-9-:+]+)); +} + +1; + +__END__ diff --git a/lib/App/BorgRestore/Settings.pm b/lib/App/BorgRestore/Settings.pm new file mode 100644 index 0000000..7416c54 --- /dev/null +++ b/lib/App/BorgRestore/Settings.pm @@ -0,0 +1,33 @@ +package App::BorgRestore::Settings; +use v5.10; +use strict; +use warnings; + +use App::BorgRestore::Helper; + +our $borg_repo = ""; +our $cache_path_base = sprintf("%s/borg-restore.pl", $ENV{XDG_CACHE_HOME} // $ENV{HOME}."/.cache"); +our @backup_prefixes = ( + {regex => "^/", replacement => ""}, +); + +my @configfiles = ( + sprintf("%s/borg-restore.cfg", $ENV{XDG_CONFIG_HOME} // $ENV{HOME}."/.config"), + "/etc/borg-restore.cfg", +); + +for my $configfile (@configfiles) { + $configfile = App::BorgRestore::Helper::untaint($configfile, qr/.*/); + if (-e $configfile) { + unless (my $return = do $configfile) { + die "couldn't parse $configfile: $@" if $@; + die "couldn't do $configfile: $!" unless defined $return; + die "couldn't run $configfile" unless $return; + } + } +} +$cache_path_base = App::BorgRestore::Helper::untaint($cache_path_base, qr/.*/); + +1; + +__END__ |