#!/usr/bin/perl # # Tests for the rewrite of the scanner. To simplify testing, the tests are run against SQLite. use strict; use Config; my $SERVERDIR; my $ARTCOUNT; BEGIN { use FindBin qw($Bin); use File::Spec::Functions; my $perlmajorversion = $Config{version}; $perlmajorversion =~ s/\.\d+$//; my $arch = $Config::Config{archname}; $arch =~ s/^i[3456]86-/i386-/; $arch =~ s/gnu-//; my @SlimINC = ( "$Bin/../../server", "$Bin/../../server/lib", "$Bin/../../server/CPAN", "$Bin/../../server/CPAN/arch/${perlmajorversion}/${arch}", ); $SERVERDIR = catdir($Bin, '..', '..', 'server'); $ARTCOUNT = 6; if ( $^O =~ /linux/ && -d '/usr/squeezecenter' ) { # TinySC support @SlimINC = ( "/usr/squeezecenter", "/usr/squeezecenter/lib", "/usr/squeezecenter/CPAN", ); $SERVERDIR = '/usr/squeezecenter'; $ARTCOUNT = 4; } unshift @INC, @SlimINC; } use Data::Dump qw(dump); use DBD::SQLite 1.26; use File::Copy; use File::Path; use File::Next; use JSON::RPC::Client; use IO::Socket qw(:crlf); use IO::Socket::INET; use LWP::UserAgent; use Path::Class 0.17; use Proc::Background; use URI; use URI::file; use YAML::Syck; use Slim::Utils::ArtworkCache; use Test::More tests => 527; # Server background process, if running my $server; # Store all prefs, database, etc in a temp dir my $tmpdir = catdir($Bin, 'scanner-tmp'); rmtree $tmpdir if -d $tmpdir; mkdir $tmpdir; # Setup audiodir my $audiodir = catdir($Bin, '..', 'data', 'scanner', 'library'); $audiodir = Path::Class::Dir->new($audiodir)->resolve->stringify; # Untar library if necessary # XXX tarball is not portable to at least win32 my $scandir = catdir($Bin, '..', 'data', 'scanner'); if ( !-d catdir($scandir, 'library') ) { my $library = catfile($scandir, 'library.tar.gz'); system("tar -C $scandir -zxf $library"); } # Save initial default prefs _save_prefs(); _scan( qw(--wipe --rescan) ); # Cache object used to test that the Artwork cache is working my $cache = Slim::Utils::ArtworkCache->new($tmpdir); $cache->pragma('locking_mode = NORMAL'); # To test just playlists, uncomment this # goto PLAYLIST_TESTS; # To test just the server tests, uncomment this # goto SERVER_TESTS; ### Test new file scanning # Test simple album (FLAC, external cover art) my $original_coverid; { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Winter's Return" ] ); # Check basic album properties is( $album->{compilation}, 0, 'FLAC compilation is 0' ); is( $album->{disc}, undef, 'FLAC disc is undef' ); is( $album->{discc}, undef, 'FLAC discc is undef' ); is( $album->{musicbrainz_id}, '45e1d2c7-9427-412d-9a44-e3a6aa980c45', 'FLAC MB album ID ok' ); is( $album->{replay_gain}, '-7.96', 'FLAC album gain ok' ); is( $album->{replay_peak}, 1, 'FLAC album peak ok' ); is( $album->{titlesearch}, 'WINTER S RETURN', 'FLAC titlesearch ok' ); is( $album->{titlesort}, 'WINTER S RETURN', 'FLAC titlesort ok' ); is( $album->{year}, 1998, 'FLAC year ok' ); # Check album artwork points to last track from this album like( $album->{artwork}, qr/^[0-9a-f]{8}$/, 'FLAC album cover ok' ); my $track = _row( "SELECT * FROM tracks WHERE coverid = ?", [ $album->{artwork} ] ); like( $track->{cover}, qr/cover\.jpg$/, 'FLAC track cover path ok' ); # Check rest of track properties on a specific track $track = _row( "SELECT * FROM tracks WHERE album = ? AND tracknum = 10", [ $album->{id} ] ); like( $track->{urlmd5}, qr/^[0-9a-f]{32}$/, 'FLAC urlmd5 ok' ); is( $track->{audio_offset}, 0, 'FLAC offset is 0' ); is( $track->{content_type}, 'flc', 'FLAC track content-type ok' ); is( $track->{disc}, undef, 'FLAC track disc ok' ); is( $track->{musicbrainz_id}, 'b8968c0b-fa2e-4dca-bb45-848461549361', 'FLAC track MB ID ok' ); is( $track->{primary_artist}, undef, 'FLAC track primary artist ok' ); # supposed to be undef, it's a cache is( $track->{replay_gain}, '-7.59', 'FLAC track gain ok' ); is( $track->{replay_peak}, 1, 'FLAC track peak ok' ); is( $track->{title}, 'Arizona Sky', 'FLAC track title ok' ); is( $track->{year}, 1998, 'FLAC track year ok' ); like( $track->{cover}, qr/cover\.jpg$/, 'FLAC track cover path ok' ); like( $track->{coverid}, qr/^[0-9a-f]{8}$/, 'FLAC track coverid ok' ); # Save original coverid, to check later that changed coverid is different $original_coverid = $track->{coverid}; # Check album primary contributor my $contrib = _row( "SELECT * FROM contributors WHERE id = ?", [ $album->{contributor} ] ); is( $contrib->{name}, 'Gooding', 'FLAC primary contributor name ok' ); is( $contrib->{namesearch}, 'GOODING', 'FLAC primary contributor namesearch ok' ); is( $contrib->{namesort}, 'GOODING', 'FLAC primary contributor namesort ok' ); # Check many-to-many album contributors my $album_contribs = _rows( "SELECT * FROM contributor_album WHERE contributor = ? AND album = ? ORDER BY role", [ $album->{contributor}, $album->{id} ] ); is( $album_contribs->[0]->{role}, 5, 'FLAC contributor album role ALBUMARTIST ok' ); is( $album_contribs->[1]->{role}, 6, 'FLAC contributor album role TRACKARTIST ok' ); # Check track contributor my $track_contribs = _rows( "SELECT * FROM contributor_track WHERE track = ? ORDER BY role", [ $track->{id} ] ); is( $track_contribs->[0]->{role}, 5, 'FLAC contributor track role ALBUMARTIST ok' ); is( $track_contribs->[1]->{role}, 6, 'FLAC contributor track role TRACKARTIST ok' ); # Check track genre my $genre = _row( qq{ SELECT * FROM genres LEFT JOIN genre_track ON genre_track.genre = genres.id WHERE genre_track.track = ? }, [ $track->{id} ] ); is( $genre->{name}, 'Electronic', 'FLAC genre name ok' ); is( $genre->{namesearch}, 'ELECTRONIC', 'FLAC genre namesearch ok' ); is( $genre->{namesort}, 'ELECTRONIC', 'FLAC genre namesort ok' ); # Check years table my $year = _row( "SELECT * FROM years WHERE id = ?", [ $track->{year} ] ); is( $year->{id}, 1998, 'FLAC year table entry ok' ); # Check persistent data my $p = _row( "SELECT * FROM tracks_persistent WHERE url = ?", [ $track->{url} ], 'squeezebox-persistent.db' ); is( $p->{musicbrainz_id}, 'b8968c0b-fa2e-4dca-bb45-848461549361', 'FLAC persistent MB ID ok' ); like( $p->{added}, qr/^\d{10}$/, 'FLAC persistent added timestamp ok' ); is( $p->{urlmd5}, $track->{urlmd5}, 'FLAC persistent urlmd5 ok' ); # Check that all tracks on this album have the same coverid and cover_cached values my $t = _rows( "SELECT coverid, cover_cached FROM tracks WHERE album = ?", [ $track->{album} ] ); is( scalar( grep { $_->{cover_cached} == 1 } @{$t} ), 10, 'FLAC cover_cached set for all tracks ok' ); is( scalar( grep { $_->{coverid} eq $track->{coverid} } @{$t} ), 10, 'FLAC same coverid set for all tracks ok' ); # Check contents of the artwork cache my $coverid = $track->{coverid}; my $art = $cache->get("music/$coverid/cover_75x75_p"); like( $art->{original_path}, qr/cover\.jpg$/, 'FLAC artwork cache orig ok' ); is( $art->{content_type}, 'jpg', 'FLAC artwork cache content-type ok' ); my $mtime = (stat $art->{original_path})[9]; is( $art->{mtime}, $mtime, 'FLAC artwork cache mtime ok' ); } # Test album that was being entered as 1 different album per track # because of TCMP=0 { my $count = _row( "SELECT COUNT(*) AS c FROM albums WHERE title = ?", [ "133 Thursdays" ] ); is( $count->{c}, 1, '133 Thursdays correctly added as 1 album' ); } # Test album that was incorrectly marked as a compilation because it had # multiple artists but COMPILATION=0 { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'Legend of the Black Shawarma' ] ); is( $album->{compilation}, 0, 'Multiple-artist album with COMPILATION=0 is not a compilation' ); # Check that the album has multiple contribs my $album_contribs = _rows( "SELECT DISTINCT(contributor) FROM contributor_album WHERE album = ?", [ $album->{id} ] ); is( scalar @{$album_contribs}, 2, 'Multiple-artist album has multiple contributors ok' ); } # Test album that has multiple artists that are the same on each track, it should not be marked # as a compilation { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'Emerald' ] ); is( $album->{compilation}, 0, 'Multiple-artist album with all same artists is not a compilation' ); # Check that the album has multiple contribs my $album_contribs = _rows( "SELECT DISTINCT(contributor) FROM contributor_album WHERE album = ?", [ $album->{id} ] ); is( scalar @{$album_contribs}, 3, 'Multiple-artist album with all same artists has multiple contributors ok' ); } # Test VA album { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Mirror's Edge" ] ); is( $album->{compilation}, 1, 'VA album is a compilation' ); is( $album->{disc}, 1, 'VA album disc ok' ); is( $album->{discc}, 1, 'VA album discc ok' ); # Check artwork is found as embedded my $track = _row( "SELECT * FROM tracks WHERE coverid = ?", [ $album->{artwork} ] ); is( $track->{cover}, 46240, 'VA album artwork ok' ); # 46240 is the size of the artwork (before unsync, but that's ok) like( $track->{coverid}, qr/^[0-9a-f]{8}$/, 'VA album coverid ok' ); # Check album primary contributor is Various Artists my $primary_contrib = _row( "SELECT * FROM contributors WHERE id = ?", [ $album->{contributor} ] ); is( $primary_contrib->{name}, 'Various Artists', 'VA album primary contrib is Various Artists' ); # Check album contributors my $album_contribs = _rows( qq{ SELECT * FROM contributor_album LEFT JOIN contributors ON contributor_album.contributor = contributors.id WHERE album = ? ORDER BY role, contributor }, [ $album->{id} ] ); is( $album_contribs->[0]->{name}, 'Solar Fields', 'VA album contrib 1 ok' ); is( $album_contribs->[1]->{name}, 'Lisa Miskovsky', 'VA album contrib 2 ok' ); is( $album_contribs->[2]->{name}, 'Various Artists', 'VA album contrib 3 ok' ); # Check track 1 is only by Solar Fields my $track1_contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track = ( SELECT id FROM tracks WHERE album = ? AND tracknum = 1 ) ORDER BY name }, [ $album->{id} ] ); is( scalar @{$track1_contribs}, 2, 'VA track 1 contrib total is 2' ); is( $track1_contribs->[0]->{name}, 'Solar Fields', 'VA track 1 contrib 1 ok' ); is( $track1_contribs->[1]->{name}, 'Various Artists', 'VA track 1 contrib 2 ok' ); # Check track 12 is only by Lisa Miskovsky my $track12_contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track = ( SELECT id FROM tracks WHERE album = ? AND tracknum = 12 ) ORDER BY name }, [ $album->{id} ] ); is( scalar @{$track12_contribs}, 2, 'VA track 12 contrib total is 2' ); is( $track12_contribs->[0]->{name}, 'Lisa Miskovsky', 'VA track 12 contrib 1 ok' ); is( $track12_contribs->[1]->{name}, 'Various Artists', 'VA track 12 contrib 2 ok' ); # Check contents of the artwork cache my $coverid = $track->{coverid}; my $art = $cache->get("music/$coverid/cover_75x75_p"); like( $art->{original_path}, qr/mp3$/, 'VA artwork cache orig ok' ); # Artwork could be jpeg or png depending on which resized version we get like( $art->{content_type}, qr{^(png|jpg)$}, 'VA artwork cache content-type ok' ); my $mtime = (stat $art->{original_path})[9]; is( $art->{mtime}, $mtime, 'VA artwork cache mtime ok' ); } # Test VA album with COMPILATION=1 and some bad Album Artist tags { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "The Narada Wilderness Collection" ] ); is( $album->{compilation}, 1, 'VA bad AA compilation ok' ); # Check album primary contributor is Various Artists # This test is no longer valid, the correct behavior is to give this album a primary artist # based on Album Artist #my $primary_contrib = _row( "SELECT * FROM contributors WHERE id = ?", [ $album->{contributor} ] ); #is( $primary_contrib->{name}, 'Various Artists', 'VA bad AA album primary contrib is Various Artists' ); } # Test Unicode paths and tags { use utf8; my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Link" ] ); is( $album->{year}, 1993, 'Unicode path album ok' ); my $track = _row( "SELECT * FROM tracks WHERE album = ? LIMIT 1", [ $album->{id} ] ); like( $track->{url}, qr/%C3%98ystein/, 'Unicode track URL ok' ); my $contrib = _row( "SELECT * FROM contributors WHERE id = ?", [ $album->{contributor} ] ); utf8::decode( $contrib->{name} ); utf8::decode( $contrib->{namesearch} ); utf8::decode( $contrib->{namesort} ); is( $contrib->{name}, "Øystein Sevåg", 'Unicode contrib name ok' ); is( $contrib->{namesearch}, uc("Øystein Sevåg"), 'Unicode contrib namesearch ok' ); is( $contrib->{namesort}, uc("Sevåg Øystein"), 'Unicode contrib namesort ok' ); } # Test guess tags { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Wild Earth" ] ); is( $album->{title}, 'Wild Earth', 'Guess tags album title ok' ); my $track = _row( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); is( $track->{title}, 'Wild Earth', 'Guess tags track title ok' ); is( $track->{tracknum}, 1, 'Guess tags tracknum ok' ); my $contrib = _row( "SELECT * FROM contributors WHERE id = ?", [ $album->{contributor} ] ); utf8::decode( $contrib->{name} ); if ( $^O =~ /darwin/ ) { # On HFS+ filesystems this path is stored with decomposed UTF-8 like( $track->{url}, qr/Bu%CC%88di%20Siebert/, 'Guess tags URL ok' ); is( $contrib->{name}, "Bu\x{308}di Siebert", 'Guess tags contributor ok' ); } else { like( $track->{url}, qr/B%C3%BCdi%20Siebert/, 'Guess tags URL ok' ); is( $contrib->{name}, "B\x{FC}di Siebert", 'Guess tags contributor ok' ); } } # Test Moonbase's files, various UTF-8 with some decomposition on HFS { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'SqueezeCenter Bug 9772' ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ? ORDER BY tracknum", [ $album->{id} ] ); is( scalar @{$tracks}, 12, 'Moonbase track count ok' ); # Verify track URLs match the bytes on disk, which may vary based on the OS my @urls; opendir DIR, catdir($audiodir, 'Moonbase'); while ( my $file = readdir DIR ) { next unless $file =~ /\.mp3$/i; push @urls, _url_from_path( catfile($audiodir, 'Moonbase', $file) ); } closedir DIR; my $i = 0; for my $url ( @urls ) { is( $tracks->[$i]->{url}, $urls[$i], "Moonbase track $i URL ok" ); $i++; } } # Test No Artist { my $contrib = _row( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track = ( SELECT id FROM tracks WHERE title = ? ) }, [ "Zero Gravity (original)" ] ); is( $contrib->{name}, 'No Artist', 'No Artist ok' ); is( $contrib->{role}, 1, 'No Artist role ok' ); } # Test No Album, No Genre { my $track = _row( "SELECT * FROM tracks WHERE title = ?", [ "Invisible Light" ] ); my $album = _row( "SELECT * FROM albums WHERE id = ?", [ $track->{album} ] ); is( $album->{artwork}, undef, 'No Album has no artwork' ); is( $album->{title}, 'No Album', 'No Album title ok' ); is( $album->{year}, 0, 'No Album year ok' ); my $genres = _row( qq{ SELECT * FROM genres LEFT JOIN genre_track ON genre_track.genre = genres.id WHERE genre_track.track = ? }, [ $track->{id} ] ); is( $genres->{name}, 'No Genre', 'No Genre ok' ); } # XXX Test no tags with no guess tags and UTF-8 filenames: http://forums.slimdevices.com/showthread.php?t=70929 # XXX Test Greatest Hits issues, to test matchTrack handling in create album # Bug 4361, 8214 =pod XXX From Phil Meyer: Same two artists on every song: ARTIST: Markus Reuter ARTIST: Robert Rich When Browsing Artists, the album should appear under both artists, but it doesn't - it only appears under Various Artists. -- ARTIST=Jimmy Page ARTIST=Robert Plant BAND=Page & Plant (where BAND is id3v2.4 TPE2 frame) I normally use TPE2 to represent BAND, not ALBUM ARTIST. I normally include artists and bands in the Browse Artist list. When I look at a Song Info, I see the two Artists and the Band/Orchestra listed for the song, and no Album Artist. However, the album is visible under Browse Artists > Page & Plant (i.e. the band) and Browse Artists > Various Artists (i.e. thinks it is a compilation), and not under Jimmy Page or Robert Plant. So this doesn't work too well. =cut ### Test deleted file handling { # Disable mp3 extension and rescan # Also disables M3U playlist scanning so the playlists don't re-add the mp3 tracks # XXX if mp3 is disabled, they shouldn't be added even if found in a playlist _save_prefs( { disabledextensionsaudio => 'mp3', disabledextensionsplaylist => 'm3u,m3u8,pls,cue', } ); _scan('--rescan'); # Check that everything relating to the MP3 album was removed my $count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE url LIKE '%mp3' AND content_type = 'mp3'" ); is( $count->{c}, 0, 'MP3 album tracks removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM albums WHERE title = ?", [ "Mirror's Edge" ] ); is( $count->{c}, 0, 'MP3 album removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM genres WHERE name = ?", [ 'Ambient' ] ); is( $count->{c}, 0, 'MP3 album genre was removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Solar Fields' ] ); is( $count->{c}, 0, 'MP3 album contrib 1 was removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Lisa Miskovsky' ] ); is( $count->{c}, 0, 'MP3 album contrib 2 was removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM years WHERE id = 2009" ); is( $count->{c}, 0, 'MP3 album year was removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM comments WHERE value LIKE '%Amazon%'" ); is( $count->{c}, 0, 'MP3 album comments removed ok' ); # XXX Make sure MP3 virtual tracks added from a cue sheet were removed # because the base mp3 file was removed? #$count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE url LIKE '%mp3#%'" ); #is( $count->{c}, 0, 'MP3 virtual tracks removed ok' ); # Make sure FLAC virtual tracks added from a cue sheet were not deleted because # they weren't found on disk $count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE url LIKE '%flac#%'" ); is( $count->{c}, 70, 'FLAC virtual tracks were not removed' ); } # Bug 10636, test deleting a flac with embedded cue sheet { # XXX if flac is disabled, they shouldn't be added even if found in a playlist _save_prefs( { disabledextensionsaudio => 'flac', disabledextensionsplaylist => 'm3u,m3u8,pls,cue', } ); _scan('--rescan'); # Check that everything related to the FLAC album was removed my $count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE content_type = 'flc'" ); is( $count->{c}, 0, 'FLAC+CUE removed all tracks ok' ); $count = _row( "SELECT COUNT(*) AS c FROM albums WHERE title = ?", [ "O gemma, lux - Huelgas Ensemble" ] ); is( $count->{c}, 0, 'FLAC+CUE album removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM genres WHERE name = ?", [ 'A_Medieval' ] ); is( $count->{c}, 0, 'FLAC+CUE album genre was removed ok' ); $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Du Fay, G' ] ); is( $count->{c}, 0, 'FLAC+CUE album contrib was removed ok' ); } ### Test changed file handling { # Rescan everything _save_prefs(); _scan('--rescan'); # Copy in a changed file, this file has the following changes: # Added embedded cover art # Track title # Date # Genre # Track Number # Added comment # Track Gain # Album Gain my $file = catfile($Bin, '..', 'data', 'scanner', 'library', 'Gooding', "Winter's Return", '10 - Arizona Sky.flac'); _backup($file); my $changed = catfile($Bin, '..', 'data', 'scanner', 'changes', '10 - Arizona Sky.flac'); File::Copy::copy( $changed, $file ) or die "Unable to copy $changed to $file: $!"; # Rescan for changes _scan('--rescan'); my $track = _row( "SELECT * FROM tracks WHERE title = ?", [ 'Arizona Sky Changed' ] ); # Check cover value changed from a path to cover.jpg to 1 for embedded is( $track->{cover}, 58416, 'Changed embedded cover ok' ); like( $track->{coverid}, qr/^[0-9a-f]{8}$/, 'Changed embedded coverid ok' ); # Check coverid has changed from previous coverid isnt( $track->{coverid}, $original_coverid, 'Changed embedded coverid does not match original coverid' ); # Check rest of changed track properties is( $track->{year}, 1989, 'Changed year ok' ); is( $track->{tracknum}, 20, 'Changed tracknum ok' ); is( $track->{replay_gain}, '-1.23', 'Changed track gain ok' ); # Check album properties are correctly changed my $album = _row( "SELECT * FROM albums WHERE id = ?", [ $track->{album} ] ); is( $album->{replay_gain}, '-1.23', 'Changed album gain ok' ); is( $album->{year}, 1989, 'Changed album year ok' ); # Check genre my $genres = _rows( qq{ SELECT * FROM genres LEFT JOIN genre_track ON genre_track.genre = genres.id WHERE genre_track.track = ? }, [ $track->{id} ] ); is( scalar @{$genres}, 1, 'Changed genre ok' ); is( $genres->[0]->{name}, 'Instrumental', 'Changed genre name ok' ); # Check comments my $comment = _row( "SELECT * FROM comments WHERE track = ?", [ $track->{id} ] ); is( $comment->{value}, 'New Comment', 'Changed comment ok' ); # Check album artwork has been changed to point to newest track is( $album->{artwork}, $track->{coverid}, 'Changed artwork also changed the album artwork ok' ); # Verify newly found embedded artwork has been cached my $coverid = $track->{coverid}; my $art = $cache->get("music/$coverid/cover_75x75_p"); like( $art->{original_path}, qr/Arizona\sSky\.flac$/, 'Changed artwork cache orig ok' ); is( $art->{content_type}, 'jpg', 'Changed artwork cache content-type ok' ); my $mtime = (stat $art->{original_path})[9]; is( $art->{mtime}, $mtime, 'Changed artwork cache mtime ok' ); } ### Bug 9938, VA <-> non-VA changes # Test changing a VA -> non-VA album by deleting files # Test changing a non-VA album to VA by adding a track { my @files = ( '11 - Lisa Miskovsky - Still Alive.mp3', '12 - Lisa Miskovsky - Still Alive (instrumental).mp3', ); for my $file ( @files ) { _backup( catfile($Bin, '..', 'data', 'scanner', 'library', 'Various Artists', "Mirror's Edge", $file) ); } _scan('--rescan'); # Check that compilation was switched off for this album my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Mirror's Edge" ] ); is( $album->{compilation}, 0, 'VA -> non-VA rescan via deletion ok' ); # Check that Lisa was removed as a contributor my $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Lisa Miskovsky' ] ); is( $count->{c}, 0, 'VA -> non-VA via deletion contrib was removed ok' ); # Restore files and rescan for my $file ( @files ) { _restore( catfile($Bin, '..', 'data', 'scanner', 'library', 'Various Artists', "Mirror's Edge", "$file.bak") ); } _scan('--rescan'); # Check that compilation was switched on for this album my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Mirror's Edge" ] ); is( $album->{compilation}, 1, 'Non-VA -> VA via new rescan ok' ); # Check that Lisa was added as a contributor my $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Lisa Miskovsky' ] ); is( $count->{c}, 1, 'Non-VA -> VA via new contrib was added ok' ); } # Test changing VA <-> non-VA by changing a file's tags, also tests that # the contributor is properly removed when rescanning via changed() { my @files = ( '11 - Lisa Miskovsky - Still Alive.mp3', '12 - Lisa Miskovsky - Still Alive (instrumental).mp3', ); for my $file ( @files ) { my $copy = catfile($Bin, '..', 'data', 'scanner', 'changes', $file); my $orig = catfile($Bin, '..', 'data', 'scanner', 'library', 'Various Artists', "Mirror's Edge", $file); _backup($orig); File::Copy::copy( $copy, $orig ) or die "Cannot copy $copy -> $orig: $!"; } _scan('--rescan'); # Check that compilation was switched off for this album my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Mirror's Edge" ] ); is( $album->{compilation}, 0, 'VA -> non-VA rescan via changed ok' ); # Check that Lisa was removed as a contributor my $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Lisa Miskovsky' ] ); is( $count->{c}, 0, 'VA -> non-VA via changed contrib was removed ok' ); # Restore files and rescan for my $file ( @files ) { _restore( catfile($Bin, '..', 'data', 'scanner', 'library', 'Various Artists', "Mirror's Edge", "$file.bak") ); } _scan('--rescan'); # Check that compilation was switched on for this album my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Mirror's Edge" ] ); is( $album->{compilation}, 1, 'Non-VA -> VA via changed rescan ok' ); # Check that Lisa was added as a contributor my $count = _row( "SELECT COUNT(*) AS c FROM contributors WHERE name = ?", [ 'Lisa Miskovsky' ] ); is( $count->{c}, 1, 'Non-VA -> VA via changed contrib was added ok' ); } # XXX Test changing disc/discc values as this will create a split album # XXX Test changing a flac file with embedded cue sheet # XXX Test changing from no genre to a genre, does not seem to apply genre correctly # XXX Test music folder entries, they are being deleted ### Artwork changes # XXX Test removing embedded artwork, replacing with cover.jpg # Test artwork filename/folder prefs { my $artfolder = catdir($Bin, '..', 'data', 'scanner', 'artwork'); $artfolder = Path::Class::Dir->new($artfolder)->resolve->stringify; _save_prefs( { coverArt => '%ARTIST - ALBUM', artfolder => $artfolder, } ); _scan( qw(--wipe --rescan) ); my $track = _row( "SELECT * FROM tracks WHERE title = ?", [ 'Cathedrals' ] ); like( $track->{cover}, qr{artwork/Gooding - Winter's Return.jpg$}, 'Artwork filename/folder pref ok' ); # Reset prefs _save_prefs(); } ### Playlist tests # New playlist scanning # Start fresh _scan( qw(--wipe --rescan) ); PLAYLIST_TESTS: { # Test ASCII M3U playlist my $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%m3u'", [ 'ascii' ] ); is( $playlist->{content_type}, 'ssp', 'ASCII M3U content-type is ssp' ); is( $playlist->{musicmagic_mixable}, 1, 'ASCII M3U is mixable' ); is( $playlist->{year}, undef, 'ASCII M3U year is undef' ); my $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Winter%27s%20Return/, 'ASCII M3U track 1 ok' ); like( $tracks->[1]->{track}, qr/No%20Artist/, 'ASCII M3U track 2 ok' ); like( $tracks->[2]->{track}, qr/133%20Thursdays/, 'ASCII M3U track 3 ok' ); # Test UTF-8 M3U playlist # This playlist also has a decomposed path: Seva%CC%8A which should be properly recomposed to Sev%C3%A5 $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%m3u'", [ 'utf8' ] ); is( $playlist->{content_type}, 'ssp', 'UTF-8 M3U content-type is ssp' ); $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Winter%27s%20Return/, 'UTF-8 M3U track 1 ok' ); like( $tracks->[1]->{track}, qr/133%20Thursdays/, 'UTF-8 M3U track 2 ok' ); like( $tracks->[2]->{track}, qr/Mirror%27s%20Edge/, 'UTF-8 M3U track 3 ok' ); SKIP: { skip 'UTF-8 M3U entry does not work on OSX', 1 if $^O =~ /darwin/; like( $tracks->[3]->{track}, qr/%C3%98ystein%20Sev%C3%A5g/, 'UTF-8 M3U track 4 ok' ); } # Test CP1252 M3U playlist with EXTM3U data $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%m3u'", [ 'ext-cp1252' ] ); is( $playlist->{content_type}, 'ssp', 'CP1252 M3U content-type is ssp' ); $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Invisible%20Light/, 'CP1252 M3U track 1 ok' ); like( $tracks->[1]->{track}, qr/B%FCdi%20Siebert/, 'CP1252 M3U track 2 ok' ); SKIP: { # Test UTF-8 M3U8 playlist # This playlist does not work on OSX because the filesystem uses decomposed chars skip 'UTF-8 M3U8 playlist does not work on OSX', 14 if $^O =~ /darwin/; $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%m3u8'", [ 'moonbase' ] ); is( $playlist->{content_type}, 'ssp', 'UTF-8 M3U8 content-type is ssp' ); $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); is( scalar @{$tracks}, 12, 'UTF-8 M3U8 playlist has 12 tracks' ); # Verify track URLs match the bytes on disk, which may vary based on the OS my @urls; opendir DIR, catdir($audiodir, 'Moonbase'); while ( my $file = readdir DIR ) { next unless $file =~ /\.mp3$/i; push @urls, _url_from_path( catfile($audiodir, 'Moonbase', $file) ); } closedir DIR; my $i = 0; for my $url ( @urls ) { is( $tracks->[$i]->{track}, $urls[$i], "UTF-8 M3U8 track $i URL ok" ); $i++; } } } { # Test ASCII PLS playlist my $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%pls'", [ 'ascii' ] ); is( $playlist->{content_type}, 'ssp', 'ASCII PLS content-type is ssp' ); is( $playlist->{musicmagic_mixable}, 1, 'ASCII PLS is mixable' ); is( $playlist->{year}, undef, 'ASCII PLS year is undef' ); my $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Winter%27s%20Return/, 'ASCII PLS track 1 ok' ); like( $tracks->[1]->{track}, qr/No%20Artist/, 'ASCII PLS track 2 ok' ); like( $tracks->[2]->{track}, qr/133%20Thursdays/, 'ASCII PLS track 3 ok' ); # Test UTF-8 PLS playlist # This playlist also has a decomposed path: Seva%CC%8A which should be properly recomposed to Sev%C3%A5 $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%pls'", [ 'utf8' ] ); is( $playlist->{content_type}, 'ssp', 'UTF-8 PLS content-type is ssp' ); $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Winter%27s%20Return/, 'UTF-8 PLS track 1 ok' ); like( $tracks->[1]->{track}, qr/133%20Thursdays/, 'UTF-8 PLS track 2 ok' ); like( $tracks->[2]->{track}, qr/Mirror%27s%20Edge/, 'UTF-8 PLS track 3 ok' ); SKIP: { skip 'UTF-8 PLS entry does not work on OSX', 1 if $^O =~ /darwin/; like( $tracks->[3]->{track}, qr/%C3%98ystein%20Sev%C3%A5g/, 'UTF-8 PLS track 4 ok' ); } # Test CP1252 PLS playlist $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%pls'", [ 'cp1252' ] ); is( $playlist->{content_type}, 'ssp', 'CP1252 PLS content-type is ssp' ); $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Invisible%20Light/, 'CP1252 PLS track 1 ok' ); like( $tracks->[1]->{track}, qr/B%FCdi%20Siebert/, 'CP1252 PLS track 2 converted to UTF-8 ok' ); } # XXX Other playlist types: asx, xspf, wpl # XXX cue with UTF-8 BOM, bug 11289 # XXX cue with ARTISTSORT, ALBUMSORT, COMPILATION, bug 10027 # XXX cue with FILE item encoded in ISO-8859-1, bug 12674 # Test standalone mp3 + cue, a mix set with various artists { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'Proton Radio Mix (2003-07-30) CUE' ] ); is( $album->{year}, 2003, 'MP3+CUE album year ok' ); is( $album->{compilation}, 1, 'MP3+CUE compilation ok' ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); is( scalar @{$tracks}, 14, 'MP3+CUE track count ok' ); # Check some track data is( $tracks->[0]->{title}, 'Intro (Waking Life sample)', 'MP3+CUE track 1 title ok' ); is( $tracks->[0]->{titlesearch}, 'INTRO WAKING LIFE SAMPLE', 'MP3+CUE track 1 titlesearch ok' ); is( $tracks->[0]->{titlesort}, 'INTRO WAKING LIFE SAMPLE', 'MP3+CUE track 1 titlesort ok' ); is( $tracks->[0]->{tracknum}, 1, 'MP3+CUE track 1 tracknum ok' ); is( $tracks->[0]->{year}, 2003, 'MP3+CUE track 1 year ok' ); is( $tracks->[0]->{secs}, 60, 'MP3+CUE track 1 secs ok' ); # Check comments from cue sheet my $comments = _row( "SELECT COUNT(*) AS c FROM comments WHERE value = ?", [ 'Test CUE Comment' ] ); is( $comments->{c}, 14, 'MP3+CUE comments ok' ); # Check cue file entry my $cue = _row( "SELECT * FROM tracks WHERE title = ?", [ 'Digital Witchcraft - Proton Radio Mix (2003-07-30)' ] ); is( $cue->{content_type}, 'cue', 'MP3+CUE cue entry content-type is cue' ); # Check playlist_track entries my $pt = _rows( "SELECT * FROM playlist_track WHERE playlist = ?", [ $cue->{id} ] ); is( scalar @{$pt}, 14, 'MP3+CUE playlist_track entries ok' ); # Check track genre my $genre = _row( qq{ SELECT * FROM genres LEFT JOIN genre_track ON genre_track.genre = genres.id WHERE genre_track.track = ? }, [ $tracks->[0]->{id} ] ); is( $genre->{name}, 'DJ Mix', 'MP3+CUE genre is DJ Mix' ); # Check album contributor is VA my $album_contrib = _row( "SELECT * FROM contributors WHERE id = ?", [ $album->{contributor} ] ); is( $album_contrib->{name}, 'Various Artists', 'MP3+CUE album contributor is Various Artists' ); # Check track contributors my $contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track IN (?, ?, ?) ORDER BY track, role }, [ $tracks->[0]->{id}, $tracks->[1]->{id}, $tracks->[2]->{id} ] ); is( $contribs->[0]->{name}, 'Digital Witchcraft', 'MP3+CUE contrib 1 ok' ); is( $contribs->[1]->{name}, 'Peter Benisch', 'MP3+CUE contrib 2 ok' ); is( $contribs->[2]->{name}, 'Icehouse', 'MP3+CUE contrib 3 ok' ); # Check folder.jpg artwork was found and linked for all virtual tracks my $covers = _rows( "SELECT cover, coverid, cover_cached FROM tracks WHERE album = ? AND coverid IS NOT NULL", [ $album->{id} ] ); is ( scalar @{$covers}, 14, 'MP3+CUE cover ok for all 14 virtual tracks' ); like( $covers->[0]->{cover}, qr{/Digital Witchcraft/folder.jpg$}, 'MP3+CUE cover ok for virtual tracks' ); is( $covers->[0]->{cover_cached}, 1, 'MP3+CUE cover_cached ok for virtual tracks' ); # Check album artwork is set is( $album->{artwork}, $covers->[0]->{coverid}, 'MP3+CUE album artwork ok' ); # Check original mp3 file entry, it should be marked as a 'cur' (cue referenced) type # It should also have no album, genre, contributors, comments, cover, etc my $mp3 = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%mp3'", [ 'Proton Radio Mix (2003-07-30)' ] ); is( $mp3->{content_type}, 'cur', 'MP3+CUE mp3 entry content-type is mp3' ); is( $mp3->{album}, undef, 'MP3+CUE mp3 entry has undef album' ); is( $mp3->{cover}, undef, 'MP3+CUE mp3 entry has no cover' ); is( $mp3->{coverid}, undef, 'MP3+CUE mp3 entry has no coverid' ); my $count = _row( "SELECT COUNT(*) AS c FROM genre_track WHERE track = ?", [ $mp3->{id} ] ); is( $count->{c}, 0, 'MP3+CUE mp3 entry has no genre' ); $count = _row( "SELECT COUNT(*) AS c FROM contributor_track WHERE track = ?", [ $mp3->{id} ] ); is( $count->{c}, 0, 'MP3+CUE mp3 entry has no contributors' ); $count = _row( "SELECT COUNT(*) AS c FROM comments WHERE track = ?", [ $mp3->{id} ] ); is( $count->{c}, 0, 'MP3+CUE mp3 entry has no comments' ); } # FLAC+CUE, UTF-8 issues, bug 14386 (from gharris) { # Test FLAC+CUE, only ASCII path/filename # Has UTF-8 text within the cue sheet # The original file my $file = _row( "SELECT * FROM tracks WHERE title = ?", [ 'O gemma, lux - Huelgas Ensemble' ] ); is( $file->{content_type}, 'fec', 'ASCII FLAC+CUE original file content-type is fec' ); like( $file->{url}, qr{Du%20Fay,%20G/O%20gemma,%20lux%20-%20Huelgas%20Ensemble.flac$}, 'ASCII FLAC+CUE original file url ok' ); # The CUE album/tracks my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'O gemma, lux - Huelgas Ensemble' ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); is( scalar @{$tracks}, 13, 'ASCII FLAC+CUE has 13 tracks' ); # Compare some of the tracks in the cue use utf8; my $ref = { 0 => { title => "Vasilissa Ergo Gaude (Triplum & motetus), à 4 voix, 1420", url => qr/O%20gemma,%20lux%20-%20Huelgas%20Ensemble.flac#0-1$/, }, 1 => { title => "O Sancte Sebastiane (Triplum) - O martyr Sebastiane (Motetus) ) quam mira (contratenor), à 4 voix, c. 1437", url => qr/O%20gemma,%20lux%20-%20Huelgas%20Ensemble.flac#1-2$/, }, 12 => { title => "Moribus Et Genere (Triplum - Virgo, virga virens (Motetus), à 4 voix, années, ? 1442", url => qr/O%20gemma,%20lux%20-%20Huelgas%20Ensemble.flac#12-25.986$/, }, }; my $i = 0; for my $track ( @{$tracks} ) { if ( $ref->{$i} ) { utf8::decode( $track->{title} ); is( $track->{title}, $ref->{$i}->{title}, "ASCII FLAC+CUE track $i ok" ); like( $track->{url}, $ref->{$i}->{url}, "ASCII FLAC+CUE url $i ok" ); } $i++; } # Check year is 2000 is( $album->{year}, 2000, 'ASCII FLAC+CUE album year ok' ); is( $tracks->[0]->{year}, 2000, 'ASCII FLAC+CUE track year ok' ); # Check genre of tracks is "A_Medieval" (in file it is a_Medieval but it's ucfirst()'ed) my $genre = _row( qq{ SELECT * FROM genres LEFT JOIN genre_track ON genre_track.genre = genres.id WHERE genre_track.track = ? }, [ $tracks->[0]->{id} ] ); is( $genre->{name}, "A_Medieval", "ASCII FLAC+CUE genre ok" ); # Check performer and composer are set to "Du Fay, G" my $contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track = ? ORDER BY role }, [ $tracks->[0]->{id} ] ); is( $contribs->[0]->{name}, 'Du Fay, G', 'ASCII FLAC+CUE artist name ok' ); is( $contribs->[0]->{role}, 1, 'ASCII FLAC+CUE artist role ok' ); is( $contribs->[1]->{name}, 'Du Fay, G', 'ASCII FLAC+CUE composer name ok' ); is( $contribs->[1]->{role}, 2, 'ASCII FLAC+CUE composer role ok' ); } { # Test FLAC+CUE, ASCII path, UTF-8 filename use utf8; # Get the actual filename, as it may differ on some OS's my $url; my $dir = catdir($audiodir, 'gharris_flac_cue_bug14386', 'a_Medieval', 'Du Fay, G'); opendir DIR, $dir; while ( my $file = readdir DIR ) { next unless $file =~ /^L'Arbre/; $url = _url_from_path( catfile($dir, $file) ); } closedir DIR; # The original file my $file = _row( "SELECT * FROM tracks WHERE title = ?", [ "L'Arbre de Mai - Chansons & dances au temps de Guillaume Dufay - Allégorie" ] ); is( $file->{content_type}, 'fec', 'UTF-8 filename FLAC+CUE original file content-type is fec' ); is( $file->{url}, $url, 'UTF-8 filename FLAC+CUE original file url ok' ); my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "L'Arbre de Mai - Chansons & dances au temps de Guillaume Dufay - Allégorie" ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); # Compare some of the tracks in the cue my $ref = { 0 => { title => "L'Amour & le jeunesse - 1 Amoroso [Manuscrit de Marguerite d'Autriche]", url => qr/^$url#0-1$/, }, 1 => { title => "  2 Le grand désir", # 2 chars here are NO-BREAK SPACE (C2 A0) url => qr/^$url#1-2$/, }, }; my $i = 0; for my $track ( @{$tracks} ) { if ( $ref->{$i} ) { utf8::decode( $track->{title} ); is( $track->{title}, $ref->{$i}->{title}, "UTF-8 filename FLAC+CUE track $i ok" ); like( $track->{url}, $ref->{$i}->{url}, "UTF-8 filename FLAC+CUE url $i ok" ); } $i++; } } { # Test FLAC+CUE, UTF-8 path, ASCII filename # This caused double-encoding of the path (was Cabez%C3%83%C2%B3n,%20A instead of Cabez%C3%B3n,%20A) use utf8; # Get the actual directory/filename, as it may differ on some OS's my $url; my $dir = catdir($audiodir, 'gharris_flac_cue_bug14386', 'b_Renaissance'); opendir DIR, $dir; my $file = readdir DIR; while ( my $file = readdir DIR ) { next unless $file =~ /^Cabez/; $dir = catdir($dir, $file); } closedir DIR; opendir DIR, $dir; while ( my $file = readdir DIR ) { next unless $file =~ /^Tientos/; $url = _url_from_path( catfile($dir, $file) ); } closedir DIR; # The original file my $file = _row( "SELECT * FROM tracks WHERE title = ?", [ "Tientos & Glosados - Ensemble Accentus, Thomas Wimmer" ] ); is( $file->{content_type}, 'fec', 'UTF-8 path FLAC+CUE original file content-type is fec' ); is( $file->{url}, $url, 'UTF-8 path FLAC+CUE original file url ok' ); my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Tientos & Glosados - Ensemble Accentus, Thomas Wimmer" ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); # Compare some of the tracks in the cue use utf8; my $ref = { 0 => { title => "Diferencias sobre la Gallarda Milanesa", url => qr{^$url#0-1$}, }, 1 => { title => "Tiento I", url => qr{^$url#1-2}, }, }; my $i = 0; for my $track ( @{$tracks} ) { if ( $ref->{$i} ) { utf8::decode( $track->{title} ); is( $track->{title}, $ref->{$i}->{title}, "UTF-8 path FLAC+CUE track $i ok" ); like( $track->{url}, $ref->{$i}->{url}, "UTF-8 path FLAC+CUE url $i ok" ); } $i++; } # check contributors are correct my $contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track = ? ORDER BY role }, [ $tracks->[0]->{id} ] ); utf8::decode( $contribs->[0]->{name} ); utf8::decode( $contribs->[1]->{name} ); is( $contribs->[0]->{name}, 'Cabezón, A', 'UTF-8 path FLAC+CUE artist name ok' ); is( $contribs->[0]->{role}, 1, 'UTF-8 path FLAC+CUE artist role ok' ); is( $contribs->[1]->{name}, 'Cabezón, A', 'UTF-8 path FLAC+CUE composer name ok' ); is( $contribs->[1]->{role}, 2, 'UTF-8 path FLAC+CUE composer role ok' ); # Check embedded artwork was found and linked for all virtual tracks my $covers = _rows( "SELECT cover, coverid, cover_cached FROM tracks WHERE album = ? AND coverid IS NOT NULL", [ $album->{id} ] ); is ( scalar @{$covers}, 20, 'UTF-8 path FLAC+CUE cover ok for all 20 virtual tracks' ); is( $covers->[0]->{cover}, 44899, 'UTF-8 path FLAC+CUE cover ok for virtual tracks' ); is( $covers->[0]->{cover_cached}, 1, 'UTF-8 path FLAC+CUE cover_cached ok for virtual tracks' ); # Check album artwork is set is( $album->{artwork}, $covers->[0]->{coverid}, 'UTF-8 path FLAC+CUE album artwork ok' ); } { # Test FLAC+CUE, UTF-8 path, UTF-8 filename # This also caused double-encoding of the path, but not the filename use utf8; # Get the actual directory/filename, as it may differ on some OS's my $url; my $dir = catdir($audiodir, 'gharris_flac_cue_bug14386', 'b_Renaissance'); opendir DIR, $dir; my $file = readdir DIR; while ( my $file = readdir DIR ) { next unless $file =~ /^Cabez/; $dir = catdir($dir, $file); } closedir DIR; opendir DIR, $dir; while ( my $file = readdir DIR ) { next unless $file =~ /^Instrumental/; $url = _url_from_path( catfile($dir, $file) ); } closedir DIR; # The original file my $file = _row( "SELECT * FROM tracks WHERE title = ?", [ "Instrumental Works - Hespèrion XX, Jordi Savall" ] ); is( $file->{content_type}, 'fec', 'UTF-8 path/file FLAC+CUE original file content-type is fec' ); is( $file->{url}, $url, 'UTF-8 path/file FLAC+CUE original file url ok' ); my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Instrumental Works - Hespèrion XX, Jordi Savall" ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); utf8::decode( $album->{title} ); is( $album->{title}, "Instrumental Works - Hespèrion XX, Jordi Savall", 'UTF-8 path/file FLAC+CUE album title ok' ); # Compare some of the tracks in the cue my $ref = { 0 => { title => "Himno XIX Pange lingua IV", url => qr{^$url#0-1$}, }, 1 => { title => "Pour un plaisir", url => qr{^$url#1-2}, }, }; my $i = 0; for my $track ( @{$tracks} ) { if ( $ref->{$i} ) { utf8::decode( $track->{title} ); is( $track->{title}, $ref->{$i}->{title}, "UTF-8 path/file FLAC+CUE track $i ok" ); like( $track->{url}, $ref->{$i}->{url}, "UTF-8 path/file FLAC+CUE url $i ok" ); } $i++; } # check contributors are correct my $contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track = ? ORDER BY role }, [ $tracks->[0]->{id} ] ); utf8::decode( $contribs->[0]->{name} ); utf8::decode( $contribs->[1]->{name} ); is( $contribs->[0]->{name}, 'Cabezón, A', 'UTF-8 path/file FLAC+CUE artist name ok' ); is( $contribs->[0]->{role}, 1, 'UTF-8 path/file FLAC+CUE artist role ok' ); is( $contribs->[1]->{name}, 'Cabezón, A', 'UTF-8 path/file FLAC+CUE composer name ok' ); is( $contribs->[1]->{role}, 2, 'UTF-8 path/file FLAC+CUE composer role ok' ); } # Deleted playlists # XXX more types { # Get some initial values my @ssp_ids = map { $_->{id} } ( _rows( "SELECT id FROM tracks WHERE content_type = 'ssp'" ) ); my @cue_ids = map { $_->{id} } ( _rows( "SELECT id FROM tracks WHERE content_type = 'cue'" ) ); my $cue_album = _row( "SELECT id FROM albums WHERE title = ?", [ 'Proton Radio Mix (2003-07-30) CUE' ] ); my @cue_track_ids = map { $_->{id} } ( _rows( "SELECT id FROM tracks WHERE album = ?", [ $cue_album->{id} ] ) ); # Disable all playlist types and rescan _save_prefs( { disabledextensionsplaylist => 'm3u,m3u8,pls,cue', } ); _scan('--rescan'); # Test that m3u playlists were removed my $count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE content_type = 'ssp'" ); is( $count->{c}, 0, 'All ssp playlists removed ok' ); # Test that playlist_track entries were removed via cascade my $ph = join ',', @ssp_ids; $count = _row( "SELECT COUNT(*) AS c FROM playlist_track WHERE playlist IN ($ph)" ); is( $count->{c}, 0, 'All ssp playlist_track entries removed ok' ); # Test that standalone cue sheet was removed $count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE content_type = 'cue'" ); is( $count->{c}, 0, 'All cue sheets removed ok' ); # Test that cue sheet playlist_track entries were removed $count = _row( "SELECT COUNT(*) AS c FROM playlist_track WHERE playlist IN (?)", [ @cue_ids ] ); is( $count->{c}, 0, 'All cue playlist_track entries removed ok' ); # Test that cue sheet virtual tracks were removed $count = _row( "SELECT COUNT(*) AS c FROM tracks WHERE album = ?", [ $cue_album->{id} ] ); is( $count->{c}, 0, 'MP3+CUE album removed ok' ); # Test that cue sheet comments were removed $count = _row( "SELECT COUNT(*) AS c FROM comments WHERE value = ?", [ 'Test CUE Comment' ] ); is( $count->{c}, 0, 'MP3+CUE comments removed ok' ); # Test that cue sheet contributors were removed my $ids = join ',', @cue_track_ids; $count = _row( "SELECT COUNT(*) AS c FROM contributor_track WHERE track IN ($ids)" ); is( $count->{c}, 0, 'MP3+CUE contributors removed ok' ); # Test that cue sheet genre was removed $count = _row( "SELECT COUNT(*) AS c FROM genres WHERE name = ?", [ 'DJ Mix' ] ); is( $count->{c}, 0, 'MP3+CUE DJ Mix genre removed ok' ); # XXX original mp3 remains as type 'cur', ideally it should be rescanned as a normal track } # Changed playlists # Rescan everything _save_prefs(); _scan( qw(--wipe --rescan) ); # Change a simple M3U playlist { my $file = catfile($Bin, '..', 'data', 'scanner', 'playlists', 'm3u', 'ascii.m3u'); _backup($file); my $changed = catfile($Bin, '..', 'data', 'scanner', 'changes', 'ascii.m3u'); File::Copy::copy( $changed, $file ) or die "Unable to copy $changed to $file: $!"; # Rescan for changes _scan('--rescan'); my $playlist = _row( "SELECT * FROM tracks WHERE title = ? AND url LIKE '%m3u'", [ 'ascii' ] ); my $tracks = _rows( "SELECT * FROM playlist_track WHERE playlist = ? ORDER BY position", [ $playlist->{id} ] ); like( $tracks->[0]->{track}, qr/Winter%27s%20Return/, 'ASCII changed M3U track 1 ok' ); like( $tracks->[1]->{track}, qr/Wears%20the%20Summer/, 'ASCII changed M3U track 2 ok' ); like( $tracks->[2]->{track}, qr/Solar%20Fields/, 'ASCII changed M3U track 3 ok' ); } # Change a standalone cue sheet # Changes include: # Cue Genre # Cue Comment # Cue Performer # Cue Title # Reduced tracks from 14 to 10 # Changed performers on tracks 2 and 3 # Changed title on tracks 4 and 5 { my $file = catfile($Bin, '..', 'data', 'scanner', 'library', 'Digital Witchcraft', 'Digital Witchcraft - Proton Radio Mix (2003-07-30).cue'); _backup($file); my $changed = catfile($Bin, '..', 'data', 'scanner', 'changes', 'Digital Witchcraft - Proton Radio Mix (2003-07-30).cue'); File::Copy::copy( $changed, $file ) or die "Unable to copy $changed to $file: $!"; # Rescan for changes _scan('--rescan'); my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'Proton Radio Mix (2003-07-30) CUE Changed' ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); is( scalar @{$tracks}, 10, 'MP3+CUE changed track count ok' ); # Check some track data is( $tracks->[3]->{title}, 'Skymning Changed', 'MP3+CUE changed track 4 title ok' ); is( $tracks->[4]->{title}, 'Snowday Changed', 'MP3+CUE changed track 5 title ok' ); # Check comments from cue sheet my $comments = _row( "SELECT COUNT(*) AS c FROM comments WHERE value = ?", [ 'Test CUE Comment Changed' ] ); is( $comments->{c}, 10, 'MP3+CUE changed comments ok' ); # Check track genre my $genre = _row( qq{ SELECT * FROM genres LEFT JOIN genre_track ON genre_track.genre = genres.id WHERE genre_track.track = ? }, [ $tracks->[0]->{id} ] ); is( $genre->{name}, 'DJ Mix Changed', 'MP3+CUE changed genre is DJ Mix Changed' ); # Check track contributors my $contribs = _rows( qq{ SELECT * FROM contributor_track LEFT JOIN contributors ON contributor_track.contributor = contributors.id WHERE track IN (?, ?) ORDER BY track, role }, [ $tracks->[1]->{id}, $tracks->[2]->{id} ] ); is( $contribs->[0]->{name}, 'Peter Benisch Changed', 'MP3+CUE changed contrib 2 ok' ); is( $contribs->[1]->{name}, 'Icehouse Changed', 'MP3+CUE changed contrib 3 ok' ); _restore("$file.bak"); } ### XXX Tests for various pref changes on the My Music tab that affect the scanner # Test groupdiscs=1, check that a 2-cd album is grouped as a single album { _save_prefs( { groupdiscs => 1 } ); _scan( qw(--wipe --rescan) ); my $album = _row( "SELECT * FROM albums WHERE title = ?", [ 'The Incident' ] ); my $tracks = _rows( "SELECT * FROM tracks WHERE album = ?", [ $album->{id} ] ); is( scalar @{$tracks}, 18, 'groupdiscs album grouped ok' ); } ############# ### Tests that require the server to be running. These don't technically belong ### in this test file, but since they depend on a scanned database, it it easier to put them here SERVER_TESTS: $server = _server(); # Test various forms of HTTP artwork requests # Test non-cover images { # Test async call to gdresize my $res = _http_get('/html/images/artists_25x25_f.png'); is( $res->content_type, 'image/png', 'artists_25x25_f resized is PNG ok' ); is( $res->header('Cache-Control'), 'max-age=86400', 'artists_25x25_f resized max-age is 1 day' ); is( _compare( $res->content, 'ref/artists_25x25_f.png' ), 1, 'artists_25x25_f resized content ok' ); # Test again to get cached version $res = _http_get('/html/images/artists_25x25_f.png'); is( $res->content_type, 'image/png', 'artists_25x25_f from cache PNG ok' ); is( $res->header('Cache-Control'), 'max-age=86400', 'artists_25x25_f from cache max-age is 1 day' ); is( _compare( $res->content, 'ref/artists_25x25_f.png' ), 1, 'artists_25x25_f from cache content ok' ); # Test plugin images are found ok $res = _http_get('/plugins/MyApps/html/images/icon_41x41_m.png'); is( $res->content_type, 'image/png', 'plugin icon_41x41_m is PNG ok' ); is( $res->header('Cache-Control'), 'max-age=86400', 'plugin icon_41x41_m max-age is 1 day' ); is( _compare( $res->content, 'ref/myapps_icon_41x41_m.png' ), 1, 'plugin icon_41x41_m content ok' ); } # Test special images { # XXX /music/current/cover.jpg (requires player, not sure how to test) # /music/all_items/cover_100x100_o my $res = _http_get('/music/all_items/cover_25x25_o.png'); is( $res->content_type, 'image/png', 'all_items 25x25 is PNG ok' ); is( $res->header('Cache-Control'), 'max-age=86400', 'all_items 25x25 max-age is 1 day' ); is( _compare( $res->content, 'ref/all_items_25x25_o.png' ), 1, 'all_items 25x25 content ok' ); # Should work without an extension too $res = _http_get('/music/all_items/cover_25x25_o'); is( $res->content_type, 'image/png', 'all_items 25x25 without extension is PNG ok' ); is( $res->header('Cache-Control'), 'max-age=86400', 'all_items 25x25 without extension max-age is 1 day' ); is( _compare( $res->content, 'ref/all_items_25x25_o.png' ), 1, 'all_items 25x25 without extension content ok' ); } # Test images returning various error codes, 404, 500 { my $res = _http_get('/plugins/cache/icons/asdf_41x41_m.png'); is( $res->code, 404, '404 icon is ok' ); is( $res->content_type, 'text/html', '404 URL returned text/html content-type' ); is( $res->header('Cache-Control'), 'no-cache', '404 URL returned no-cache header' ); # To test 500, try to resize a non-image file $res = _http_get('/html/silence_50x50_o.mp3'); is( $res->code, 500, '500 resize of non-image file ok' ); is( $res->header('Cache-Control'), 'no-cache', '500 cache-control ok' ); } # Test cover images # Have to use the server database because the server has moved the scanner one back { my $album = _row( "SELECT * FROM albums WHERE title = ?", [ "Mirror's Edge" ], 'squeezebox.db' ); my $res = _http_get('/music/' . $album->{artwork} . '/cover_75x75_m'); is( $res->content_type, 'image/jpeg', 'cover content-type is JPEG ok' ); is( $res->header('Cache-Control'), 'max-age=31536000', 'cover cached for 1 year ok' ); is( _compare( $res->content, 'ref/cover_75x75_m.jpg'), 1, 'cover content ok' ); # Test invalid cover returns CD image $res = _http_get('/music/xxxxxxx/cover_75x75_m'); is( $res->content_type, 'image/png', 'invalid cover content-type is PNG ok' ); is( $res->header('Cache-Control'), 'no-cache', 'invalid cover not cached ok' ); is( _compare( $res->content, 'ref/cd_cover_75x75_m.png'), 1, 'invalid cover content ok' ); # Test full-size cover $res = _http_get('/music/' . $album->{artwork} . '/cover.jpg'); is( $res->content_type, 'image/jpeg', 'full-size cover.jpg content-type is JPEG ok' ); is( $res->header('Cache-Control'), 'max-age=31536000', 'cover cached for 1 year ok' ); is( _compare( $res->content, 'ref/cover.jpg'), 1, 'cover content ok' ); # Test resizing cover to original size in PNG (bug 15944) $res = _http_get('/music/' . $album->{artwork} . '/cover.png'); is( $res->content_type, 'image/png', 'full-size cover.png content-type is PNG ok' ); is( _compare( $res->content, 'ref/cover.png'), 1, 'cover.png content ok' ); # Test spec regex in various ways $res = _http_get('/music/' . $album->{artwork} . '/cover_600x599.png'); is( $res->content_type, 'image/png', 'cover_600x599.png content-type is PNG ok' ); is( _compare( $res->content, 'ref/cover.png'), 1, 'cover_600x599.png content ok' ); $res = _http_get('/music/' . $album->{artwork} . '/cover_600x599_F.png'); is( $res->content_type, 'image/png', 'cover_600x599_F.png content-type is PNG ok' ); is( _compare( $res->content, 'ref/cover.png'), 1, 'cover_600x599_F.png content ok' ); $res = _http_get('/music/' . $album->{artwork} . '/cover_600x599_m_ffffff'); is( $res->content_type, 'image/jpeg', 'cover_600x599_m_ffffff content-type is JPEG ok' ); is( _compare( $res->content, 'ref/cover.jpg'), 1, 'cover_600x599_m_ffffff content ok' ); $res = _http_get('/music/' . $album->{artwork} . '/cover_200x400_aaaaaa.png'); is( $res->content_type, 'image/png', 'cover_200x400_aaaaaa.png content-type is PNG ok' ); is( _compare( $res->content, 'ref/cover_200x400_aaaaaa.png'), 1, 'cover_200x400_aaaaaa.png content ok' ); # Test bad data $res = _http_get('/music/' . $album->{artwork} . '/cover_asdf_q_png'); is( $res->content_type, 'image/jpeg', 'cover_asdf_q_png content-type is JPEG ok' ); is( _compare( $res->content, 'ref/cover.jpg'), 1, 'cover_asdf_q_png content ok' ); } # Test gdresized and async artwork requests { my $gdresized = _gdresized(); # Test pipelined requests, the first request will need to be resized while the second # one will be served instantly. They should not be returned out of order. my @reqs = ( 'http://localhost:9000/html/images/artists_60x60_m.png', 'http://localhost:9000/music/all_items/cover_25x25_o.png', 'http://localhost:9000/html/images/artists_70x70_m.png', ); my $req; for my $url ( @reqs ) { my $uri = URI->new($url); $req .= 'GET ' . $uri->path_query . ' HTTP/1.1' . $CRLF . 'Host: ' . $uri->host . ':' . $uri->port . $CRLF . $CRLF; } my $sock = IO::Socket::INET->new( PeerAddr => '127.0.0.1', PeerPort => 9000, Proto => 'tcp', ReuseAddr => 1, Timeout => 2, ) or die "Cannot connect to server"; # Send 2 requests pipelined syswrite $sock, $req; my $pipebuf; my $sel = IO::Select->new($sock); while ( $sel->can_read(1) ) { my $n = sysread $sock, my $buf2, 64 * 1024; last unless $n; $pipebuf .= $buf2; } $sock->close; # XXX sometimes this does not read all responses my $count = 1; for my $resp ( split m{HTTP/1.1 200 OK}, $pipebuf ) { next unless $resp; $resp = 'HTTP/1.1 200 OK' . $resp; my $res = HTTP::Response->parse($resp); if ( $count == 1 ) { is( _compare( $res->content, 'ref/artists_60x60_m.png'), 1, 'gdresized async artwork 1 ok' ); } elsif ( $count == 2 ) { is( _compare( $res->content, 'ref/all_items_25x25_o.png'), 1, 'gdresized with pipelined request ok' ); } elsif ( $count == 3 ) { is( _compare( $res->content, 'ref/artists_70x70_m.png'), 1, 'gdresized async artwork 2 ok' ); } $count++; } warn "# Shutting down gdresized\n"; $gdresized->die; } ### Test various CLI queries # Browse Music Folder { my $res = _jsonrpc_get( [ 'musicfolder', 0, 100 ] ); my $folder = $res->{folder_loop}; is( $res->{count}, 14, 'BMF CLI count ok' ); is( $folder->[0]->{filename}, '12 Moons', 'BMF CLI filename 1 ok' ); # Touch a folder to trigger non-recursive rescan my $path = catfile($scandir, 'library'); `touch "$path"`; # XXX bug 15940? $res = _jsonrpc_get( [ 'musicfolder', 0, 100 ] ); is( $res->{count}, 14, 'BMF CLI count after rescan ok' ); is( $folder->[0]->{filename}, '12 Moons', 'BMF CLI filename 1 after rescan ok' ); } # artists query { use utf8; # iPeng uses this query my $res = _jsonrpc_get( [ 'artists', 0, 100, 'tags:s' ] ); my $artists = $res->{artists_loop}; is( $artists->[0]->{artist}, 'Various Artists', 'artists CLI ok' ); is( $artists->[-1]->{artist}, 'Øystein Sevåg', 'artists CLI ok 2' ); # Test limiting is counting properly with extra VA item $res = _jsonrpc_get( [ 'artists', 0, 4, 'tags:s' ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 4, 'artists CLI limit is ok' ); my $last_item = $artists->[-1]->{artist}; # Test second query overlap $res = _jsonrpc_get( [ 'artists', 3, 100, 'tags:s' ] ); $artists = $res->{artists_loop}; is( $artists->[0]->{artist}, $last_item, 'artists CLI limit overlap ok' ); # Test search param $res = _jsonrpc_get( [ 'artists', 0, 100, 'search:Tree' ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI search count ok' ); is( $artists->[0]->{artist}, 'Porcupine Tree', 'artists CLI search result ok' ); # Test genre_id param my $genre = _row( "SELECT id FROM genres WHERE name = ?", [ 'Progressive Rock' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'genre_id:' . $genre->{id} ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI genre_id count ok' ); is( $artists->[0]->{artist}, 'Porcupine Tree', 'artists CLI genre_id result ok' ); # Test genre_id of Ambient returns only a VA item $genre = _row( "SELECT id FROM genres WHERE name = ?", [ 'Ambient' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'genre_id:' . $genre->{id} ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI genre_id VA count ok' ); is( $artists->[0]->{artist}, 'Various Artists', 'artists CLI genre_id VA result ok' ); # Test album_id param my $album = _row( "SELECT id FROM albums WHERE title = ?", [ "133 Thursdays" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'album_id:' . $album->{id} ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI album_id count ok' ); is( $artists->[0]->{artist}, 'Pushmipulyu', 'artists CLI album_id result ok' ); # Test album_id on a VA album $album = _row( "SELECT id FROM albums WHERE title = ?", [ "Mirror's Edge" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'album_id:' . $album->{id} ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI album_id VA count ok' ); is( $artists->[0]->{artist}, 'Various Artists', 'artists CLI album_id VA result ok' ); # Test track_id param (undocumented) my $track = _row( "SELECT id FROM tracks WHERE title = ?", [ "White Water" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'track_id:' . $track->{id} ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI track_id count ok' ); is( $artists->[0]->{artist}, 'Doug Cameron', 'artists CLI track_id result ok' ); # Test year param (undocumented) $res = _jsonrpc_get( [ 'artists', 0, 100, 'year:2007' ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI year count ok' ); is( $artists->[0]->{artist}, 'Pushmipulyu', 'artists CLI year result ok' ); # Test year that only contains VA artists $res = _jsonrpc_get( [ 'artists', 0, 100, 'year:2009' ] ); $artists = $res->{artists_loop}; is( scalar @{$artists}, 1, 'artists CLI year VA count ok' ); is( $artists->[0]->{artist}, 'Various Artists', 'artists CLI year VA result ok' ); # Test results when VA pref is disabled _jsonrpc_get( [ 'pref', 'variousArtistAutoIdentification', 0 ] ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'tags:s' ] ); $artists = $res->{artists_loop}; is( $artists->[0]->{artist}, '12 Moons', 'artists CLI without VA ok' ); is( $artists->[-1]->{artist}, 'Jim Jacobsen', 'artists CLI without VA ok 2' ); # Test genre_id of Ambient which has only VA artists $genre = _row( "SELECT id FROM genres WHERE name = ?", [ 'Ambient' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'genre_id:' . $genre->{id} ] ); $artists = $res->{artists_loop}; is( $artists->[0]->{artist}, 'Lisa Miskovsky', 'artists CLI genre_id without VA ok' ); is( $artists->[1]->{artist}, 'Solar Fields', 'artists CLI genre_id without VA ok 2' ); # Test album_id on a VA album $album = _row( "SELECT id FROM albums WHERE title = ?", [ "Mirror's Edge" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'artists', 0, 100, 'album_id:' . $album->{id} ] ); $artists = $res->{artists_loop}; is( $artists->[0]->{artist}, 'Lisa Miskovsky', 'artists CLI album_id without VA ok' ); is( $artists->[1]->{artist}, 'Solar Fields', 'artists CLI album_id without VA ok 2' ); # Test year that only contains VA artists $res = _jsonrpc_get( [ 'artists', 0, 100, 'year:2009' ] ); $artists = $res->{artists_loop}; is( $artists->[0]->{artist}, 'Lisa Miskovsky', 'artists CLI year without VA ok' ); is( $artists->[1]->{artist}, 'Solar Fields', 'artists CLI year without VA ok 2' ); # Re-enable VA pref _jsonrpc_get( [ 'pref', 'variousArtistAutoIdentification', 1 ] ); } # albums query { # Query used by iPeng my $res = _jsonrpc_get( [ 'albums', 0, 100, 'tags:ljywags', 'sort:album' ] ); my $albums = $res->{albums_loop}; is( $albums->[0]->{album}, '133 Thursdays', 'albums CLI album ok 1' ); is( $albums->[0]->{artist}, 'Pushmipulyu', 'albums CLI artist ok' ); is( $albums->[0]->{compilation}, 0, 'albums CLI compilation ok' ); is( $albums->[0]->{textkey}, 1, 'albums CLI textkey ok' ); is( $albums->[0]->{year}, 2007, 'albums CLI year ok' ); like( $albums->[1]->{artwork_track_id}, qr/^[0-9a-f]{8}$/, 'albums CLI artwork_track_id ok' ); is( $albums->[-1]->{album}, 'Zero Gravity EP', 'albums CLI album ok 2' ); # Test limiting $res = _jsonrpc_get( [ 'albums', 0, 4 ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 4, 'albums CLI limit is ok' ); my $last_item = $albums->[-1]->{album}; # Test second query overlap $res = _jsonrpc_get( [ 'albums', 3, 100 ] ); $albums = $res->{albums_loop}; is( $albums->[0]->{album}, $last_item, 'albums CLI limit overlap ok' ); # Test search, and some extra tags $res = _jsonrpc_get( [ 'albums', 0, 100, 'search:Incident', 'tags:altiq' ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 1, 'albums CLI search count ok' ); is( $albums->[0]->{album}, 'The Incident', 'albums CLI search result ok' ); is( $albums->[0]->{title}, 'The Incident', 'albums CLI tag:t title ok' ); is( $albums->[0]->{disc}, 2, 'albums CLI tag:i disc ok' ); is( $albums->[0]->{disccount}, 2, 'albums CLI tag:q disccount ok' ); # Test genre_id my $genre = _row( "SELECT id FROM genres WHERE name = ?", [ 'Progressive Rock' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'albums', 0, 100, 'genre_id:' . $genre->{id} ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 1, 'albums CLI genre_id count ok' ); is( $albums->[0]->{album}, 'The Incident', 'albums CLI genre_id result ok' ); # Test genre_id that only has VA albums $genre = _row( "SELECT id FROM genres WHERE name = ?", [ 'Ambient' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'albums', 0, 100, 'genre_id:' . $genre->{id} ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 1, 'albums CLI genre_id VA count ok' ); is( $albums->[0]->{album}, "Mirror's Edge", 'albums CLI genre_id VA result ok' ); # Test artist_id my $artist = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Porcupine Tree' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'albums', 0, 100, 'artist_id:' . $artist->{id} ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 1, 'albums CLI artist_id count ok' ); is( $albums->[0]->{album}, 'The Incident', 'albums CLI artist_id result ok' ); # Test track_id my $track = _row( "SELECT id FROM tracks WHERE title = ?", [ "Occam's Razor" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'albums', 0, 100, 'track_id:' . $track->{id} ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 1, 'albums CLI track_id count ok' ); is( $albums->[0]->{album}, 'The Incident', 'albums CLI track_id result ok' ); # Test year $res = _jsonrpc_get( [ 'albums', 0, 100, 'year:2009' ] ); $albums = $res->{albums_loop}; is( scalar @{$albums}, 1, 'albums CLI year VA count ok' ); is( $albums->[0]->{album}, "Mirror's Edge", 'albums CLI year VA result ok' ); # Test compilation $res = _jsonrpc_get( [ 'albums', 0, 100, 'compilation:1' ] ); $albums = $res->{albums_loop}; is( $albums->[0]->{album}, 'The Divine Invasion', 'albums CLI compilation ok' ); is( $albums->[-1]->{album}, 'Proton Radio Mix (2003-07-30) CUE', 'albums CLI compilation ok 2' ); # Test sort:new # XXX can't properly test this as it can break depending on timestamps $res = _jsonrpc_get( [ 'albums', 0, 100, 'sort:new' ] ); $albums = $res->{albums_loop}; cmp_ok( scalar @{$albums}, '>', 0, 'albums CLI sort:new count ok' ); # Test sort:artflow $res = _jsonrpc_get( [ 'albums', 0, 100, 'sort:artflow', 'tags:aly' ] ); $albums = $res->{albums_loop}; is( $albums->[0]->{album}, 'SqueezeCenter Bug 9772', 'albums CLI sort:artflow ok' ); is( $albums->[-1]->{album}, "Mirror's Edge", 'albums CLI sort:artflow ok 2' ); # Test sort:artistalbum (undocumented) $res = _jsonrpc_get( [ 'albums', 0, 100, 'sort:artistalbum', 'tags:al' ] ); $albums = $res->{albums_loop}; is( $albums->[0]->{artist}, 'Alfred E. Moonbase', 'albums CLI sort:artistalbum ok' ); is( $albums->[-1]->{artist}, 'Various Artists', 'albums CLI sort:artistalbum ok 2' ); # Test sort:yearartistalbum (undocumented) $res = _jsonrpc_get( [ 'albums', 0, 100, 'sort:yearartistalbum', 'tags:aly' ] ); $albums = $res->{albums_loop}; is( $albums->[0]->{album}, 'Wild Earth', 'albums CLI sort:yearartistalbum ok' ); is( $albums->[-1]->{album}, "Mirror's Edge", 'albums CLI sort:yearartistalbum ok 2' ); # Test sort:yearalbum (undocumented) $res = _jsonrpc_get( [ 'albums', 0, 100, 'sort:yearalbum', 'tags:ly' ] ); $albums = $res->{albums_loop}; is( $albums->[0]->{album}, 'The Divine Invasion', 'albums CLI sort:yearalbum ok' ); is( $albums->[-1]->{album}, "Mirror's Edge", 'albums CLI sort:yearalbum ok 2' ); # Test SP menu mode $res = _jsonrpc_get( [ 'albums', 0, 100, 'menu:1' ] ); $albums = $res->{item_loop}; is( $albums->[0]->{text}, "133 Thursdays\nPushmipulyu", 'albums CLI menu:1 ok' ); is( $albums->[-1]->{text}, "Zero Gravity EP\nNo Artist", 'albums CLI menu:1 ok 2' ); } # genres query { # iPeng uses this query my $res = _jsonrpc_get( [ 'genres', 0, 100, 'tags:s' ] ); my $genres = $res->{genres_loop}; is( $genres->[0]->{genre}, 'A_Medieval', 'genres CLI name ok' ); is( $genres->[0]->{textkey}, 'A', 'genres CLI textkey ok' ); is( $genres->[-1]->{genre}, 'Test Genre 2', 'genres CLI last name ok' ); # Test limiting the genres query $res = _jsonrpc_get( [ 'genres', 0, 4, 'tags:s' ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 4, 'genres CLI limit is ok' ); my $last_item = $genres->[-1]->{genre}; # Test second query overlap $res = _jsonrpc_get( [ 'genres', 3, 100, 'tags:s' ] ); $genres = $res->{genres_loop}; is( $genres->[0]->{genre}, $last_item, 'genres CLI limit overlap ok' ); # Test search param $res = _jsonrpc_get( [ 'genres', 0, 100, 'search:Amb' ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 1, 'genres CLI search count ok '); is( $genres->[0]->{genre}, 'Ambient', 'genres CLI search result ok' ); # Test artist_id param my $artist = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Digital Witchcraft' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'genres', 0, 100, 'artist_id:' . $artist->{id} ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 1, 'genres CLI artist_id count ok '); is( $genres->[0]->{genre}, 'DJ Mix', 'genres CLI artist_id result ok' ); # Test artist_id == VA artist $artist = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Various Artists' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'genres', 0, 100, 'artist_id:' . $artist->{id} ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 4, 'genres CLI artist_id VA count ok '); is( $genres->[0]->{genre}, 'Ambient', 'genres CLI artist_id VA result ok' ); is( $genres->[-1]->{genre}, 'No Genre', 'genres CLI artist_id VA result 2 ok' ); # Test album_id param my $album = _row( "SELECT id FROM albums WHERE title = ?", [ "Mirror's Edge" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'genres', 0, 100, 'album_id:' . $album->{id} ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 1, 'genres CLI album_id count ok '); is( $genres->[0]->{genre}, 'Ambient', 'genres CLI album_id result ok' ); # Test track_id param my $track = _row( "SELECT id FROM tracks WHERE title = ?", [ "White Water" ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'genres', 0, 100, 'track_id:' . $track->{id} ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 1, 'genres CLI track_id count ok '); is( $genres->[0]->{genre}, 'New Age', 'genres CLI track_id result ok' ); # Test year param $res = _jsonrpc_get( [ 'genres', 0, 100, 'year:2009' ] ); $genres = $res->{genres_loop}; is( scalar @{$genres}, 1, 'genres CLI year count ok '); is( $genres->[0]->{genre}, 'Ambient', 'genres CLI year result ok' ); } # titles query { use utf8; my $album = _row( "SELECT id FROM albums WHERE title = ?", [ "Mirror's Edge" ], 'squeezebox.db' ); # Test SP menu mode (tracks == titles) my $res = _jsonrpc_get( [ 'tracks', 0, 200, 'menu_all:1', 'album_id:' . $album->{id}, 'menu:trackinfo', 'sort:tracknum' ] ); my $titles = $res->{item_loop}; is( scalar @{$titles}, 12, 'titles CLI menuMode count ok' ); is( $titles->[0]->{params}->{album_id}, $album->{id}, 'titles CLI menuMode album_id ok' ); is( $titles->[0]->{text}, 'Introduction', 'titles CLI menuMode text ok' ); # iPeng uses this query $res = _jsonrpc_get( [ 'titles', 0, 100, 'album_id:' . $album->{id}, 'tags:alted', 'sort:tracknum' ] ); $titles = $res->{titles_loop}; is( scalar @{$titles}, 12, 'titles CLI count ok' ); is( $titles->[0]->{tracknum}, 1, 'titles CLI tracknum sort ok' ); is( $titles->[-1]->{tracknum}, 12, 'titles CLI tracknum sort ok 2' ); is( $titles->[0]->{artist}, 'Solar Fields', 'titles CLI tags:a ok' ); is( $titles->[0]->{album}, "Mirror's Edge", 'titles CLI tags:l ok' ); is( $titles->[0]->{album_id}, $album->{id}, 'titles CLI tags:e ok' ); is( $titles->[0]->{duration}, 334.471, 'titles CLI tags:d ok' ); # Test genre_id param and default tags (gald), default sort (titlesort) my $genre = _row( "SELECT id FROM genres WHERE name = ?", [ 'Ambient' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'titles', 0, 100, 'genre_id:' . $genre->{id} ] ); $titles = $res->{titles_loop}; is( scalar @{$titles}, 12, 'titles CLI genre_id count ok' ); is( $titles->[0]->{title}, 'Boat', 'titles CLI genre_id default sort ok' ); is( $titles->[-1]->{title}, 'Still Alive (instrumental)', 'titles CLI genre_id default sort ok 2' ); is( $titles->[0]->{genre}, 'Ambient', 'titles CLI genre_id default tags:g ok' ); is( $titles->[0]->{artist}, 'Solar Fields', 'titles CLI genre_id default tags:a ok' ); is( $titles->[0]->{album}, "Mirror's Edge", 'titles CLI genre_id default tags:l ok' ); is( $titles->[0]->{duration}, 450.246, 'titles CLI genre_id default tags:d ok' ); # Test artist_id param, tags:s (artist_id) my $artist = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Gooding' ], 'squeezebox.db' ); $res = _jsonrpc_get( [ 'titles', 0, 100, 'artist_id:' . $artist->{id}, 'tags:as' ] ); $titles = $res->{titles_loop}; is( scalar @{$titles}, 10, 'titles CLI artist_id count ok' ); is( $titles->[0]->{artist}, 'Gooding', 'titles CLI artist_id tags:a ok' ); is( $titles->[0]->{artist_id}, $artist->{id}, 'titles CLI artist_id tags:s ok' ); is( $titles->[0]->{title}, 'A Loss for Words', 'titles CLI artist_id default sort ok' ); # Test year param $res = _jsonrpc_get( [ 'titles', 0, 100, 'year:1993' ] ); $titles = $res->{titles_loop}; is( scalar @{$titles}, 13, 'titles CLI year count ok' ); is( $titles->[0]->{artist}, 'Øystein Sevåg', 'titles CLI year UTF-8 tags:a ok' ); is( $titles->[0]->{album}, 'Link', 'titles CLI year tags:l ok' ); # Test search param, tags:pyvC (genre_id, year, tagversion, compilation) $res = _jsonrpc_get( [ 'titles', 0, 100, 'search:Alive', 'tags:pyvC' ] ); $titles = $res->{titles_loop}; is( scalar @{$titles}, 2, 'titles CLI search count ok' ); is( $titles->[0]->{title}, 'Still Alive', 'titles CLI search result ok' ); is( $titles->[0]->{genre_id}, $genre->{id}, 'titles CLI tags:p genre_id ok' ); is( $titles->[0]->{year}, 2009, 'titles CLI tags:y year ok' ); is( $titles->[0]->{tagversion}, 'ID3v2.4.0', 'titles CLI tags:v tagversion ok' ); is( $titles->[0]->{compilation}, 1, 'titles CLI tags:C compilation ok' ); # Get genres to test IDs my $genre1 = _row( "SELECT id FROM genres WHERE name = ?", [ 'Progressive Rock' ], 'squeezebox.db' ); my $genre2 = _row( "SELECT id FROM genres WHERE name = ?", [ 'Test Genre 2' ], 'squeezebox.db' ); # Get contributor IDs my $composer_id = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Steven Wilson' ], 'squeezebox.db' ); my $composer2_id = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Composer 2' ], 'squeezebox.db' ); my $aartist_id = _row( "SELECT id FROM contributors WHERE name = ?", [ 'Porcupine Tree' ], 'squeezebox.db' ); # Test all of the other possible tags $res = _jsonrpc_get( [ 'titles', 0, 100, 'search:Razor', 'tags:agGpPsASeiqtymMkovrfjJnCYXRTIuwc' ] ); $titles = $res->{titles_loop}; is( $titles->[0]->{artist}, 'Porcupine Tree', 'titles CLI tags:a ok' ); #is( $titles->[0]->{genre}, 'Progressive Rock', 'titles CLI tags:g ok' ); # XXX buggy, returns random genre is( $titles->[0]->{genres}, 'Progressive Rock, Test Genre 2', 'titles CLI tags:G ok' ); is( $titles->[0]->{genre_ids}, join( ', ', $genre1->{id}, $genre2->{id} ), 'titles CLI tags:P ok' ); is( $titles->[0]->{composer}, 'Steven Wilson, Composer 2', 'titles CLI tags:A composer ok' ); is( $titles->[0]->{composer_ids}, join( ', ', $composer_id->{id}, $composer2_id->{id} ), 'titles CLI tags:S composer_ids ok' ); is( $titles->[0]->{albumartist}, 'Porcupine Tree', 'titles CLI tags:A albumartist ok' ); is( $titles->[0]->{albumartist_ids}, $aartist_id->{id}, 'titles CLI tags:S albumartist_ids ok' ); is( $titles->[0]->{bpm}, 120, 'titles CLI tags:m bpm ok' ); is( $titles->[0]->{musicmagic_mixable}, 0, 'titles CLI tags:M musicmagic_mixable ok' ); is( $titles->[0]->{comment}, 'First Comment / Second Comment', 'titles CLI tags:k comment ok' ); is( $titles->[0]->{disc}, 1, 'titles CLI tags:i disc ok' ); is( $titles->[0]->{disccount}, 2, 'titles CLI tags:q disccount ok' ); is( $titles->[0]->{tracknum}, 1, 'titles CLI tags:t tracknum ok' ); is( $titles->[0]->{year}, 0, 'titles CLI tags:y year ok' ); is( $titles->[0]->{type}, 'flc', 'titles CLI tags:o type ok' ); is( $titles->[0]->{bitrate}, '0kbps VBR', 'titles CLI tags:r bitrate ok' ); is( $titles->[0]->{filesize}, 49152, 'titles CLI tags:f filesize ok' ); is( $titles->[0]->{coverart}, 1, 'titles CLI tags:j coverart ok' ); like( $titles->[0]->{artwork_track_id}, qr/^[0-9a-f]{8}$/, 'titles CLI tags:J artwork_track_id ok' ); like( $titles->[0]->{modificationTime}, qr/\d+:\d+ [AP]M/, 'titles CLI tags:n modificationTime ok' ); is( $titles->[0]->{replay_gain}, '-5.26', 'titles CLI tags:Y replay_gain ok' ); is( $titles->[0]->{album_replay_gain}, '-4.99', 'titles CLI tags:X album_replay_gain ok' ); is( $titles->[0]->{rating}, 5, 'titles CLI tags:R rating ok' ); is( $titles->[0]->{samplerate}, 44100, 'titles CLI tags:T samplerate ok' ); is( $titles->[0]->{samplesize}, 16, 'titles CLI tags:I samplesize ok' ); like( $titles->[0]->{url}, qr/Occam%27s%20Razor/, 'titles CLI tags:u url ok' ); is( $titles->[0]->{lyrics}, 'Some Lyrics Here', 'titles CLI tags:w lyrics ok' ); is( $titles->[0]->{coverid}, $titles->[0]->{artwork_track_id}, 'titles CLI tags:c coverid ok' ); # XXX Bluetech problems } # statusQuery { my $player = _require_player(); my $res; my $track1; $res = _jsonrpc_get( $player, [ 'status', '-', 100 ] ); # Player should have no playlist, etc is( $res->{mode}, 'stop', 'player is stopped' ); is( $res->{playlist_tracks}, 0, 'player has no playlist' ); # Add some tracks to the playlist my $album = _row( "SELECT id FROM albums WHERE title = ?", [ "The Incident" ], 'squeezebox.db' ); $res = _jsonrpc_get( $player, [ 'playlistcontrol', 'cmd:add', 'album_id:' . $album->{id} ] ); is( $res->{count}, 18, 'playlistcontrol cmd:add count ok' ); # Test status query as made by iPeng (for current track) $res = _jsonrpc_get( $player, [ 'status', '-', 1, 'tags:uBJjKlax' ] ); my $track1 = $res->{playlist_loop}->[0]; is( $track1->{album}, 'The Incident', 'iPeng track1 album ok' ); is( $track1->{artist}, 'Porcupine Tree', 'iPeng track1 artist ok' ); like( $track1->{artwork_track_id}, qr/^[0-9a-f]{8}$/, 'iPeng track1 atrwork_track_id ok' ); is( $track1->{coverart}, 1, 'iPeng track1 coverart ok' ); is( $track1->{title}, "Occam's Razor", 'iPeng track1 title ok' ); like( $track1->{url}, qr/Porcupine%20Tree/, 'iPeng track1 url ok' ); # Status query made by iPeng for whole playlist $res = _jsonrpc_get( $player, [ 'status', '-', 25, 'tags:uBjJKlaxdN' ] ); is( scalar @{ $res->{playlist_loop} }, 18, 'playlist_loop ok' ); is( $res->{playlist_tracks}, 18, 'playlist_tracks ok' ); $track1 = $res->{playlist_loop}->[0]; is( $track1->{album}, 'The Incident', 'track1 album ok' ); is( $track1->{artist}, 'Porcupine Tree', 'track1 artist ok' ); is( $track1->{duration}, 116.08, 'track1 duration ok' ); is( $track1->{'playlist index'}, 0, 'track1 playlist index ok' ); is( $track1->{title}, "Occam's Razor", 'track1 title ok' ); # Status query for iPeng with offset $res = _jsonrpc_get( $player, [ 'status', 4, 25, 'tags:uBjJKlaxdN' ] ); is( scalar @{ $res->{playlist_loop} }, 14, 'playlist_loop ok' ); $track1 = $res->{playlist_loop}->[0]; is( $track1->{title}, 'Drawing the Line', 'offset track1 title ok' ); is( $track1->{'playlist index'}, 4, 'offset track1 playlist index ok' ); # Test status query as made by SqueezePlay $res = _jsonrpc_get( $player, [ 'status', 4, 100, 'menu:menu', 'useContextMenu:1' ] ); is( scalar @{ $res->{item_loop} }, 16, 'status menuMode item_loop ok' ); is( $res->{count}, 20, 'status menuMode count is ok' ); # 2 extra items are added is( $res->{playlist_tracks}, 18, 'status menuMode playlist_tracks ok' ); $track1 = $res->{item_loop}->[0]; is( $track1->{album}, 'The Incident', 'menuMode track1 album ok' ); is( $track1->{artist}, 'Porcupine Tree', 'menuMode track1 artist ok' ); like( $track1->{'icon-id'}, qr/^[0-9a-f]{8}$/, 'menuMode track1 icon-id ok' ); is( $track1->{text}, "Drawing the Line\nPorcupine Tree - The Incident", 'menuMode track1 text ok' ); is( $track1->{track}, "Drawing the Line", 'menuMode track1 track ok' ); is( $track1->{trackType}, 'local', 'menuMode track1 trackType ok' ); is( $track1->{params}->{playlist_index}, 4, 'menuMode track1 playlist_index ok' ); is( $res->{item_loop}->[-1]->{text}, 'Save Playlist', 'menuMode Save Playlist ok' ); is( $res->{item_loop}->[-2]->{text}, 'Clear Playlist', 'menuMode Clear Playlist ok' ); # Test the rest of the tags $res = _jsonrpc_get( $player, [ 'status', '-', 2, 'tags:agGpPsASeiqtymkovrfjJnCYXRTIuwc' ] ); $track1 = $res->{playlist_loop}->[0]; like( $track1->{album_id}, qr/^\d+$/, 'status album_id ok' ); is( $track1->{album_replay_gain}, '-4.99', 'status album_replay_gain ok' ); is( $track1->{albumartist}, 'Porcupine Tree', 'status albumartist ok' ); like( $track1->{albumartist_ids}, qr/^\d+$/, 'status albumartist_ids ok' ); like( $track1->{artist_id}, qr/^\d+$/, 'status artist_id ok' ); is( $track1->{bitrate}, '0kbps VBR', 'status bitrate ok' ); is( $track1->{bpm}, 120, 'status bpm ok' ); is( $track1->{comment}, "First Comment / Second Comment", 'status comment ok' ); is( $track1->{compilation}, 0, 'status compilation ok' ); is( $track1->{composer}, 'Steven Wilson, Composer 2', 'status composer ok' ); like( $track1->{composer_ids}, qr/^\d+,\s\d+$/, 'status composer_ids ok' ); is( $track1->{disc}, 1, 'status disc ok' ); is( $track1->{disccount}, 2, 'status disccount ok' ); is( $track1->{filesize}, 49152, 'status filesize ok' ); #is( $track1->{genre}, 'Progressive Rock', 'status genre ok' ); # XXX buggy, returns random genre like( $track1->{genre_id}, qr/^\d+$/, 'status genre_id ok' ); like( $track1->{genre_ids}, qr/^\d+,\s\d+$/, 'status genre_ids ok' ); is( $track1->{genres}, 'Progressive Rock, Test Genre 2', 'status genres ok' ); is( $track1->{lyrics}, 'Some Lyrics Here', 'status lyrics ok' ); like( $track1->{modificationTime}, qr/\d+:\d+ [AP]M/, 'status modificationTime ok' ); is( $track1->{rating}, 5, 'status rating ok' ); is( $track1->{replay_gain}, '-5.26', 'status replay_gain ok' ); is( $track1->{samplerate}, 44100, 'status samplerate ok' ); is( $track1->{samplesize}, 16, 'satus samplesize ok' ); is( $track1->{trackartist}, 'Porcupine Tree', 'status trackartist ok' ); like( $track1->{trackartist_ids}, qr/^\d+$/, 'status trackartist_ids ok' ); is( $track1->{tracknum}, 1, 'status tracknum ok' ); is( $track1->{type}, 'flc', 'status type ok' ); is( $track1->{year}, 0, 'status year ok' ); # Test a remote stream _jsonrpc_get( $player, [ 'playlist', 'play', 'http://soma.fm/groovesalad.pls' ] ); warn "# waiting for remote stream to connect\n"; sleep 5; # should be long enough to get metadata $res = _jsonrpc_get( $player, [ 'status', '-', 1, 'tags:uBJjKlax' ] ); _jsonrpc_get( $player, [ 'stop' ] ); like( $res->{current_title}, qr/Groove Salad/, 'remote current_title ok' ); is( $res->{remote}, 1, 'remote ok' ); $track1 = $res->{playlist_loop}->[0]; ok( exists $track1->{artist}, 'remote track artist ok' ); is( $track1->{coverart}, 0, 'remote track coverart ok' ); like( $track1->{id}, qr/^\-\d+$/, 'remote track id is negative' ); is( $track1->{remote}, 1, 'remote track remote ok' ); ok( exists $track1->{title}, 'remote track title ok' ); like( $track1->{url}, qr/soma/, 'remote track url ok' ); } END { # Make sure copied files are put back in the right place if we abort my $iter = File::Next::files( { file_filter => sub { $_ =~ /\.bak$/ }, }, catdir($Bin, '..', 'data', 'scanner') ); while ( my $file = $iter->() ) { _restore($file); } if ( $server ) { $server->die; } # XXX Cleanup #rmtree $tmpdir if -d $tmpdir; } sub _save_prefs { my $prefs = shift || {}; my $playlistdir = catdir($Bin, '..', 'data', 'scanner', 'playlists'); $playlistdir = Path::Class::Dir->new($playlistdir)->resolve->stringify; # Write stub prefs file to point to our directories $prefs->{audiodir} ||= $audiodir; $prefs->{playlistdir} ||= $playlistdir; $prefs->{cachedir} ||= $tmpdir; $prefs->{librarycachedir} ||= $tmpdir; $prefs->{autorescan} = 0; YAML::Syck::DumpFile( catfile($tmpdir, 'server.prefs'), $prefs ); } sub _scan { # Launch the scanner chdir $SERVERDIR; warn "# Running scanner...\n"; =pod # move old scanner.log out of the way for debugging my $log = catfile($tmpdir, 'scanner.log'); if ( -e $log ) { require Time::HiRes; File::Copy::move( $log, "$log." . Time::HiRes::time() ); } =cut # silence any output open OLD_STDERR, '>&STDERR'; open OLD_STDOUT, '>&STDOUT'; open STDOUT, '>/dev/null'; open STDERR, '>/dev/null'; system( "$^X scanner.pl --prefsdir $tmpdir --logdir $tmpdir --dbtype SQLite --debug scan.scanner,scan.import " . join( " ", @_ ) ); open STDOUT, '>&OUT_STDOUT'; open STDERR, '>&OLD_STDERR'; } sub _server { # Launch the server chdir $SERVERDIR; warn "# Starting server...\n"; # $ENV{NYTPROF} = "start=no"; # Note --failsafe is on to prevent the iTunes plugin from scanning stuff my $proc = Proc::Background->new( "$^X slimserver.pl --prefsdir $tmpdir --logdir $tmpdir --dbtype SQLite --failsafe --debug scan.scanner,artwork " . join( " ", @_ ) ); # Wait for the server to startup while ( !_check_port( 'localhost', 9000 ) ) { sleep 1; } # Tell server to change SQLite locking mode so we can read the database _jsonrpc_get( [ 'pragma', 'locking_mode = NORMAL' ] ); warn "# Server started\n"; return $proc; } sub _gdresized { # Launch gdresized chdir $SERVERDIR; warn "# Starting gdresized...\n"; my $proc = Proc::Background->new( "$^X gdresized.pl " . join( " ", @_ ) ); # Wait for it to startup and create the socket while ( !-e '/tmp/sbs_artwork' ) { sleep 1; } return $proc; } sub _check_port { my ( $host, $port ) = @_; my $remote = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => $host, PeerPort => $port, ); if ( $remote ) { close $remote; return 1; } else { return; } } # Return a single row sub _row { my ( $sql, $attrs, $db ) = @_; $db ||= 'squeezebox-scanner.db'; $db = catfile($tmpdir, $db); my $dbh = DBI->connect( "dbi:SQLite:dbname=${db}", ) or die $DBI::errstr; $dbh->{RaiseError} = 1; return $dbh->selectrow_hashref( $sql, {}, @{ $attrs || [] } ); } # Return multiple rows sub _rows { my ( $sql, $attrs, $db ) = @_; $db ||= 'squeezebox-scanner.db'; $db = catfile($tmpdir, $db); my $dbh = DBI->connect( "dbi:SQLite:dbname=${db}", ) or die $DBI::errstr; $dbh->{RaiseError} = 1; my $rows = $dbh->selectall_arrayref( $sql, { Slice => {} }, @{ $attrs || [] } ); return wantarray ? @{$rows} : $rows; } # Return HTTP::Response object for path sub _http_get { my $path = shift; my $ua = LWP::UserAgent->new( timeout => 5 ); return $ua->get("http://localhost:9000${path}"); } sub _jsonrpc_get { my $cmd = shift; my $client = JSON::RPC::Client->new; my $uri = 'http://localhost:9000/jsonrpc.js'; # Support optional initial player param my $player = ''; if ( !ref $cmd ) { $player = $cmd; $cmd = shift; } my $res = $client->call( $uri, { method => 'slim.request', params => [ $player, $cmd ] } ); if ( $res && $res->is_success ) { return $res->content->{result}; } return; } sub _backup { my $file = shift; File::Copy::move( $file, "$file.bak" ) or die "Unable to backup $file: $!"; } sub _restore { my $file = shift; my $orig = $file; $orig =~ s/\.bak//; File::Copy::move( $file, $orig ) or die "Unable to restore $file: $!"; } sub _load { my $path = shift; open my $fh, '<', catfile( $FindBin::Bin, '..', 'data', 'images', $path ) or die "Can't open $path: $!\n"; my $data = do { local $/; <$fh> }; close $fh; return \$data; } sub _compare { my ( $test, $path ) = @_; my $ref = _load($path); # XXX: This might break next time we update GD return $$ref eq ( ref $test eq 'SCALAR' ? $$test : $test ); } # For creating test images sub _out { my $ref = shift; open my $fh, '>', catfile($Bin, 'out.jpg'); print $fh ref $ref eq 'SCALAR' ? $$ref : $ref; close $fh; } # Require a player to connect to the server sub _require_player { while (1) { warn "# Please connect a player to this server...\n"; my $player = _jsonrpc_get( [ 'player', 'id', '0', '?' ] ); if ( $player && $player->{_id} ) { warn "# Using player " . $player->{_id} . "\n"; return $player->{_id}; } sleep 1; } } sub _url_from_path { my $uri = URI::file->new(shift); $uri->host(''); return $uri->as_string; }