diff --git a/src/main/java/fr/packageviewer/Pair.java b/src/main/java/fr/packageviewer/Pair.java new file mode 100644 index 0000000..1a5763d --- /dev/null +++ b/src/main/java/fr/packageviewer/Pair.java @@ -0,0 +1,36 @@ +package fr.packageviewer; + +public class Pair { + private K first; + private V second; + + public Pair() { + + } + + public Pair(K first, V second) { + this.first = first; + this.second = second; + } + + public K getFirst() { + return first; + } + + public void setFirst(K first) { + this.first = first; + } + + public V getSecond() { + return second; + } + + public void setSecond(V second) { + this.second = second; + } + + @Override + public String toString() { + return "Pair{key=%s,value=%s}".formatted(first, second); + } +} diff --git a/src/main/java/fr/packageviewer/distribution/ArchDistribution.java b/src/main/java/fr/packageviewer/distribution/ArchDistribution.java index 56a8eaf..ac5ebf9 100644 --- a/src/main/java/fr/packageviewer/distribution/ArchDistribution.java +++ b/src/main/java/fr/packageviewer/distribution/ArchDistribution.java @@ -2,37 +2,69 @@ package fr.packageviewer.distribution; import java.io.IOException; import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.net.http.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import fr.packageviewer.LoggerManager; +import fr.packageviewer.Pair; import fr.packageviewer.pack.Package; import fr.packageviewer.pack.SearchedPackage; +import fr.packageviewer.parser.AsyncRequestsParser; import org.json.*; -public class ArchDistribution implements Distribution { +public class ArchDistribution extends AsyncRequestsParser implements Distribution { - private static final Logger logger = LoggerManager.getLogger("ArchParser"); + private static final Logger logger = LoggerManager.getLogger("ArchDistribution"); /** * Will return the String json of the package from the Arch Linux API * @param packageName the package name to get the json from * @return json of the package */ - public CompletableFuture> getPackageFromAPI(String packageName) { - // create a new http client - HttpClient client = HttpClient.newHttpClient(); - // and create its url - HttpRequest request = HttpRequest.newBuilder(URI.create("https://archlinux.org/packages/search/json/?name="+packageName)).build(); - // send its url and return the string given - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()); - } + +@Override +public CompletableFuture>> getPackageFromAPI(String packageName) { + // create a new http client + HttpClient client = HttpClient.newHttpClient(); + // and create its url + HttpRequest request = HttpRequest.newBuilder(URI.create("https://archlinux.org/packages/search/json/?name="+packageName)).build(); + + CompletableFuture>> futureResult = new CompletableFuture<>(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenAccept(result ->{ + + JSONObject json = new JSONObject(result.body()); + + JSONArray resultsArrayJson = json.getJSONArray("results"); + if(resultsArrayJson.length()==0){ + // unknown package, probably an abstract dependency + futureResult.complete(null); + return; + } + JSONObject resultJson = resultsArrayJson.getJSONObject(0); + + // get infos + + Set dependenciesNames = new HashSet<>(); + for(Object dependency : resultJson.getJSONArray("depends")){ + dependenciesNames.add((String)dependency); + } + futureResult.complete(new Pair<>( + new Package( + resultJson.getString("pkgname"), + resultJson.getString("pkgver"), + resultJson.getString("repo"), + resultJson.getString("pkgdesc") + ), + dependenciesNames + )); + }); + + return futureResult; + +} /** @@ -74,85 +106,4 @@ public class ArchDistribution implements Distribution { return searchedPackagesList; } - - - /** - * Will generate a dependencies tree of depth 'depth' given the package name - * @param packageName the package name to search - * @param depth depth to search dependencies - * @return new Package - */ - public CompletableFuture getPackageTree(String packageName, int depth) { - - // parse the json - var futurePackage = new CompletableFuture(); - - logger.fine("Querying package %s from API... (depth=%s)".formatted(packageName, depth)); - - CompletableFuture> futureRequest; - try{ - futureRequest = getPackageFromAPI(packageName); - }catch(IllegalArgumentException e){ - logger.warning("Caught exception for package %s :\n%s".formatted(packageName, e)); - return CompletableFuture.completedFuture(null); - } - futureRequest.thenAccept(result->{ - List deps = new ArrayList<>(); - String name, version, repo, description; - - JSONObject json = new JSONObject(result.body()); - - JSONArray resultsArrayJson = json.getJSONArray("results"); - if(resultsArrayJson.length()==0){ - // unknown package, probably an abstract dependency - logger.fine("Completing callback INVALID for package %s (depth=%s)".formatted(packageName, depth)); - futurePackage.complete(null); - return; - } - JSONObject resultJson = resultsArrayJson.getJSONObject(0); - - // get infos except dependencies - name = resultJson.getString("pkgname"); - version = resultJson.getString("pkgver"); - repo = resultJson.getString("repo"); - description = resultJson.getString("pkgdesc"); - - // if we're at the maximum depth, return the package without its dependencies - if(depth==0) { - logger.fine("Completing callback NODEP for package %s (depth=%s)".formatted(packageName, depth)); - futurePackage.complete(new Package(name, version, repo, description, Collections.emptyList())); - return; - } - // iterate for every package in the list - List> futureDeps = new ArrayList<>(); - for (Object depPackageNameObj : resultJson.getJSONArray("depends")) { - // convert object into String - String depPackageName = (String) depPackageNameObj; - // add package into Package List - futureDeps.add(getPackageTree(depPackageName, depth - 1)); - } - for(CompletableFuture future : futureDeps){ - Package dep; - try { - dep = future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - if(dep!=null){ - deps.add(dep); - } - } - - // TODO this doesn't seem clean - logger.fine("Completing callback DEPS for package %s (depth=%s)".formatted(packageName, depth)); - futurePackage.complete(new Package(name, version, repo, description, deps)); - }).exceptionally((e2->{ - logger.warning("Error while fetching package %s (depth=%s) from the API : \n%s".formatted(packageName, depth, e2)); - e2.printStackTrace(); - futurePackage.complete(null); - return null; - })); - - return futurePackage; - } } diff --git a/src/main/java/fr/packageviewer/distribution/FedoraDistribution.java b/src/main/java/fr/packageviewer/distribution/FedoraDistribution.java index 3969589..b79f014 100644 --- a/src/main/java/fr/packageviewer/distribution/FedoraDistribution.java +++ b/src/main/java/fr/packageviewer/distribution/FedoraDistribution.java @@ -2,13 +2,11 @@ package fr.packageviewer.distribution; import java.io.IOException; import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; +import java.util.*; import java.net.http.*; + +import fr.packageviewer.Pair; +import fr.packageviewer.parser.AsyncRequestsParser; import org.json.*; import java.util.concurrent.CompletableFuture; import fr.packageviewer.pack.Package; @@ -17,25 +15,54 @@ import fr.packageviewer.LoggerManager; import fr.packageviewer.pack.Package; import fr.packageviewer.pack.SearchedPackage; -public class FedoraDistribution implements Distribution { +public class FedoraDistribution extends AsyncRequestsParser implements Distribution { - private String getPackageFromAPI(String packageName) { + protected CompletableFuture>> getPackageFromAPI(String packageName) { // create a new http client HttpClient client = HttpClient.newHttpClient(); // and create its url String url = "https://mdapi.fedoraproject.org/rawhide/pkg/"+packageName+""; HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build(); - // send its url and return the string given - try { - String response = client.send(request, HttpResponse.BodyHandlers.ofString()).body(); - if(response.contains("404: Not Found")) return ""; - return response; - } catch (IOException|InterruptedException e) { - e.printStackTrace(); - - } + + CompletableFuture>> futureResult = new CompletableFuture<>(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenAccept(result->{ + + String body = result.body(); + + if(body.contains("404: Not Found")) { + futureResult.complete(null); + return; + } + + JSONObject json = new JSONObject(result.body()); + + // get infos + Set dependenciesNames = new HashSet<>(); + + for (Object depPackageObj : json.getJSONArray("requires")) { + // convert object into String + JSONObject depPackageJson = (JSONObject) depPackageObj; + // add package into Package List + String depName = depPackageJson.getString("name"); + if (depName.contains(".so")) + continue; + if (depName.contains("/")) + continue; + dependenciesNames.add(depName); + } + + futureResult.complete(new Pair<>( + new Package( + json.getString("basename"), + json.getString("version"), + json.getString("repo"), + json.getString("description") + ), + dependenciesNames + )); + }); // if there's an error, return an empty string - return ""; + return futureResult; } @Override @@ -73,46 +100,4 @@ public class FedoraDistribution implements Distribution { } return searchedPackagesList; } - - public Package getPackageTreeInternal(String packageName, int depth) { - String name, version, repo, description; - List deps = new ArrayList<>(); - - // parse the json - String response = getPackageFromAPI(packageName); - if (response == "") { - return new Package(packageName + "(not found)", "N/A", "N/A", "N/A", Collections.emptyList()); - } - JSONObject json = new JSONObject(response); - // get infos except dependencies - name = json.getString("basename"); - version = json.getString("version"); - repo = "rpms/" + packageName; - description = json.getString("description"); - - // if we're at the maximum depth, return the package without its dependencies - if (depth == 0) { - return new Package(name, version, repo, description, Collections.emptyList()); - } else { - // iterate for every package in the list - for (Object depPackageNameObj : json.getJSONArray("requires")) { - // convert object into String - JSONObject depPackageJSONObj = (JSONObject) depPackageNameObj; - // add package into Package List - String depName = depPackageJSONObj.getString("name"); - if (depName.contains(".so")) - continue; - if (depName.contains("/")) - continue; - deps.add(getPackageTreeInternal(depName, depth - 1)); - } - return new Package(name, version, repo, description, deps); - } - } - - public CompletableFuture getPackageTree(String packageName, int depth){ - return CompletableFuture.supplyAsync(()->{ - return getPackageTreeInternal(packageName, depth); - }); - } } diff --git a/src/main/java/fr/packageviewer/pack/Package.java b/src/main/java/fr/packageviewer/pack/Package.java index 855fef9..0371559 100644 --- a/src/main/java/fr/packageviewer/pack/Package.java +++ b/src/main/java/fr/packageviewer/pack/Package.java @@ -1,5 +1,7 @@ package fr.packageviewer.pack; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class Package extends SearchedPackage { @@ -9,6 +11,13 @@ public class Package extends SearchedPackage { return deps; } + public void addDep(Package pack) { + deps.add(pack); + } + + public Package(String name, String version, String repo, String description) { + this(name, version, repo, description, new ArrayList<>()); + } public Package(String name, String version, String repo, String description, List deps) { super(name, version, repo, description); this.deps = deps; diff --git a/src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java b/src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java new file mode 100644 index 0000000..a279cf6 --- /dev/null +++ b/src/main/java/fr/packageviewer/parser/AsyncRequestsParser.java @@ -0,0 +1,81 @@ +package fr.packageviewer.parser; + +import fr.packageviewer.LoggerManager; +import fr.packageviewer.Pair; +import fr.packageviewer.pack.Package; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +public abstract class AsyncRequestsParser { + + private static final Logger logger = LoggerManager.getLogger("AsyncRequestsParser"); + + protected abstract CompletableFuture>> getPackageFromAPI(String name); + + public CompletableFuture getPackageTree(String packageName, int depth) { + // parse the json + var futurePackage = new CompletableFuture(); + + logger.fine("Querying package %s from API... (depth=%s)".formatted(packageName, depth)); + + CompletableFuture>> futureRequest; + try { + futureRequest = getPackageFromAPI(packageName); + } catch (IllegalArgumentException e) { + logger.warning("Caught exception for package %s :\n%s".formatted(packageName, e)); + return CompletableFuture.completedFuture(null); + } + futureRequest.thenAccept(result -> { + if(result==null){ + logger.fine("Completing callback INVALID for package %s (depth=%s)".formatted(packageName, depth)); + futurePackage.complete(null); + return; + } + Package pack = result.getFirst(); + Set dependenciesNames = result.getSecond(); + + + // if we're at the maximum depth, return the package without its dependencies + if (depth == 0) { + logger.fine("Completing callback NODEP for package %s (depth=%s)".formatted(packageName, depth)); + futurePackage.complete(pack); + return; + } + + // iterate for every package in the list + List> futureDeps = new ArrayList<>(); + for (String depPackageName : dependenciesNames) { + // convert object into String + // add package into Package List + futureDeps.add(getPackageTree(depPackageName, depth - 1)); + } + for (CompletableFuture future : futureDeps) { + Package dep; + try { + dep = future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + if (dep != null) { + pack.addDep(dep); + } + } + + // TODO this doesn't seem clean + logger.fine("Completing callback DEPS for package %s (depth=%s)".formatted(packageName, depth)); + futurePackage.complete(pack); + }).exceptionally((e2 -> { + logger.warning("Error while fetching package %s (depth=%s) from the API : \n%s".formatted(packageName, depth, e2)); + e2.printStackTrace(); + futurePackage.complete(null); + return null; + })); + + return futurePackage; + } +}