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