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