1# ex:ts=8 sw=4:
2# $OpenBSD: Cache.pm,v 1.13 2023/09/16 09:33:13 espie Exp $
3#
4# Copyright (c) 2022 Marc Espie <espie@openbsd.org>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18use v5.36;
19
20# supplementary glue to add support for reading the update.db locate(1)
21# database in quirks
22package OpenBSD::PackageRepository::Cache;
23
24sub new($class, $state, $setlist)
25{
26	return undef unless -f OpenBSD::Paths->updateinfodb;
27
28	my $o = bless {
29	    raw_data => {},
30	    stems => {},
31	    state => $state }, $class;
32
33	$o->prime_update_info_cache($state, $setlist);
34	return $o;
35
36}
37sub pipe_locate($self, @params)
38{
39	unshift(@params, OpenBSD::Paths->locate,
40	    '-d', OpenBSD::Paths->updateinfodb, '--');
41	my $state = $self->{state};
42	$state->errsay("Running #1", join(' ', @params))
43	    if $state->defines("CACHING_VERBOSE");
44	return @params;
45}
46
47# this is a hack to talk to quirks: the interface expects a list of
48# search objects such that the last one can do add_stem, so we oblige
49# (probably TODO: add a secondary interface in quirks, but this can do
50# in the meantime)
51sub add_stem($self, $stem)
52{
53	$self->{stems}{$stem} = 1;
54}
55
56sub prime_update_info_cache($self, $state, $setlist)
57{
58	my $progress = $state->progress;
59	my $found = {};
60
61	my $pseudo_search = [$self];
62
63	# figure out a list of names to precache
64
65	# okay, so basically instead of hitting locate once for each
66	# package on the distant repository, we precache all the stems
67	# we are asking to update/install
68	# this is based on the assumption that most names are "regular"
69	# and we won't cache too little or too much
70	for my $set (@{$setlist}) {
71		for my $h ($set->older, $set->hints) {
72			next if $h->{update_found};
73			my $name = $h->pkgname;
74			my $stem = OpenBSD::PackageName::splitstem($name);
75			next if $stem =~ m/^\.libs\d*\-/;
76			next if $stem =~ m/^partial\-/;
77			$stem =~ s/\%.*//; # zap branch info
78			$stem =~ s/\-\-.*//; # and set flavors
79			$self->add_stem($stem);
80			$state->run_quirks(
81			    sub($quirks) {
82				$quirks->tweak_search($pseudo_search, $h,
83				    $state);
84			    });
85		}
86	}
87	my @list = sort keys %{$self->{stems}};
88	return if @list == 0;
89
90	my $total = scalar @list;
91	$progress->set_header(
92	    $state->f("Reading update info for installed packages",
93		$total));
94	my $done = 0;
95	my $oldname = "";
96	# This can't go much faster, I've tried splitting the params
97	# and running several locate(1) in //, but this yields negligible
98	# gains for a lot of added complexity (reduced from 18 to 14 seconds
99	# on my usual package install).
100	open my $fh, "-|", $self->pipe_locate(map { "$_-[0-9]*"} @list)
101	    or $state->fatal("Can't run locate: #1", $!);
102	while (<$fh>) {
103		if (m/^(.*?)\:(.*)/) {
104			my ($pkgname, $value) = ($1, $2);
105			$found->{OpenBSD::PackageName::splitstem($pkgname)} = 1;
106			$self->{raw_data}{$pkgname} //= '';
107			$self->{raw_data}{$pkgname} .= "$value\n";
108			if ($pkgname ne $oldname) {
109				$oldname = $pkgname;
110				$done++;
111			}
112			$progress->show($done, $total);
113		}
114	}
115	close($fh);
116	return unless $state->defines("CACHING_VERBOSE");
117	for my $k (@list) {
118		if (!defined $found->{$k}) {
119			$state->say("No cache entry for #1", $k);
120		}
121	}
122}
123
124sub get_cached_info($self, $name)
125{
126	my $state = $self->{state};
127	my $content;
128	if (exists $self->{raw_data}{$name}) {
129		$content = $self->{raw_data}{$name};
130	} else {
131		my $stem = OpenBSD::PackageName::splitstem($name);
132		if (exists $self->{stems}{$stem}) {
133			$state->say("Negative caching for #1", $name)
134			    if $state->defines("CACHING_VERBOSE");
135			return undef;
136		}
137		$content = '';
138		open my $fh, "-|", $self->pipe_locate($name.":*") or die $!;
139		while (<$fh>) {
140			if (m/^.*?\:(.*)/) {
141				$content .= $1."\n";
142			} else {
143				return undef;
144			}
145		}
146		close ($fh);
147	}
148	if ($content eq '') {
149		$state->say("Cache miss for #1", $name)
150		    if $state->defines("CACHING_VERBOSE");
151		return undef;
152	}
153	open my $fh2, "<", \$content;
154	return OpenBSD::PackingList->read($fh2);
155}
156
1571;
158