1 /*
2  * Copyright (c) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "cloud_file_cache_napi.h"
17 
18 #include <cstddef>
19 #include <sys/types.h>
20 
21 #include "async_work.h"
22 #include "cloud_sync_manager.h"
23 #include "dfs_error.h"
24 #include "utils_log.h"
25 #include "uv.h"
26 
27 namespace OHOS::FileManagement::CloudSync {
28 using namespace FileManagement::LibN;
29 using namespace std;
30 
HasEvent(const string & eventType)31 bool RegisterManager::HasEvent(const string &eventType)
32 {
33     unique_lock<mutex> registerMutex_;
34     bool hasEvent = false;
35     for (auto &iter : registerInfo_) {
36         if (iter->eventType == eventType) {
37             hasEvent = true;
38             break;
39         }
40     }
41     return hasEvent;
42 }
43 
AddRegisterInfo(shared_ptr<RegisterInfoArg> info)44 bool RegisterManager::AddRegisterInfo(shared_ptr<RegisterInfoArg> info)
45 {
46     if (HasEvent(info->eventType)) {
47         return false;
48     }
49     {
50         unique_lock<mutex> registerMutex_;
51         registerInfo_.insert(info);
52     }
53     return true;
54 }
55 
RemoveRegisterInfo(const string & eventType)56 bool RegisterManager::RemoveRegisterInfo(const string &eventType)
57 {
58     unique_lock<mutex> registerMutex_;
59     bool isFound = false;
60     for (auto iter = registerInfo_.begin(); iter != registerInfo_.end();) {
61         if ((*iter)->eventType == eventType) {
62             isFound = true;
63             iter = registerInfo_.erase(iter);
64         } else {
65             iter++;
66         }
67     }
68     return isFound;
69 }
70 
Constructor(napi_env env,napi_callback_info info)71 napi_value CloudFileCacheNapi::Constructor(napi_env env, napi_callback_info info)
72 {
73     NFuncArg funcArg(env, info);
74     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
75         LOGE("Start Number of arguments unmatched");
76         NError(E_PARAMS).ThrowErr(env);
77         return nullptr;
78     }
79 
80     auto fileCacheEntity = make_unique<FileCacheEntity>();
81     if (!NClass::SetEntityFor<FileCacheEntity>(env, funcArg.GetThisVar(), move(fileCacheEntity))) {
82         LOGE("Failed to set file cache entity.");
83         NError(E_UNKNOWN_ERR).ThrowErr(env);
84         return nullptr;
85     }
86     return funcArg.GetThisVar();
87 }
88 
ToExport(std::vector<napi_property_descriptor> props)89 bool CloudFileCacheNapi::ToExport(std::vector<napi_property_descriptor> props)
90 {
91     std::string className = GetClassName();
92     auto [succ, classValue] = NClass::DefineClass(exports_.env_, className, Constructor, std::move(props));
93     if (!succ) {
94         NError(E_UNKNOWN_ERR).ThrowErr(exports_.env_);
95         LOGE("Failed to define GallerySync class");
96         return false;
97     }
98 
99     succ = NClass::SaveClass(exports_.env_, className, classValue);
100     if (!succ) {
101         NError(E_UNKNOWN_ERR).ThrowErr(exports_.env_);
102         LOGE("Failed to save GallerySync class");
103         return false;
104     }
105 
106     return exports_.AddProp(className, classValue);
107 }
108 
CleanCloudFileCache(napi_env env,napi_callback_info info)109 napi_value CloudFileCacheNapi::CleanCloudFileCache(napi_env env, napi_callback_info info)
110 {
111     LOGI("CleanCache start");
112     NFuncArg funcArg(env, info);
113 
114     if (!funcArg.InitArgs(NARG_CNT::ONE)) {
115         NError(E_PARAMS).ThrowErr(env);
116         return nullptr;
117     }
118 
119     auto [succ, uri, ignore] = NVal(env, funcArg[(int)NARG_POS::FIRST]).ToUTF8String();
120     if (!succ) {
121         LOGE("Get uri error");
122         NError(EINVAL).ThrowErr(env);
123         return nullptr;
124     }
125 
126     int32_t ret = CloudSyncManager::GetInstance().CleanCache(uri.get());
127     if (ret != E_OK) {
128         NError(Convert2JsErrNum(ret)).ThrowErr(env);
129         return nullptr;
130     }
131     return NVal::CreateUndefined(env).val_;
132 }
133 
Export()134 bool CloudFileCacheNapi::Export()
135 {
136     std::vector<napi_property_descriptor> props = {
137         NVal::DeclareNapiFunction("on", CloudFileCacheNapi::On),
138         NVal::DeclareNapiFunction("off", CloudFileCacheNapi::Off),
139         NVal::DeclareNapiFunction("start", CloudFileCacheNapi::StartFileCache),
140         NVal::DeclareNapiFunction("startBatch", CloudFileCacheNapi::StartBatchFileCache),
141         NVal::DeclareNapiFunction("stop", CloudFileCacheNapi::StopFileCache),
142         NVal::DeclareNapiFunction("stopBatch", CloudFileCacheNapi::StopBatchFileCache),
143         NVal::DeclareNapiFunction("cleanCache", CloudFileCacheNapi::CleanCloudFileCache),
144     };
145 
146     return ToExport(props);
147 }
148 
StartFileCache(napi_env env,napi_callback_info info)149 napi_value CloudFileCacheNapi::StartFileCache(napi_env env, napi_callback_info info)
150 {
151     NFuncArg funcArg(env, info);
152     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
153         LOGE("Start Number of arguments unmatched");
154         NError(E_PARAMS).ThrowErr(env);
155         return nullptr;
156     }
157     auto [succUri, uri, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
158     if (!succUri) {
159         LOGE("Start get uri parameter failed!");
160         NError(E_PARAMS).ThrowErr(env);
161         return nullptr;
162     }
163 
164     auto cbExec = [uri = string(uri.get())]() -> NError {
165         int32_t ret = CloudSyncManager::GetInstance().StartFileCache(uri);
166         if (ret != E_OK) {
167             LOGE("Start Download failed! ret = %{public}d", ret);
168             return NError(Convert2JsErrNum(ret));
169         }
170         LOGI("Start Download Success!");
171         return NError(ERRNO_NOERR);
172     };
173 
174     auto cbCompl = [](napi_env env, NError err) -> NVal {
175         if (err) {
176             return {env, err.GetNapiErr(env)};
177         }
178         return NVal::CreateUndefined(env);
179     };
180 
181     string procedureName = "cloudFileCache";
182     auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::TWO));
183     return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
184 }
185 
StopFileCache(napi_env env,napi_callback_info info)186 napi_value CloudFileCacheNapi::StopFileCache(napi_env env, napi_callback_info info)
187 {
188     NFuncArg funcArg(env, info);
189     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
190         LOGE("Stop Number of arguments unmatched");
191         NError(E_PARAMS).ThrowErr(env);
192         return nullptr;
193     }
194     auto [succ, uri, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
195     if (!succ) {
196         LOGE("Stop get uri parameter failed!");
197         NError(E_PARAMS).ThrowErr(env);
198         return nullptr;
199     }
200     bool needClean = false;
201     size_t maxArgSize = static_cast<size_t>(NARG_CNT::TWO);
202     if (funcArg.GetArgc() >= NARG_CNT::TWO) {
203         NVal option(env, funcArg[NARG_POS::SECOND]);
204         if (!option.TypeIs(napi_function)) {
205             tie(succ, needClean) = option.ToBool();
206             if (!succ) {
207                 LOGE("Failed to get clean flag!");
208                 NError(E_PARAMS).ThrowErr(env);
209                 return nullptr;
210             }
211             maxArgSize = static_cast<size_t>(NARG_CNT::THREE);
212         }
213     }
214     auto cbExec = [uri = string(uri.get()), env = env, needClean]() -> NError {
215         int32_t ret = CloudSyncManager::GetInstance().StopDownloadFile(uri, needClean);
216         if (ret != E_OK) {
217             LOGE("Stop Download failed! ret = %{public}d", ret);
218             return NError(Convert2JsErrNum(ret));
219         }
220         return NError(ERRNO_NOERR);
221     };
222 
223     auto cbCompl = [](napi_env env, NError err) -> NVal {
224         if (err) {
225             return {env, err.GetNapiErr(env)};
226         }
227         return NVal::CreateUndefined(env);
228     };
229 
230     string procedureName = "cloudFileCache";
231     auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, maxArgSize);
232     return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
233 }
234 
235 struct FileCacheArg {
236     vector<string> uriList;
237     int64_t downloadId;
238 };
239 
StartBatchFileCache(napi_env env,napi_callback_info info)240 napi_value CloudFileCacheNapi::StartBatchFileCache(napi_env env, napi_callback_info info)
241 {
242     NFuncArg funcArg(env, info);
243     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
244         LOGE("Start Number of arguments unmatched");
245         NError(E_PARAMS).ThrowErr(env);
246         return nullptr;
247     }
248 
249     auto fileUris = std::make_shared<FileCacheArg>();
250     auto [resGetUris, uriArray, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToStringArray();
251     if (!resGetUris) {
252         LOGE("Start get uri array parameter failed!");
253         NError(E_PARAMS).ThrowErr(env);
254         return nullptr;
255     }
256 
257     fileUris->uriList = uriArray;
258     auto cbExec = [fileUris]() -> NError {
259         int32_t ret = CloudSyncManager::GetInstance().StartFileCache(fileUris->uriList, fileUris->downloadId);
260         if (ret != E_OK) {
261             LOGE("Batch start file cache failed! ret = %{public}d", ret);
262             ret = (ret == E_CLOUD_SDK) ? E_UNKNOWN_ERR : ret;
263             return NError(Convert2JsErrNum(ret));
264         }
265         return NError(ERRNO_NOERR);
266     };
267 
268     auto cbCompl = [fileUris](napi_env env, NError err) -> NVal {
269         if (err) {
270             return {env, err.GetNapiErr(env)};
271         }
272         return NVal::CreateInt64(env, fileUris->downloadId);
273     };
274 
275     string procedureName = "cloudFileCache";
276     auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, static_cast<size_t>(NARG_CNT::TWO));
277     return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
278 }
279 
StopBatchFileCache(napi_env env,napi_callback_info info)280 napi_value CloudFileCacheNapi::StopBatchFileCache(napi_env env, napi_callback_info info)
281 {
282     NFuncArg funcArg(env, info);
283     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::THREE)) {
284         LOGE("Start Number of arguments unmatched");
285         NError(E_PARAMS).ThrowErr(env);
286         return nullptr;
287     }
288 
289     auto [succ, downloadId] = NVal(env, funcArg[NARG_POS::FIRST]).ToInt64();
290     if (!succ || downloadId <= 0) {
291         LOGE("Start get download ID parameter failed!");
292         NError(E_PARAMS).ThrowErr(env);
293         return nullptr;
294     }
295 
296     bool needClean = false;
297     size_t maxArgSize = static_cast<size_t>(NARG_CNT::TWO);
298     if (funcArg.GetArgc() >= NARG_CNT::TWO) {
299         NVal option(env, funcArg[NARG_POS::SECOND]);
300         if (!option.TypeIs(napi_function)) {
301             tie(succ, needClean) = option.ToBool();
302             if (!succ) {
303                 LOGE("Failed to get clean flag!");
304                 NError(E_PARAMS).ThrowErr(env);
305                 return nullptr;
306             }
307             maxArgSize = static_cast<size_t>(NARG_CNT::THREE);
308         }
309     }
310 
311     auto cbExec = [downloadId = downloadId, needClean]() -> NError {
312         int32_t ret = CloudSyncManager::GetInstance().StopFileCache(downloadId, needClean);
313         if (ret != E_OK) {
314             LOGE("Batch stop file cache failed! ret = %{public}d", ret);
315             ret = (ret == E_CLOUD_SDK) ? E_UNKNOWN_ERR : ret;
316             return NError(Convert2JsErrNum(ret));
317         }
318         return NError(ERRNO_NOERR);
319     };
320 
321     auto cbCompl = [](napi_env env, NError err) -> NVal {
322         if (err) {
323             return {env, err.GetNapiErr(env)};
324         }
325         return NVal::CreateUndefined(env);
326     };
327 
328     string procedureName = "cloudFileCache";
329     auto asyncWork = GetPromiseOrCallBackWork(env, funcArg, maxArgSize);
330     return asyncWork == nullptr ? nullptr : asyncWork->Schedule(procedureName, cbExec, cbCompl).val_;
331 }
332 
On(napi_env env,napi_callback_info info)333 napi_value CloudFileCacheNapi::On(napi_env env, napi_callback_info info)
334 {
335     NFuncArg funcArg(env, info);
336     if (!funcArg.InitArgs(NARG_CNT::TWO)) {
337         LOGE("Batch-On Number of arguments unmatched");
338         NError(E_PARAMS).ThrowErr(env);
339         return nullptr;
340     }
341     auto [succProgress, progress, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
342     string eventType(progress.get());
343     if (!succProgress || (eventType != PROGRESS && eventType != MULTI_PROGRESS)) {
344         LOGE("Batch-On get progress failed!");
345         NError(E_PARAMS).ThrowErr(env);
346         return nullptr;
347     }
348 
349     if (!NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function)) {
350         LOGE("Batch-On argument type mismatch");
351         NError(E_PARAMS).ThrowErr(env);
352         return nullptr;
353     }
354 
355     auto fileCacheEntity = NClass::GetEntityOf<FileCacheEntity>(env, funcArg.GetThisVar());
356     if (!fileCacheEntity) {
357         LOGE("Failed to get file cache entity.");
358         NError(E_PARAMS).ThrowErr(env);
359         return nullptr;
360     }
361 
362     auto arg = make_shared<RegisterInfoArg>();
363     arg->eventType = eventType;
364     if (eventType == PROGRESS) {
365         arg->callback = make_shared<CloudDownloadCallbackImpl>(env, NVal(env, funcArg[(int)NARG_POS::SECOND]).val_);
366     } else {
367         arg->callback =
368             make_shared<CloudDownloadCallbackImpl>(env, NVal(env, funcArg[(int)NARG_POS::SECOND]).val_, true);
369     }
370 
371     if (!fileCacheEntity->registerMgr.AddRegisterInfo(arg)) {
372         LOGE("Batch-On register callback fail, callback already exist");
373         NError(E_PARAMS).ThrowErr(env);
374         return nullptr;
375     }
376 
377     int32_t ret = CloudSyncManager::GetInstance().RegisterDownloadFileCallback(arg->callback);
378     if (ret != E_OK) {
379         LOGE("Failed to register callback, error: %{public}d", ret);
380         (void)fileCacheEntity->registerMgr.RemoveRegisterInfo(eventType);
381         NError(Convert2JsErrNum(ret)).ThrowErr(env);
382         return nullptr;
383     }
384 
385     return NVal::CreateUndefined(env).val_;
386 }
387 
Off(napi_env env,napi_callback_info info)388 napi_value CloudFileCacheNapi::Off(napi_env env, napi_callback_info info)
389 {
390     NFuncArg funcArg(env, info);
391     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
392         LOGE("Off Number of arguments unmatched");
393         NError(E_PARAMS).ThrowErr(env);
394         return nullptr;
395     }
396     auto [succProgress, progress, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
397     string eventType(progress.get());
398     if (!succProgress || (eventType != PROGRESS && eventType != MULTI_PROGRESS)) {
399         LOGE("Batch-Off get progress failed!");
400         NError(E_PARAMS).ThrowErr(env);
401         return nullptr;
402     }
403 
404     if (funcArg.GetArgc() == (uint)NARG_CNT::TWO && !NVal(env, funcArg[NARG_POS::SECOND]).TypeIs(napi_function)) {
405         LOGE("Batch-Off argument type mismatch");
406         NError(E_PARAMS).ThrowErr(env);
407         return nullptr;
408     }
409 
410     auto fileCacheEntity = NClass::GetEntityOf<FileCacheEntity>(env, funcArg.GetThisVar());
411     if (!fileCacheEntity) {
412         LOGE("Failed to get file cache entity.");
413         NError(E_PARAMS).ThrowErr(env);
414         return nullptr;
415     }
416 
417     if (!fileCacheEntity->registerMgr.HasEvent(eventType)) {
418         LOGE("Batch-Off no callback is registered for this event type: %{public}s.", eventType.c_str());
419         NError(E_PARAMS).ThrowErr(env);
420         return nullptr;
421     }
422 
423     int32_t ret = CloudSyncManager::GetInstance().UnregisterDownloadFileCallback();
424     if (ret != E_OK) {
425         LOGE("Failed to unregister callback, error: %{public}d", ret);
426         NError(Convert2JsErrNum(ret)).ThrowErr(env);
427         return nullptr;
428     }
429 
430     if (!fileCacheEntity->registerMgr.RemoveRegisterInfo(eventType)) {
431         LOGE("Batch-Off no callback is registered for this event type: %{public}s.", eventType.c_str());
432         NError(E_PARAMS).ThrowErr(env);
433         return nullptr;
434     }
435 
436     return NVal::CreateUndefined(env).val_;
437 }
438 
GetClassName()439 string CloudFileCacheNapi::GetClassName()
440 {
441     return className_;
442 }
443 } // namespace OHOS::FileManagement::CloudSync