Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PackageStore.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.IO;
6 using System.Linq;
7 using NuGet;
8 using SiliconStudio.Core;
9 using SiliconStudio.Core.Diagnostics;
10 using SiliconStudio.Core.IO;
11 
12 namespace SiliconStudio.Assets
13 {
14  /// <summary>
15  /// Manage packages locally installed and accessible on the store.
16  /// </summary>
17  /// <remarks>
18  /// This class is the frontend to the packaging/distribution system. It is currently using nuget for its packaging but may
19  /// change in the future.
20  /// </remarks>
21  public class PackageStore
22  {
23  private static readonly Lazy<PackageStore> DefaultPackageStore = new Lazy<PackageStore>(() => new PackageStore());
24 
25  private const string DefaultEnvironmentSdkDir = "SiliconStudioParadoxSdkDir";
26 
27  private const string CommonTargets = @"Targets\SiliconStudio.Common.targets";
28 
29  private const string ParadoxSolution = @"build\Paradox.sln";
30 
31  private readonly Package defaultPackage;
32 
33  private readonly UDirectory globalInstallationPath;
34 
35  private readonly UDirectory packagesDirectory;
36 
37  private readonly bool isDev;
38 
39  private readonly UDirectory defaultPackageDirectory;
40 
41  private readonly NugetStore store;
42 
43  /// <summary>
44  /// Initializes a new instance of the <see cref="PackageStore"/> class.
45  /// </summary>
46  /// <exception cref="System.InvalidOperationException">Unable to find a valid Paradox installation path</exception>
47  private PackageStore(string installationPath = null, string defaultPackageName = "Paradox", string defaultPackageVersion = ParadoxVersion.CurrentAsText)
48  {
49  // 1. Try to use the specified installation path
50  if (installationPath != null)
51  {
52  if (!IsRootDirectory(installationPath))
53  {
54  throw new ArgumentException("Invalid Paradox installation path [{0}]".ToFormat(installationPath), "installationPath");
55  }
56 
57  globalInstallationPath = installationPath;
58  }
59 
60  // TODO: these are currently hardcoded to Paradox
61  DefaultPackageName = defaultPackageName;
62  DefaultPackageVersion = new PackageVersion(defaultPackageVersion);
63 
64  // 2. Try to resolve an installation path from the path of this assembly
65  // We need to be able to use the package manager from an official Paradox install as well as from a developer folder
66 
67  // Try to determine the root package manager from the current assembly
68  var thisAssemblyLocation = typeof(PackageStore).Assembly.Location;
69  var binDirectory = new FileInfo(thisAssemblyLocation).Directory;
70  if (binDirectory != null && binDirectory.Parent != null && binDirectory.Parent.Parent != null)
71  {
72  var defaultPackageDirectoryTemp = binDirectory.Parent.Parent;
73 
74  // If we have a root directory, then store it as the default package directory
75  if (IsPackageDirectory(defaultPackageDirectoryTemp.FullName, DefaultPackageName))
76  {
77  defaultPackageDirectory = defaultPackageDirectoryTemp.FullName;
78  }
79  else
80  {
81  throw new InvalidOperationException("The current assembly [{0}] is not part of the package [{1}]".ToFormat(thisAssemblyLocation, DefaultPackageName));
82  }
83 
84  if (globalInstallationPath == null)
85  {
86  // Check if we have a regular distribution
87  if (defaultPackageDirectoryTemp.Parent != null && IsRootDirectory(defaultPackageDirectoryTemp.Parent.FullName))
88  {
89  globalInstallationPath = defaultPackageDirectoryTemp.Parent.FullName;
90  }
91  else if (IsRootDirectory(defaultPackageDirectory))
92  {
93  // we have a dev distribution
94  globalInstallationPath = defaultPackageDirectory;
95  }
96  }
97  }
98  else
99  {
100  throw new InvalidOperationException("The current assembly [{0}] must be loaded from a valid installation".ToFormat(thisAssemblyLocation));
101  }
102 
103  // 3. Try from the environement variable
104  if (globalInstallationPath == null)
105  {
106  var rootDirectory = Environment.GetEnvironmentVariable(DefaultEnvironmentSdkDir);
107  if (!string.IsNullOrWhiteSpace(rootDirectory) && IsRootDirectory(rootDirectory))
108  {
109  globalInstallationPath = rootDirectory;
110  }
111  }
112 
113  // If there is no root, this is an error
114  if (globalInstallationPath == null)
115  {
116  throw new InvalidOperationException("Unable to find a valid Paradox installation or dev path");
117  }
118 
119  // Preload default package
120  var logger = new LoggerResult();
121  var defaultPackageFile = GetPackageFile(defaultPackageDirectory, DefaultPackageName);
122  defaultPackage = Package.Load(logger, defaultPackageFile, GetDefaultPackageLoadParameters());
123  if (defaultPackage == null)
124  {
125  throw new InvalidOperationException("Error while loading default package from [{0}]: {1}".ToFormat(defaultPackageFile, logger.ToText()));
126  }
127  defaultPackage.IsSystem = true;
128 
129  // A flag variable just to know if it is a bare bone development directory
130  isDev = defaultPackageDirectory != null && IsRootDevDirectory(defaultPackageDirectory);
131 
132  // Check if we are in a root directory with store/packages facilities
133  if (NugetStore.IsStoreDirectory(globalInstallationPath))
134  {
135  packagesDirectory = UPath.Combine(globalInstallationPath, (UDirectory)NugetStore.DefaultGamePackagesDirectory);
136  store = new NugetStore(globalInstallationPath) { DefaultPackageId = DefaultPackageName };
137  }
138  }
139 
140  /// <summary>
141  /// Gets or sets the default package name (mainly used in dev environment).
142  /// </summary>
143  /// <value>The default package name.</value>
144  public string DefaultPackageName { get; private set; }
145 
146  /// <summary>
147  /// Gets the default package minimum version.
148  /// </summary>
149  /// <value>The default package minimum version.</value>
150  public PackageVersionRange DefaultPackageMinVersion
151  {
152  get
153  {
154  return new PackageVersionRange(DefaultPackageVersion, true);
155  }
156  }
157 
158  /// <summary>
159  /// Gets the default package version.
160  /// </summary>
161  /// <value>The default package version.</value>
162  public PackageVersion DefaultPackageVersion { get; private set; }
163 
164  /// <summary>
165  /// Gets the default package.
166  /// </summary>
167  /// <value>The default package.</value>
168  public Package DefaultPackage
169  {
170  get
171  {
172  return defaultPackage;
173  }
174  }
175 
176  /// <summary>
177  /// The root directory of packages.
178  /// </summary>
179  public UDirectory InstallationPath
180  {
181  get
182  {
183  return globalInstallationPath;
184  }
185  }
186 
187  /// <summary>
188  /// Gets the packages available online.
189  /// </summary>
190  /// <returns>IEnumerable&lt;PackageMeta&gt;.</returns>
192  {
193  if (store == null)
194  {
195  return Enumerable.Empty<PackageMeta>().AsQueryable();
196  }
197 
198  var packages = store.Manager.SourceRepository.Search(null, false);
199 
200  // Order by download count and Id to allow collapsing
201  var orderedPackages = packages.OrderByDescending(p => p.DownloadCount).ThenBy(p => p.Id);
202 
203  // For some unknown reasons, we can't select directly from IQueryable<IPackage> to IQueryable<PackageMeta>,
204  // so we need to pass through a IEnumerable<PackageMeta> and translate it to IQueyable. Not sure it has
205  // an implication on the original query behinds the scene
206  return orderedPackages.Select(PackageMeta.FromNuGet);
207  }
208 
209  /// <summary>
210  /// Gets the packages installed locally.
211  /// </summary>
212  /// <returns>An enumeratior of <see cref="Package"/>.</returns>
214  {
215  var packages = new List<Package> { defaultPackage };
216 
217  if (store != null)
218  {
219  var log = new LoggerResult();
220 
221  var metas = store.Manager.LocalRepository.GetPackages();
222  foreach (var meta in metas)
223  {
224  var path = store.PathResolver.GetPackageDirectory(meta.Id, meta.Version);
225 
226  var package = Package.Load(log, path, GetDefaultPackageLoadParameters());
227  if (package != null && packages.All(packageRegistered => packageRegistered.Meta.Name != defaultPackage.Meta.Name))
228  {
229  package.IsSystem = true;
230  packages.Add(package);
231  }
232  }
233  }
234 
235  return packages;
236  }
237 
238  /// <summary>
239  /// Gets the filename to the specific package.
240  /// </summary>
241  /// <param name="packageName">Name of the package.</param>
242  /// <param name="versionRange">The version range.</param>
243  /// <param name="allowPreleaseVersion">if set to <c>true</c> [allow prelease version].</param>
244  /// <param name="allowUnlisted">if set to <c>true</c> [allow unlisted].</param>
245  /// <returns>A location on the disk to the specified package or null if not found.</returns>
246  /// <exception cref="System.ArgumentNullException">packageName</exception>
247  public UFile GetPackageFileName(string packageName, PackageVersionRange versionRange = null, bool allowPreleaseVersion = true, bool allowUnlisted = false)
248  {
249  if (packageName == null) throw new ArgumentNullException("packageName");
250  var directory = GetPackageDirectory(packageName, versionRange, allowPreleaseVersion, allowUnlisted);
251  return directory != null ? UPath.Combine(directory, new UFile(packageName + Package.PackageFileExtension)) : null;
252  }
253 
254  /// <summary>
255  /// Gets the default package manager.
256  /// </summary>
257  /// <value>A default instance.</value>
258  public static PackageStore Instance
259  {
260  get
261  {
262  return DefaultPackageStore.Value;
263  }
264  }
265 
266  private static PackageLoadParameters GetDefaultPackageLoadParameters()
267  {
268  // By default, we are not loading assets for installed packages
269  return new PackageLoadParameters() { AutoLoadTemporaryAssets = false };
270  }
271 
272  private UDirectory GetPackageDirectory(string packageName, PackageVersionRange versionRange, bool allowPreleaseVersion = false, bool allowUnlisted = false)
273  {
274  if (packageName == null) throw new ArgumentNullException("packageName");
275 
276  if (store != null)
277  {
278  var versionSpec = versionRange.ToVersionSpec();
279  var package = store.Manager.LocalRepository.FindPackage(packageName, versionSpec, allowPreleaseVersion, allowUnlisted);
280 
281  // If package was not found,
282  if (package != null)
283  {
284  var directory = store.PathResolver.GetPackageDirectory(package);
285  if (directory != null)
286  {
287  return directory;
288  }
289  }
290  }
291 
292  // TODO: Check version for default package
293  return DefaultPackageName == packageName ? defaultPackageDirectory : null;
294  }
295 
296  private static string GetCommonTargets(string directory)
297  {
298  if (directory == null) throw new ArgumentNullException("directory");
299  return Path.Combine(directory, CommonTargets);
300  }
301 
302  private static string GetPackageFile(string directory, string packageName)
303  {
304  if (directory == null) throw new ArgumentNullException("directory");
305  return Path.Combine(directory, packageName + Package.PackageFileExtension);
306  }
307 
308  private static bool IsRootDirectory(string directory)
309  {
310  if (directory == null) throw new ArgumentNullException("directory");
311  var commonTargets = GetCommonTargets(directory);
312  return File.Exists(commonTargets);
313  }
314 
315  private static bool IsPackageDirectory(string directory, string packageName)
316  {
317  if (directory == null) throw new ArgumentNullException("directory");
318  var packageFile = GetPackageFile(directory, packageName);
319  return File.Exists(packageFile);
320  }
321 
322  private static bool IsRootDevDirectory(string directory)
323  {
324  if (directory == null) throw new ArgumentNullException("directory");
325  var paradoxSolution = Path.Combine(directory, ParadoxSolution);
326  return File.Exists(paradoxSolution);
327  }
328  }
329 }
IEnumerable< PackageMeta > GetPackages()
Gets the packages available online.
Metadata for a Package accessible from Package.Meta.
Definition: PackageMeta.cs:15
IEnumerable< Package > GetInstalledPackages()
Gets the packages installed locally.
SiliconStudio.Core.Diagnostics.LoggerResult LoggerResult
Manage packages locally installed and accessible on the store.
Definition: PackageStore.cs:21
UFile GetPackageFileName(string packageName, PackageVersionRange versionRange=null, bool allowPreleaseVersion=true, bool allowUnlisted=false)
Gets the filename to the specific package.
Defines a normalized directory path. See UPath for details. This class cannot be inherited.
Definition: UDirectory.cs:13
const string PackageFileExtension
The file extension used for Package.
Definition: Package.cs:46
A package managing assets.
Definition: Package.cs:28
Parameters used for loading a package.
A hybrid implementation of SemVer that supports semantic versioning as described at http://semver...
Defines a normalized file path. See UPath for details. This class cannot be inherited.
Definition: UFile.cs:13
A dependency to a range of version.