示例#1
0
        private static async Task <List <PackageInfo> > LoadAllDependenciesAsync()
        {
            ServicePointManager.DefaultConnectionLimit = 64;
            var feed = new Uri("https://api.nuget.org/v3/index.json");

            using (var catalog = new CatalogReader(feed, TimeSpan.FromDays(1)))
            {
                Console.WriteLine("Loading latest package IDs");
                // The documentation lies: this retrieves all entries. Ignore pre-release,
                // and find the latest version for each package.
                var catalogEntries = (await catalog.GetFlattenedEntriesAsync())
                                     .Where(p => !p.Version.IsPrerelease)
                                     .Where(p => !p.IsDelete)
                                     .GroupBy(p => p.Id)
                                     // Spam... there must be a better way of detecting this.
                                     .Where(g => !g.Key.Contains("1-800") && !g.Key.ToLowerInvariant().Contains("-phone-") && !g.Key.ToLowerInvariant().Contains("-number-"))
                                     .Select(g => g.OrderByDescending(p => p.Version).First())
                                     .ToList();

                Console.WriteLine($"Loading dependencies for {catalogEntries.Count} packages");
                return(catalogEntries
                       .AsParallel()
                       .WithDegreeOfParallelism(16)
                       .Select(GetPackageInfo)
                       .Where(p => p != null)
                       .ToList());
            }
        }
        public async Task VerifyEditsAreIgnoredInFlattenedViewAsync()
        {
            // Arrange
            using (var cache = new LocalCache())
                using (var cacheContext = new SourceCacheContext())
                    using (var workingDir = new TestFolder())
                    {
                        var log          = new TestLogger();
                        var baseUri      = Sleet.UriUtility.CreateUri("https://localhost:8080/testFeed/");
                        var feedFolder   = Path.Combine(workingDir, "feed");
                        var nupkgsFolder = Path.Combine(workingDir, "nupkgs");
                        Directory.CreateDirectory(feedFolder);
                        Directory.CreateDirectory(nupkgsFolder);

                        var packageA = new TestNupkg("a", "1.0.0");
                        TestNupkg.Save(nupkgsFolder, packageA);

                        // Create and push
                        await CatalogReaderTestHelpers.CreateCatalogAsync(workingDir, feedFolder, nupkgsFolder, baseUri, log);

                        // 2nd push
                        await CatalogReaderTestHelpers.PushPackagesAsync(workingDir, nupkgsFolder, baseUri, log);

                        // 3rd push
                        await CatalogReaderTestHelpers.PushPackagesAsync(workingDir, nupkgsFolder, baseUri, log);

                        var feedUri    = Sleet.UriUtility.CreateUri(baseUri.AbsoluteUri + "index.json");
                        var httpSource = CatalogReaderTestHelpers.GetHttpSource(cache, feedFolder, baseUri);

                        // Act
                        using (var catalogReader = new CatalogReader(feedUri, httpSource, cacheContext, TimeSpan.FromMinutes(1), log))
                        {
                            var entries = await catalogReader.GetEntriesAsync();

                            var flatEntries = await catalogReader.GetFlattenedEntriesAsync();

                            var set = await catalogReader.GetPackageSetAsync();

                            var entry = entries.FirstOrDefault();

                            // Assert
                            // 3 adds, 2 removes
                            Assert.Equal(5, entries.Count);
                            Assert.Equal(1, flatEntries.Count);
                            Assert.Equal(1, set.Count);

                            Assert.Equal("a", entry.Id);
                            Assert.Equal("1.0.0", entry.Version.ToNormalizedString());
                        }
                    }
        }
        public async Task VerifyNoEntriesWhenReadingAnEmptyCatalogAsync()
        {
            // Arrange
            using (var cache = new LocalCache())
                using (var cacheContext = new SourceCacheContext())
                    using (var workingDir = new TestFolder())
                    {
                        var log          = new TestLogger();
                        var baseUri      = Sleet.UriUtility.CreateUri("https://localhost:8080/testFeed/");
                        var feedFolder   = Path.Combine(workingDir, "feed");
                        var nupkgsFolder = Path.Combine(workingDir, "nupkgs");
                        Directory.CreateDirectory(feedFolder);
                        Directory.CreateDirectory(nupkgsFolder);

                        await CatalogReaderTestHelpers.CreateCatalogAsync(workingDir, feedFolder, nupkgsFolder, baseUri, log);

                        var feedUri = Sleet.UriUtility.CreateUri(baseUri.AbsoluteUri + "index.json");

                        var httpSource = CatalogReaderTestHelpers.GetHttpSource(cache, feedFolder, baseUri);

                        // Act
                        using (var catalogReader = new CatalogReader(feedUri, httpSource, cacheContext, TimeSpan.FromMinutes(1), log))
                        {
                            var entries = await catalogReader.GetEntriesAsync();

                            var flatEntries = await catalogReader.GetFlattenedEntriesAsync();

                            var set = await catalogReader.GetPackageSetAsync();

                            // Assert
                            Assert.Empty(entries);
                            Assert.Empty(flatEntries);
                            Assert.Empty(set);
                        }
                    }
        }
