1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24
25 #include "pxr/pxr.h"
26 #include "pxr/usd/ar/defaultResolver.h"
27
28 #include "pxr/usd/ar/defaultResolverContext.h"
29 #include "pxr/usd/ar/defineResolver.h"
30 #include "pxr/usd/ar/filesystemAsset.h"
31 #include "pxr/usd/ar/assetInfo.h"
32 #include "pxr/usd/ar/resolverContext.h"
33
34 #include "pxr/base/arch/fileSystem.h"
35 #include "pxr/base/arch/systemInfo.h"
36 #include "pxr/base/tf/getenv.h"
37 #include "pxr/base/tf/fileUtils.h"
38 #include "pxr/base/tf/pathUtils.h"
39 #include "pxr/base/tf/staticData.h"
40 #include "pxr/base/tf/stringUtils.h"
41 #include "pxr/base/vt/value.h"
42
43 #include <tbb/concurrent_hash_map.h>
44
45 PXR_NAMESPACE_OPEN_SCOPE
46
47 AR_DEFINE_RESOLVER(ArDefaultResolver, ArResolver);
48
49 static bool
_IsFileRelative(const std::string & path)50 _IsFileRelative(const std::string& path) {
51 return path.find("./") == 0 || path.find("../") == 0;
52 }
53
54 static TfStaticData<std::vector<std::string>> _SearchPath;
55
56 struct ArDefaultResolver::_Cache
57 {
58 using _PathToResolvedPathMap =
59 tbb::concurrent_hash_map<std::string, std::string>;
60 _PathToResolvedPathMap _pathToResolvedPathMap;
61 };
62
ArDefaultResolver()63 ArDefaultResolver::ArDefaultResolver()
64 {
65 std::vector<std::string> searchPath = *_SearchPath;
66
67 const std::string envPath = TfGetenv("PXR_AR_DEFAULT_SEARCH_PATH");
68 if (!envPath.empty()) {
69 const std::vector<std::string> envSearchPath =
70 TfStringTokenize(envPath, ARCH_PATH_LIST_SEP);
71 searchPath.insert(
72 searchPath.end(), envSearchPath.begin(), envSearchPath.end());
73 }
74
75 _fallbackContext = ArDefaultResolverContext(searchPath);
76 }
77
~ArDefaultResolver()78 ArDefaultResolver::~ArDefaultResolver()
79 {
80 }
81
82 void
SetDefaultSearchPath(const std::vector<std::string> & searchPath)83 ArDefaultResolver::SetDefaultSearchPath(
84 const std::vector<std::string>& searchPath)
85 {
86 *_SearchPath = searchPath;
87 }
88
89 void
ConfigureResolverForAsset(const std::string & path)90 ArDefaultResolver::ConfigureResolverForAsset(const std::string& path)
91 {
92 _defaultContext = CreateDefaultContextForAsset(path);
93 }
94
95 bool
IsRelativePath(const std::string & path)96 ArDefaultResolver::IsRelativePath(const std::string& path)
97 {
98 return (!path.empty() && TfIsRelativePath(path));
99 }
100
101 bool
IsRepositoryPath(const std::string & path)102 ArDefaultResolver::IsRepositoryPath(const std::string& path)
103 {
104 return false;
105 }
106
107 std::string
AnchorRelativePath(const std::string & anchorPath,const std::string & path)108 ArDefaultResolver::AnchorRelativePath(
109 const std::string& anchorPath,
110 const std::string& path)
111 {
112 if (TfIsRelativePath(anchorPath) ||
113 !IsRelativePath(path)) {
114 return path;
115 }
116
117 // Ensure we are using forward slashes and not back slashes.
118 std::string forwardPath = anchorPath;
119 std::replace(forwardPath.begin(), forwardPath.end(), '\\', '/');
120
121 // If anchorPath does not end with a '/', we assume it is specifying
122 // a file, strip off the last component, and anchor the path to that
123 // directory.
124 const std::string anchoredPath = TfStringCatPaths(
125 TfStringGetBeforeSuffix(forwardPath, '/'), path);
126 return TfNormPath(anchoredPath);
127 }
128
129 bool
IsSearchPath(const std::string & path)130 ArDefaultResolver::IsSearchPath(const std::string& path)
131 {
132 return IsRelativePath(path) && !_IsFileRelative(path);
133 }
134
135 std::string
GetExtension(const std::string & path)136 ArDefaultResolver::GetExtension(const std::string& path)
137 {
138 return TfGetExtension(path);
139 }
140
141 std::string
ComputeNormalizedPath(const std::string & path)142 ArDefaultResolver::ComputeNormalizedPath(const std::string& path)
143 {
144 return TfNormPath(path);
145 }
146
147 std::string
ComputeRepositoryPath(const std::string & path)148 ArDefaultResolver::ComputeRepositoryPath(const std::string& path)
149 {
150 return std::string();
151 }
152
153 static std::string
_Resolve(const std::string & anchorPath,const std::string & path)154 _Resolve(
155 const std::string& anchorPath,
156 const std::string& path)
157 {
158 std::string resolvedPath = path;
159 if (!anchorPath.empty()) {
160 // XXX - CLEANUP:
161 // It's tempting to use AnchorRelativePath to combine the two
162 // paths here, but that function's file-relative anchoring
163 // causes consumers to break.
164 //
165 // Ultimately what we should do is specify whether anchorPath
166 // in both Resolve and AnchorRelativePath can be files or directories
167 // and fix up all the callers to accommodate this.
168 resolvedPath = TfStringCatPaths(anchorPath, path);
169 }
170 return TfPathExists(resolvedPath) ? resolvedPath : std::string();
171 }
172
173 std::string
_ResolveNoCache(const std::string & path)174 ArDefaultResolver::_ResolveNoCache(const std::string& path)
175 {
176 if (path.empty()) {
177 return path;
178 }
179
180 if (IsRelativePath(path)) {
181 // First try to resolve relative paths against the current
182 // working directory.
183 std::string resolvedPath = _Resolve(ArchGetCwd(), path);
184 if (!resolvedPath.empty()) {
185 return resolvedPath;
186 }
187
188 // If that fails and the path is a search path, try to resolve
189 // against each directory in the specified search paths.
190 if (IsSearchPath(path)) {
191 const ArDefaultResolverContext* contexts[2] =
192 {_GetCurrentContext(), &_fallbackContext};
193 for (const ArDefaultResolverContext* ctx : contexts) {
194 if (ctx) {
195 for (const auto& searchPath : ctx->GetSearchPath()) {
196 resolvedPath = _Resolve(searchPath, path);
197 if (!resolvedPath.empty()) {
198 return resolvedPath;
199 }
200 }
201 }
202 }
203 }
204
205 return std::string();
206 }
207
208 return _Resolve(std::string(), path);
209 }
210
211 std::string
Resolve(const std::string & path)212 ArDefaultResolver::Resolve(const std::string& path)
213 {
214 return ResolveWithAssetInfo(path, /* assetInfo = */ nullptr);
215 }
216
217 std::string
ResolveWithAssetInfo(const std::string & path,ArAssetInfo * assetInfo)218 ArDefaultResolver::ResolveWithAssetInfo(
219 const std::string& path,
220 ArAssetInfo* assetInfo)
221 {
222 if (path.empty()) {
223 return path;
224 }
225
226 if (_CachePtr currentCache = _GetCurrentCache()) {
227 _Cache::_PathToResolvedPathMap::accessor accessor;
228 if (currentCache->_pathToResolvedPathMap.insert(
229 accessor, std::make_pair(path, std::string()))) {
230 accessor->second = _ResolveNoCache(path);
231 }
232 return accessor->second;
233 }
234
235 return _ResolveNoCache(path);
236 }
237
238 std::string
ComputeLocalPath(const std::string & path)239 ArDefaultResolver::ComputeLocalPath(const std::string& path)
240 {
241 return path.empty() ? path : TfAbsPath(path);
242 }
243
244 void
UpdateAssetInfo(const std::string & identifier,const std::string & filePath,const std::string & fileVersion,ArAssetInfo * resolveInfo)245 ArDefaultResolver::UpdateAssetInfo(
246 const std::string& identifier,
247 const std::string& filePath,
248 const std::string& fileVersion,
249 ArAssetInfo* resolveInfo)
250 {
251 if (resolveInfo) {
252 if (!fileVersion.empty()) {
253 resolveInfo->version = fileVersion;
254 }
255 }
256 }
257
258 VtValue
GetModificationTimestamp(const std::string & path,const std::string & resolvedPath)259 ArDefaultResolver::GetModificationTimestamp(
260 const std::string& path,
261 const std::string& resolvedPath)
262 {
263 // Since the default resolver always resolves paths to local
264 // paths, we can just look at the mtime of the file indicated
265 // by resolvedPath.
266 double time;
267 if (ArchGetModificationTime(resolvedPath.c_str(), &time)) {
268 return VtValue(time);
269 }
270 return VtValue();
271 }
272
273 bool
FetchToLocalResolvedPath(const std::string & path,const std::string & resolvedPath)274 ArDefaultResolver::FetchToLocalResolvedPath(
275 const std::string& path,
276 const std::string& resolvedPath)
277 {
278 // ArDefaultResolver always resolves paths to a file on the
279 // local filesystem. Because of this, we know the asset specified
280 // by the given path already exists on the filesystem at
281 // resolvedPath, so no further data fetching is needed.
282 return true;
283 }
284
285 std::shared_ptr<ArAsset>
OpenAsset(const std::string & resolvedPath)286 ArDefaultResolver::OpenAsset(
287 const std::string& resolvedPath)
288 {
289 FILE* f = ArchOpenFile(resolvedPath.c_str(), "rb");
290 if (!f) {
291 return nullptr;
292 }
293
294 return std::shared_ptr<ArAsset>(new ArFilesystemAsset(f));
295 }
296
297 bool
CreatePathForLayer(const std::string & path)298 ArDefaultResolver::CreatePathForLayer(
299 const std::string& path)
300 {
301 const std::string layerDir = TfGetPathName(path);
302 return layerDir.empty() || TfIsDir(layerDir) || TfMakeDirs(layerDir);
303 }
304
305 bool
CanWriteLayerToPath(const std::string & path,std::string * whyNot)306 ArDefaultResolver::CanWriteLayerToPath(
307 const std::string& path,
308 std::string* whyNot)
309 {
310 return true;
311 }
312
313 bool
CanCreateNewLayerWithIdentifier(const std::string & identifier,std::string * whyNot)314 ArDefaultResolver::CanCreateNewLayerWithIdentifier(
315 const std::string& identifier,
316 std::string* whyNot)
317 {
318 return true;
319 }
320
321 ArResolverContext
CreateDefaultContext()322 ArDefaultResolver::CreateDefaultContext()
323 {
324 return _defaultContext;
325 }
326
327 ArResolverContext
CreateDefaultContextForAsset(const std::string & filePath)328 ArDefaultResolver::CreateDefaultContextForAsset(
329 const std::string& filePath)
330 {
331 if (filePath.empty()){
332 return ArResolverContext(ArDefaultResolverContext());
333 }
334
335 std::string assetDir = TfGetPathName(TfAbsPath(filePath));
336
337 return ArResolverContext(ArDefaultResolverContext(
338 std::vector<std::string>(1, assetDir)));
339 }
340
341 void
RefreshContext(const ArResolverContext & context)342 ArDefaultResolver::RefreshContext(const ArResolverContext& context)
343 {
344 }
345
346 ArResolverContext
GetCurrentContext()347 ArDefaultResolver::GetCurrentContext()
348 {
349 const ArDefaultResolverContext* ctx = _GetCurrentContext();
350 return ctx ? ArResolverContext(*ctx) : ArResolverContext();
351 }
352
353 void
BeginCacheScope(VtValue * cacheScopeData)354 ArDefaultResolver::BeginCacheScope(
355 VtValue* cacheScopeData)
356 {
357 _threadCache.BeginCacheScope(cacheScopeData);
358 }
359
360 void
EndCacheScope(VtValue * cacheScopeData)361 ArDefaultResolver::EndCacheScope(
362 VtValue* cacheScopeData)
363 {
364 _threadCache.EndCacheScope(cacheScopeData);
365 }
366
367 ArDefaultResolver::_CachePtr
_GetCurrentCache()368 ArDefaultResolver::_GetCurrentCache()
369 {
370 return _threadCache.GetCurrentCache();
371 }
372
373 void
BindContext(const ArResolverContext & context,VtValue * bindingData)374 ArDefaultResolver::BindContext(
375 const ArResolverContext& context,
376 VtValue* bindingData)
377 {
378 const ArDefaultResolverContext* ctx =
379 context.Get<ArDefaultResolverContext>();
380
381 _ContextStack& contextStack = _threadContextStack.local();
382 contextStack.push_back(ctx);
383 }
384
385 void
UnbindContext(const ArResolverContext & context,VtValue * bindingData)386 ArDefaultResolver::UnbindContext(
387 const ArResolverContext& context,
388 VtValue* bindingData)
389 {
390 _ContextStack& contextStack = _threadContextStack.local();
391 if (contextStack.empty()) {
392 TF_CODING_ERROR(
393 "No context was bound, cannot unbind context: %s",
394 context.GetDebugString().c_str());
395 }
396
397 if (!contextStack.empty()) {
398 contextStack.pop_back();
399 }
400 }
401
402 const ArDefaultResolverContext*
_GetCurrentContext()403 ArDefaultResolver::_GetCurrentContext()
404 {
405 _ContextStack& contextStack = _threadContextStack.local();
406 return contextStack.empty() ? nullptr : contextStack.back();
407 }
408
409 PXR_NAMESPACE_CLOSE_SCOPE
410