2015-12-13 04:42:28 +00:00
|
|
|
<?php
|
|
|
|
|
2021-12-06 16:12:47 +00:00
|
|
|
namespace Tests\Integration\Services;
|
2017-02-14 06:53:02 +00:00
|
|
|
|
2016-02-02 07:47:00 +00:00
|
|
|
use App\Events\LibraryChanged;
|
2024-01-04 21:51:32 +00:00
|
|
|
use App\Events\MediaScanCompleted;
|
2016-02-04 15:04:53 +00:00
|
|
|
use App\Libraries\WatchRecord\InotifyWatchRecord;
|
2015-12-13 04:42:28 +00:00
|
|
|
use App\Models\Album;
|
2016-04-24 04:37:04 +00:00
|
|
|
use App\Models\Artist;
|
2022-07-07 10:45:57 +00:00
|
|
|
use App\Models\Setting;
|
2015-12-13 04:42:28 +00:00
|
|
|
use App\Models\Song;
|
2024-01-04 21:51:32 +00:00
|
|
|
use App\Models\User;
|
|
|
|
use App\Services\FileScanner;
|
|
|
|
use App\Services\MediaScanner;
|
|
|
|
use App\Values\ScanConfiguration;
|
2019-10-09 17:36:22 +00:00
|
|
|
use getID3;
|
2022-07-07 10:45:57 +00:00
|
|
|
use Illuminate\Support\Arr;
|
2020-12-22 23:01:49 +00:00
|
|
|
use Mockery;
|
2024-01-09 18:34:40 +00:00
|
|
|
use Tests\TestCase;
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
class MediaScannerTest extends TestCase
|
2015-12-13 04:42:28 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
private MediaScanner $scanner;
|
2018-08-19 09:05:33 +00:00
|
|
|
|
2019-07-22 07:03:23 +00:00
|
|
|
public function setUp(): void
|
2018-08-19 09:05:33 +00:00
|
|
|
{
|
|
|
|
parent::setUp();
|
2020-11-14 16:57:25 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
Setting::set('media_path', realpath($this->mediaPath));
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner = app(MediaScanner::class);
|
2018-08-19 09:05:33 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
private function path($subPath): string
|
|
|
|
{
|
|
|
|
return realpath($this->mediaPath . $subPath);
|
|
|
|
}
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testScan(): void
|
2015-12-13 04:42:28 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->expectsEvents(MediaScanCompleted::class);
|
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Standard mp3 files under root path should be recognized
|
2021-12-06 16:12:47 +00:00
|
|
|
self::assertDatabaseHas(Song::class, [
|
2022-07-07 10:45:57 +00:00
|
|
|
'path' => $this->path('/full.mp3'),
|
2016-03-20 14:14:02 +00:00
|
|
|
'track' => 5,
|
2024-01-04 21:51:32 +00:00
|
|
|
'owner_id' => $owner->id,
|
2016-03-20 14:14:02 +00:00
|
|
|
]);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Ogg files and audio files in subdirectories should be recognized
|
2024-01-04 21:51:32 +00:00
|
|
|
self::assertDatabaseHas(Song::class, [
|
|
|
|
'path' => $this->path('/subdir/back-in-black.ogg'),
|
|
|
|
'owner_id' => $owner->id,
|
|
|
|
]);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2016-08-07 10:30:55 +00:00
|
|
|
// GitHub issue #380. folder.png should be copied and used as the cover for files
|
|
|
|
// under subdir/
|
2022-07-07 10:45:57 +00:00
|
|
|
/** @var Song $song */
|
2022-08-09 18:45:11 +00:00
|
|
|
$song = Song::query()->where('path', $this->path('/subdir/back-in-black.ogg'))->first();
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertNotEmpty($song->album->cover);
|
2016-08-07 10:30:55 +00:00
|
|
|
|
2016-01-26 06:30:48 +00:00
|
|
|
// File search shouldn't be case-sensitive.
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseHas(Song::class, ['path' => $this->path('/subdir/no-name.mp3')]);
|
2016-01-26 06:30:48 +00:00
|
|
|
|
2015-12-13 04:42:28 +00:00
|
|
|
// Non-audio files shouldn't be recognized
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseMissing(Song::class, ['path' => $this->path('/rubbish.log')]);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Broken/corrupted audio files shouldn't be recognized
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseMissing(Song::class, ['path' => $this->path('/fake.mp3')]);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Artists should be created
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseHas(Artist::class, ['name' => 'Cuckoo']);
|
|
|
|
self::assertDatabaseHas(Artist::class, ['name' => 'Koel']);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Albums should be created
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseHas(Album::class, ['name' => 'Koel Testing Vol. 1']);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
|
|
|
// Albums and artists should be correctly linked
|
2022-07-07 10:45:57 +00:00
|
|
|
/** @var Album $album */
|
2022-08-09 18:45:11 +00:00
|
|
|
$album = Album::query()->where('name', 'Koel Testing Vol. 1')->first();
|
2022-10-07 14:25:44 +00:00
|
|
|
self::assertSame('Koel', $album->artist->name);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2016-04-24 04:37:04 +00:00
|
|
|
// Compilation albums, artists and songs must be recognized
|
2022-07-07 10:45:57 +00:00
|
|
|
/** @var Song $song */
|
2022-08-09 18:45:11 +00:00
|
|
|
$song = Song::query()->where('title', 'This song belongs to a compilation')->first();
|
2022-10-12 09:27:35 +00:00
|
|
|
self::assertFalse($song->album_artist->is($song->artist));
|
|
|
|
self::assertSame('Koel', $song->album_artist->name);
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertSame('Cuckoo', $song->artist->name);
|
|
|
|
}
|
2016-04-24 04:37:04 +00:00
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testModifiedFileIsRescanned(): void
|
2022-07-07 10:45:57 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->expectsEvents(MediaScanCompleted::class);
|
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
$config = ScanConfiguration::make(owner: $owner);
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2022-08-09 18:45:11 +00:00
|
|
|
/** @var Song $song */
|
|
|
|
$song = Song::query()->first();
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
touch($song->path, $time = time() + 1000);
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertSame($time, $song->refresh()->mtime);
|
2015-12-13 04:42:28 +00:00
|
|
|
}
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testRescanWithoutForceDoesNotResetData(): void
|
2016-03-22 08:22:39 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->expectsEvents(MediaScanCompleted::class);
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
$config = ScanConfiguration::make(owner: $owner);
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2022-08-09 18:45:11 +00:00
|
|
|
/** @var Song $song */
|
|
|
|
$song = Song::query()->first();
|
2016-03-22 08:22:39 +00:00
|
|
|
|
|
|
|
$song->update([
|
|
|
|
'title' => "It's John Cena!",
|
|
|
|
'lyrics' => 'Booom Wroooom',
|
|
|
|
]);
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
$song->refresh();
|
|
|
|
self::assertSame("It's John Cena!", $song->title);
|
|
|
|
self::assertSame('Booom Wroooom', $song->lyrics);
|
|
|
|
}
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testForceScanResetsData(): void
|
2022-07-07 10:45:57 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->expectsEvents(MediaScanCompleted::class);
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
2022-07-07 10:45:57 +00:00
|
|
|
|
2022-08-09 18:45:11 +00:00
|
|
|
/** @var Song $song */
|
|
|
|
$song = Song::query()->first();
|
2022-07-07 10:45:57 +00:00
|
|
|
|
|
|
|
$song->update([
|
|
|
|
'title' => "It's John Cena!",
|
|
|
|
'lyrics' => 'Booom Wroooom',
|
|
|
|
]);
|
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $anotherOwner */
|
|
|
|
$anotherOwner = User::factory()->admin()->create();
|
|
|
|
$this->scanner->scan(ScanConfiguration::make(owner: $anotherOwner, force: true));
|
2022-07-07 10:45:57 +00:00
|
|
|
|
|
|
|
$song->refresh();
|
|
|
|
|
|
|
|
self::assertNotSame("It's John Cena!", $song->title);
|
|
|
|
self::assertNotSame('Booom Wroooom', $song->lyrics);
|
2024-01-04 21:51:32 +00:00
|
|
|
// make sure the user is not changed
|
|
|
|
self::assertSame($owner->id, $song->owner_id);
|
2016-03-22 08:22:39 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testScanWithIgnoredTags(): void
|
2016-03-22 08:22:39 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->expectsEvents(MediaScanCompleted::class);
|
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2022-08-09 18:45:11 +00:00
|
|
|
/** @var Song $song */
|
|
|
|
$song = Song::query()->first();
|
2016-03-22 08:22:39 +00:00
|
|
|
|
|
|
|
$song->update([
|
|
|
|
'title' => "It's John Cena!",
|
|
|
|
'lyrics' => 'Booom Wroooom',
|
|
|
|
]);
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan(ScanConfiguration::make(owner: $owner, ignores: ['title'], force: true));
|
2016-03-22 08:22:39 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
$song->refresh();
|
|
|
|
|
|
|
|
self::assertSame("It's John Cena!", $song->title);
|
|
|
|
self::assertNotSame('Booom Wroooom', $song->lyrics);
|
2016-03-22 08:22:39 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testScanAllTagsForNewFilesRegardlessOfIgnoredOption(): void
|
2016-07-05 10:14:12 +00:00
|
|
|
{
|
2024-01-07 12:43:10 +00:00
|
|
|
$this->expectsEvents(MediaScanCompleted::class);
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
2017-08-05 21:20:05 +00:00
|
|
|
|
2022-08-09 18:45:11 +00:00
|
|
|
/** @var Song $song */
|
|
|
|
$song = Song::query()->first();
|
|
|
|
|
2016-07-05 10:14:12 +00:00
|
|
|
$song->delete();
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan(ScanConfiguration::make(
|
|
|
|
owner: $owner,
|
|
|
|
ignores: ['title', 'disc', 'track'],
|
|
|
|
force: true
|
|
|
|
));
|
2017-06-03 16:21:53 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
// Song should be added back with all info
|
|
|
|
self::assertEquals(
|
2022-08-09 18:45:11 +00:00
|
|
|
Arr::except(Song::query()->where('path', $song->path)->first()->toArray(), ['id', 'created_at']),
|
2022-08-01 10:42:33 +00:00
|
|
|
Arr::except($song->toArray(), ['id', 'created_at'])
|
2022-07-07 10:45:57 +00:00
|
|
|
);
|
2016-07-05 10:14:12 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testScanAddedSongViaWatch(): void
|
2016-02-02 07:47:00 +00:00
|
|
|
{
|
2017-01-06 03:04:08 +00:00
|
|
|
$this->expectsEvents(LibraryChanged::class);
|
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
$path = $this->path('/blank.mp3');
|
2024-01-04 21:51:32 +00:00
|
|
|
|
|
|
|
$this->scanner->scanWatchRecord(
|
|
|
|
new InotifyWatchRecord("CLOSE_WRITE,CLOSE $path"),
|
2024-01-07 12:43:10 +00:00
|
|
|
ScanConfiguration::make(owner: $owner)
|
2024-01-04 21:51:32 +00:00
|
|
|
);
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseHas(Song::class, ['path' => $path]);
|
2016-02-02 07:47:00 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testScanDeletedSongViaWatch(): void
|
2016-02-02 07:47:00 +00:00
|
|
|
{
|
|
|
|
$this->expectsEvents(LibraryChanged::class);
|
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
|
2022-08-09 18:45:11 +00:00
|
|
|
/** @var Song $song */
|
2024-01-10 23:11:45 +00:00
|
|
|
$song = Song::factory()->create();
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scanWatchRecord(
|
|
|
|
new InotifyWatchRecord("DELETE $song->path"),
|
2024-01-07 12:43:10 +00:00
|
|
|
ScanConfiguration::make(owner: $owner)
|
2024-01-04 21:51:32 +00:00
|
|
|
);
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertModelMissing($song);
|
2016-02-02 07:47:00 +00:00
|
|
|
}
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
public function testScanDeletedDirectoryViaWatch(): void
|
2016-02-02 07:47:00 +00:00
|
|
|
{
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->expectsEvents(LibraryChanged::class, MediaScanCompleted::class);
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
$config = ScanConfiguration::make(owner: $owner);
|
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
|
|
|
$this->scanner->scanWatchRecord(new InotifyWatchRecord("MOVED_FROM,ISDIR $this->mediaPath/subdir"), $config);
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2023-12-25 17:15:49 +00:00
|
|
|
self::assertDatabaseMissing(Song::class, ['path' => $this->path('/subdir/sic.mp3')]);
|
|
|
|
self::assertDatabaseMissing(Song::class, ['path' => $this->path('/subdir/no-name.mp3')]);
|
|
|
|
self::assertDatabaseMissing(Song::class, ['path' => $this->path('/subdir/back-in-black.mp3')]);
|
2016-02-02 07:47:00 +00:00
|
|
|
}
|
2016-05-05 15:03:30 +00:00
|
|
|
|
2020-11-14 16:57:25 +00:00
|
|
|
public function testHtmlEntities(): void
|
2016-05-05 15:03:30 +00:00
|
|
|
{
|
2022-07-07 10:45:57 +00:00
|
|
|
$path = $this->path('/songs/blank.mp3');
|
2022-08-31 21:27:40 +00:00
|
|
|
$analyzed = [
|
|
|
|
'filenamepath' => $path,
|
|
|
|
'tags' => [
|
|
|
|
'id3v2' => [
|
|
|
|
'title' => ['水谷広実'],
|
|
|
|
'album' => ['小岩井こ Random'],
|
|
|
|
'artist' => ['佐倉綾音 Unknown'],
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'encoding' => 'UTF-8',
|
|
|
|
'playtime_seconds' => 100,
|
|
|
|
];
|
2022-07-07 10:45:57 +00:00
|
|
|
|
2020-12-22 23:01:49 +00:00
|
|
|
$this->swap(
|
|
|
|
getID3::class,
|
|
|
|
Mockery::mock(getID3::class, [
|
2022-08-31 21:27:40 +00:00
|
|
|
'CopyTagsToComments' => $analyzed,
|
|
|
|
'analyze' => $analyzed,
|
2020-12-22 23:01:49 +00:00
|
|
|
])
|
|
|
|
);
|
2016-05-05 15:03:30 +00:00
|
|
|
|
2024-01-04 21:51:32 +00:00
|
|
|
/** @var FileScanner $fileScanner */
|
|
|
|
$fileScanner = app(FileScanner::class);
|
2024-01-06 11:31:50 +00:00
|
|
|
$info = $fileScanner->setFile($path)->getScanInformation();
|
2016-05-05 15:03:30 +00:00
|
|
|
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertSame('佐倉綾音 Unknown', $info->artistName);
|
|
|
|
self::assertSame('小岩井こ Random', $info->albumName);
|
|
|
|
self::assertSame('水谷広実', $info->title);
|
2016-05-05 15:03:30 +00:00
|
|
|
}
|
2016-09-17 15:56:58 +00:00
|
|
|
|
2020-11-14 16:57:25 +00:00
|
|
|
public function testOptionallyIgnoreHiddenFiles(): void
|
2016-09-17 15:56:58 +00:00
|
|
|
{
|
2024-01-07 12:43:10 +00:00
|
|
|
/** @var User $owner */
|
|
|
|
$owner = User::factory()->admin()->create();
|
|
|
|
$config = ScanConfiguration::make(owner: $owner);
|
2024-01-04 21:51:32 +00:00
|
|
|
|
2016-09-19 02:08:50 +00:00
|
|
|
config(['koel.ignore_dot_files' => false]);
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseHas(Album::class, ['name' => 'Hidden Album']);
|
2016-09-17 15:56:58 +00:00
|
|
|
|
2016-09-19 02:08:50 +00:00
|
|
|
config(['koel.ignore_dot_files' => true]);
|
2024-01-04 21:51:32 +00:00
|
|
|
$this->scanner->scan($config);
|
2022-07-07 10:45:57 +00:00
|
|
|
self::assertDatabaseMissing(Album::class, ['name' => 'Hidden Album']);
|
2016-09-17 15:56:58 +00:00
|
|
|
}
|
2015-12-13 04:42:28 +00:00
|
|
|
}
|