示例#4
0
        private static void Run(CommandLineApplication cmd, HttpSource httpSource, ILogger log)
        {
            cmd.Description = "List packages from a v3 source.";

            var start   = cmd.Option("-s|--start", "Beginning of the commit time range. Packages commited AFTER this time will be included.", CommandOptionType.SingleValue);
            var end     = cmd.Option("-e|--end", "End of the commit time range. Packages commited at this time will be included.", CommandOptionType.SingleValue);
            var verbose = cmd.Option("-v|--verbose", "Write out additional network call information.", CommandOptionType.NoValue);

            var argRoot = cmd.Argument(
                "[root]",
                "V3 feed index.json URI",
                multipleValues: false);

            cmd.HelpOption(Constants.HelpOption);

            cmd.OnExecute(async() =>
            {
                try
                {
                    if (string.IsNullOrEmpty(argRoot.Value))
                    {
                        throw new ArgumentException("Provide the full http url to a v3 nuget feed.");
                    }

                    var index = new Uri(argRoot.Value);

                    if (!index.AbsolutePath.EndsWith("/index.json", StringComparison.OrdinalIgnoreCase))
                    {
                        throw new ArgumentException($"Invalid feed url: '{argRoot.Value}'. Provide the full http url to a v3 nuget feed.");
                    }

                    var startTime = DateTimeOffset.MinValue;
                    var endTime   = DateTimeOffset.UtcNow;

                    if (start.HasValue())
                    {
                        startTime = DateTimeOffset.Parse(start.Value());
                    }

                    if (end.HasValue())
                    {
                        endTime = DateTimeOffset.Parse(end.Value());
                    }

                    if (log is ConsoleLogger consoleLogger)
                    {
                        if (verbose.HasValue())
                        {
                            consoleLogger.VerbosityLevel = LogLevel.Information;
                        }
                        else
                        {
                            consoleLogger.VerbosityLevel = LogLevel.Minimal;
                        }
                    }

                    // CatalogReader
                    using (var cacheContext = new SourceCacheContext())
                        using (var catalogReader = new CatalogReader(index, httpSource, cacheContext, TimeSpan.Zero, log))
                        {
                            var entries = await catalogReader.GetFlattenedEntriesAsync(startTime, endTime, CancellationToken.None);

                            foreach (var entry in entries
                                     .OrderBy(e => e.Id, StringComparer.OrdinalIgnoreCase)
                                     .ThenBy(e => e.Version))
                            {
                                log.LogMinimal($"{entry.Id} {entry.Version.ToFullString()}");
                            }
                        };

                    return(0);
                }
                catch (Exception ex)
                {
                    ExceptionUtils.LogException(ex, log);
                }

                return(1);
            });
        }
