The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package File::Spotlight;

use strict;
use 5.008_001;
our $VERSION = '0.05';

use Carp;
use Mac::Spotlight::MDQuery ':constants';
use Mac::Spotlight::MDItem  ':constants';
use Scalar::Util qw(blessed);

sub new {
    my $class = shift;
    my($file) = @_;

    my $self = bless { path => $file }, $class;
    $self->init() if $file;

    return $self;
}

sub path {
    my $self = shift;
    if (@_) {
        $self->{path} = shift;
        $self->init;
    }
    $self->{path};
}

sub init {
    my $self = shift;

    my $plist = $self->parse_plist($self->path)
        or croak "Can't open savedSearch file " . $self->path;

    my $query = $plist->{RawQuery};
       $query = _get_value($query) if blessed $query;
    my @search_paths = @{ $plist->{SearchCriteria}{FXScopeArrayOfPaths} || [] };

    $self->{query} = $query;
    $self->{search_paths} = \@search_paths;
}

sub list {
    my $self = shift;

    if (@_) {
        # backward compatiblity
        $self = (ref $self)->new(@_);
    }

    my @files;
    for my $path (@{$self->{search_paths}}) {
        $path = _get_value($path) if blessed $path;
        push @files, $self->_run_mdfind($path, $self->{query});
    }

    return @files;
}

sub parse_plist {
    my($self, $file) = @_;

    if (eval { require Mac::Tie::PList }) {
        return Mac::Tie::PList->new_from_file($file);
    } else {
        require Mac::PropertyList;
        Mac::PropertyList::parse_plist_file($file);
    }
}

sub _run_mdfind {
    my($self, $path, $query) = @_;

    my $mq = Mac::Spotlight::MDQuery->new($query);
    $mq->setScope( $self->_scope($path) );
    $mq->execute;
    $mq->stop;

    my @files;
    for my $result ($mq->getResults) {
        push @files, $result->get(kMDItemPath);
    }

    return @files;
}

# string => constant
my %scope = (
   kMDQueryScopeHome     => kMDQueryScopeHome,
   kMDQueryScopeComputer => kMDQueryScopeComputer,
   kMDQueryScopeNetwork  => kMDQueryScopeNetwork,
);

sub _scope {
    my($self, $str) = @_;
    $scope{$str};
}

my %decode = (amp => '&', quot => '"', lt => '<', gt => '>');

sub _get_value {
    my $string = shift;

    # Mac::PropertyList doesn't decode XML escapes
    $string = $string->value;
    $string =~ s/&(amp|quot|lt|gt);/$decode{$1}/eg;

    return $string;
}


1;
__END__

=encoding utf-8

=for stopwords savedSearch .savedSearch plist

=head1 NAME

File::Spotlight - List files from Smart Folder by reading .savedSearch files

=head1 SYNOPSIS

  use File::Spotlight;

  my $path = "$ENV{HOME}/Library/Saved Searches/New Smart Folder.savedSearch";

  my $folder = File::Spotlight->new($path);
  my @found  = $folder->list();

=head1 DESCRIPTION

File::Spotlight is a simple module to parse I<.savedSearch> Smart
Folder definition, run the query and get the results with OS X
Spotlight binding via Mac::Spotlight.

This is a low-level module to open and execute the saved search plist
files. In your application you might better wrap or integrate this
module with higher-level file system abstraction like L<IO::Dir>,
L<Path::Class::Dir> or L<Filesys::Virtual>.

=head1 METHODS

=over 4

=item new

  $folder = File::Spotlight->new("/path/to/foo.savedSearch");

Creates a new File::Spotlight object with the I<.savedSearch> file
path usually in C<~/Library/Saved Searches> folder.

=item list

  @files = $folder->list;

Executes the saved Spotlight query and returns the list of files found
in the smart folder.

=back

=head1 AUTHOR

Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 SEE ALSO

L<Mac::Spotlight::MDQuery> L<http://www.macosxhints.com/dlfiles/spotlightls.txt>

=cut