Merge pull request #21 from ThomasRubini/async

This commit is contained in:
Thomas Rubini 2022-12-13 19:42:29 +01:00 committed by GitHub
commit 84a9778944
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 217 additions and 155 deletions

View File

@ -0,0 +1,36 @@
package fr.packageviewer;
public class Pair<K,V> {
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);
}
}

View File

@ -2,37 +2,69 @@ package fr.packageviewer.distribution;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import java.net.http.*; import java.net.http.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger; import java.util.logging.Logger;
import fr.packageviewer.LoggerManager; import fr.packageviewer.LoggerManager;
import fr.packageviewer.Pair;
import fr.packageviewer.pack.Package; import fr.packageviewer.pack.Package;
import fr.packageviewer.pack.SearchedPackage; import fr.packageviewer.pack.SearchedPackage;
import fr.packageviewer.parser.AsyncRequestsParser;
import org.json.*; 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 * Will return the String json of the package from the Arch Linux API
* @param packageName the package name to get the json from * @param packageName the package name to get the json from
* @return json of the package * @return json of the package
*/ */
public CompletableFuture<HttpResponse<String>> getPackageFromAPI(String packageName) {
// create a new http client @Override
HttpClient client = HttpClient.newHttpClient(); public CompletableFuture<Pair<Package, Set<String>>> getPackageFromAPI(String packageName) {
// and create its url // create a new http client
HttpRequest request = HttpRequest.newBuilder(URI.create("https://archlinux.org/packages/search/json/?name="+packageName)).build(); HttpClient client = HttpClient.newHttpClient();
// send its url and return the string given // and create its url
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()); HttpRequest request = HttpRequest.newBuilder(URI.create("https://archlinux.org/packages/search/json/?name="+packageName)).build();
}
CompletableFuture<Pair<Package, Set<String>>> 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<String> 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; 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<Package> getPackageTree(String packageName, int depth) {
// parse the json
var futurePackage = new CompletableFuture<Package>();
logger.fine("Querying package %s from API... (depth=%s)".formatted(packageName, depth));
CompletableFuture<HttpResponse<String>> 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<Package> 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<CompletableFuture<Package>> 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<Package> 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;
}
} }

View File

@ -2,13 +2,11 @@ package fr.packageviewer.distribution;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.net.http.*; import java.net.http.*;
import fr.packageviewer.Pair;
import fr.packageviewer.parser.AsyncRequestsParser;
import org.json.*; import org.json.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import fr.packageviewer.pack.Package; import fr.packageviewer.pack.Package;
@ -17,25 +15,54 @@ import fr.packageviewer.LoggerManager;
import fr.packageviewer.pack.Package; import fr.packageviewer.pack.Package;
import fr.packageviewer.pack.SearchedPackage; import fr.packageviewer.pack.SearchedPackage;
public class FedoraDistribution implements Distribution { public class FedoraDistribution extends AsyncRequestsParser implements Distribution {
private String getPackageFromAPI(String packageName) { protected CompletableFuture<Pair<Package, Set<String>>> getPackageFromAPI(String packageName) {
// create a new http client // create a new http client
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
// and create its url // and create its url
String url = "https://mdapi.fedoraproject.org/rawhide/pkg/"+packageName+""; String url = "https://mdapi.fedoraproject.org/rawhide/pkg/"+packageName+"";
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).build(); 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<Pair<Package, Set<String>>> 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<String> 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 // if there's an error, return an empty string
return ""; return futureResult;
} }
@Override @Override
@ -73,46 +100,4 @@ public class FedoraDistribution implements Distribution {
} }
return searchedPackagesList; return searchedPackagesList;
} }
public Package getPackageTreeInternal(String packageName, int depth) {
String name, version, repo, description;
List<Package> 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<Package> getPackageTree(String packageName, int depth){
return CompletableFuture.supplyAsync(()->{
return getPackageTreeInternal(packageName, depth);
});
}
} }

View File

@ -1,5 +1,7 @@
package fr.packageviewer.pack; package fr.packageviewer.pack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class Package extends SearchedPackage { public class Package extends SearchedPackage {
@ -9,6 +11,13 @@ public class Package extends SearchedPackage {
return deps; 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<Package> deps) { public Package(String name, String version, String repo, String description, List<Package> deps) {
super(name, version, repo, description); super(name, version, repo, description);
this.deps = deps; this.deps = deps;

View File

@ -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<Pair<Package, Set<String>>> getPackageFromAPI(String name);
public CompletableFuture<Package> getPackageTree(String packageName, int depth) {
// parse the json
var futurePackage = new CompletableFuture<Package>();
logger.fine("Querying package %s from API... (depth=%s)".formatted(packageName, depth));
CompletableFuture<Pair<Package, Set<String>>> 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<String> 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<CompletableFuture<Package>> futureDeps = new ArrayList<>();
for (String depPackageName : dependenciesNames) {
// convert object into String
// add package into Package List
futureDeps.add(getPackageTree(depPackageName, depth - 1));
}
for (CompletableFuture<Package> 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;
}
}