示例#5
0
        private static void Run(CommandLineApplication cmd, HttpSource httpSource, ILogger consoleLog)
        {
            cmd.Description = "Mirror nupkgs to a folder.";

            var output            = cmd.Option("-o|--output", "Output directory for nupkgs.", CommandOptionType.SingleValue);
            var folderFormat      = cmd.Option("--folder-format", "Output folder format. Defaults to v3. Options: (v2|v3)", CommandOptionType.SingleValue);
            var ignoreErrors      = cmd.Option("--ignore-errors", "Continue on errors.", CommandOptionType.NoValue);
            var delay             = cmd.Option("--delay", "Avoid downloading the very latest packages on the feed to avoid errors. This value is in minutes. Default: 10", CommandOptionType.SingleValue);
            var maxThreadsOption  = cmd.Option("--max-threads", "Maximum number of concurrent downloads. Default: 8", CommandOptionType.SingleValue);
            var verbose           = cmd.Option("--verbose", "Output additional network information.", CommandOptionType.NoValue);
            var includeIdOption   = cmd.Option("-i|--include-id", "Include only these package ids or wildcards. May be provided multiple times.", CommandOptionType.MultipleValue);
            var excludeIdOption   = cmd.Option("-e|--exclude-id", "Exclude these package ids or wildcards. May be provided multiple times.", CommandOptionType.MultipleValue);
            var additionalOutput  = cmd.Option("--additional-output", "Additional output directory for nupkgs. The output path with the most free space will be used.", CommandOptionType.MultipleValue);
            var onlyLatestVersion = cmd.Option("--latest-only", "Include only the latest version of that package in the result", CommandOptionType.NoValue);

            var argRoot = cmd.Argument(
                "[root]",
                "V3 feed index.json URI",
                multipleValues: false);

            cmd.HelpOption(Constants.HelpOption);

            cmd.OnExecute(async() =>
            {
                var timer = new Stopwatch();
                timer.Start();

                if (string.IsNullOrEmpty(argRoot.Value))
                {
                    throw new ArgumentException("Provide the full http url to a v3 nuget feed.");
                }

                var index = new Uri(argRoot.Value);

                if (!index.AbsolutePath.EndsWith("/index.json", StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException($"Invalid feed url: '{argRoot.Value}'. Provide the full http url to a v3 nuget feed. For nuget.org use: https://api.nuget.org/v3/index.json");
                }

                // Create root
                var outputPath = Directory.GetCurrentDirectory();

                if (output.HasValue())
                {
                    outputPath = output.Value();
                }

                var tmpCachePath = Path.Combine(outputPath, ".tmp");

                var storagePaths = new HashSet <DirectoryInfo>()
                {
                    new DirectoryInfo(outputPath)
                };

                if (additionalOutput.Values?.Any() == true)
                {
                    storagePaths.UnionWith(additionalOutput.Values.Select(e => new DirectoryInfo(e)));
                }

                // Create all output folders
                foreach (var path in storagePaths)
                {
                    path.Create();
                }

                var delayTime = TimeSpan.FromMinutes(10);

                if (delay.HasValue())
                {
                    if (int.TryParse(delay.Value(), out int x))
                    {
                        var delayMinutes = Math.Max(0, x);
                        delayTime        = TimeSpan.FromMinutes(delayMinutes);
                    }
                    else
                    {
                        throw new ArgumentException("Invalid --delay value. This must be an integer.");
                    }
                }

                var maxThreads = 8;

                if (maxThreadsOption.HasValue())
                {
                    if (int.TryParse(maxThreadsOption.Value(), out int x))
                    {
                        maxThreads = Math.Max(1, x);
                    }
                    else
                    {
                        throw new ArgumentException("Invalid --max-threads value. This must be an integer.");
                    }
                }

                var batchSize = 64;

                var outputRoot      = new DirectoryInfo(outputPath);
                var outputFilesInfo = new FileInfo(Path.Combine(outputRoot.FullName, "updatedFiles.txt"));
                FileUtility.Delete(outputFilesInfo.FullName);

                var useV3Format = true;

                if (folderFormat.HasValue())
                {
                    switch (folderFormat.Value().ToLowerInvariant())
                    {
                    case "v2":
                        useV3Format = false;
                        break;

                    case "v3":
                        useV3Format = true;
                        break;

                    default:
                        throw new ArgumentException($"Invalid {folderFormat.LongName} value: '{folderFormat.Value()}'.");
                    }
                }

                var start = MirrorUtility.LoadCursor(outputRoot);
                var end   = DateTimeOffset.UtcNow.Subtract(delayTime);
                var token = CancellationToken.None;
                var mode  = DownloadMode.OverwriteIfNewer;

                var errorLogPath = Path.Combine(outputPath, "lastRunErrors.txt");
                FileUtility.Delete(errorLogPath);

                // Loggers
                // source -> deep -> file -> Console
                var log        = new FileLogger(consoleLog, LogLevel.Error, errorLogPath);
                var deepLogger = new FilterLogger(log, LogLevel.Error);

                // Init
                log.LogInformation($"Mirroring {index.AbsoluteUri} -> {outputPath}");

                var formatName = useV3Format ? "{id}/{version}/{id}.{version}.nupkg" : "{id}/{id}.{version}.nupkg";
                log.LogInformation($"Folder format:\t{formatName}");

                log.LogInformation($"Cursor:\t\t{Path.Combine(outputPath, "cursor.json")}");
                log.LogInformation($"Change log:\t{outputFilesInfo.FullName}");
                log.LogInformation($"Error log:\t{errorLogPath}");

                log.LogInformation("Range start:\t" + start.ToString("o"));
                log.LogInformation("Range end:\t" + end.ToString("o"));
                log.LogInformation($"Batch size:\t{batchSize}");
                log.LogInformation($"Threads:\t{maxThreads}");

                // CatalogReader
                using (var cacheContext = new SourceCacheContext())
                {
                    cacheContext.SetTempRoot(tmpCachePath);

                    using (var catalogReader = new CatalogReader(index, httpSource, cacheContext, TimeSpan.Zero, deepLogger))
                    {
                        // Clear old cache files
                        catalogReader.ClearCache();

                        // Find the most recent entry for each package in the range
                        // Order by oldest first
                        IEnumerable <CatalogEntry> entryQuery = (await catalogReader
                                                                 .GetFlattenedEntriesAsync(start, end, token));

                        // Remove all but includes if given
                        if (includeIdOption.HasValue())
                        {
                            var regex = includeIdOption.Values.Select(s => MirrorUtility.WildcardToRegex(s)).ToArray();

                            entryQuery = entryQuery.Where(e =>
                                                          regex.Any(r => r.IsMatch(e.Id)));
                        }

                        // Remove all excludes if given
                        if (excludeIdOption.HasValue())
                        {
                            var regex = excludeIdOption.Values.Select(s => MirrorUtility.WildcardToRegex(s)).ToArray();

                            entryQuery = entryQuery.Where(e =>
                                                          regex.All(r => !r.IsMatch(e.Id)));
                        }

                        if (onlyLatestVersion.HasValue())
                        {
                            entryQuery = entryQuery.GroupBy(x => x.Id).Select(y => y.OrderByDescending(z => z.Version).First());
                        }

                        var toProcess = new Queue <CatalogEntry>(entryQuery.OrderBy(e => e.CommitTimeStamp));

                        log.LogInformation($"Catalog entries found: {toProcess.Count}");

                        var done           = new List <CatalogEntry>(batchSize);
                        var complete       = 0;
                        var total          = toProcess.Count;
                        var totalDownloads = 0;

                        // Download files
                        var tasks          = new List <Task <NupkgResult> >(maxThreads);
                        var batchTimersMax = 20;
                        var batchTimers    = new Queue <Tuple <Stopwatch, int> >(batchTimersMax);

                        // Download with throttling
                        while (toProcess.Count > 0)
                        {
                            // Create batches
                            var batch      = new Queue <CatalogEntry>(batchSize);
                            var files      = new List <string>();
                            var batchTimer = new Stopwatch();
                            batchTimer.Start();

                            while (toProcess.Count > 0 && batch.Count < batchSize)
                            {
                                batch.Enqueue(toProcess.Dequeue());
                            }

                            while (batch.Count > 0)
                            {
                                if (tasks.Count == maxThreads)
                                {
                                    await CompleteTaskAsync(files, tasks, done);
                                }

                                var entry = batch.Dequeue();

                                Func <CatalogEntry, Task <FileInfo> > getNupkg = null;

                                if (useV3Format)
                                {
                                    getNupkg = (e) => DownloadNupkgV3Async(e, storagePaths, mode, log, deepLogger, token);
                                }
                                else
                                {
                                    getNupkg = (e) => DownloadNupkgV2Async(e, storagePaths, mode, log, token);
                                }

                                // Queue download task
                                tasks.Add(Task.Run(async() => await RunWithRetryAsync(entry, ignoreErrors.HasValue(), getNupkg, log, token)));
                            }

                            // Wait for all batch downloads
                            while (tasks.Count > 0)
                            {
                                await CompleteTaskAsync(files, tasks, done);
                            }

                            files = files.Where(e => e != null).ToList();

                            // Write out new files
                            using (var newFileWriter = new StreamWriter(new FileStream(outputFilesInfo.FullName, FileMode.Append, FileAccess.Write)))
                            {
                                foreach (var file in files)
                                {
                                    newFileWriter.WriteLine(file);
                                }
                            }

                            complete       += done.Count;
                            totalDownloads += files.Count;
                            batchTimer.Stop();
                            batchTimers.Enqueue(new Tuple <Stopwatch, int>(batchTimer, done.Count));

                            while (batchTimers.Count > batchTimersMax)
                            {
                                batchTimers.Dequeue();
                            }

                            // Update cursor
                            var newestCommit = GetNewestCommit(done, toProcess);
                            if (newestCommit != null)
                            {
                                log.LogMinimal($"================[batch complete]================");
                                log.LogMinimal($"Processed:\t\t{complete} / {total}");
                                log.LogMinimal($"Batch downloads:\t{files.Count}");
                                log.LogMinimal($"Batch time:\t\t{batchTimer.Elapsed}");
                                log.LogMinimal($"Updating cursor.json:\t{newestCommit.Value.ToString("o")}");

                                var rate     = batchTimers.Sum(e => e.Item1.Elapsed.TotalSeconds) / Math.Max(1, batchTimers.Sum(e => e.Item2));
                                var timeLeft = TimeSpan.FromSeconds(rate * (total - complete));

                                var timeLeftString = string.Empty;

                                if (timeLeft.TotalHours >= 1)
                                {
                                    timeLeftString = $"{(int)timeLeft.TotalHours} hours";
                                }
                                else if (timeLeft.TotalMinutes >= 1)
                                {
                                    timeLeftString = $"{(int)timeLeft.TotalMinutes} minutes";
                                }
                                else
                                {
                                    timeLeftString = $"{(int)timeLeft.TotalSeconds} seconds";
                                }

                                log.LogMinimal($"Estimated time left:\t{timeLeftString}");
                                log.LogMinimal($"================================================");

                                MirrorUtility.SaveCursor(outputRoot, newestCommit.Value);
                            }

                            done.Clear();

                            // Free up space
                            catalogReader.ClearCache();
                        }

                        // Set cursor to end time
                        MirrorUtility.SaveCursor(outputRoot, end);

                        timer.Stop();

                        var plural = totalDownloads == 1 ? "" : "s";
                        log.LogMinimal($"Downloaded {totalDownloads} nupkg{plural} in {timer.Elapsed.ToString()}.");
                    }
                }

                return(0);
            });
        }