1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Globalization;
7 using System.IO;
8 using System.Linq;
9 using Microsoft.Internal.Web.Utils;
10 using NuGet;
11 using NuGet.Runtime;
12 
13 namespace System.Web.WebPages.Administration.PackageManager
14 {
15     [EditorBrowsable(EditorBrowsableState.Never)]
16     public class WebProjectManager : IWebProjectManager
17     {
18         private const string WebPagesPreferredTag = " aspnetwebpages ";
19         private readonly IProjectManager _projectManager;
20         private readonly string _siteRoot;
21 
WebProjectManager(string remoteSource, string siteRoot)22         public WebProjectManager(string remoteSource, string siteRoot)
23         {
24             if (String.IsNullOrEmpty(remoteSource))
25             {
26                 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "remoteSource");
27             }
28             if (String.IsNullOrEmpty(siteRoot))
29             {
30                 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "siteRoot");
31             }
32 
33             _siteRoot = siteRoot;
34             string webRepositoryDirectory = GetWebRepositoryDirectory(siteRoot);
35             _projectManager = new ProjectManager(sourceRepository: PackageRepositoryFactory.Default.CreateRepository(remoteSource),
36                                                  pathResolver: new DefaultPackagePathResolver(webRepositoryDirectory),
37                                                  localRepository: PackageRepositoryFactory.Default.CreateRepository(webRepositoryDirectory),
38                                                  project: new WebProjectSystem(siteRoot));
39         }
40 
WebProjectManager(IProjectManager projectManager, string siteRoot)41         internal WebProjectManager(IProjectManager projectManager, string siteRoot)
42         {
43             if (String.IsNullOrEmpty(siteRoot))
44             {
45                 throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "siteRoot");
46             }
47 
48             if (projectManager == null)
49             {
50                 throw new ArgumentNullException("projectManager");
51             }
52 
53             _siteRoot = siteRoot;
54             _projectManager = projectManager;
55         }
56 
57         public IPackageRepository LocalRepository
58         {
59             get { return _projectManager.LocalRepository; }
60         }
61 
62         public IPackageRepository SourceRepository
63         {
64             get { return _projectManager.SourceRepository; }
65         }
66 
67         internal bool DoNotAddBindingRedirects { get; set; }
68 
69         [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#",
70             Justification = "We want to ensure we get server-side counts for the IQueryable which can only be performed before we collapse versions.")]
GetRemotePackages(string searchTerms, bool filterPreferred)71         public virtual IQueryable<IPackage> GetRemotePackages(string searchTerms, bool filterPreferred)
72         {
73             var packages = GetPackages(SourceRepository, searchTerms);
74             if (filterPreferred)
75             {
76                 packages = packages.Where(p => p.Tags.ToLower().Contains(WebPagesPreferredTag));
77             }
78 
79             // Order by download count and Id to allow collapsing
80             return packages.OrderByDescending(p => p.DownloadCount)
81                 .ThenBy(p => p.Id);
82         }
83 
GetInstalledPackages(string searchTerms)84         public IQueryable<IPackage> GetInstalledPackages(string searchTerms)
85         {
86             return GetPackages(LocalRepository, searchTerms);
87         }
88 
GetPackagesWithUpdates(string searchTerms, bool filterPreferredPackages)89         public IEnumerable<IPackage> GetPackagesWithUpdates(string searchTerms, bool filterPreferredPackages)
90         {
91             var packagesToUpdate = GetPackages(LocalRepository, searchTerms);
92             if (filterPreferredPackages)
93             {
94                 packagesToUpdate = packagesToUpdate.Where(p => p.Tags.ToLower().Contains(WebPagesPreferredTag));
95             }
96             return SourceRepository.GetUpdates(packagesToUpdate, includePrerelease: false).AsQueryable();
97         }
98 
InstallPackage(IPackage package)99         internal IEnumerable<string> InstallPackage(IPackage package)
100         {
101             return InstallPackage(package, AppDomain.CurrentDomain);
102         }
103 
104         /// <summary>
105         /// Installs and adds a package reference to the project
106         /// </summary>
107         /// <returns>Warnings encountered when installing the package.</returns>
InstallPackage(IPackage package, AppDomain appDomain)108         public IEnumerable<string> InstallPackage(IPackage package, AppDomain appDomain)
109         {
110             IEnumerable<string> result = PerformLoggedAction(() =>
111             {
112                 _projectManager.AddPackageReference(package.Id, package.Version, ignoreDependencies: false, allowPrereleaseVersions: false);
113                 AddBindingRedirects(appDomain);
114             });
115             return result;
116         }
117 
UpdatePackage(IPackage package)118         internal IEnumerable<string> UpdatePackage(IPackage package)
119         {
120             return UpdatePackage(package, AppDomain.CurrentDomain);
121         }
122 
123         /// <summary>
124         /// Updates a package reference. Installs the package to the App_Data repository if it does not already exist.
125         /// </summary>
126         /// <returns>Warnings encountered when updating the package.</returns>
UpdatePackage(IPackage package, AppDomain appDomain)127         public IEnumerable<string> UpdatePackage(IPackage package, AppDomain appDomain)
128         {
129             return PerformLoggedAction(() =>
130             {
131                 _projectManager.UpdatePackageReference(package.Id, package.Version, updateDependencies: true, allowPrereleaseVersions: false);
132                 AddBindingRedirects(appDomain);
133             });
134         }
135 
136         /// <summary>
137         /// Removes a package reference and uninstalls the package
138         /// </summary>
139         /// <returns>Warnings encountered when uninstalling the package.</returns>
UninstallPackage(IPackage package, bool removeDependencies)140         public IEnumerable<string> UninstallPackage(IPackage package, bool removeDependencies)
141         {
142             return PerformLoggedAction(() =>
143             {
144                 _projectManager.RemovePackageReference(package.Id, forceRemove: false, removeDependencies: removeDependencies);
145             });
146         }
147 
148         [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "It seems more appropriate to deal with IPackages")]
IsPackageInstalled(IPackage package)149         public bool IsPackageInstalled(IPackage package)
150         {
151             return LocalRepository.Exists(package);
152         }
153 
GetUpdate(IPackage package)154         public IPackage GetUpdate(IPackage package)
155         {
156             return SourceRepository.GetUpdates(new[] { package }, includePrerelease: false).SingleOrDefault();
157         }
158 
AddBindingRedirects(AppDomain appDomain)159         private void AddBindingRedirects(AppDomain appDomain)
160         {
161             if (DoNotAddBindingRedirects)
162             {
163                 return;
164             }
165             // We can't use HttpRuntime.BinDirectory since there is no runtime when installing via WebMatrix.
166             var binDirectory = Path.Combine(_siteRoot, "bin");
167             var assemblies = RemoteAssembly.GetAssembliesForBindingRedirect(appDomain, binDirectory);
168             var bindingRedirects = BindingRedirectResolver.GetBindingRedirects(assemblies);
169 
170             if (bindingRedirects.Any())
171             {
172                 // NuGet ends up reading our web.config file regardless of if any bindingRedirects are needed.
173                 var bindingRedirectManager = new BindingRedirectManager(_projectManager.Project, "web.config");
174                 bindingRedirectManager.AddBindingRedirects(bindingRedirects);
175             }
176         }
177 
PerformLoggedAction(Action action)178         private IEnumerable<string> PerformLoggedAction(Action action)
179         {
180             ErrorLogger logger = new ErrorLogger();
181             _projectManager.Logger = logger;
182             try
183             {
184                 action();
185             }
186             finally
187             {
188                 _projectManager.Logger = null;
189             }
190             return logger.Errors;
191         }
192 
193         /// <remarks>
194         /// Ensure that some form of sorting is applied to the IQueryable before this method is invoked.
195         /// </remarks>
196         /// <returns>A sequence with the most recent version for each package.</returns>
CollapseVersions(IQueryable<IPackage> packages)197         public static IEnumerable<IPackage> CollapseVersions(IQueryable<IPackage> packages)
198         {
199             const int BufferSize = 30;
200             return packages.Where(package => package.IsLatestVersion)
201                 .AsBufferedEnumerable(BufferSize)
202                 .DistinctLast(PackageEqualityComparer.Id, PackageComparer.Version);
203         }
204 
GetPackagesRequiringLicenseAcceptance(IPackage package)205         internal IEnumerable<IPackage> GetPackagesRequiringLicenseAcceptance(IPackage package)
206         {
207             return GetPackagesRequiringLicenseAcceptance(package, localRepository: LocalRepository, sourceRepository: SourceRepository);
208         }
209 
GetPackagesRequiringLicenseAcceptance(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository)210         internal static IEnumerable<IPackage> GetPackagesRequiringLicenseAcceptance(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository)
211         {
212             var dependencies = GetPackageDependencies(package, localRepository, sourceRepository);
213 
214             return from p in dependencies
215                    where p.RequireLicenseAcceptance
216                    select p;
217         }
218 
GetPackageDependencies(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository)219         private static IEnumerable<IPackage> GetPackageDependencies(IPackage package, IPackageRepository localRepository, IPackageRepository sourceRepository)
220         {
221             InstallWalker walker = new InstallWalker(localRepository: localRepository, sourceRepository: sourceRepository, logger: NullLogger.Instance,
222                                                      ignoreDependencies: false, allowPrereleaseVersions: false);
223             IEnumerable<PackageOperation> operations = walker.ResolveOperations(package);
224 
225             return from operation in operations
226                    where operation.Action == PackageAction.Install
227                    select operation.Package;
228         }
229 
GetPackages(IPackageRepository repository, string searchTerm)230         internal static IQueryable<IPackage> GetPackages(IPackageRepository repository, string searchTerm)
231         {
232             return GetPackages(repository.GetPackages(), searchTerm);
233         }
234 
GetPackages(IQueryable<IPackage> packages, string searchTerm)235         internal static IQueryable<IPackage> GetPackages(IQueryable<IPackage> packages, string searchTerm)
236         {
237             if (!String.IsNullOrEmpty(searchTerm))
238             {
239                 searchTerm = searchTerm.Trim();
240                 packages = packages.Find(searchTerm);
241             }
242             return packages;
243         }
244 
GetWebRepositoryDirectory(string siteRoot)245         internal static string GetWebRepositoryDirectory(string siteRoot)
246         {
247             return Path.Combine(siteRoot, "App_Data", "packages");
248         }
249 
250         private class ErrorLogger : ILogger
251         {
252             private readonly IList<string> _errors = new List<string>();
253 
254             public IEnumerable<string> Errors
255             {
256                 get { return _errors; }
257             }
258 
Log(MessageLevel level, string message, params object[] args)259             public void Log(MessageLevel level, string message, params object[] args)
260             {
261                 if (level == MessageLevel.Warning)
262                 {
263                     _errors.Add(String.Format(CultureInfo.CurrentCulture, message, args));
264                 }
265             }
266         }
267     }
268 }
269