Ejemplo n.º 1
0
        /// <inheritdoc cref="IDownloader.Download(IPackage, string)" />
        public virtual Task Download(IPackage package, string cwd, bool output)
        {
            if (string.IsNullOrEmpty(package.GetDistUri()))
            {
                throw new RuntimeException("The given package is missing url information.");
            }

            var downloadedFilePath = GetDownloadedFilePath(package, cwd);

            fileSystem.Delete(cwd);

            void DoDownload(string uri)
            {
                var processedUri = ProcessUri(package, uri);

                if (eventDispatcher != null)
                {
                    var preFileDownloadEvent = new PreFileDownloadEventArgs(PluginEvents.PreFileDownload, transport, processedUri);
                    eventDispatcher.Dispatch(this, preFileDownloadEvent);
                }

                try
                {
                    var checksum = package.GetDistShasum();
                    var cacheKey = GetCacheKey(package, processedUri);

                    void DownloadFromHttp(int retries = 3)
                    {
                        SException processException = null;

                        while (retries-- > 0)
                        {
                            try
                            {
                                transport.Copy(
                                    processedUri,
                                    downloadedFilePath,
                                    new ReportDownloadProgress(io, $"{GetDownloadingPrompt(package)}: "));
                                break;
                            }
                            catch (TransportException ex)
                            {
                                // clean up immediately. otherwise it will lead to failure forever.
                                fileSystem.Delete(downloadedFilePath);

                                // if we got an http response with a proper code, then
                                // requesting again will probably not help, abort.
                                var serverProblems = new[]
                                {
                                    HttpStatusCode.InternalServerError,
                                    HttpStatusCode.BadGateway,
                                    HttpStatusCode.ServiceUnavailable,
                                    HttpStatusCode.GatewayTimeout,
                                };

                                if (ex.HttpStatusCode != 0 &&
                                    (!Array.Exists(serverProblems, (code) => code == ex.HttpStatusCode) || retries <= 0))
                                {
                                    throw;
                                }

                                processException = ex;
                                Thread.Sleep(500);
                            }
                        }

                        if (processException != null)
                        {
                            ExceptionDispatchInfo.Capture(processException).Throw();
                        }

                        if (cache != null && cache.CopyFrom(cacheKey, downloadedFilePath))
                        {
                            lastCacheWrites[package.GetName()] = cacheKey;
                        }
                    }

                    // use from cache if it is present and has a valid checksum
                    // or we have no checksum to check against
                    if (cache != null && cache.Sha1(cacheKey, checksum) && cache.CopyTo(cacheKey, downloadedFilePath))
                    {
                        if (output)
                        {
                            io.WriteError($"  - Loading <info>{package.GetName()}</info> (<comment>{package.GetVersionPrettyFull()}</comment>) from cache", false);
                        }
                    }
                    else
                    {
                        if (output)
                        {
                            io.WriteError(GetDownloadingPrompt(package), false);
                        }

                        DownloadFromHttp();
                    }

                    // Verify the file to ensure that the file is not damaged.
                    if (!fileSystem.Exists(downloadedFilePath, FileSystemOptions.File))
                    {
                        throw new UnexpectedException($"{uri} could not be saved to {downloadedFilePath} , make sure the directory is writable and you have internet connectivity.");
                    }

                    if (!string.IsNullOrEmpty(checksum))
                    {
                        using (var stream = fileSystem.Read(downloadedFilePath))
                        {
                            if (Security.Sha1(stream) != checksum)
                            {
                                throw new UnexpectedException($"The checksum verification of the file failed (downloaded from {uri})");
                            }
                        }
                    }
                }
                catch
                {
                    fileSystem.Delete(downloadedFilePath);
                    ClearLastCacheWrite(package);
                    throw;
                }
                finally
                {
                    if (output)
                    {
                        io.OverwriteError(string.Empty, false);
                    }
                }
            }

            void Start()
            {
                var uris = package.GetDistUris();

                // The performance popped from the tail will be better
                // than popping out from the head
                Array.Reverse(uris);
                while (uris.Length > 0)
                {
                    var uri = Arr.Pop(ref uris);
                    try
                    {
                        DoDownload(uri);
                        break;
                    }
                    catch (SException ex)
                    {
                        if (io.IsDebug)
                        {
                            io.WriteError(string.Empty);
                            io.WriteError($"Faild: [{ex.GetType()}] {ex.Message}");
                        }
                        else if (uris.Length > 0)
                        {
                            io.WriteError(string.Empty);
                            io.WriteError($" Failed, trying the next URL ({ex.Message})");
                        }

                        if (uris.Length <= 0)
                        {
                            throw;
                        }
                    }
                }
            }

            return(Task.Run(Start));
        }