feat(share) : recherche globale récursive par nom de fichier dans le partage SMB
Endpoint GET /api/share/search?q= parcourant tout le partage en largeur (garde-fous 200 résultats / 2000 dossiers). Le champ de l'explorateur déclenche une recherche globale debouncée dès 2 caractères (filtre local en deçà), avec affichage du dossier parent de chaque résultat.
This commit is contained in:
@@ -16,8 +16,13 @@ use Icewind\SMB\ServerFactory;
|
||||
use Symfony\Component\Mime\MimeTypes;
|
||||
use Throwable;
|
||||
|
||||
use function count;
|
||||
|
||||
final class SmbFileSource implements FileSource
|
||||
{
|
||||
/** Garde-fou : nombre maximum de dossiers explorés par recherche (évite de bloquer sur un très gros partage). */
|
||||
private const int SEARCH_MAX_DIRS = 2000;
|
||||
|
||||
public function __construct(
|
||||
private readonly ShareConfigurationRepository $configRepository,
|
||||
private readonly TokenEncryptor $tokenEncryptor,
|
||||
@@ -38,17 +43,60 @@ final class SmbFileSource implements FileSource
|
||||
|
||||
$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);
|
||||
});
|
||||
$this->sortEntries($entries);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function search(string $query, int $limit = 200): array
|
||||
{
|
||||
$needle = trim($query);
|
||||
|
||||
if ('' === $needle) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$config = $this->requireUsableConfig();
|
||||
$share = $this->connect($config);
|
||||
$base = (string) $config->getBasePath();
|
||||
|
||||
$results = [];
|
||||
$queue = ['']; // chemins relatifs des dossiers à explorer, racine en premier (parcours en largeur)
|
||||
$visitedDirs = 0;
|
||||
|
||||
while ([] !== $queue && count($results) < $limit && $visitedDirs < self::SEARCH_MAX_DIRS) {
|
||||
$relative = array_shift($queue);
|
||||
$full = $this->pathResolver->fullPath($base, $relative);
|
||||
|
||||
try {
|
||||
$infos = $share->dir($full);
|
||||
} catch (Throwable) {
|
||||
continue; // dossier illisible (droits, lien mort…) : on l'ignore et on poursuit
|
||||
}
|
||||
++$visitedDirs;
|
||||
|
||||
foreach ($infos as $info) {
|
||||
$entry = $this->toEntry($info, $relative);
|
||||
|
||||
if ($entry->isDir) {
|
||||
$queue[] = $entry->path;
|
||||
}
|
||||
|
||||
if (false !== mb_stripos($entry->name, $needle)) {
|
||||
$results[] = $entry;
|
||||
|
||||
if (count($results) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->sortEntries($results);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function read(string $relativePath)
|
||||
{
|
||||
$config = $this->requireUsableConfig();
|
||||
@@ -108,6 +156,22 @@ final class SmbFileSource implements FileSource
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trie en place : dossiers d'abord, puis tri alphabétique insensible à la casse.
|
||||
*
|
||||
* @param FileEntry[] $entries
|
||||
*/
|
||||
private function sortEntries(array &$entries): void
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private function toEntry(IFileInfo $info, string $parentRelative): FileEntry
|
||||
{
|
||||
$parent = '' === $parentRelative ? '' : rtrim($parentRelative, '/').'/';
|
||||
|
||||
Reference in New Issue
Block a user