+sub spotlight_all_files {
+ my ($dir) = @_;
+
+ my @terms = ();
+ # "public.image" matches all (indexed) images, including Photoshop, etc.
+# push @terms, "kMDItemContentTypeTree == 'public.image'";
+ foreach (@good_extensions) {
+
+ # kMDItemFSName hits the file system every time: much worse than "find".
+# push @terms, "kMDItemFSName == '*.$_'";
+
+ # kMDItemDisplayName matches against the name in the Spotlight index,
+ # but won't find files that (for whatever reason) didn't get indexed.
+ push @terms, "kMDItemDisplayName == '*.$_'";
+ }
+
+ $dir =~ s@([^-_/a-z\d.,])@\\$1@gsi; # quote for sh
+ my $cmd = "mdfind -onlyin $dir \"" . join (' || ', @terms) . "\"";
+
+ print STDERR "$progname: executing: $cmd\n" if ($verbose > 1);
+ @all_files = split (/[\r\n]+/, `$cmd`);
+}
+
+
+# If we're using cacheing, read the cache file and return its contents,
+# if any. This also holds an exclusive lock on the cache file, which
+# has the additional benefit that if two copies of this program are
+# running at once, one will wait for the other, instead of both of
+# them spanking the same file system at the same time.
+#
+local *CACHE_FILE;
+my $cache_file_name = undef;
+my $read_cache_p = 0;
+
+sub read_cache($) {
+ my ($dir) = @_;
+
+ return () unless ($cache_p);
+
+ my $dd = "$ENV{HOME}/Library/Caches"; # MacOS location
+ if (-d $dd) {
+ $cache_file_name = "$dd/org.jwz.xscreensaver.getimage.cache";
+ } elsif (-d "$ENV{HOME}/tmp") {
+ $cache_file_name = "$ENV{HOME}/tmp/.xscreensaver-getimage.cache";
+ } else {
+ $cache_file_name = "$ENV{HOME}/.xscreensaver-getimage.cache";
+ }
+
+ my $file = $cache_file_name;
+ open (CACHE_FILE, "+>>$file") || error ("unable to write $file: $!");
+ flock (CACHE_FILE, LOCK_EX) || error ("unable to lock $file: $!");
+ seek (CACHE_FILE, 0, 0) || error ("unable to rewind $file: $!");
+
+ print STDERR "$progname: reading cache $cache_file_name\n"
+ if ($verbose > 1);
+
+ my $mtime = (stat(CACHE_FILE))[9];
+
+ if ($mtime + $cache_max_age < time) {
+ print STDERR "$progname: cache is too old\n" if ($verbose);
+ return ();
+ }
+
+ my $odir = <CACHE_FILE>;
+ $odir =~ s/[\r\n]+$//s if defined ($odir);
+ if (!defined ($odir) || ($dir ne $odir)) {
+ print STDERR "$progname: cache is for $odir, not $dir\n"
+ if ($verbose && $odir);
+ return ();
+ }
+
+ my @files = ();
+ while (<CACHE_FILE>) {
+ s/[\r\n]+$//s;
+ push @files, "$odir/$_";
+ }
+
+ print STDERR "$progname: " . ($#files+1) . " files in cache\n"
+ if ($verbose);
+
+ $read_cache_p = 1;
+ return @files;
+}
+
+
+sub write_cache($) {
+ my ($dir) = @_;
+
+ return unless ($cache_p);
+
+ # If we read the cache, just close it without rewriting it.
+ # If we didn't read it, then write it now.
+
+ if (! $read_cache_p) {
+
+ truncate (CACHE_FILE, 0) ||
+ error ("unable to truncate $cache_file_name: $!");
+ seek (CACHE_FILE, 0, 0) ||
+ error ("unable to rewind $cache_file_name: $!");
+
+ if ($#all_files >= 0) {
+ print CACHE_FILE "$dir\n";
+ my $re = qr/$dir/;
+ foreach (@all_files) {
+ my $f = $_; # stupid Perl. do this to avoid modifying @all_files!
+ $f =~ s@^$re/@@so || die;
+ print CACHE_FILE "$f\n";
+ }
+ }
+
+ print STDERR "$progname: cached " . ($#all_files+1) . " files\n"
+ if ($verbose);
+ }
+
+ flock (CACHE_FILE, LOCK_UN) ||
+ error ("unable to unlock $cache_file_name: $!");
+ close (CACHE_FILE);
+}
+
+
+sub find_random_file($) {