133 lines
4.2 KiB
PHP
133 lines
4.2 KiB
PHP
<?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,
|
|
);
|
|
}
|
|
}
|