6 changed files with 104 additions and 115 deletions
@ -0,0 +1,7 @@ |
|||||||
|
services: |
||||||
|
islandora_inplace_media.commands: |
||||||
|
class: Drupal\islandora_inplace_media\Commands\IslandoraInplaceMediaCommands |
||||||
|
arguments: |
||||||
|
- '@islandora_inplace_media.processor' |
||||||
|
tags: |
||||||
|
- { name: drush.command } |
||||||
@ -1,7 +1,7 @@ |
|||||||
name: 'Islandora Inplace Media' |
name: 'Islandora Inplace Media' |
||||||
type: module |
type: module |
||||||
description: 'Allows Uploaded files to be attched to exisitng nodes as media' |
description: 'Allows Uploaded files to be attached to existing nodes as media' |
||||||
package: Custom |
package: Islandora |
||||||
core_version_requirement: ^10 || ^11 |
core_version_requirement: ^10 || ^11 |
||||||
dependencies: |
dependencies: |
||||||
- islandora:islandora |
- islandora:islandora |
||||||
|
|||||||
@ -1,4 +1,13 @@ |
|||||||
services: |
services: |
||||||
islandora_inplace_media.utils: |
|
||||||
class: Drupal\islandora_inplace_media\Utils |
logger.channel.islandora_inplace_media: |
||||||
arguments: ['@entity_type.manager', '@file_system', '@logger.factory', '@entity_field.manager'] |
class: Drupal\Core\Logger\LoggerChannel |
||||||
|
factory: logger.factory:get |
||||||
|
arguments: ['islandora_inplace_media'] |
||||||
|
|
||||||
|
islandora_inplace_media.processor: |
||||||
|
class: Drupal\islandora_inplace_media\Service\InplaceMediaProcessor |
||||||
|
arguments: |
||||||
|
- '@file_system' |
||||||
|
- '@file.repository' |
||||||
|
- '@logger.channel.islandora_inplace_media' |
||||||
|
|||||||
@ -1,155 +1,77 @@ |
|||||||
<?php |
<?php |
||||||
|
|
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
namespace Drupal\islandora_inplace_media\Commands; |
namespace Drupal\islandora_inplace_media\Commands; |
||||||
|
|
||||||
use Drush\Commands\DrushCommands; |
use Drush\Commands\DrushCommands; |
||||||
use Drupal\Core\File\FileSystemInterface; |
use Drupal\islandora_inplace_media\Service\InplaceMediaProcessor; |
||||||
use Drupal\file\FileRepositoryInterface; |
|
||||||
use Drupal\Core\State\StateInterface; |
|
||||||
use Drupal\Core\Queue\QueueFactory; |
|
||||||
use Drupal\file\Entity\File; |
|
||||||
use Drupal\media\Entity\Media; |
|
||||||
use Drupal\Core\File\FileExists; |
|
||||||
use Symfony\Component\Console\Helper\ProgressBar; |
use Symfony\Component\Console\Helper\ProgressBar; |
||||||
use Psr\Log\LoggerInterface; |
|
||||||
|
|
||||||
class IslandoraInplaceMediaCommands extends DrushCommands { |
class IslandoraInplaceMediaCommands extends DrushCommands { |
||||||
|
|
||||||
protected FileSystemInterface $fileSystem; |
|
||||||
protected FileRepositoryInterface $fileRepository; |
|
||||||
protected LoggerInterface $logger; |
|
||||||
protected StateInterface $state; |
|
||||||
protected QueueFactory $queueFactory; |
|
||||||
|
|
||||||
public function __construct( |
public function __construct( |
||||||
FileSystemInterface $fileSystem, |
protected InplaceMediaProcessor $processor |
||||||
FileRepositoryInterface $fileRepository, |
|
||||||
LoggerInterface $logger, |
|
||||||
StateInterface $state, |
|
||||||
QueueFactory $queueFactory, |
|
||||||
) { |
) { |
||||||
parent::__construct(); |
parent::__construct(); |
||||||
$this->fileSystem = $fileSystem; |
|
||||||
$this->fileRepository = $fileRepository; |
|
||||||
$this->logger = $logger; |
|
||||||
$this->state = $state; |
|
||||||
$this->queueFactory = $queueFactory; |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Process media files (optionally queued and resumable). |
* Create Islandora media from files. |
||||||
* |
* |
||||||
* @command islandora:inplace-media |
* @command islandora:inplace-media |
||||||
* @aliases iim |
* @aliases iim |
||||||
* |
* |
||||||
* @option source_dir |
* @option source_dir |
||||||
|
* Source directory containing files. |
||||||
* @option destination_path |
* @option destination_path |
||||||
|
* Destination directory (public://, private://, or absolute). |
||||||
* @option media_type |
* @option media_type |
||||||
|
* Media bundle machine name. |
||||||
* @option media_use |
* @option media_use |
||||||
|
* Taxonomy term ID for field_media_use. |
||||||
* @option file_type |
* @option file_type |
||||||
* @option ownership |
* Media file field name. |
||||||
|
* @option limit |
||||||
|
* Maximum number of files to process. |
||||||
|
* @option offset |
||||||
|
* Number of files to skip before processing. |
||||||
* @option queue |
* @option queue |
||||||
* Queue files instead of processing immediately. |
* Queue files instead of processing immediately. |
||||||
* @option reset |
|
||||||
* Reset saved progress. |
|
||||||
*/ |
*/ |
||||||
public function inplaceMedia(array $options = [ |
public function run(array $options = [ |
||||||
'source_dir' => NULL, |
'source_dir' => NULL, |
||||||
'destination_path' => NULL, |
'destination_path' => NULL, |
||||||
'media_type' => NULL, |
'media_type' => NULL, |
||||||
'media_use' => NULL, |
'media_use' => NULL, |
||||||
'file_type' => 'field_media_file', |
'file_type' => 'field_media_file', |
||||||
'ownership' => NULL, |
'limit' => NULL, |
||||||
|
'offset' => 0, |
||||||
'queue' => FALSE, |
'queue' => FALSE, |
||||||
'reset' => FALSE, |
]) { |
||||||
]): void { |
$files = array_values(array_diff(scandir($options['source_dir']), ['.', '..'])); |
||||||
|
|
||||||
$files = array_diff(scandir($options['source_dir']), ['.', '..']); |
|
||||||
|
|
||||||
$job_id = hash('sha256', serialize($options)); |
|
||||||
$state_key = "islandora_inplace_media.progress.$job_id"; |
|
||||||
|
|
||||||
if ($options['reset']) { |
|
||||||
$this->state->delete($state_key); |
|
||||||
$this->output()->writeln('Progress reset.'); |
|
||||||
} |
|
||||||
|
|
||||||
$processed = $this->state->get($state_key, []); |
|
||||||
$remaining = array_values(array_diff($files, $processed)); |
|
||||||
|
|
||||||
if (empty($remaining)) { |
if ($options['offset']) { |
||||||
$this->output()->success('Nothing left to process.'); |
$files = array_slice($files, (int) $options['offset']); |
||||||
return; |
|
||||||
} |
} |
||||||
|
if ($options['limit']) { |
||||||
if ($options['queue']) { |
$files = array_slice($files, 0, (int) $options['limit']); |
||||||
$this->enqueueFiles($remaining, $options); |
|
||||||
$this->output()->success('Files queued for processing.'); |
|
||||||
return; |
|
||||||
} |
} |
||||||
|
|
||||||
$progress = new ProgressBar($this->output(), count($remaining)); |
$progress = new ProgressBar($this->output(), count($files)); |
||||||
$progress->start(); |
$progress->start(); |
||||||
|
|
||||||
foreach ($remaining as $file_name) { |
foreach ($files as $file) { |
||||||
$this->processFile($file_name, $options); |
if ($options['queue']) { |
||||||
$processed[] = $file_name; |
\Drupal::queue('islandora_inplace_media') |
||||||
$this->state->set($state_key, $processed); |
->createItem(['file' => $file, 'options' => $options]); |
||||||
|
} |
||||||
|
else { |
||||||
|
$this->processor->processFile($file, $options); |
||||||
|
} |
||||||
$progress->advance(); |
$progress->advance(); |
||||||
} |
} |
||||||
|
|
||||||
$progress->finish(); |
$progress->finish(); |
||||||
$this->output()->newLine(2); |
$this->output()->writeln(''); |
||||||
$this->output()->success('Processing complete.'); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Enqueue files for background processing. |
|
||||||
*/ |
|
||||||
protected function enqueueFiles(array $files, array $options): void { |
|
||||||
$queue = $this->queueFactory->get('islandora_inplace_media'); |
|
||||||
|
|
||||||
foreach ($files as $file) { |
|
||||||
$queue->createItem([ |
|
||||||
'file' => $file, |
|
||||||
'options' => $options, |
|
||||||
]); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Process a single file. |
|
||||||
*/ |
|
||||||
protected function processFile(string $file_name, array $build_data): void { |
|
||||||
$source = "{$build_data['source_dir']}/{$file_name}"; |
|
||||||
$dest = "{$build_data['destination_path']}/{$file_name}"; |
|
||||||
|
|
||||||
if (!file_exists($source)) { |
|
||||||
$this->logger->warning('Missing file @file', ['@file' => $source]); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
$path = ($source === $dest) |
|
||||||
? $dest |
|
||||||
: $this->fileSystem->copy($source, $dest, FileExists::Rename); |
|
||||||
|
|
||||||
$file = $this->fileRepository->loadByUri($path) |
|
||||||
?? File::create(['uri' => $path, 'status' => 1]); |
|
||||||
|
|
||||||
$file->save(); |
|
||||||
|
|
||||||
preg_match('/^(\d+)_/', $file_name, $m); |
|
||||||
$nid = $m[1] ?? NULL; |
|
||||||
|
|
||||||
Media::create([ |
|
||||||
'bundle' => $build_data['media_type'], |
|
||||||
'name' => $file_name, |
|
||||||
$build_data['file_type'] => ['target_id' => $file->id()], |
|
||||||
'field_media_use' => ['target_id' => $build_data['media_use']], |
|
||||||
'field_media_of' => $nid, |
|
||||||
])->save(); |
|
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,50 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\islandora_inplace_media\Service; |
||||||
|
|
||||||
|
use Drupal\Core\File\FileSystemInterface; |
||||||
|
use Drupal\file\FileRepositoryInterface; |
||||||
|
use Drupal\Core\File\FileExists; |
||||||
|
use Drupal\file\Entity\File; |
||||||
|
use Drupal\media\Entity\Media; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
class InplaceMediaProcessor { |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
protected FileSystemInterface $fileSystem, |
||||||
|
protected FileRepositoryInterface $fileRepository, |
||||||
|
protected LoggerInterface $logger, |
||||||
|
) {} |
||||||
|
|
||||||
|
public function processFile(string $file, array $opts): void { |
||||||
|
$source = "{$opts['source_dir']}/{$file}"; |
||||||
|
$dest = "{$opts['destination_path']}/{$file}"; |
||||||
|
|
||||||
|
if (!file_exists($source)) { |
||||||
|
$this->logger->warning('Missing file @file', ['@file' => $source]); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$uri = ($source === $dest) |
||||||
|
? $dest |
||||||
|
: $this->fileSystem->copy($source, $dest, FileExists::Rename); |
||||||
|
|
||||||
|
$fileEntity = $this->fileRepository->loadByUri($uri) |
||||||
|
?? File::create(['uri' => $uri, 'status' => 1]); |
||||||
|
|
||||||
|
$fileEntity->save(); |
||||||
|
|
||||||
|
preg_match('/^(\d+)_/', $file, $m); |
||||||
|
$nid = $m[1] ?? NULL; |
||||||
|
|
||||||
|
Media::create([ |
||||||
|
'bundle' => $opts['media_type'], |
||||||
|
'name' => $file, |
||||||
|
$opts['file_type'] => ['target_id' => $fileEntity->id()], |
||||||
|
'field_media_use' => ['target_id' => $opts['media_use']], |
||||||
|
'field_media_of' => $nid, |
||||||
|
])->save(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue