mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-27360 Unify implementations of PyPI and Conda package caches
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class CondaPackageCache {
|
||||
private static final Logger LOG = Logger.getInstance(PyPIPackageCache.class);
|
||||
private static final Gson ourGson = new GsonBuilder().create();
|
||||
private static CondaPackageCache ourInstance = null;
|
||||
|
||||
@NotNull
|
||||
public static synchronized CondaPackageCache getInstance() {
|
||||
if (ourInstance == null) {
|
||||
ourInstance = new CondaPackageCache();
|
||||
try (Reader reader = Files.newBufferedReader(getCachePath(), StandardCharsets.UTF_8)) {
|
||||
ourInstance = ourGson.fromJson(reader, CondaPackageCache.class);
|
||||
LOG.info("Loaded " + ourInstance.myPackages.size() + " packages from " + getCachePath());
|
||||
}
|
||||
catch (IOException exception) {
|
||||
LOG.warn("Cannot load Conda package cache from the filesystem", exception);
|
||||
}
|
||||
}
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static synchronized CondaPackageCache reload(@NotNull Map<String, List<String>> packageNames) {
|
||||
ourInstance = new CondaPackageCache(packageNames);
|
||||
try {
|
||||
Files.createDirectories(getCachePath().getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(getCachePath(), StandardCharsets.UTF_8)) {
|
||||
ourGson.toJson(ourInstance, writer);
|
||||
}
|
||||
}
|
||||
catch (IOException exception) {
|
||||
LOG.warn("Cannot save Conda package cache to the filesystem", exception);
|
||||
}
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Path getCachePath() {
|
||||
return Paths.get(PathManager.getSystemPath(), "python_packages", "conda-cache.json");
|
||||
}
|
||||
|
||||
public CondaPackageCache() {
|
||||
|
||||
}
|
||||
|
||||
private CondaPackageCache(@NotNull Map<String, List<String>> nameToVersion) {
|
||||
for (Map.Entry<String, List<String>> entry : nameToVersion.entrySet()) {
|
||||
myPackages.put(entry.getKey(), new PackageInfo(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsPackage(@NotNull String packageName) {
|
||||
return myPackages.containsKey(packageName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<String> getPackageNames() {
|
||||
return Collections.unmodifiableSet(myPackages.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available package versions sorted in the reversed order using {@link com.intellij.webcore.packaging.PackageVersionComparator}.
|
||||
* @param packageName case-insensitive name of a package
|
||||
*/
|
||||
@NotNull
|
||||
public List<String> getVersions(@NotNull String packageName) {
|
||||
return Collections.unmodifiableList(myPackages.get(packageName).getVersions());
|
||||
}
|
||||
|
||||
@SerializedName("packages")
|
||||
private final TreeMap<String, PackageInfo> myPackages = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private static class PackageInfo {
|
||||
|
||||
@SerializedName("v")
|
||||
private List<String> myVersions;
|
||||
|
||||
public PackageInfo(@NotNull List<String> versions) {
|
||||
myVersions = versions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public PackageInfo() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getVersions() {
|
||||
return Collections.unmodifiableList(myVersions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public abstract class PyAbstractPackageCache {
|
||||
private static final Logger LOG = Logger.getInstance(PyPIPackageCache.class);
|
||||
private static final Gson ourGson = new GsonBuilder().create();
|
||||
|
||||
@SerializedName("packages")
|
||||
protected TreeMap<String, PackageInfo> myPackages = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
protected PyAbstractPackageCache() {
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
protected static <T extends PyAbstractPackageCache> T load(@NotNull Class<T> classToken,
|
||||
@NotNull T fallbackValue,
|
||||
@NotNull String cacheFileName) {
|
||||
T cache = fallbackValue;
|
||||
try (Reader reader = Files.newBufferedReader(getCachePath(cacheFileName), StandardCharsets.UTF_8)) {
|
||||
cache = ourGson.fromJson(reader, classToken);
|
||||
LOG.info("Loaded " + cache.getPackageNames().size() + " packages from " + getCachePath(cacheFileName));
|
||||
}
|
||||
catch (IOException exception) {
|
||||
LOG.warn("Cannot load " + cacheFileName + " package cache from the filesystem", exception);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
protected static void store(@NotNull PyAbstractPackageCache newValue, @NotNull String cacheFileName) {
|
||||
try {
|
||||
final Path cacheFilePath = getCachePath(cacheFileName);
|
||||
Files.createDirectories(cacheFilePath.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(cacheFilePath, StandardCharsets.UTF_8)) {
|
||||
ourGson.toJson(newValue, writer);
|
||||
}
|
||||
}
|
||||
catch (IOException exception) {
|
||||
LOG.warn("Cannot save " + cacheFileName + " package cache to the filesystem", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Path getCachePath(@NotNull String cacheFileName) {
|
||||
return Paths.get(PathManager.getSystemPath(), "python_packages", cacheFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a case-insensitive set of packages names available in the cache.
|
||||
*/
|
||||
@NotNull
|
||||
public Set<String> getPackageNames() {
|
||||
return Collections.unmodifiableSet(myPackages.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given name is among those available in the repository <em>case-insensitively</em>.
|
||||
* <p>
|
||||
* Note that if the cache hasn't been initialized yet or there was an error during its loading,
|
||||
* {@link #load(Class, PyAbstractPackageCache, String)} returns an empty sentinel value, and, therefore, this method will return {@code false}.
|
||||
* It's worth writing code analysis so that this value doesn't lead to false positives in the editor
|
||||
* when the cache is merely not ready.
|
||||
*
|
||||
* @param name case-insensitive name of a package
|
||||
*/
|
||||
public boolean containsPackage(@NotNull String name) {
|
||||
return myPackages.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns available package versions sorted in the reversed order using
|
||||
* {@link com.intellij.webcore.packaging.PackageVersionComparator} so that the latest version is the first on the list
|
||||
* or {@code null} if the given package is not contained in the cache or this feature is not available.
|
||||
*
|
||||
* @param packageName case-insensitive name of a package
|
||||
*/
|
||||
@Nullable
|
||||
public List<String> getVersions(@NotNull String packageName) {
|
||||
final PackageInfo packageInfo = myPackages.get(packageName);
|
||||
return packageInfo != null ? packageInfo.getVersions() : null;
|
||||
}
|
||||
|
||||
protected static class PackageInfo {
|
||||
public static final PackageInfo EMPTY = new PackageInfo();
|
||||
|
||||
@SerializedName("v")
|
||||
private List<String> myVersions;
|
||||
|
||||
public PackageInfo(@NotNull List<String> versions) {
|
||||
myVersions = versions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public PackageInfo() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<String> getVersions() {
|
||||
return myVersions != null ? Collections.unmodifiableList(myVersions) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyCondaPackageCache extends PyAbstractPackageCache {
|
||||
private static final String CACHE_FILE_NAME = "conda-cache.json";
|
||||
|
||||
private static PyCondaPackageCache ourInstance;
|
||||
|
||||
@NotNull
|
||||
public static synchronized PyCondaPackageCache getInstance() {
|
||||
if (ourInstance == null) {
|
||||
ourInstance = load(PyCondaPackageCache.class, new PyCondaPackageCache(), CACHE_FILE_NAME);
|
||||
}
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static synchronized PyCondaPackageCache reload(@NotNull Map<String, List<String>> packageNames) {
|
||||
ourInstance = new PyCondaPackageCache(packageNames);
|
||||
store(ourInstance, CACHE_FILE_NAME);
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
private PyCondaPackageCache() {
|
||||
}
|
||||
|
||||
private PyCondaPackageCache(@NotNull Map<String, List<String>> nameToVersion) {
|
||||
for (Map.Entry<String, List<String>> entry : nameToVersion.entrySet()) {
|
||||
myPackages.put(entry.getKey(), new PyAbstractPackageCache.PackageInfo(entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
|
||||
}
|
||||
|
||||
public void loadAndGetPackages(boolean force) {
|
||||
if (CondaPackageCache.getInstance().getPackageNames().isEmpty() || force) {
|
||||
if (PyCondaPackageCache.getInstance().getPackageNames().isEmpty() || force) {
|
||||
updatePackagesCache();
|
||||
}
|
||||
}
|
||||
@@ -194,13 +194,13 @@ public class PyCondaPackageService implements PersistentStateComponent<PyCondaPa
|
||||
versions.add(-(sortedIndex + 1), packageVersion);
|
||||
}
|
||||
}
|
||||
CondaPackageCache.reload(nameToVersions);
|
||||
PyCondaPackageCache.reload(nameToVersions);
|
||||
LAST_TIME_CHECKED = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getPackageVersions(@NotNull final String packageName) {
|
||||
return CondaPackageCache.getInstance().getVersions(packageName);
|
||||
return ContainerUtil.notNullize(PyCondaPackageCache.getInstance().getVersions(packageName));
|
||||
}
|
||||
|
||||
public void updateChannels() {
|
||||
|
||||
@@ -1,45 +1,22 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.packaging;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyPIPackageCache {
|
||||
private static final Logger LOG = Logger.getInstance(PyPIPackageCache.class);
|
||||
private static final Gson ourGson = new GsonBuilder().create();
|
||||
private static PyPIPackageCache ourInstance = null;
|
||||
public class PyPIPackageCache extends PyAbstractPackageCache {
|
||||
private static final String CACHE_FILE_NAME = "pypi-cache.json";
|
||||
|
||||
private static PyPIPackageCache ourInstance;
|
||||
|
||||
@NotNull
|
||||
public static synchronized PyPIPackageCache getInstance() {
|
||||
if (ourInstance == null) {
|
||||
ourInstance = new PyPIPackageCache();
|
||||
try (Reader reader = Files.newBufferedReader(getCachePath(), StandardCharsets.UTF_8)) {
|
||||
ourInstance = ourGson.fromJson(reader, PyPIPackageCache.class);
|
||||
LOG.info("Loaded " + ourInstance.getPackageNames().size() + " packages from " + getCachePath());
|
||||
}
|
||||
catch (IOException exception) {
|
||||
LOG.warn("Cannot load PyPI package cache from the filesystem", exception);
|
||||
}
|
||||
ourInstance = PyAbstractPackageCache.load(PyPIPackageCache.class, new PyPIPackageCache(), CACHE_FILE_NAME);
|
||||
}
|
||||
return ourInstance;
|
||||
}
|
||||
@@ -47,67 +24,19 @@ public class PyPIPackageCache {
|
||||
@NotNull
|
||||
public static synchronized PyPIPackageCache reload(@NotNull List<String> packageNames) {
|
||||
ourInstance = new PyPIPackageCache(packageNames);
|
||||
try {
|
||||
Files.createDirectories(getCachePath().getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(getCachePath(), StandardCharsets.UTF_8)) {
|
||||
ourGson.toJson(ourInstance, writer);
|
||||
}
|
||||
}
|
||||
catch (IOException exception) {
|
||||
LOG.warn("Cannot save PyPI package cache to the filesystem", exception);
|
||||
}
|
||||
store(ourInstance, CACHE_FILE_NAME);
|
||||
return ourInstance;
|
||||
}
|
||||
|
||||
// For GSON
|
||||
private PyPIPackageCache() {
|
||||
}
|
||||
|
||||
public PyPIPackageCache(@NotNull List<String> packageNames) {
|
||||
myPackageNames = ContainerUtil.map(packageNames, PackageInfo::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Path getCachePath() {
|
||||
return Paths.get(PathManager.getSystemPath(), "python_packages", "pypi-cache.json");
|
||||
}
|
||||
|
||||
@SerializedName("packages")
|
||||
private List<PackageInfo> myPackageNames = new ArrayList<>();
|
||||
|
||||
@NotNull
|
||||
public List<String> getPackageNames() {
|
||||
return Collections.unmodifiableList(ContainerUtil.map(myPackageNames, PackageInfo::getName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given name is among those available at PyPI <em>case-insensitively</em>.
|
||||
* <p>
|
||||
* Note that if the cache hasn't been initialized yet or there was an error during its loading,
|
||||
* {@link #getInstance()} returns an empty sentinel value, and, therefore, this method will return {@code false}.
|
||||
* It's worth writing code analysis so that this value doesn't lead to false positives in the editor
|
||||
* when the cache is merely not ready.
|
||||
*/
|
||||
public boolean containsPackage(@NotNull String name) {
|
||||
final Comparator<PackageInfo> caseInsensitiveNameComparator = Comparator.comparing(PackageInfo::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
return Collections.binarySearch(myPackageNames, new PackageInfo(name), caseInsensitiveNameComparator) >= 0;
|
||||
}
|
||||
|
||||
private static class PackageInfo {
|
||||
@SerializedName("n")
|
||||
private String myName;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public PackageInfo() {
|
||||
}
|
||||
|
||||
public PackageInfo(@NotNull String name) {
|
||||
myName = name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return myName;
|
||||
for (String name : packageNames) {
|
||||
// Don't save null as a value, since GSON excludes such entries from serialization,
|
||||
// unless GsonBuilder#serializeNulls() is used, but then object fields with null values
|
||||
// are saved as well.
|
||||
myPackages.put(name, PackageInfo.EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.CatchingConsumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.webcore.packaging.RepoPackage;
|
||||
import com.jetbrains.python.packaging.CondaPackageCache;
|
||||
import com.jetbrains.python.packaging.PyCondaPackageCache;
|
||||
import com.jetbrains.python.packaging.PyCondaPackageManagerImpl;
|
||||
import com.jetbrains.python.packaging.PyCondaPackageService;
|
||||
import com.jetbrains.python.packaging.PyPackageManager;
|
||||
@@ -153,7 +153,7 @@ public class PyCondaManagementService extends PyPackageManagementService {
|
||||
|
||||
@NotNull
|
||||
private static List<RepoPackage> getCachedCondaPackages() {
|
||||
final CondaPackageCache instance = CondaPackageCache.getInstance();
|
||||
final PyCondaPackageCache instance = PyCondaPackageCache.getInstance();
|
||||
return ContainerUtil.map(instance.getPackageNames(), name -> {
|
||||
final String latestVersion = ContainerUtil.getFirstItem(instance.getVersions(name));
|
||||
return new RepoPackage(name, null, latestVersion);
|
||||
|
||||
Reference in New Issue
Block a user