diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6576de5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.iml +.idea/ +target/ +package-index/ \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b991d90 --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + io.github.andrewlalis + dub-registry-search + 1.0.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + + org.apache.lucene + lucene-core + 9.5.0 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.2 + + + + \ No newline at end of file diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/DubPackageFetcher.java b/src/main/java/io/github/andrewlalis/dub_registry_search/DubPackageFetcher.java new file mode 100644 index 0000000..a4fb6c5 --- /dev/null +++ b/src/main/java/io/github/andrewlalis/dub_registry_search/DubPackageFetcher.java @@ -0,0 +1,43 @@ +package io.github.andrewlalis.dub_registry_search; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.zip.GZIPInputStream; + +public class DubPackageFetcher implements PackageFetcher { + private final HttpClient httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(3)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + private static final String API_URL = "https://code.dlang.org/api/packages/dump"; + + @Override + public ArrayNode fetch() throws IOException { + HttpRequest req = HttpRequest.newBuilder(URI.create(API_URL)) + .GET() + .timeout(Duration.ofSeconds(60)) + .header("Accept", "application/json") + .header("Accept-Encoding", "gzip") + .build(); + try { + HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() != 200) { + throw new IOException("Response status code " + response.statusCode()); + } + ObjectMapper mapper = new ObjectMapper(); + try (var in = new GZIPInputStream(response.body())) { + return mapper.readValue(in, ArrayNode.class); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/DubRegistrySearch.java b/src/main/java/io/github/andrewlalis/dub_registry_search/DubRegistrySearch.java new file mode 100644 index 0000000..72631d9 --- /dev/null +++ b/src/main/java/io/github/andrewlalis/dub_registry_search/DubRegistrySearch.java @@ -0,0 +1,43 @@ +package io.github.andrewlalis.dub_registry_search; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; + +public class DubRegistrySearch { + public static void main(String[] args) throws Exception { + if (args.length == 1 && args[0].strip().equalsIgnoreCase("index")) { + buildIndex(); + } + } + + public static void buildIndex() throws Exception { + System.out.println("Building package index."); + PackageFetcher fetcher = new DubPackageFetcher(); + System.out.println("Fetching packages..."); + ArrayNode packagesArray = fetcher.fetch(); + int docCount = 0; + Duration indexDuration; + try (var indexer = new LucenePackageIndexer(Path.of("package-index"))) { + Instant start = Instant.now(); + for (JsonNode node : packagesArray) { + if (node.isObject()) { + try { + indexer.addToIndex((ObjectNode) node); + docCount++; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + Instant end = Instant.now(); + indexDuration = Duration.between(start, end); + } + System.out.println("Done! Added " + docCount + " packages to the index in " + indexDuration.toMillis() + " ms."); + } +} diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/LucenePackageIndexer.java b/src/main/java/io/github/andrewlalis/dub_registry_search/LucenePackageIndexer.java new file mode 100644 index 0000000..950a3c6 --- /dev/null +++ b/src/main/java/io/github/andrewlalis/dub_registry_search/LucenePackageIndexer.java @@ -0,0 +1,51 @@ +package io.github.andrewlalis.dub_registry_search; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; + +import java.io.IOException; +import java.nio.file.Path; + +public class LucenePackageIndexer implements PackageIndexer, AutoCloseable { + private final IndexWriter indexWriter; + private final Directory dir; + private final Analyzer analyzer; + + public LucenePackageIndexer(Path indexPath) throws IOException { + this.dir = FSDirectory.open(indexPath); + this.analyzer = new StandardAnalyzer(); + IndexWriterConfig config = new IndexWriterConfig(analyzer); + config.setOpenMode(IndexWriterConfig.OpenMode.CREATE); + config.setCommitOnClose(true); + this.indexWriter = new IndexWriter(dir, config); + } + + + @Override + public void addToIndex(ObjectNode packageJson) throws IOException { + String registryId = packageJson.get("_id").asText(); + String name = packageJson.get("name").asText(); + String dubUrl = "https://code.dlang.org/packages/" + name; + + Document doc = new Document(); + doc.add(new StoredField("registryId", registryId)); + doc.add(new TextField("name", name, Field.Store.YES)); + doc.add(new StoredField("dubUrl", dubUrl)); + } + + @Override + public void close() throws Exception { + indexWriter.close(); + analyzer.close(); + dir.close(); + } +} diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/PackageFetcher.java b/src/main/java/io/github/andrewlalis/dub_registry_search/PackageFetcher.java new file mode 100644 index 0000000..3eca6a6 --- /dev/null +++ b/src/main/java/io/github/andrewlalis/dub_registry_search/PackageFetcher.java @@ -0,0 +1,9 @@ +package io.github.andrewlalis.dub_registry_search; + +import com.fasterxml.jackson.databind.node.ArrayNode; + +import java.io.IOException; + +public interface PackageFetcher { + ArrayNode fetch() throws IOException; +} diff --git a/src/main/java/io/github/andrewlalis/dub_registry_search/PackageIndexer.java b/src/main/java/io/github/andrewlalis/dub_registry_search/PackageIndexer.java new file mode 100644 index 0000000..1f5b0cc --- /dev/null +++ b/src/main/java/io/github/andrewlalis/dub_registry_search/PackageIndexer.java @@ -0,0 +1,9 @@ +package io.github.andrewlalis.dub_registry_search; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.IOException; + +public interface PackageIndexer { + void addToIndex(ObjectNode packageJson) throws IOException; +}