#!/usr/bin/perl # # cache_saved_searches.pl: # a little script that will look in your ~/Library/Saved Searches # directory, and with each .savedSearch found there, creates a # subdirectory by the same name, containing symlinks to all of the # search results # # e.g. if you have ~/Library/Saved Searches/NewPDFs.savedSearch # which # # by Mithras The Prophet (mithras.the.prophet at that gmail thing) # v1.0 4/24/2005 # use warnings; use strict; use File::Basename; # try to read contents of ~/Library/Saved Searches/ my $saved_searches_dir = $ENV{HOME} . "/Library/Saved Searches"; opendir (SS_DIR, $saved_searches_dir) or die("Can't find saved searches in '$saved_searches_dir'\n"); # loop through all files while (my $filename = readdir(SS_DIR)) { if ($filename =~ /\.savedSearch$/) { # for each .savedSearch: print STDERR "Caching $filename...\n"; # 1. determine the folder name to use my $foldername = $filename; $foldername =~ s/\.savedSearch$//; # 2. if the directory doesn't exist, create it; # if it already exists, remove any symlinks within it if (-d "$saved_searches_dir/$foldername") { opendir (SUBFOLDER, "$saved_searches_dir/$foldername"); while (my $subfile = readdir(SUBFOLDER)) { if (-l "$saved_searches_dir/$foldername/$subfile") { unlink("$saved_searches_dir/$foldername/$subfile"); } } } else { mkdir($foldername) or die("Couldn't create directory '$foldername' for saved searched '$filename'"); } # 3. get the results for the saved search my @search_results = execute_savedSearch( "$saved_searches_dir/$filename" ); # 4. With each result, make a symlink in the search folder, # using just the basename (but avoiding collisions) # # e.g. if the search yields # /Users/mithras/Documents/doc1.pdf # /Users/mithras/Desktop/doc1.pdf # # we will create # /doc1.pdf # /doc1_2.pdf foreach my $result (@search_results) { # get the basename etc. my ($result_plainname, $result_dir, $result_ext) = fileparse($result, '\..*?'); # if there's already a file at this basename, # add numbers until we're unique my $index = 2; my $orig_result_plainname = $result_plainname; while (-e "$saved_searches_dir/$foldername/$result_plainname$result_ext") { $result_plainname=$orig_result_plainname . "_$index"; } # now create the symlink! print STDERR "\t$result_plainname$result_ext\n"; symlink( $result, "$saved_searches_dir/$foldername/$result_plainname$result_ext"); } print "\n"; } } closedir(SS_DIR); sub execute_savedSearch { my @results; while (scalar @_ > 0) { my ($thisarg) = shift (@_); # print STDERR "Expanding saved search: $thisarg\n"; # parse the searchfile my $query = ""; my @searchLocations; open (INFILE, "$thisarg") or die("Can't read saved search: '$thisarg'\n"); my $query_next = 0; my $searchlocation_next = 0; while () { if (m/RawQuery<\/key>/) { $query_next = 1; } elsif (m/FXScopeArrayOfPaths<\/key>/) { $searchlocation_next = 1; } elsif (m/<\/array>/) { $query_next = 0; $searchlocation_next = 0; } elsif ( $query_next && m/([^<]+)<\/string>/ ) { $query = $1; $query_next = 0; $query =~ s/&/&/g; $query =~ s/<//g; } elsif ( $searchlocation_next && m/([^<]+)<\/string>/ ) { my $thisloc = $1; $thisloc =~ s/&/&/g; $thisloc =~ s/<//g; $thisloc =~ s/kMDQueryScopeHome/$ENV{HOME}/ge; $thisloc =~ s/kMDQueryScopeComputer/\//g; push (@searchLocations, $thisloc); } } close (INFILE); # now, run and mdfind over each of the search locations: # print STDERR "\tquery: $query\n"; foreach my $searchLoc (@searchLocations) { # print STDERR "\tlocation: $searchLoc\n"; # we'll fork, and run mdfind # so we can pass the query parameter to mdfind without # the shell interpreter interfering my $pid = open(KID_TO_READ, "-|"); if ($pid) { # parent while () { # add each result to our final results array chomp; push (@results, $_); } close(KID_TO_READ) || warn "kid exited $?"; } else { # child exec("mdfind", '-onlyin',$searchLoc,$query) || die "can't exec program: $!"; # NOTREACHED } } shift; } return @results; } sub extension { my $path = shift; my $ext = (fileparse($path,'\..*'))[2]; $ext =~ s/^\.//; return $ext; }