* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "io/std_directory.h"
#include <algorithm>
#if defined(__OHOS__)
#undef HAS_FILESYSTEM
#else
#ifdef __has_include
#if __has_include(<filesystem>)
#include <filesystem>
#define HAS_FILESYSTEM
#include <system_error>
#endif
#endif
#endif
#if !defined(HAS_FILESYSTEM)
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif
#include <cstddef>
#include <cstdint>
#include <base/containers/string.h>
#include <base/containers/string_view.h>
#include <base/containers/type_traits.h>
#include <base/containers/unique_ptr.h>
#include <base/containers/vector.h>
#include <base/namespace.h>
#include <core/io/intf_directory.h>
#include <core/log.h>
#include <core/namespace.h>
CORE_BEGIN_NAMESPACE()
using BASE_NS::make_unique;
using BASE_NS::string;
using BASE_NS::string_view;
using BASE_NS::vector;
#if defined(HAS_FILESYSTEM)
struct DirImpl {
explicit DirImpl(string_view path) : path_(path)
{}
string path_;
};
namespace {
uint64_t GetTimeStamp(const std::filesystem::directory_entry& entry)
{
std::error_code ec;
auto time = entry.last_write_time(ec);
if (ec) {
return 0;
}
return static_cast<uint64_t>(time.time_since_epoch().count());
}
IDirectory::Entry::Type GetEntryType(const std::filesystem::directory_entry& entry)
{
std::error_code ec;
if (entry.is_directory(ec)) {
return IDirectory::Entry::DIRECTORY;
}
if (entry.is_regular_file(ec)) {
return IDirectory::Entry::FILE;
}
return IDirectory::Entry::UNKNOWN;
}
std::filesystem::path U8Path(string_view str)
{
return std::filesystem::u8path(str.begin().ptr(), str.end().ptr());
}
}
StdDirectory::StdDirectory(BASE_NS::unique_ptr<DirImpl> dir) : dir_(BASE_NS::move(dir))
{}
StdDirectory::~StdDirectory()
{
Close();
}
void StdDirectory::Close()
{
if (dir_) {
dir_.reset();
}
}
IDirectory::Ptr StdDirectory::Create(BASE_NS::string_view path)
{
std::error_code ec;
if (std::filesystem::create_directory(U8Path(path), ec)) {
return IDirectory::Ptr{BASE_NS::make_unique<StdDirectory>(make_unique<DirImpl>(path)).release()};
}
return {};
}
IDirectory::Ptr StdDirectory::Open(const string_view path)
{
std::error_code ec;
if (std::filesystem::is_directory(U8Path(path), ec)) {
return IDirectory::Ptr{BASE_NS::make_unique<StdDirectory>(make_unique<DirImpl>(path)).release()};
}
return {};
}
bool StdDirectory::DirectoryExists(const string_view path)
{
std::error_code ec;
return (std::filesystem::is_directory(U8Path(path), ec));
}
vector<IDirectory::Entry> StdDirectory::GetEntries() const
{
CORE_ASSERT_MSG(dir_, "Dir not open");
vector<IDirectory::Entry> result;
if (dir_) {
std::error_code ec;
for (auto& iter : std::filesystem::directory_iterator(U8Path(dir_->path_), ec)) {
const auto filename = iter.path().filename().u8string();
auto str = string(filename.c_str(), filename.length());
result.push_back(IDirectory::Entry{GetEntryType(iter), move(str), GetTimeStamp(iter)});
}
}
return result;
}
#else
struct DirImpl {
DirImpl(const string_view path, DIR* aDir) : path_(path), dir_(aDir)
{}
string path_;
DIR* dir_;
};
namespace {
IDirectory::Entry CreateEntry(const string_view path, const dirent& entry)
{
const auto fullPath = path + string(entry.d_name);
struct stat statBuf {};
const auto statResult = (stat(fullPath.c_str(), &statBuf) == 0);
IDirectory::Entry::Type type = IDirectory::Entry::UNKNOWN;
#ifdef _DIRENT_HAVE_D_TYPE
switch (entry.d_type) {
case DT_DIR:
type = IDirectory::Entry::DIRECTORY;
break;
case DT_REG:
type = IDirectory::Entry::FILE;
break;
default:
type = IDirectory::Entry::UNKNOWN;
break;
}
#else
if (statResult) {
if (S_ISDIR(statBuf.st_mode)) {
type = IDirectory::Entry::DIRECTORY;
} else if (S_ISREG(statBuf.st_mode)) {
type = IDirectory::Entry::FILE;
}
}
#endif
uint64_t timestamp = 0;
if (statResult) {
timestamp = static_cast<uint64_t>(statBuf.st_mtime);
}
return IDirectory::Entry{type, entry.d_name, timestamp};
}
}
StdDirectory::StdDirectory(BASE_NS::unique_ptr<DirImpl> dir) : dir_(BASE_NS::move(dir))
{}
StdDirectory::~StdDirectory()
{
Close();
}
void StdDirectory::Close()
{
if (dir_) {
closedir(dir_->dir_);
dir_.reset();
}
}
IDirectory::Ptr StdDirectory::Create(BASE_NS::string_view path)
{
int result = mkdir(string(path).c_str(), S_IRWXU | S_IRWXO);
if (result == 0) {
return Open(path);
}
return {};
}
IDirectory::Ptr StdDirectory::Open(const string_view path)
{
DIR* dir = opendir(string(path).c_str());
if (dir) {
return IDirectory::Ptr{make_unique<StdDirectory>(make_unique<DirImpl>(path, dir)).release()};
}
return {};
}
bool StdDirectory::DirectoryExists(const string_view path)
{
struct stat statBuf {};
return (stat(path.data(), &statBuf) == 0) && S_ISDIR(statBuf.st_mode);
}
vector<IDirectory::Entry> StdDirectory::GetEntries() const
{
CORE_ASSERT_MSG(dir_, "Dir not open");
vector<IDirectory::Entry> result;
if (dir_) {
rewinddir(dir_->dir_);
struct dirent* directoryEntry = nullptr;
while ((directoryEntry = readdir(dir_->dir_)) != nullptr) {
if (!strcmp(directoryEntry->d_name, ".") || !strcmp(directoryEntry->d_name, "..")) {
continue;
}
result.push_back(CreateEntry(dir_->path_, *directoryEntry));
}
}
return result;
}
#endif
string StdDirectory::ResolveAbsolutePath(const string_view pathIn, bool isDirectory)
{
string absolutePath;
auto path = pathIn;
#ifdef _WIN32
if (!path.empty() && path.front() == '/') {
path = path.substr(1);
}
#endif
#ifdef HAS_FILESYSTEM
std::error_code ec;
std::filesystem::path fsPath = std::filesystem::canonical(U8Path(path), ec);
if (ec.value() == 0) {
const auto pathStr = fsPath.string();
absolutePath.assign(pathStr.data(), pathStr.size());
}
#elif defined(_WIN32)
char resolvedPath[_MAX_PATH] = {};
if (_fullpath(resolvedPath, string(path).c_str(), _MAX_PATH) != nullptr) {
if (isDirectory) {
auto handle = opendir(resolvedPath);
if (handle) {
closedir(handle);
absolutePath = resolvedPath;
}
} else {
auto handle = fopen(resolvedPath, "r");
if (handle) {
fclose(handle);
absolutePath = resolvedPath;
}
}
}
#elif defined(__OHOS__) || defined(__linux__)
char resolvedPath[PATH_MAX];
if (realpath(string(path).c_str(), resolvedPath) != nullptr) {
absolutePath = resolvedPath;
}
#endif
FormatPath(absolutePath, isDirectory);
return absolutePath;
}
void StdDirectory::FormatPath(string& path, bool isDirectory)
{
const size_t length = path.length();
std::replace(path.begin(), path.end(), '\\', '/');
if (!path.empty() && isDirectory) {
if (path[length - 1] != '/') {
path += '/';
}
}
}
string StdDirectory::GetDirName(const string_view fullPath)
{
auto path = string(fullPath);
StdDirectory::FormatPath(path, false);
const size_t separatorPos = path.find_last_of('/');
if (separatorPos == string::npos) {
return ".";
}
path.resize(separatorPos);
return path;
}
string StdDirectory::GetBaseName(const string_view fullPath)
{
auto path = string(fullPath);
StdDirectory::FormatPath(path, false);
const size_t separatorPos = path.find_last_of('/');
if (separatorPos == string::npos) {
return path;
}
path.erase(0, separatorPos + 1);
return path;
}
CORE_END_NAMESPACE()