App-DrivePlayer
view release on metacpan or search on metacpan
lib/App/DrivePlayer/Scanner.pm view on Meta::CPAN
folder_drive_id => $drive_id,
folder_name => $name,
parent_drive_id => undef,
path => $name,
scan_folder_id => $scan_folder->{id},
);
my ($removed_tracks, $removed_folders) = (0, 0);
unless ($self->_stop) {
my $pending = $self->db->count_unseen_tracks($scan_folder->{id}, $self->_seen_track_ids);
my $confirmed = $pending <= $LARGE_DELETION_THRESHOLD
|| !$self->on_large_deletion
|| $self->on_large_deletion->($pending, $name);
if ($confirmed) {
$removed_tracks = $self->db->remove_unseen_tracks($scan_folder->{id}, $self->_seen_track_ids);
$removed_folders = $self->db->remove_unseen_folders($scan_folder->{id}, $self->_seen_folder_ids);
}
}
return { removed_tracks => $removed_tracks, removed_folders => $removed_folders };
}
sub _progress {
my ($self, $msg) = @_;
$self->on_progress->($msg) if $self->has_on_progress;
}
sub _scan_dir {
my ($self, %args) = @_;
return if $self->_stop;
my $drive_id = $args{folder_drive_id};
my $folder_name = $args{folder_name};
my $path = $args{path};
my $scan_folder_id = $args{scan_folder_id};
$self->_progress("Scanning: $path");
return if $self->_stop;
$self->_seen_folder_ids->{$drive_id} = 1;
my $folder = $self->db->upsert_folder(
drive_id => $drive_id,
name => $folder_name,
parent_drive_id => $args{parent_drive_id},
path => $path,
scan_folder_id => $scan_folder_id,
);
my @items = eval {
$self->drive->list(
filter => "'$drive_id' in parents and trashed=false",
params => { fields => $DRIVE_FIELDS, pageSize => 1000 },
);
};
if ($@) {
$self->_progress("Error scanning $path: $@");
return;
}
# Normalize Drive response bytes to decoded Unicode so SQLite doesn't
# re-encode them as Latin-1 â UTF-8 (double-encoding).
$_->{name} = _u8($_->{name}) for @items;
my (@subfolders, @audio_files);
for my $item (@items) {
if ($item->{mimeType} eq $FOLDER_MIME) {
push @subfolders, $item;
} elsif ($item->{mimeType} =~ m{^audio/}i) {
push @audio_files, $item;
}
}
for my $file (@audio_files) {
return if $self->_stop;
$self->_store_track($file, $folder, $path);
}
for my $subfolder (@subfolders) {
return if $self->_stop;
$self->_scan_dir(
folder_drive_id => $subfolder->{id},
folder_name => $subfolder->{name},
parent_drive_id => $drive_id,
path => "$path/$subfolder->{name}",
scan_folder_id => $scan_folder_id,
);
}
}
sub _store_track {
my ($self, $file, $folder, $folder_path) = @_;
$self->_seen_track_ids->{$file->{id}} = 1;
my ($title, $artist, $album, $track_num, $year) = _parse_filename($file->{name});
# Infer artist/album from folder path depth: Root/Artist/Album/track
my @parts = split m{/}, $folder_path;
if (!$artist && @parts >= 3) {
$artist = $parts[-2];
$album = $parts[-1];
} elsif (!$album && @parts >= 2) {
$album = $parts[-1];
}
# "YYYY-AlbumName" folder convention: strip the year prefix and use the
# captured year as the track's year when one wasn't parsed from the file.
if ($album && $album =~ s{
^
( (?: 19 | 20) \d{2} ) # 4-digit year, 19xx or 20xx
-
}{}x) {
$year //= $1;
}
my $duration_ms;
if (my $meta = $file->{videoMediaMetadata}) {
$duration_ms = $meta->{durationMillis};
}
( run in 1.219 second using v1.01-cache-2.11-cpan-d06a3f9ecfd )