2015-12-13 04:42:28 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2016-02-04 15:48:15 +00:00
|
|
|
use App\Events\LibraryChanged;
|
2021-12-06 16:12:47 +00:00
|
|
|
use App\Events\MediaSyncCompleted;
|
2016-02-04 15:04:53 +00:00
|
|
|
use App\Libraries\WatchRecord\WatchRecordInterface;
|
2015-12-13 04:42:28 +00:00
|
|
|
use App\Models\Song;
|
2022-07-07 10:45:47 +00:00
|
|
|
use App\Repositories\SettingRepository;
|
2018-08-29 06:15:11 +00:00
|
|
|
use App\Repositories\SongRepository;
|
2022-07-29 10:51:20 +00:00
|
|
|
use App\Values\SyncResultCollection;
|
2019-01-01 11:53:20 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2018-08-24 15:27:19 +00:00
|
|
|
use SplFileInfo;
|
2016-02-04 15:48:15 +00:00
|
|
|
use Symfony\Component\Finder\Finder;
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2018-08-19 15:26:34 +00:00
|
|
|
class MediaSyncService
|
2015-12-13 04:42:28 +00:00
|
|
|
{
|
2022-07-29 10:51:20 +00:00
|
|
|
/** @var array<array-key, callable> */
|
|
|
|
private array $events = [];
|
|
|
|
|
2018-08-29 06:15:11 +00:00
|
|
|
public function __construct(
|
2022-07-07 10:45:47 +00:00
|
|
|
private SettingRepository $settingRepository,
|
2022-06-10 10:47:46 +00:00
|
|
|
private SongRepository $songRepository,
|
|
|
|
private FileSynchronizer $fileSynchronizer,
|
|
|
|
private Finder $finder,
|
|
|
|
private LoggerInterface $logger
|
2018-08-29 07:07:44 +00:00
|
|
|
) {
|
2018-08-19 09:05:33 +00:00
|
|
|
}
|
|
|
|
|
2016-03-22 08:22:39 +00:00
|
|
|
/**
|
2022-07-07 10:45:47 +00:00
|
|
|
* @param array<string> $ignores The tags to ignore.
|
2020-12-22 20:11:22 +00:00
|
|
|
* Only taken into account for existing records.
|
|
|
|
* New records will have all tags synced in regardless.
|
|
|
|
* @param bool $force Whether to force syncing even unchanged files
|
2015-12-13 04:42:28 +00:00
|
|
|
*/
|
2022-07-29 10:51:20 +00:00
|
|
|
public function sync(array $ignores = [], bool $force = false): SyncResultCollection
|
2022-07-07 10:45:47 +00:00
|
|
|
{
|
|
|
|
/** @var string $mediaPath */
|
|
|
|
$mediaPath = $this->settingRepository->getByKey('media_path');
|
|
|
|
|
2018-08-29 09:41:24 +00:00
|
|
|
$this->setSystemRequirements();
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2022-07-29 10:51:20 +00:00
|
|
|
$results = SyncResultCollection::create();
|
2022-07-07 10:45:47 +00:00
|
|
|
$songPaths = $this->gatherFiles($mediaPath);
|
2022-07-29 10:51:20 +00:00
|
|
|
|
|
|
|
if (isset($this->events['paths-gathered'])) {
|
|
|
|
$this->events['paths-gathered']($songPaths);
|
|
|
|
}
|
2017-06-03 23:21:50 +00:00
|
|
|
|
|
|
|
foreach ($songPaths as $path) {
|
2022-07-07 10:45:47 +00:00
|
|
|
$result = $this->fileSynchronizer->setFile($path)->sync($ignores, $force);
|
2022-07-29 10:51:20 +00:00
|
|
|
$results->add($result);
|
2017-06-03 23:21:50 +00:00
|
|
|
|
2022-07-29 10:51:20 +00:00
|
|
|
if (isset($this->events['progress'])) {
|
|
|
|
$this->events['progress']($result);
|
2015-12-13 04:42:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-29 10:51:20 +00:00
|
|
|
event(new MediaSyncCompleted($results));
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2021-12-10 15:27:06 +00:00
|
|
|
// Trigger LibraryChanged, so that PruneLibrary handler is fired to prune the lib.
|
2016-02-02 07:47:00 +00:00
|
|
|
event(new LibraryChanged());
|
2022-07-29 10:51:20 +00:00
|
|
|
|
|
|
|
return $results;
|
2016-02-02 07:47:00 +00:00
|
|
|
}
|
2015-12-13 04:42:28 +00:00
|
|
|
|
2016-02-02 07:47:00 +00:00
|
|
|
/**
|
|
|
|
* Gather all applicable files in a given directory.
|
|
|
|
*
|
|
|
|
* @param string $path The directory's full path
|
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @return array<SplFileInfo>
|
2016-02-02 07:47:00 +00:00
|
|
|
*/
|
2018-08-24 15:27:19 +00:00
|
|
|
public function gatherFiles(string $path): array
|
2016-02-02 07:47:00 +00:00
|
|
|
{
|
2017-04-23 16:01:02 +00:00
|
|
|
return iterator_to_array(
|
2018-08-29 09:41:24 +00:00
|
|
|
$this->finder->create()
|
2017-04-23 16:01:02 +00:00
|
|
|
->ignoreUnreadableDirs()
|
2022-07-05 13:47:26 +00:00
|
|
|
->ignoreDotFiles((bool) config('koel.ignore_dot_files')) // https://github.com/koel/koel/issues/450
|
2017-04-23 16:01:02 +00:00
|
|
|
->files()
|
|
|
|
->followLinks()
|
2020-10-03 09:25:19 +00:00
|
|
|
->name('/\.(mp3|wav|ogg|m4a|flac|opus)$/i')
|
2017-04-23 16:01:02 +00:00
|
|
|
->in($path)
|
|
|
|
);
|
2015-12-13 04:42:28 +00:00
|
|
|
}
|
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
public function syncByWatchRecord(WatchRecordInterface $record): void
|
2016-02-02 07:47:00 +00:00
|
|
|
{
|
2018-09-03 12:41:49 +00:00
|
|
|
$this->logger->info("New watch record received: '{$record->getPath()}'");
|
2017-06-03 23:21:50 +00:00
|
|
|
$record->isFile() ? $this->syncFileRecord($record) : $this->syncDirectoryRecord($record);
|
|
|
|
}
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
private function syncFileRecord(WatchRecordInterface $record): void
|
2017-06-03 23:21:50 +00:00
|
|
|
{
|
|
|
|
$path = $record->getPath();
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("'$path' is a file.");
|
2017-01-06 03:04:08 +00:00
|
|
|
|
2017-06-03 23:21:50 +00:00
|
|
|
if ($record->isDeleted()) {
|
2018-08-29 09:41:24 +00:00
|
|
|
$this->handleDeletedFileRecord($path);
|
2020-12-22 20:11:22 +00:00
|
|
|
} elseif ($record->isNewOrModified()) {
|
2018-08-29 09:41:24 +00:00
|
|
|
$this->handleNewOrModifiedFileRecord($path);
|
2016-02-02 07:47:00 +00:00
|
|
|
}
|
2017-06-03 23:21:50 +00:00
|
|
|
}
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2018-08-24 15:27:19 +00:00
|
|
|
private function syncDirectoryRecord(WatchRecordInterface $record): void
|
2017-06-03 23:21:50 +00:00
|
|
|
{
|
|
|
|
$path = $record->getPath();
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("'$path' is a directory.");
|
2016-02-02 07:47:00 +00:00
|
|
|
|
2016-02-04 15:04:53 +00:00
|
|
|
if ($record->isDeleted()) {
|
2018-08-29 09:41:24 +00:00
|
|
|
$this->handleDeletedDirectoryRecord($path);
|
2016-02-04 15:04:53 +00:00
|
|
|
} elseif ($record->isNewOrModified()) {
|
2018-08-29 09:41:24 +00:00
|
|
|
$this->handleNewOrModifiedDirectoryRecord($path);
|
2016-02-02 07:47:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 09:41:24 +00:00
|
|
|
private function setSystemRequirements(): void
|
|
|
|
{
|
|
|
|
if (!app()->runningInConsole()) {
|
|
|
|
set_time_limit(config('koel.sync.timeout'));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config('koel.memory_limit')) {
|
2020-12-22 20:11:22 +00:00
|
|
|
ini_set('memory_limit', config('koel.memory_limit') . 'M');
|
2018-08-29 09:41:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleDeletedFileRecord(string $path): void
|
|
|
|
{
|
2020-12-22 20:11:22 +00:00
|
|
|
$song = $this->songRepository->getOneByPath($path);
|
|
|
|
|
|
|
|
if ($song) {
|
2018-08-29 09:41:24 +00:00
|
|
|
$song->delete();
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("$path deleted.");
|
2018-08-29 09:41:24 +00:00
|
|
|
event(new LibraryChanged());
|
|
|
|
} else {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("$path doesn't exist in our database--skipping.");
|
2018-08-29 09:41:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleNewOrModifiedFileRecord(string $path): void
|
|
|
|
{
|
2022-07-05 13:47:26 +00:00
|
|
|
$result = $this->fileSynchronizer->setFile($path)->sync();
|
2018-08-29 09:41:24 +00:00
|
|
|
|
2022-07-29 10:51:20 +00:00
|
|
|
if ($result->isSuccess()) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("Synchronized $path");
|
2018-08-29 09:41:24 +00:00
|
|
|
} else {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("Failed to synchronized $path. Maybe an invalid file?");
|
2018-08-29 09:41:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
event(new LibraryChanged());
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleDeletedDirectoryRecord(string $path): void
|
|
|
|
{
|
2022-08-09 18:45:11 +00:00
|
|
|
$count = Song::query()->inDirectory($path)->delete();
|
2020-12-22 20:11:22 +00:00
|
|
|
|
|
|
|
if ($count) {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("Deleted $count song(s) under $path");
|
2018-08-29 09:41:24 +00:00
|
|
|
|
|
|
|
event(new LibraryChanged());
|
|
|
|
} else {
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("$path is empty--no action needed.");
|
2018-08-29 09:41:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleNewOrModifiedDirectoryRecord(string $path): void
|
|
|
|
{
|
|
|
|
foreach ($this->gatherFiles($path) as $file) {
|
2022-07-05 13:47:26 +00:00
|
|
|
$this->fileSynchronizer->setFile($file)->sync();
|
2018-08-29 09:41:24 +00:00
|
|
|
}
|
|
|
|
|
2018-08-31 13:47:15 +00:00
|
|
|
$this->logger->info("Synced all song(s) under $path");
|
2018-08-29 09:41:24 +00:00
|
|
|
|
|
|
|
event(new LibraryChanged());
|
|
|
|
}
|
2022-07-29 10:51:20 +00:00
|
|
|
|
|
|
|
public function on(string $event, callable $callback): void
|
|
|
|
{
|
|
|
|
$this->events[$event] = $callback;
|
|
|
|
}
|
2015-12-13 04:42:28 +00:00
|
|
|
}
|