diff --git a/interfaces/kits/js/BUILD.gn b/interfaces/kits/js/BUILD.gn index 9334220be9d28e01972e60ed84bec52d044d4e8a..d24e8c530ac70fa3daa714ad49d2e9d8d5ae1b06 100644 --- a/interfaces/kits/js/BUILD.gn +++ b/interfaces/kits/js/BUILD.gn @@ -686,6 +686,7 @@ ohos_shared_library("ani_fs_class") { "src/mod_fs/properties/ani/listfile_ani.cpp", "src/mod_fs/properties/ani/mkdir_ani.cpp", "src/mod_fs/properties/ani/move_ani.cpp", + "src/mod_fs/properties/ani/movedir_ani.cpp", "src/mod_fs/properties/ani/open_ani.cpp", "src/mod_fs/properties/ani/read_ani.cpp", "src/mod_fs/properties/ani/read_text_ani.cpp", @@ -698,6 +699,7 @@ ohos_shared_library("ani_fs_class") { "src/mod_fs/properties/listfile_core.cpp", "src/mod_fs/properties/mkdir_core.cpp", "src/mod_fs/properties/move_core.cpp", + "src/mod_fs/properties/movedir_core.cpp", "src/mod_fs/properties/open_core.cpp", "src/mod_fs/properties/read_core.cpp", "src/mod_fs/properties/read_text_core.cpp", diff --git a/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp b/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp index 065495c9ade9c4c775ce494fa218d0c382077459..5565a15ba3cbb821fa0ff2240e7e085a09856bf5 100644 --- a/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp +++ b/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp @@ -26,6 +26,7 @@ #include "listfile_ani.h" #include "mkdir_ani.h" #include "move_ani.h" +#include "movedir_ani.h" #include "open_ani.h" #include "read_ani.h" #include "read_text_ani.h" @@ -79,6 +80,7 @@ static ani_status BindStaticMethods(ani_env *env) ani_native_function { "listFileSync", nullptr, reinterpret_cast(ListFileAni::ListFileSync) }, ani_native_function { "mkdirSync", "Lstd/core/String;:V", reinterpret_cast(MkdirkAni::MkdirSync0) }, ani_native_function { "mkdirSync", "Lstd/core/String;Z:V", reinterpret_cast(MkdirkAni::MkdirSync1) }, + ani_native_function { "movedirSync", nullptr, reinterpret_cast(MoveDirAni::MoveDirSync) }, ani_native_function { "moveFileSync", nullptr, reinterpret_cast(MoveAni::MoveFileSync) }, ani_native_function { "openSync", nullptr, reinterpret_cast(OpenAni::OpenSync) }, ani_native_function { "readSync", nullptr, reinterpret_cast(ReadAni::ReadSync) }, diff --git a/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets b/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets index 67eb95add30a0cd803f0542ae46c90f870189651..ca08314785a067554a90acb5ed5b4d3ee00ad9c7 100644 --- a/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets +++ b/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets @@ -164,6 +164,60 @@ function mkdir(path: string, recursion: boolean, callback: AsyncCallback): }); } +function moveDirSync(src: string, dest: string, mode?: number): void { + return FileIoImpl.movedirSync(src, dest, mode) +} + +function moveDir(src: string, dest: string, mode?: number): Promise { + return new Promise((resolve: (result: undefined) => void, reject: (e: BusinessError) => void): void => { + if (mode === undefined) { + let promise = taskpool.execute((src: string, dest: string): undefined => { + return FileIoImpl.movedirSync(src, dest); + }, src, dest); + promise.then((ret: NullishType): void => { + resolve(undefined); + }).catch((e: BusinessError): void => { + reject(e); + }); + } else { + let promise = taskpool.execute((src: string, dest: string, mode: number): undefined => { + return FileIoImpl.movedirSync(src, dest, mode); + }, src, dest, mode); + promise.then((ret: NullishType): void => { + resolve(undefined); + }).catch((e: BusinessError): void => { + reject(e); + }); + } + }) +} + +function moveDir(src: string, dest: string, callback: AsyncCallback): void { + let promise = taskpool.execute((src: string, dest: string): undefined => { + return FileIoImpl.movedirSync(src, dest); + }, src, dest); + promise.then((ret: NullishType): void => { + let e = new BusinessError(); + e.code = 0; + callback(e, undefined); + }).catch((e: BusinessError): void => { + callback(e, undefined); + }); +} + +function moveDir(src: string, dest: string, mode: number, callback: AsyncCallback): void { + let promise = taskpool.execute((src: string, dest: string, mode: number): undefined => { + return FileIoImpl.movedirSync(src, dest, mode); + }, src, dest, mode); + promise.then((ret: NullishType): void => { + let e = new BusinessError(); + e.code = 0; + callback(e, undefined); + }).catch((e: BusinessError): void => { + callback(e, undefined); + }); +} + function moveFileSync(src: string, dest: string, mode?: number): void { return FileIoImpl.moveFileSync(src, dest, mode); } @@ -641,6 +695,8 @@ class FileIoImpl { static native mkdirSync(path: string, recursion: boolean): void; + static native movedirSync(src: string, dest: string, mode?: number): void; + static native moveFileSync(src: String, dest: String, mode?: number): void; static native openSync(path: String, mode?: number): File; diff --git a/interfaces/kits/js/src/mod_fs/properties/ani/movedir_ani.cpp b/interfaces/kits/js/src/mod_fs/properties/ani/movedir_ani.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1e2c137c908208a5a760f571fc527ad9ce5529de --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/properties/ani/movedir_ani.cpp @@ -0,0 +1,57 @@ +/* + * 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 "movedir_ani.h" + +#include "error_handler.h" +#include "filemgmt_libhilog.h" +#include "movedir_core.h" +#include "type_converter.h" + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +namespace ANI { + +void MoveDirAni::MoveDirSync( + ani_env *env, [[maybe_unused]] ani_class clazz, ani_string src, ani_string dest, ani_object mode) +{ + auto [succSrc, srcPath] = TypeConverter::ToUTF8String(env, src); + auto [succDest, destPath] = TypeConverter::ToUTF8String(env, dest); + if (!succSrc || !succDest) { + HILOGE("The first/second argument requires filepath"); + ErrorHandler::Throw(env, EINVAL); + return; + } + + auto [succMode, optMode] = TypeConverter::ToOptionalInt32(env, mode); + if (!succMode) { + HILOGE("Failed to convert mode to int32"); + ErrorHandler::Throw(env, EINVAL); + return; + } + + auto ret = MoveDirCore::DoMoveDir(srcPath, destPath, optMode); + if (!ret.fsResult.IsSuccess()) { + HILOGE("DoCopyFile failed!"); + const FsError &err = ret.fsResult.GetError(); + ErrorHandler::Throw(env, err); + return; + } +} +} // namespace ANI +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/properties/ani/movedir_ani.h b/interfaces/kits/js/src/mod_fs/properties/ani/movedir_ani.h new file mode 100644 index 0000000000000000000000000000000000000000..bd17504464739b6b56f293980792b1e72b05d858 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/properties/ani/movedir_ani.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#ifndef INTERFACES_KITS_JS_SRC_MOD_FS_PROPERTIES_ANI_MOVEDIR_ANI_H +#define INTERFACES_KITS_JS_SRC_MOD_FS_PROPERTIES_ANI_MOVEDIR_ANI_H + +#include + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +namespace ANI { + +class MoveDirAni final { +public: + static void MoveDirSync( + ani_env *env, [[maybe_unused]] ani_class clazz, ani_string src, ani_string dest, ani_object mode); +}; +} // namespace ANI +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS + +#endif // INTERFACES_KITS_JS_SRC_MOD_FS_PROPERTIES_ANI_MOVEDIR_ANI_H \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/properties/movedir_core.cpp b/interfaces/kits/js/src/mod_fs/properties/movedir_core.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fb357ff05a3a69c00db190eb57a16472988fd709 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/properties/movedir_core.cpp @@ -0,0 +1,306 @@ +/* + * 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 "movedir_core.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "file_utils.h" +#include "filemgmt_libhilog.h" + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +using namespace std; + +static int RecurMoveDir(const string &srcPath, const string &destPath, const int mode, + deque &errfiles); + +static tuple JudgeExistAndEmpty(const string &path) +{ + std::error_code errCode; + filesystem::path pathName(path); + if (filesystem::exists(pathName, errCode)) { + if (filesystem::is_empty(pathName, errCode)) { + return { true, true }; + } + return { true, false }; + } + return { false, false }; +} + +static int RmDirectory(const string &path) +{ + filesystem::path pathName(path); + std::error_code errCode; + if (filesystem::exists(pathName, errCode)) { + std::error_code errCode; + (void)filesystem::remove_all(pathName, errCode); + if (errCode.value() != 0) { + HILOGE("Failed to remove directory, error code: %{public}d", errCode.value()); + return errCode.value(); + } + } else if (errCode.value() != ERRNO_NOERR) { + HILOGE("fs exists fail, errcode is %{public}d", errCode.value()); + return errCode.value(); + } + return ERRNO_NOERR; +} + +static int RemovePath(const string& pathStr) +{ + filesystem::path pathTarget(pathStr); + std::error_code errCode; + if (!filesystem::remove(pathTarget, errCode)) { + HILOGE("Failed to remove file or directory, error code: %{public}d", errCode.value()); + return errCode.value(); + } + return ERRNO_NOERR; +} + +static int CopyAndDeleteFile(const string &src, const string &dest) +{ + filesystem::path dstPath(dest); + std::error_code errCode; + if (filesystem::exists(dstPath, errCode)) { + int removeRes = RemovePath(dest); + if (removeRes != 0) { + HILOGE("Failed to remove dest file"); + return removeRes; + } + } + filesystem::path srcPath(src); + if (!filesystem::copy_file(srcPath, dstPath, filesystem::copy_options::overwrite_existing, errCode)) { + HILOGE("Failed to copy file, error code: %{public}d", errCode.value()); + return errCode.value(); + } + return RemovePath(src); +} + +static int RenameFile(const string &src, const string &dest, const int mode, deque &errfiles) +{ + filesystem::path dstPath(dest); + std::error_code errCode; + if (filesystem::exists(dstPath, errCode)) { + if (filesystem::is_directory(dstPath, errCode)) { + errfiles.emplace_front(src, dest); + return ERRNO_NOERR; + } + if (mode == DIRMODE_FILE_THROW_ERR) { + errfiles.emplace_back(src, dest); + return ERRNO_NOERR; + } + } + if (errCode.value() != ERRNO_NOERR) { + HILOGE("fs exists or is_directory fail, errcode is %{public}d", errCode.value()); + } + filesystem::path srcPath(src); + filesystem::rename(srcPath, dstPath, errCode); + if (errCode.value() == EXDEV) { + HILOGD("Failed to rename file due to EXDEV"); + return CopyAndDeleteFile(src, dest); + } + return errCode.value(); +} + +static int32_t FilterFunc(const struct dirent *filename) +{ + if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") { + return FILE_DISMATCH; + } + return FILE_MATCH; +} + +static int RenameDir(const string &src, const string &dest, const int mode, deque &errfiles) +{ + filesystem::path destPath(dest); + std::error_code errCode; + if (filesystem::exists(destPath, errCode)) { + return RecurMoveDir(src, dest, mode, errfiles); + } else if (errCode.value() != ERRNO_NOERR) { + HILOGE("fs exists fail, errcode is %{public}d", errCode.value()); + return errCode.value(); + } + filesystem::path srcPath(src); + filesystem::rename(srcPath, destPath, errCode); + if (errCode.value() == EXDEV) { + HILOGD("Failed to rename file due to EXDEV"); + if (!filesystem::create_directory(destPath, errCode)) { + HILOGE("Failed to create directory, error code: %{public}d", errCode.value()); + return errCode.value(); + } + return RecurMoveDir(src, dest, mode, errfiles); + } + if (errCode.value() != 0) { + HILOGE("Failed to rename file, error code: %{public}d", errCode.value()); + return errCode.value(); + } + return ERRNO_NOERR; +} + +struct NameListArg { + struct dirent** namelist; + int num; +}; + +static void Deleter(struct NameListArg *arg) +{ + for (int i = 0; i < arg->num; i++) { + free((arg->namelist)[i]); + (arg->namelist)[i] = nullptr; + } + free(arg->namelist); + arg->namelist = nullptr; + delete arg; + arg = nullptr; +} + +static int RecurMoveDir(const string &srcPath, const string &destPath, const int mode, + deque &errfiles) +{ + filesystem::path dpath(destPath); + std::error_code errCode; + if (!filesystem::is_directory(dpath, errCode)) { + errfiles.emplace_front(srcPath, destPath); + return ERRNO_NOERR; + } + + unique_ptr ptr = {new struct NameListArg, Deleter}; + if (!ptr) { + HILOGE("Failed to request heap memory."); + return ENOMEM; + } + int num = scandir(srcPath.c_str(), &(ptr->namelist), FilterFunc, alphasort); + ptr->num = num; + + for (int i = 0; i < num; i++) { + if ((ptr->namelist[i])->d_type == DT_DIR) { + string srcTemp = srcPath + '/' + string((ptr->namelist[i])->d_name); + string destTemp = destPath + '/' + string((ptr->namelist[i])->d_name); + size_t size = errfiles.size(); + int res = RenameDir(srcTemp, destTemp, mode, errfiles); + if (res != ERRNO_NOERR) { + return res; + } + if (size != errfiles.size()) { + continue; + } + res = RemovePath(srcTemp); + if (res) { + return res; + } + } else { + string src = srcPath + '/' + string((ptr->namelist[i])->d_name); + string dest = destPath + '/' + string((ptr->namelist[i])->d_name); + int res = RenameFile(src, dest, mode, errfiles); + if (res != ERRNO_NOERR) { + HILOGE("Failed to rename file for error %{public}d", res); + return res; + } + } + } + return ERRNO_NOERR; +} + +static int MoveDirFunc(const string &src, const string &dest, const int mode, deque &errfiles) +{ + size_t found = string(src).rfind('/'); + if (found == std::string::npos) { + return EINVAL; + } + if (access(src.c_str(), W_OK) != 0) { + HILOGE("Failed to move src directory due to doesn't exist or hasn't write permission"); + return errno; + } + string dirName = string(src).substr(found); + string destStr = dest + dirName; + auto [destStrExist, destStrEmpty] = JudgeExistAndEmpty(destStr); + if (destStrExist && !destStrEmpty) { + if (mode == DIRMODE_DIRECTORY_REPLACE) { + int removeRes = RmDirectory(destStr); + if (removeRes) { + HILOGE("Failed to remove dest directory in DIRMODE_DIRECTORY_REPLACE"); + return removeRes; + } + } + if (mode == DIRMODE_DIRECTORY_THROW_ERR) { + HILOGE("Failed to move directory in DIRMODE_DIRECTORY_THROW_ERR"); + return ENOTEMPTY; + } + } + int res = RenameDir(src, destStr, mode, errfiles); + if (res == ERRNO_NOERR) { + if (!errfiles.empty()) { + HILOGE("Failed to movedir with some conflicted files"); + return EEXIST; + } + int removeRes = RmDirectory(src); + if (removeRes) { + HILOGE("Failed to remove src directory"); + return removeRes; + } + } + return res; +} + +static tuple ValidMoveDirArg( + const string &src, const string &dest, optional mode) +{ + std::error_code errCode; + if (!filesystem::is_directory(filesystem::status(src.c_str(), errCode))) { + HILOGE("Invalid src, errCode = %{public}d", errCode.value()); + return { false, 0 }; + } + if (!filesystem::is_directory(filesystem::status(dest.c_str(), errCode))) { + HILOGE("Invalid dest,errCode = %{public}d", errCode.value()); + return { false, 0 }; + } + int modeType = 0; + if (mode.has_value()) { + modeType = mode.value(); + if (modeType < DIRMODE_MIN || modeType > DIRMODE_MAX) { + HILOGE("Invalid mode"); + return { false, 0 }; + } + } + return { true, modeType }; +} + +MoveDirResult MoveDirCore::DoMoveDir( + const string &src, const string &dest, optional modeType) +{ + auto [succ, mode] = ValidMoveDirArg(src, dest, modeType); + if (!succ) { + return { FsResult::Error(EINVAL), nullopt }; + } + deque errfiles = {}; + int ret = MoveDirFunc(src, dest, mode, errfiles); + if (ret == EEXIST) { + return { FsResult::Error(EEXIST), optional> { errfiles } }; + } else if (ret) { + return { FsResult::Error(ret), nullopt }; + } + return { FsResult::Success(), nullopt }; +} + +} // ModuleFileIO +} // FileManagement +} // OHOS \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/properties/movedir_core.h b/interfaces/kits/js/src/mod_fs/properties/movedir_core.h new file mode 100755 index 0000000000000000000000000000000000000000..f8d8f7f2e2b452ca3a4791a50ee0f2e1bad22ee8 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/properties/movedir_core.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#ifndef INTERFACES_KITS_JS_SRC_MOD_FS_PROPERTIES_MOVEDIR_CORE_H +#define INTERFACES_KITS_JS_SRC_MOD_FS_PROPERTIES_MOVEDIR_CORE_H + +#include +#include + +#include "filemgmt_libfs.h" +#include "fs_utils.h" + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { + +constexpr int DIRMODE_MIN = 0; +constexpr int DIRMODE_MAX = 3; + +constexpr int FILE_DISMATCH = 0; +constexpr int FILE_MATCH = 1; +constexpr int MOVEDIR_DEFAULT_PERM = 0770; + +enum ModeOfMoveDir { + DIRMODE_DIRECTORY_THROW_ERR = 0, + DIRMODE_FILE_THROW_ERR, + DIRMODE_FILE_REPLACE, + DIRMODE_DIRECTORY_REPLACE +}; + +struct ErrFiles { + std::string srcFiles; + std::string destFiles; + ErrFiles(const std::string& src, const std::string& dest) : srcFiles(src), destFiles(dest) {} + ~ErrFiles() = default; +}; + +struct MoveDirResult { + FsResult fsResult; + optional> errFiles; +}; + +class MoveDirCore final { +public: + static MoveDirResult DoMoveDir( + const std::string &src,const std::string &dest, std::optional mode = std::nullopt); +}; + +const std::string PROCEDURE_MOVEDIR_NAME = "FileIOMoveDir"; +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS +#endif // INTERFACES_KITS_JS_SRC_MOD_FS_PROPERTIES_MOVEDIR_CORE_H \ No newline at end of file