feat(share) : source de fichiers SMB (FileSource + SmbFileSource)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Share\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class ShareConnectionException extends RuntimeException {}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Share\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class ShareNotConfiguredException extends RuntimeException {}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Share;
|
||||
|
||||
final readonly class FileEntry
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $path,
|
||||
public bool $isDir,
|
||||
public int $size,
|
||||
public ?int $modifiedAt,
|
||||
public string $mimeType,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Share;
|
||||
|
||||
interface FileSource
|
||||
{
|
||||
/**
|
||||
* @return FileEntry[] dossiers d'abord, puis fichiers, triés par nom
|
||||
*/
|
||||
public function dir(string $relativePath): array;
|
||||
|
||||
/**
|
||||
* @return resource flux binaire en lecture
|
||||
*/
|
||||
public function read(string $relativePath);
|
||||
|
||||
public function test(): ShareTestResult;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Share;
|
||||
|
||||
final readonly class ShareTestResult
|
||||
{
|
||||
public function __construct(
|
||||
public bool $success,
|
||||
public ?string $message = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Share;
|
||||
|
||||
use App\Entity\ShareConfiguration;
|
||||
use App\Repository\ShareConfigurationRepository;
|
||||
use App\Service\Share\Exception\ShareConnectionException;
|
||||
use App\Service\Share\Exception\ShareNotConfiguredException;
|
||||
use App\Service\TokenEncryptor;
|
||||
use Icewind\SMB\BasicAuth;
|
||||
use Icewind\SMB\IFileInfo;
|
||||
use Icewind\SMB\IShare;
|
||||
use Icewind\SMB\ServerFactory;
|
||||
use Symfony\Component\Mime\MimeTypes;
|
||||
use Throwable;
|
||||
|
||||
final class SmbFileSource implements FileSource
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ShareConfigurationRepository $configRepository,
|
||||
private readonly TokenEncryptor $tokenEncryptor,
|
||||
private readonly SharePathResolver $pathResolver,
|
||||
) {}
|
||||
|
||||
public function dir(string $relativePath): array
|
||||
{
|
||||
$config = $this->requireUsableConfig();
|
||||
$share = $this->connect($config);
|
||||
$full = $this->pathResolver->fullPath((string) $config->getBasePath(), $relativePath);
|
||||
|
||||
try {
|
||||
$infos = $share->dir($full);
|
||||
} catch (Throwable $e) {
|
||||
throw new ShareConnectionException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$entries = array_map(fn (IFileInfo $i): FileEntry => $this->toEntry($i, $relativePath), $infos);
|
||||
|
||||
usort($entries, static function (FileEntry $a, FileEntry $b): int {
|
||||
if ($a->isDir !== $b->isDir) {
|
||||
return $a->isDir ? -1 : 1;
|
||||
}
|
||||
|
||||
return strcasecmp($a->name, $b->name);
|
||||
});
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function read(string $relativePath)
|
||||
{
|
||||
$config = $this->requireUsableConfig();
|
||||
$share = $this->connect($config);
|
||||
$full = $this->pathResolver->fullPath((string) $config->getBasePath(), $relativePath);
|
||||
|
||||
try {
|
||||
return $share->read($full);
|
||||
} catch (Throwable $e) {
|
||||
throw new ShareConnectionException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function test(): ShareTestResult
|
||||
{
|
||||
try {
|
||||
$config = $this->requireUsableConfig();
|
||||
$share = $this->connect($config);
|
||||
$share->dir($this->pathResolver->fullPath((string) $config->getBasePath(), ''));
|
||||
|
||||
return new ShareTestResult(true);
|
||||
} catch (ShareNotConfiguredException $e) {
|
||||
return new ShareTestResult(false, 'Configuration incomplète ou désactivée.');
|
||||
} catch (Throwable $e) {
|
||||
return new ShareTestResult(false, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function requireUsableConfig(): ShareConfiguration
|
||||
{
|
||||
$config = $this->configRepository->findSingleton();
|
||||
|
||||
if (null === $config || !$config->isUsable()) {
|
||||
throw new ShareNotConfiguredException('Share is not configured or disabled.');
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function connect(ShareConfiguration $config): IShare
|
||||
{
|
||||
$password = null !== $config->getEncryptedPassword()
|
||||
? $this->tokenEncryptor->decrypt($config->getEncryptedPassword())
|
||||
: '';
|
||||
|
||||
$auth = new BasicAuth(
|
||||
(string) $config->getUsername(),
|
||||
$config->getDomain() ?: 'WORKGROUP',
|
||||
$password,
|
||||
);
|
||||
$server = new ServerFactory()->createServer((string) $config->getHost(), $auth);
|
||||
|
||||
try {
|
||||
return $server->getShare((string) $config->getShareName());
|
||||
} catch (Throwable $e) {
|
||||
throw new ShareConnectionException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function toEntry(IFileInfo $info, string $parentRelative): FileEntry
|
||||
{
|
||||
$parent = '' === $parentRelative ? '' : rtrim($parentRelative, '/').'/';
|
||||
$path = $parent.$info->getName();
|
||||
$isDir = $info->isDirectory();
|
||||
|
||||
$mime = 'application/octet-stream';
|
||||
if (!$isDir) {
|
||||
$guessed = MimeTypes::getDefault()->getMimeTypes(pathinfo($info->getName(), PATHINFO_EXTENSION));
|
||||
$mime = $guessed[0] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
return new FileEntry(
|
||||
name: $info->getName(),
|
||||
path: $path,
|
||||
isDir: $isDir,
|
||||
size: $isDir ? 0 : $info->getSize(),
|
||||
modifiedAt: $info->getMTime(),
|
||||
mimeType: $mime,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user