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 /// \file Sdf/AssetPathResolver.cpp
26
27 #include "pxr/pxr.h"
28 #include "pxr/usd/sdf/assetPathResolver.h"
29 #include "pxr/usd/sdf/debugCodes.h"
30 #include "pxr/usd/sdf/fileFormat.h"
31
32 #include "pxr/base/trace/trace.h"
33 #include "pxr/base/arch/systemInfo.h"
34 #include "pxr/usd/ar/assetInfo.h"
35 #include "pxr/usd/ar/packageUtils.h"
36 #include "pxr/usd/ar/resolver.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/staticTokens.h"
41
42 #include <utility>
43 #include <vector>
44
45 using std::make_pair;
46 using std::pair;
47 using std::string;
48 using std::vector;
49
50 PXR_NAMESPACE_OPEN_SCOPE
51
52 TF_DEFINE_PRIVATE_TOKENS(_Tokens,
53 ((AnonLayerPrefix, "anon:"))
54 ((ArgsDelimiter, ":SDF_FORMAT_ARGS:"))
55 );
56
57 bool
operator ==(const Sdf_AssetInfo & lhs,const Sdf_AssetInfo & rhs)58 operator==(
59 const Sdf_AssetInfo& lhs,
60 const Sdf_AssetInfo& rhs)
61 {
62 return (lhs.identifier == rhs.identifier)
63 && (lhs.resolvedPath == rhs.resolvedPath)
64 && (lhs.resolverContext == rhs.resolverContext)
65 && (lhs.assetInfo == rhs.assetInfo);
66 }
67
68 bool
Sdf_CanCreateNewLayerWithIdentifier(const string & identifier,string * whyNot)69 Sdf_CanCreateNewLayerWithIdentifier(
70 const string& identifier,
71 string* whyNot)
72 {
73 if (identifier.empty()) {
74 if (whyNot) {
75 *whyNot = "cannot use empty identifier.";
76 }
77 return false;
78 }
79
80 if (Sdf_IsAnonLayerIdentifier(identifier)) {
81 if (whyNot) {
82 *whyNot = "cannot use anonymous layer identifier.";
83 }
84 return false;
85 }
86
87 if (Sdf_IdentifierContainsArguments(identifier)) {
88 if (whyNot) {
89 *whyNot = "cannot use arguments in the identifier.";
90 }
91 return false;
92 }
93
94 #if AR_VERSION == 1
95 return ArGetResolver().CanCreateNewLayerWithIdentifier(identifier, whyNot);
96 #else
97 return true;
98 #endif
99 }
100
101 ArResolvedPath
Sdf_ResolvePath(const string & layerPath,ArAssetInfo * assetInfo)102 Sdf_ResolvePath(
103 const string& layerPath,
104 ArAssetInfo* assetInfo)
105 {
106 TRACE_FUNCTION();
107 #if AR_VERSION == 1
108 return ArResolvedPath(
109 ArGetResolver().ResolveWithAssetInfo(layerPath, assetInfo));
110 #else
111 return ArGetResolver().Resolve(layerPath);
112 #endif
113 }
114
115 bool
Sdf_CanWriteLayerToPath(const ArResolvedPath & resolvedPath)116 Sdf_CanWriteLayerToPath(
117 const ArResolvedPath& resolvedPath)
118 {
119 #if AR_VERSION == 1
120 return ArGetResolver().CanWriteLayerToPath(
121 resolvedPath, /* whyNot = */ nullptr);
122 #else
123 return ArGetResolver().CanWriteAssetToPath(
124 resolvedPath, /* whyNot = */ nullptr);
125 #endif
126 }
127
128 ArResolvedPath
Sdf_ComputeFilePath(const string & layerPath,ArAssetInfo * assetInfo)129 Sdf_ComputeFilePath(
130 const string& layerPath,
131 ArAssetInfo* assetInfo)
132 {
133 TRACE_FUNCTION();
134
135 ArResolvedPath resolvedPath = Sdf_ResolvePath(layerPath, assetInfo);
136 if (resolvedPath.empty()) {
137 #if AR_VERSION == 1
138 // If we can't resolve layerPath, it means no layer currently
139 // exists at that location. Compute the local path to figure
140 // out where this layer would go if we were to create a new
141 // one.
142 //
143 // However, we skip this for search paths since the real path
144 // is ambiguous if we can't resolve the search path above.
145 // This is important for layers with search path identifiers,
146 // because otherwise we may compute a confusing real path
147 // for these layers.
148 ArResolver& resolver = ArGetResolver();
149 if (!resolver.IsSearchPath(layerPath)) {
150 resolvedPath = ArResolvedPath(resolver.ComputeLocalPath(layerPath));
151 }
152 #else
153 // If we can't resolve layerPath, it means no layer currently
154 // exists at that location. Use ResolveForNewAsset to figure
155 // out where this layer would go if we were to create a new
156 // one.
157 ArResolver& resolver = ArGetResolver();
158 resolvedPath = resolver.ResolveForNewAsset(layerPath);
159 #endif
160 }
161
162 return resolvedPath;
163 }
164
165 Sdf_AssetInfo*
Sdf_ComputeAssetInfoFromIdentifier(const string & identifier,const string & filePath,const ArAssetInfo & inResolveInfo,const string & fileVersion)166 Sdf_ComputeAssetInfoFromIdentifier(
167 const string& identifier,
168 const string& filePath,
169 const ArAssetInfo& inResolveInfo,
170 const string& fileVersion)
171 {
172 // Allocate a new asset info object. The caller is responsible for
173 // managing the returned object.
174 Sdf_AssetInfo* assetInfo = new Sdf_AssetInfo;
175 ArAssetInfo resolveInfo = inResolveInfo;
176
177 TF_DEBUG(SDF_ASSET).Msg(
178 "Sdf_ComputeAssetInfoFromIdentifier('%s', '%s', '%s')\n",
179 identifier.c_str(),
180 filePath.c_str(),
181 fileVersion.c_str());
182
183 if (Sdf_IsAnonLayerIdentifier(identifier)) {
184 // If the identifier is an anonymous layer identifier, don't
185 // normalize, and also don't set any of the other assetInfo fields.
186 // Anonymous layers do not have repository, overlay, or real paths.
187 assetInfo->identifier = identifier;
188 } else {
189 #if AR_VERSION == 1
190 assetInfo->identifier = ArGetResolver()
191 .ComputeNormalizedPath(identifier);
192 #else
193 assetInfo->identifier = identifier;
194 #endif
195
196 string layerPath, arguments;
197 Sdf_SplitIdentifier(assetInfo->identifier, &layerPath, &arguments);
198 if (filePath.empty()) {
199 assetInfo->resolvedPath =
200 Sdf_ComputeFilePath(layerPath, &resolveInfo);
201 } else {
202 assetInfo->resolvedPath = ArResolvedPath(filePath);
203 }
204
205 #if AR_VERSION == 1
206 assetInfo->resolvedPath = ArResolvedPath(
207 Sdf_CanonicalizeRealPath(assetInfo->resolvedPath));
208
209 ArGetResolver().UpdateAssetInfo(
210 assetInfo->identifier, assetInfo->resolvedPath, fileVersion,
211 &resolveInfo);
212 #else
213 resolveInfo = ArGetResolver().GetAssetInfo(
214 layerPath, assetInfo->resolvedPath);
215 #endif
216 }
217
218 assetInfo->resolverContext =
219 ArGetResolver().GetCurrentContext();
220 assetInfo->assetInfo = resolveInfo;
221
222 TF_DEBUG(SDF_ASSET).Msg("Sdf_ComputeAssetInfoFromIdentifier:\n"
223 " assetInfo->identifier = '%s'\n"
224 " assetInfo->resolvedPath = '%s'\n"
225 " assetInfo->repoPath = '%s'\n"
226 " assetInfo->assetName = '%s'\n"
227 " assetInfo->version = '%s'\n",
228 assetInfo->identifier.c_str(),
229 assetInfo->resolvedPath.GetPathString().c_str(),
230 resolveInfo.repoPath.c_str(),
231 resolveInfo.assetName.c_str(),
232 resolveInfo.version.c_str());
233
234 return assetInfo;
235 }
236
237 string
Sdf_ComputeAnonLayerIdentifier(const string & identifierTemplate,const SdfLayer * layer)238 Sdf_ComputeAnonLayerIdentifier(
239 const string& identifierTemplate,
240 const SdfLayer* layer)
241 {
242 TF_VERIFY(layer);
243 return TfStringPrintf(identifierTemplate.c_str(), layer);
244 }
245
246 bool
Sdf_IsAnonLayerIdentifier(const string & identifier)247 Sdf_IsAnonLayerIdentifier(
248 const string& identifier)
249 {
250 return TfStringStartsWith(identifier,
251 _Tokens->AnonLayerPrefix.GetString());
252 }
253
254 string
Sdf_GetAnonLayerDisplayName(const string & identifier)255 Sdf_GetAnonLayerDisplayName(
256 const string& identifier)
257 {
258 // We want to find the second occurence of ':', traversing from the left,
259 // in our identifier which is of the form anon:0x4rfs23:displayName
260 auto fst = std::find(identifier.begin(), identifier.end(), ':');
261 if (fst == identifier.end()) {
262 return std::string();
263 }
264
265 auto snd = std::find(fst + 1, identifier.end(), ':');
266 if (snd == identifier.end()) {
267 return std::string();
268 }
269
270 return identifier.substr(std::distance(identifier.begin(), snd) + 1);
271 }
272
273 string
Sdf_GetAnonLayerIdentifierTemplate(const string & tag)274 Sdf_GetAnonLayerIdentifierTemplate(
275 const string& tag)
276 {
277 string idTag = tag.empty() ? tag : TfStringTrim(tag);
278 return _Tokens->AnonLayerPrefix.GetString() + "%p" +
279 (idTag.empty() ? idTag : ":" + idTag);
280 }
281
282 string
Sdf_CreateIdentifier(const string & layerPath,const string & arguments)283 Sdf_CreateIdentifier(
284 const string& layerPath,
285 const string& arguments)
286 {
287 return layerPath + arguments;
288 }
289
290 // XXX: May need to escape characters in the arguments map
291 // when encoding arguments and unescape then when decoding?
292 static string
Sdf_EncodeArguments(const SdfLayer::FileFormatArguments & args)293 Sdf_EncodeArguments(
294 const SdfLayer::FileFormatArguments& args)
295 {
296 const char* delimiter = _Tokens->ArgsDelimiter.GetText();
297 string argString;
298 for (const auto& entry : args) {
299 argString += delimiter;
300 argString += entry.first;
301 argString += '=';
302 argString += entry.second;
303
304 delimiter = "&";
305 }
306
307 return argString;
308 }
309
310 static bool
Sdf_DecodeArguments(const string & argString,SdfLayer::FileFormatArguments * args)311 Sdf_DecodeArguments(
312 const string& argString,
313 SdfLayer::FileFormatArguments* args)
314 {
315 if (argString.empty() || argString.size() == _Tokens->ArgsDelimiter.size()) {
316 args->clear();
317 return true;
318 }
319
320 const size_t argStringLength = argString.size();
321 if (!TF_VERIFY(argStringLength > _Tokens->ArgsDelimiter.size())) {
322 return false;
323 }
324
325 SdfLayer::FileFormatArguments tmpArgs;
326
327 size_t startIdx = _Tokens->ArgsDelimiter.size();
328 while (startIdx < argStringLength) {
329 const size_t eqIdx = argString.find('=', startIdx);
330 if (eqIdx == string::npos) {
331 TF_CODING_ERROR(
332 "Invalid file format arguments: %s", argString.c_str());
333 return false;
334 }
335
336 const string key = argString.substr(startIdx, eqIdx - startIdx);
337 startIdx = eqIdx + 1;
338
339 const size_t sepIdx = argString.find('&', startIdx);
340 if (sepIdx == string::npos) {
341 tmpArgs[key] = argString.substr(startIdx);
342 break;
343 }
344 else {
345 tmpArgs[key] = argString.substr(startIdx, sepIdx - startIdx);
346 startIdx = sepIdx + 1;
347 }
348 }
349
350 args->swap(tmpArgs);
351 return true;
352 }
353
354 string
Sdf_CreateIdentifier(const string & layerPath,const SdfLayer::FileFormatArguments & arguments)355 Sdf_CreateIdentifier(
356 const string& layerPath,
357 const SdfLayer::FileFormatArguments& arguments)
358 {
359 return layerPath + Sdf_EncodeArguments(arguments);
360 }
361
362 bool
Sdf_SplitIdentifier(const string & identifier,string * layerPath,string * arguments)363 Sdf_SplitIdentifier(
364 const string& identifier,
365 string* layerPath,
366 string* arguments)
367 {
368 size_t argPos = identifier.find(_Tokens->ArgsDelimiter.GetString());
369 if (argPos == string::npos) {
370 argPos = identifier.size();
371 }
372
373 *layerPath = string(identifier, 0, argPos);
374 *arguments = string(identifier, argPos, string::npos);
375 return true;
376 }
377
378 bool
Sdf_SplitIdentifier(const string & identifier,string * layerPath,SdfLayer::FileFormatArguments * args)379 Sdf_SplitIdentifier(
380 const string& identifier,
381 string* layerPath,
382 SdfLayer::FileFormatArguments* args)
383 {
384 string tmpLayerPath, tmpArgs;
385 if (!Sdf_SplitIdentifier(identifier, &tmpLayerPath, &tmpArgs)) {
386 return false;
387 }
388
389 if (!Sdf_DecodeArguments(tmpArgs, args)) {
390 return false;
391 }
392
393 layerPath->swap(tmpLayerPath);
394 return true;
395 }
396
397 bool
Sdf_IdentifierContainsArguments(const string & identifier)398 Sdf_IdentifierContainsArguments(
399 const string& identifier)
400 {
401 return identifier.find(_Tokens->ArgsDelimiter.GetString()) != string::npos;
402 }
403
404 string
Sdf_GetLayerDisplayName(const string & identifier)405 Sdf_GetLayerDisplayName(
406 const string& identifier)
407 {
408
409 string layerPath, arguments;
410 Sdf_SplitIdentifier(identifier, &layerPath, &arguments);
411
412 if (Sdf_IsAnonLayerIdentifier(layerPath)) {
413 return Sdf_GetAnonLayerDisplayName(layerPath);
414 }
415
416 // If the layer path is a package-relative path, we want
417 // the basename of the outermost package combined with
418 // the packaged path. For example, given:
419 // "/tmp/asset.package[sub/dir/file.sdf]",
420 // we want:
421 // "asset.package[sub/dir/file.sdf]".
422 if (ArIsPackageRelativePath(layerPath)) {
423 std::pair<std::string, std::string> packagePath =
424 ArSplitPackageRelativePathOuter(layerPath);
425 packagePath.first = TfGetBaseName(packagePath.first);
426 return ArJoinPackageRelativePath(packagePath);
427 }
428
429 return TfGetBaseName(layerPath);
430 }
431
432 string
Sdf_GetExtension(const string & identifier)433 Sdf_GetExtension(
434 const string& identifier)
435 {
436 // Split the identifier to get the layer asset path without
437 // any file format arguments.
438 string assetPath;
439 std::string dummyArgs;
440 Sdf_SplitIdentifier(identifier, &assetPath, &dummyArgs);
441
442 if (Sdf_IsAnonLayerIdentifier(assetPath)) {
443 // Strip off the "anon:0x...:" portion of the anonymous layer
444 // identifier and look for an extension in the remainder. This
445 // allows clients to create anonymous layers using tags that
446 // match their asset path scheme and retrieve the extension
447 // via ArResolver.
448 assetPath = Sdf_GetAnonLayerDisplayName(assetPath);
449 }
450
451 // XXX: If the asset path is a dot file (e.g. ".sdf"), we append
452 // a temporary name so that the path we pass to Ar is not
453 // interpreted as a directory name. This is legacy behavior that
454 // should be fixed.
455 if (!assetPath.empty() && assetPath[0] == '.') {
456 assetPath = "temp_file_name" + assetPath;
457 }
458
459 return ArGetResolver().GetExtension(assetPath);
460 }
461
462 bool
Sdf_IsPackageOrPackagedLayer(const SdfLayerHandle & layer)463 Sdf_IsPackageOrPackagedLayer(
464 const SdfLayerHandle& layer)
465 {
466 return Sdf_IsPackageOrPackagedLayer(
467 layer->GetFileFormat(), layer->GetIdentifier());
468 }
469
470 bool
Sdf_IsPackageOrPackagedLayer(const SdfFileFormatConstPtr & fileFormat,const std::string & identifier)471 Sdf_IsPackageOrPackagedLayer(
472 const SdfFileFormatConstPtr& fileFormat,
473 const std::string& identifier)
474 {
475 return fileFormat->IsPackage() || ArIsPackageRelativePath(identifier);
476 }
477
478 string
Sdf_CanonicalizeRealPath(const string & realPath)479 Sdf_CanonicalizeRealPath(
480 const string& realPath)
481 {
482 // Use the given realPath as-is if it's a relative path, otherwise
483 // use TfAbsPath to compute a platform-dependent real path.
484 //
485 // XXX: This method needs to be re-examined as we move towards a
486 // less filesystem-dependent implementation.
487
488 // If realPath is a package-relative path, absolutize just the
489 // outer path; the packaged path has a specific format defined in
490 // Ar that we don't want to modify.
491 if (ArIsPackageRelativePath(realPath)) {
492 pair<string, string> packagePath =
493 ArSplitPackageRelativePathOuter(realPath);
494 return TfIsRelativePath(packagePath.first) ?
495 realPath : ArJoinPackageRelativePath(
496 TfAbsPath(packagePath.first), packagePath.second);
497 }
498
499 return TfIsRelativePath(realPath) ? realPath : TfAbsPath(realPath);
500 }
501
502 PXR_NAMESPACE_CLOSE_SCOPE
503