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