Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
Package.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.Diagnostics;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using SharpYaml;
11 using SiliconStudio.Assets.Analysis;
12 using SiliconStudio.Assets.Diagnostics;
13 using SiliconStudio.Assets.Templates;
14 using SiliconStudio.Core;
15 using SiliconStudio.Core.Diagnostics;
16 using SiliconStudio.Core.IO;
17 using SiliconStudio.Core.Reflection;
18 using SiliconStudio.Core.Storage;
19 
20 namespace SiliconStudio.Assets
21 {
22  /// <summary>
23  /// A package managing assets.
24  /// </summary>
25  [DataContract("Package")]
26  [AssetFileExtension(PackageFileExtension)]
27  [DebuggerDisplay("Id: {Id}, Name: {Meta.Name}, Version: {Meta.Version}, Assets [{Assets.Count}]")]
28  public sealed class Package : Asset, IFileSynchronizable
29  {
30  private readonly PackageAssetCollection assets;
31 
32  private readonly AssetItemCollection temporaryAssets;
33 
34  private readonly List<PackageReference> localDependencies;
35 
36  private readonly List<UDirectory> explicitFolders;
37 
38  private PackageSession session;
39 
40  private UFile packagePath;
41  private bool isDirty;
42 
43  /// <summary>
44  /// The file extension used for <see cref="Package"/>.
45  /// </summary>
46  public const string PackageFileExtension = ".pdxpkg";
47 
48  /// <summary>
49  /// Occurs when an asset dirty changed occured.
50  /// </summary>
51  public event Action<Asset> AssetDirtyChanged;
52 
53  /// <summary>
54  /// Initializes a new instance of the <see cref="Package"/> class.
55  /// </summary>
56  public Package()
57  {
58  localDependencies = new List<PackageReference>();
59  temporaryAssets = new AssetItemCollection();
60  assets = new PackageAssetCollection(this);
61  explicitFolders = new List<UDirectory>();
62  Bundles = new BundleCollection(this);
63  Meta = new PackageMeta();
64  TemplateFolders = new List<TemplateFolder>();
65  Templates = new List<TemplateDescription>();
66  Profiles = new PackageProfileCollection();
67  IsDirty = true;
68  }
69 
70  /// <summary>
71  /// Gets or sets a value indicating whether this package is a system package.
72  /// </summary>
73  /// <value><c>true</c> if this package is a system package; otherwise, <c>false</c>.</value>
74  [DataMemberIgnore]
75  public bool IsSystem { get; internal set; }
76 
77  /// <summary>
78  /// Gets or sets the metadata associated with this package.
79  /// </summary>
80  /// <value>The meta.</value>
81  [DataMember(10)]
82  public PackageMeta Meta { get; set; }
83 
84  /// <summary>
85  /// Gets the local package dependencies used by this package (only valid for local references). Global dependencies
86  /// are defined through the <see cref="Meta"/> property in <see cref="PackageMeta.Dependencies"/>
87  /// </summary>
88  /// <value>The package local dependencies.</value>
89  [DataMember(30)]
90  public List<PackageReference> LocalDependencies
91  {
92  get
93  {
94  return localDependencies;
95  }
96  }
97 
98  /// <summary>
99  /// Gets the profiles.
100  /// </summary>
101  /// <value>The profiles.</value>
102  [DataMember(50)]
103  public PackageProfileCollection Profiles { get; private set; }
104 
105  /// <summary>
106  /// Gets or sets the list of folders that are explicitly created but contains no assets.
107  /// </summary>
108  [DataMember(70)]
109  public List<UDirectory> ExplicitFolders
110  {
111  get
112  {
113  return explicitFolders;
114  }
115  }
116 
117  /// <summary>
118  /// Gets the bundles defined for this package.
119  /// </summary>
120  /// <value>The bundles.</value>
121  [DataMember(80)]
122  public BundleCollection Bundles { get; private set; }
123 
124  /// <summary>
125  /// Gets the template folders.
126  /// </summary>
127  /// <value>The template folders.</value>
128  [DataMember(90)]
129  public List<TemplateFolder> TemplateFolders { get; private set; }
130 
131  /// <summary>
132  /// Gets the loaded templates from the <see cref="TemplateFolders"/>
133  /// </summary>
134  /// <value>The templates.</value>
135  [DataMemberIgnore]
136  public List<TemplateDescription> Templates { get; private set; }
137 
138  /// <summary>
139  /// Gets the assets stored in this package.
140  /// </summary>
141  /// <value>The assets.</value>
142  [DataMemberIgnore]
143  public PackageAssetCollection Assets
144  {
145  get
146  {
147  return assets;
148  }
149  }
150 
151  /// <summary>
152  /// Gets the temporary assets list loaded from disk before they are going into <see cref="Assets"/>.
153  /// </summary>
154  /// <value>The temporary assets.</value>
155  [DataMemberIgnore]
156  public AssetItemCollection TemporaryAssets
157  {
158  get
159  {
160  return temporaryAssets;
161  }
162  }
163 
164  /// <summary>
165  /// Gets the path to the package file. May be null if the package was not loaded or saved.
166  /// </summary>
167  /// <value>The package path.</value>
168  [DataMemberIgnore]
169  public UFile FullPath
170  {
171  get
172  {
173  return packagePath;
174  }
175  set
176  {
177  SetPackagePath(value, true);
178  }
179  }
180 
181  /// <summary>
182  /// Gets or sets a value indicating whether this instance has been modified since last saving.
183  /// </summary>
184  /// <value><c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
185  [DataMemberIgnore]
186  public bool IsDirty
187  {
188  get
189  {
190  return isDirty;
191  }
192  set
193  {
194  isDirty = value;
195  OnAssetDirtyChanged(this);
196  }
197  }
198 
199  /// <summary>
200  /// Gets the top directory of this package on the local disk.
201  /// </summary>
202  /// <value>The top directory.</value>
203  [DataMemberIgnore]
204  public UDirectory RootDirectory
205  {
206  get
207  {
208  return FullPath != null ? FullPath.GetParent() : null;
209  }
210  }
211 
212  /// <summary>
213  /// Gets the session.
214  /// </summary>
215  /// <value>The session.</value>
216  /// <exception cref="System.InvalidOperationException">Cannot attach a package to more than one session</exception>
217  [DataMemberIgnore]
218  public PackageSession Session
219  {
220  get
221  {
222  return session;
223  }
224  internal set
225  {
226  if (value != null && session != null && !ReferenceEquals(session, value))
227  {
228  throw new InvalidOperationException("Cannot attach a package to more than one session");
229  }
230  session = value;
231  IsIdLocked = (session != null);
232  }
233  }
234 
235  /// <summary>
236  /// Adds an exiting project to this package.
237  /// </summary>
238  /// <param name="pathToMsproj">The path to msproj.</param>
239  /// <returns>LoggerResult.</returns>
240  public LoggerResult AddExitingProject(UFile pathToMsproj)
241  {
242  var logger = new LoggerResult();
243  AddExitingProject(pathToMsproj, logger);
244  return logger;
245  }
246 
247  /// <summary>
248  /// Adds an exiting project to this package.
249  /// </summary>
250  /// <param name="pathToMsproj">The path to msproj.</param>
251  /// <param name="logger">The logger.</param>
252  public void AddExitingProject(UFile pathToMsproj, LoggerResult logger)
253  {
254  if (pathToMsproj == null) throw new ArgumentNullException("pathToMsproj");
255  if (logger == null) throw new ArgumentNullException("logger");
256  if (!pathToMsproj.IsAbsolute) throw new ArgumentException("Expecting relative path", "pathToMsproj");
257 
258  try
259  {
260  // Load a project without specifying a platform to make sure we get the correct platform type
261  var msProject = VSProjectHelper.LoadProject(pathToMsproj, platform: "NoPlatform");
262 
263  var projectType = VSProjectHelper.GetProjectTypeFromProject(msProject);
264  if (!projectType.HasValue)
265  {
266  logger.Error("This project is not a project created with the editor");
267  }
268  else
269  {
270  var platformType = VSProjectHelper.GetPlatformTypeFromProject(msProject) ?? PlatformType.Shared;
271 
272  var projectReference = new ProjectReference()
273  {
274  Id = VSProjectHelper.GetProjectGuid(msProject),
275  Location = pathToMsproj.MakeRelative(RootDirectory),
276  Type = projectType.Value
277  };
278 
279  // Add the ProjectReference only for the compatible profiles (same platform or no platform)
280  foreach (var profile in Profiles.Where(profile => platformType == profile.Platform))
281  {
282  profile.ProjectReferences.Add(projectReference);
283  }
284  }
285  }
286  catch (Exception ex)
287  {
288  logger.Error("Unexpected exception while loading project [{0}]", ex, pathToMsproj);
289  }
290  }
291 
292  internal UDirectory GetDefaultAssetFolder()
293  {
294  if (Profiles.Contains(PlatformType.Shared))
295  {
296  var sharedProfile = Profiles[PlatformType.Shared];
297  var folder = sharedProfile.AssetFolders.FirstOrDefault();
298  if (folder != null && folder.Path != null)
299  {
300  return folder.Path;
301  }
302  }
303 
304  return "Assets/" + PackageProfile.SharedName;
305  }
306 
307 
308  /// <summary>
309  /// Deep clone this package.
310  /// </summary>
311  /// <param name="deepCloneAsset">if set to <c>true</c> assets will stored in this package will be also deeply cloned.</param>
312  /// <returns>The package cloned.</returns>
313  public Package Clone(bool deepCloneAsset)
314  {
315  // Use a new ShadowRegistry to copy override parameters
316  // Clone this asset
317  var package = (Package)AssetCloner.Clone(this);
318  package.FullPath = FullPath;
319  foreach (var asset in Assets)
320  {
321  var newAsset = deepCloneAsset ? (Asset)AssetCloner.Clone(asset.Asset) : asset.Asset;
322  var assetItem = new AssetItem(asset.Location, newAsset);
323  package.Assets.Add(assetItem);
324  }
325  return package;
326  }
327 
328  /// <summary>
329  /// Sets the package path.
330  /// </summary>
331  /// <param name="newPath">The new path.</param>
332  /// <param name="copyAssets">if set to <c>true</c> assets will be copied relatively to the new location.</param>
333  public void SetPackagePath(UFile newPath, bool copyAssets = true)
334  {
335  var previousPath = packagePath;
336  var previousRootDirectory = RootDirectory;
337  packagePath = newPath;
338  if (packagePath != null && !packagePath.IsAbsolute)
339  {
340  packagePath = UPath.Combine(Environment.CurrentDirectory, packagePath);
341  }
342 
343  if (copyAssets && packagePath != previousPath)
344  {
345  // Update source folders
346  var currentRootDirectory = RootDirectory;
347  if (previousRootDirectory != null && currentRootDirectory != null)
348  {
349  foreach (var profile in Profiles)
350  {
351  foreach (var sourceFolder in profile.AssetFolders)
352  {
353  if (sourceFolder.Path.IsAbsolute)
354  {
355  var relativePath = sourceFolder.Path.MakeRelative(previousRootDirectory);
356  sourceFolder.Path = UPath.Combine(currentRootDirectory, relativePath);
357  }
358  }
359  }
360  }
361 
362  foreach (var asset in Assets)
363  {
364  asset.IsDirty = true;
365  }
366  IsDirty = true;
367  }
368  }
369 
370  internal void OnAssetDirtyChanged(Asset asset)
371  {
372  Action<Asset> handler = AssetDirtyChanged;
373  if (handler != null) handler(asset);
374  }
375 
376  /// <summary>
377  /// Saves this package and all dirty assets. See remarks.
378  /// </summary>
379  /// <param name="saveAllAssets">if set to <c>true</c> [save all assets].</param>
380  /// <returns>LoggerResult.</returns>
381  /// <remarks>When calling this method directly, it does not handle moving assets between packages.
382  /// Call <see cref="PackageSession.Save"/> instead.
383  /// </remarks>
385  {
386  var result = new LoggerResult();
387  Save(result);
388  return result;
389  }
390 
391  /// <summary>
392  /// Saves this package and all dirty assets. See remarks.
393  /// </summary>
394  /// <param name="log">The log.</param>
395  /// <exception cref="System.ArgumentNullException">log</exception>
396  /// <remarks>When calling this method directly, it does not handle moving assets between packages.
397  /// Call <see cref="PackageSession.Save" /> instead.</remarks>
398  public void Save(ILogger log)
399  {
400  if (log == null) throw new ArgumentNullException("log");
401 
402  if (FullPath == null)
403  {
404  log.Error(this, null, AssetMessageCode.PackageCannotSave, "null");
405  return;
406  }
407 
408  // Use relative paths when saving
409  var analysis = new PackageAnalysis(this, new PackageAnalysisParameters()
410  {
411  SetDirtyFlagOnAssetWhenFixingUFile = false,
412  ConvertUPathTo = UPathType.Relative,
413  IsProcessingUPaths = true
414  });
415  analysis.Run(log);
416 
417  try
418  {
419  // Update source folders
420  UpdateSourceFolders();
421 
422  if (IsDirty)
423  {
424  try
425  {
426  // Notifies the dependency manager that a package with the specified path is being saved
427  if (session != null && session.HasDependencyManager)
428  {
429  session.DependencyManager.AddFileBeingSaveDuringSessionSave(FullPath);
430  }
431 
432  AssetSerializer.Save(FullPath, this);
433 
434  IsDirty = false;
435  }
436  catch (Exception ex)
437  {
438  log.Error(this, null, AssetMessageCode.PackageCannotSave, ex, FullPath);
439  return;
440  }
441  }
442 
443  foreach (var asset in Assets)
444  {
445  if (asset.IsDirty)
446  {
447  var assetPath = asset.FullPath;
448  try
449  {
450  // Notifies the dependency manager that an asset with the specified path is being saved
451  if (session != null && session.HasDependencyManager)
452  {
453  session.DependencyManager.AddFileBeingSaveDuringSessionSave(assetPath);
454  }
455 
456  // Incject a copy of the base into the current asset when saving
457  var assetBase = asset.Asset.Base;
458  if (assetBase != null && !assetBase.IsRootImport)
459  {
460  var assetBaseItem = session != null ? session.FindAsset(assetBase.Id) : Assets.Find(assetBase.Id);
461  if (assetBaseItem != null)
462  {
463  var newBase = (Asset)AssetCloner.Clone(assetBaseItem.Asset);
464  newBase.Base = null;
465  asset.Asset.Base = new AssetBase(asset.Asset.Base.Location, newBase);
466  }
467  }
468 
469  AssetSerializer.Save(assetPath, asset.Asset);
470  asset.IsDirty = false;
471  }
472  catch (Exception ex)
473  {
474  log.Error(this, asset.ToReference(), AssetMessageCode.AssetCannotSave, ex, assetPath);
475  }
476  }
477  }
478 
479  Assets.IsDirty = false;
480  }
481  finally
482  {
483  // Rollback all relative UFile to absolute paths
484  analysis.Parameters.ConvertUPathTo = UPathType.Absolute;
485  analysis.Run();
486  }
487  }
488 
489  /// <summary>
490  /// Gets the package identifier from file.
491  /// </summary>
492  /// <param name="filePath">The file path.</param>
493  /// <returns>Guid.</returns>
494  /// <exception cref="System.ArgumentNullException">
495  /// log
496  /// or
497  /// filePath
498  /// </exception>
499  public static Guid GetPackageIdFromFile(string filePath)
500  {
501  if (filePath == null) throw new ArgumentNullException("filePath");
502  return AssetSerializer.Load<Package>(filePath).Id;
503  }
504 
505  /// <summary>
506  /// Loads only the package description but not assets or plugins.
507  /// </summary>
508  /// <param name="log">The log to receive error messages.</param>
509  /// <param name="filePath">The file path.</param>
510  /// <param name="loadParametersArg">The load parameters argument.</param>
511  /// <returns>A package.</returns>
512  /// <exception cref="System.ArgumentNullException">log
513  /// or
514  /// filePath</exception>
515  public static Package Load(ILogger log, string filePath, PackageLoadParameters loadParametersArg = null)
516  {
517  if (log == null) throw new ArgumentNullException("log");
518  if (filePath == null) throw new ArgumentNullException("filePath");
519 
520  filePath = FileUtility.GetAbsolutePath(filePath);
521 
522  if (!File.Exists(filePath))
523  {
524  log.Error("Package file [{0}] was not found", filePath);
525  return null;
526  }
527 
528  var loadParameters = loadParametersArg ?? PackageLoadParameters.Default();
529 
530  try
531  {
532  var package = AssetSerializer.Load<Package>(filePath);
533  package.FullPath = filePath;
534  package.IsDirty = false;
535 
536  // Load assembly references
537  if (loadParameters.LoadAssemblyReferences)
538  {
539  package.LoadAssemblyReferencesForPackage(log, loadParameters);
540  }
541 
542  // Load assets
543  if (loadParameters.AutoLoadTemporaryAssets)
544  {
545  package.LoadTemporaryAssets(log, loadParameters.CancelToken);
546  }
547 
548  // Convert UPath to absolute
549  if (loadParameters.ConvertUPathToAbsolute)
550  {
551  var analysis = new PackageAnalysis(package, new PackageAnalysisParameters()
552  {
553  ConvertUPathTo = UPathType.Absolute,
554  IsProcessingUPaths = true, // This is done already by Package.Load
555  SetDirtyFlagOnAssetWhenFixingAbsoluteUFile = true // When loading tag attributes that have an absolute file
556  });
557  analysis.Run(log);
558  }
559 
560  // Load templates
561  package.LoadTemplates(log);
562 
563  return package;
564  }
565  catch (Exception ex)
566  {
567  log.Error("Error while pre-loading package [{0}]", ex, filePath);
568  }
569 
570  return null;
571  }
572 
573  public void ValidateAssets(bool alwaysGenerateNewAssetId = false)
574  {
575  if (TemporaryAssets.Count == 0)
576  {
577  return;
578  }
579 
580  try
581  {
582  // Make sure we are suspending notifications before updating all assets
583  Assets.SuspendCollectionChanged();
584 
585  Assets.Clear();
586 
587  // Get generated output items
588  var outputItems = new AssetItemCollection();
589 
590  // Create a resolver from the package
591  var resolver = AssetResolver.FromPackage(this);
592  resolver.AlwaysCreateNewId = alwaysGenerateNewAssetId;
593 
594  // Clean assets
595  AssetCollision.Clean(TemporaryAssets, outputItems, resolver, false);
596 
597  // Add them back to the package
598  foreach (var item in outputItems)
599  {
600  Assets.Add(item);
601  }
602 
603  TemporaryAssets.Clear();
604  }
605  finally
606  {
607  // Restore notification on assets
608  Assets.ResumeCollectionChanged();
609  }
610  }
611 
612  /// <summary>
613  /// Refreshes this package from the disk by loading or reloading all assets.
614  /// </summary>
615  /// <param name="log">The log.</param>
616  /// <param name="cancelToken">The cancel token.</param>
617  /// <returns>A logger that contains error messages while refreshing.</returns>
618  /// <exception cref="System.InvalidOperationException">Package RootDirectory is null
619  /// or
620  /// Package RootDirectory [{0}] does not exist.ToFormat(RootDirectory)</exception>
621  public void LoadTemporaryAssets(ILogger log, CancellationToken? cancelToken = null)
622  {
623  if (log == null) throw new ArgumentNullException("log");
624 
625  // If FullPath is null, then we can't load assets from disk, just return
626  if (FullPath == null)
627  {
628  log.Warning("Fullpath not set on this package");
629  return;
630  }
631 
632  // Clears the assets already loaded and reload them
633  TemporaryAssets.Clear();
634 
635  // List all package files on disk
636  var listFiles = ListAssetFiles(log, this, cancelToken);
637 
638  var progressMessage = String.Format("Loading Assets from Package [{0}]", FullPath.GetFileNameWithExtension());
639 
640  // Display this message at least once if the logger does not log progress (And it shouldn't in this case)
641  var loggerResult = log as LoggerResult;
642  if (loggerResult == null || !loggerResult.IsLoggingProgressAsInfo)
643  {
644  log.Info(progressMessage);
645  }
646 
647  // Update step counter for log progress
648  for (int i = 0; i < listFiles.Count; i++)
649  {
650  var fileUPath = listFiles[i].Item1;
651  var sourceFolder = listFiles[i].Item2;
652  if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
653  {
654  log.Warning("Skipping loading assets. PackageSession.Load cancelled");
655  break;
656  }
657 
658  // Update the loading progress
659  if (loggerResult != null)
660  {
661  loggerResult.Progress(progressMessage, i, listFiles.Count);
662  }
663 
664  // Try to load only if asset is not already in the package or assetRef.Asset is null
665  var assetPath = fileUPath.MakeRelative(sourceFolder).GetDirectoryAndFileName();
666  try
667  {
668  // An exception can occur here, so we make sure that loading a single asset is not going to break
669  // the loop
670  var assetFullPath = fileUPath.FullPath;
671  var asset = LoadAsset(log, assetFullPath, assetPath, fileUPath);
672 
673  // Create asset item
674  var assetItem = new AssetItem(assetPath, asset)
675  {
676  IsDirty = false,
677  Package = this,
678  SourceFolder = sourceFolder.MakeRelative(RootDirectory)
679  };
680  // Set the modified time to the time loaded from disk
681  assetItem.ModifiedTime = File.GetLastWriteTime(assetFullPath);
682 
683  FixAssetImport(assetItem);
684 
685  // Add to temporary assets
686  TemporaryAssets.Add(assetItem);
687  }
688  catch (Exception ex)
689  {
690  int row = 1;
691  int column = 1;
692  var yamlException = ex as YamlException;
693  if (yamlException != null)
694  {
695  row = yamlException.Start.Line + 1;
696  column = yamlException.Start.Column;
697  }
698 
699  var module = log.Module;
700 
701  var assetReference = new AssetReference<Asset>(Guid.Empty, fileUPath.FullPath);
702 
703  // TODO: Change this instead of patching LoggerResult.Module, use a proper log message
704  if (loggerResult != null)
705  {
706  loggerResult.Module = "{0}({1},{2})".ToFormat(Path.GetFullPath(fileUPath.FullPath), row, column);
707  }
708 
709  log.Error(this, assetReference, AssetMessageCode.AssetLoadingFailed, ex, fileUPath, ex.Message);
710 
711  if (loggerResult != null)
712  {
713  loggerResult.Module = module;
714  }
715  }
716  }
717  }
718 
719  private static Asset LoadAsset(ILogger log, string assetFullPath, string assetPath, UFile fileUPath)
720  {
721  AssetMigration.MigrateAssetIfNeeded(log, assetFullPath);
722 
723  var asset = AssetSerializer.Load<Asset>(assetFullPath);
724 
725  // Set location on source code asset
726  var sourceCodeAsset = asset as SourceCodeAsset;
727  if (sourceCodeAsset != null)
728  {
729  // Use an id generated from the location instead of the default id
730  sourceCodeAsset.Id = SourceCodeAsset.GenerateGuidFromLocation(assetPath);
731  sourceCodeAsset.AbsoluteSourceLocation = fileUPath;
732  }
733 
734  return asset;
735  }
736 
737  private void LoadAssemblyReferencesForPackage(ILogger log, PackageLoadParameters loadParameters)
738  {
739  if (log == null) throw new ArgumentNullException("log");
740  if (loadParameters == null) throw new ArgumentNullException("loadParameters");
741  var assemblyContainer = loadParameters.AssemblyContainer ?? AssemblyContainer.Default;
742  foreach (var profile in Profiles)
743  {
744  foreach (var projectReference in profile.ProjectReferences.Where(projectRef => projectRef.Type == ProjectType.Plugin || projectRef.Type == ProjectType.Library))
745  {
746  string assemblyPath = null;
747  var fullProjectLocation = UPath.Combine(RootDirectory, projectReference.Location);
748  try
749  {
750  assemblyPath = VSProjectHelper.GetOrCompileProjectAssembly(fullProjectLocation, log, loadParameters.AutoCompileProjects, extraProperties: loadParameters.ExtraCompileProperties, onlyErrors: true);
751 
752  if (String.IsNullOrWhiteSpace(assemblyPath))
753  {
754  log.Error("Unable to locate assembly reference for project [{0}]", fullProjectLocation);
755  continue;
756  }
757 
758  if (!File.Exists(assemblyPath))
759  {
760  log.Error("Unable to build assembly reference [{0}]", assemblyPath);
761  continue;
762  }
763 
764  var assembly = assemblyContainer.LoadAssemblyFromPath(assemblyPath, log);
765  if (assembly == null)
766  {
767  log.Error("Unable to load assembly reference [{0}]", assemblyPath);
768  }
769  }
770  catch (Exception ex)
771  {
772  log.Error("Unexpected error while loading project [{0}] or assembly reference [{1}]", ex, fullProjectLocation, assemblyPath);
773  }
774  }
775  }
776  }
777 
778  private void UpdateSourceFolders()
779  {
780  // If there are not assets, we don't need to update or create an asset folder
781  if (Assets.Count == 0)
782  {
783  return;
784  }
785 
786  // Make sure there is a shared profile at least
787  PackageProfile sharedProfile;
788  if (!Profiles.Contains(PlatformType.Shared))
789  {
790  sharedProfile = PackageProfile.NewShared();
791  Profiles.Add(sharedProfile);
792  }
793  else
794  {
795  sharedProfile = Profiles[PlatformType.Shared];
796  }
797 
798  // Use by default the first asset folders if not defined on the asset item
799  var defaultFolder = sharedProfile.AssetFolders.Count > 0 ? sharedProfile.AssetFolders.First().Path : UDirectory.This;
800  var assetFolders = new HashSet<UDirectory>(GetDistinctAssetFolderPaths());
801  foreach (var asset in Assets)
802  {
803  if (asset.SourceFolder == null)
804  {
805  asset.SourceFolder = defaultFolder.IsAbsolute ? defaultFolder.MakeRelative(RootDirectory) : defaultFolder;
806  asset.IsDirty = true;
807  }
808 
809  var assetFolderAbsolute = UPath.Combine(RootDirectory, asset.SourceFolder);
810  if (!assetFolders.Contains(assetFolderAbsolute))
811  {
812  assetFolders.Add(assetFolderAbsolute);
813  sharedProfile.AssetFolders.Add(new AssetFolder(assetFolderAbsolute));
814  IsDirty = true;
815  }
816  }
817  }
818 
819  /// <summary>
820  /// Loads the templates.
821  /// </summary>
822  /// <param name="log">The log result.</param>
823  private void LoadTemplates(ILogger log)
824  {
825  foreach (var templateDir in TemplateFolders)
826  {
827  foreach (var filePath in templateDir.Files)
828  {
829  try
830  {
831  var file = new FileInfo(filePath);
832  if (!file.Exists)
833  {
834  log.Warning("Template [{0}] does not exist ", file);
835  continue;
836  }
837 
838  var templateDescription = AssetSerializer.Load<TemplateDescription>(file.FullName);
839  templateDescription.FullPath = file.FullName;
840  Templates.Add(templateDescription);
841  }
842  catch (Exception ex)
843  {
844  log.Error("Error while loading template from [{0}]", ex, filePath);
845  }
846  }
847  }
848  }
849 
850  private List<UDirectory> GetDistinctAssetFolderPaths()
851  {
852  var existingAssetFolders = new List<UDirectory>();
853  foreach (var profile in Profiles)
854  {
855  foreach (var folder in profile.AssetFolders)
856  {
857  var folderPath = RootDirectory != null ? UPath.Combine(RootDirectory, folder.Path) : folder.Path;
858  if (!existingAssetFolders.Contains(folderPath))
859  {
860  existingAssetFolders.Add(folderPath);
861  }
862  }
863  }
864  return existingAssetFolders;
865  }
866 
867  private static List<Tuple<UFile, UDirectory>> ListAssetFiles(ILogger log, Package package, CancellationToken? cancelToken)
868  {
869  var listFiles = new List<Tuple<UFile, UDirectory>>();
870 
871  // TODO Check how to handle refresh correctly as a public API
872  if (package.RootDirectory == null)
873  {
874  throw new InvalidOperationException("Package RootDirectory is null");
875  }
876 
877  if (!Directory.Exists(package.RootDirectory))
878  {
879  throw new InvalidOperationException("Package RootDirectory [{0}] does not exist".ToFormat(package.RootDirectory));
880  }
881 
882  // Iterate on each source folders
883  foreach (var sourceFolder in package.GetDistinctAssetFolderPaths())
884  {
885  // Lookup all files
886  foreach (var directory in FileUtility.EnumerateDirectories(sourceFolder, SearchDirection.Down))
887  {
888  var files = directory.GetFiles();
889 
890  foreach (var filePath in files)
891  {
892  // Don't load package via this method
893  if (filePath.FullName.EndsWith(PackageFileExtension))
894  {
895  continue;
896  }
897 
898  // Make an absolute path from the root of this package
899  var fileUPath = new UFile(filePath.FullName);
900  if (fileUPath.GetFileExtension() == null)
901  {
902  continue;
903  }
904 
905  // If this kind of file an asset file?
906  if (!AssetRegistry.IsAssetFileExtension(fileUPath.GetFileExtension()))
907  {
908  continue;
909  }
910 
911  listFiles.Add(new Tuple<UFile, UDirectory>(fileUPath, sourceFolder));
912  }
913  }
914  }
915 
916  return listFiles;
917  }
918 
919  /// <summary>
920  /// Fixes asset import that were imported by the previous method. Add a AssetImport.SourceHash and ImporterId
921  /// </summary>
922  /// <param name="item">The item.</param>
923  private static void FixAssetImport(AssetItem item)
924  {
925  // TODO: this whole method is a temporary migration. This should be removed in the next version
926 
927  var assetImport = item.Asset as AssetImport;
928  if (assetImport == null || assetImport.Source == null)
929  {
930  return;
931  }
932 
933  // If the asset has a source but no import base, then we are going to simulate an original import
934  if (assetImport.Base == null)
935  {
936  var fileExtension = assetImport.Source.GetFileExtension();
937 
938  var assetImportBase = (AssetImport)AssetCloner.Clone(assetImport);
939  assetImportBase.SetAsRootImport();
940  assetImportBase.SetDefaults();
941 
942  // Setup default importer
943  if (!String.IsNullOrEmpty(fileExtension))
944  {
945  var importerId = AssetRegistry.FindImporterByExtension(fileExtension).FirstOrDefault();
946  if (importerId != null)
947  {
948  assetImport.ImporterId = importerId.Id;
949  }
950  }
951  var assetImportTracked = assetImport as AssetImportTracked;
952  if (assetImportTracked != null)
953  {
954  assetImportTracked.SourceHash = ObjectId.Empty;
955  }
956 
957  assetImport.Base = new AssetBase(assetImportBase);
958  item.IsDirty = true;
959  }
960  }
961  }
962 }
Package()
Initializes a new instance of the Package class.
Definition: Package.cs:56
static bool IsAssetFileExtension(string extension)
Determines whether the extension is an asset file type.
PlatformType
Describes the platform operating system.
Definition: PlatformType.cs:9
static Package Load(ILogger log, string filePath, PackageLoadParameters loadParametersArg=null)
Loads only the package description but not assets or plugins.
Definition: Package.cs:515
Package Clone(bool deepCloneAsset)
Deep clone this package.
Definition: Package.cs:313
SearchDirection
A direction to search for files in directories
A location relative to a package from where assets will be loaded
Definition: AssetFolder.cs:16
File Utilities methods.
Definition: FileUtility.cs:13
Metadata for a Package accessible from Package.Meta.
Definition: PackageMeta.cs:15
A collection of AssetItem that contains only absolute location without any drive information. This class cannot be inherited.
Base class for Asset.
Definition: Asset.cs:14
void AddExitingProject(UFile pathToMsproj, LoggerResult logger)
Adds an exiting project to this package.
Definition: Package.cs:252
SiliconStudio.Core.Diagnostics.LoggerResult LoggerResult
A logger that stores messages locally useful for internal log scenarios.
Definition: LoggerResult.cs:14
A registry for file extensions, IAssetImporter, IAssetFactory and aliases associated with assets...
An asset item part of a Package accessible through Package.Assets.
Definition: AssetBase.cs:16
AssetMessageCode
A message code used by AssetLogMessage to identify an error/warning.
Class PackageAnalysisParameters. This class cannot be inherited.
static readonly UDirectory This
A this '.' directory.
Definition: UDirectory.cs:23
A collection of AssetItem that contains only absolute location without any drive information. This class cannot be inherited.
An analysis to check the validity of a Package, convert UFile or UDirectory references to absolute/re...
An asset item part of a Package accessible through SiliconStudio.Assets.Package.Assets.
Definition: AssetItem.cs:17
A session for editing a package.
Description of a template generator that can be displayed in the GameStudio.
void LoadTemporaryAssets(ILogger log, CancellationToken?cancelToken=null)
Refreshes this package from the disk by loading or reloading all assets.
Definition: Package.cs:621
Defines a normalized directory path. See UPath for details. This class cannot be inherited.
Definition: UDirectory.cs:13
A collection of PackageProfile.
bool IsAbsolute
Determines whether this instance is absolute.
Definition: UPath.cs:269
System.IO.File File
UDirectory RootDirectory
Gets the top directory of this package on the local disk.
Definition: Package.cs:205
LoggerResult AddExitingProject(UFile pathToMsproj)
Adds an exiting project to this package.
Definition: Package.cs:240
An importable asset with a content that need to be tracked if original asset is changing.
ProjectType
Type of the project.
Definition: ProjectType.cs:11
Action< Asset > AssetDirtyChanged
Occurs when an asset dirty changed occured.
Definition: Package.cs:51
LoggerResult Save()
Saves this package and all dirty assets. See remarks.
Definition: Package.cs:384
void Save(ILogger log)
Saves this package and all dirty assets. See remarks.
Definition: Package.cs:398
object Clone()
Clones the current value of this cloner with the specified new shadow registry (optional) ...
Definition: AssetCloner.cs:43
Identify an object that is associated with an anchor file on the disk where all the UPath members of ...
An importable asset.
Definition: AssetImport.cs:14
static IEnumerable< DirectoryInfo > EnumerateDirectories(string rootDirectory, SearchDirection direction)
Definition: FileUtility.cs:113
A reference to a Visual Studio project that is part of a Package
static Guid GetPackageIdFromFile(string filePath)
Gets the package identifier from file.
Definition: Package.cs:499
void SetPackagePath(UFile newPath, bool copyAssets=true)
Sets the package path.
Definition: Package.cs:333
Describes buld parameters used when building assets.
void ValidateAssets(bool alwaysGenerateNewAssetId=false)
Definition: Package.cs:573
The template can be applied to an existing PackageSession.
A package managing assets.
Definition: Package.cs:28
Parameters used for loading a package.
Interface for logging.
Definition: ILogger.cs:8
Allows to clone an asset or values stored in an asset.
Definition: AssetCloner.cs:14
Defines a normalized file path. See UPath for details. This class cannot be inherited.
Definition: UFile.cs:13