1 /*
2 * Copyright (c) 2024 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 "util/io_util.h"
17
18 #include <charconv>
19
20 #include <base/math/mathf.h>
21
22 #include <core/io/intf_file_manager.h>
23 #include <core/json/json.h>
24 #include <core/intf_engine.h>
25
26 #include "util/path_util.h"
27 #include "util/json_util.h"
28
29 using namespace BASE_NS;
30 using namespace CORE_NS;
31
32 UTIL_BEGIN_NAMESPACE()
33
34 namespace IoUtil {
35
WriteCompatibilityInfo(json::standalone_value & jsonOut,const CompatibilityInfo & info)36 bool WriteCompatibilityInfo(json::standalone_value& jsonOut, const CompatibilityInfo& info)
37 {
38 jsonOut["compatibility_info"] = json::standalone_value::object();
39 jsonOut["compatibility_info"]["version"] =
40 string(to_string(info.versionMajor) + "." + to_string(info.versionMinor));
41 jsonOut["compatibility_info"]["type"] = string(info.type);
42 return true;
43 }
44
CheckCompatibility(const json::value & json,array_view<CompatibilityRange const> validVersions)45 SerializationResult CheckCompatibility(const json::value& json, array_view<CompatibilityRange const> validVersions)
46 {
47 SerializationResult result;
48
49 string type;
50 string version;
51 if (const json::value* iter = json.find("compatibility_info"); iter) {
52 string parseError;
53 SafeGetJsonValue(*iter, "type", parseError, type);
54 SafeGetJsonValue(*iter, "version", parseError, version);
55
56 uint32_t versionMajor{ 0 };
57 uint32_t versionMinor{ 0 };
58 if (const auto delim = version.find('.'); delim != string::npos) {
59 std::from_chars(version.data(), version.data() + delim, versionMajor);
60 const size_t minorStart = delim + 1;
61 std::from_chars(version.data() + minorStart, version.data() + version.size(), versionMinor);
62 } else {
63 std::from_chars(version.data(), version.data() + version.size(), versionMajor);
64 }
65
66 result.compatibilityInfo.versionMajor = versionMajor;
67 result.compatibilityInfo.versionMinor = versionMinor;
68 result.compatibilityInfo.type = type;
69
70 for (const auto& range : validVersions) {
71 if (type != range.type) {
72 continue;
73 }
74 if ((range.versionMajorMin != CompatibilityRange::IGNORE_VERSION) &&
75 (versionMajor < range.versionMajorMin)) {
76 continue;
77 }
78 if ((range.versionMajorMax != CompatibilityRange::IGNORE_VERSION) &&
79 (versionMajor > range.versionMajorMax)) {
80 continue;
81 }
82 if ((range.versionMinorMin != CompatibilityRange::IGNORE_VERSION) &&
83 (versionMinor < range.versionMinorMin)) {
84 continue;
85 }
86 if ((range.versionMinorMax != CompatibilityRange::IGNORE_VERSION) &&
87 (versionMinor > range.versionMinorMax)) {
88 continue;
89 }
90
91 // A compatible version was found from the list of valid versions.
92 return result;
93 }
94 }
95
96 // Not a compatible version.
97 result.status = Status::ERROR_COMPATIBILITY_MISMATCH;
98 result.error = "Unsupported version. type: '" + type + "' version: '" + version + "'";
99 return result;
100 }
101
CreateDirectories(CORE_NS::IFileManager & fileManager,string_view pathUri)102 bool CreateDirectories(CORE_NS::IFileManager& fileManager, string_view pathUri)
103 {
104 // Verify that the target path exists. (and create missing ones)
105 // Remove protocol.
106 auto pos = pathUri.find("://") + 3;
107 for (;;) {
108 size_t end = pathUri.find('/', pos);
109 auto part = pathUri.substr(0, end);
110
111 // The last "part" should be a file name, so terminate there.
112 if (end == string_view::npos) {
113 break;
114 }
115
116 auto entry = fileManager.GetEntry(part);
117 if (entry.type == entry.UNKNOWN) {
118 fileManager.CreateDirectory(part);
119 } else if (entry.type == entry.DIRECTORY) {
120 } else if (entry.type == entry.FILE) {
121 // Invalid path..
122 return false;
123 }
124 pos = end + 1;
125 }
126 return true;
127 }
128
DeleteDirectory(CORE_NS::IFileManager & fileManager,string_view pathUri)129 bool DeleteDirectory(CORE_NS::IFileManager& fileManager, string_view pathUri)
130 {
131 auto dir = fileManager.OpenDirectory(pathUri);
132 if (!dir) {
133 return false;
134 }
135
136 bool result = true;
137
138 for (auto& entry : dir->GetEntries()) {
139 auto childUri = PathUtil::ResolvePath(pathUri, entry.name);
140 switch (entry.type) {
141 case IDirectory::Entry::Type::FILE:
142 result = fileManager.DeleteFile(childUri) && result;
143 break;
144 case IDirectory::Entry::Type::DIRECTORY:
145 result = DeleteDirectory(fileManager, childUri) && result;
146 break;
147 default:
148 // NOTE: currently unknown type is just ignored and does not affect the result.
149 break;
150 }
151 }
152
153 // Result is true if all copy operations succeeded.
154 return fileManager.DeleteDirectory(pathUri) && result;
155 }
156
Copy(CORE_NS::IFileManager & fileManager,string_view sourceUri,string_view destinationUri)157 bool Copy(CORE_NS::IFileManager& fileManager, string_view sourceUri, string_view destinationUri)
158 {
159 bool destinationIsDir = false;
160 string tmp; // Just to keep the string alive for this function scope.
161
162 // If the destination is a directory. copy the source into that dir.
163 {
164 auto destinationDir = fileManager.OpenDirectory(destinationUri);
165 if (destinationDir) {
166 destinationIsDir = true;
167 auto filename = PathUtil::GetFilename(sourceUri);
168 tmp = PathUtil::ResolvePath(destinationUri, filename);
169 destinationUri = tmp;
170 }
171 }
172
173 // First try copying as a file.
174 if (CopyFile(fileManager, sourceUri, destinationUri)) {
175 return true;
176 }
177
178 // Then try copying as a dir (if the destination is a dir).
179 if (destinationIsDir) {
180 auto destinationUriAsDir = destinationUri + "/";
181 CreateDirectories(fileManager, destinationUriAsDir);
182 return CopyDirectoryContents(fileManager, sourceUri, destinationUriAsDir);
183 }
184
185 return false;
186 }
187
Move(CORE_NS::IFileManager & fileManager,string_view sourceUri,string_view destinationUri)188 bool Move(CORE_NS::IFileManager& fileManager, string_view sourceUri, string_view destinationUri)
189 {
190 return fileManager.Rename(sourceUri, destinationUri);
191 }
192
CopyFile(CORE_NS::IFileManager & fileManager,string_view sourceUri,string_view destinationUri)193 bool CopyFile(CORE_NS::IFileManager& fileManager, string_view sourceUri, string_view destinationUri)
194 {
195 auto s = fileManager.OpenFile(sourceUri);
196 if (!s) {
197 return false;
198 }
199 if (!CreateDirectories(fileManager, destinationUri)) {
200 return false;
201 }
202 auto d = fileManager.CreateFile(destinationUri);
203 if (!d) {
204 return false;
205 }
206 constexpr size_t bufferSize = 65536; // copy in 64kb blocks
207 uint8_t buffer[bufferSize];
208 size_t total = s->GetLength();
209 while (total > 0) {
210 auto todo = Math::min(total, bufferSize);
211 if (todo != s->Read(buffer, todo)) {
212 return false;
213 }
214 if (todo != d->Write(buffer, todo)) {
215 return false;
216 }
217 total -= todo;
218 }
219 return true;
220 }
221
CopyDirectoryContents(CORE_NS::IFileManager & fileManager,string_view sourceUri,string_view destinationUri)222 bool CopyDirectoryContents(CORE_NS::IFileManager& fileManager, string_view sourceUri, string_view destinationUri)
223 {
224 auto dst = fileManager.OpenDirectory(destinationUri);
225 if (!dst) {
226 return false;
227 }
228
229 auto src = fileManager.OpenDirectory(sourceUri);
230 if (!src) {
231 return false;
232 }
233
234 bool result = true;
235
236 for (auto& entry : src->GetEntries()) {
237 auto childSrc = PathUtil::ResolvePath(sourceUri, entry.name);
238 auto childDst = PathUtil::ResolvePath(destinationUri, entry.name);
239
240 switch (entry.type) {
241 case IDirectory::Entry::Type::FILE:
242 result = CopyFile(fileManager, childSrc, childDst) && result;
243 break;
244 case IDirectory::Entry::Type::DIRECTORY:
245 if (auto dirExists = fileManager.OpenDirectory(childDst); !dirExists) {
246 if (!fileManager.CreateDirectory(childDst)) {
247 result = false;
248 continue;
249 }
250 }
251 result = CopyDirectoryContents(fileManager, childSrc, childDst) && result;
252 break;
253 default:
254 // NOTE: currently unknown type is just ignored and does not affect the result.
255 break;
256 }
257 }
258
259 // Result is true if all copy operations succeeded.
260 return result;
261 }
262
SaveTextFile(CORE_NS::IFileManager & fileManager,string_view fileUri,string_view fileContents)263 bool SaveTextFile(CORE_NS::IFileManager& fileManager, string_view fileUri, string_view fileContents)
264 {
265 // the safest way to save files would be to save to a temp file and rename.
266 auto file = fileManager.CreateFile(fileUri);
267 if (file) {
268 file->Write(fileContents.data(), fileContents.length());
269 file->Close();
270 return true;
271 }
272
273 return false;
274 }
275
LoadTextFile(CORE_NS::IFileManager & fileManager,string_view fileUri,string & fileContentsOut)276 bool LoadTextFile(CORE_NS::IFileManager& fileManager, string_view fileUri, string& fileContentsOut)
277 {
278 auto file = fileManager.OpenFile(fileUri);
279 if (file) {
280 const size_t length = file->GetLength();
281 fileContentsOut.resize(length);
282 return file->Read(fileContentsOut.data(), length) == length;
283 }
284 return false;
285 }
286
287 template<typename Work>
ReplaceTextInFilesImpl(CORE_NS::IFileManager & fileManager,BASE_NS::string_view folderUri,Work & replace)288 void ReplaceTextInFilesImpl(CORE_NS::IFileManager& fileManager, BASE_NS::string_view folderUri, Work& replace)
289 {
290 if (auto dir = fileManager.OpenDirectory(folderUri)) {
291 auto entries = dir->GetEntries();
292 for (const auto& entry : entries) {
293 if (entry.type == CORE_NS::IDirectory::Entry::FILE) {
294 auto separator_pos = entry.name.find_last_of(".");
295 auto ending = entry.name.substr(separator_pos);
296 bool isPlaintext { false };
297 static BASE_NS::vector<BASE_NS::string_view> plaintextTypes {
298 ".txt", // C++ Buld files
299 ".cpp", // C++ source files
300 ".h", // C++ header files
301 ".json", // Configuration files
302 ".json5", // Harmony OS build scripts
303 ".cmake", // Build files
304 ".in", // CMake configuration files
305 ".ets" // Harmony OS app code
306 };
307 for (const auto& type : plaintextTypes) {
308 if (ending == type) {
309 isPlaintext = true;
310 }
311 }
312 // odds of getting a match in a binary just by chance seem pretty slim so perhaps this check
313 // could be omitted, but I suppose that depends on the length of the tag we're replacing
314 if (isPlaintext) {
315 auto inFilePath = PathUtil::ResolvePath(folderUri, entry.name);
316 ReplaceTextInFileImpl(fileManager, inFilePath, replace);
317 }
318 } else if (entry.type == CORE_NS::IDirectory::Entry::DIRECTORY) {
319 auto path = PathUtil::ResolvePath(folderUri, entry.name);
320 ReplaceTextInFilesImpl(fileManager, path, replace);
321 }
322 }
323 }
324 }
325
326 template<typename Work>
ReplaceTextInFileImpl(CORE_NS::IFileManager & fileManager,BASE_NS::string_view uri,Work & replace)327 void ReplaceTextInFileImpl(CORE_NS::IFileManager& fileManager, BASE_NS::string_view uri, Work& replace)
328 {
329 auto inFile = fileManager.OpenFile(uri);
330 if (inFile) {
331 BASE_NS::string stringContents;
332 size_t len = inFile->GetLength();
333 stringContents.resize(len);
334 inFile->Read(stringContents.data(), len);
335
336 replace(stringContents);
337
338 auto dataToWrite = stringContents.data();
339 auto lenToWrite = stringContents.length();
340 fileManager.DeleteFile(uri);
341 auto outFile = fileManager.CreateFile(uri);
342 if (outFile) {
343 outFile->Write(dataToWrite, lenToWrite);
344 }
345 }
346 }
347
ReplaceTextInFiles(CORE_NS::IFileManager & fileManager,BASE_NS::string_view folderUri,BASE_NS::string_view text,BASE_NS::string_view replaceWith)348 void ReplaceTextInFiles(CORE_NS::IFileManager& fileManager, BASE_NS::string_view folderUri, BASE_NS::string_view text,
349 BASE_NS::string_view replaceWith)
350 {
351 auto replace = [&text, &replaceWith](BASE_NS::string& stringContents) {
352 auto pos = stringContents.find(text, 0UL);
353 while (pos != BASE_NS::string::npos) {
354 stringContents = stringContents.replace(
355 stringContents.begin() + static_cast<int64_t>(pos), stringContents.begin() +
356 static_cast<int64_t>(pos + text.size()), replaceWith);
357 pos += replaceWith.size();
358 pos = stringContents.find(text, pos);
359 }
360 };
361 ReplaceTextInFilesImpl(fileManager, folderUri, replace);
362 }
363
ReplaceTextInFiles(CORE_NS::IFileManager & fileManager,BASE_NS::string_view folderUri,BASE_NS::vector<Replacement> replacements)364 void ReplaceTextInFiles(
365 CORE_NS::IFileManager& fileManager, BASE_NS::string_view folderUri, BASE_NS::vector<Replacement> replacements)
366 {
367 auto replace = [&replacements](BASE_NS::string& stringContents) {
368 for (const auto& repl : replacements) {
369 auto pos = stringContents.find(repl.from, 0UL);
370 while (pos != BASE_NS::string::npos) {
371 stringContents = stringContents.replace(
372 stringContents.begin() + static_cast<int64_t>(pos), stringContents.begin() +
373 static_cast<int64_t>(pos + repl.from.size()), repl.to);
374 pos += repl.to.size();
375 pos = stringContents.find(repl.from, pos);
376 }
377 }
378 };
379 ReplaceTextInFilesImpl(fileManager, folderUri, replace);
380 }
381
ReplaceTextInFile(CORE_NS::IFileManager & fileManager,BASE_NS::string_view uri,BASE_NS::vector<Replacement> replacements)382 void ReplaceTextInFile(
383 CORE_NS::IFileManager& fileManager, BASE_NS::string_view uri, BASE_NS::vector<Replacement> replacements)
384 {
385 auto replace = [&replacements](BASE_NS::string& stringContents) {
386 for (const auto& repl : replacements) {
387 auto pos = stringContents.find(repl.from, 0UL);
388 while (pos != BASE_NS::string::npos) {
389 stringContents = stringContents.replace(stringContents.begin() + static_cast<int64_t>(pos),
390 stringContents.begin() + static_cast<int64_t>(pos + repl.from.size()), repl.to);
391 pos += repl.to.size();
392 pos = stringContents.find(repl.from, pos);
393 }
394 }
395 };
396 ReplaceTextInFileImpl(fileManager, uri, replace);
397 }
398
CopyAndRenameFiles(CORE_NS::IFileManager & fileManager,BASE_NS::string_view fromUri,BASE_NS::string_view toUri,BASE_NS::string_view filename)399 bool CopyAndRenameFiles(CORE_NS::IFileManager& fileManager, BASE_NS::string_view fromUri, BASE_NS::string_view toUri,
400 BASE_NS::string_view filename)
401 {
402 auto template_dir = fileManager.OpenDirectory(fromUri);
403 auto project_dir = fileManager.OpenDirectory(toUri);
404 bool result{ true };
405 // copy the .cpp and .h behavior files from the template to the project, renaming them
406 if (template_dir && project_dir) {
407 for (const auto& entry : template_dir->GetEntries()) {
408 if (entry.type != CORE_NS::IDirectory::Entry::Type::FILE) {
409 continue;
410 }
411 const auto& n = entry.name;
412 auto ending = n.substr(n.find_last_of('.'));
413 auto from = PathUtil::ResolvePath(fromUri, n);
414 auto to = PathUtil ::ResolvePath(toUri, filename + ending);
415 if (!CopyFile(fileManager, from, to)) {
416 result = false;
417 break;
418 }
419 }
420 } else {
421 result = false;
422 }
423 return result;
424 }
425
426 template<typename Work>
InsertInFileBoilerplate(CORE_NS::IFileManager & fileManager,BASE_NS::string_view fileUri,Work & inner)427 void InsertInFileBoilerplate(CORE_NS::IFileManager& fileManager, BASE_NS::string_view fileUri, Work& inner)
428 {
429 if (auto inFile = fileManager.OpenFile(fileUri)) {
430 BASE_NS::string stringContents;
431 size_t len = inFile->GetLength();
432 stringContents.resize(len);
433 inFile->Read(stringContents.data(), len);
434
435 inner(stringContents, len);
436
437 auto dataToWrite = stringContents.data();
438 auto lenToWrite = stringContents.length();
439 fileManager.DeleteFile(fileUri);
440 auto outFile = fileManager.CreateFile(fileUri);
441 outFile->Write(dataToWrite, lenToWrite);
442 }
443 }
444
InsertIntoString(BASE_NS::string & search,BASE_NS::string & insertion,InsertType type,BASE_NS::string & stringContents,size_t len)445 void InsertIntoString(
446 BASE_NS::string& search, BASE_NS::string& insertion, InsertType type, BASE_NS::string& stringContents, size_t len)
447 {
448 auto pos = stringContents.find(search, 0UL);
449 if (pos != BASE_NS::string::npos) {
450 if (type == InsertType::TAG) {
451 auto nlPos = stringContents.find("\n", pos);
452 if (nlPos == BASE_NS::string::npos) {
453 nlPos = len - 1;
454 }
455 stringContents.insert(nlPos + 1, (insertion + "\r\n").data());
456 } else if (type == InsertType::SIGNATURE) {
457 auto bPos = stringContents.find('{', pos);
458 if (bPos == BASE_NS::string::npos) {
459 return;
460 }
461 size_t depth{ 0 };
462 auto endPos = BASE_NS::string::npos;
463 while (bPos < len) {
464 const auto& ch = stringContents[bPos];
465 if (ch == '}') {
466 depth--;
467 if (depth == 0) {
468 endPos = bPos;
469 break;
470 }
471 } else if (ch == '{') {
472 depth++;
473 }
474 bPos++;
475 }
476 if (endPos != BASE_NS::string::npos) {
477 if (endPos == len - 1) {
478 stringContents.insert(len - 1, ("\r\n" + insertion + "\r\n").data());
479 } else {
480 stringContents.insert(endPos, (insertion + "\r\n").data());
481 }
482 }
483 }
484 }
485 }
486
InsertInFile(CORE_NS::IFileManager & fileManager,BASE_NS::string_view fileUri,BASE_NS::string search,BASE_NS::string insertion,InsertType type)487 void InsertInFile(CORE_NS::IFileManager& fileManager, BASE_NS::string_view fileUri, BASE_NS::string search,
488 BASE_NS::string insertion, InsertType type)
489 {
490 auto func = [&search, &insertion, &type](BASE_NS::string& stringContents, size_t len) {
491 InsertIntoString(search, insertion, type, stringContents, len);
492 };
493 InsertInFileBoilerplate(fileManager, fileUri, func);
494 }
495
InsertInFile(CORE_NS::IFileManager & fileManager,BASE_NS::string_view fileUri,BASE_NS::vector<Insertion> insertions)496 void InsertInFile(
497 CORE_NS::IFileManager& fileManager, BASE_NS::string_view fileUri, BASE_NS::vector<Insertion> insertions)
498 {
499 auto func = [&insertions](BASE_NS::string& stringContents, size_t len) {
500 for (auto& ins : insertions) {
501 InsertIntoString(ins.searchStr, ins.insertStr, ins.type, stringContents, len);
502 }
503 };
504 InsertInFileBoilerplate(fileManager, fileUri, func);
505 };
506
ReplaceInString(BASE_NS::string & string,const BASE_NS::string & target,const BASE_NS::string & replacement)507 void ReplaceInString(BASE_NS::string& string, const BASE_NS::string& target, const BASE_NS::string& replacement)
508 {
509 auto pos = string.find(target, 0UL);
510 while (pos != BASE_NS::string::npos) {
511 string = string.replace(string.begin() + static_cast<int64_t>(pos), string.begin() +
512 static_cast<int64_t>(pos + target.size()), replacement);
513 pos += replacement.size();
514 pos = string.find(target, pos);
515 }
516 }
517
518 } // namespace IoUtil
519
520 UTIL_END_NAMESPACE()
521