From c66dd6b94cd29ec16af7734f2544383569065d02 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 10 Jul 2024 10:42:17 +0300 Subject: [PATCH] implement local directory browser --- src/Entity/Browser/Container/Page/Content.php | 106 +++++++++----- src/Model/Filesystem.php | 132 ++++++++++++++++++ 2 files changed, 201 insertions(+), 37 deletions(-) diff --git a/src/Entity/Browser/Container/Page/Content.php b/src/Entity/Browser/Container/Page/Content.php index de12a724..c52e1a14 100644 --- a/src/Entity/Browser/Container/Page/Content.php +++ b/src/Entity/Browser/Container/Page/Content.php @@ -103,59 +103,91 @@ class Content { case 'file': - if (file_exists($address->getPath()) && is_readable($address->getPath())) + switch (true) { - switch ($address->getPath()) - { - case is_dir($address->getPath()): + // Try directory + case ( + $list = \Yggverse\Yoda\Model\Filesystem::getList( + $address->getPath() + ) + ): - // @TODO build fs listing + $map = []; - break; + foreach ($list as $item) + { + $map[] = trim( + sprintf( + '=> file://%s %s', + $item['path'], + $item['name'] . ( + $item['file'] ? null : '/' + ) + ) + ); + } - case str_ends_with($address->getPath(), '.gmi'): + $this->data->setGemtext( + implode( + PHP_EOL, + $map + ) . PHP_EOL + ); - $title = null; + $this->page->title->set( + basename( + $address->getPath() + ), + 'localhost' + ); - $this->data->setGemtext( - file_get_contents( // @TODO format relative links - $address->getPath() - ), - $title - ); + break; - if ($title) // detect title by document h1 - { - $this->page->title->set( - $title - ); - } + // Try open file by extension supported + case str_ends_with( + $address->getPath(), + '.gmi' + ): - break; + $title = null; - default: + $this->data->setGemtext( + file_get_contents( // @TODO format relative links + $address->getPath() + ), + $title + ); + if ($title) // detect title by document h1 + { $this->page->title->set( - 'Oops!', - 'file extension not supported' + $title, + 'localhost' ); + } - $this->data->setPlain( - 'File extension not supported' + else + { + $this->page->title->set( + basename( + $address->getPath() + ), + 'localhost' ); - } - } + } - else - { - $this->page->title->set( - 'Failure', - 'resource not found or not readable' - ); + break; - $this->data->setPlain( - 'Could not open file' - ); + default: + + $this->page->title->set( + 'Failure', + 'resource not found or not readable' + ); + + $this->data->setPlain( + 'Could not open location' + ); } break; diff --git a/src/Model/Filesystem.php b/src/Model/Filesystem.php index bbeab1cd..d80ed5c8 100644 --- a/src/Model/Filesystem.php +++ b/src/Model/Filesystem.php @@ -81,6 +81,112 @@ class Filesystem return $this->_base . $filename; } + + public static function getList( + ?string $dirname, + string $sort = 'name', + int $order = SORT_ASC, + int $method = SORT_STRING | SORT_NATURAL | SORT_FLAG_CASE + ): ?array + { + // Convert to realpath with ending slash + if (!$realpath = self::_getRealpath($dirname)) + { + return null; + } + + // Make sure requested path is directory + if (!is_dir($realpath)) + { + return null; + } + + // Begin list builder + $directories = []; + $files = []; + + foreach ((array) scandir($realpath) as $name) + { + // Skip system locations + if (empty($name) || $name == '.') + { + continue; + } + + // Try to build destination path + if (!$path = self::_getRealpath($realpath . $name)) + { + continue; + } + + // Context + switch (true) + { + case is_dir($path): + + $directories[] = + [ + 'file' => false, + 'path' => $path, + 'name' => $name, + 'link' => urlencode( + $name + ), + 'time' => filemtime( + $path + ) + ]; + + break; + + case is_file($path): + + $files[] = + [ + 'file' => true, + 'path' => $path, + 'name' => $name, + 'link' => urlencode( + $name + ), + 'time' => filemtime( + $path + ) + ]; + + break; + } + } + + // Sort order + array_multisort( + array_column( + $directories, + $sort + ), + $order, + $method, + $directories + ); + + // Sort files by name ASC + array_multisort( + array_column( + $directories, + $sort + ), + $order, + $method, + $directories + ); + + // Merge list + return array_merge( + $directories, + $files + ); + } + private static function _fixDirectorySeparators( string $path, string $separator = DIRECTORY_SEPARATOR @@ -95,4 +201,30 @@ class Filesystem $path ); } + + // PHP::realpath extension appending slash to dir paths + private static function _getRealpath( + ?string $path + ): ?string + { + if (empty($path)) + { + return null; + } + + if (!$realpath = realpath($path)) + { + return null; + } + + if (is_dir($realpath)) + { + $realpath = rtrim( + $realpath, + DIRECTORY_SEPARATOR + ) . DIRECTORY_SEPARATOR; + } + + return $realpath; + } } \ No newline at end of file