From 27e5dd5c37225679f62eeaec7965468c66bf159b Mon Sep 17 00:00:00 2001 From: y30045862 Date: Fri, 25 Aug 2023 17:16:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=80=E8=BF=91=E5=88=A0=E9=99=A4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=BB=A3=E7=A0=81=20Signed-off-by:=20yangjingbo10=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I10c6a616cece48857ed016ee7a463d4f4c19168c --- bundle.json | 4 +- interfaces/kits/native/trash/BUILD.gn | 46 ++ .../kits/native/trash/include/file_info.h | 32 + .../trash/include/file_trash_n_exporter.h | 50 ++ interfaces/kits/native/trash/module.cpp | 61 ++ .../trash/src/file_trash_n_exporter.cpp | 638 ++++++++++++++++++ .../FileExtensionAbility.ts | 88 ++- utils/file_util.h | 257 +++++++ 8 files changed, 1162 insertions(+), 14 deletions(-) create mode 100644 interfaces/kits/native/trash/BUILD.gn create mode 100644 interfaces/kits/native/trash/include/file_info.h create mode 100644 interfaces/kits/native/trash/include/file_trash_n_exporter.h create mode 100644 interfaces/kits/native/trash/module.cpp create mode 100644 interfaces/kits/native/trash/src/file_trash_n_exporter.cpp create mode 100644 utils/file_util.h diff --git a/bundle.json b/bundle.json index 9701271f..7952dbe4 100644 --- a/bundle.json +++ b/bundle.json @@ -24,6 +24,7 @@ "components": [ "ability_base", "ability_runtime", + "app_file_service", "ipc", "samgr", "safwk", @@ -42,7 +43,8 @@ "build": { "group_type":{ "fwk_group": [ - "//foundation/filemanagement/user_file_service/interfaces/kits/picker:picker" + "//foundation/filemanagement/user_file_service/interfaces/kits/picker:picker", + "//foundation/filemanagement/user_file_service/interfaces/kits/native/trash:trash" ], "service_group": [ "//foundation/filemanagement/user_file_service/services:user_file_managers", diff --git a/interfaces/kits/native/trash/BUILD.gn b/interfaces/kits/native/trash/BUILD.gn new file mode 100644 index 00000000..8b04d7c3 --- /dev/null +++ b/interfaces/kits/native/trash/BUILD.gn @@ -0,0 +1,46 @@ +# Copyright (C) 2023 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. + +import("//build/ohos.gni") +import("//foundation/filemanagement/user_file_service/filemanagement_aafwk.gni") + +ohos_shared_library("trash") { + include_dirs = [ + "include", + "${file_api_path}/utils/filemgmt_libn/include", + "${user_file_service_path}/utils", + ] + + sources = [ + "module.cpp", + "src/file_trash_n_exporter.cpp", + ] + + external_deps = [ + "ability_base:zuri", + "ability_runtime:abilitykit_native", + "access_token:libaccesstoken_sdk", + "access_token:libtokenid_sdk", + "app_file_service:fileuri_native", + "file_api:filemgmt_libhilog", + "file_api:filemgmt_libn", + "hilog:libhilog", + "hitrace:hitrace_meter", + "ipc:ipc_core", + "napi:ace_napi", + ] + + relative_install_dir = "module/file" + subsystem_name = "filemanagement" + part_name = "user_file_service" +} diff --git a/interfaces/kits/native/trash/include/file_info.h b/interfaces/kits/native/trash/include/file_info.h new file mode 100644 index 00000000..24d6e946 --- /dev/null +++ b/interfaces/kits/native/trash/include/file_info.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 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. + */ + +#ifndef INTERFACES_KITS_JS_SRC_TRASH_CLASS_FILE_INFO_H +#define INTERFACES_KITS_JS_SRC_TRASH_CLASS_FILE_INFO_H + +namespace OHOS { +namespace Trash { +struct FileInfo { + std::string uri; + std::string srcPath; + std::string fileName; + int64_t mode { 0 }; + int64_t size { 0 }; + int64_t mtime { 0 }; + int64_t ctime { 0 }; +}; +} // namespace Trash +} // namespace OHOS +#endif // INTERFACES_KITS_JS_SRC_TRASH_CLASS_FILE_INFO_H \ No newline at end of file diff --git a/interfaces/kits/native/trash/include/file_trash_n_exporter.h b/interfaces/kits/native/trash/include/file_trash_n_exporter.h new file mode 100644 index 00000000..94898a4e --- /dev/null +++ b/interfaces/kits/native/trash/include/file_trash_n_exporter.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 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. + */ + +#ifndef INTERFACES_KITS_JS_SRC_TRASH_FILE_TRASH_N_EXPORTER_H +#define INTERFACES_KITS_JS_SRC_TRASH_FILE_TRASH_N_EXPORTER_H + +#include "uv.h" + +#include "filemgmt_libn.h" +#include "napi/native_api.h" +#include "napi/native_node_api.h" + +namespace OHOS { +namespace Trash { +using namespace FileManagement::LibN; +using namespace std; +// 暂时默认用户100 +const std::string TRASH_PATH = "/storage/.Trash/Users/100"; +const std::string URI_PATH_PREFIX = "file://docs"; +const std::string TRASH_SUB_DIR = "oh_trash_content"; + +class FileTrashNExporter final : public NExporter { +public: + inline static const std::string className_ = "trash"; + + static napi_value ListFile(napi_env env, napi_callback_info info); + static napi_value Recover(napi_env env, napi_callback_info info); + static napi_value CompletelyDelete(napi_env env, napi_callback_info info); + + bool Export() override; + std::string GetClassName() override; + FileTrashNExporter(napi_env env, napi_value exports); + ~FileTrashNExporter() override; +}; + +} // namespace Trash +} // namespace OHOS +#endif // INTERFACES_KITS_JS_SRC_TRASH_FILE_TRASH_N_EXPORTER_H \ No newline at end of file diff --git a/interfaces/kits/native/trash/module.cpp b/interfaces/kits/native/trash/module.cpp new file mode 100644 index 00000000..ba0e575a --- /dev/null +++ b/interfaces/kits/native/trash/module.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 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 +#include + +#include "file_trash_n_exporter.h" +#include "filemgmt_libn.h" +#include "hilog_wrapper.h" + +namespace OHOS { +namespace Trash { +using namespace std; + +EXTERN_C_START +static napi_value Export(napi_env env, napi_value exports) +{ + std::vector> products; + products.emplace_back(make_unique(env, exports)); + + for (auto &&product : products) { + string nExporterName = product->GetClassName(); + if (!product->Export()) { + HILOG_ERROR("INNER BUG. Failed to export class %{public}s for module trash", nExporterName.c_str()); + return nullptr; + } else { + HILOG_INFO("Class %{public}s for module trash has been exported", nExporterName.c_str()); + } + } + return exports; +} +EXTERN_C_END + +static napi_module _module = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Export, + .nm_modname = "file.trash", + .nm_priv = ((void *)0), + .reserved = {0} +}; + +extern "C" __attribute__((constructor)) void RegisterModule(void) +{ + napi_module_register(&_module); +} +} // namespace Trash +} // namespace OHOS diff --git a/interfaces/kits/native/trash/src/file_trash_n_exporter.cpp b/interfaces/kits/native/trash/src/file_trash_n_exporter.cpp new file mode 100644 index 00000000..c0472227 --- /dev/null +++ b/interfaces/kits/native/trash/src/file_trash_n_exporter.cpp @@ -0,0 +1,638 @@ +/* + * Copyright (c) 2023 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 "file_trash_n_exporter.h" + +#include + +#include "access_token.h" +#include "accesstoken_kit.h" +#include "file_access_framework_errno.h" +#include "file_info.h" +#include "file_uri.h" +#include "file_util.h" +#include "ipc_skeleton.h" + +namespace OHOS { +namespace Trash { +namespace { + const std::string FILE_ACCESS_PERMISSION = "ohos.permission.FILE_ACCESS_MANAGER"; +} + +using namespace FileManagement::LibN; +using namespace FileManagement; +using namespace std; + +static bool CheckCallingPermission(const std::string &permission) +{ + Security::AccessToken::AccessTokenID tokenCaller = IPCSkeleton::GetCallingTokenID(); + int res = Security::AccessToken::AccessTokenKit::VerifyAccessToken(tokenCaller, permission); + if (res != Security::AccessToken::PermissionState::PERMISSION_GRANTED) { + HILOG_ERROR("FileTrashNExporter::CheckCallingPermission have no fileAccess permission"); + return false; + } + return true; +} + +static string GetTimeSlotFromPath(const string &path) +{ + int slashSize = 1; + // 获取时间戳 + size_t trashPathPrefixPos = path.find(TRASH_PATH); + size_t expectTimeSlotStartPos = trashPathPrefixPos + TRASH_PATH.length() + slashSize; + if (expectTimeSlotStartPos >= path.length()) { + return ""; + } + string realFilePathWithTime = path.substr(trashPathPrefixPos + TRASH_PATH.length() + slashSize); + // 获取时间戳目录位置 + size_t trashPathWithTimePrefixPos = realFilePathWithTime.find_first_of("/"); + if (trashPathWithTimePrefixPos == string::npos) { + return ""; + } + string timeSlot = realFilePathWithTime.substr(0, trashPathWithTimePrefixPos); + HILOG_INFO("GetTimeSlotFromPath: timeSlot = %{public}s", timeSlot.c_str()); + return timeSlot; +} + +static int RecursiveFunc(const string &path, vector &dirents) +{ + unique_ptr pNameList = { new (nothrow) struct NameListArg, Deleter }; + if (!pNameList) { + HILOG_ERROR("Failed to request heap memory."); + return ENOMEM; + } + HILOG_INFO("RecursiveFunc: scandir path = %{public}s", path.c_str()); + int num = scandir(path.c_str(), &(pNameList->namelist), FilterFunc, alphasort); + if (num < 0) { + HILOG_ERROR("RecursiveFunc: Failed to scan dir"); + return errno; + } + pNameList->direntNum = num; + string pathInRecur = path; + 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 = pathInRecur; + pathInRecur += '/' + string((*(pNameList->namelist[i])).d_name); + // check if path include TRASH_SUB_DIR + "/", need to add it into dirents + HILOG_INFO("RecursiveFunc: pathTemp = %{public}s", pathTemp.c_str()); + string timeSlot = GetTimeSlotFromPath(pathTemp); + if (!timeSlot.empty() && pathInRecur.rfind(TRASH_SUB_DIR + timeSlot + "/") != string::npos) { + // Only filter previous dir is TRASH_SUB_DIR + dirents.emplace_back(pathInRecur); + } + int ret = RecursiveFunc(pathInRecur, dirents); + if (ret != ERRNO_NOERR) { + return ret; + } + pathInRecur = pathTemp; + } + } + return ERRNO_NOERR; +} + +static napi_value CreateObjectArray(napi_env env, vector result) +{ + uint32_t status = napi_ok; + napi_value fileInfoResultArray = nullptr; + status = napi_create_array_with_length(env, result.size(), &fileInfoResultArray); + if (status != napi_ok) { + HILOG_ERROR("Create napi array fail"); + return nullptr; + } + + for (size_t i = 0; i < result.size(); i++) { + FileInfo &tmpResult = result.at(i); + napi_value resultVal; + status |= napi_create_object(env, &resultVal); + napi_value tmpVal; + status |= napi_create_string_utf8(env, tmpResult.uri.c_str(), tmpResult.uri.length(), &tmpVal); + status |= napi_set_named_property(env, resultVal, "uri", tmpVal); + status |= napi_create_string_utf8(env, tmpResult.srcPath.c_str(), tmpResult.srcPath.length(), &tmpVal); + status |= napi_set_named_property(env, resultVal, "srcPath", tmpVal); + status |= napi_create_string_utf8(env, tmpResult.fileName.c_str(), tmpResult.fileName.length(), &tmpVal); + status |= napi_set_named_property(env, resultVal, "fileName", tmpVal); + status |= napi_create_int64(env, tmpResult.mode, &tmpVal); + status |= napi_set_named_property(env, resultVal, "mode", tmpVal); + status |= napi_create_int64(env, tmpResult.mode, &tmpVal); + status |= napi_set_named_property(env, resultVal, "mode", tmpVal); + status |= napi_create_int64(env, tmpResult.size, &tmpVal); + status |= napi_set_named_property(env, resultVal, "size", tmpVal); + status |= napi_create_int64(env, tmpResult.mtime, &tmpVal); + status |= napi_set_named_property(env, resultVal, "mtime", tmpVal); + status |= napi_create_int64(env, tmpResult.ctime, &tmpVal); + status |= napi_set_named_property(env, resultVal, "ctime", tmpVal); + status |= napi_set_element(env, fileInfoResultArray, i, resultVal); + if (status != napi_ok) { + HILOG_ERROR("Create CopyResult object error"); + return nullptr; + } + } + return fileInfoResultArray; +} + +static string FindSourceFilePath(const string &path) +{ + HILOG_INFO("FindSourceFilePath: curFilePath = %{public}s", path.c_str()); + size_t slashSize = 1; + // 获取/trash目录位置 + size_t trashPathPrefixPos = path.find(TRASH_PATH); + if (trashPathPrefixPos == string::npos) { + HILOG_ERROR("FindSourceFilePath: Invalid Path No Trash Path"); + return ""; + } + size_t timeSLotStartPos = trashPathPrefixPos + TRASH_PATH.length() + slashSize; + string realFilePathWithTime = path.substr(timeSLotStartPos); + // 获取时间戳目录位置 + size_t trashPathWithTimePrefixPos = realFilePathWithTime.find_first_of("/"); + if (trashPathWithTimePrefixPos == string::npos) { + HILOG_ERROR("FindSourceFilePath: : Invalid Path No timestamp"); + return ""; + } + // 获取时间戳 + string timeSlot = realFilePathWithTime.substr(0, trashPathWithTimePrefixPos); + string realFilePath = realFilePathWithTime.substr(trashPathWithTimePrefixPos + slashSize); + size_t pos = realFilePath.rfind(TRASH_SUB_DIR + timeSlot + "/"); + if (pos == string::npos) { + HILOG_ERROR("FindSourceFilePath: : Invalid Path No Trash Sub Path"); + return ""; + } + string realFilePathPrefix = realFilePath.substr(0, pos); + string realFileName = realFilePath.substr(pos + TRASH_SUB_DIR.length() + + timeSlot.length() + slashSize); + realFilePath = "/" + realFilePathPrefix + realFileName; + HILOG_INFO("FindSourceFilePath: realFilePath After = %{public}s", realFilePath.c_str()); + return realFilePath; +} + +static bool Mkdirs(const string &path, bool isDir, string &newRecoveredPath) +{ + HILOG_INFO("Mkdirs: path = %{public}s", path.c_str()); + bool isExist = false; + string recoveredPath = path; + string folderName = ""; + size_t lastPos = 0; + if (recoveredPath.length() <= 0) { + return false; + } + // if argument uri is dir, then add "/" + if (isDir) { + recoveredPath = recoveredPath + "/"; + } + + size_t recoveredPathLength = recoveredPath.length(); + + for (size_t i = 1; i < recoveredPath.length(); ++i) { + if (recoveredPath[i] != '/') { + continue; + } + recoveredPath[i] = '\0'; + folderName = recoveredPath.substr(lastPos + 1, i); + lastPos = i; + isExist = Access(recoveredPath); + if (isExist) { + HILOG_INFO("Mkdir: Access exist for %{public}s ", recoveredPath.c_str()); + // 对于最后一层目录(即最后一层),如果存在同名,则需要新建(原名称+时间戳)的目录 + if (i != recoveredPathLength - 1) { + recoveredPath[i] = '/'; + continue; + } + recoveredPath[i] = '_'; + int64_t curTime = chrono::duration_cast + ((chrono::system_clock::now()).time_since_epoch()).count(); + newRecoveredPath = recoveredPath + to_string(curTime); + if (!Mkdir(newRecoveredPath)) { + HILOG_ERROR("Access exist for last Mkdirs fail path = %{public}s", newRecoveredPath.c_str()); + return false; + } else { + return true; + } + } else if (!Mkdir(recoveredPath)) { + HILOG_ERROR("Mkdirs fail for %{public}s ", recoveredPath.c_str()); + return false; + } + recoveredPath[i] = '/'; + } + return true; +} + +static bool MoveFile(const string &srcFile, const string &destFile) +{ + unique_ptr access_req = { + new uv_fs_t, fs_req_cleanup }; + if (!access_req) { + HILOG_ERROR("MoveFile: Failed to request heap memory."); + return false; + } + + int ret = uv_fs_access(nullptr, access_req.get(), destFile.c_str(), 0, nullptr); + if (ret < 0 && (string_view(uv_err_name(ret)) != "ENOENT")) { + HILOG_ERROR("MoveFile: destPath not access and err is not ENOENT"); + return false; + } + if (ret == 0) { + // 存在同名文件,需要加上时间戳 + int64_t expireTime = chrono::duration_cast + ((chrono::system_clock::now()).time_since_epoch()).count(); + string newDestFile = destFile + to_string(expireTime); + return RenameFile(srcFile, newDestFile); + } + return RenameFile(srcFile, destFile); +} + +static string RecurCheckIfOnlyContentInDir(const string &path, size_t trashWithTimePos, const string &trashWithTimePath) +{ + HILOG_INFO("RecurCheckIfOnlyContentInDir: path = %{public}s", path.c_str()); + size_t slashPos = path.find_last_of("/"); + if (slashPos <= trashWithTimePos) { + HILOG_INFO("RecurCheckIfOnlyContentInDir: slashPos = %{public}u", slashPos); + return trashWithTimePath; + } + string parentPath = path.substr(0, slashPos); + HILOG_INFO("RecurCheckIfOnlyContentInDir: parentPath = %{public}s", parentPath.c_str()); + int num = ScanDir(parentPath); + HILOG_INFO("RecurCheckIfOnlyContentInDir: num = %{public}d", num); + if (num > 1) { + // 同一时间戳目录下存在多个删除项,则不论是还原后的删除还是彻底删除,仅需删除该项 + return path; + } else if (num == 1) { + // 需要向上一层目录判断 + return RecurCheckIfOnlyContentInDir(parentPath, trashWithTimePos, trashWithTimePath); + } else { + HILOG_ERROR("RecurCheckIfOnlyContentInDir: invalid path = %{public}s", path.c_str()); + } + return nullptr; +} + +static string GetToDeletePath(const string &toDeletePath, napi_env env) +{ + HILOG_INFO("GetToDeletePath: toDeletePath = %{public}s", toDeletePath.c_str()); + // 判断是否为有效回收站路径 + size_t slashSize = 1; + // 获取/Trash目录位置 + size_t trashPathPrefixPos = toDeletePath.find(TRASH_PATH); + if (trashPathPrefixPos == string::npos + || trashPathPrefixPos + TRASH_PATH.length() + slashSize >= toDeletePath.length()) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + string realFilePathWithTime = toDeletePath.substr(trashPathPrefixPos + TRASH_PATH.length() + slashSize); + // 获取时间戳目录位置 + size_t trashPathWithTimePrefixPos = realFilePathWithTime.find_first_of("/"); + size_t realTimeDirPos = trashPathPrefixPos + TRASH_PATH.length() + + slashSize + trashPathWithTimePrefixPos; + // 回收站下一级的时间戳目录 + string trashWithTimePath = toDeletePath.substr(0, realTimeDirPos); + + // 从待删除目录开始向内层遍历父目录,判断父目录是否仅有一个子目录,如果是则继续向前查找,直到时间戳目录为止; + // 如果不是仅有一个子目录,则待删除目录即子目录 + return RecurCheckIfOnlyContentInDir(toDeletePath, realTimeDirPos, trashWithTimePath); +} + +static vector GenerateFileInfoEntities(vector filterDirents) +{ + vector fileInfoList; + for (int k = 0; k < filterDirents.size(); k++) { + string filterDirent = filterDirents[k]; + HILOG_INFO("ListFile: After filter dirent = %{public}s", filterDirent.c_str()); + + string realFilePath = FindSourceFilePath(filterDirent); + HILOG_INFO("ListFile: After filter realFilePath = %{public}s", realFilePath.c_str()); + size_t lastSlashPos = filterDirent.find_last_of("/"); + string fileName = filterDirent.substr(lastSlashPos + 1); + + FileInfo fileInfoEntity; + fileInfoEntity.uri = URI_PATH_PREFIX + filterDirent; + fileInfoEntity.srcPath = URI_PATH_PREFIX + realFilePath; + fileInfoEntity.fileName = fileName; + + StatEntity statEntity; + if (GetStat(filterDirent, statEntity)) { + fileInfoEntity.mode = static_cast(statEntity.stat_.st_mode); + fileInfoEntity.size = static_cast(statEntity.stat_.st_size); + fileInfoEntity.mtime = static_cast(statEntity.stat_.st_mtim.tv_sec); + fileInfoEntity.ctime = static_cast(statEntity.stat_.st_ctim.tv_sec); + } + fileInfoList.emplace_back(fileInfoEntity); + } + return fileInfoList; +} + +napi_value FileTrashNExporter::ListFile(napi_env env, napi_callback_info info) +{ + if (!CheckCallingPermission(FILE_ACCESS_PERMISSION)) { + HILOG_ERROR("permission error"); + NError(E_PERMISSION).ThrowErr(env); + return nullptr; + } + + NFuncArg funcArg(env, info); + if (!funcArg.InitArgs(NARG_CNT::ZERO)) { + HILOG_ERROR("Number of arguments unmatched"); + NError(EINVAL).ThrowErr(env); + return nullptr; + } + vector dirents; + unique_ptr pNameList = { new (nothrow) struct NameListArg, Deleter }; + if (!pNameList) { + HILOG_ERROR("Failed to request heap memory."); + return nullptr; + } + int ret = RecursiveFunc(TRASH_PATH, dirents); + if (ret != ERRNO_NOERR) { + NError(ENOMEM).ThrowErr(env); + return nullptr; + } + + vector filterDirents; + size_t slashSize = 1; + for (int j = 0; j < dirents.size(); j++) { + string dirent = dirents[j]; + HILOG_INFO("ListFile: After RecursiveFunc dirent = %{public}s", dirent.c_str()); + + string timeSlot = GetTimeSlotFromPath(dirent); + if (timeSlot.empty()) { + continue; + } + // Only filter previous dir is TRASH_SUB_DIR + size_t pos = dirent.find(TRASH_SUB_DIR + timeSlot + "/"); + if (pos != string::npos) { + string trashSubDir = TRASH_SUB_DIR + timeSlot; + string pathBehTrashSub = dirent.substr(pos + trashSubDir.length() + slashSize, dirent.length()); + HILOG_INFO("ListFile: pathBehTrashSub = %{public}s", pathBehTrashSub.c_str()); + if (dirent.find("/", pos + trashSubDir.length() + slashSize) == string::npos) { + filterDirents.emplace_back(dirent); + } + } + } + vector fileInfoList = GenerateFileInfoEntities(filterDirents); + return CreateObjectArray(env, fileInfoList); +} + +static napi_value RecoverFile(napi_env env, const string &filePath) +{ + string sourceFilePath = FindSourceFilePath(filePath); + HILOG_INFO("RecoverFile: sourceFilePath = %{public}s", sourceFilePath.c_str()); + string newDestPath = sourceFilePath; + if (newDestPath.length() != 0 && Mkdirs(sourceFilePath, false, newDestPath)) { + MoveFile(filePath, newDestPath); + } + + // 文件已被移动,则如果前一层目录包含其他内容,则直接返回; + // 如果不包含,则需要一层层向父目录回退判断对应目录是否需要删除 + size_t slashPos = filePath.find_last_of("/"); + string parentPath = filePath.substr(0, slashPos); + int num = ScanDir(parentPath); + if (num == 0) { + auto err = RmDirent(GetToDeletePath(parentPath, env)); + if (err) { + err.ThrowErr(env); + return nullptr; + } + } + return NVal::CreateUndefined(env).val_; +} + +static void RecoverFilePart(vector filePathList, map dirPath2UpdatedNameMap) +{ + // 处理文件 + for (int j = 0; j < filePathList.size(); j++) { + string filePath = filePathList[j]; + HILOG_INFO("RecoverFilePart: filePath = %{public}s", filePath.c_str()); + string sourceFilePath = FindSourceFilePath(filePath); + HILOG_INFO("RecoverFilePart: sourceFilePath = %{public}s", sourceFilePath.c_str()); + + size_t lastSlashPos = sourceFilePath.find_last_of("/"); + string fileName = sourceFilePath.substr(lastSlashPos + 1); + string sourceFilePathOnly = sourceFilePath.substr(0, lastSlashPos); + map::iterator iter = dirPath2UpdatedNameMap.find(sourceFilePathOnly); + if (iter != dirPath2UpdatedNameMap.end()) { + sourceFilePath = iter->second + "/" + fileName; + } + MoveFile(filePath, sourceFilePath); + } +} + +static vector FilterDirsNoContains(vector dirPathList) +{ + //先处理目录,仅保留不互相包含的目录(取子目录较深的) + vector filterDirPathList; + for (int j = 0; j < dirPathList.size(); j++) { + string dirPath = dirPathList[j]; + bool isIncluded = false; + for (int k = 0; k < filterDirPathList.size(); k++) { + string filterDirPath = filterDirPathList[k]; + if (StartsWith(filterDirPath, dirPath)) { + isIncluded = true; + break; + } + } + if (!isIncluded) { + filterDirPathList.emplace_back(dirPath); + } + } + return filterDirPathList; +} + +static map MakeAndFindUpdateNameDir(vector filterDirPathList) +{ + map dirPath2UpdatedNameMap; + for (int j = 0; j < filterDirPathList.size(); j++) { + string dirPath = filterDirPathList[j]; + string sourceFilePath = FindSourceFilePath(dirPath); + HILOG_ERROR("MakeAndFindUpdateNameDir: sourceFilePath = %{public}s", sourceFilePath.c_str()); + string newDestPath = sourceFilePath; + if (Mkdirs(sourceFilePath, true, newDestPath)) { + HILOG_ERROR("MakeAndFindUpdateNameDir: newDestPath = %{public}s", newDestPath.c_str()); + if (newDestPath != sourceFilePath) { + dirPath2UpdatedNameMap.insert(make_pair(sourceFilePath, newDestPath)); + } + } + } + return dirPath2UpdatedNameMap; +} + +static napi_value RecoverDir(napi_env env, const string &dirPath) +{ + vector dirents; + unique_ptr pNameList = { new (nothrow) struct NameListArg, Deleter }; + if (!pNameList) { + HILOG_ERROR("RecoverDir: Failed to request heap memory."); + return nullptr; + } + int ret = RecursiveFunc(dirPath, dirents); + if (ret != ERRNO_NOERR) { + HILOG_ERROR("RecoverDir: Failed to Recursive Dir."); + return nullptr; + } + dirents.emplace_back(dirPath); + + // 区分目录和文件 + vector dirPathList; + vector filePathList; + for (int j = 0; j < dirents.size(); j++) { + string dirent = dirents[j]; + if (CheckDir(dirent)) { + dirPathList.emplace_back(dirent); + } else { + filePathList.emplace_back(dirent); + } + } + // 目录从长到短排序 + sort(dirPathList.begin(), dirPathList.end(), [&](const string &a, const string &b) { + return a.length() > b.length(); + }); + + // 先处理目录,仅保留不互相包含的目录(取子目录较深的) + vector filterDirPathList = FilterDirsNoContains(dirPathList); + if (filterDirPathList.size() == 0) { + HILOG_ERROR("RecoverDir: NO valid dirs found"); + return nullptr; + } + + // 新建目录并获取因为存在同名目录修改过名称的目录 + map dirPath2UpdatedNameMap = MakeAndFindUpdateNameDir(filterDirPathList); + + // 处理文件部分 + RecoverFilePart(filePathList, dirPath2UpdatedNameMap); + + // 删除目录 + auto err = RmDirent(GetToDeletePath(dirPath, env)); + if (err) { + err.ThrowErr(env); + return nullptr; + } + + return NVal::CreateUndefined(env).val_; +} + +napi_value FileTrashNExporter::Recover(napi_env env, napi_callback_info info) +{ + if (!CheckCallingPermission(FILE_ACCESS_PERMISSION)) { + HILOG_ERROR("permission error"); + NError(E_PERMISSION).ThrowErr(env); + return nullptr; + } + + NFuncArg funcArg(env, info); + if (!funcArg.InitArgs(NARG_CNT::ONE)) { + HILOG_ERROR("Number of arguments unmatched"); + NError(EINVAL).ThrowErr(env); + return nullptr; + } + bool succ = false; + unique_ptr uriPtr; + tie(succ, uriPtr, ignore) = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String(); + if (!succ) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + string uriStr = uriPtr.get(); + HILOG_ERROR("Recover: uriPtr.get() = %{public}s", uriStr.c_str()); + + // 获取沙箱目录地址 + AppFileService::ModuleFileUri::FileUri fileUri(uriStr); + string path = fileUri.GetPath(); + HILOG_ERROR("Recover: path = %{public}s", path.c_str()); + + // 判断是否是回收站路径 + if (path.find(TRASH_PATH) == string::npos) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + + // 判断路径是否存在 + if (!Access(path)) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + + if (!CheckDir(path)) { + return RecoverFile(env, path); + } + return RecoverDir(env, path); +} + +napi_value FileTrashNExporter::CompletelyDelete(napi_env env, napi_callback_info info) +{ + if (!CheckCallingPermission(FILE_ACCESS_PERMISSION)) { + HILOG_ERROR("permission error"); + NError(E_PERMISSION).ThrowErr(env); + return nullptr; + } + + NFuncArg funcArg(env, info); + if (!funcArg.InitArgs(NARG_CNT::ONE)) { + HILOG_ERROR("Number of arguments unmatched"); + NError(EINVAL).ThrowErr(env); + return nullptr; + } + bool succ = false; + unique_ptr uriPtr; + tie(succ, uriPtr, ignore) = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String(); + if (!succ) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + + string uriStr = uriPtr.get(); + HILOG_ERROR("Recover: uriPtr.get() = %{public}s", uriStr.c_str()); + + // 获取沙箱目录地址 + AppFileService::ModuleFileUri::FileUri fileUri(uriStr); + string path = fileUri.GetPath(); + HILOG_ERROR("Recover: path = %{public}s", path.c_str()); + + // 判断是否是回收站路径 + if (path.find(TRASH_PATH) == string::npos) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + + // 判断路径是否存在 + if (!Access(path)) { + NError(EINVAL).ThrowErr(env); + return nullptr; + } + + // 删除目录 + auto err = RmDirent(GetToDeletePath(path, env)); + if (err) { + err.ThrowErr(env); + return nullptr; + } + return NVal::CreateUndefined(env).val_; +} + +bool FileTrashNExporter::Export() +{ + return exports_.AddProp({ + NVal::DeclareNapiFunction("listFile", ListFile), + NVal::DeclareNapiFunction("recover", Recover), + NVal::DeclareNapiFunction("completelyDelete", CompletelyDelete) + }); +} + +string FileTrashNExporter::GetClassName() +{ + return FileTrashNExporter::className_; +} + +FileTrashNExporter::FileTrashNExporter(napi_env env, napi_value exports) : NExporter(env, exports) {} + +FileTrashNExporter::~FileTrashNExporter() {} +} // namespace Trash +} // namespace OHOS \ No newline at end of file diff --git a/services/file_extension_hap/entry/src/main/ets/FileExtensionAbility/FileExtensionAbility.ts b/services/file_extension_hap/entry/src/main/ets/FileExtensionAbility/FileExtensionAbility.ts index aba430b5..9f47f49e 100644 --- a/services/file_extension_hap/entry/src/main/ets/FileExtensionAbility/FileExtensionAbility.ts +++ b/services/file_extension_hap/entry/src/main/ets/FileExtensionAbility/FileExtensionAbility.ts @@ -50,6 +50,8 @@ const DELETE_EVENT = 1; const MOVED_TO = 2; const MOVED_FROM = 3; const MOVED_SELF = 4; +const TRASH_PATH = '/storage/.Trash/Users/100/'; +const TRASH_SUB_FODER = '/oh_trash_content'; let observerMap = new Map(); let watcherCountMap = new Map(); let eventMap = new Map([ @@ -275,6 +277,76 @@ export default class FileExtAbility extends Extension { } } + mkdirs(path, isDirectory): void { + if (path.length > 0) { + // if argument uri is dir, then add '/' + if (isDirectory) { + path = path + '/'; + } + for (let i = 1; i < path.length; ++i) { + if (path.charAt(i) === '/') { + let subDir = path.substring(0, i); + hilog.info(DOMAIN_CODE, TAG, 'mkdirs: subDir path = ' + subDir); + try { + let isAccess = fs.accessSync(subDir); + if (!isAccess) { + fs.mkdirSync(subDir); + } + } catch (e) { + hilog.error(DOMAIN_CODE, TAG, 'mkdirs error ' + e.message); + } + } + } + } + } + + deleteToTrash(path): number { + hilog.info(DOMAIN_CODE, TAG, 'deleteToTrash: path:' + path); + let code = ERR_OK; + let pathLen = path.length; + if (path.charAt(pathLen - 1) === '/') { + path = path.substring(0, pathLen - 1); + } + // 取最后一级和前一层目录 + let posLastSlash = path.lastIndexOf('/'); + if (posLastSlash === -1) { + hilog.error(DOMAIN_CODE, TAG, 'Mkdirs: invalid uri'); + return E_URIS; + } + + let selectPathOnly = path.substring(0, posLastSlash); + // 获取时间戳 + let curTime = new Date().getTime(); + // 拼接新路径 + let currentTrashParentPath = TRASH_PATH + curTime + selectPathOnly + TRASH_SUB_FODER + curTime; + hilog.info(DOMAIN_CODE, TAG, 'deleteToTrash: currentTrashParentPath:' + currentTrashParentPath); + // 创建回收站目录 + this.mkdirs(currentTrashParentPath, true); + try { + let stat = fs.statSync(path); + if (!stat.isDirectory()) { + let selectFileOnly = path.substring(posLastSlash); + hilog.info(DOMAIN_CODE, TAG, 'deleteToTrash: selectFileOnly:' + selectFileOnly); + let newFileName = currentTrashParentPath + selectFileOnly; + hilog.info(DOMAIN_CODE, TAG, 'deleteToTrash: newFileName:' + newFileName); + // 移动文件 + fs.moveFileSync(path, newFileName, 0); + } else { + // 待删除文件夹 + let toDeleteDir = path.substring(posLastSlash); + // 移动待删除文件夹 + fs.moveDirSync(selectPathOnly + toDeleteDir, currentTrashParentPath, MOVE_MODLE_CODE); + if (fs.accessSync(path)) { + fs.rmdirSync(path); + } + } + } catch (e) { + hilog.error(DOMAIN_CODE, TAG, 'deleteToTrash error ' + e.message); + return e.code; + } + return code; + } + delete(selectFileUri): number { selectFileUri = this.decode(selectFileUri); if (selectFileUri === '') { @@ -287,19 +359,9 @@ export default class FileExtAbility extends Extension { return E_URIS; } let path = getPath(selectFileUri); - let code = ERR_OK; - try { - let stat = fs.statSync(path); - if (stat.isDirectory()) { - fs.rmdirSync(path); - } else { - fs.unlinkSync(path); - } - } catch (e) { - hilog.error(DOMAIN_CODE, TAG, 'deleteFile error ' + e.message); - return e.code; - } - return code; + + hilog.info(DOMAIN_CODE, TAG, 'Delete: path = ' + path); + return this.deleteToTrash(path); } move(sourceFileUri, targetParentUri): {string, number} { diff --git a/utils/file_util.h b/utils/file_util.h new file mode 100644 index 00000000..a9b1b616 --- /dev/null +++ b/utils/file_util.h @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2023 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 +#include +#include +#include +#include +#include +#include +#include + +#include "filemgmt_libn.h" +#include "hilog_wrapper.h" + +namespace OHOS::FileManagement { + +using namespace FileManagement::LibN; +using namespace std; + +constexpr int DIR_DEFAULT_PERM = 0770; +constexpr int FILTER_MATCH = 1; +constexpr int FILTER_DISMATCH = 0; +constexpr int MODE_FORCE_MOVE = 0; + +struct NameListArg { + struct dirent** namelist = { nullptr }; + int direntNum = 0; +}; + +struct StatEntity { + uv_stat_t stat_; +}; + +static bool StartsWith(const string& str, const string prefix) +{ + return (str.rfind(prefix, 0) == 0); +} + +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); +} + +static int32_t FilterFunc(const struct dirent *filename) +{ + if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") { + return FILTER_DISMATCH; + } + return FILTER_MATCH; +} + +static void fs_req_cleanup(uv_fs_t* req) +{ + uv_fs_req_cleanup(req); + if (req) { + delete req; + req = nullptr; + } +} + +static int CheckFsStatByPath(const string &path, uv_fs_t* req) +{ + int ret = uv_fs_stat(nullptr, req, path.c_str(), nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to stat file with path"); + return ret; + } + return ERRNO_NOERR; +} + +static bool GetStat(const string &path, StatEntity &statEntity) +{ + unique_ptr stat_req = { + new (nothrow) uv_fs_t, fs_req_cleanup }; + if (!stat_req) { + HILOG_ERROR("Failed to request heap memory."); + return false; + } + auto err = CheckFsStatByPath(path, stat_req.get()); + if (!err) { + statEntity = StatEntity { stat_req->statbuf }; + return true; + } + return false; +} + +static bool CheckDir(const string &path) +{ + struct stat fileInformation; + if (stat(path.c_str(), &fileInformation) == 0 && (fileInformation.st_mode & S_IFDIR)) { + return true; + } else { + HILOG_ERROR("Failed to stat file"); + } + return false; +} + +static bool Access(const string &path) +{ + unique_ptr access_req = { new uv_fs_t, fs_req_cleanup }; + if (!access_req) { + HILOG_ERROR("Failed to request heap memory."); + return false; + } + int ret = uv_fs_access(nullptr, access_req.get(), path.c_str(), 0, nullptr); + if (ret < 0 && (string_view(uv_err_name(ret)) != "ENOENT")) { + HILOG_ERROR("Failed to access file by path"); + return false; + } + if (ret == 0) { + return true; + } + + return false; +} + +static bool Mkdir(const string &path) +{ + unique_ptr mkdir_req = { new uv_fs_t, fs_req_cleanup }; + if (!mkdir_req) { + HILOG_ERROR("Failed to request heap memory."); + return false; + } + int ret = uv_fs_mkdir(nullptr, mkdir_req.get(), path.c_str(), DIR_DEFAULT_PERM, nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to create directory"); + return false; + } + if (ret == 0) { + return true; + } + return false; +} + +static int CopyAndDeleteFile(const string &src, const string &dest) +{ + int ret = 0; + uv_fs_t copyfile_req; + ret = uv_fs_copyfile(nullptr, ©file_req, src.c_str(), dest.c_str(), MODE_FORCE_MOVE, nullptr); + uv_fs_req_cleanup(©file_req); + if (ret < 0) { + HILOG_ERROR("Failed to move file using copyfile interface."); + return ret; + } + uv_fs_t unlink_req; + ret = uv_fs_unlink(nullptr, &unlink_req, src.c_str(), nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to unlink src file"); + ret = uv_fs_unlink(nullptr, &unlink_req, dest.c_str(), nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to unlink dest file"); + } + uv_fs_req_cleanup(&unlink_req); + return ret; + } + uv_fs_req_cleanup(&unlink_req); + return ERRNO_NOERR; +} + +static int RenameFile(const string &src, const string &dest) +{ + unique_ptr rename_req = { + new uv_fs_t, fs_req_cleanup }; + if (!rename_req) { + HILOG_ERROR("RenameFile: Failed to request heap memory."); + return false; + } + int ret = uv_fs_rename(nullptr, rename_req.get(), src.c_str(), dest.c_str(), nullptr); + if (ret < 0 && (string_view(uv_err_name(ret)) == "EXDEV")) { + return CopyAndDeleteFile(src, dest); + } + if (ret < 0) { + HILOG_ERROR("RenameFile: Failed to move file using rename syscall ret %{public}d ", ret); + return ret; + } + return ERRNO_NOERR; +} + +static NError RmDirent(const string &fpath) +{ + unique_ptr scandir_req = { + new (nothrow) uv_fs_t, fs_req_cleanup }; + if (!scandir_req) { + HILOG_ERROR("Failed to request heap memory."); + return NError(ENOMEM); + } + int ret = 0; + ret = uv_fs_scandir(nullptr, scandir_req.get(), fpath.c_str(), 0, nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to scandir, ret: %{public}d", ret); + return NError(ret); + } + uv_dirent_t dent; + while (uv_fs_scandir_next(scandir_req.get(), &dent) != UV_EOF) { + string filePath = fpath + "/" + string(dent.name); + if (dent.type == UV_DIRENT_FILE) { + unique_ptr unlink_req = { + new (nothrow) uv_fs_t, fs_req_cleanup }; + if (!unlink_req) { + HILOG_ERROR("Failed to request heap memory."); + return NError(ENOMEM); + } + ret = uv_fs_unlink(nullptr, unlink_req.get(), filePath.c_str(), nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to unlink file, ret: %{public}d", ret); + return NError(ret); + } + } else if (dent.type == UV_DIRENT_DIR) { + auto rmDirentRes = RmDirent(filePath); + if (rmDirentRes) { + return rmDirentRes; + } + } + } + unique_ptr rmdir_req = { + new (nothrow) uv_fs_t, fs_req_cleanup }; + if (!rmdir_req) { + HILOG_ERROR("Failed to request heap memory."); + return NError(ENOMEM); + } + ret = uv_fs_rmdir(nullptr, rmdir_req.get(), fpath.c_str(), nullptr); + if (ret < 0) { + HILOG_ERROR("Failed to rmdir empty dir, ret: %{public}d", ret); + return NError(ret); + } + return NError(ERRNO_NOERR); +} + +static int ScanDir(const string &path) +{ + unique_ptr pNameList = { new (nothrow) struct NameListArg, Deleter }; + if (!pNameList) { + HILOG_ERROR("Failed to request heap memory."); + return ENOMEM; + } + HILOG_INFO("RecursiveFunc: scandir path = %{public}s", path.c_str()); + return scandir(path.c_str(), &(pNameList->namelist), FilterFunc, alphasort); +} + +} // OHOS::FileManagement \ No newline at end of file -- Gitee