Пример #1
0
 public async Task<PackageSpec> GetInstallablePackageAsync(PackageSpec package, CancellationToken cancellationToken) {
     if (!package.IsValid) {
         return new PackageSpec();
     }
     await Task.Delay(10);
     return _installable.FirstOrDefault(p => p.Name == package.Name) ?? new PackageSpec();
 }
Пример #2
0
 public async Task<bool> InstallAsync(PackageSpec package, IPackageManagerUI ui, CancellationToken cancellationToken) {
     ui?.OnOperationStarted(this, "install " + package.FullSpec);
     _installed.Add(package);
     await Task.Delay(100, cancellationToken);
     _seenChange = true;
     ui?.OnOperationFinished(this, "install " + package.FullSpec, true);
     return true;
 }
Пример #3
0
 public InterpretersPackageNode(PythonProjectNode project, PackageSpec spec)
     : base(project, new VirtualProjectElement(project)) {
     ExcludeNodeFromScc = true;
     _package = spec.Clone();
     _packageName = spec.FullSpec;
     if (spec.ExactVersion.IsEmpty) {
         _caption = spec.Name;
         _canUninstall = false;
     } else {
         _caption = string.Format("{0} ({1})", spec.Name, spec.ExactVersion);
         _canUninstall = !CannotUninstall.Contains(spec.Name);
     }
 }
Пример #4
0
        public async Task <PackageSpec> GetPackageInfoAsync(PackageSpec entry, CancellationToken cancel)
        {
            string        description = null;
            List <string> versions    = null;

            bool useCache = IsInLiveCache(entry.Name);

            if (useCache)
            {
                using (await _cacheLock.LockAsync(cancel)) {
                    PackageSpec result;
                    if (_cache.TryGetValue(entry.Name, out result))
                    {
                        return(result.Clone());
                    }
                    else
                    {
                        return(new PackageSpec());
                    }
                }
            }

            TaskCompletionSource <PackageSpec> tcs = null;
            Task <PackageSpec> t = null;

            lock (_activeRequests) {
                if (_activeRequests.TryGetValue(entry.Name, out tcs))
                {
                    t = tcs.Task;
                }
                else
                {
                    _activeRequests[entry.Name] = tcs = new TaskCompletionSource <PackageSpec>();
                }
            }

            if (t != null)
            {
                return((await t).Clone());
            }

            try {
                using (var client = new WebClient()) {
                    Stream data;
                    client.Headers[HttpRequestHeader.UserAgent] = UserAgent;
                    try {
                        data = await client.OpenReadTaskAsync(new Uri(Index, entry.Name + "/json"))
                               .ConfigureAwait(false);
                    } catch (WebException) {
                        // No net access or no such package
                        AddToLiveCache(entry.Name);
                        return(new PackageSpec());
                    }

                    try {
                        using (var reader = JsonReaderWriterFactory.CreateJsonReader(data, new XmlDictionaryReaderQuotas())) {
                            var doc = XDocument.Load(reader);

                            // TODO: Get package URL
                            //url = (string)doc.Document
                            //    .Elements("root")
                            //    .Elements("info")
                            //    .Elements("package_url")
                            //    .FirstOrDefault();

                            description = (string)doc.Document
                                          .Elements("root")
                                          .Elements("info")
                                          .Elements("description")
                                          .FirstOrDefault();

                            versions = doc.Document
                                       .Elements("root")
                                       .Elements("releases")
                                       .Elements()
                                       .Attributes("item")
                                       .Select(a => a.Value)
                                       .ToList();
                        }
                    } catch (InvalidOperationException) {
                    }
                }

                bool        changed = false;
                PackageSpec result;

                using (await _cacheLock.LockAsync(cancel)) {
                    if (!_cache.TryGetValue(entry.Name, out result))
                    {
                        result = _cache[entry.Name] = new PackageSpec(entry.Name);
                    }

                    if (!string.IsNullOrEmpty(description))
                    {
                        var lines     = description.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
                        var firstLine = string.Join(
                            " ",
                            lines.TakeWhile(s => !IsSeparatorLine(s)).Select(s => s.Trim())
                            );
                        if (firstLine.Length > 500)
                        {
                            firstLine = firstLine.Substring(0, 497) + "...";
                        }
                        if (firstLine == "UNKNOWN")
                        {
                            firstLine = string.Empty;
                        }

                        result.Description = firstLine;
                        changed            = true;
                    }

                    if (versions != null)
                    {
                        var updateVersion = PackageVersion.TryParseAll(versions)
                                            .Where(v => v.IsFinalRelease)
                                            .OrderByDescending(v => v)
                                            .FirstOrDefault();
                        result.ExactVersion = updateVersion;
                        changed             = true;
                    }
                }

                if (changed)
                {
                    TriggerWriteCacheToDisk();
                    AddToLiveCache(entry.Name);
                }

                var r = result.Clone();

                // Inform other waiters that we have completed
                tcs.TrySetResult(r);

                return(r);
            } catch (Exception ex) {
                tcs.TrySetException(ex);
                throw;
            } finally {
                lock (_activeRequests) {
                    _activeRequests.Remove(entry.Name);
                }
            }
        }
Пример #5
0
 public async Task<PackageSpec> GetInstallablePackageAsync(PackageSpec package, CancellationToken cancellationToken) {
     if (!package.IsValid) {
         return package;
     }
     return await _cache.GetPackageInfoAsync(package, cancellationToken);
 }
Пример #6
0
 public async Task<PackageSpec> GetInstalledPackageAsync(PackageSpec package, CancellationToken cancellationToken) {
     if (!package.IsValid) {
         return package;
     }
     using (await _working.LockAsync(cancellationToken)) {
         if (!_everCached) {
             await CacheInstalledPackagesAsync(true, false, cancellationToken);
         }
         return _packages.FirstOrDefault(p => p.Name == package.Name) ?? new PackageSpec(null);
     }
 }
Пример #7
0
        public async Task<bool> UninstallAsync(PackageSpec package, IPackageManagerUI ui, CancellationToken cancellationToken) {
            AbortOnInvalidConfiguration();
            await AbortIfNotReady(cancellationToken);

            bool success = false;
            var args = _extraInterpreterArgs.ToList();

            if (!SupportsDashMPip) {
                args.Add("-c");
                args.Add("\"import pip; pip.main()\"");
            } else {
                args.Add("-m");
                args.Add("pip");
            }
            args.Add("uninstall");
            args.Add("-y");

            args.Add(package.FullSpec);
            var name = string.IsNullOrEmpty(package.Name) ? package.FullSpec : package.Name;
            var operation = string.Join(" ", args);

            try {
                using (await _working.LockAsync(cancellationToken)) {
                    ui?.OnOperationStarted(this, operation);
                    ui?.OnOutputTextReceived(this, Strings.InstallingPackageStarted.FormatUI(name));

                    using (var output = ProcessOutput.Run(
                        _factory.Configuration.InterpreterPath,
                        args,
                        _factory.Configuration.PrefixPath,
                        UnbufferedEnv,
                        false,
                        PackageManagerUIRedirector.Get(this, ui),
                        elevate: await ShouldElevate(ui, operation)
                    )) {
                        if (!output.IsStarted) {
                            // The finally block handles output
                            return false;
                        }
                        var exitCode = await output;
                        success = exitCode == 0;
                    }
                    return success;
                }
            } catch (IOException) {
                return false;
            } finally {
                if (!success) {
                    // Check whether we failed because pip is missing
                    UpdateIsReadyAsync(false, CancellationToken.None).DoNotWait();
                }

                if (IsReady) {
                    await CacheInstalledPackagesAsync(false, false, cancellationToken);
                    if (!success) {
                        // Double check whether the package has actually
                        // been uninstalled, to avoid reporting errors 
                        // where, for all practical purposes, there is no
                        // error.
                        if (!(await GetInstalledPackageAsync(package, cancellationToken)).IsValid) {
                            success = true;
                        }
                    }
                }

                var msg = success ? Strings.UninstallingPackageSuccess : Strings.UninstallingPackageFailed;
                ui?.OnOutputTextReceived(this, msg.FormatUI(name));
                ui?.OnOperationFinished(this, operation, success);
            }
        }
Пример #8
0
        public async Task <bool> InstallAsync(PackageSpec package, IPackageManagerUI ui, CancellationToken cancellationToken)
        {
            AbortOnInvalidConfiguration();
            await AbortIfNotReady(cancellationToken);

            bool success = false;
            var  args    = _extraInterpreterArgs.ToList();

            if (!SupportsDashMPip)
            {
                args.Add("-c");
                args.Add("\"import pip; pip.main()\"");
            }
            else
            {
                args.Add("-m");
                args.Add("pip");
            }
            args.Add("install");

            args.Add(package.FullSpec);
            var name      = string.IsNullOrEmpty(package.Name) ? package.FullSpec : package.Name;
            var operation = string.Join(" ", args);

            using (await _working.LockAsync(cancellationToken)) {
                ui?.OnOperationStarted(this, operation);
                ui?.OnOutputTextReceived(this, Strings.InstallingPackageStarted.FormatUI(name));

                try {
                    using (var output = ProcessOutput.Run(
                               _factory.Configuration.InterpreterPath,
                               args,
                               _factory.Configuration.PrefixPath,
                               UnbufferedEnv,
                               false,
                               PackageManagerUIRedirector.Get(this, ui),
                               quoteArgs: false,
                               elevate: await ShouldElevate(ui, operation)
                               )) {
                        if (!output.IsStarted)
                        {
                            return(false);
                        }
                        var exitCode = await output;
                        success = exitCode == 0;
                    }
                    return(success);
                } catch (IOException) {
                    return(false);
                } finally {
                    if (!success)
                    {
                        // Check whether we failed because pip is missing
                        UpdateIsReadyAsync(true, CancellationToken.None).DoNotWait();
                    }

                    var msg = success ? Strings.InstallingPackageSuccess : Strings.InstallingPackageFailed;
                    ui?.OnOutputTextReceived(this, msg.FormatUI(name));
                    ui?.OnOperationFinished(this, operation, success);
                    await CacheInstalledPackagesAsync(true, cancellationToken);
                }
            }
        }
Пример #9
0
 public void AddInstallable(PackageSpec package) {
     _installable.Add(package);
 }
Пример #10
0
 public Task<bool> UninstallAsync(PackageSpec package, IPackageManagerUI ui, CancellationToken cancellationToken) {
     throw new NotSupportedException();
 }
Пример #11
0
        public async Task <bool> UninstallAsync(PackageSpec package, IPackageManagerUI ui, CancellationToken cancellationToken)
        {
            AbortOnInvalidConfiguration();
            await AbortIfNotReady(cancellationToken);

            bool success = false;
            var  args    = new List <string>();

            args.Add("uninstall");
            args.Add("-p");
            args.Add(ProcessOutput.QuoteSingleArgument(_factory.Configuration.GetPrefixPath()));
            args.Add("-y");

            args.Add(package.Name);
            var name      = string.IsNullOrEmpty(package.Name) ? package.FullSpec : package.Name;
            var operation = string.Join(" ", args);

            try {
                using (await _working.LockAsync(cancellationToken)) {
                    ui?.OnOperationStarted(this, operation);
                    ui?.OnOutputTextReceived(this, Strings.UninstallingPackageStarted.FormatUI(name));

                    using (var output = ProcessOutput.Run(
                               _condaPath,
                               args,
                               _factory.Configuration.GetPrefixPath(),
                               UnbufferedEnv,
                               false,
                               PackageManagerUIRedirector.Get(this, ui),
                               elevate: await ShouldElevate(ui, operation)
                               )) {
                        if (!output.IsStarted)
                        {
                            // The finally block handles output
                            return(false);
                        }
                        var exitCode = await output;
                        success = exitCode == 0;
                    }
                    return(success);
                }
            } catch (IOException) {
                return(false);
            } finally {
                if (!success)
                {
                    // Check whether we failed because conda is missing
                    UpdateIsReadyAsync(false, CancellationToken.None).DoNotWait();
                }

                if (IsReady)
                {
                    await CacheInstalledPackagesAsync(false, false, cancellationToken);

                    if (!success)
                    {
                        // Double check whether the package has actually
                        // been uninstalled, to avoid reporting errors
                        // where, for all practical purposes, there is no
                        // error.
                        if (!(await GetInstalledPackageAsync(package, cancellationToken)).IsValid)
                        {
                            success = true;
                        }
                    }
                }

                var msg = success ? Strings.UninstallingPackageSuccess : Strings.UninstallingPackageFailed;
                ui?.OnOutputTextReceived(this, msg.FormatUI(name));
                ui?.OnOperationFinished(this, operation, success);
            }
        }
Пример #12
0
 public Task<PackageSpec> GetInstalledPackageAsync(PackageSpec package, CancellationToken cancellationToken) {
     if (!package.IsValid) {
         return Task.FromResult(new PackageSpec());
     }
     return Task.FromResult(_installed.FirstOrDefault(p => p.Name == package.Name) ?? new PackageSpec());
 }
Пример #13
0
        private async Task CacheInstalledPackagesAsync(
            bool alreadyHasLock,
            bool alreadyHasConcurrencyLock,
            CancellationToken cancellationToken
            )
        {
            if (!IsReady)
            {
                await UpdateIsReadyAsync(alreadyHasLock, cancellationToken);

                if (!IsReady)
                {
                    return;
                }
            }

            List <PackageSpec> packages = null;

            var workingLock = alreadyHasLock ? null : await _working.LockAsync(cancellationToken);

            try {
                var args = _pipListHasFormatOption ? _commands.ListJson() : _commands.List();

                var concurrencyLock = alreadyHasConcurrencyLock ? null : await _concurrencyLock.LockAsync(cancellationToken);

                try {
                    using (var proc = ProcessOutput.Run(
                               _factory.Configuration.InterpreterPath,
                               args,
                               _factory.Configuration.GetPrefixPath(),
                               UnbufferedEnv,
                               false,
                               null
                               )) {
                        try {
                            if ((await proc) == 0)
                            {
                                if (_pipListHasFormatOption)
                                {
                                    try {
                                        var data = JToken.ReadFrom(new JsonTextReader(new StringListReader(proc.StandardOutputLines)));
                                        packages = data
                                                   .Select(j => new PackageSpec(j.Value <string>("name"), j.Value <string>("version")))
                                                   .Where(p => p.IsValid)
                                                   .OrderBy(p => p.Name)
                                                   .ToList();
                                    } catch (JsonException ex) {
                                        Debug.WriteLine("Failed to parse: {0}".FormatInvariant(ex.Message));
                                        foreach (var l in proc.StandardOutputLines)
                                        {
                                            Debug.WriteLine(l);
                                        }
                                    }
                                }
                                else
                                {
                                    packages = proc.StandardOutputLines
                                               .Select(i => PackageSpec.FromPipList(i))
                                               .Where(p => p.IsValid)
                                               .OrderBy(p => p.Name)
                                               .ToList();
                                }
                            }
                            else if (_pipListHasFormatOption)
                            {
                                // Actually, pip probably doesn't have the --format option
                                Debug.WriteLine("{0} does not support --format".FormatInvariant(_factory.Configuration.InterpreterPath));
                                _pipListHasFormatOption = false;
                                await CacheInstalledPackagesAsync(true, true, cancellationToken);

                                return;
                            }
                            else
                            {
                            }
                        } catch (OperationCanceledException) {
                            // Process failed to run
                            Debug.WriteLine("Failed to run pip to collect packages");
                            foreach (var line in proc.StandardOutputLines)
                            {
                                Debug.WriteLine(line);
                            }
                        }
                    }

                    if (packages == null)
                    {
                        // Pip failed, so return a directory listing
                        var paths = await PythonLibraryPath.GetDatabaseSearchPathsAsync(_factory.Configuration, null);

                        packages = await Task.Run(() => paths.Where(p => !p.IsStandardLibrary && Directory.Exists(p.Path))
                                                  .SelectMany(p => PathUtils.EnumerateDirectories(p.Path, recurse: false))
                                                  .Select(path => Path.GetFileName(path))
                                                  .Select(name => PackageNameRegex.Match(name))
                                                  .Where(match => match.Success)
                                                  .Select(match => new PackageSpec(match.Groups["name"].Value))
                                                  .Where(p => p.IsValid)
                                                  .OrderBy(p => p.Name)
                                                  .ToList());
                    }
                } finally {
                    concurrencyLock?.Dispose();
                }

                // Outside of concurrency lock, still in working lock

                _packages.Clear();
                _packages.AddRange(packages);
                _everCached = true;
            } finally {
                workingLock?.Dispose();
            }

            InstalledPackagesChanged?.Invoke(this, EventArgs.Empty);
            _factory.NotifyImportNamesChanged();
        }
Пример #14
0
 public bool CanUninstall(PackageSpec package)
 {
     return(true);
 }
Пример #15
0
 public Task <PackageSpec> GetInstalledPackageAsync(PackageSpec package, CancellationToken cancellationToken)
 {
     return(Task.FromResult(new PackageSpec()));
 }
Пример #16
0
 public Task <bool> UninstallAsync(PackageSpec package, IPackageManagerUI ui, CancellationToken cancellationToken)
 {
     throw new NotSupportedException();
 }
Пример #17
0
        private async Task CacheInstalledPackagesAsync(bool alreadyHasLock, CancellationToken cancellationToken)
        {
            if (!IsReady)
            {
                await UpdateIsReadyAsync(alreadyHasLock, cancellationToken);

                if (!IsReady)
                {
                    return;
                }
            }

            List <PackageSpec> packages = null;

            var workingLock = alreadyHasLock ? null : await _working.LockAsync(cancellationToken);

            try {
                var args = _extraInterpreterArgs.ToList();

                if (!SupportsDashMPip)
                {
                    args.Add("-c");
                    args.Add("\"import pip; pip.main()\"");
                }
                else
                {
                    args.Add("-m");
                    args.Add("pip");
                }
                args.Add("list");

                using (await _concurrencyLock.LockAsync(cancellationToken)) {
                    using (var proc = ProcessOutput.Run(
                               _factory.Configuration.InterpreterPath,
                               args,
                               _factory.Configuration.PrefixPath,
                               UnbufferedEnv,
                               false,
                               null
                               )) {
                        try {
                            if (await proc == 0)
                            {
                                packages = proc.StandardOutputLines
                                           .Select(i => PackageSpec.FromPipList(i))
                                           .Where(p => p.IsValid)
                                           .OrderBy(p => p.Name)
                                           .ToList();
                            }
                        } catch (OperationCanceledException) {
                            // Process failed to run
                            Debug.WriteLine("Failed to run pip to collect packages");
                            Debug.WriteLine(string.Join(Environment.NewLine, proc.StandardOutputLines));
                        }
                    }

                    if (packages == null)
                    {
                        // Pip failed, so return a directory listing
                        var paths = await PythonTypeDatabase.GetDatabaseSearchPathsAsync(_factory);

                        packages = await Task.Run(() => paths.Where(p => !p.IsStandardLibrary && Directory.Exists(p.Path))
                                                  .SelectMany(p => PathUtils.EnumerateDirectories(p.Path, recurse: false))
                                                  .Select(path => Path.GetFileName(path))
                                                  .Select(name => PackageNameRegex.Match(name))
                                                  .Where(match => match.Success)
                                                  .Select(match => new PackageSpec(match.Groups["name"].Value))
                                                  .Where(p => p.IsValid)
                                                  .OrderBy(p => p.Name)
                                                  .ToList());
                    }
                }

                // Outside of concurrency lock, still in working lock

                _packages.Clear();
                _packages.AddRange(packages);
                _everCached = true;
            } finally {
                workingLock?.Dispose();
            }

            InstalledPackagesChanged?.Invoke(this, EventArgs.Empty);
        }
Пример #18
0
        public async Task<PackageSpec> GetPackageInfoAsync(PackageSpec entry, CancellationToken cancel) {
            string description = null;
            List<string> versions = null;

            lock (NotOnPyPI) {
                if (NotOnPyPI.Contains(entry.Name)) {
                    return new PackageSpec();
                }
            }

            using (var client = new WebClient()) {
                Stream data;
                try {
                    data = await client.OpenReadTaskAsync(new Uri(_index ?? DefaultIndex, entry.Name + "/json"));
                } catch (WebException ex) {
                    if ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) {
                        lock (NotOnPyPI) {
                            NotOnPyPI.Add(entry.Name);
                        }
                    }

                    // No net access or no such package
                    return new PackageSpec();
                }

                try {
                    using (var reader = JsonReaderWriterFactory.CreateJsonReader(data, new XmlDictionaryReaderQuotas())) {
                        var doc = XDocument.Load(reader);

                        // TODO: Get package URL
                        //url = (string)doc.Document
                        //    .Elements("root")
                        //    .Elements("info")
                        //    .Elements("package_url")
                        //    .FirstOrDefault();

                        description = (string)doc.Document
                            .Elements("root")
                            .Elements("info")
                            .Elements("description")
                            .FirstOrDefault();
                        
                        versions = doc.Document
                            .Elements("root")
                            .Elements("releases")
                            .Elements()
                            .Attributes("item")
                            .Select(a => a.Value)
                            .ToList();
                    }
                } catch (InvalidOperationException) {
                }
            }

            bool changed = false;
            PackageSpec result;

            using (await _cacheLock.LockAsync(cancel)) {
                if (!_cache.TryGetValue(entry.Name, out result)) {
                    result = _cache[entry.Name] = new PackageSpec(entry.Name);
                }

                if (!string.IsNullOrEmpty(description)) {
                    var lines = description.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
                    var firstLine = string.Join(
                        " ",
                        lines.TakeWhile(s => !IsSeparatorLine(s)).Select(s => s.Trim())
                    );
                    if (firstLine.Length > 500) {
                        firstLine = firstLine.Substring(0, 497) + "...";
                    }
                    if (firstLine == "UNKNOWN") {
                        firstLine = string.Empty;
                    }

                    result.Description = firstLine;
                    changed = true;
                }

                if (versions != null) {
                    var updateVersion = PackageVersion.TryParseAll(versions)
                        .Where(v => v.IsFinalRelease)
                        .OrderByDescending(v => v)
                        .FirstOrDefault();
                    result.ExactVersion = updateVersion;
                    changed = true;
                }
            }

            if (changed) {
                TriggerWriteCacheToDisk();
            }

            return result.Clone();
        }
Пример #19
0
        private async Task RefreshCacheAsync(CancellationToken cancel) {
            Debug.Assert(_cacheLock.CurrentCount == 0, "Cache must be locked before calling RefreshCacheAsync");

            string htmlList;
            using (var client = new WebClient()) {
                // ../simple is a list of <a href="package">package</a>
                try {
                    htmlList = await client.DownloadStringTaskAsync(
                        new Uri(_index ?? DefaultIndex, "../simple")
                    ).ConfigureAwait(false);
                } catch (WebException) {
                    // No net access, so can't refresh
                    return;
                }
            }

            bool changed = false;
            var toRemove = new HashSet<string>(_cache.Keys);

            // We only want to add new packages so we don't blow away
            // existing package specs in the cache.
            foreach (Match match in SimpleListRegex.Matches(htmlList)) {
                var package = match.Groups["package"].Value;
                if (string.IsNullOrEmpty(package)) {
                    continue;
                }

                if (!toRemove.Remove(package)) {
                    try {
                        _cache[package] = new PackageSpec(package);
                        changed = true;
                    } catch (FormatException) {
                    }
                }
            }

            foreach (var package in toRemove) {
                _cache.Remove(package);
                changed = true;
            }

            if (changed) {
                TriggerWriteCacheToDisk();
            }

            _cacheAge = DateTime.Now;
        }
Пример #20
0
 public async Task UninstallPackage(PackageSpec package) {
     AbortOnInvalidConfiguration();
     await _packageManager.UninstallAsync(package, this, _cancelAll.Token);
 }
Пример #21
0
 internal PipPackageView(IPackageManager provider, PackageSpec package, bool isInstalled) {
     _provider = provider;
     _package = package;
     _isInstalled = isInstalled;
 }
Пример #22
0
 public Task<PackageSpec> GetInstallablePackageAsync(PackageSpec package, CancellationToken cancellationToken) {
     return Task.FromResult(new PackageSpec());
 }
Пример #23
0
        public async Task <PackageSpec> GetPackageInfoAsync(PackageSpec entry, CancellationToken cancel)
        {
            string        description = null;
            List <string> versions    = null;

            lock (NotOnPyPI) {
                if (NotOnPyPI.Contains(entry.Name))
                {
                    return(new PackageSpec());
                }
            }

            using (var client = new WebClient()) {
                Stream data;
                try {
                    data = await client.OpenReadTaskAsync(new Uri(_index ?? DefaultIndex, entry.Name + "/json"));
                } catch (WebException ex) {
                    if ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound)
                    {
                        lock (NotOnPyPI) {
                            NotOnPyPI.Add(entry.Name);
                        }
                    }

                    // No net access or no such package
                    return(new PackageSpec());
                }

                try {
                    using (var reader = JsonReaderWriterFactory.CreateJsonReader(data, new XmlDictionaryReaderQuotas())) {
                        var doc = XDocument.Load(reader);

                        // TODO: Get package URL
                        //url = (string)doc.Document
                        //    .Elements("root")
                        //    .Elements("info")
                        //    .Elements("package_url")
                        //    .FirstOrDefault();

                        description = (string)doc.Document
                                      .Elements("root")
                                      .Elements("info")
                                      .Elements("description")
                                      .FirstOrDefault();

                        versions = doc.Document
                                   .Elements("root")
                                   .Elements("releases")
                                   .Elements()
                                   .Attributes("item")
                                   .Select(a => a.Value)
                                   .ToList();
                    }
                } catch (InvalidOperationException) {
                }
            }

            bool        changed = false;
            PackageSpec result;

            using (await _cacheLock.LockAsync(cancel)) {
                if (!_cache.TryGetValue(entry.Name, out result))
                {
                    result = _cache[entry.Name] = new PackageSpec(entry.Name);
                }

                if (!string.IsNullOrEmpty(description))
                {
                    var lines     = description.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
                    var firstLine = string.Join(
                        " ",
                        lines.TakeWhile(s => !IsSeparatorLine(s)).Select(s => s.Trim())
                        );
                    if (firstLine.Length > 500)
                    {
                        firstLine = firstLine.Substring(0, 497) + "...";
                    }
                    if (firstLine == "UNKNOWN")
                    {
                        firstLine = string.Empty;
                    }

                    result.Description = firstLine;
                    changed            = true;
                }

                if (versions != null)
                {
                    var updateVersion = PackageVersion.TryParseAll(versions)
                                        .Where(v => v.IsFinalRelease)
                                        .OrderByDescending(v => v)
                                        .FirstOrDefault();
                    result.ExactVersion = updateVersion;
                    changed             = true;
                }
            }

            if (changed)
            {
                TriggerWriteCacheToDisk();
            }

            return(result.Clone());
        }