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 )