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