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 #ifndef SCENEPLUGIN_ASSETLOADER_H
17 #define SCENEPLUGIN_ASSETLOADER_H
18 
19 #include <scene_plugin/interface/intf_asset_loader.h>
20 
21 #include <3d/intf_graphics_context.h>
22 #include <base/containers/string_view.h>
23 #include <base/containers/unordered_map.h>
24 #include <base/math/mathf.h>
25 #include <render/intf_render_context.h>
26 
27 SCENE_BEGIN_NAMESPACE()
28 
29 class AssetManager;
30 
31 IAssetLoader::Ptr CreateAssetLoader(AssetManager& assetManager, RENDER_NS::IRenderContext& renderContext,
32     CORE3D_NS::IGraphicsContext& graphicsContext, IEntityCollection& ec, BASE_NS::string_view src,
33     BASE_NS::string_view contextUri);
34 
35 struct PathUtil {
36 public:
NormalizePathPathUtil37     static BASE_NS::string NormalizePath(BASE_NS::string_view path)
38     {
39         BASE_NS::string res;
40         if (path[0] != '/') {
41             res.reserve(path.size() + 1);
42             res.push_back('/');
43         } else {
44             res.reserve(path.size());
45         }
46         while (!path.empty()) {
47             if (path[0] == '/') {
48                 if (res.empty()) {
49                     res.push_back('/');
50                 }
51                 path = path.substr(1);
52                 continue;
53             }
54             auto pos = path.find_first_of("/", 0);
55             BASE_NS::string_view sub = path.substr(0, pos);
56             if (sub == ".") {
57                 path = path.substr(pos);
58                 continue;
59             } else if (sub == "..") {
60                 if ((!res.empty()) && (res.back() == '/')) {
61                     res.resize(res.size() - 1);
62                 }
63                 if (auto p = res.find_last_of('/'); BASE_NS::string::npos != p) {
64                     res.resize(p);
65                 } else {
66                     if (res.empty()) {
67                         // trying to back out of root. (ie. invalid path)
68                         return "";
69                     }
70                     res.clear();
71                 }
72                 if (pos == BASE_NS::string::npos) {
73                     res.push_back('/');
74                     break;
75                 }
76             } else {
77                 res.append(sub);
78             }
79             if (pos == BASE_NS::string::npos) {
80                 break;
81             } else {
82                 res.push_back('/');
83             }
84             path = path.substr(pos);
85         }
86         if (res[0] != '/') {
87             res.insert(0, "/");
88         }
89         return res;
90     }
91 
GetParentPathPathUtil92     static BASE_NS::string GetParentPath(BASE_NS::string_view path)
93     {
94         if (path.size() > 1 && path[path.size() - 1] == '/' && path[path.size() - 2] != '/') { // 2 array index
95             // Allow (ignore) trailing '/' for folders.
96             path = path.substr(0, path.size() - 1);
97         }
98 
99         const size_t separatorPos = path.rfind('/');
100         if (separatorPos == BASE_NS::string::npos) {
101             return "";
102         } else {
103             return BASE_NS::string(path.substr(0, separatorPos + 1));
104         }
105     }
106 
ResolvePathPathUtil107     static BASE_NS::string ResolvePath(BASE_NS::string_view parent, BASE_NS::string_view uri, bool allowQueryString)
108     {
109         size_t queryPos = allowQueryString ? BASE_NS::string::npos : uri.find('?');
110         BASE_NS::string_view path = (BASE_NS::string::npos != queryPos) ? uri.substr(0, queryPos) : uri;
111 
112         if (parent.empty()) {
113             return BASE_NS::string(path);
114         }
115 
116         if (path.empty()) {
117             return BASE_NS::string(parent);
118         }
119 
120         if (path[0] == '/') {
121             path = path.substr(1);
122         } else if (path.find("://") != path.npos) {
123             return BASE_NS::string(path);
124         }
125 
126         // NOTE: Resolve always assumes the parent path is a directory even if there is no '/' in the end
127         if (parent.back() == '/') {
128             return parent + path;
129         } else {
130             return parent + "/" + path;
131         }
132     }
133 
GetRelativePathPathUtil134     static BASE_NS::string GetRelativePath(BASE_NS::string_view path, BASE_NS::string_view relativeTo)
135     {
136         // remove the common prefix
137         {
138             int lastSeparator = -1;
139 
140             for (size_t i = 0, iMax = BASE_NS::Math::min(path.size(), relativeTo.size()); i < iMax; ++i) {
141                 if (path[i] != relativeTo[i]) {
142                     break;
143                 }
144                 if (path[i] == '/') {
145                     lastSeparator = int(i);
146                 }
147             }
148 
149             path.remove_prefix(lastSeparator + 1);
150             relativeTo.remove_prefix(lastSeparator + 1);
151         }
152 
153         if (path[1] == ':' && relativeTo[1] == ':' && path[0] != relativeTo[0]) {
154             // files are on different drives
155             return BASE_NS::string(path);
156         }
157 
158         // count and remove the directories left in relative_to
159         auto directoriesCount = 0;
160         {
161             auto nextSeparator = relativeTo.find('/');
162             while (nextSeparator != BASE_NS::string::npos) {
163                 relativeTo.remove_prefix(nextSeparator + 1);
164                 nextSeparator = relativeTo.find('/');
165                 ++directoriesCount;
166             }
167         }
168 
169         BASE_NS::string relativePath = "";
170         for (auto i = 0, iMax = directoriesCount; i < iMax; ++i) {
171             relativePath.append("../");
172         }
173 
174         relativePath.append(path);
175 
176         return relativePath;
177     }
178 
GetFilenamePathUtil179     static BASE_NS::string GetFilename(BASE_NS::string_view path)
180     {
181         if (!path.empty() && path[path.size() - 1] == '/') {
182             // Return a name also for folders.
183             path = path.substr(0, path.size() - 1);
184         }
185 
186         size_t cutPos = path.find_last_of("\\/");
187         if (BASE_NS::string::npos != cutPos) {
188             return BASE_NS::string(path.substr(cutPos + 1));
189         } else {
190             return BASE_NS::string(path);
191         }
192     }
193 
GetExtensionPathUtil194     static BASE_NS::string GetExtension(BASE_NS::string_view path)
195     {
196         size_t fileExtCut = path.rfind('.');
197         if (fileExtCut != BASE_NS::string::npos) {
198             size_t queryCut = path.find('?', fileExtCut);
199             if (queryCut != BASE_NS::string::npos) {
200                 return BASE_NS::string(path.substr(fileExtCut + 1, queryCut));
201             } else {
202                 return BASE_NS::string(path.substr(fileExtCut + 1, queryCut));
203             }
204         }
205         return "";
206     }
207 
GetBaseNamePathUtil208     static BASE_NS::string GetBaseName(BASE_NS::string_view path)
209     {
210         auto filename = GetFilename(path);
211         size_t fileExtCut = filename.rfind(".");
212         if (BASE_NS::string::npos != fileExtCut) {
213             filename.erase(fileExtCut);
214         }
215         return filename;
216     }
217 
GetUriParametersPathUtil218     static BASE_NS::unordered_map<BASE_NS::string, BASE_NS::string> GetUriParameters(BASE_NS::string_view uri)
219     {
220         const size_t queryPos = uri.find('?');
221         if (queryPos != BASE_NS::string::npos) {
222             BASE_NS::unordered_map<BASE_NS::string, BASE_NS::string> params;
223             size_t paramStartPos = queryPos;
224             while (paramStartPos < uri.size()) {
225                 size_t paramValuePos = uri.find('=', paramStartPos + 1);
226                 size_t paramEndPos = uri.find('&', paramStartPos + 1);
227                 if (paramEndPos == BASE_NS::string::npos) {
228                     paramEndPos = uri.size();
229                 }
230                 if (paramValuePos != BASE_NS::string::npos && paramValuePos < paramEndPos) {
231                     auto key = uri.substr(paramStartPos + 1, paramValuePos - paramStartPos - 1);
232                     auto value = uri.substr(paramValuePos + 1, paramEndPos - paramValuePos - 1);
233                     params[key] = value;
234                 } else {
235                     auto key = uri.substr(paramStartPos + 1, paramEndPos - paramStartPos - 1);
236                     params[key] = key;
237                 }
238                 paramStartPos = paramEndPos;
239             }
240             return params;
241         }
242         return {};
243     }
244 
245     static BASE_NS::string ResolveUri(
246         BASE_NS::string_view contextUri, BASE_NS::string_view uri, bool allowQueryString = true)
247     {
248         return ResolvePath(GetParentPath(contextUri), uri, allowQueryString);
249     }
250 };
251 
252 SCENE_END_NAMESPACE()
253 
254 #endif // SCENEPLUGIN_ASSETLOADER_H
255