App-BorgRestore

 view release on metacpan or  search on metacpan

lib/App/BorgRestore.pm  view on Meta::CPAN

	my $backup_path = $self->map_path_to_backup_path($abs_path);

	$destination //= dirname($abs_path);

	my $archives = $self->find_archives($backup_path);
	my $selected_archive = $self->select_archive_timespec($archives, $timespec);
	$self->restore($backup_path, $selected_archive, $destination);
}

=head3 search_path

 my $paths = $app->search_path($pattern)

Returns a arrayref of paths that match the pattern. The pattern is matched as
an sqlite LIKE pattern. If no % occurs in the pattern, the patterns is
automatically wrapped between two % so it may match anywhere in the path.

=cut

method search_path($pattern) {
	$pattern = '%'.$pattern.'%' if $pattern !~ m/%/;
	return $self->{deps}->{db}->search_path($pattern);
}

=head3 get_missing_items

 my $items = $app->get_missing_items($have, $want);

Returns an arrayref of items that are part of C<$want>, but not of C<$have>.

=cut

method get_missing_items($have, $want) {
	my $ret = [];

	for my $item (@$want) {
		my $exists = any { $_ eq $item } @$have;
		push @$ret, $item if not $exists;
	}

	return $ret;
}

method _handle_removed_archives($borg_archives) {
	my $start = Time::HiRes::gettimeofday();

	my $existing_archives = $self->{deps}->{db}->get_archive_names();

	# TODO this name is slightly confusing, but it works as expected and
	# returns elements that are in the previous list, but missing in the new
	# one
	my $remove_archives = $self->get_missing_items($borg_archives, $existing_archives);

	if (@$remove_archives) {
		for my $archive (@$remove_archives) {
			$log->infof("Removing archive %s", $archive);
			$self->{deps}->{db}->begin_work;
			$self->{deps}->{db}->remove_archive($archive);
			$self->{deps}->{db}->commit;
			$self->{deps}->{db}->vacuum;
			$self->{deps}->{db}->verify_cache_fill_rate_ok();
		}

		my $end = Time::HiRes::gettimeofday();
		$log->debugf("Removing archives finished after: %.5fs", $end - $start);
	}
}

method _handle_added_archives($borg_archives) {
	my $archives = $self->{deps}->{db}->get_archive_names();
	my $add_archives = $self->get_missing_items($archives, $borg_archives);

	for my $archive (@$add_archives) {
		my $start = Time::HiRes::gettimeofday();
		my $lookuptable_class = $self->{config}->{cache}->{prepare_data_in_memory} == 1 ? "Memory" : "DB";
		$log->debugf("Using '%s' class for PathTimeTable", $lookuptable_class);
		my $lookuptable = "App::BorgRestore::PathTimeTable::$lookuptable_class"->new({db => $self->{deps}->{db}});

		$log->infof("Adding archive %s", $archive);
		$self->{deps}->{db}->begin_work;
		$self->{deps}->{db}->add_archive_name($archive);
		my $archive_id = $self->{deps}->{db}->get_archive_id($archive);
		$lookuptable->set_archive_id($archive_id);

		$self->{deps}->{borg}->list_archive($archive, sub {
			my $line = shift;
			# roll our own parsing of timestamps for speed since we will be parsing
			# a huge number of lines here
			# XXX: this also exists in BorgRestore::Helper::parse_borg_time()
			# example timestamp: "Wed, 2016-01-27 10:31:59"
			if ($line =~ m/^.{4} (?<year>....)-(?<month>..)-(?<day>..) (?<hour>..):(?<minute>..):(?<second>..) (?<path>.+)$/) {
				my $time = POSIX::mktime($+{second},$+{minute},$+{hour},$+{day},$+{month}-1,$+{year}-1900);
				#$log->debugf("Adding path %s with time %s", $+{path}, $time);
				$lookuptable->add_path($+{path}, $time);
			}
		});

		my $borg_time = Time::HiRes::gettimeofday;

		$lookuptable->save_nodes();
		$self->{deps}->{db}->commit;
		$self->{deps}->{db}->vacuum;
		$self->{deps}->{db}->verify_cache_fill_rate_ok();

		my $end = Time::HiRes::gettimeofday();
		$log->debugf("Adding archive finished after: %.5fs (parsing borg output took %.5fs)", $end - $start, $borg_time - $start);
	}
}

=head3 update_cache

 $app->update_cache();

Updates the database used by e.g. C<find_archives>.

=cut

method update_cache() {
	my $v2_basedir = $self->{deps}->{settings}->get_cache_base_dir_path("v2");
	if (-e $v2_basedir) {
		$log->info("Removing old v2 cache directory: $v2_basedir");
		path($v2_basedir)->remove_tree;
	}

	$log->debug("Updating cache if required");

	my $borg_archives = $self->{deps}->{borg}->borg_list();

	# write operations benefit from the large cache so set the cache size here
	$self->{deps}->{db}->set_cache_size();
	$self->_handle_removed_archives($borg_archives);
	$self->_handle_added_archives($borg_archives);

	$log->debugf("DB contains information for %d archives in %d rows", scalar(@{$self->{deps}->{db}->get_archive_names()}), $self->{deps}->{db}->get_archive_row_count());
	$self->{deps}->{db}->verify_cache_fill_rate_ok();
}


1;
__END__

=head1 LICENSE

Copyright (C) 2016-2018  Florian Pritz E<lt>bluewind@xinu.atE<gt>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

See LICENSE for the full license text.

=head1 AUTHOR

Florian Pritz E<lt>bluewind@xinu.atE<gt>

=cut



( run in 2.699 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )