* Copyright (c) 2025 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 "listfile.h"
#include <fnmatch.h>
#include <memory>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <thread>
#include <tuple>
#include "file_utils.h"
#include "filemgmt_libhilog.h"
namespace OHOS::FileManagement::ModuleFileIO {
using namespace std;
using namespace OHOS::FileManagement::LibN;
thread_local OptionArgs g_optionArgs;
static bool CheckSuffix(const vector<string> &suffixs)
{
for (string suffix : suffixs) {
if (suffix.length() <= 1 || suffix.length() > MAX_SUFFIX_LENGTH) {
return false;
}
if (suffix[0] != '.') {
return false;
}
for (size_t i = 1; i < suffix.length(); i++) {
if (!isalnum(suffix[i])) {
return false;
}
}
}
return true;
}
static bool GetFileFilterParam(const NVal &argv, FileFilter *filter)
{
bool ret = false;
if (argv.HasProp("suffix") && !argv.GetProp("suffix").TypeIs(napi_undefined)) {
vector<string> suffixs;
tie(ret, suffixs, ignore) = argv.GetProp("suffix").ToStringArray();
if (!ret) {
HILOGE("Failed to get suffix prop.");
return false;
}
if (!CheckSuffix(suffixs) || suffixs.size() == 0) {
HILOGE("Invalid suffix.");
return false;
}
filter->SetSuffix(suffixs);
}
if (argv.HasProp("displayName") && !argv.GetProp("displayName").TypeIs(napi_undefined)) {
vector<string> displayNames;
tie(ret, displayNames, ignore) = argv.GetProp("displayName").ToStringArray();
if (!ret) {
HILOGE("Failed to get displayname prop.");
return false;
}
if (displayNames.size() == 0) {
HILOGE("Invalid displayName.");
return false;
}
filter->SetDisplayName(displayNames);
}
if (argv.HasProp("fileSizeOver") && !argv.GetProp("fileSizeOver").TypeIs(napi_undefined)) {
int64_t fileSizeOver = 0;
tie(ret, fileSizeOver) = argv.GetProp("fileSizeOver").ToInt64();
if (!ret || fileSizeOver < 0) {
HILOGE("Failed to get fileSizeOver prop.");
return false;
}
filter->SetFileSizeOver(fileSizeOver);
}
if (argv.HasProp("lastModifiedAfter") && !argv.GetProp("lastModifiedAfter").TypeIs(napi_undefined)) {
double lastModifiedAfter = 0;
tie(ret, lastModifiedAfter) = argv.GetProp("lastModifiedAfter").ToDouble();
if (!ret || lastModifiedAfter < 0) {
HILOGE("Failed to get lastModifiedAfter prop.");
return false;
}
filter->SetLastModifiedAfter(lastModifiedAfter);
}
return true;
}
static bool GetOptionParam(const NVal &argv, OptionArgs *optionArgs)
{
bool succ = false;
if (argv.HasProp("listNum")) {
tie(succ, optionArgs->listNum) = argv.GetProp("listNum").ToInt64(0);
if (!succ || optionArgs->listNum < 0) {
HILOGE("Failed to get listNum prop");
return false;
}
}
if (argv.HasProp("recursion")) {
tie(succ, optionArgs->recursion) = argv.GetProp("recursion").ToBool(false);
if (!succ) {
HILOGE("Failed to get recursion prop.");
return false;
}
}
if (argv.HasProp("filter")) {
NVal filterProp = argv.GetProp("filter");
if (!filterProp.TypeIs(napi_undefined)) {
auto ret = GetFileFilterParam(filterProp, &optionArgs->filter);
if (!ret) {
HILOGE("Failed to get filter prop.");
return false;
}
}
}
return true;
}
static bool GetOptionArg(napi_env env, const NFuncArg &funcArg, OptionArgs &optionArgs, const string &path)
{
optionArgs.Clear();
optionArgs.path = path;
if (funcArg.GetArgc() == NARG_CNT::ONE) {
return true;
}
if (funcArg.GetArgc() >= NARG_CNT::TWO) {
auto options = NVal(env, funcArg[NARG_POS::SECOND]);
if (options.TypeIs(napi_object)) {
return GetOptionParam(options, &optionArgs);
} else if (options.TypeIs(napi_undefined) || options.TypeIs(napi_function)) {
return true;
}
}
return false;
}
static bool FilterSuffix(const vector<string> &suffixs, const struct dirent &filename)
{
if (filename.d_type == DT_DIR) {
return true;
}
size_t found = string(filename.d_name).rfind('.');
if (found == std::string::npos) {
return false;
}
string suffixStr = string(filename.d_name).substr(found);
for (const auto &iter : suffixs) {
if (iter == suffixStr) {
return true;
}
}
return false;
}
static bool FilterDisplayname(const vector<string> &displaynames, const struct dirent &filename)
{
for (const auto &iter : displaynames) {
int ret = fnmatch(iter.c_str(), filename.d_name, FNM_PATHNAME | FNM_PERIOD);
if (ret == 0) {
return true;
}
}
return false;
}
static bool FilterFilesizeOver(const int64_t fFileSizeOver, const struct dirent &filename)
{
if (fFileSizeOver < 0) {
return true;
}
struct stat info;
string stPath = (g_optionArgs.path + '/' + string(filename.d_name));
int32_t res = stat(stPath.c_str(), &info);
if (res != 0) {
HILOGE("Failed to stat file.");
return false;
}
if (info.st_size > fFileSizeOver) {
return true;
}
return false;
}
static bool FilterLastModifyTime(const double lastModifiedAfter, const struct dirent &filename)
{
if (lastModifiedAfter < 0) {
return true;
}
struct stat info;
string stPath = g_optionArgs.path + '/' + string(filename.d_name);
int32_t res = stat(stPath.c_str(), &info);
if (res != 0) {
HILOGE("Failed to stat file.");
return false;
}
if (static_cast<double>(info.st_mtime) > lastModifiedAfter) {
return true;
}
return false;
}
static bool FilterResult(const struct dirent &filename)
{
vector<string> fSuffixs = g_optionArgs.filter.GetSuffix();
if (!FilterSuffix(fSuffixs, filename) && fSuffixs.size() > 0) {
return false;
}
vector<string> fDisplaynames = g_optionArgs.filter.GetDisplayName();
if (!FilterDisplayname(fDisplaynames, filename) && fDisplaynames.size() > 0) {
return false;
}
int64_t fFileSizeOver = g_optionArgs.filter.GetFileSizeOver();
if (!FilterFilesizeOver(fFileSizeOver, filename)) {
return false;
}
double fLastModifiedAfter = g_optionArgs.filter.GetLastModifiedAfter();
if (!FilterLastModifyTime(fLastModifiedAfter, filename)) {
return false;
}
g_optionArgs.countNum++;
return true;
}
static int32_t FilterFunc(const struct dirent *filename)
{
if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
return FILTER_DISMATCH;
}
if (g_optionArgs.countNum < g_optionArgs.listNum || g_optionArgs.listNum == 0) {
if ((filename->d_type == DT_DIR && g_optionArgs.recursion) || FilterResult(*filename)) {
return FILTER_MATCH;
}
}
return FILTER_DISMATCH;
}
static void Deleter(struct NameListArg *arg)
{
for (int i = 0; i < arg->direntNum; i++) {
free((arg->namelist)[i]);
(arg->namelist)[i] = nullptr;
}
free(arg->namelist);
arg->namelist = nullptr;
delete arg;
arg = nullptr;
}
static int FilterFileRes(const string &path, vector<string> &dirents)
{
unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
if (!pNameList) {
HILOGE("Failed to request heap memory.");
return ENOMEM;
}
int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, nullptr);
int err = errno;
if (num < 0) {
HILOGE("Failed to scan dir");
return err;
}
pNameList->direntNum = num;
for (int i = 0; i < num; i++) {
dirents.emplace_back(pNameList->namelist[i]->d_name);
}
return ERRNO_NOERR;
}
static int RecursiveFunc(const string &path, vector<string> &dirents)
{
unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
if (!pNameList) {
HILOGE("Failed to request heap memory.");
return ENOMEM;
}
int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, nullptr);
if (num < 0) {
HILOGE("Failed to scan dir");
return errno;
}
pNameList->direntNum = num;
for (int i = 0; i < num; i++) {
if ((*(pNameList->namelist[i])).d_type == DT_REG) {
dirents.emplace_back(path + '/' + pNameList->namelist[i]->d_name);
} else if ((*(pNameList->namelist[i])).d_type == DT_DIR) {
string pathTemp = g_optionArgs.path;
g_optionArgs.path += '/' + string((*(pNameList->namelist[i])).d_name);
int ret = RecursiveFunc(g_optionArgs.path, dirents);
if (ret != ERRNO_NOERR) {
return ret;
}
g_optionArgs.path = pathTemp;
}
}
return ERRNO_NOERR;
}
static napi_value DoListFileVector2NV(napi_env env, const string &path, vector<string> &dirents, bool recursion)
{
if (recursion) {
for (size_t i = 0; i < dirents.size(); i++) {
dirents[i] = dirents[i].substr(path.length());
}
}
return NVal::CreateArrayString(env, dirents).val_;
}
napi_value ListFile::Sync(napi_env env, napi_callback_info info)
{
NFuncArg funcArg(env, info);
if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
HILOGE("Number of arguments unmatched");
NError(EINVAL).ThrowErr(env);
return nullptr;
}
auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8StringPath();
if (!succPath) {
HILOGE("Invalid path");
NError(EINVAL).ThrowErr(env);
return nullptr;
}
if (!GetOptionArg(env, funcArg, g_optionArgs, string(path.get()))) {
HILOGE("Invalid options");
NError(EINVAL).ThrowErr(env);
return nullptr;
}
vector<string> direntsRes;
int ret = 0;
ret = g_optionArgs.recursion ? RecursiveFunc(path.get(), direntsRes) : FilterFileRes(path.get(), direntsRes);
if (ret) {
NError(ret).ThrowErr(env);
return nullptr;
}
auto res = DoListFileVector2NV(env, string(path.get()), direntsRes, g_optionArgs.recursion);
g_optionArgs.Clear();
return res;
}
napi_value ListFile::Async(napi_env env, napi_callback_info info)
{
NFuncArg funcArg(env, info);
if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
HILOGE("Number of arguments unmatched");
NError(EINVAL).ThrowErr(env);
return nullptr;
}
auto [succPath, path, unused] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8StringPath();
if (!succPath) {
HILOGE("Invalid path");
NError(EINVAL).ThrowErr(env);
return nullptr;
}
OptionArgs optionArgsTmp = {};
if (!GetOptionArg(env, funcArg, optionArgsTmp, string(path.get()))) {
HILOGE("Invalid options");
NError(EINVAL).ThrowErr(env);
return nullptr;
}
auto arg = CreateSharedPtr<ListFileArgs>();
if (arg == nullptr) {
HILOGE("Failed to request heap memory.");
NError(ENOMEM).ThrowErr(env);
return nullptr;
}
auto cbExec = [arg, optionArgsTmp]() -> NError {
g_optionArgs = optionArgsTmp;
int ret = 0;
ret = g_optionArgs.recursion ? RecursiveFunc(g_optionArgs.path, arg->dirents) :
FilterFileRes(g_optionArgs.path, arg->dirents);
g_optionArgs.Clear();
return ret ? NError(ret) : NError(ERRNO_NOERR);
};
auto cbCompl = [arg, optionArgsTmp, path = string(path.get())](napi_env env, NError err) -> NVal {
if (err) {
return { env, err.GetNapiErr(env) };
}
return { env, DoListFileVector2NV(env, path, arg->dirents, optionArgsTmp.recursion) };
};
NVal thisVar(env, funcArg.GetThisVar());
if (funcArg.GetArgc() == NARG_CNT::ONE || (funcArg.GetArgc() == NARG_CNT::TWO &&
!NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function))) {
return NAsyncWorkPromise(env, thisVar).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
} else {
NVal cb(env, funcArg[((funcArg.GetArgc() == NARG_CNT::TWO) ? NARG_POS::SECOND : NARG_POS::THIRD)]);
return NAsyncWorkCallback(env, thisVar, cb).Schedule(LIST_FILE_PRODUCE_NAME, cbExec, cbCompl).val_;
}
}
}