summaryrefslogtreecommitdiffstats
path: root/lib/App/BorgRestore/PathTimeTable/DB.pm
blob: d514f0cb5664d106f86d9588099abdcb0dac8846 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package App::BorgRestore::PathTimeTable::DB;
use strictures 2;

use Function::Parameters;
use Log::Any qw($log);

=head1 NAME

App::BorgRestore::PathTimeTable::DB - Directly write new archive data to the database

=head1 DESCRIPTION

This is used by L<App::BorgRestore> to add new archive data into the database.
Data is written to the database directly and existing data is updated where necessary.

For performance reasons this class keeps an internal cache so that the database
is only contacted when necessary. Depending on the distribution of modification
times of files and directories, the effectiveness of this cache can vary. The
cache also assumes that the path are sorted so that all files from one
directory are added, before files from another. If a path from a different
directory is added, the previous cache is invalidated.

=cut

# Trace logging statements are disabled here since they incur a performance
# overhead because they are in tight loops. Change to 1 when necessary.
use constant TRACE => 0;

method new($class: $deps = {}) {
	return $class->new_no_defaults($deps);
}

method new_no_defaults($class: $deps = {}) {
	my $self = {};
	bless $self, $class;
	$self->{deps} = $deps;
	$self->{cache} = {};
	$self->{current_path_in_cache} = "";
	$self->{stats} = {};
	return $self;
}

method set_archive_id($archive_id) {
	$self->{archive_id} = $archive_id;
}

method add_path($path, $time) {
	$self->{stats}->{total_paths}++;
	my $old_cache_path = $self->{current_path_in_cache};
	while ((my $slash_index = rindex($old_cache_path, "/")) != -1) {
		$self->{stats}->{cache_invalidation_loop_iterations}++;
		if ($old_cache_path eq substr($path, 0, length($old_cache_path))) {
			# keep the cache content for the part of the path that stays the same
			last;
		}
		delete $self->{cache}->{$old_cache_path};
		# strip last part of path
		$old_cache_path = substr($old_cache_path, 0, $slash_index);
	}

	my $full_path = $path;
	while ((my $slash_index = rindex($path, "/")) != -1) {
		$self->_add_path_to_db($self->{archive_id}, $path, $time);
		my $cached = $self->{cache}->{$path};
		if ($path ne $full_path && (!defined $cached || $cached < $time)) {
			$log->tracef("Setting cache time for path '%s' to %d", $path, $time) if TRACE;
			$self->{cache}->{$path} = $time;
		}
		$path = substr($path, 0, $slash_index);
	}
	$self->_add_path_to_db($self->{archive_id}, $path, $time) unless $path eq ".";
	my $cached = $self->{cache}->{$path};
	if ($path ne $full_path && (!defined $cached || $cached < $time)) {
		$log->tracef("Setting cache time for path '%s' to %d", $path, $time) if TRACE;
		$self->{cache}->{$path} = $time;
	}
	$self->{current_path_in_cache} = $full_path;
}

method _add_path_to_db($archive_id, $path,$time) {
	my $cached = $self->{cache}->{$path};
	$self->{stats}->{total_potential_calls_to_db_class}++;
	if (!defined $cached || $cached < $time) {
		$log->tracef("Updating DB for path '%s' with time %d", $path, $time) if TRACE;
		$self->{stats}->{real_calls_to_db_class}++;
		$self->{deps}->{db}->update_path_if_greater($archive_id, $path, $time);
	} else {
		$log->tracef("Skipping DB update for path '%s' because (cached) DB time is %d and file time is %d which is lower", $path, $cached, $time) if TRACE;
	}
}


method save_nodes() {
	for my $key (keys %{$self->{stats}}) {
		$log->debugf("Performance counter %s = %s", $key, $self->{stats}->{$key});
	}
	# do nothing because we already write everything to the DB directly
}

1;

__END__