• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "resources/resource_manager.h"
17 
18 #include <algorithm>
19 #include <unordered_set>
20 
21 #include <base/containers/byte_array.h>
22 #include <base/math/mathf.h>
23 #include <base/util/uid_util.h>
24 #include <core/intf_engine.h>
25 #include <core/io/intf_file_manager.h>
26 #include <core/log.h>
27 #include <core/resources/intf_serializable.h>
28 
29 template<>
30 struct std::hash<BASE_NS::string> {
operator ()std::hash31     std::size_t operator()(const BASE_NS::string& s) const noexcept
32     {
33         return BASE_NS::hash(s);
34     }
35 };
36 
37 CORE_BEGIN_NAMESPACE()
38 
39 struct ResourceFileHeader {
40     uint8_t magic[4U] { 'L', 'R', 'F', 0x0U };
41     uint32_t resourceCount { 0U };
42     uint8_t padding[16U] {};
43 };
44 
45 struct ResourceBlobHeader {
46     BASE_NS::Uid uid;
47     uint32_t uriOffset { 0U };
48     uint32_t uriSize { 0U };
49     uint32_t dataOffset { 0U };
50     uint32_t dataSize { 0U };
51 };
52 
53 struct ResourceManager::ImportFile {
54     IFile::Ptr file;
55     BASE_NS::vector<ResourceBlobHeader> resourceHeaders;
56     BASE_NS::vector<BASE_NS::string> resourceUris;
57 };
58 
59 struct ResourceManager::ExportData {
60     BASE_NS::vector<ResourceBlobHeader> resourceHeaders;
61     BASE_NS::string uris;
62     BASE_NS::vector<uint8_t> data;
63 };
64 
ResourceManager(IEngine & engine)65 ResourceManager::ResourceManager(IEngine& engine) : engine_(engine) {}
66 
67 ResourceManager::~ResourceManager() = default;
68 
GetInterface(const BASE_NS::Uid & uid) const69 const IInterface* ResourceManager::GetInterface(const BASE_NS::Uid& uid) const
70 {
71     if (auto ret = IInterfaceHelper::GetInterface(uid)) {
72         return ret;
73     }
74     return nullptr;
75 }
76 
GetInterface(const BASE_NS::Uid & uid)77 IInterface* ResourceManager::GetInterface(const BASE_NS::Uid& uid)
78 {
79     if (auto ret = IInterfaceHelper::GetInterface(uid)) {
80         return ret;
81     }
82     return nullptr;
83 }
84 
Import(BASE_NS::string_view url)85 IResourceManager::Status ResourceManager::Import(BASE_NS::string_view url)
86 {
87     auto lock = std::lock_guard(mutex_);
88     if (importedFiles_.contains(url)) {
89         return Status::STATUS_OK;
90     }
91     auto& fm = engine_.GetFileManager();
92     auto file = fm.OpenFile(url);
93     if (!file) {
94         return Status::STATUS_FILE_NOT_FOUND;
95     }
96     ResourceFileHeader header;
97     if (file->Read(&header, sizeof(header)) != sizeof(header)) {
98         return Status::STATUS_FILE_READ_ERROR;
99     }
100     if (memcmp(header.magic, ResourceFileHeader {}.magic, sizeof(header.magic)) != 0) {
101         return Status::STATUS_INVALID_FILE;
102     }
103     const auto fileLength = file->GetLength();
104     // check that all the blob headers fit in the file.
105     if ((sizeof(ResourceBlobHeader) * header.resourceCount) > fileLength) {
106         return Status::STATUS_INVALID_FILE;
107     }
108     BASE_NS::vector<ResourceBlobHeader> resourceHeaders;
109     resourceHeaders.resize(header.resourceCount);
110     if (file->Read(resourceHeaders.data(), sizeof(ResourceBlobHeader) * resourceHeaders.size()) !=
111         (sizeof(ResourceBlobHeader) * header.resourceCount)) {
112         return Status::STATUS_INVALID_FILE;
113     }
114     // check that all the offset + size fit in the file.
115     for (const auto& blob : resourceHeaders) {
116         if (((uint64_t(blob.uriOffset) + blob.uriSize) > fileLength)) {
117             return Status::STATUS_INVALID_FILE;
118         }
119         if (((uint64_t(blob.dataOffset) + blob.dataSize) > fileLength)) {
120             return Status::STATUS_INVALID_FILE;
121         }
122     }
123     BASE_NS::vector<BASE_NS::string> resourceUris;
124     for (const auto& blob : resourceHeaders) {
125         file->Seek(blob.uriOffset);
126         BASE_NS::string uri;
127         uri.resize(blob.uriSize);
128         file->Read(uri.data(), blob.uriSize);
129         resourceUris.push_back(BASE_NS::move(uri));
130     }
131 
132     importedFiles_.insert_or_assign(
133         url, ImportFile { BASE_NS::move(file), BASE_NS::move(resourceHeaders), BASE_NS::move(resourceUris) });
134 
135     return Status::STATUS_OK;
136 }
137 
GetResourceUris(BASE_NS::string_view url) const138 BASE_NS::vector<BASE_NS::string> ResourceManager::GetResourceUris(BASE_NS::string_view url) const
139 {
140     BASE_NS::vector<BASE_NS::string> uris;
141     if (auto file = importedFiles_.find(url); file != importedFiles_.end()) {
142         uris = file->second.resourceUris;
143     }
144     for (auto it = importedResources_.begin(); it != importedResources_.end(); ++it) {
145         if (!it->first.starts_with(url)) {
146             continue;
147         }
148         uris.emplace_back(it->first.substr(url.size() + 1U));
149     }
150     return uris;
151 }
152 
GetResourceUris(BASE_NS::string_view url,BASE_NS::Uid resourceType) const153 BASE_NS::vector<BASE_NS::string> ResourceManager::GetResourceUris(
154     BASE_NS::string_view url, BASE_NS::Uid resourceType) const
155 {
156     BASE_NS::vector<BASE_NS::string> uris;
157     if (auto pos = importedFiles_.find(url); pos != importedFiles_.end()) {
158         const auto& file = pos->second;
159         const auto count = BASE_NS::Math::min(file.resourceHeaders.size(), file.resourceUris.size());
160         for (auto i = 0LLU; i < count; ++i) {
161             if (file.resourceHeaders[i].uid == resourceType) {
162                 uris.push_back(file.resourceUris[i]);
163             }
164         }
165     }
166     return uris;
167 }
168 
GetResource(BASE_NS::string_view uri)169 IResource::Ptr ResourceManager::GetResource(BASE_NS::string_view uri)
170 {
171     auto lock = std::lock_guard(mutex_);
172     if (auto pos = importedResources_.find(uri); pos != importedResources_.end()) {
173         return pos->second;
174     }
175 
176     for (const auto& file : importedFiles_) {
177         if (!uri.starts_with(file.first)) {
178             continue;
179         }
180         const auto resourceUri = uri.substr(file.first.size() + 1U);
181         auto& importFile = file.second;
182         auto pos = std::find(importFile.resourceUris.cbegin(), importFile.resourceUris.cend(), resourceUri);
183         if (pos == importFile.resourceUris.cend()) {
184             continue;
185         }
186         auto index = static_cast<size_t>(pos - importFile.resourceUris.cbegin());
187         auto& resHeader = importFile.resourceHeaders[index];
188 
189         auto resource = engine_.GetInterface<CORE_NS::IClassFactory>()->CreateInstance(resHeader.uid);
190         if (!resource) {
191             const auto uidStr = BASE_NS::to_string(resHeader.uid);
192             CORE_LOG_ONCE_W(uidStr, "Unknown resource type '%s'", uidStr.data());
193             return {};
194         }
195         auto serializable = resource->GetInterface<CORE_NS::ISerializable>();
196         if (!serializable) {
197             const auto uidStr = BASE_NS::to_string(resHeader.uid);
198             CORE_LOG_ONCE_W(uidStr, "Cannot import resource type '%s'", uidStr.data());
199             return {};
200         }
201         BASE_NS::ByteArray data(resHeader.dataSize);
202         if (resHeader.dataSize) {
203             importFile.file->Seek(resHeader.dataOffset);
204             importFile.file->Read(data.GetData().data(), resHeader.dataSize);
205         }
206 
207         if (serializable->Import(resourceUri, data.GetData())) {
208             importedResources_.insert({ uri, resource });
209             return resource;
210         }
211     }
212     return {};
213 }
214 
GetResource(BASE_NS::string_view groupUri,BASE_NS::string_view uri)215 IResource::Ptr ResourceManager::GetResource(BASE_NS::string_view groupUri, BASE_NS::string_view uri)
216 {
217     return GetResource(groupUri + '/' + uri);
218 }
219 
Export(BASE_NS::string_view groupUri,BASE_NS::string_view filePath)220 IResourceManager::Status ResourceManager::Export(BASE_NS::string_view groupUri, BASE_NS::string_view filePath)
221 {
222     auto lock = std::lock_guard(mutex_);
223 
224     auto& fm = engine_.GetFileManager();
225     auto file = fm.CreateFile(filePath);
226     if (!file) {
227         return Status::STATUS_FILE_NOT_FOUND;
228     }
229 
230     const auto exportData = GatherExportData(groupUri);
231 
232     ResourceFileHeader header;
233     header.resourceCount = static_cast<uint32_t>(exportData.resourceHeaders.size());
234     auto status = Status::STATUS_OK;
235     if (file->Write(&header, sizeof(header)) != sizeof(header)) {
236         status = Status::STATUS_FILE_WRITE_ERROR;
237     }
238     if (file->Write(exportData.resourceHeaders.data(), sizeof(ResourceBlobHeader) * header.resourceCount) !=
239         (sizeof(ResourceBlobHeader) * header.resourceCount)) {
240         status = Status::STATUS_FILE_WRITE_ERROR;
241     }
242     if (file->Write(exportData.uris.data(), exportData.uris.size()) != exportData.uris.size()) {
243         status = Status::STATUS_FILE_WRITE_ERROR;
244     }
245     if (file->Write(exportData.data.data(), exportData.data.size()) != exportData.data.size()) {
246         status = Status::STATUS_FILE_WRITE_ERROR;
247     }
248     if (status != Status::STATUS_OK) {
249         file.reset();
250         fm.DeleteFile(filePath);
251     }
252     return status;
253 }
254 
AddResource(BASE_NS::string_view groupUri,const IResource::Ptr & resource)255 void ResourceManager::AddResource(BASE_NS::string_view groupUri, const IResource::Ptr& resource)
256 {
257     if (!resource) {
258         return;
259     }
260     auto uri = resource->GetUri();
261     if (uri.empty()) {
262         return;
263     }
264     const auto fullUri = groupUri + '/' + uri;
265     auto lock = std::lock_guard(mutex_);
266     importedResources_.insert_or_assign(fullUri, resource);
267 }
268 
RemoveResource(BASE_NS::string_view groupUri,const IResource::Ptr & resource)269 void ResourceManager::RemoveResource(BASE_NS::string_view groupUri, const IResource::Ptr& resource)
270 {
271     if (!resource) {
272         return;
273     }
274     const auto resourceUri = resource->GetUri();
275     const auto fullUri = groupUri + '/' + resourceUri;
276     auto lock = std::lock_guard(mutex_);
277     importedResources_.erase(fullUri);
278     if (auto file = importedFiles_.find(groupUri); file != importedFiles_.end()) {
279         const auto& resourceUris = file->second.resourceUris;
280         const auto pos = std::find(resourceUris.cbegin(), resourceUris.cend(), resourceUri);
281         if (pos != resourceUris.cend()) {
282             const auto index = pos - resourceUris.cbegin();
283             file->second.resourceHeaders.erase(file->second.resourceHeaders.cbegin() + index);
284             file->second.resourceUris.erase(pos);
285         }
286     }
287 }
288 
RemoveResources(BASE_NS::string_view groupUri)289 void ResourceManager::RemoveResources(BASE_NS::string_view groupUri)
290 {
291     auto lock = std::lock_guard(mutex_);
292     for (auto it = importedResources_.begin(); it != importedResources_.end();) {
293         if (it->first.starts_with(groupUri)) {
294             it = importedResources_.erase(it);
295         } else {
296             ++it;
297         }
298     }
299     importedFiles_.erase(groupUri);
300 }
301 
GatherExportData(BASE_NS::string_view groupUri) const302 ResourceManager::ExportData ResourceManager::GatherExportData(BASE_NS::string_view groupUri) const
303 {
304     ExportData exportData;
305     std::unordered_set<BASE_NS::string> exportedResources;
306     for (auto it = importedResources_.begin(); it != importedResources_.end(); ++it) {
307         if (!it->first.starts_with(groupUri)) {
308             continue;
309         }
310         const auto& imported = *it;
311         const auto uriOffset = static_cast<uint32_t>(exportData.uris.size());
312         const auto resourceUri = imported.first.substr(groupUri.size() + 1U);
313         const auto uriSize = static_cast<uint32_t>(resourceUri.size());
314         exportData.uris.append(resourceUri);
315         exportedResources.insert(BASE_NS::string(resourceUri));
316 
317         const auto& resource = imported.second;
318         const auto uid = resource->GetType();
319         auto serializable = resource->GetInterface<CORE_NS::ISerializable>();
320         if (!serializable) {
321             const auto uidStr = BASE_NS::to_string(uid);
322             CORE_LOG_ONCE_W(uidStr, "Cannot serialize resource type '%s'", uidStr.data());
323             continue;
324         }
325         auto& header = exportData.resourceHeaders.emplace_back();
326         header.uid = uid;
327         header.uriOffset = uriOffset;
328         header.uriSize = uriSize;
329         auto resData = serializable->Export();
330         header.dataOffset = static_cast<uint32_t>(exportData.data.size());
331         header.dataSize = static_cast<uint32_t>(resData.size());
332         exportData.data.append(resData.cbegin(), resData.cend());
333     }
334 
335     // check if groupUri is found from importedFiles_ and copy the resource which were not in importedResources_
336     if (auto pos = importedFiles_.find(groupUri); pos != importedFiles_.cend()) {
337         auto& importedFile = pos->second;
338         for (auto uriIt = importedFile.resourceUris.cbegin(), end = importedFile.resourceUris.cend(); uriIt != end;
339              ++uriIt) {
340             if (exportedResources.count(*uriIt)) {
341                 continue;
342             }
343             const auto index = size_t(uriIt - importedFile.resourceUris.cbegin());
344             const auto& resourceHeader = importedFile.resourceHeaders[index];
345             if (!resourceHeader.dataSize) {
346                 continue;
347             }
348             BASE_NS::ByteArray resourceData(resourceHeader.dataSize);
349             importedFile.file->Seek(resourceHeader.dataOffset);
350             if (importedFile.file->Read(resourceData.GetData().data(), resourceHeader.dataSize) !=
351                 resourceHeader.dataSize) {
352                 continue;
353             }
354 
355             const auto uriOffset = static_cast<uint32_t>(exportData.uris.size());
356             const auto uriSize = static_cast<uint32_t>(uriIt->size());
357             exportData.uris.append(*uriIt);
358             exportedResources.insert(*uriIt);
359 
360             auto& header = exportData.resourceHeaders.emplace_back();
361             header.uid = resourceHeader.uid;
362             header.uriOffset = uriOffset;
363             header.uriSize = uriSize;
364             header.dataOffset = static_cast<uint32_t>(exportData.data.size());
365             header.dataSize = resourceHeader.dataSize;
366             exportData.data.append(resourceData.GetData().cbegin(), resourceData.GetData().cend());
367         }
368     }
369     const auto resourceCount = exportData.resourceHeaders.size();
370     const auto uriOffset =
371         static_cast<uint32_t>(sizeof(ResourceFileHeader) + sizeof(ResourceBlobHeader) * resourceCount);
372     const auto dataOffset = uriOffset + static_cast<uint32_t>(exportData.uris.size());
373     for (auto& blob : exportData.resourceHeaders) {
374         blob.uriOffset += uriOffset;
375         blob.dataOffset += dataOffset;
376     }
377     return exportData;
378 }
379 CORE_END_NAMESPACE()
380