namespace Microsoft.PackageManagement.NuGetProvider
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.PackageManagement.Provider.Utility;
///
/// Package repository for downloading data from file repositories
///
internal class LocalPackageRepository : IPackageRepository {
private readonly string _path;
///
/// Ctor's
///
///
///
public LocalPackageRepository(string physicalPath, NuGetRequest request) {
_path = physicalPath;
}
///
/// Package source location
///
public string Source {
get {
return _path;
}
}
///
/// Finding the packages in the file repository
///
/// Delegate function which is actually finding a package
/// Package Id
/// File repository path
///
///
private static IEnumerable GetPackages(Func openPackage,
string packageId,
IEnumerable packagePaths,
Request request)
{
request.Debug(Resources.Messages.DebugInfoCallMethod3, "LocalPackageRepository", "GetPackages", packageId);
foreach (var path in packagePaths)
{
IPackage package = null;
try {
package = GetPackage(openPackage, path, request);
} catch (InvalidOperationException ex) {
// ignore error for unzipped packages (nuspec files).
if (!string.Equals(NuGetConstant.ManifestExtension, Path.GetExtension(path), StringComparison.OrdinalIgnoreCase)) {
request.Verbose(ex.Message);
throw;
}
}
if (package != null && package.Id.Equals(packageId, StringComparison.OrdinalIgnoreCase)) {
yield return package;
}
}
}
///
/// Finding the package in the file repository
///
/// Delegate function which is actually finding a package
/// File repository path
///
///
private static IPackage GetPackage(Func openPackage, string path, Request request)
{
//Create the package
IPackage package = openPackage(path, request);
return package;
}
///
/// A delegate used for finding the package.
///
/// File repository path
///
///
private static IPackage OpenPackage(string path, Request request) {
request.Debug(Resources.Messages.DebugInfoCallMethod3, "LocalPackageRepository", "OpenPackage", path);
if (!File.Exists(path)) {
request.Warning(Resources.Messages.FileNotFound, path,"LocalPackageRepository::OpenPackage");
return null;
}
//deal with .nupkg
if (string.Equals(Path.GetExtension(path), NuGetConstant.PackageExtension, StringComparison.OrdinalIgnoreCase)) {
PackageBase package;
try {
package = ProcessZipPackage(path);
} catch (Exception ex) {
request.Verbose(ex.Message);
throw;
}
// Set the last modified date on the package
package.Published = FileUtility.GetLastModified(path);
// We assume local files in the local repository are all latest version
package.IsAbsoluteLatestVersion = true;
package.IsLatestVersion = true;
package.FullFilePath = path;
return package;
}
return null;
}
///
/// Unzip the package and create PackageImpl object
///
/// The .nupkg file path
///
private static PackageBase ProcessZipPackage(string nupkgPath) {
string packageName = Path.GetFileNameWithoutExtension(nupkgPath);
packageName = PackageUtility.GetPackageNameWithoutVersionInfo(packageName);
PackageBase package = PackageUtility.DecompressFile(nupkgPath, packageName);
return package;
}
///
/// Get the .nupkg files
///
/// The file filter to be applied while finding the .nupkg files
///
private IEnumerable GetPackageFiles(string filter = null) {
filter = filter ?? "*" + NuGetConstant.PackageExtension;
// Check for package files one level deep. We use this at package install time
// to determine the set of installed packages. Installed packages are copied to
// {id}.{version}\{packagefile}.{extension}.
foreach (var dir in FileUtility.GetDirectories(_path))
//foreach (var dir in FileSystem.GetDirectories(String.Empty))
{
foreach (var path in FileUtility.GetFiles(dir, filter, recursive: false)) {
yield return path;
}
}
// Check top level directory
foreach (var path in FileUtility.GetFiles(_path, filter, recursive: false)) {
yield return path;
}
}
///
/// Find-Package
///
/// Package id
/// Package Name
///
///
public virtual IPackage FindPackage(string packageId, SemanticVersion version, NuGetRequest request)
{
return FindPackage(OpenPackage, packageId, version, request);
}
///
/// Find-Package
///
/// Delegate function which is actually finding a package
/// Package Id
/// Package version
///
///
private IPackage FindPackage(Func openPackage, string packageId, SemanticVersion version, NuGetRequest nugetRequest)
{
if (nugetRequest == null) {
return null;
}
nugetRequest.Debug(Resources.Messages.SearchingRepository, "FindPackage", packageId);
var lookupPackageName = new PackageName(packageId, version);
//handle file cache here if we want to support local package cache later
// Lookup files which start with the name "." and attempt to match it with all possible version string combinations (e.g. 1.2.0, 1.2.0.0)
// before opening the package. To avoid creating file name strings, we attempt to specifically match everything after the last path separator
// which would be the file name and extension.
return (from path in GetPackageLookupPaths(packageId, version)
let package = GetPackage(openPackage, path, nugetRequest)
where lookupPackageName.Equals(new PackageName(package.Id, package.Version))
select package).FirstOrDefault();
}
///
/// Find the package files (.nupkg or .nuspec).
///
///
///
///
private IEnumerable GetPackageLookupPaths(string packageId, SemanticVersion version) {
// Files created by the path resolver. This would take into account the non-side-by-side scenario
// and we do not need to match this for id and version.
//var packageFileName = packageId + version + Constant.PackageExtension;
var packageFileName = FileUtility.MakePackageFileName(false, packageId, version.ToString(), NuGetConstant.PackageExtension);
// var packageFileName = PathResolver.GetPackageFileName(packageId, version);
var manifestFileName = Path.ChangeExtension(packageFileName, NuGetConstant.ManifestExtension);
var filesMatchingFullName = GetPackageFiles(packageFileName).Concat(GetPackageFiles(manifestFileName));
if (version.Version.Revision < 1) {
// If the build or revision number is not set, we need to look for combinations of the format
// * Foo.1.2.nupkg
// * Foo.1.2.3.nupkg
// * Foo.1.2.0.nupkg
// * Foo.1.2.0.0.nupkg
// To achieve this, we would look for files named 1.2*.nupkg if both build and revision are 0 and
// 1.2.3*.nupkg if only the revision is set to 0.
string partialName = version.Version.Build < 1 ?
String.Join(".", packageId, version.Version.Major, version.Version.Minor) :
String.Join(".", packageId, version.Version.Major, version.Version.Minor, version.Version.Build);
string partialManifestName = partialName + "*" + NuGetConstant.ManifestExtension;
partialName += "*" + NuGetConstant.PackageExtension;
// Partial names would result is gathering package with matching major and minor but different build and revision.
// Attempt to match the version in the path to the version we're interested in.
var partialNameMatches = GetPackageFiles(partialName).Where(path => FileNameMatchesPattern(packageId, version, path));
var partialManifestNameMatches = GetPackageFiles(partialManifestName).Where(
path => FileNameMatchesPattern(packageId, version, path));
filesMatchingFullName = filesMatchingFullName.Concat(partialNameMatches).Concat(partialManifestNameMatches);
}
// cannot find matching files, we should try to search for just packageid.nupkg
if (filesMatchingFullName.Count() == 0)
{
// exclude version
var packageWithoutVersionName = FileUtility.MakePackageFileName(true, packageId, null, NuGetConstant.PackageExtension);
var packageWithoutVersionManifest = Path.ChangeExtension(packageWithoutVersionName, NuGetConstant.ManifestExtension);
return GetPackageFiles(packageWithoutVersionName).Concat(GetPackageFiles(packageWithoutVersionManifest));
}
return filesMatchingFullName;
}
///
/// True if the the file contains the right id and version.
///
/// package id
/// package version
/// File path
///
private static bool FileNameMatchesPattern(string packageId, SemanticVersion version, string path)
{
var name = Path.GetFileNameWithoutExtension(path);
SemanticVersion parsedVersion;
if (string.IsNullOrWhiteSpace(name))
{
return false;
}
// When matching by pattern, we will always have a version token. Packages without versions would be matched early on by the version-less path resolver
// when doing an exact match.
return name.Length > packageId.Length &&
SemanticVersion.TryParse(name.Substring(packageId.Length + 1), out parsedVersion) &&
parsedVersion == version;
}
///
/// Find-Package based the given Id
///
/// Package Id
///
///
public IEnumerable FindPackagesById(string packageId, NuGetRequest request)
{
return FindPackagesById(OpenPackage, packageId, request);
}
///
/// Find-Package based the given Id
///
/// Delegate function which is actually finding a package
/// Package version
///
///
private IEnumerable FindPackagesById(Func openPackage, string packageId, Request request)
{
request.Debug(Resources.Messages.DebugInfoCallMethod3, "LocalPackageRepository", "FindPackagesById", packageId);
// get packages in .nupkg or .nuspec files
// add "*" to avoid parsing the version (id: packageName+Version, e.g. jQuery.1.10.0)
return GetPackages(
openPackage,
packageId,
GetPackageFiles(packageId + "*" + NuGetConstant.PackageExtension),
request).Union(
GetPackages(
openPackage,
packageId,
GetPackageFiles(packageId + "*" + NuGetConstant.ManifestExtension),
request));
}
///
/// True if the packagesource is a file repository
///
public bool IsFile {
get {
//true because this is not a local file repository
return true;
}
}
///
/// Search the entire repository for the case when a user does not provider package name or uses wildcards in the name.
///
/// The Searchterm
///
///
public IEnumerable Search(string searchTerm, NuGetRequest nugetRequest)
{
var packages = SearchImpl(searchTerm, nugetRequest);
if (packages == null) {
return Enumerable.Empty();
}
if (nugetRequest != null && nugetRequest.AllVersions.Value) {
//return whatever we can find
return packages;
}
//return the latest version
return packages.GroupBy(p => p.Id).Select(each => each.OrderByDescending(pp => pp.Version).FirstOrDefault());
}
private IEnumerable SearchImpl(string searchTerm, NuGetRequest nugetRequest)
{
if (nugetRequest == null) {
yield break;
}
nugetRequest.Debug(Resources.Messages.SearchingRepository, "LocalPackageRepository", Source);
var files = Directory.GetFiles(Source);
foreach (var package in nugetRequest.FilterOnTags(files.Select(nugetRequest.GetPackageByFilePath).Where(pkgItem => pkgItem != null).Select(pkg => pkg.Package)))
{
yield return package;
}
// look in the package source location for directories that contain nupkg files.
var subdirs = Directory.EnumerateDirectories(Source, "*", SearchOption.AllDirectories);
foreach (var subdir in subdirs) {
var nupkgs = Directory.EnumerateFileSystemEntries(subdir, "*.nupkg", SearchOption.TopDirectoryOnly);
foreach (var package in nugetRequest.FilterOnTags(nupkgs.Select(nugetRequest.GetPackageByFilePath).Where(pkgItem => pkgItem != null).Select(pkg => pkg.Package)))
{
yield return package;
}
}
}
}
}