Merge pull request #21 from ThomasRubini/async
This commit is contained in:
		
						commit
						84a9778944
					
				
							
								
								
									
										36
									
								
								src/main/java/fr/packageviewer/Pair.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/main/java/fr/packageviewer/Pair.java
									
									
									
									
									
										Normal 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); | ||||
| 	} | ||||
| } | ||||
| @ -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<HttpResponse<String>> 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<Pair<Package, Set<String>>> 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<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; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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<Pair<Package, Set<String>>> 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<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 | ||||
|         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<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); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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<Package> deps) { | ||||
|         super(name, version, repo, description); | ||||
|         this.deps = deps; | ||||
|  | ||||
| @ -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; | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user