diff --git a/OAT.xml b/OAT.xml index 29eae41eedee99a9e500aa1f4e53b08ccda5e501..956a5add37c4e7ed742a1a2a5c81dc634e9eb89e 100644 --- a/OAT.xml +++ b/OAT.xml @@ -16,6 +16,7 @@ + @@ -35,6 +36,7 @@ + @@ -57,6 +59,7 @@ + diff --git a/example/.eslintrc b/example/.eslintrc new file mode 100644 index 0000000000000000000000000000000000000000..76cd1cdca17f5f45ea5a75fd4b493c47655cbe67 --- /dev/null +++ b/example/.eslintrc @@ -0,0 +1,13 @@ +{ + "extends": "@react-native", + "rules": { + "react-native/no-inline-styles": "off", + "react/no-unstable-nested-components": "off", + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-unused-vars": "warn", + "react-hooks/exhaustive-deps": "off", + "radix": "off", + "prettier/prettier": "warn", + "max-lines": "off" + } +} \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7613cdedd309866629ce9ae75eb3855ea0849f0a --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,71 @@ +.husky +bundle.harmony.js +package-lock.json +*.hbc +lintCppResult.txt + +# --- + +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore +BuildProfile.ets + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* +*.htrace \ No newline at end of file diff --git a/example/.node-version b/example/.node-version new file mode 100644 index 0000000000000000000000000000000000000000..3c032078a4a21c5c51d3c93d91717c1dabbb8cd0 --- /dev/null +++ b/example/.node-version @@ -0,0 +1 @@ +18 diff --git a/example/.prettierrc.js b/example/.prettierrc.js new file mode 100644 index 0000000000000000000000000000000000000000..2ae7b381ed9590ddf3ba512a496dc8d53dadeea0 --- /dev/null +++ b/example/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + arrowParens: 'avoid', + bracketSameLine: true, + bracketSpacing: true, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/example/.watchmanconfig b/example/.watchmanconfig new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/example/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/example/app.json b/example/app.json new file mode 100644 index 0000000000000000000000000000000000000000..7586f6fa99a6680a35a27fac7255ba721880d461 --- /dev/null +++ b/example/app.json @@ -0,0 +1,4 @@ +{ + "name": "app_name", + "displayName": "tester" +} \ No newline at end of file diff --git a/example/babel.config.js b/example/babel.config.js new file mode 100644 index 0000000000000000000000000000000000000000..481136144aa7bed704ad81628802d8a43930a6a1 --- /dev/null +++ b/example/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: ['module:metro-react-native-babel-preset'], + plugins: [ + ], +}; diff --git a/example/contexts.ts b/example/contexts.ts new file mode 100644 index 0000000000000000000000000000000000000000..9437598452691ccc56d19f5532607475aa5d33b4 --- /dev/null +++ b/example/contexts.ts @@ -0,0 +1,3 @@ +import React from 'react'; + +export const AppParamsContext = React.createContext(undefined); diff --git a/example/harmony/.gitignore b/example/harmony/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..72d73fb4b6240ccd907882c931ce08ee45ca62ba --- /dev/null +++ b/example/harmony/.gitignore @@ -0,0 +1,24 @@ +# it may cause some issues when building the project when switching branches +package-lock.json +# we add this because we want to keep the signing configs out of git +/build-profile.json5 + +rnoh_modules + +**/oh-package-lock.json5 +# --- + +/node_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +/oh_modules +hvigorw +hvigorw.bat +hvigor/hvigor-wrapper.js + diff --git a/example/harmony/AppScope/app.json5 b/example/harmony/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..058bd391dac60928a1857aa31e4b0f712a7bc743 --- /dev/null +++ b/example/harmony/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.harmony.wechat.lib.demo", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/example/harmony/AppScope/resources/base/element/string.json b/example/harmony/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..698a720065342ae0dadad63eb87d45fec8725f36 --- /dev/null +++ b/example/harmony/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "RN Tester" + } + ] +} diff --git a/example/harmony/AppScope/resources/base/media/app_icon.png b/example/harmony/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/harmony/AppScope/resources/base/media/app_icon.png differ diff --git a/example/harmony/build-profile.template.json5 b/example/harmony/build-profile.template.json5 new file mode 100644 index 0000000000000000000000000000000000000000..261441975aaea2bd1ae88a5b063a5b2878efe0e9 --- /dev/null +++ b/example/harmony/build-profile.template.json5 @@ -0,0 +1,40 @@ +{ + "app": { + "products": [ + { + "name": 'default', + "signingConfig": 'default', + "compileSdkVersion": '5.0.0(12)', + "compatibleSdkVersion": '5.0.0(12)', + "runtimeOS": 'HarmonyOS' + }, + ], + "buildModeSet": [ + { + "name": 'debug', + }, + { + "name": 'release', + }, + ], + "signingConfigs": [] + }, + "modules": [ + { + "name": 'entry', + "srcPath": './entry', + "targets": [ + { + "name": 'default', + "applyToProducts": [ + 'default' + ], + }, + ], + }, + { + "name": "fs", + "srcPath": './fs' + } + ], +} \ No newline at end of file diff --git a/example/harmony/codelinter.json b/example/harmony/codelinter.json new file mode 100644 index 0000000000000000000000000000000000000000..e7f91acb037aeda939967a042f16033f4c6e21b9 --- /dev/null +++ b/example/harmony/codelinter.json @@ -0,0 +1,32 @@ +{ + "files": ["**/*.ts", "**/*.ets"], + "ignore": [ + "**/ohosTest/**/*", + "**/node_modules/**/*", + "**/hvigorfile.ts", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "plugins": ["@typescript-eslint"], + "ruleSet": [], + "rules": { + "@typescript-eslint/await-thenable": "warn", + "@typescript-eslint/consistent-type-imports": "warn", + "@typescript-eslint/explicit-function-return-type": "warn", + "@typescript-eslint/explicit-module-boundary-types": "warn", + "@typescript-eslint/no-dynamic-delete": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-for-in-array": "warn", + "@typescript-eslint/no-this-alias": "warn", + "@typescript-eslint/no-unnecessary-type-constraint": "warn", + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/prefer-literal-enum-member": "warn" + }, + "overrides": [] +} diff --git a/example/harmony/entry/.gitignore b/example/harmony/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bbc049cfeb689c61d6c2922810f652cbb19aad21 --- /dev/null +++ b/example/harmony/entry/.gitignore @@ -0,0 +1,7 @@ +/node_modules +/.preview +/build +/.cxx +package-lock.json +/oh_modules +/assets diff --git a/example/harmony/entry/build-profile.json5 b/example/harmony/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2408c163287e825139a36f35cbec83e9772c49db --- /dev/null +++ b/example/harmony/entry/build-profile.json5 @@ -0,0 +1,19 @@ +{ + "apiType": 'stageMode', + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "-s", + }, + }, + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/example/harmony/entry/hvigorfile.ts b/example/harmony/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..aced8afddf910b99762b7eff3a8df92cb9f30c2b --- /dev/null +++ b/example/harmony/entry/hvigorfile.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 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. + */ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { hapTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/example/harmony/entry/oh-package.json5 b/example/harmony/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4e2abcf914291f9b6ee5803b5654c004e4aa8456 --- /dev/null +++ b/example/harmony/entry/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "license": "ISC", + "devDependencies": {}, + "name": "entry", + "description": "example description", + "version": "1.0.0", + "dependencies": { + "@rnoh/react-native-openharmony": "0.72.38", + "@react-native-ohos/react-native-fs": "file:../fs" +// "@react-native-ohos/react-native-fs": "file:../../node_modules/@react-native-ohos/react-native-fs/harmony/fs.har", + } +} \ No newline at end of file diff --git a/example/harmony/entry/src/main/cpp/.gitignore b/example/harmony/entry/src/main/cpp/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..881dde5479f322de9db2b2bb98fd94a95f73715e --- /dev/null +++ b/example/harmony/entry/src/main/cpp/.gitignore @@ -0,0 +1,2 @@ +jsbundle.h +generated/ diff --git a/example/harmony/entry/src/main/cpp/CMakeLists.txt b/example/harmony/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..46c611b05f9f6775bd001f0979edd63d7acef3dd --- /dev/null +++ b/example/harmony/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,35 @@ +project(rnapp) +cmake_minimum_required(VERSION 3.4.1) +set(CMAKE_SKIP_BUILD_RPATH TRUE) +set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules") +set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") +set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules/@rnoh/react-native-openharmony/src/main/cpp") +set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated") +set(LOG_VERBOSITY_LEVEL 1) +set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments") +set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie") +set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") + +set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use +add_compile_definitions(WITH_HITRACE_SYSTRACE) + +add_subdirectory("${RNOH_CPP_DIR}" ./rn) + +# RNOH_BEGIN: manual_package_linking_1 +add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-fs/src/main/cpp" ./fs) +# RNOH_END: manual_package_linking_1 + +file(GLOB GENERATED_CPP_FILES "${CMAKE_CURRENT_SOURCE_DIR}/generated/*.cpp") # this line is needed by codegen v1 + +add_library(rnoh_app SHARED + ${GENERATED_CPP_FILES} + "./PackageProvider.cpp" + "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp" +) +target_link_libraries(rnoh_app PUBLIC rnoh) + +# RNOH_BEGIN: manual_package_linking_2 +target_link_libraries(rnoh_app PUBLIC rnoh_fs) + +# RNOH_END: manual_package_linking_2 diff --git a/example/harmony/entry/src/main/cpp/PackageProvider.cpp b/example/harmony/entry/src/main/cpp/PackageProvider.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d84358255f2ada250a8171bf83b0bf6264076b76 --- /dev/null +++ b/example/harmony/entry/src/main/cpp/PackageProvider.cpp @@ -0,0 +1,12 @@ +#include "RNOH/PackageProvider.h" +#include "generated/RNOHGeneratedPackage.h" +#include "RNFSPackage.h" + +using namespace rnoh; + +std::vector> PackageProvider::getPackages(Package::Context ctx) { + return { + std::make_shared(ctx), // generated by codegen v1 + std::make_shared(ctx) + }; +} \ No newline at end of file diff --git a/example/harmony/entry/src/main/ets/RNPackagesFactory.ets b/example/harmony/entry/src/main/ets/RNPackagesFactory.ets new file mode 100644 index 0000000000000000000000000000000000000000..6fc249c5903e5fe697b10e81cc1383e7ac66c0b9 --- /dev/null +++ b/example/harmony/entry/src/main/ets/RNPackagesFactory.ets @@ -0,0 +1,32 @@ +/** + * MIT License + * + * Copyright (C) 2024 Huawei Device Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import type {RNPackageContext, RNPackage} from '@rnoh/react-native-openharmony/ts'; +import { FsPackage } from '@react-native-ohos/react-native-fs/ts'; + +export function createRNPackages(ctx: RNPackageContext): RNPackage[] { + return [ + new FsPackage(ctx) + ]; +} diff --git a/example/harmony/entry/src/main/ets/assets/fonts/Pacifico-Regular.ttf b/example/harmony/entry/src/main/ets/assets/fonts/Pacifico-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7def95d3f44c82086f6e74d93fc0aadac7c454a Binary files /dev/null and b/example/harmony/entry/src/main/ets/assets/fonts/Pacifico-Regular.ttf differ diff --git a/example/harmony/entry/src/main/ets/assets/fonts/StintUltraCondensed-Regular.ttf b/example/harmony/entry/src/main/ets/assets/fonts/StintUltraCondensed-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25c749e167bd5beef18360b13ae660f1c10ebc62 Binary files /dev/null and b/example/harmony/entry/src/main/ets/assets/fonts/StintUltraCondensed-Regular.ttf differ diff --git a/example/harmony/entry/src/main/ets/entryability/EntryAbility.ets b/example/harmony/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..35746494ac1dda87b830c77681493aed4f389b61 --- /dev/null +++ b/example/harmony/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import {RNAbility} from '@rnoh/react-native-openharmony'; +import { AbilityConstant, Want } from '@kit.AbilityKit'; + +export default class EntryAbility extends RNAbility { + + onCreate(want: Want) { + super.onCreate(want) + } + + getPagePath() { + return 'pages/Index'; + } +} diff --git a/example/harmony/entry/src/main/ets/pages/Index.ets b/example/harmony/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..5d4aa1e47485efbe3ddcffd5503e622c99463a46 --- /dev/null +++ b/example/harmony/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import { + AnyJSBundleProvider, + ComponentBuilderContext, + FileJSBundleProvider, + MetroJSBundleProvider, + ResourceJSBundleProvider, + RNApp, + RNOHErrorDialog, + RNOHLogger, + TraceJSBundleProviderDecorator, + RNOHCoreContext +} from '@rnoh/react-native-openharmony'; +import font from '@ohos.font'; +import { createRNPackages } from '../RNPackagesFactory'; + +const arkTsComponentNames: Array = [ +]; + +@Builder +export function buildCustomRNComponent(ctx: ComponentBuilderContext) { + // There seems to be a problem with the placement of ArkTS components in mixed mode. Nested Stack temporarily avoided. + Stack() { + } + .position({ x: 0, y: 0 }) + +} + +const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent) + +/** + * If you want to use custom fonts, you need to register them here. + * We should support react-native-asset to handle registering fonts automatically. + */ +const fonts: font.FontOptions[] = [ + { + familyName: 'Pacifico-Regular', + familySrc: '/assets/fonts/Pacifico-Regular.ttf' + }, + { + familyName: 'StintUltraCondensed-Regular', + familySrc: '/assets/fonts/StintUltraCondensed-Regular.ttf' + } +] + +@Entry +@Component +struct Index { + @StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined + @State shouldShow: boolean = false + private logger!: RNOHLogger + bundlePath: string = 'bunlde.harmony.js' + @State hasBundle: boolean = false + + aboutToAppear() { + this.logger = this.rnohCoreContext!.logger.clone("Index") + const stopTracing = this.logger.clone("aboutToAppear").startTracing() + for (const customFont of fonts) { + font.registerFont(customFont) + } + + this.shouldShow = true + stopTracing() + } + + onBackPress(): boolean | undefined { + // NOTE: this is required since `Ability`'s `onBackPressed` function always + // terminates or puts the app in the background, but we want Ark to ignore it completely + // when handled by RN + this.rnohCoreContext!.dispatchBackPress() + return true + } + + build() { + Column() { + if (this.rnohCoreContext && this.shouldShow) { + if (this.rnohCoreContext?.isDebugModeEnabled) { + RNOHErrorDialog({ ctx: this.rnohCoreContext }) + } + RNApp({ + rnInstanceConfig: { + createRNPackages, + enableNDKTextMeasuring: true, + enableBackgroundExecutor: false, + enableCAPIArchitecture: true, + arkTsComponentNames: arkTsComponentNames, + }, + initialProps: { "foo": "bar" } as Record, + appKey: "app_name", + wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder, + onSetUp: (rnInstance) => { + rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP") + }, + jsBundleProvider: new TraceJSBundleProviderDecorator( + new AnyJSBundleProvider([ + new MetroJSBundleProvider(), + // NOTE: to load the bundle from file, place it in + // `/data/app/el2/100/base/com.rnoh.tester/files/bundle.harmony.js` + // on your device. The path mismatch is due to app sandboxing on HarmonyOS + new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'), + // new FileJSBundleProvider(context.filesDir + '/' + this.bundlePath), + new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'), + new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js') + ]), + this.rnohCoreContext.logger), + }) + } + Text("1233333333122 1212323") + } + .height('100%') + .width('100%') + } +} diff --git a/example/harmony/entry/src/main/ets/pages/SurfaceDeadlockTest.ets b/example/harmony/entry/src/main/ets/pages/SurfaceDeadlockTest.ets new file mode 100644 index 0000000000000000000000000000000000000000..88e4439681c4981d7ab7e1b87a46653a9e5a9c35 --- /dev/null +++ b/example/harmony/entry/src/main/ets/pages/SurfaceDeadlockTest.ets @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ +import { RNInstance, JSBundleProvider, RNAbility, RNSurface } from '@rnoh/react-native-openharmony' +import { CustomComponentBuilder } from "@rnoh/react-native-openharmony/src/main/ets/RNOHCorePackage" +import { SurfaceConfig2 } from '@rnoh/react-native-openharmony/src/main/ets/RNSurface' + + +@Component +export struct SurfaceDeadlockTest { + public jsBundleProvider: JSBundleProvider | undefined = undefined + public appKeys: string[] = [] + public numberOfIterations: number = 1 + @BuilderParam public buildCustomComponent!: CustomComponentBuilder + // ------------------------------------------------------------------------------------------------------------------- + @StorageLink('RNAbility') private rnAbility: RNAbility = {} as RNAbility + private rnInstance!: RNInstance + @State private shouldShow: boolean = false + private shouldDestroyRNInstance: boolean = false + private cleanUpCallbacks: (() => void)[] = [] + + aboutToAppear() { + this.getOrCreateRNInstance().then(rnInstance => { + this.rnInstance = rnInstance + const jsBundleExecutionStatus = this.rnInstance.getBundleExecutionStatus(this.jsBundleProvider?.getURL()) + if (this.jsBundleProvider && jsBundleExecutionStatus === undefined) { + this.rnInstance.runJSBundle(this.jsBundleProvider).then(() => { + this.shouldShow = true + }) + return; + } + }).catch((reason: string | Error) => { + if (typeof reason === "string") + this.rnAbility.getLogger().error(reason) + else if (reason instanceof Error) { + this.rnAbility.getLogger().error(reason.message) + } else { + this.rnAbility.getLogger().error("Fatal exception") + } + }) + } + + aboutToDisappear() { + if (this.shouldDestroyRNInstance) + this.rnAbility.destroyAndUnregisterRNInstance(this.rnInstance) + this.cleanUpCallbacks.forEach(cleanUp => cleanUp()) + } + + private getOrCreateRNInstance(): Promise { + return this.rnAbility.createAndRegisterRNInstance({ createRNPackages: () => [] }) + } + + build() { + Stack() { + if (this.shouldShow) { + ForEach(this.appKeys, (appKey: string, idx) => { + Stack() { + Blinker({ + minDelayInMs: 1000, + maxDelayInMs: 2000, + blinksCount: this.numberOfIterations, + randomnessPrecisionInMs: 500 + }) { + RNSurface({ + ctx: this.rnAbility.createRNOHContext({ rnInstance: this.rnInstance }), + surfaceConfig: { + initialProps: {}, + appKey: appKey, + } as SurfaceConfig2, + buildCustomComponent: this.buildCustomComponent, + }) + } + }.height(`${100 / this.appKeys.length}%`) + .position({ x: 0, y: `${(idx / this.appKeys.length) * 100}%` }) + }) + } + }.width("100%") + .height("100%") + } +} + + +@Component +struct Blinker { + public minDelayInMs: number = 0 + public maxDelayInMs: number = 1000 + public blinksCount: number = 0 + public randomnessPrecisionInMs: number = 250 + @BuilderParam public renderChildren: () => void + private currentBlinksCount = 0 + @State private isVisible: boolean = false + private timeout: number = 0 + + aboutToAppear() { + this.blink(this.minDelayInMs) + } + + aboutToDisappear() { + clearTimeout(this.timeout) + } + + private blink(ms: number) { + this.isVisible = !this.isVisible + this.currentBlinksCount += 1 + if (this.currentBlinksCount >= this.blinksCount) { + if (this.timeout) { + clearTimeout(this.timeout) + } + this.isVisible = true + return; + } + this.timeout = setTimeout(() => { + this.blink(this.getNextDelay()) + }, ms) + } + + private getNextDelay(): number { + return ((Math.floor(Math.random() * (Number.MAX_VALUE / this.randomnessPrecisionInMs)) * this.randomnessPrecisionInMs) % this.maxDelayInMs) + this.minDelayInMs + } + + build() { + Stack() { + if (this.isVisible) { + this.renderChildren() + } + } + } +} + + + + + diff --git a/example/harmony/entry/src/main/ets/pages/TouchDisplayer.ets b/example/harmony/entry/src/main/ets/pages/TouchDisplayer.ets new file mode 100644 index 0000000000000000000000000000000000000000..2c62df2983416a0fd5eca7e2e730283d139e0671 --- /dev/null +++ b/example/harmony/entry/src/main/ets/pages/TouchDisplayer.ets @@ -0,0 +1,37 @@ +@Component +export struct TouchDisplayer { + @State currentTouches: TouchObject[] = [] + @State touchIndicatorOpacity: number = 0 + @BuilderParam buildChildren: () => void + build() { + Stack() { + this.buildChildren() + ForEach(this.currentTouches, (activeTouch: TouchObject) => { + Stack() { + } + .width(64) + .height(64) + .backgroundColor("blue") + .borderWidth(2) + .borderColor("white") + .opacity(this.touchIndicatorOpacity) + .position({ x: activeTouch.x - 32, y: activeTouch.y - 32 }) + .borderRadius(1000) + .hitTestBehavior(HitTestMode.Transparent) + }) + } + .width("100%") + .height("100%") + .hitTestBehavior(HitTestMode.Transparent) + .onTouch(e => { + this.currentTouches = e.touches + this.touchIndicatorOpacity = 0.5 + animateTo({ + duration: 500, + curve: Curve.Linear, + }, () => { + this.touchIndicatorOpacity = 0 + }) + }) + } +} \ No newline at end of file diff --git a/example/harmony/entry/src/main/module.json5 b/example/harmony/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c7700c2c5f348cd7d73ed4cf3b8d7112a46295b9 --- /dev/null +++ b/example/harmony/entry/src/main/module.json5 @@ -0,0 +1,52 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default" + ], + "querySchemes": [ + "weixin", + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + // below property is supported from 5.0.0 - it is needed by bundleManager.canOpenLink to check if the app can open some url + // "querySchemes": ["maps", "http", "https", "customDomain"], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, + ], + "metadata": [ + { + "name": "OPTLazyForEach", + "value": "true", + } + ], + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "visible": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/example/harmony/entry/src/main/resources/.gitignore b/example/harmony/entry/src/main/resources/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..037cea9e70bef8c906560a871a394f748ec2325d --- /dev/null +++ b/example/harmony/entry/src/main/resources/.gitignore @@ -0,0 +1 @@ +rawfile/assets diff --git a/example/harmony/entry/src/main/resources/base/element/color.json b/example/harmony/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/harmony/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/harmony/entry/src/main/resources/base/element/string.json b/example/harmony/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..074b2173f51352f71629252715526554911d44bb --- /dev/null +++ b/example/harmony/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "RN Tester" + } + ] +} \ No newline at end of file diff --git a/example/harmony/entry/src/main/resources/base/media/icon.png b/example/harmony/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f05abe7eb110a3c4958a48f71ea15b28bae5d3 Binary files /dev/null and b/example/harmony/entry/src/main/resources/base/media/icon.png differ diff --git a/example/harmony/entry/src/main/resources/rawfile/1.txt b/example/harmony/entry/src/main/resources/rawfile/1.txt new file mode 100644 index 0000000000000000000000000000000000000000..71f6ee3dfcd13fad21fc73e697c7c5a3c31ec039 --- /dev/null +++ b/example/harmony/entry/src/main/resources/rawfile/1.txt @@ -0,0 +1 @@ +text test \ No newline at end of file diff --git a/example/harmony/format.ps1 b/example/harmony/format.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..7735881fded75cc61ee05ec623e0848b85f6d639 --- /dev/null +++ b/example/harmony/format.ps1 @@ -0,0 +1,12 @@ +$directoryPath = Split-Path -Parent $MyInvocation.MyCommand.Path +$filePaths = Get-ChildItem $directoryPath -Recurse -Include *.h, *.cpp | +Where-Object { + $_.DirectoryName -notmatch 'third-party' -and + $_.DirectoryName -notmatch 'patches' -and + $_.DirectoryName -notmatch 'node_modules' -and + $_.DirectoryName -notmatch '.cxx' -and + $_.DirectoryName -notmatch 'build' +} +foreach ($filePath in $filePaths) { + & "clang-format.exe" -style=file -i $filePath.FullName +} \ No newline at end of file diff --git a/example/harmony/fs/.gitignore b/example/harmony/fs/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/example/harmony/fs/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/example/harmony/fs/build-profile.json5 b/example/harmony/fs/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..9c5ce9bd0b25854f15872fb125f89096a34a21e3 --- /dev/null +++ b/example/harmony/fs/build-profile.json5 @@ -0,0 +1,12 @@ +{ + "apiType": "stageMode", + + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/hvigorfile.ts b/example/harmony/fs/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4045ef241b4759680eeba205dcf9c1c839c2c51 --- /dev/null +++ b/example/harmony/fs/hvigorfile.ts @@ -0,0 +1,6 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ +export { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/example/harmony/fs/index.ets b/example/harmony/fs/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..d6579ae7ad61ee8d78fdcc747adb18d441c1bbd5 --- /dev/null +++ b/example/harmony/fs/index.ets @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +export * from './ts' \ No newline at end of file diff --git a/example/harmony/fs/oh-package.json5 b/example/harmony/fs/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7611d7c4e2745db53ad2937022d3d351b5bae5d9 --- /dev/null +++ b/example/harmony/fs/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "@react-native-ohos/react-native-fs", + "version": "2.20.0", + "description": "Please describe the basic information.", + "main": "index.ets", + "types": "module", + "dependencies": { + "@rnoh/react-native-openharmony": "0.72.38" + } +} \ No newline at end of file diff --git a/example/harmony/fs/src/main/cpp/CMakeLists.txt b/example/harmony/fs/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..607ba12b098ac8a6f030238184fcde72b4f1b226 --- /dev/null +++ b/example/harmony/fs/src/main/cpp/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) +set(rnoh_fs_generated_dir "${CMAKE_CURRENT_SOURCE_DIR}/generated") + +file(GLOB rnoh_fs_SRC CONFIGURE_DEPENDS *.cpp) +file(GLOB_RECURSE rnoh_fs_generated_SRC "${rnoh_fs_generated_dir}/**/*.cpp") + +add_library(rnoh_fs SHARED ${rnoh_fs_SRC} ${rnoh_fs_generated_SRC}) +target_include_directories(rnoh_fs PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${rnoh_fs_generated_dir} +) + +target_link_libraries(rnoh_fs PUBLIC rnoh) \ No newline at end of file diff --git a/example/harmony/fs/src/main/cpp/RNFSPackage.h b/example/harmony/fs/src/main/cpp/RNFSPackage.h new file mode 100644 index 0000000000000000000000000000000000000000..dab1595c9b6dd269426ffb4e313cebd896113024 --- /dev/null +++ b/example/harmony/fs/src/main/cpp/RNFSPackage.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ +#pragma once + +#include "RNOH/generated/BaseReactNativeFsPackage.h" + +namespace rnoh { + +class RNFSPackage : public BaseReactNativeFsPackage { + using Super = BaseReactNativeFsPackage; + +public: + RNFSPackage(Package::Context ctx) : Super(ctx) {} +}; +} // namespace rnoh \ No newline at end of file diff --git a/example/harmony/fs/src/main/cpp/generated/RNOH/generated/BaseReactNativeFsPackage.h b/example/harmony/fs/src/main/cpp/generated/RNOH/generated/BaseReactNativeFsPackage.h new file mode 100644 index 0000000000000000000000000000000000000000..56c68b9e96abf22d9937a2adf90f7456e7b5dc1a --- /dev/null +++ b/example/harmony/fs/src/main/cpp/generated/RNOH/generated/BaseReactNativeFsPackage.h @@ -0,0 +1,65 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +#pragma once + +#include "RNOH/Package.h" +#include "RNOH/ArkTSTurboModule.h" +#include "RNOH/generated/turbo_modules/ReactNativeFs.h" + +namespace rnoh { + +class BaseReactNativeFsPackageTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate { + public: + SharedTurboModule createTurboModule(Context ctx, const std::string &name) const override { + if (name == "ReactNativeFs") { + return std::make_shared(ctx, name); + } + return nullptr; + }; +}; + +class BaseReactNativeFsPackageEventEmitRequestHandler : public EventEmitRequestHandler { + public: + void handleEvent(Context const &ctx) override { + auto eventEmitter = ctx.shadowViewRegistry->getEventEmitter(ctx.tag); + if (eventEmitter == nullptr) { + return; + } + + std::vector supportedEventNames = { + }; + if (std::find(supportedEventNames.begin(), supportedEventNames.end(), ctx.eventName) != supportedEventNames.end()) { + eventEmitter->dispatchEvent(ctx.eventName, ArkJS(ctx.env).getDynamic(ctx.payload)); + } + } +}; + + +class BaseReactNativeFsPackage : public Package { + public: + BaseReactNativeFsPackage(Package::Context ctx) : Package(ctx){}; + + std::unique_ptr createTurboModuleFactoryDelegate() override { + return std::make_unique(); + } + + std::vector createComponentDescriptorProviders() override { + return { + }; + } + + ComponentJSIBinderByString createComponentJSIBinderByName() override { + return { + }; + }; + + EventEmitRequestHandlers createEventEmitRequestHandlers() override { + return { + std::make_shared(), + }; + } +}; + +} // namespace rnoh diff --git a/example/harmony/fs/src/main/cpp/generated/RNOH/generated/turbo_modules/ReactNativeFs.cpp b/example/harmony/fs/src/main/cpp/generated/RNOH/generated/turbo_modules/ReactNativeFs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..84c80e0fa115eb3946fbed2a757a642c3eb2173b --- /dev/null +++ b/example/harmony/fs/src/main/cpp/generated/RNOH/generated/turbo_modules/ReactNativeFs.cpp @@ -0,0 +1,35 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +#include "ReactNativeFs.h" + +namespace rnoh { +using namespace facebook; + +ReactNativeFs::ReactNativeFs(const ArkTSTurboModule::Context ctx, const std::string name) : ArkTSTurboModule(ctx, name) { + methodMap_ = { + ARK_METHOD_METADATA(getConstants, 0), + ARK_ASYNC_METHOD_METADATA(readFile, 1), + ARK_ASYNC_METHOD_METADATA(exists, 1), + ARK_ASYNC_METHOD_METADATA(mkdir, 2), + ARK_ASYNC_METHOD_METADATA(appendFile, 3), + ARK_ASYNC_METHOD_METADATA(writeFile, 3), + ARK_ASYNC_METHOD_METADATA(readFileAssets, 1), + ARK_ASYNC_METHOD_METADATA(copyFile, 2), + ARK_ASYNC_METHOD_METADATA(unlink, 1), + ARK_ASYNC_METHOD_METADATA(hash, 2), + ARK_ASYNC_METHOD_METADATA(moveFile, 2), + ARK_ASYNC_METHOD_METADATA(read, 3), + ARK_ASYNC_METHOD_METADATA(write, 3), + ARK_ASYNC_METHOD_METADATA(stat, 1), + ARK_ASYNC_METHOD_METADATA(touch, 3), + ARK_ASYNC_METHOD_METADATA(downloadFile, 1), + ARK_ASYNC_METHOD_METADATA(readDir, 1), + ARK_ASYNC_METHOD_METADATA(existsAssets, 1), + ARK_METHOD_METADATA(addListener, 1), + ARK_METHOD_METADATA(removeListeners, 1), + }; +} + +} // namespace rnoh diff --git a/example/harmony/fs/src/main/cpp/generated/RNOH/generated/turbo_modules/ReactNativeFs.h b/example/harmony/fs/src/main/cpp/generated/RNOH/generated/turbo_modules/ReactNativeFs.h new file mode 100644 index 0000000000000000000000000000000000000000..fd1b48ab460a0d481c0fe3102e03f35654f05aae --- /dev/null +++ b/example/harmony/fs/src/main/cpp/generated/RNOH/generated/turbo_modules/ReactNativeFs.h @@ -0,0 +1,16 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +#pragma once + +#include "RNOH/ArkTSTurboModule.h" + +namespace rnoh { + +class JSI_EXPORT ReactNativeFs : public ArkTSTurboModule { + public: + ReactNativeFs(const ArkTSTurboModule::Context ctx, const std::string name); +}; + +} // namespace rnoh diff --git a/example/harmony/fs/src/main/ets/FsPackage.ts b/example/harmony/fs/src/main/ets/FsPackage.ts new file mode 100644 index 0000000000000000000000000000000000000000..a58cb55d178a8c7e2776bb2c5c17a735882ca6d9 --- /dev/null +++ b/example/harmony/fs/src/main/ets/FsPackage.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import { RNPackage, TurboModulesFactory } from '@rnoh/react-native-openharmony/ts'; +import type { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts'; +import { FsTurboModule } from './FsTurboModule'; + +class FsTurboModulesFactory extends TurboModulesFactory { + createTurboModule(name: string): TurboModule | null { + if (name === 'ReactNativeFs') { + // 上下文对象 + globalThis.uiAbilityContext = this.ctx.uiAbilityContext; + return new FsTurboModule(this.ctx); + } + return null; + } + + hasTurboModule(name: string): boolean { + return name === 'ReactNativeFs'; + } +} + +export class FsPackage extends RNPackage { + createTurboModulesFactory(ctx: TurboModuleContext): TurboModulesFactory { + return new FsTurboModulesFactory(ctx); + } +} diff --git a/example/harmony/fs/src/main/ets/FsTurboModule.ts b/example/harmony/fs/src/main/ets/FsTurboModule.ts new file mode 100644 index 0000000000000000000000000000000000000000..d29d7cba51047087e168a8706674a86193dd701f --- /dev/null +++ b/example/harmony/fs/src/main/ets/FsTurboModule.ts @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import { TurboModule, RNOHError, TurboModuleContext } from '@rnoh/react-native-openharmony/ts'; +import { TM } from "./generated/ts" +import fs, { ListFileOptions, ReadOptions, ReadTextOptions, WriteOptions } from '@ohos.file.fs'; +import hash from '@ohos.file.hash'; +import { BusinessError } from '@ohos.base'; +import resourceManager from '@ohos.resourceManager' +import util from '@ohos.util'; +import loadRequest from '@ohos.request'; +import buffer from '@ohos.buffer'; +import HashMap from '@ohos.util.HashMap'; +import { Context } from '@ohos.abilityAccessCtrl'; +import common from '@ohos.app.ability.common'; + +const TAG: string = "[RNOH] Fs" + +interface StatResult { + ctime: number, // The creation date of the file + mtime: number, // The last modified date of the file + size: number, // Size in bytes + mode: number, // UNIX file mode + originalFilepath: string, // ANDROID: In case of content uri this is the pointed file path, otherwise is the same as path + type: number // Is the file just a file? Is the file a directory? +} + +type Headers = { [name: string]: string } +type Fields = { [name: string]: string } + +type DownloadFileOptions = { + jobId: number + fromUrl: string // URL to download file from + toFile: string // Local filesystem path to save the file to + headers?: Headers // An object of headers to be passed to the server + background?: boolean // Continue the download in the background after the app terminates (iOS only) + progressInterval?: number + progressDivider?: number + readTimeout?: number + hasBeginCallback?: (res: DownloadBeginCallbackResult) => void + hasProgressCallback?: (res: DownloadProgressCallbackResult) => void + hasResumableCallback?: () => void // only supported on iOS yet + connectionTimeout?: number // only supported on Android yet + backgroundTimeout?: number // Maximum time (in milliseconds) to download an entire resource (iOS only, useful for timing out background downloads) +} + +type DownloadBeginCallbackResult = { + jobId: number // The download job ID, required if one wishes to cancel the download. See `stopDownload`. + statusCode: number // The HTTP status code + contentLength: number // The total size in bytes of the download resource + headers: Headers // The HTTP response headers from the server +} + +type DownloadProgressCallbackResult = { + jobId: number // The download job ID, required if one wishes to cancel the download. See `stopDownload`. + contentLength: number // The total size in bytes of the download resource + bytesWritten: number // The number of bytes written to the file so far +} + +type DownloadResult = { + jobId: number // The download job ID, required if one wishes to cancel the download. See `stopDownload`. + statusCode: number // The HTTP status code + bytesWritten: number // The number of bytes written to the file +} + +type ReadDirItem = { + ctime?: number; + mtime?: number; + name: string; + path: string; + size: number; + type: number; +}; + +enum LOADTASK_STATUS { + PROGRESS = 'progress', + COMPLETE = 'complete', + PAUSE = 'pause', + REMOVE = 'remove', + FAIL = 'fail' +} + +export class FsTurboModule extends TurboModule implements TM.ReactNativeFs.Spec { + private context: Context; // ApplicationContext + private resourceManager: resourceManager.ResourceManager; + private FOUR_ZERO_NINE_SIX: number = 4096 + private ZERO: number = 0 + private ONE: number = 1 + private ONE_Three_Nine_ZERO_ZERO_ZERO_ONE_FIVE: number = 13900015 + private UTF8: buffer.BufferEncoding = 'utf8' + private MD5: string = 'md5' + private SHA1: string = 'sha1' + private SHA256: string = 'sha256' + private UTF_8: buffer.BufferEncoding = 'utf-8' + private BASE64: buffer.BufferEncoding = 'base64' + private logger = this.ctx.logger.clone("ReactNativeFsLogger") + + + constructor(ctx: TurboModuleContext) { + super(ctx) + this.context = this.ctx.uiAbilityContext; + this.resourceManager = this.context.resourceManager; + } + + existsAssets(filepath: string): Promise { + this.logger.info('existsAssets') + return new Promise((resolve, reject) => { + try { + this.resourceManager.getRawFileList(filepath, (error: BusinessError, value: Array) => { + if (error != null) { + resolve(false); + } else { + resolve(true); + } + }); + } catch (error) { + this.logger.error(`existsAssets error: ${error?.message}`) + resolve(false); + } + }); + } + + readDir(dirpath: string): Promise { + this.logger.info('readDir') + return new Promise((resolve, reject) => { + let listFileOption: ListFileOptions = { + recursion: false, + listNum: this.ZERO + }; + if (!dirpath.endsWith('/')) { + dirpath += '/'; + } + fs.listFile(dirpath, listFileOption, (err: BusinessError, filenames: Array) => { + if (err) { + reject("list file failed with error message: " + err.message + ", error code: " + err.code); + } else { + try { + let readDirResult: ReadDirItem[] = []; + for (let i = this.ZERO; i < filenames.length; i++) { + let filename = filenames[i]; + let filePath = dirpath + filename; + let file = fs.statSync(filePath); + readDirResult.push({ + ctime: file.ctime, + mtime: file.mtime, + name: filename, + path: filePath, + size: file.size, + type: file.isDirectory() ? this.ONE : this.ZERO, + }); + } + resolve(readDirResult); + } catch (e) { + this.logger.error(`readDir error: ${e?.message}`) + reject(e) + } + } + }); + }); + } + + downloadFile(options: Object): Promise { + this.logger.info('downloadFile') + return new Promise((resolve, reject) => { + let downloadFileOptions: DownloadFileOptions = options as DownloadFileOptions; + + try { + let res = fs.accessSync(downloadFileOptions.toFile); + if (res) { + fs.unlinkSync(downloadFileOptions.toFile); + } + } catch (error) { + reject(error); + } + let downloadConfig: loadRequest.DownloadConfig = { + url: downloadFileOptions.fromUrl, + header: downloadFileOptions.headers, + enableMetered: true, + enableRoaming: true, + description: "", + filePath: downloadFileOptions.toFile, + title: '', + background: false + }; + + loadRequest.downloadFile((this.context as common.BaseContext), downloadConfig) + .then((downloadTask: loadRequest.DownloadTask) => { + if (downloadTask) { + let loadTask: loadRequest.DownloadTask | null = downloadTask; + + if (downloadFileOptions.hasBeginCallback) { + let downloadBeginCallbackResult: DownloadBeginCallbackResult = { + jobId: downloadFileOptions.jobId, + statusCode: this.ZERO, + contentLength: this.ZERO, + headers: downloadFileOptions.headers + } + this.ctx.rnInstance.emitDeviceEvent('DownloadBegin', downloadBeginCallbackResult) + } + if (downloadFileOptions.hasProgressCallback) { + loadTask.on('progress', (receivedSize, totalSize) => { + if (totalSize > this.ZERO) { + let downloadProgressCallbackResult: DownloadProgressCallbackResult = { + jobId: downloadFileOptions.jobId, + contentLength: totalSize, + bytesWritten: receivedSize + } + this.ctx.rnInstance.emitDeviceEvent('DownloadProgress', downloadProgressCallbackResult) + } + }); + } + + loadTask.on('complete', () => { + let downloadResult: DownloadResult = { + jobId: downloadFileOptions.jobId, + statusCode: 200, + bytesWritten: this.ZERO + } + resolve(downloadResult); + }) + loadTask.on('pause', () => { + }) + + loadTask.on('remove', () => { + }) + loadTask.on('fail', (err) => { + reject(err); + }) + } else { + reject("downloadTask dismiss"); + } + }).catch((err: BusinessError) => { + this.logger.error(`downloadFile error: ${err?.message}`) + reject(err); + }) + }); + } + + // 常量 + getConstants(): Object { + let applicationContext = this.context.getApplicationContext(); + let result = { + // 沙箱路径 + FileSandBoxPath: this.context.filesDir, + // 缓存路径 + FileCachePath: this.context.cacheDir, + MainBundlePath: applicationContext.bundleCodeDir, + TemporaryDirectoryPath: applicationContext.tempDir, + LibraryDirectoryPath: applicationContext.preferencesDir, + // 文件 + RNFSFileTypeRegular: this.ZERO, + // 文件夹 + RNFSFileTypeDirectory: this.ONE, + } + + return result; + }; + + // 读取文件内容 + readFile(path: string): Promise { + this.logger.info('readFile') + return new Promise((resolve, reject) => { + try { + let file = fs.openSync(path); + let bufSize = this.FOUR_ZERO_NINE_SIX; + let readSize = this.ZERO; + let buf = new ArrayBuffer(bufSize); + let readOptions: ReadOptions = { + offset: readSize, + length: bufSize + }; + let buffers: buffer.Buffer[] = []; + let readLen = fs.readSync(file.fd, buf, readOptions); + while (readLen > this.ZERO) { + readSize += readLen; + readOptions.offset = readSize; + buffers.push(buffer.from(buf.slice(this.ZERO, readLen))) + readLen = fs.readSync(file.fd, buf, readOptions); + } + fs.closeSync(file); + let finalBuf: ArrayBuffer = buffer.concat(buffers).buffer; + let base64Helper = new util.Base64Helper; + let result = base64Helper.encodeToStringSync(new Uint8Array(finalBuf)); + resolve(result); + } catch (e) { + this.logger.error(`readFile error: ${e?.message}`) + reject(e); + } + }) + }; + + // 判断文件是否存在 + exists(path: string): Promise { + return new Promise((resolve, reject) => { + fs.access(path, (err: BusinessError, result: boolean) => { + if (err) { + reject('File does not exist'); + } else { + resolve(result); + } + }); + }) + }; + + // 创建文件 + mkdir(path: string): Promise { + this.logger.info('mkdir') + return new Promise(async (resolve, reject) => { + fs.mkdir(path, true, (err: BusinessError) => { + if (err) { + if (err.code == this.ONE_Three_Nine_ZERO_ZERO_ZERO_ONE_FIVE) { + // 文件夹存在 + resolve(); + } else { + reject(`Directory could not be created ${err.message} ${err.code}`); + } + } else { + resolve(); + } + }) + }) + } + + + // 写入文件内容 + writeFile(path: string, contentStr: string): Promise { + return new Promise((resolve, reject) => { + // base64 decode 解码 + let result = buffer.from(contentStr, this.BASE64); + // 读写创建 文件不存在则创建文件 + let file = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC); + fs.write(file.fd, result.buffer, (err: BusinessError, writeLen: number) => { + if (err) { + reject('Directory could not be created'); + } else { + resolve(); + } + fs.closeSync(file); + }); + }) + }; + + // 文件内容追加 + appendFile(path: string, contentStr: string): Promise { + return new Promise((resolve, reject) => { + // base64 decode 解码 + let result = buffer.from(contentStr, this.BASE64).toString(this.UTF8); + // 读写创建 文件内容追加到末尾 + let file = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.APPEND); + fs.write(file.fd, result, (err: BusinessError, writeLen: number) => { + if (err) { + reject('Directory could not be created'); + } else { + resolve(); + } + fs.closeSync(file); + }); + }) + }; + + // 资源文件内容读取 + readFileAssets(path: string): Promise { + return new Promise((resolve, reject) => { + this.resourceManager.getRawFileContent(path, (err: BusinessError, value: Uint8Array) => { + if (err) { + this.logger.error(`readFileAssets error: ${err?.message}`) + reject(err.message); + } else { + resolve(this.parsingRawFile(value)); + } + }); + }) + } + + private parsingRawFile(rawFile: Uint8Array): string { + let base64 = new util.Base64Helper(); + let result = base64.encodeToStringSync(rawFile); + return result; + } + + // 将位于from的文件复制到into。 + copyFile(from: string, into: string): Promise { + return new Promise((resolve, reject) => { + let fromResult: string[] = from.split('/'); + let intoResult: string[] = into.split('/'); + if (fromResult[fromResult.length-this.ONE] === intoResult[intoResult.length-1]) { + reject(new Error('The file already exists.')); + return; + } + fs.copyFile(from, into, (err: BusinessError) => { + if (err) { + reject(err.message); + } else { + resolve(); + } + }) + }) + }; + + // 删除文件 + unlink(path: string): Promise { + return new Promise((resolve, reject) => { + fs.rmdir(path, (err: BusinessError) => { + if (err) { + reject('FilePath does not exist'); + } else { + resolve(); + } + }); + }) + } + + // 文件hash + hash(path: string, algorithm: string): Promise { + return new Promise((resolve, reject) => { + let algorithms: HashMap = new HashMap(); + algorithms.set(this.MD5, this.MD5); + algorithms.set(this.SHA1, this.SHA1); + algorithms.set(this.SHA256, this.SHA256); + // algorithm不存在 + if (!algorithms.hasKey(algorithm)) { + reject('Invalid hash algorithm'); + return; + } + // 判断是否是文件夹 + let isDirectory = fs.statSync(path).isDirectory(); + if (isDirectory) { + reject('file IsDirectory'); + return; + } + // 判断文件是否在 + let res = fs.accessSync(path); + if (!res) { + reject('file not exists'); + return; + } + hash.hash(path, algorithm, (err: BusinessError, result: string) => { + if (err) { + reject("calculate file hash failed with error message: " + err.message + ", error code: " + err.code); + } else { + resolve(result.toLocaleLowerCase()); + } + }) + }) + } + + // 移动文件 + moveFile(filepath: string, destPath: string): Promise { + return new Promise((resolve, reject) => { + fs.moveFile(filepath, destPath, this.ZERO, (err: BusinessError) => { + if (err) { + reject('move file failed with error message: ' + err.message + ', error code: ' + err.code); + } else { + resolve(); + } + }) + }) + } + + // 文件内容部分读 + read(path: string, length: number, position: number): Promise { + return new Promise((resolve, reject) => { + let readTextOption: ReadTextOptions = { + offset: position, + length: length, + encoding: this.UTF_8 + }; + fs.readText(path, readTextOption, (err: BusinessError, str: string) => { + if (err) { + reject('readText failed with error message: ' + err.message + ', error code: ' + err.code); + } else { + let result = buffer.from(str, this.UTF8).toString(this.BASE64); + resolve(result); + } + }); + }) + } + + // 文件内容从某位置写 + write(filepath: string, contents: string, position: number): Promise { + return new Promise((resolve, reject) => { + let result = buffer.from(contents, this.BASE64).toString(this.UTF8); + let file = fs.openSync(filepath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + let writeOption: WriteOptions = { + offset: fs.statSync(filepath).size + }; + fs.write(file.fd, result, writeOption, (err: BusinessError, writeLen: number) => { + if (err) { + reject('write data to file failed with error message:' + err.message + ', error code: ' + err.code); + } else { + resolve(); + } + fs.closeSync(file); + }); + }) + } + + touch(filePath: string, mtime?: number, ctime?: number): Promise { + return new Promise((resolve, reject) => { + // 判断是否是文件夹 + let isDirectory = fs.statSync(filePath).isDirectory(); + if (isDirectory) { + reject('file IsDirectory'); + return; + } + // 判断文件是否在 + let res = fs.accessSync(filePath); + if (!res) { + reject('No such file or directory'); + return; + } + if (mtime) { + try { + fs.utimes(filePath, mtime); + resolve(true) + } catch (err) { + resolve(err.message) + } + } else { + resolve(false) + } + }) + } + + // 获取文件详细属性信息 + stat(filepath: string): Promise { + return new Promise((resolve, reject) => { + let statResult: StatResult = { + ctime: -this.ONE, + mtime: -this.ONE, + size: -this.ONE, + mode: -this.ONE, + originalFilepath: '', + type: -this.ONE + }; + // 判断文件是否在 + let res = fs.accessSync(filepath); + if (!res) { + reject('file not exists'); + return; + } + fs.stat(filepath, (err: BusinessError, stat: fs.Stat) => { + if (err) { + this.logger.error(TAG, `error message: ` + err.message + ', error code: ' + err.code); + } else { + statResult.ctime = stat.ctime; + statResult.mtime = stat.mtime; + statResult.size = stat.size; + statResult.mode = stat.mode; + statResult.originalFilepath = filepath; + statResult.type = stat.isDirectory() ? this.ONE : this.ZERO; + this.logger.info(TAG, 'file statResult: ' + JSON.stringify(statResult)); + resolve(statResult); + } + }); + }) + } + + // 处理异常警告 没有实际业务 + addListener(eventName: string): void { + + } + + // 处理异常警告 没有实际业务 + removeListeners(count: number): void { + + } +} \ No newline at end of file diff --git a/example/harmony/fs/src/main/ets/generated/components/ts.ts b/example/harmony/fs/src/main/ets/generated/components/ts.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1dae5612c329e2d338cb21936a3aadf279efcc5 --- /dev/null +++ b/example/harmony/fs/src/main/ets/generated/components/ts.ts @@ -0,0 +1,5 @@ + +/** + */ + +export {} diff --git a/example/harmony/fs/src/main/ets/generated/index.ets b/example/harmony/fs/src/main/ets/generated/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..041b7edf42bfd5af1779aa40009e5d5e53e2152f --- /dev/null +++ b/example/harmony/fs/src/main/ets/generated/index.ets @@ -0,0 +1,5 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +export * from "./ts" diff --git a/example/harmony/fs/src/main/ets/generated/ts.ts b/example/harmony/fs/src/main/ets/generated/ts.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c568a8e486aaafd28ba432cf802ae5277923d56 --- /dev/null +++ b/example/harmony/fs/src/main/ets/generated/ts.ts @@ -0,0 +1,6 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +export * as RNC from "./components/ts" +export * as TM from "./turboModules/ts" diff --git a/example/harmony/fs/src/main/ets/generated/turboModules/ReactNativeFs.ts b/example/harmony/fs/src/main/ets/generated/turboModules/ReactNativeFs.ts new file mode 100644 index 0000000000000000000000000000000000000000..580f748577ced3995fdea33d62b6835a80568e5b --- /dev/null +++ b/example/harmony/fs/src/main/ets/generated/turboModules/ReactNativeFs.ts @@ -0,0 +1,58 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +import { Tag } from "@rnoh/react-native-openharmony/ts" + +export namespace ReactNativeFs { + export const NAME = 'ReactNativeFs' as const + + export type MkdirOptionsT = {NSURLIsExcludedFromBackupKey?: boolean} + + export type StatResult = {ctime: number, mtime: number, size: number, mode: number, originalFilepath: string, type: number} + + export type DownloadResult = {jobId: number, statusCode: number, bytesWritten: number} + + export interface Spec { + getConstants(): Object; + + readFile(path: string): Promise; + + exists(path: string): Promise; + + mkdir(path: string, options: MkdirOptionsT): Promise; + + appendFile(path: string, contents: string, encoding: string): Promise; + + writeFile(path: string, contents: string, encoding: string): Promise; + + readFileAssets(path: string): Promise; + + copyFile(from: string, into: string): Promise; + + unlink(filepath: string): Promise; + + hash(filepath: string, algorithm: string): Promise; + + moveFile(filepath: string, destPath: string): Promise; + + read(path: string, length: number, position: number): Promise; + + write(filepath: string, contents: string, position: number): Promise; + + stat(filepath: string): Promise; + + touch(filepath: string, mtime: number, ctime: number): Promise; + + downloadFile(bridgeOptions: Object): Promise; + + readDir(dirpath: string): Promise; + + existsAssets(filepath: string): Promise; + + addListener(eventName: string): void; + + removeListeners(count: number): void; + + } +} diff --git a/example/harmony/fs/src/main/ets/generated/turboModules/ts.ts b/example/harmony/fs/src/main/ets/generated/turboModules/ts.ts new file mode 100644 index 0000000000000000000000000000000000000000..6281d124a09af9b4ab854073b69bf7e0bd7c7ac7 --- /dev/null +++ b/example/harmony/fs/src/main/ets/generated/turboModules/ts.ts @@ -0,0 +1,5 @@ +/** + * This code was generated by "react-native codegen-lib-harmony" + */ + +export * from "./ReactNativeFs" diff --git a/example/harmony/fs/src/main/module.json5 b/example/harmony/fs/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8aaa1c471d4818afa46ed77f12aa7087dc363876 --- /dev/null +++ b/example/harmony/fs/src/main/module.json5 @@ -0,0 +1,12 @@ +{ + "module": { + "name": "fs", + "type": "har", + "description": "$string:module_desc", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ] + } +} \ No newline at end of file diff --git a/example/harmony/fs/src/main/resources/base/element/color.json b/example/harmony/fs/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/harmony/fs/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/src/main/resources/base/element/string.json b/example/harmony/fs/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..97ba7bb7d6e1869991833ee28f0be4c3aa0fd102 --- /dev/null +++ b/example/harmony/fs/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "FsAbility_desc", + "value": "description" + }, + { + "name": "FsAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/src/main/resources/en_US/element/string.json b/example/harmony/fs/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..97ba7bb7d6e1869991833ee28f0be4c3aa0fd102 --- /dev/null +++ b/example/harmony/fs/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "FsAbility_desc", + "value": "description" + }, + { + "name": "FsAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/src/main/resources/zh_CN/element/string.json b/example/harmony/fs/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..bd36d39fe384e696bcb5bc28d6d6e59629661119 --- /dev/null +++ b/example/harmony/fs/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "FsAbility_desc", + "value": "description" + }, + { + "name": "FsAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/ets/test/Ability.test.ets b/example/harmony/fs/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..ca1b2dbe28656527d4b33f381198ab49619eefea --- /dev/null +++ b/example/harmony/fs/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/ets/test/List.test.ets b/example/harmony/fs/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f6c27e3f2b7769ca67ede6995576bd250e8e406c --- /dev/null +++ b/example/harmony/fs/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/ets/testability/TestAbility.ets b/example/harmony/fs/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..1dcec5fb78fb4f8a47fa20e86b0eebb46e5e160e --- /dev/null +++ b/example/harmony/fs/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; +import Want from '@ohos.app.ability.Want'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); + let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator; + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs; + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments(); + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/ets/testability/pages/Index.ets b/example/harmony/fs/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..783cd7a07a81ffe97f4be04e249c6b5bc6b3b64b --- /dev/null +++ b/example/harmony/fs/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets b/example/harmony/fs/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets new file mode 100644 index 0000000000000000000000000000000000000000..05f743b36bbaf4aef35bfa6a4e9d08c548ffd75a --- /dev/null +++ b/example/harmony/fs/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import hilog from '@ohos.hilog'; +import { BusinessError } from '@ohos.base'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import Want from '@ohos.app.ability.Want'; +import resourceManager from '@ohos.resourceManager'; +import util from '@ohos.util'; + +let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator; +let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs; +let jsonPath: string = 'mock/mock-config.json'; +let tag: string = 'testTag'; + +async function onAbilityCreateCallback(data: UIAbility) { + hilog.info(0x0000, 'testTag', 'onAbilityCreateCallback, data: ${}', JSON.stringify(data)); +} + +async function addAbilityMonitorCallback(err: BusinessError) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare'); + } + + async onRun() { + let tag = 'testTag'; + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + let moduleName = abilityDelegatorArguments.parameters['-m']; + let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); + let mResourceManager = context.resourceManager; + await checkMock(abilityDelegator, mResourceManager); + const bundleName = abilityDelegatorArguments.bundleName; + const testAbilityName: string = 'TestAbility'; + let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + moduleName: moduleName + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + const want: Want = { + bundleName: bundleName, + abilityName: testAbilityName, + moduleName: moduleName + }; + abilityDelegator.startAbility(want, (err: BusinessError, data: void) => { + hilog.info(0x0000, tag, 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, tag, 'startAbility : data : %{public}s', JSON.stringify(data) ?? ''); + }) + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} + +async function checkMock(abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { + let rawFile: Uint8Array; + try { + rawFile = resourceManager.getRawFileContentSync(jsonPath); + hilog.info(0x0000, tag, 'MockList file exists'); + let mockStr: string = util.TextDecoder.create("utf-8", { ignoreBOM: true }).decodeWithStream(rawFile); + let mockMap: Record = getMockList(mockStr); + try { + abilityDelegator.setMockList(mockMap) + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); + } + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); + } +} + +function getMockList(jsonStr: string) { + let jsonObj: Record = JSON.parse(jsonStr); + let map: Map = new Map(Object.entries(jsonObj)); + let mockList: Record = {}; + map.forEach((value: object, key: string) => { + let realValue: string = value['source'].toString(); + mockList[key] = realValue; + }); + hilog.info(0x0000, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); + return mockList; +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/module.json5 b/example/harmony/fs/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6cc4de6f52388e402073702d228a9641bb1be467 --- /dev/null +++ b/example/harmony/fs/src/ohosTest/module.json5 @@ -0,0 +1,38 @@ +{ + "module": { + "name": "fs_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/example/harmony/fs/src/ohosTest/resources/base/element/color.json b/example/harmony/fs/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/harmony/fs/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/src/ohosTest/resources/base/element/string.json b/example/harmony/fs/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c --- /dev/null +++ b/example/harmony/fs/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/example/harmony/fs/src/test/List.test.ets b/example/harmony/fs/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..45ccbccc7497b6fc736684cd37a53704de0383cd --- /dev/null +++ b/example/harmony/fs/src/test/List.test.ets @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/example/harmony/fs/src/test/LocalUnit.test.ets b/example/harmony/fs/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..4a395d8f128856926ebaa26e487c328913120af2 --- /dev/null +++ b/example/harmony/fs/src/test/LocalUnit.test.ets @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest',() => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/example/harmony/fs/ts.ts b/example/harmony/fs/ts.ts new file mode 100644 index 0000000000000000000000000000000000000000..e48256beaca8f1360b5bbf172e45448960acb551 --- /dev/null +++ b/example/harmony/fs/ts.ts @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved + * Use of this source code is governed by a MIT license that can be + * found in the LICENSE file. + */ + +export * from "./src/main/ets/FsPackage" diff --git a/example/harmony/hvigor/.gitignore b/example/harmony/hvigor/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..aa1ec1ea0618124672d14a63d00d943240f2db2a --- /dev/null +++ b/example/harmony/hvigor/.gitignore @@ -0,0 +1 @@ +*.tgz diff --git a/example/harmony/hvigor/hvigor-config.json5 b/example/harmony/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c8ba182673e80fbc3da9e614580e9453b718cd3b --- /dev/null +++ b/example/harmony/hvigor/hvigor-config.json5 @@ -0,0 +1,21 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "default", /* Define the build analyze mode. Value: [ "default" | "verbose" | false ]. Default: "default" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 4096 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */ + } +} \ No newline at end of file diff --git a/example/harmony/hvigorfile.ts b/example/harmony/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..6478186902c0c1ad7c966a929c7d6b7d8ae7a9f3 --- /dev/null +++ b/example/harmony/hvigorfile.ts @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { appTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/example/harmony/oh-package.json5 b/example/harmony/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fe6ab68c076ba72f6fc67f919699ff0862be86b2 --- /dev/null +++ b/example/harmony/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "modelVersion": "5.0.0", + "license": "ISC", + "name": "rnoh", + "description": "example description", + "repository": {}, + "version": "1.0.0", + "overrides": { + "@rnoh/react-native-openharmony": "0.72.38" + }, + "dynamicDependencies": {} +} \ No newline at end of file diff --git a/example/index.js b/example/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5b0e15d456a71c43c23de86c7885336a53ff0da7 --- /dev/null +++ b/example/index.js @@ -0,0 +1,5 @@ +import {AppRegistry, View, Text} from 'react-native'; +import {name as appName} from './app.json'; +import App from './src/FsTest'; + +AppRegistry.registerComponent(appName, () => App); \ No newline at end of file diff --git a/example/jest.config.js b/example/jest.config.js new file mode 100644 index 0000000000000000000000000000000000000000..3745fc223702d5de74319e30d185b1dd5960d87b --- /dev/null +++ b/example/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/example/metro.config.js b/example/metro.config.js new file mode 100644 index 0000000000000000000000000000000000000000..f33c325ac1fad6c7733a76bfe002d136fb442c8f --- /dev/null +++ b/example/metro.config.js @@ -0,0 +1,24 @@ +const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config'); +const {createHarmonyMetroConfig} = require('react-native-harmony/metro.config'); + +/** + * @type {import("metro-config").ConfigT} + */ +const config = { + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, +}; + +module.exports = mergeConfig( + getDefaultConfig(__dirname), + createHarmonyMetroConfig({ + reactNativeHarmonyPackageName: 'react-native-harmony', + }), + config, +); diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000000000000000000000000000000000000..94d3ddab05f9cf5528b76596e7a6da29177f0389 --- /dev/null +++ b/example/package.json @@ -0,0 +1,66 @@ +{ + "name": "react-native-harmony-tester", + "version": "1.0.0", + "private": true, + "scripts": { + "i": "cd ../ && npm i && npm run pack && cd ./example && npm i", + "reStart": "npm run install:pkg && npm run codegen && hdc rport tcp:8081 tcp:8081 && react-native start", + "start": "hdc rport tcp:8081 tcp:8081 && react-native start", + "codegen": "react-native codegen-harmony --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony", + "pack:pkg": "cd ../ && npm run pack && cd ./example", + "install:pkg": "npm uninstall @react-native-ohos/react-native-fs && npm run pack:pkg && npm i @react-native-ohos/react-native-fs@file:../react-native-ohos-react-native-fs-2.20.0.tgz", + "dev": "npm run codegen && react-native bundle-harmony --dev --minify=false", + "prod": "npm run codegen && react-native bundle-harmony --dev=false --minify=true", + "postinstall": "node ./scripts/create-build-profile" + }, + "dependencies": { + "@gorhom/portal": "^1.0.14", + "@react-native-ohos/react-native-fs": "file:../react-native-ohos-react-native-fs-2.20.0.tgz", + "@rnoh/testerino": "npm:@react-native-oh/rnoh-testerino@0.0.9", + "react": "18.2.0", + "react-native": "0.72.5", + "react-native-harmony": "npm:@react-native-oh/react-native-harmony@^0.72.32" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@babel/preset-env": "^7.20.0", + "@babel/runtime": "^7.20.0", + "@react-native-community/eslint-config": "^3.2.0", + "@react-native/eslint-config": "^0.74.0", + "@react-native/metro-config": "^0.72.6", + "@tsconfig/react-native": "^2.0.2", + "@types/chai": "^4.3.4", + "@types/d3-scale-chromatic": "^3.0.0", + "@types/fs-extra": "^11.0.1", + "@types/jest": "^29.5.5", + "@types/metro-config": "^0.76.2", + "@types/react": "17.0.14", + "@types/react-dom": "17.0.14", + "@types/react-test-renderer": "^18.0.0", + "babel-jest": "^29.2.1", + "csv-parser": "^3.0.0", + "eslint": "^8.19.0", + "eslint-plugin-prettier": "^5.0.1", + "fs-extra": "^11.1.1", + "husky": "^8.0.3", + "jest": "^29.7.0", + "json5": "^2.2.3", + "metro": "^0.76.3", + "metro-config": "^0.76.3", + "metro-react-native-babel-preset": "0.73.9", + "prettier": "3.2.4", + "react-test-renderer": "18.2.0", + "simple-statistics": "^7.8.3", + "ts-jest": "^29.1.1", + "typescript": "^5.3.2", + "yargs": "^17.7.2" + }, + "overrides": { + "@rnoh/react-native-harmony-cli": "npm:@react-native-oh/react-native-harmony-cli@^0.0.27", + "@react-native-community/cli": "11.3.6", + "@react-native/codegen": "0.74.0" + }, + "resolutions": { + "@react-native-community/cli": "11.3.6" + } +} diff --git a/example/react-native.config.js b/example/react-native.config.js new file mode 100644 index 0000000000000000000000000000000000000000..4a038ab52d08072540e95ca80d06f5e6afe7e867 --- /dev/null +++ b/example/react-native.config.js @@ -0,0 +1,5 @@ +module.exports = { + project: { + }, + assets: ['./assets/fonts/'], +}; diff --git a/example/scripts/create-build-profile.js b/example/scripts/create-build-profile.js new file mode 100644 index 0000000000000000000000000000000000000000..a7841d078f1258af521d389da9dc43c718903c6e --- /dev/null +++ b/example/scripts/create-build-profile.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const JSON5 = require('json5'); +const path = require('path'); + +const templatePath = path.join( + __dirname, + '..', + 'harmony', + 'build-profile.template.json5', +); +const existingProfilePath = path.join( + __dirname, + '..', + 'harmony', + 'build-profile.json5', +); + +if (fs.existsSync(existingProfilePath)) { + let existingProfile = JSON5.parse( + fs.readFileSync(existingProfilePath, 'utf-8'), + ); + let template = JSON5.parse(fs.readFileSync(templatePath, 'utf-8')); + let signingConfigs = + existingProfile.app && existingProfile.app.signingConfigs; + + existingProfile = {...template}; + + if (signingConfigs) { + existingProfile.app.signingConfigs = signingConfigs; + } + + fs.writeFileSync( + existingProfilePath, + JSON5.stringify(existingProfile, null, 2), + ); +} else { + // File doesn't exist, create a copy from the template + fs.copyFileSync(templatePath, existingProfilePath); +} \ No newline at end of file diff --git a/example/src/FsTest.tsx b/example/src/FsTest.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9c03e2789a456e27fcdfd74861dc897800846c9a --- /dev/null +++ b/example/src/FsTest.tsx @@ -0,0 +1,801 @@ +import {Tester, TestCase} from '@rnoh/testerino'; +import React, {useState} from 'react'; +import { + StyleSheet, + ScrollView, + View, + Text, + StatusBar, + TextInput, + Button, +} from 'react-native'; +import RNFS from 'react-native-fs'; +import {Colors} from 'react-native/Libraries/NewAppScreen'; + +function FsTest(): React.JSX.Element { + // 参数 + const [mkdirParam, setMkdirParam] = useState(''); + const [existsParams, setExistsParams] = useState(''); + const [readDirExampleData, setreadDirExampleData] = useState({}); + const [writeFileExampleData, setwriteFileExampleData] = useState(''); + const [hashExampleData, sethashExampleData] = useState(''); + const [readFileAssetsExampleData, setreadFileAssetsExampleData] = + useState(''); + const [existsAssetsExampleData, setexistsAssetsExampleData] = useState(''); + const [existsExampleData, setexistsExampleData] = useState(''); + const mkdirExample = () => { + //创建文件夹 + RNFS.mkdir(RNFS.DocumentDirectoryPath + '/' + mkdirParam).then( + result => { + console.log('file mkdirExample ' + mkdirParam); + }, + err => { + console.error('file mkdir: ' + err.message); + }, + ); + }; + + const existsExample = () => { + //查找文件夹 + RNFS.exists(RNFS.DocumentDirectoryPath + '/' + existsParams).then( + result => { + console.log('file existsExample ' + result); + setexistsExampleData(JSON.stringify(result)); + }, + err => { + console.error('file mkdir: ' + err.message); + }, + ); + }; + + const readDirExample = () => { + //读取文件夹 + RNFS.readDir(RNFS.DocumentDirectoryPath) // On Android, use "RNFS.DocumentDirectoryPath" (MainBundlePath is not defined) + .then(result => { + console.log('readDirExample', result); + setreadDirExampleData(result); + }) + .catch(err => { + console.log('readDirExamplereadDirExample', err); + }); + }; + + const writeFileExample = () => { + // create a path you want to write to + // :warning: on iOS, you cannot write into `RNFS.MainBundlePath`, + // but `RNFS.DocumentDirectoryPath` exists on both platforms and is writable + var path = RNFS.DocumentDirectoryPath + '/2.txt'; + + // write the file + RNFS.writeFile( + path, + ` + + + + + Document + + + 111 + + `, + 'utf8', + ) //写入文件 + .then(success => { + console.log( + 'writeFileExample' + RNFS.DocumentDirectoryPath + '/1.html', + success, + ); + setwriteFileExampleData(path); + }) + .catch(err => { + console.log(err.message); + }); + }; + + const unlinkExample = () => { + // create a path you want to delete + var path = RNFS.DocumentDirectoryPath + '/2.txt'; + + RNFS.unlink(path) //删除文件 + .then(() => { + console.log('unlinkExample'); + }) + // `unlink` will throw an error, if the item to unlink does not exist + .catch(err => { + console.log(err.message); + }); + }; + + const [readFileParam, setReadFileParam] = useState(''); + const [readFileExampleData, setreadFileExampleData] = useState({}); + const readFileExample = () => { + RNFS.readFile(RNFS.DocumentDirectoryPath + `/${readFileParam}`) + .then(contents => { + console.log('readFileExample', contents.length); + setreadFileExampleData(contents); + }) + .catch(err => { + console.log('ygb 读取失败:', err.message, err.code); + }); + }; + + const appendFileExample = () => { + //给文件写入内容 + var path = RNFS.DocumentDirectoryPath + '/2.txt'; + RNFS.appendFile(path, '新添加的文本', 'utf8') + .then(success => { + console.log('appendFileExamplesuccess'); + }) + .catch(err => { + console.log(err.message); + }); + }; + + const copyFileExample = () => { + const path = RNFS.DocumentDirectoryPath + '/2.txt'; + const path1 = RNFS.DocumentDirectoryPath + '/3.txt'; + RNFS.copyFile(path, path1) + .then(result => { + console.log('copyFileExample', path, path1); + }) + .catch(err => { + console.log(err.message); + }); + }; + + const moveFileExample = () => { + const path = RNFS.DocumentDirectoryPath + '/3.txt'; + const path1 = RNFS.DocumentDirectoryPath + '/eee/3.txt'; + RNFS.moveFile(path, path1) + .then(result => { + console.log('moveFileExample', path, path1); + }) + .catch(err => { + console.log(err.message); + }); + }; + + const [readExampleData, setreadExampleData] = useState({}); + const readExample = () => { + const path = RNFS.DocumentDirectoryPath + '/2.txt'; + RNFS.read(path, 1024, 0, 'utf8') + .then(result => { + console.log('readExample', result); + setreadExampleData(result); + }) + .catch(err => { + console.log(err.message); + }); + }; + + const [starExampleData, setstarExampleData] = useState({}); + const starExample = () => { + const path = RNFS.DocumentDirectoryPath + '/2.txt'; + RNFS.stat(path) + .then(result => { + console.log('starExample', result); + setstarExampleData(result); + }) + .catch(err => { + console.log(err.message); + }); + }; + + const downloadFileExample = () => { + RNFS.downloadFile({ + fromUrl: + 'https://www-file.huawei.com/minisite/media/annual_report/annual_report_2022_cn.pdf', // URL to download file from + toFile: RNFS.DocumentDirectoryPath + '/3.pdf', + }); + }; + + const readFileAssetsExample = () => { + //用户获取resources/rawfile/1.txt 目录下对应的rawfile文件内容,使用callback形式返回字节数组。 + RNFS.readFileAssets('1.txt', 'utf8') + .then(res => { + console.log('readFileAssetsExample', res); + setreadFileAssetsExampleData(res); + }) + .catch(err => { + console.log('readFileAssetsExample error', err); + }); + }; + + const existsAssetsExample = () => { + //用户获取resources/rawfile/ 底下有没有assets + RNFS.existsAssets('assets') + .then(res => { + console.log('existsAssetsExample', res); + setexistsAssetsExampleData(JSON.stringify(res)); + }) + .catch(err => { + console.log('existsAssetsExample error', err); + }); + }; + + const hashExample = () => { + //用户获取resources/rawfile/ 底下有没有assets + const path = RNFS.DocumentDirectoryPath + '/2.txt'; + RNFS.hash(path, 'md5') + .then(res => { + console.log('hash', res); + sethashExampleData(res); + }) + .catch(err => { + console.log('hash error', err); + }); + }; + + const touchExample = () => { + //用户获取resources/rawfile/ 底下有没有assets + const path = RNFS.DocumentDirectoryPath + '/2.txt'; + RNFS.touch(path, new Date('2018-12-2')) + .then(res => { + console.log('touchExampletouchExampletouchExample', res); + }) + .catch(err => { + console.log('hash error', err); + }); + }; + + const writeExample = () => { + //用户获取resources/rawfile/ 底下有没有assets + const path = RNFS.DocumentDirectoryPath + '/3.txt'; + RNFS.write(path, 'utxxxxxx') + .then(res => { + console.log('touchExampletouchExampletouchExample', res); + }) + .catch(err => { + console.log('hash error', err); + }); + }; + + return ( + <> + + + + + { + return ( + + + + 当前库为文件操作库 不存在页面效果请关注文件系统变更{'\n'} + 相关资料链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-sandbox-directory-0000001774280086 + + + + + FS.DocumentDirectoryPath:{'\n'} + {RNFS.DocumentDirectoryPath} + + + + + FS.CachesDirectoryPath:{RNFS.CachesDirectoryPath} + + + + + FS.MainBundlePath:{'\n'} + {RNFS.MainBundlePath} + + + + + FS.TemporaryDirectoryPath:{'\n'} + {RNFS.TemporaryDirectoryPath} + + + + + FS.LibraryDirectoryPath:{'\n'} + {RNFS.LibraryDirectoryPath} + + + + ); + }} + assert={async ({expect, state}) => { + expect(state).to.be.true; + }} + /> + { + return ( + + + setMkdirParam(mkdirParam)} + placeholderTextColor="#9a73ef" + autoCapitalize="none" + /> + + + {' '} + {RNFS.DocumentDirectoryPath + '/' + mkdirParam} + +