From e15d2f6e2d3b256d99f70440e6ed3548d53c2b73 Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Wed, 14 Aug 2024 21:01:06 -0400 Subject: [PATCH 01/39] Update README.md --- README.md | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b37070c..2eebb2a 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ -# release-downloader +# repo-viewer -[![](https://jitpack.io/v/aivruu/release-downloader.svg)](https://jitpack.io/#aivruu/release-downloader) +[![](https://jitpack.io/v/aivruu/repo-viewer.svg)](https://jitpack.io/#aivruu/repo-viewer) -`release-downloader` is a library that provides utilities to obtain GitHub repositories information since HTTP requests, and allow download assets of the releases for the requested repositories of an easily way. +`repo-viewer` is a library that provides utilities to obtain GitHub repositories information since HTTP requests, and allow download assets of the releases for the requested repositories of an easily way. > [!NOTE]\ -> This is a library that I created to testing HTTP utilities with Java. I taken as base [OcZi/release-watcher](https://github.com/OcZi/release-watcher) for the main idea of this project, later I decided to implement a downloader function type **(BETA)**. - -### Documentation -- [Javadoc](https://jitpack.io/com/github/aivruu/release-downloader/latest/javadoc/) +> This is a library that I created to testing HTTP utilities with Java. I taken as base [OcZi/release-watcher](https://github.com/OcZi/release-watcher) for the main idea of this project. ### Features -* Obtain and view the repositories information easily with requests to the GitHub API. -* Download assets of the releases for requested repositories. +* View specified repository's information. +* Easy to usage. +* Download assets from each repository release. ### Download ```kotlin @@ -22,27 +20,14 @@ repositories { dependencies { // You can visualize the latest version on the document header. - implementation("com.github.aivruu:release-downloader:VERSION") -} - -tasks { - shadowJar { - // Relocating gson-lib dependency and release-downloader into specified packages. - relocate("com.google.gson", "com.yourPackage.com") - relocate("me.qeklydev.downloader", "com.yourPackage.com") - } + implementation("com.github.aivruu:repo-viewer:VERSION") } ``` -### Requirements -- Java 21 or newer. - ### Building -This library use Gradle-Kotlin for project. +This project require Gradle for building and management, and Java 21 as minimum. ``` -git clone https://github.com/aivruu/release-downloader.git -cd release-downloader +git clone https://github.com/aivruu/repo-viewer.git +cd repo-viewer ./gradlew build ``` - -JDK 21 or newer is fully required. From 5ee16a48e75dd51a4878b1fc5d5fe19a0248ec28 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:42:40 -0400 Subject: [PATCH 02/39] chore: Remove old test classes, models and implementations --- .../codec/DeserializationProvider.java | 147 ------------------ .../codec/RepositoryModelCodec.java | 122 --------------- .../downloader/http/HTTPModelRequest.java | 55 ------- .../http/HTTPReleaseModelRequest.java | 70 --------- .../http/HTTPRepositoryModelRequest.java | 78 ---------- .../downloader/http/package-info.java | 7 - .../qeklydev/downloader/io/IoAsyncUtils.java | 93 ----------- .../downloader/license/RepositoryLicense.java | 136 ---------------- .../downloader/license/package-info.java | 7 - .../downloader/release/ReleaseModel.java | 107 ------------- .../repository/GitHubRepositoryModel.java | 58 ------- .../downloader/repository/package-info.java | 7 - .../downloader/ReleaseRequestTest.java | 43 ----- .../downloader/RepositoryRequestTest.java | 67 -------- 14 files changed, 997 deletions(-) delete mode 100644 src/main/java/me/qeklydev/downloader/codec/DeserializationProvider.java delete mode 100644 src/main/java/me/qeklydev/downloader/codec/RepositoryModelCodec.java delete mode 100644 src/main/java/me/qeklydev/downloader/http/HTTPModelRequest.java delete mode 100644 src/main/java/me/qeklydev/downloader/http/HTTPReleaseModelRequest.java delete mode 100644 src/main/java/me/qeklydev/downloader/http/HTTPRepositoryModelRequest.java delete mode 100644 src/main/java/me/qeklydev/downloader/http/package-info.java delete mode 100644 src/main/java/me/qeklydev/downloader/io/IoAsyncUtils.java delete mode 100644 src/main/java/me/qeklydev/downloader/license/RepositoryLicense.java delete mode 100644 src/main/java/me/qeklydev/downloader/license/package-info.java delete mode 100644 src/main/java/me/qeklydev/downloader/release/ReleaseModel.java delete mode 100644 src/main/java/me/qeklydev/downloader/repository/GitHubRepositoryModel.java delete mode 100644 src/main/java/me/qeklydev/downloader/repository/package-info.java delete mode 100644 src/test/java/me/qeklydev/downloader/ReleaseRequestTest.java delete mode 100644 src/test/java/me/qeklydev/downloader/RepositoryRequestTest.java diff --git a/src/main/java/me/qeklydev/downloader/codec/DeserializationProvider.java b/src/main/java/me/qeklydev/downloader/codec/DeserializationProvider.java deleted file mode 100644 index 53462c2..0000000 --- a/src/main/java/me/qeklydev/downloader/codec/DeserializationProvider.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.codec; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import java.lang.reflect.Type; -import me.qeklydev.downloader.release.ReleaseModel; -import me.qeklydev.downloader.repository.GitHubRepositoryModel; -import org.jetbrains.annotations.NotNull; - -/** - * Provide a builder pattern for models deserialization - * handling. - * - * @since 0.2.2 - */ -public final class DeserializationProvider { - /** - * The GSON provider with the custom codecs - * for release and repository models deserialization. - * - * @since 0.0.1 - */ - private static final Gson GSON_PROVIDER = new GsonBuilder() - .registerTypeAdapter(ReleaseModel.class, ReleaseModelCodec.INSTANCE) - .registerTypeAdapter(GitHubRepositoryModel.class, RepositoryModelCodec.INSTANCE) - .create(); - - /** - * Creates a new builder for deserialization model - * request. - * - * @return A new {@link Builder}. - * @since 0.2.2 - */ - public static @NotNull Builder builder() { - return new Builder(); - } - - /** - * This class is used to request new deserialization - * to needed models. - * - * @since 0.2.2 - */ - public static class Builder { - private String jsonBody; - private CodecType codecType; - - /** - * Sets the json-body for this builder instance. - * - * @param specifiedJsonBody The json required. - * @return This builder instance. - * @since 0.2.2 - */ - public @NotNull Builder jsonBody(final @NotNull String specifiedJsonBody) { - this.jsonBody = specifiedJsonBody; - return this; - } - - /** - * Sets the codec-type for this builder instance. - * - * @param specifiedCodecType The {@link CodecType} needed. - * @return This builder instance. - * @since 0.2.2 - */ - public @NotNull Builder codecType(final @NotNull CodecType specifiedCodecType) { - this.codecType = specifiedCodecType; - return this; - } - - /** - * Creates a new model with the specified json body - * and codec type. Could throw an {@code IllegalStateException} - * if the json-body or codec-type wasn't specified. - * - * @param A generic type, can be cast to any needed - * model. - * @return The deserialized model. - * @since 0.2.2 - */ - public @NotNull T build() { - // Check if json-body or codec-type have been specified - // on builder. - if (this.jsonBody == null || this.codecType == null) { - throw new IllegalStateException("Json body or codec type for the deserialization have not been specified!"); - } - // Should never be null because codec-type specified always is valid. - return GSON_PROVIDER.fromJson(this.jsonBody, this.codecType.typeClass()); - } - } - - /** - * This enum is used to specify and provide the selected type - * for the deserialization requested. - * - * @since 0.2.2 - */ - public enum CodecType { - /** - * Used for {@link ReleaseModel} types. - * - * @since 0.2.2 - */ - RELEASE(ReleaseModel.class), - /** - * Used for {@link GitHubRepositoryModel} types. - * - * @since 0.2.2 - */ - REPOSITORY(GitHubRepositoryModel.class); - - private final Type type; - - CodecType(final @NotNull Type type) { - this.type = type; - } - - /** - * Returns the type for the enum constant - * used. - * - * @since 0.2.2 - */ - public @NotNull Type typeClass() { - return this.type; - } - } -} diff --git a/src/main/java/me/qeklydev/downloader/codec/RepositoryModelCodec.java b/src/main/java/me/qeklydev/downloader/codec/RepositoryModelCodec.java deleted file mode 100644 index 8247af9..0000000 --- a/src/main/java/me/qeklydev/downloader/codec/RepositoryModelCodec.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.codec; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import java.lang.reflect.Type; -import java.net.http.HttpClient; -import java.util.ArrayList; -import java.util.Locale; -import me.qeklydev.downloader.GitHubURLProvider; -import me.qeklydev.downloader.http.HTTPReleaseModelRequest; -import me.qeklydev.downloader.license.RepositoryLicense; -import me.qeklydev.downloader.logger.LoggerUtils; -import me.qeklydev.downloader.release.ReleaseModel; -import me.qeklydev.downloader.repository.GitHubRepositoryModel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Json deserializer implementation for handle provided - * json data for repository models serialization. - * - * @since 0.0.1 - */ -public enum RepositoryModelCodec implements JsonDeserializer { - /** - * Lazy-Singleton Used for rapid-access to class instance. - * - * @since 0.0.1 - */ - INSTANCE; - - @Override - public @NotNull GitHubRepositoryModel deserialize(final @NotNull JsonElement jsonElement, final @NotNull Type type, - final @NotNull JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { - final var jsonObject = jsonElement.getAsJsonObject(); - final var repositoryOwner = jsonObject.get("owner").getAsJsonObject().get("login").getAsString(); - final var repositoryName = jsonObject.get("name").getAsString(); - final var description = jsonObject.get("description").getAsString(); - final var licenseKey = jsonObject.get("license").getAsJsonObject().get("key").getAsString(); - // Verifies if the license key for this repository is "gpl-3.0", - // if it is true, we will replace the character '-' and '.' by a - // '_' character and make it uppercase to be parsed into a valid enum. - var licenseType = RepositoryLicense.UNLICENSE; // Default license type for parsing. - try { - // We use a try/catch due to possible failed parsing for licenses - // keys from GitHub-API, and the already defined on the licenses enum. - licenseType = RepositoryLicense.valueOf(licenseKey.equals("gpl-3.0") - ? licenseKey.replace("-", "_") - .replace(".", "_") - .toUpperCase(Locale.ROOT) - : licenseKey.toUpperCase(Locale.ROOT)); - } catch (final IllegalArgumentException exception) { - // Yeah, something went wrong. - LoggerUtils.error("Failed to parse license type since GitHub-API request."); - // So, license for this repository have not been provided. - licenseType = null; - } - final var canBeForked = jsonObject.get("allow_forking").getAsBoolean(); - final var starsAmount = jsonObject.get("stargazers_count").getAsInt(); - final var forksAmount = jsonObject.get("forks_count").getAsInt(); - final var isForked = jsonObject.get("fork").getAsBoolean(); - // If this repository is a fork of another repository, we need to - // get the name of the owner of the original repository. - final var repositoryParent = isForked - ? jsonObject.get("parent").getAsJsonObject().get("owner").getAsJsonObject().get("login").getAsString() - : null; - final var isPrivate = jsonObject.get("private").getAsBoolean(); - final var isArchived = jsonObject.get("archived").getAsBoolean(); - final var isDisabled = jsonObject.get("disabled").getAsBoolean(); - final var mostUsedLanguage = jsonObject.get("language").getAsString(); - final var providedTopicsArray = jsonObject.get("topics").getAsJsonArray(); - final var topicsList = new ArrayList(providedTopicsArray.size()); - for (final var topicElement : providedTopicsArray) { - topicsList.add(topicElement.getAsString()); - } - // We need to request the release model for this repository, - // if the value is null, the latest release doesn't exist. - // Other-wise just use the given model. - final var latestReleaseModel = this.provideReleaseModel(GitHubURLProvider.of(repositoryOwner, repositoryName)); - // After obtain all required information from JSON-body provided, - // we create a new object that represents this requested repository - // with all needed information. - return new GitHubRepositoryModel( - repositoryOwner, repositoryName, description, licenseType, latestReleaseModel, - isForked, repositoryParent, canBeForked, starsAmount, forksAmount, - !isPrivate, isArchived, isDisabled, mostUsedLanguage, topicsList); - } - - /** - * Creates a new {@code HTTPReleaseModelRequest} and request the model - * for the latest repository release, this could be {@code null} - * if the repository doesn't have any release. - * - * @param repository the repository url. - * @return The {@link ReleaseModel}, or {@code null}. - * @since 0.0.1 - * @see HTTPReleaseModelRequest#provideModel() - */ - private @Nullable ReleaseModel provideReleaseModel(final @NotNull String repository) { - final var httpReleaseModelRequest = new HTTPReleaseModelRequest(HttpClient.newHttpClient(), repository); - return httpReleaseModelRequest.provideModel(); - } -} diff --git a/src/main/java/me/qeklydev/downloader/http/HTTPModelRequest.java b/src/main/java/me/qeklydev/downloader/http/HTTPModelRequest.java deleted file mode 100644 index 475e878..0000000 --- a/src/main/java/me/qeklydev/downloader/http/HTTPModelRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.http; - -import org.jetbrains.annotations.Nullable; - -/** - * This interface is used for handle HTTP requests triggered - * using the GitHub API, and provides half-complete information - * about these repositories/releases. - * - * @param The model that will be returned after the - * @since 0.0.1 - */ -public interface HTTPModelRequest { - /** - * Executes the request to the API for receive a json - * body with the repository, or release information, this - * data will be serialized using the specific codec type, - * and will return the built needed model. - * - * @return The model needed for this request type. - * Otherwise {@code null}. - * @since 0.0.1 - * @see HTTPModelRequest#executeGETRequest() - */ - @Nullable T provideModel(); - - /** - * Executes a GET type request to the API with a specific - * time-out, the response can be a Json text. Or {@code null} - * if the repository doesn't exist, or an exception was triggered - * during the operation. - * - * @return A possible {@code null} {@link String} that is the JSON-body - * response for this request. - * @since 0.0.1 - */ - @Nullable String executeGETRequest(); -} diff --git a/src/main/java/me/qeklydev/downloader/http/HTTPReleaseModelRequest.java b/src/main/java/me/qeklydev/downloader/http/HTTPReleaseModelRequest.java deleted file mode 100644 index 6693e2f..0000000 --- a/src/main/java/me/qeklydev/downloader/http/HTTPReleaseModelRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.http; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import me.qeklydev.downloader.codec.DeserializationProvider; -import me.qeklydev.downloader.logger.LoggerUtils; -import me.qeklydev.downloader.release.ReleaseModel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This record class is used to proportionate handling - * about the latest requests of any GitHub repository. - * - * @param httpClient the {@link HttpClient} for the - * request. - * @param repository the requested repository. - * @since 0.0.1 - */ -public record HTTPReleaseModelRequest(@NotNull HttpClient httpClient, @NotNull String repository) implements HTTPModelRequest { - @Override - public @Nullable ReleaseModel provideModel() { - final var bodyOrNull = this.executeGETRequest(); - // Once the request have been completed and future has ended, - // we obtain the response that will be a JSON-body, or null in - // case that an exception was triggered. - return (bodyOrNull == null) ? null : (ReleaseModel) DeserializationProvider.builder() - .jsonBody(bodyOrNull) - .codecType(DeserializationProvider.CodecType.RELEASE) - .build(); - } - - @Override - public @Nullable String executeGETRequest() { - try { - final var request = HttpRequest.newBuilder() - .GET() - .uri(new URI(this.repository + "/releases/latest")) - .build(); - final var asyncResponse = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); - // The repository request to the API is performed before this request - // execution, this request only will be executed if the requested repository - // exists, so, we can skip the non-found repositories check, and return the single - // JSON-body once the completable-future has ended for this request. - return asyncResponse.thenApply(HttpResponse::body).get(); - } catch (final Exception exception) { - LoggerUtils.error("Http request for repository latest release could not be completed."); - return null; - } - } -} diff --git a/src/main/java/me/qeklydev/downloader/http/HTTPRepositoryModelRequest.java b/src/main/java/me/qeklydev/downloader/http/HTTPRepositoryModelRequest.java deleted file mode 100644 index 9cb4166..0000000 --- a/src/main/java/me/qeklydev/downloader/http/HTTPRepositoryModelRequest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.http; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import me.qeklydev.downloader.codec.DeserializationProvider; -import me.qeklydev.downloader.logger.LoggerUtils; -import me.qeklydev.downloader.repository.GitHubRepositoryModel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This record class is used to proportionate handling - * about the requests for repositories information. - * - * @param httpClient the {@link HttpClient} for the - * request. - * @param repository the requested repository. - * @since 0.0.1 - */ -public record HTTPRepositoryModelRequest(@NotNull HttpClient httpClient, @NotNull String repository) implements HTTPModelRequest { - @Override - public @Nullable GitHubRepositoryModel provideModel() { - final var bodyOrNull = this.executeGETRequest(); - // Once the request have been completed and future has ended, - // we obtain the response that will be a JSON-body, or null in - // case that request has failed due to any reason (404 status code, - // or exception trigger). - return (bodyOrNull == null) ? null : (GitHubRepositoryModel) DeserializationProvider.builder() - .jsonBody(bodyOrNull) - .codecType(DeserializationProvider.CodecType.REPOSITORY) - .build(); - } - - @Override - public @Nullable String executeGETRequest() { - try { - final var request = HttpRequest.newBuilder() - .GET() - .uri(new URI(this.repository)) - .build(); - final var asyncResponse = this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); - // Once the request has been completed, we obtain the response from - // the API, then we check if the returned status code is 404 (non-existing - // repository). - return asyncResponse.thenApply(providedRequestResponse -> { - final var responseStatusCode = providedRequestResponse.statusCode(); - // We need to check if the requested repository exists, - // in this case the request will respond with a 404 - // status code, in another case we can return the JSON-body. - return (responseStatusCode == 404) ? null : providedRequestResponse.body(); - // After this operation have been completed, we return the final result for - // this future. - }).get(); - } catch (final Exception exception) { - LoggerUtils.error("Http request for repository could not be completed."); - return null; - } - } -} diff --git a/src/main/java/me/qeklydev/downloader/http/package-info.java b/src/main/java/me/qeklydev/downloader/http/package-info.java deleted file mode 100644 index c91d6fb..0000000 --- a/src/main/java/me/qeklydev/downloader/http/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Provides model and implementations for repository and releases - * HTTP requests handling. - * - * @since 0.0.1 - */ -package me.qeklydev.downloader.http; \ No newline at end of file diff --git a/src/main/java/me/qeklydev/downloader/io/IoAsyncUtils.java b/src/main/java/me/qeklydev/downloader/io/IoAsyncUtils.java deleted file mode 100644 index 38bad39..0000000 --- a/src/main/java/me/qeklydev/downloader/io/IoAsyncUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.io; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URI; -import java.nio.channels.Channels; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import me.qeklydev.downloader.logger.LoggerUtils; -import org.jetbrains.annotations.NotNull; - -/** - * This class is used to perform async I/O operations. - * - * @since 0.0.1 - */ -public final class IoAsyncUtils { - /** - * Represents the value that will be returned if operation - * has suffered a mishap. - * - * @since 0.1.2 - */ - private static final long SINGLE_RETURN_VALUE = 0L; - /** - * A cached-thread-pool used to perform I/O writing or - * reading operations. - * - * @since 0.0.1 - */ - private static final ExecutorService IO_POOL_THREAD = Executors.newCachedThreadPool(); - - private IoAsyncUtils() { - throw new UnsupportedOperationException("This class is used for utility and cannot be instantiated."); - } - - /** - * Uses the URL object provided to download the content - * of that URL and be written into a specified file. - * - * @param fileName the name of the file to be written. - * @param requestedUrl the url for processing. - * @return The {@link CompletableFuture} with a boolean - * status provided for this operation. - *

- * - {@code byte-amount} will provide the amount of bytes - * read for this operation. - *

- * - {@link IoAsyncUtils#SINGLE_RETURN_VALUE} if there wasn't any byte read or an exception - * was triggered. - * @since 0.0.1 - * @see IoAsyncUtils#SINGLE_RETURN_VALUE - */ - public static @NotNull CompletableFuture< - @NotNull Long> downloadOf(final @NotNull String fileName, final @NotNull String requestedUrl) { - return CompletableFuture.supplyAsync(() -> { - // Using the provided URL we create a new URI object with this - // same url, this method will throw an exception if given url - // syntax is not valid, and will provide a message for error - // description. - final var uri = URI.create(requestedUrl); - // We convert the URI instance into a URL object, and then we open - // the stream for channel data reading and write bytes to this channel - // at the given position. - try (final var readableByteChannel = Channels.newChannel(uri.toURL().openStream()); - final var fileOutputStream = new FileOutputStream(fileName)) { - return fileOutputStream.getChannel().transferFrom( - readableByteChannel, /* The initial position for bytes transfer. */ 0, Long.MAX_VALUE); - } catch (final IOException exception) { - LoggerUtils.error("URL content download has suffered an exception during the process."); - return SINGLE_RETURN_VALUE; - } - }, IO_POOL_THREAD); - } -} diff --git a/src/main/java/me/qeklydev/downloader/license/RepositoryLicense.java b/src/main/java/me/qeklydev/downloader/license/RepositoryLicense.java deleted file mode 100644 index 5822411..0000000 --- a/src/main/java/me/qeklydev/downloader/license/RepositoryLicense.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.license; - -import org.jetbrains.annotations.NotNull; - -/** - * This enum is used to represent every license type - * that can be used for a GitHub repository. - * - * @since 0.0.1 - */ -public enum RepositoryLicense { - /** - * Represents the GPL 3.0 license type. - * - * @since 0.0.1 - */ - GPL_3_0("GNU General Public License v3.0","https://www.gnu.org/licenses/gpl-3.0.html"), - /** - * Represents the GPL 2.0 license type. - * - * @since 0.2.3 - */ - GPL_2_0("GNU General Public License v2.0", "https://www.gnu.org/licenses/gpl-2.0.html"), - /** - * Represents the MIT license type. - * - * @since 0.0.1 - */ - MIT("MIT License", "https://opensource.org/licenses/MIT"), - /** - * Represents the Apache 2.0 license type. - * - * @since 0.0.1 - */ - APACHE("Apache License 2.0", "https://www.apache.org/licenses/LICENSE-2.0"), - /** - * Represents the Mozilla Public 2.0 license type. - * - * @since 0.0.1 - */ - MOZILLA("Mozilla Public License 2.0", "https://www.mozilla.org/en-US/MPL/2.0/"), - /** - * Represents the BSD license type. - * - * @since 0.0.1 - */ - BSD_3("BSD 3-Clause License", "https://opensource.org/licenses/BSD-3-Clause"), - /** - * Represents the BSD 2-Clause Simplified License type. - * - * @since 0.2.3 - */ - BSD_2("BSD 2-Clause Simplified License", "https://opensource.org/licenses/BSD-2-Clause"), - /** - * Indicates that non-license have been specified. - * - * @since 0.0.1 - */ - UNLICENSE("The Unlicense", "https://unlicense.org/"), - /** - * Represents the Eclipse Public 2.0 license type. - * - * @since 0.0.1 - */ - ECLIPSE("Eclipse Public License 2.0", "https://www.eclipse.org/legal/epl-2.0/"), - /** - * Represents the Creative C-Z 1.0 license type. - * - * @since 0.0.1 - */ - CC("Creative Commons Zero v1.0 Universal", "https://creativecommons.org/publicdomain/zero/1.0/"), - /** - * Represents the Boost Software License 1.0 license type. - * - * @since 0.2.3 - */ - BOOST_SOFTWARE("Boost Software License 1.0", "https://www.boost.org/LICENSE_1_0.txt"), - /** - * Represents the GNU Lesser General Public License v3.0 license type. - * - * @since 0.2.3 - */ - LESSER_GPL("GNU Lesser General Public License v3.0", "https://www.gnu.org/licenses/lgpl-3.0.html"), - /** - * Represents the GNU Affero General Public License v3.0 license type. - * - * @since 0.2.3 - */ - AFFERO_GPL("GNU Affero General Public License v3.0", "https://www.gnu.org/licenses/agpl-3.0.html"); - - private final String fullName; - private final String url; - - RepositoryLicense(final @NotNull String fullName, final @NotNull String url) { - this.fullName = fullName; - this.url = url; - } - - /** - * Returns the name of the license. - * - * @return the license name. - * @since 0.0.1 - */ - public @NotNull String fullName() { - return this.fullName; - } - - /** - * Returns the URL for the license - * visualization. - * - * @return the license URL. - * @since 0.0.1 - */ - public @NotNull String url() { - return this.url; - } -} diff --git a/src/main/java/me/qeklydev/downloader/license/package-info.java b/src/main/java/me/qeklydev/downloader/license/package-info.java deleted file mode 100644 index 9979f36..0000000 --- a/src/main/java/me/qeklydev/downloader/license/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Provides enum to identify the license of type used - * on the GitHub repositories. - * - * @since 0.0.1 - */ -package me.qeklydev.downloader.license; \ No newline at end of file diff --git a/src/main/java/me/qeklydev/downloader/release/ReleaseModel.java b/src/main/java/me/qeklydev/downloader/release/ReleaseModel.java deleted file mode 100644 index f220080..0000000 --- a/src/main/java/me/qeklydev/downloader/release/ReleaseModel.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.release; - -import java.util.List; -import me.qeklydev.downloader.io.IoAsyncUtils; -import me.qeklydev.downloader.logger.LoggerUtils; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -/** - * This record is used to store information about - * the requested repository release. - * - * @param version the release version. - * @param assets the list with the url and name - * of the assets for this release. - * @since 0.0.1 - */ -public record ReleaseModel(@NotNull String version, @NotNull List<@NotNull String> assets) { - /** - * Starts the requested asset download using the URL requested - * since the assets list based-on the given position value. An - * {@code IllegalArgumentException} could be triggered if requested - * position is a negative value, or is higher than the assets list size. - * - * @param position the position of the file URL to download - * of the assets list. - * @return A boolean value depending on operation result, - * {@code true} if operation was successful. Otherwise - * {@code false} if there was not bytes read, or the url - * isn't valid. - * @since 0.0.1 - */ - public boolean downloadAsset(final int position) { - final var assetsLength = this.assets.size(); - // Check if the position given is negative, or the value - // is higher than the size of the list. - if ((position < 0) || position > assetsLength) { - LoggerUtils.error("Requested URL position cannot be negative, or be greater than the release assets amount."); - return false; - } - final var provider = this.assets.get(position).split(":", 2); - // We remove additional spaces for avoid exception throws. - final var requestedUrl = provider[1].trim(); - final var bytesAmountReadDuringOperation = IoAsyncUtils.downloadOf( - /* We specify the file name with his extension as first parameter. */ provider[0], requestedUrl).join(); - // We wait until completable-future has ended, and we obtain - // the final result for that operation, and then we check if - // this operation was successful verifying if read-bytes amount - // is higher than zero, if it is true, the operation was success and - // the asset was downloaded correctly, otherwise the operation failed. - return bytesAmountReadDuringOperation > 0; - } - - /** - * Returns a string array with every value - * of the version. - * - * @return The semantic string version. - * @since 0.0.1 - */ - @Contract(pure = true) - public @NotNull String @NotNull [] semanticVersion() { - // e.g 2.10.1 -> ["2", "10", "1"] - return this.version.split("\\."); - } - - /** - * Compares the given values to determinate if the semantic - * version is equal. - * - * @param parts the version parts. - * @return The boolean status for this operation, - * {@code true} if any value is similar, otherwise return - * {@code false}. - * @since 0.0.1 - */ - public boolean semanticEqualsTo(final String @NotNull... parts) { - final var version = this.semanticVersion(); - for (int i = 0; i < parts.length; i++) { - if (parts[i].equals(version[i])) { - // This value is similar on the current iterated - // semantic version element. - break; - } - // Any value is similar so return false. - return false; - } - return true; - } -} diff --git a/src/main/java/me/qeklydev/downloader/repository/GitHubRepositoryModel.java b/src/main/java/me/qeklydev/downloader/repository/GitHubRepositoryModel.java deleted file mode 100644 index 411c6e9..0000000 --- a/src/main/java/me/qeklydev/downloader/repository/GitHubRepositoryModel.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.repository; - -import java.util.List; -import me.qeklydev.downloader.license.RepositoryLicense; -import me.qeklydev.downloader.release.ReleaseModel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This record represents a GitHub repository model based-on - * the information provided by the HTTP request to the API. - * - * @param owner the owner of the repository. - * @param name the name of the repository. - * @param description the description of the repository. - * @param license the license type of the repository, - * {@code null} if any license was provided. - * @param releaseModel the abstract model for the latest - * repository release, {@code null} - * if there's not any release. - * @param isForked if the repository is a fork. - * @param parent the original owner of this repository - * if the repository is not a fork, is {@code null}. - * @param canBeForked if the repository can be forked. - * @param stars the amount of stars the repository has. - * @param forks the amount of forks the repository has. - * @param isPublic if the repository is public. - * @param isArchived if the repository is archived. - * @param isDisabled if the repository is disabled. - * @param language the most used language on this - * repository, {@code null} if there's - * not any language. - * @param topics the topics of the repository, {@code null} - * if there's no topics. - * @since 0.0.1 - */ -public record GitHubRepositoryModel( - @NotNull String owner, @NotNull String name, @NotNull String description, @Nullable RepositoryLicense license, - @Nullable ReleaseModel releaseModel, boolean isForked, @Nullable String parent, boolean canBeForked, int stars, int forks, boolean isPublic, - boolean isArchived, boolean isDisabled, @Nullable String language, @Nullable List<@NotNull String> topics) { -} diff --git a/src/main/java/me/qeklydev/downloader/repository/package-info.java b/src/main/java/me/qeklydev/downloader/repository/package-info.java deleted file mode 100644 index 5323300..0000000 --- a/src/main/java/me/qeklydev/downloader/repository/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Provides a record model to store the information of - * the requested repositories to the GitHub API. - * - * @since 0.0.1 - */ -package me.qeklydev.downloader.repository; \ No newline at end of file diff --git a/src/test/java/me/qeklydev/downloader/ReleaseRequestTest.java b/src/test/java/me/qeklydev/downloader/ReleaseRequestTest.java deleted file mode 100644 index e892c4d..0000000 --- a/src/test/java/me/qeklydev/downloader/ReleaseRequestTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader; - -import java.net.http.HttpClient; -import java.time.Duration; -import me.qeklydev.downloader.http.HTTPReleaseModelRequest; -import org.junit.jupiter.api.Test; - -public class ReleaseRequestTest { - @Test - void test() { - final var httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .connectTimeout(Duration.ofSeconds(5)) - .build(); - final var releaseModelRequest = new HTTPReleaseModelRequest( - httpClient, GitHubURLProvider.of("aivruu", "release-downloader")); - final var releaseModel = releaseModelRequest.provideModel(); - // Check if the repository have a release. - if (releaseModel == null) { - System.out.println("Something went wrong, and release information was not provided."); - return; - } - System.out.println("Version: " + releaseModel.version()); - releaseModel.assets().forEach(System.out::println); - } -} diff --git a/src/test/java/me/qeklydev/downloader/RepositoryRequestTest.java b/src/test/java/me/qeklydev/downloader/RepositoryRequestTest.java deleted file mode 100644 index 10d172c..0000000 --- a/src/test/java/me/qeklydev/downloader/RepositoryRequestTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader; - -import java.net.http.HttpClient; -import java.time.Duration; -import me.qeklydev.downloader.http.HTTPRepositoryModelRequest; -import org.junit.jupiter.api.Test; - -public class RepositoryRequestTest { - @Test - void test() { - final var httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .connectTimeout(Duration.ofSeconds(60)) - .build(); - final var httpRepositoryRequest = new HTTPRepositoryModelRequest( - httpClient, GitHubURLProvider.of("aivruu", "AnnounceMessages")); - final var repositoryModel = httpRepositoryRequest.provideModel(); - if (repositoryModel == null) { - System.out.println("Repository doesn't exists, or HTTP request has failed."); - return; - } - final var license = repositoryModel.license(); - System.out.println("Information about aivruu/AnnounceMessages repository:"); - System.out.println("Owner: " + repositoryModel.owner()); - System.out.println("Name: " + repositoryModel.name()); - System.out.println("Description: " + repositoryModel.description()); - // We can skip the non-null check for the license type, because - // this repository have a defined license (GPL 3.0). - System.out.println("License: {"); - System.out.println(" Key: " + license.name()); - System.out.println(" Name: " + license.fullName()); - System.out.println(" Url: " + license.url()); - System.out.println("}"); - // The release model can be null, but this repository does - // have a release published, so we can skip the null check. - System.out.println("Latest Release: " + repositoryModel.releaseModel().version()); - System.out.println("Forked: " + repositoryModel.isForked()); - System.out.println("Accept Forks: " + repositoryModel.canBeForked()); - System.out.println("Stars: " + repositoryModel.stars()); - System.out.println("Forks: " + repositoryModel.forks()); - System.out.println("Public: " + repositoryModel.isPublic()); - System.out.println("Archived: " + repositoryModel.isArchived()); - System.out.println("Most-Used Language: " + repositoryModel.language()); - // Java 21 Feature - // - // After show all necessary repository information to the user, - // we close the http-client and not more request are accepted. - httpClient.close(); - } -} From b51c0559d4d6bd829eba49022234a640456da532 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:43:23 -0400 Subject: [PATCH 03/39] chore: Remove old test classes, models and implementations --- header.txt | 17 ----- .../downloader/GitHubURLProvider.java | 52 -------------- .../downloader/codec/ReleaseModelCodec.java | 71 ------------------- .../me/qeklydev/downloader/package-info.java | 7 -- .../downloader/AssetDownloadTest.java | 44 ------------ 5 files changed, 191 deletions(-) delete mode 100644 header.txt delete mode 100644 src/main/java/me/qeklydev/downloader/GitHubURLProvider.java delete mode 100644 src/main/java/me/qeklydev/downloader/codec/ReleaseModelCodec.java delete mode 100644 src/main/java/me/qeklydev/downloader/package-info.java delete mode 100644 src/test/java/me/qeklydev/downloader/AssetDownloadTest.java diff --git a/header.txt b/header.txt deleted file mode 100644 index 6dda8bf..0000000 --- a/header.txt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ diff --git a/src/main/java/me/qeklydev/downloader/GitHubURLProvider.java b/src/main/java/me/qeklydev/downloader/GitHubURLProvider.java deleted file mode 100644 index 06e8b2b..0000000 --- a/src/main/java/me/qeklydev/downloader/GitHubURLProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader; - -import org.jetbrains.annotations.NotNull; - -/** - * This class is used to create formatted GitHub API - * HTTPS urls. - * - * @since 0.0.1 - */ -public final class GitHubURLProvider { - /** - * GitHub API URL used for repositories requests. - * - * @since 0.0.1 - */ - private static final String GITHUB_API_URL = "https://api.github.com/repos/%s/%s"; - - private GitHubURLProvider() { - throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); - } - - /** - * Returns the {@link GitHubURLProvider#GITHUB_API_URL} formatted with - * the values provided to create a usable URL for HTTPS requests. - * - * @param user the github user. - * @param repository the repository of the user. - * @return A URL formatted for HTTPS requests to GitHub API. - * @since 0.0.1 - */ - public static @NotNull String of(final @NotNull String user, final @NotNull String repository) { - return GITHUB_API_URL.formatted(user, repository); - } -} diff --git a/src/main/java/me/qeklydev/downloader/codec/ReleaseModelCodec.java b/src/main/java/me/qeklydev/downloader/codec/ReleaseModelCodec.java deleted file mode 100644 index 0ff5253..0000000 --- a/src/main/java/me/qeklydev/downloader/codec/ReleaseModelCodec.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.codec; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import java.lang.reflect.Type; -import java.util.ArrayList; -import me.qeklydev.downloader.release.ReleaseModel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Json deserializer implementation for handle provided - * json data for release models serialization. - * - * @since 0.0.1 - */ -public enum ReleaseModelCodec implements JsonDeserializer { - /** - * Lazy-Singleton Used for rapid-access to class instance. - * - * @since 0.0.1 - */ - INSTANCE; - - /** - * Used to concatenate values from the JSON body provided - * by HTTP requests. - * - * @since 0.0.1 - */ - public static final StringBuilder BUILDER = new StringBuilder(); - - @Override - public @Nullable ReleaseModel deserialize(final @NotNull JsonElement jsonElement, final @NotNull Type type, - final @NotNull JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { - final var providedJsonObject = jsonElement.getAsJsonObject(); - final var possibleMessage = providedJsonObject.get("message"); - if (possibleMessage != null) { // Checks if the latest repository exists. - return null; - } - final var providedAssets = providedJsonObject.getAsJsonArray("assets").asList(); - final var assetsList = new ArrayList(providedAssets.size()); - for (final var element : providedAssets) { - final var assetObject = element.getAsJsonObject(); - assetsList.add(BUILDER.append(assetObject.get("name").getAsString()) - .append(": ") - .append(assetObject.get("browser_download_url").getAsString()) - .toString()); - } - return new ReleaseModel(providedJsonObject.get("tag_name").getAsString(), assetsList); - } -} diff --git a/src/main/java/me/qeklydev/downloader/package-info.java b/src/main/java/me/qeklydev/downloader/package-info.java deleted file mode 100644 index 01860ef..0000000 --- a/src/main/java/me/qeklydev/downloader/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Provides models and tools for GitHub repositories - * releases handling. - * - * @since 0.0.1 - */ -package me.qeklydev.downloader; \ No newline at end of file diff --git a/src/test/java/me/qeklydev/downloader/AssetDownloadTest.java b/src/test/java/me/qeklydev/downloader/AssetDownloadTest.java deleted file mode 100644 index 1726d71..0000000 --- a/src/test/java/me/qeklydev/downloader/AssetDownloadTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader; - -import java.net.http.HttpClient; -import java.time.Duration; -import me.qeklydev.downloader.http.HTTPReleaseModelRequest; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class AssetDownloadTest { - @Test - void downloadTest() { - final var httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .connectTimeout(Duration.ofSeconds(5)) - .build(); - final var releaseModelRequest = new HTTPReleaseModelRequest( - httpClient, GitHubURLProvider.of("java-decompiler", "jd-gui")); - final var releaseModel = releaseModelRequest.provideModel(); - // Check if the repository have a release. - if (releaseModel == null) { - System.out.println("Something went wrong, and release information was not provided."); - return; - } - final var assetWasDownloaded = releaseModel.downloadAsset(/* We will download the first asset for the requested release. */ 0); - Assertions.assertTrue(assetWasDownloaded); - } -} From 857ea587e64be3a46464b3237bd03c74c7159b26 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:43:56 -0400 Subject: [PATCH 04/39] chore(projects): Include sub-projects on project's settings file --- settings.gradle.kts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index fa55ba3..07a4603 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,9 @@ -rootProject.name = "release-downloader" +@file:Suppress("UnstableApiUsage") + +rootProject.name = "repo-viewer" + +sequenceOf("api", "implementation").forEach { + val kerbalProject = ":${rootProject.name}-$it" + include(kerbalProject) + project(kerbalProject).projectDir = file(it) +} From 5034718348e994c1d180a1e2d1c44e13e8814013 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:47:51 -0400 Subject: [PATCH 05/39] chore(project): Declare new project structure with new properties and settings --- .editorconfig | 4 +- .gitignore | 3 ++ api/build.gradle.kts | 4 ++ build.gradle.kts | 85 +++++++++++++++++++-------------- README.md => docs/readme.md | 0 gradle.properties | 4 +- gradle/libs.versions.toml | 13 +++-- implementation/build.gradle.kts | 5 ++ license/header.txt | 16 +++++++ LICENSE => license/license.txt | 0 10 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 api/build.gradle.kts rename README.md => docs/readme.md (100%) create mode 100644 implementation/build.gradle.kts create mode 100644 license/header.txt rename LICENSE => license/license.txt (100%) diff --git a/.editorconfig b/.editorconfig index 335b58d..ca0242d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,9 +4,9 @@ root = true charset = utf-8 indent_size = 2 indent_style = space -max_line_length = 200 +max_line_length = 121 -ij_continuation_indent_size = 4 +ij_continuation_indent_size = 2 ij_java_class_count_to_use_import_on_demand = 999999 ij_java_insert_inner_class_imports = false ij_java_names_count_to_use_import_on_demand = 999999 diff --git a/.gitignore b/.gitignore index 5e4d6f8..1e20804 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Ignore Gradle project-specific cache directory .gradle +# Ignore git attributes. +.gitattributes + # Ignore Gradle build output directory build diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 0000000..a80bc93 --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,4 @@ +dependencies { + api(libs.annotations) + api(libs.gson) +} diff --git a/build.gradle.kts b/build.gradle.kts index 05e0392..cc12aa1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,59 +1,72 @@ plugins { - `java-library` - `maven-publish` - alias(libs.plugins.indra) - alias(libs.plugins.spotless) - alias(libs.plugins.shadow) + `java-library` + `maven-publish` + alias(libs.plugins.indra) + alias(libs.plugins.spotless) + alias(libs.plugins.shadow) } -repositories { - mavenCentral() -} +subprojects { + apply(plugin = "java-library") + apply(plugin = "maven-publish") + apply(plugin = "net.kyori.indra") + apply(plugin = "com.diffplug.spotless") + apply(plugin = "io.github.goooler.shadow") -indra { + indra { javaVersions { - target(21) - minimumToolchain(21) + target(21) + minimumToolchain(21) } -} + } -spotless { + repositories { + mavenCentral() + } + + spotless { java { - licenseHeaderFile("$rootDir/header.txt") - removeUnusedImports() - trimTrailingWhitespace() - indentWithSpaces(2) + licenseHeaderFile("$rootDir/license/header.txt") + removeUnusedImports() + trimTrailingWhitespace() + indentWithSpaces(2) } kotlinGradle { - trimTrailingWhitespace() - indentWithSpaces(2) + trimTrailingWhitespace() + indentWithSpaces(2) } -} - -dependencies { - compileOnly("org.jetbrains:annotations:24.0.1") - implementation("com.google.code.gson:gson:2.10.1") + } + dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.1") -} + } -tasks { + tasks { compileJava { - dependsOn("spotlessApply") - options.compilerArgs.add("-parameters") + dependsOn("spotlessApply") + options.compilerArgs.add("-parameters") } shadowJar { - archiveFileName.set(rootProject.name) - minimize() + archiveFileName.set(rootProject.name) + minimize() + + // Package expected to use as final directory for dependencies used. + val relocationFinalPackage = "io.github.aivruu.repoviewer.libs" - relocate("com.google.gson", "me.qeklydev.downloader.gson") + relocate("org.jetbrains.annotations", "$relocationFinalPackage.org.jetbrains.annotations") + relocate("com.google.gson", "$relocationFinalPackage.com.google.gson") } -} + } -publishing { + publishing { publications { - create("maven") { - from(components["java"]) - } + create("maven") { + groupId = "io.github.aivruu.repoviewer" + artifactId = project.name + version = project.version.toString() + + from(components["java"]) + } } + } } diff --git a/README.md b/docs/readme.md similarity index 100% rename from README.md rename to docs/readme.md diff --git a/gradle.properties b/gradle.properties index a4108af..58c5487 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.caching=true -group=me.qeklydev.downloader -version=1.3.4 +group=io.github.aivruu.repoviewer +version=2.3.4 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 38f14d5..870831f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,16 @@ [versions] -shadow = "8.1.7" +shadow = "8.1.8" spotless = "6.25.0" -indra = "3.0.1" +indra = "3.1.3" -# Version declaration for GSON. -gson = "2.10.1" +# Version declaration for GSON library. +gson = "2.11.0" +# Version declaration for JetBrains Annotations. +annotations = "24.0.1" [libraries] -gson = { group = "com.google.code", name = "gson", version.ref = "gson" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" } [plugins] shadow = { id = "io.github.goooler.shadow", version.ref = "shadow" } diff --git a/implementation/build.gradle.kts b/implementation/build.gradle.kts new file mode 100644 index 0000000..c1a0c38 --- /dev/null +++ b/implementation/build.gradle.kts @@ -0,0 +1,5 @@ +dependencies { + api(project(":repo-viewer-api")) + + api(libs.annotations) +} diff --git a/license/header.txt b/license/header.txt new file mode 100644 index 0000000..336d895 --- /dev/null +++ b/license/header.txt @@ -0,0 +1,16 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// diff --git a/LICENSE b/license/license.txt similarity index 100% rename from LICENSE rename to license/license.txt From 82b7bd494aac37fcbb2c4f7bcb57f73c81ae73e8 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:49:49 -0400 Subject: [PATCH 06/39] chore(docs): Modify and include some new documentation for packages --- .../github/aivruu/repoviewer/api}/codec/package-info.java | 2 +- .../aivruu/repoviewer/api/download}/package-info.java | 2 +- .../io/github/aivruu/repoviewer/api/http/package-info.java | 6 ++++++ .../github/aivruu/repoviewer/api}/logger/package-info.java | 2 +- .../aivruu/repoviewer/api}/release/package-info.java | 2 +- .../aivruu/repoviewer/api/repository/package-info.java | 7 +++++++ .../java/io.github.aivruu.repoviewer/package-info.java | 7 +++++++ 7 files changed, 24 insertions(+), 4 deletions(-) rename {src/main/java/me/qeklydev/downloader => api/src/main/java/io/github/aivruu/repoviewer/api}/codec/package-info.java (71%) rename {src/main/java/me/qeklydev/downloader/io => api/src/main/java/io/github/aivruu/repoviewer/api/download}/package-info.java (67%) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java rename {src/main/java/me/qeklydev/downloader => api/src/main/java/io/github/aivruu/repoviewer/api}/logger/package-info.java (59%) rename {src/main/java/me/qeklydev/downloader => api/src/main/java/io/github/aivruu/repoviewer/api}/release/package-info.java (65%) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/repository/package-info.java create mode 100644 implementation/src/main/java/io.github.aivruu.repoviewer/package-info.java diff --git a/src/main/java/me/qeklydev/downloader/codec/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/codec/package-info.java similarity index 71% rename from src/main/java/me/qeklydev/downloader/codec/package-info.java rename to api/src/main/java/io/github/aivruu/repoviewer/api/codec/package-info.java index 4c011bd..74478b5 100644 --- a/src/main/java/me/qeklydev/downloader/codec/package-info.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/codec/package-info.java @@ -4,4 +4,4 @@ * * @since 0.0.1 */ -package me.qeklydev.downloader.codec; \ No newline at end of file +package io.github.aivruu.repoviewer.api.codec; \ No newline at end of file diff --git a/src/main/java/me/qeklydev/downloader/io/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java similarity index 67% rename from src/main/java/me/qeklydev/downloader/io/package-info.java rename to api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java index 81a9305..b522682 100644 --- a/src/main/java/me/qeklydev/downloader/io/package-info.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java @@ -4,4 +4,4 @@ * * @since 0.0.1 */ -package me.qeklydev.downloader.io; \ No newline at end of file +package io.github.aivruu.repoviewer.api.download; \ No newline at end of file diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java new file mode 100644 index 0000000..bbdb393 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides base-interface and model for http-requests to GitHub's API. + * + * @since 0.0.1 + */ +package io.github.aivruu.repoviewer.api.http; \ No newline at end of file diff --git a/src/main/java/me/qeklydev/downloader/logger/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java similarity index 59% rename from src/main/java/me/qeklydev/downloader/logger/package-info.java rename to api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java index b427b55..b0abd82 100644 --- a/src/main/java/me/qeklydev/downloader/logger/package-info.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java @@ -3,4 +3,4 @@ * * @since 0.1.2 */ -package me.qeklydev.downloader.logger; \ No newline at end of file +package io.github.aivruu.repoviewer.api.logger; \ No newline at end of file diff --git a/src/main/java/me/qeklydev/downloader/release/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/release/package-info.java similarity index 65% rename from src/main/java/me/qeklydev/downloader/release/package-info.java rename to api/src/main/java/io/github/aivruu/repoviewer/api/release/package-info.java index 7bbe0ea..0d62e65 100644 --- a/src/main/java/me/qeklydev/downloader/release/package-info.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/release/package-info.java @@ -4,4 +4,4 @@ * * @since 0.0.1 */ -package me.qeklydev.downloader.release; \ No newline at end of file +package io.github.aivruu.repoviewer.api.release; \ No newline at end of file diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/repository/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/package-info.java new file mode 100644 index 0000000..70b1331 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides models for represent the requested repository, and their attributes, and builders + * for these models creation. + * + * @since 0.0.1 + */ +package io.github.aivruu.repoviewer.api.repository; \ No newline at end of file diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/package-info.java b/implementation/src/main/java/io.github.aivruu.repoviewer/package-info.java new file mode 100644 index 0000000..830e9ac --- /dev/null +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides URL-Builder provider for repository/release models, http-implementations for + * this models, and codec implementations for deserialization of these models. + * + * @since 2.3.4 + */ +package io.github.aivruu.repoviewer; From 1cc0542fa15f78d7af2a9eedd609ec4ea9539f2c Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:50:54 -0400 Subject: [PATCH 07/39] feat(test): Create new tests for verify http-requests, models and more --- .../aivruu/repoviewer/AssetsDownloadTest.java | 39 +++++++++++++++++++ .../aivruu/repoviewer/ReleaseRequestTest.java | 37 ++++++++++++++++++ .../repoviewer/RepositoryRequestTest.java | 38 ++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java create mode 100644 implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java create mode 100644 implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java diff --git a/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java b/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java new file mode 100644 index 0000000..2555727 --- /dev/null +++ b/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java @@ -0,0 +1,39 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.File; + +public class AssetsDownloadTest { + @Test + void downloadTest() { + // We create a custom release-model for this test. + final var latestReleaseModel = new LatestReleaseModel("1.3.4", + new String[]{ "https://github.com/aivruu/repo-viewer/releases/download/1.3.4/release-downloader-1.3.4.jar" }); + final var destinationDirectory = new File("downloads"); + if (!destinationDirectory.exists()) destinationDirectory.mkdir(); + + latestReleaseModel.downloadAll(destinationDirectory) + .whenComplete((downloaded, exception) -> Assertions.assertNull(exception, "Failed to download assets.")) + .thenAccept(downloaded -> + Assertions.assertTrue(downloaded, "Appears that all, or some assets, where not downloaded.")); + } +} diff --git a/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java b/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java new file mode 100644 index 0000000..b22b4cb --- /dev/null +++ b/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java @@ -0,0 +1,37 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ReleaseRequestTest { + @Test + void releaseRequest() { + final var releaseHttpRequest = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); + System.out.println("Requesting latest-release for repository: " + releaseHttpRequest.repository()); + final var requestResponse = releaseHttpRequest.request(10); + System.out.println("Made request for repository correctly."); + requestResponse.thenAccept(latestReleaseModel -> { + if (latestReleaseModel == null) { + Assertions.fail("Failed to deserialize json response into a LatestReleaseModel."); + } + System.out.println("Requested repository's latest-release %s deserialized correctly: %s" + .formatted(latestReleaseModel.version(), latestReleaseModel.assets()[0])); + }); + } +} diff --git a/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java b/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java new file mode 100644 index 0000000..5c5fa60 --- /dev/null +++ b/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java @@ -0,0 +1,38 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class RepositoryRequestTest { + @Test + void repositoryRequest() { + final var repositoryHttpRequest = new RepositoryHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); + System.out.print("Requesting repository from: " + repositoryHttpRequest.repository()); + final var requestResponse = repositoryHttpRequest.request(5); + Assertions.assertNotNull(requestResponse, + "Request response wasn't provided, check the given repository url for valid syntax."); + + requestResponse.thenAccept(repositoryModel -> { + if (repositoryModel == null) { + Assertions.fail("Failed to deserialize the json response into a GithubRepositoryModel."); + } + System.out.print("Requested repository %s deserialized correctly".formatted(repositoryModel.name())); + }); + } +} From 5132a780ea2f51e5d7c9d10b9cfff9a1b1d4ccf8 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 21:59:57 -0400 Subject: [PATCH 08/39] feat: Create new codecs, models and builders for Repository and Release models --- .../api/http/GithubHttpRequestModel.java | 58 ++++++++++ .../api/release/LatestReleaseModel.java | 104 ++++++++++++++++++ .../api/repository/GithubRepositoryModel.java | 46 ++++++++ .../repository/RepositoryModelBuilder.java | 65 +++++++++++ .../attribute/RepositoryAttributes.java | 23 ++++ .../RepositoryAttributesBuilder.java | 94 ++++++++++++++++ .../RepositoryHttpRequestModel.java | 66 +++++++++++ .../RepositoryUrlBuilder.java | 44 ++++++++ .../codec/type/RepositoryCodec.java | 83 ++++++++++++++ .../codec/type/RepositoryReleaseCodec.java | 57 ++++++++++ 10 files changed, 640 insertions(+) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/repository/GithubRepositoryModel.java create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/repository/RepositoryModelBuilder.java create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributes.java create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributesBuilder.java create mode 100644 implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java create mode 100644 implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java create mode 100644 implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java create mode 100644 implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java new file mode 100644 index 0000000..7e6ff11 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java @@ -0,0 +1,58 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.http; + +import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import org.jetbrains.annotations.Nullable; + +import java.net.http.HttpClient; +import java.util.concurrent.CompletableFuture; + +/** + * This base-model for another request-implementations such as for releases or repositories. + * + * @param The model-type to use for this request. + * @since 1.0.0 + */ +public interface GithubHttpRequestModel { + /** + * Executes the {@code GET} request-type using the specified {@link HttpClient}, and return + * the serialized-model if a {@code json-body} was provided, otherwise result will be a {@code null}. + * + * @param httpClient the {@link HttpClient} to use for this request. + * @param timeout the time-out for the request. + * @return The {@link CompletableFuture} with a {@code nullable} value. + * @since 2.3.4 + */ + CompletableFuture<@Nullable Model> requestUsing(final HttpClient httpClient, final int timeout); + + /** + * Executes the {@code GET} request-type using a default new {@link HttpClient}, once the + * request has ended, the early created {@link HttpClient} is closed. + * + * @param timeout the time-out for this request. + * @return The {@link CompletableFuture} with a {@code nullable} value. + * @see #requestUsing(HttpClient, int) + * @since 2.3.4 + */ + default CompletableFuture<@Nullable Model> request(final int timeout) { + final var httpClient = HttpClient.newHttpClient(); + final var requestModelResponse = this.requestUsing(httpClient, timeout); + httpClient.close(); // Close http-client after the request. + return requestModelResponse; + } +} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java new file mode 100644 index 0000000..c3293cf --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java @@ -0,0 +1,104 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.release; + +import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import io.github.aivruu.repoviewer.api.download.DownloaderUtils; +import org.jetbrains.annotations.Contract; + +import java.io.File; +import java.util.concurrent.CompletableFuture; + +/** + * This {@link RequestableModel} implementation is used to proportionate access to the + * latest-release information for the requested-repository. + * + * @param version the release version. + * @param assets the list with the url and name of the assets for this release. + * @since 0.0.1 + */ +public record LatestReleaseModel(String version, String[] assets) implements RequestableModel { + @Override + public String urlForRequest() { + throw new UnsupportedOperationException("This function is expected to be implemented in a future version."); + } + + @Override + public String browserUrlForRequest() { + throw new UnsupportedOperationException("This function is expected to be implemented in a future version."); + } + + /** + * Creates a new {@link CompletableFuture} and tries to download the asset located + * at the specified position of the assets-array. + * + * @param directory the directory to where the asset is going to be downloaded. + * @param position the asset's position at the array. + * @return The {@link CompletableFuture} with the boolean-result for this operation. + * @since 2.3.4 + */ + public CompletableFuture downloadFrom(final File directory, final int position) { + return CompletableFuture.supplyAsync(() -> { + if ((position < 0) || position > this.assets.length) { + return false; + } + final var parts = this.assets[position].split("->", 2); + final var readBytesAmount = DownloaderUtils.fromUrlToFile(directory, /* File-name with extension. */ parts[0], + /* URL without extra-spaces. */ parts[1].trim()); + // If something went wrong during the process, or there's nothing to download, + // will return false, otherwise, return true. + return readBytesAmount > 0; + }); + } + + /** + * Creates a new {@link CompletableFuture} and tries to download all the available-assets + * located at the array for this release of the specified repository. + * + * @param directory the directory to where assets is going to be downloaded. + * @return The {@link CompletableFuture} with the boolean-result for this operation. + * @see DownloaderUtils#fromUrlToFile(File, String, String) + * @since 1.0.0 + */ + public CompletableFuture downloadAll(final File directory) { + return CompletableFuture.supplyAsync(() -> { + var downloadedAssetsAmount = 0; + for (final var asset : this.assets) { + final var assetParts = asset.split(":", 2); + final var readBytesAmount = DownloaderUtils.fromUrlToFile(directory, assetParts[0], assetParts[1].trim()); + if (readBytesAmount <= 0) { + continue; + } + // Another asset was downloaded correctly. + downloadedAssetsAmount++; + } + return downloadedAssetsAmount > 0; + }); + } + + /** + * Returns a string-array with every char of the version-string. + * + * @return The semantic string version. + * @since 0.0.1 + */ + @Contract(pure = true) + public String[] semanticVersion() { + // e.g 2.10.1 -> ["2", "10", "1"] + return this.version.split("\\."); + } +} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/repository/GithubRepositoryModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/GithubRepositoryModel.java new file mode 100644 index 0000000..3eb57fa --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/GithubRepositoryModel.java @@ -0,0 +1,46 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.repository; + +import io.github.aivruu.repoviewer.api.RequestConstants; +import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import io.github.aivruu.repoviewer.api.repository.attribute.RepositoryAttributes; + +/** + * This {@link RequestableModel} implementation is used to proportionate access to the + * requested-repository's information. + * + * @param owner the owner of the repository. + * @param name the name of the repository. + * @param description the description of the repository. + * @param license the license's identifier selected for this repository. + * @param attributes a {@link RepositoryAttributes} which contains the most of the attributes + * for this repository. + * @since 0.0.1 + */ +public record GithubRepositoryModel(String owner, String name, String description, String license, + RepositoryAttributes attributes) implements RequestableModel { + @Override + public String urlForRequest() { + return RequestConstants.GITHUB_API_URL.formatted(this.owner, this.name); + } + + @Override + public String browserUrlForRequest() { + return RequestConstants.GITHUB_NORMAL_URL.formatted(this.owner, this.name); + } +} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/repository/RepositoryModelBuilder.java b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/RepositoryModelBuilder.java new file mode 100644 index 0000000..acc83d2 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/RepositoryModelBuilder.java @@ -0,0 +1,65 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.repository; + +import io.github.aivruu.repoviewer.api.repository.attribute.RepositoryAttributes; + +/** + * This class uses the builder-pattern for create new {@link GithubRepositoryModel} instances. + * + * @since 2.3.4 + */ +public class RepositoryModelBuilder { + private String owner; + private String name; + private String description; + private String license; + private RepositoryAttributes attributes; + + public static RepositoryModelBuilder newBuilder() { + return new RepositoryModelBuilder(); + } + + public RepositoryModelBuilder owner(final String repositoryCreator) { + this.owner = repositoryCreator; + return this; + } + + public RepositoryModelBuilder name(final String repositoryName) { + this.name = repositoryName; + return this; + } + + public RepositoryModelBuilder description(final String description) { + this.description = description; + return this; + } + + public RepositoryModelBuilder license(final String license) { + this.license = license; + return this; + } + + public RepositoryModelBuilder attributes(final RepositoryAttributes attributes) { + this.attributes = attributes; + return this; + } + + public GithubRepositoryModel build() { + return new GithubRepositoryModel(this.owner, this.name, this.description, this.license, this.attributes); + } +} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributes.java b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributes.java new file mode 100644 index 0000000..317e326 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributes.java @@ -0,0 +1,23 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.repository.attribute; + +import org.jetbrains.annotations.Nullable; + +public record RepositoryAttributes( + boolean isForked, @Nullable String parent, boolean canBeForked, int stars, int forks, boolean isPublic, + boolean isArchived, boolean isDisabled, @Nullable String language, @Nullable String[] topics) {} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributesBuilder.java b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributesBuilder.java new file mode 100644 index 0000000..6f8d08a --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/repository/attribute/RepositoryAttributesBuilder.java @@ -0,0 +1,94 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.repository.attribute; + +/** + * This class uses the builder-pattern for create new {@link RepositoryAttributes} instances. + * + * @since 2.3.4 + */ +public class RepositoryAttributesBuilder { + private boolean isForked; + private String parent; + private boolean canBeForked; + private int stars; + private int forks; + private boolean isPublic; + private boolean isArchived; + private boolean isDisabled; + private String language; + private String[] topics; + + public static RepositoryAttributesBuilder newBuilder() { + return new RepositoryAttributesBuilder(); + } + + public RepositoryAttributesBuilder fork(final boolean repositoryIsFork) { + this.isForked = repositoryIsFork; + return this; + } + + public RepositoryAttributesBuilder parent(final String originalRepository) { + this.parent = originalRepository; + return this; + } + + public RepositoryAttributesBuilder canBeForked(final boolean canBeForked) { + this.canBeForked = canBeForked; + return this; + } + + public RepositoryAttributesBuilder stars(final int stars) { + this.stars = stars; + return this; + } + + public RepositoryAttributesBuilder forksAmount(final int forks) { + this.forks = forks; + return this; + } + + public RepositoryAttributesBuilder visible(final boolean isPublic) { + this.isPublic = isPublic; + return this; + } + + public RepositoryAttributesBuilder archived(final boolean isArchived) { + this.isArchived = isArchived; + return this; + } + + public RepositoryAttributesBuilder disabled(final boolean isDisabled) { + this.isDisabled = isDisabled; + return this; + } + + public RepositoryAttributesBuilder language(final String language) { + this.language = language; + return this; + } + + public RepositoryAttributesBuilder topics(final String[] topics) { + this.topics = topics; + return this; + } + + public RepositoryAttributes build() { + return new RepositoryAttributes(this.isForked, this.parent, this.canBeForked, this.stars, this.forks, this.isPublic, + this.isArchived, this.isDisabled, this.language, this.topics); + } +} diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java new file mode 100644 index 0000000..302aa73 --- /dev/null +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java @@ -0,0 +1,66 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; +import io.github.aivruu.repoviewer.api.logger.LoggerUtils; +import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +/** + * This {@link GithubHttpRequestModel} implementation is used to handle http-request for the + * specified repository. + * + * @param repository the requested repository. + * @since 0.0.1 + */ +public record RepositoryHttpRequestModel(@NotNull String repository) implements GithubHttpRequestModel { + @Override + public CompletableFuture<@Nullable GithubRepositoryModel> requestUsing(final HttpClient httpClient, final int timeout) { + final var responseJsonBody = this.response(httpClient, timeout); + return responseJsonBody.whenComplete((response, exception) -> { + if (exception != null) { + LoggerUtils.error("Request for '%s' repository could not be completed.".formatted(this.repository)); + } + }).thenApply(response -> CodecProviderImpl.INSTANCE.from(GithubRepositoryModel.class, response)); + } + + private CompletableFuture response(final HttpClient httpClient, final int timeout) { + return CompletableFuture.supplyAsync(() -> { + try { + final var request = HttpRequest.newBuilder().GET() + .timeout(Duration.ofSeconds(timeout)) + .uri(new URI(this.repository)) + .build(); + final var requestReceivedResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + return requestReceivedResponse.body(); // Returns the json-body provided by the request's response. + } catch (final IOException | InterruptedException | URISyntaxException exception) { + return null; + } + }); + } +} diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java new file mode 100644 index 0000000..c668d6d --- /dev/null +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java @@ -0,0 +1,44 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import io.github.aivruu.repoviewer.api.RequestConstants; +import org.jetbrains.annotations.NotNull; + +/** + * This class is used to create formatted GitHub's API usable URLs. + * + * @since 0.0.1 + */ +public final class RepositoryUrlBuilder { + private RepositoryUrlBuilder() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + /** + * Returns a new {@link String} using the GitHub's API url as base for given parameters + * fusion into that url. + * + * @param user the user to search. + * @param repository the repository of the user. + * @return A URL formatted for HTTPS requests to GitHub API. + * @since 2.3.4 + */ + public static @NotNull String from(final @NotNull String user, final @NotNull String repository) { + return RequestConstants.GITHUB_API_URL.formatted(user, repository); + } +} diff --git a/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java new file mode 100644 index 0000000..8574d75 --- /dev/null +++ b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java @@ -0,0 +1,83 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.codec.type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; +import io.github.aivruu.repoviewer.api.repository.RepositoryModelBuilder; +import io.github.aivruu.repoviewer.api.repository.attribute.RepositoryAttributes; +import io.github.aivruu.repoviewer.api.repository.attribute.RepositoryAttributesBuilder; + +import java.lang.reflect.Type; + +/** + * {@link JsonDeserializer} used for manual deserialization with {@link GithubRepositoryModel}s. + * + * @since 0.0.1 + */ +public enum RepositoryCodec implements JsonDeserializer { + /** Used for rapid-access to the enum's instance. */ + INSTANCE; + + @Override + public GithubRepositoryModel deserialize(final JsonElement jsonElement, final Type type, + final JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + final var jsonObject = jsonElement.getAsJsonObject(); + if (jsonObject.get("message") != null) { // Checks if the latest repository exists. + return null; + } + final var repositoryOwner = jsonObject.get("owner").getAsJsonObject().get("login").getAsString(); + final var license = jsonObject.get("license").getAsJsonObject().get("key").getAsString(); + return RepositoryModelBuilder.newBuilder() + .owner(repositoryOwner) + .name(jsonObject.get("name").getAsString()) + .description(jsonObject.get("description").getAsString()) + .license(license) + .attributes(this.createAttributesContainer(jsonObject)) + .build(); + } + + private RepositoryAttributes createAttributesContainer(final JsonObject jsonObject) { + final var isForked = jsonObject.get("fork").getAsBoolean(); + // If this repository is a fork of another repository, we need to + // get the name of the owner of the original repository. + final var repositoryParent = isForked + ? jsonObject.get("parent").getAsJsonObject().get("owner").getAsJsonObject().get("login").getAsString() + : null; + final var providedTopicsArray = jsonObject.get("topics").getAsJsonArray(); + final var topicsArray = new String[providedTopicsArray.size()]; + for (int i = 0; i < topicsArray.length; i++) { + topicsArray[i] = providedTopicsArray.get(i).getAsString(); + } + return RepositoryAttributesBuilder.newBuilder() + .canBeForked(jsonObject.get("allow_forking").getAsBoolean()) + .stars(jsonObject.get("stargazers_count").getAsInt()) + .forksAmount(jsonObject.get("forks_count").getAsInt()) + .fork(jsonObject.get("fork").getAsBoolean()) + .parent(repositoryParent) + .visible(jsonObject.get("private").getAsBoolean()) + .archived(jsonObject.get("archived").getAsBoolean()) + .disabled(jsonObject.get("disabled").getAsBoolean()) + .language(jsonObject.get("language").getAsString()) + .topics(topicsArray) + .build(); + } +} diff --git a/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java new file mode 100644 index 0000000..39f6c26 --- /dev/null +++ b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java @@ -0,0 +1,57 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.codec.type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Type; + +/** + * {@link JsonDeserializer} implementation used for manual deserialization with {@link LatestReleaseModel}s. + * + * @since 0.0.1 + */ +public enum RepositoryReleaseCodec implements JsonDeserializer { + /** Used for rapid-access to the enum's instance. */ + INSTANCE; + /** Used to concatenate values from the {@code json} provided the requests responses. */ + public static final StringBuilder BUILDER = new StringBuilder(); + + @Override + public @Nullable LatestReleaseModel deserialize(final JsonElement jsonElement, final Type type, + final JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + final var providedJsonObject = jsonElement.getAsJsonObject(); + if (providedJsonObject.get("message") != null) { // Checks if the latest-release exists. + return null; + } + final var providedAssets = providedJsonObject.getAsJsonArray("assets").asList(); + final var assetsArray = new String[providedAssets.size()]; + for (int i = 0; i < assetsArray.length; i++) { + final var assetJsonObject = providedAssets.get(i).getAsJsonObject(); + assetsArray[i] = BUILDER.append(assetJsonObject.get("name").getAsString()) + .append("->") + .append(assetJsonObject.get("browser_download_url").getAsString()) + .toString(); + } + return new LatestReleaseModel(providedJsonObject.get("tag_name").getAsString(), assetsArray); + } +} From 7f5e76cbf113276abc1af4485a980b3a73b4bb0f Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 22:00:57 -0400 Subject: [PATCH 09/39] feat: Create new codecs, models and builders for Repository and Release models --- .../ReleaseHttpRequestModel.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java new file mode 100644 index 0000000..4db8536 --- /dev/null +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java @@ -0,0 +1,67 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; +import io.github.aivruu.repoviewer.api.logger.LoggerUtils; +import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +/** + * This {@link GithubHttpRequestModel} implementation is used to handle http-requests for the + * latest-release of the specified repository. + * + * @param repository the specified repository's url. + * @since 0.0.1 + */ +public record ReleaseHttpRequestModel(@NotNull String repository) implements GithubHttpRequestModel { + @Override + public CompletableFuture<@Nullable LatestReleaseModel> requestUsing(final HttpClient httpClient, final int timeout) { + final var responseJsonBody = this.response(httpClient, timeout); + return responseJsonBody.whenComplete((response, exception) -> { + if (exception != null) { + LoggerUtils.error("Request for latest-release of '%s' repository could not be completed." + .formatted(this.repository)); + } + }).thenApply(response -> CodecProviderImpl.INSTANCE.from(LatestReleaseModel.class, response)); + } + + private CompletableFuture response(final HttpClient httpClient, final int timeout) { + return CompletableFuture.supplyAsync(() -> { + try { + final var request = HttpRequest.newBuilder().GET() + .timeout(Duration.ofSeconds(timeout)) + .uri(new URI(this.repository + "/releases/latest")) + .build(); + final var requestReceivedResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + return requestReceivedResponse.body(); // Returns the json-body provided by the request's response. + } catch (final IOException | InterruptedException | URISyntaxException exception) { + return null; + } + }); + } +} From ca0b402031ba474c43dd02f89c4ad20ae72593df Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 22:01:32 -0400 Subject: [PATCH 10/39] chore(LoggerUtils): Suffered package relocation --- .../repoviewer/api}/logger/LoggerUtils.java | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) rename {src/main/java/me/qeklydev/downloader => api/src/main/java/io/github/aivruu/repoviewer/api}/logger/LoggerUtils.java (66%) diff --git a/src/main/java/me/qeklydev/downloader/logger/LoggerUtils.java b/api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java similarity index 66% rename from src/main/java/me/qeklydev/downloader/logger/LoggerUtils.java rename to api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java index 3d3548c..cb63b89 100644 --- a/src/main/java/me/qeklydev/downloader/logger/LoggerUtils.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java @@ -1,21 +1,20 @@ -/* - * This file is part of release-downloader - https://github.com/aivruu/release-downloader - * Copyright (C) 2020-2024 aivruu (https://github.com/aivruu) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package me.qeklydev.downloader.logger; +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.logger; import java.util.logging.Level; import java.util.logging.Logger; From 75c4cb76bebe56c9f698b0ab9392933b859d6710 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 22:02:55 -0400 Subject: [PATCH 11/39] feat(codec): Implement new CodecProvider with implementation for deserialization of repository/release models --- .../repoviewer/api/codec/CodecProvider.java | 39 ++++++++++++ .../CodecProviderImpl.java | 61 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/codec/CodecProvider.java create mode 100644 implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/codec/CodecProvider.java b/api/src/main/java/io/github/aivruu/repoviewer/api/codec/CodecProvider.java new file mode 100644 index 0000000..3bd955e --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/codec/CodecProvider.java @@ -0,0 +1,39 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.codec; + +import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import org.jetbrains.annotations.Nullable; + +/** + * This class is used for the deserialization handling of {@link RequestableModel}s. + * + * @since 2.3.4 + */ +public interface CodecProvider { + /** + * Deserializes the {@code json} given into a specified-type {@link RequestableModel}. + * + * @param modelType the model-type to create since the given json. + * @param json the json to deserialize into a new model. + * @param the model-type specified that must be a {@link RequestableModel} implementation. + * @return The deserialized model, or {@code null} if something went wrong, or there's nothing + * to deserialize. + * @since 2.3.4 + */ + @Nullable Model from(final Class modelType, final String json); +} diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java b/implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java new file mode 100644 index 0000000..6d5d8d9 --- /dev/null +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java @@ -0,0 +1,61 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import io.github.aivruu.repoviewer.api.codec.CodecProvider; +import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; +import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; +import io.github.aivruu.repoviewer.codec.type.RepositoryCodec; +import io.github.aivruu.repoviewer.codec.type.RepositoryReleaseCodec; +import org.jetbrains.annotations.Nullable; + +/** + * {@link CodecProvider} implementation for usage of codecs for {@link LatestReleaseModel}s and + * {@link GithubRepositoryModel}s. + * + * @since 2.3.4 + */ +public enum CodecProviderImpl implements CodecProvider { + /** Used for rapid-access to the implementation. */ + INSTANCE; + + /** + * A {@link Gson} instance with defined-attributes for custom-codecs for {@link LatestReleaseModel} + * and {@link GithubRepositoryModel} types for deserialization. + */ + private static final Gson GSON_PROVIDER = new GsonBuilder() + .registerTypeAdapter(LatestReleaseModel.class, RepositoryReleaseCodec.INSTANCE) + .registerTypeAdapter(GithubRepositoryModel.class, RepositoryCodec.INSTANCE) + .create(); + + /** + * Deserializes the {@code json} given into a specified-type {@link RequestableModel}. + * + * @param modelType the model-type to create since the given json. + * @param json the json to deserialize into a new model. + * @param the model-type specified that must be a {@link RequestableModel} implementation. + * @return The deserialized model, or {@code null} if something went wrong, or there's nothing + * to deserialize. + * @since 2.3.4 + */ + public @Nullable Model from(final Class modelType, final String json) { + return GSON_PROVIDER.fromJson(json, modelType); + } +} From 7ab0d61190f469adf95fb8ad11c944af371691e0 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 22:04:54 -0400 Subject: [PATCH 12/39] feat(DownloaderUtils): Create class for download-operations static-functions for replace old IoAsyncUtils class --- .../api/download/DownloaderUtils.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java b/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java new file mode 100644 index 0000000..374f381 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java @@ -0,0 +1,77 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.download; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.nio.channels.Channels; + +/** + * This class is used to perform download-operations from given URLs. + * + * @since 2.3.4 + */ +public final class DownloaderUtils { + private DownloaderUtils() { + throw new UnsupportedOperationException("This class is used for utility and cannot be instantiated."); + } + + /** + * Uses the URL object provided to download the content of that URL and be written into + * a specified file. + * + * @param fileName the name of the file to be written. + * @param from the url for the download. + * @return The amount of read bytes for this operation, following expected logic for the + * {@link #fromUrlToFile(File, String)} function. + * @see #fromUrlToFile(File, String) + * @since 2.3.4 + */ + public static long fromUrlToFile(final File directory, final String fileName, final String from) { + final var expectedFile = new File(directory, fileName); + return fromUrlToFile(expectedFile, from); + } + + /** + * Downloads the content of the given url to write it into the given {@link File}. + * + * @param file the file where download's content will be written. + * @param from the url from where the content is going to be downloaded. + * @return The amount of read bytes for this operation, could return {@code 0} if nothing + * was downloaded, or {@code -1} if something went wrong during the process. + * @since 2.3.4 + */ + public static long fromUrlToFile(final File file, final String from) { + // Using the provided URL we create a new URI object with this + // same url, this method will throw an exception if given url + // syntax is not valid, and will provide a message for error + // description. + final var uriFromGiven = URI.create(from); + // We convert the URI instance into a URL object, and then we open + // the stream for channel data reading and write bytes to this channel + // at the given position. + try (final var readableByteChannel = Channels.newChannel(uriFromGiven.toURL().openStream()); + final var fileOutputStream = new FileOutputStream(file)) { + return fileOutputStream.getChannel().transferFrom(readableByteChannel, + /* The initial position for bytes transfer. */ 0, Long.MAX_VALUE); + } catch (final IOException exception) { + return -1; + } + } +} From 9c9d5d36e5804dfc7a7e66dfb24d6e9ea0cb1774 Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 22:05:56 -0400 Subject: [PATCH 13/39] feat(RequestableModel): Implement interface model to mark identifiable, and usable models by GithubHttpRequestModel --- .../api/http/request/RequestableModel.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/http/request/RequestableModel.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/request/RequestableModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/request/RequestableModel.java new file mode 100644 index 0000000..27a0ce5 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/request/RequestableModel.java @@ -0,0 +1,42 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.http.request; + +import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; + +/** + * This interface is used to mark models which can be used for {@link GithubHttpRequestModel}s. + * + * @since 2.3.4 + */ +public interface RequestableModel { + /** + * Returns the url where the request is sent. + * + * @return The url for the request. + * @since 2.3.4 + */ + String urlForRequest(); + + /** + * Returns the url (for browsers) where the request is sent. + * + * @return The url for browsers where the request is sent. + * @since 2.3.4 + */ + String browserUrlForRequest(); +} From eea8265e61696963631b89cb952cc9320fb9ca3a Mon Sep 17 00:00:00 2001 From: aivruu Date: Mon, 19 Aug 2024 22:06:25 -0400 Subject: [PATCH 14/39] feat(RequestConstants): Create class to store constant-values used for http-requests --- .../repoviewer/api/RequestConstants.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/RequestConstants.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/RequestConstants.java b/api/src/main/java/io/github/aivruu/repoviewer/api/RequestConstants.java new file mode 100644 index 0000000..ea1f8e1 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/RequestConstants.java @@ -0,0 +1,33 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api; + +/** + * This class is used to store constants used for https-requests to GitHub's API. + * + * @since 2.3.4 + */ +public class RequestConstants { + /** The normal url used for common-users to access to GitHub. */ + public static final String GITHUB_NORMAL_URL = "https://github.com/%s/%s"; + /** The url used for https-requests to GitHub's API. */ + public static final String GITHUB_API_URL = "https://api.github.com/repos/%s/%s"; + + private RequestConstants() { + throw new UnsupportedOperationException("This class is for utility and cannot be instantiated."); + } +} From 3708ed5e7123d3846dde94b88cfcbb88a18269c3 Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:11:27 -0400 Subject: [PATCH 15/39] feat(request-guide): Create guide with instructions for request-model/impls usage --- docs/make-request-guide.md | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/make-request-guide.md diff --git a/docs/make-request-guide.md b/docs/make-request-guide.md new file mode 100644 index 0000000..396e9b0 --- /dev/null +++ b/docs/make-request-guide.md @@ -0,0 +1,50 @@ +# How to make a request to a Repository, or get their Latest Release +For this, we have the [GithubHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java) +interface, which is used as main-model for another implementations responsable of handling the requests made to the specified GitHub repository, +and the latest-release for that repository. You can create your own implementation for make custom-requests to the API. + +This interface provides two methods (one default), `requestUsing(HttpClient, int)` or `request(int)`. The first requires an `HttpClient` object +which will be used for the request handling, and the max-timeout (on seconds) for this request. The second method only require the timeout (on seconds) +for the request. + +This interface have two implementations, [ReleaseHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java) +used for repository's latest-release request, this implementation will return a `LatestReleaseModel`. [RepositoryHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java) +is used for repository requests, and will return a `GithubRepositoryModel` for the made request. + +To use this implementations only we need to give them the url for the repository which we are searching for, for get an usable-url for the request, +we use the [RepositoryUrlBuilder](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java) +class, which provides the method `from(String, String)` that require the repository's creator username, and the repository's name. + +This examples also applies for the [RepositoryHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java), +where the process is the same for repository requests, with the difference that this implementation returns a `GithubRepositoryModel`. + +```java +// ... +final var releaseHttpRequestModel = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); +releaseHttpRequest.request(5) // Timeout will be 5 seconds. + .thenAccept(latestReleaseModel -> { + // The deserialized-model could be null, so we need to check first. + if (latestReleaseModel == null) return; + + System.out.println("Viewing latest-release with version: " + latestReleaseModel.version()); + }); +// ... +``` + +An example using a pre-configured http-client for make the request: +```java +// ... +final var customHttpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) // We define http-protocol 1.1 to use. + .build(); +final var releaseHttpRequestModel = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); +releaseHttpRequest.requestUsing(customHttpClient, 5) // Timeout will be 5 seconds. + .thenAccept(latestReleaseModel -> { + // The deserialized-model could be null, so we need to check first. + if (latestReleaseModel == null) return; + + System.out.println("Viewing latest-release with version: " + latestReleaseModel.version()); + }); +customHttpClient.close(); +// ... +``` From 84fae5942c2c7db467c97a0eb08c9e543157c889 Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:26:18 -0400 Subject: [PATCH 16/39] Create download-assets-guide.md --- docs/download-assets-guide.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 docs/download-assets-guide.md diff --git a/docs/download-assets-guide.md b/docs/download-assets-guide.md new file mode 100644 index 0000000..c438aad --- /dev/null +++ b/docs/download-assets-guide.md @@ -0,0 +1,6 @@ +# How to download assets from the requested repository's latest-release? +A [LatestReleaseModel](https://github.com/aivruu/repo-viewer/blob/recode/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java) have the +possibility of download one, or all the assets adjunted by the developer at the current latest-release, at a pre-defined directory. + +This model provide two methods, `downloadFrom(File, int)` & `downloadAll(File)`. Both functions requires a `File` object which represent the directory +where the asset, or assets will be downloaded. The first method also require the asset's position at the assets-array to be downloaded. From 6928d7b727fa602cb939fc84adaa48381b3b375d Mon Sep 17 00:00:00 2001 From: aivruu Date: Wed, 21 Aug 2024 13:48:53 -0400 Subject: [PATCH 17/39] docs(RepositoryUrlBuilder): Small change on function documentation text --- .../java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java index c668d6d..1f7061c 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java @@ -31,7 +31,7 @@ private RepositoryUrlBuilder() { /** * Returns a new {@link String} using the GitHub's API url as base for given parameters - * fusion into that url. + * merging for that url. * * @param user the user to search. * @param repository the repository of the user. From 662ebabc6eafcf3e5a3f620b8f94ac4581036261 Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:57:16 -0400 Subject: [PATCH 18/39] chore(LatestReleaseModel): Use "->" char instead of ":" on string splitting --- .../aivruu/repoviewer/api/release/LatestReleaseModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java index c3293cf..292f651 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java @@ -78,7 +78,7 @@ public CompletableFuture downloadAll(final File directory) { return CompletableFuture.supplyAsync(() -> { var downloadedAssetsAmount = 0; for (final var asset : this.assets) { - final var assetParts = asset.split(":", 2); + final var assetParts = asset.split("->", 2); final var readBytesAmount = DownloaderUtils.fromUrlToFile(directory, assetParts[0], assetParts[1].trim()); if (readBytesAmount <= 0) { continue; From fb7e83e7f0b2014e423892412823053cb0e2ed4c Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:44:32 -0400 Subject: [PATCH 19/39] chore(download-guide): Finalize assets-download usage guide --- docs/download-assets-guide.md | 47 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/docs/download-assets-guide.md b/docs/download-assets-guide.md index c438aad..39c2576 100644 --- a/docs/download-assets-guide.md +++ b/docs/download-assets-guide.md @@ -1,6 +1,47 @@ # How to download assets from the requested repository's latest-release? -A [LatestReleaseModel](https://github.com/aivruu/repo-viewer/blob/recode/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java) have the -possibility of download one, or all the assets adjunted by the developer at the current latest-release, at a pre-defined directory. +A [LatestReleaseModel](https://github.com/aivruu/repo-viewer/blob/recode/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java) have the possibility of download one, or all the assets adjunted by the developer at the current latest-release, at a pre-defined directory. This model provide two methods, `downloadFrom(File, int)` & `downloadAll(File)`. Both functions requires a `File` object which represent the directory -where the asset, or assets will be downloaded. The first method also require the asset's position at the assets-array to be downloaded. +where the asset, or assets will be downloaded. The first method also require an `int` which is the asset's position at the assets-array to be downloaded. + +This functions internally uses the class [DownloaderUtils](https://github.com/aivruu/repo-viewer/blob/recode/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java) for this processes. To use these functions we invoke them +given their required parameters, and handle them for possible results: + +```java +// ... +requestHttpModel.thenAccept(latestReleaseModel -> { + if (latestReleaseModel == null) return; + + final var downloadedFileDirectory = new File("downloads"); + if (!downloadedFileDirectory.exists()) downloadedFileDirectory.mkdir(); + + latestReleaseModel.downloadFrom(downloadedFileDirectory, 0)} + .whenComplete((downloaded, exception) -> + if (exception != null) System.out.println("An exception has happened during download!"); + ) + .thenAccept(downloaded -> + if (downloaded) System.out.println("File downloaded"); + ); +}); +// ... +``` +An example downloading a single file into an expected directory. +```java +// ... +requestHttpModel.thenAccept(latestReleaseModel -> { + if (latestReleaseModel == null) return; + + final var filesDirectory = new File("downloads"); + if (!filesDirectory.exists()) filesDirectory.mkdir(); + + latestReleaseModel.downloadAll(downloadedFileDirectory)} + .whenComplete((downloaded, exception) -> + if (exception != null) System.out.println("An exception has happened during download!"); + ) + .thenAccept(downloaded -> { + if (downloaded) System.out.println("Files downloaded"); + else System.out.println("No files downloaded."); + }); +}); +// ... +``` From 9399eeac607cd1d09c441642ef9e20c96a264c71 Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 14:18:00 -0400 Subject: [PATCH 20/39] feat(http-model): Implement functions with Consumer's for define logic to execute with deserialized-models, and make a fixed-thread-pool for better use of asynchronous operations --- .../api/http/GithubHttpRequestModel.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java index 7e6ff11..09e272e 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java @@ -21,6 +21,9 @@ import java.net.http.HttpClient; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; /** * This base-model for another request-implementations such as for releases or repositories. @@ -29,6 +32,9 @@ * @since 1.0.0 */ public interface GithubHttpRequestModel { + /** Four fixed-thread-pool used for http-requests operations. */ + ExecutorService EXECUTOR = Executors.newFixedThreadPool(4, r -> new Thread(r, "HttpRequest-Thread")); + /** * Executes the {@code GET} request-type using the specified {@link HttpClient}, and return * the serialized-model if a {@code json-body} was provided, otherwise result will be a {@code null}. @@ -40,6 +46,21 @@ public interface GithubHttpRequestModel { */ CompletableFuture<@Nullable Model> requestUsing(final HttpClient httpClient, final int timeout); + /** + * Executes the {@code GET} request-type using the specified {@link HttpClient}, then, if the + * deserialized-model isn't null, the consumer's logic will be called with the model as argument, then + * the model will be returned only if a {@code json-body} was provided, otherwise will + * return {@code null} and the consumer's logic will not be executed. + * + * @param httpClient the {@link HttpClient} to use for this request. + * @param timeout the time-out for the request. + * @param consumer the logic to execute with the given model, if the request produced a + * response ({@code json-body}). + * @return The {@link CompletableFuture} with a {@code nullable} model. + * @since 2.3.4 + */ + CompletableFuture<@Nullable Model> requestUsingThen(final HttpClient httpClient, final int timeout, Consumer consumer); + /** * Executes the {@code GET} request-type using a default new {@link HttpClient}, once the * request has ended, the early created {@link HttpClient} is closed. @@ -55,4 +76,24 @@ public interface GithubHttpRequestModel { httpClient.close(); // Close http-client after the request. return requestModelResponse; } + + /** + * Realizes an internal execution to {@link #requestUsingThen(HttpClient, int, Consumer)} using the + * given parameters, and a non-configured http-client for the request handling. + * + * @param timeout the time-out for this request. + * @param consumer the logic to execute with the given model, if the request produced a + * response ({@code json-body}). + * @return The {@link CompletableFuture} with a {@code nullable} model. + * @since 2.3.4 + */ + default CompletableFuture<@Nullable Model> requestThen(final int timeout, Consumer consumer) { + final var httpClient = HttpClient.newHttpClient(); + final var requestModelResponse = this.requestUsing(httpClient, timeout); + // Execute the consumer's logic if the model isn't null. + requestModelResponse.thenAccept(model -> { + if (model != null) consumer.accept(model); + }).thenRun(httpClient::close); // Close http-client after the request. + return requestModelResponse; + } } From 33c6b361392242564d353ab4e8d560bd3844bb84 Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 14:19:21 -0400 Subject: [PATCH 21/39] feat(release-model): Include fixed-thread-pool for better use of async-operations, and handling possible exceptions for download functions --- .../repoviewer/api/release/LatestReleaseModel.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java index 292f651..48bd46e 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java @@ -22,6 +22,8 @@ import java.io.File; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * This {@link RequestableModel} implementation is used to proportionate access to the @@ -32,6 +34,9 @@ * @since 0.0.1 */ public record LatestReleaseModel(String version, String[] assets) implements RequestableModel { + private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(3, + r -> new Thread(r, "ReleaseAssetsDownloader-Thread")); + @Override public String urlForRequest() { throw new UnsupportedOperationException("This function is expected to be implemented in a future version."); @@ -62,6 +67,9 @@ public CompletableFuture downloadFrom(final File directory, final int p // If something went wrong during the process, or there's nothing to download, // will return false, otherwise, return true. return readBytesAmount > 0; + }, EXECUTOR).exceptionally(exception -> { + exception.printStackTrace(); + return false; }); } @@ -87,6 +95,9 @@ public CompletableFuture downloadAll(final File directory) { downloadedAssetsAmount++; } return downloadedAssetsAmount > 0; + }, EXECUTOR).exceptionally(exception -> { + exception.printStackTrace(); + return false; }); } From 66c0629dc663a07fab8f6428a0e4db92181c740a Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 14:20:24 -0400 Subject: [PATCH 22/39] feat(http): Implement changes from super-class and improve structure and requests' responses handling --- .../ReleaseHttpRequestModel.java | 23 ++++++++++++++----- .../RepositoryHttpRequestModel.java | 22 +++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java index 4db8536..59ca21e 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java @@ -30,6 +30,7 @@ import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; /** * This {@link GithubHttpRequestModel} implementation is used to handle http-requests for the @@ -42,12 +43,18 @@ public record ReleaseHttpRequestModel(@NotNull String repository) implements Git @Override public CompletableFuture<@Nullable LatestReleaseModel> requestUsing(final HttpClient httpClient, final int timeout) { final var responseJsonBody = this.response(httpClient, timeout); - return responseJsonBody.whenComplete((response, exception) -> { - if (exception != null) { - LoggerUtils.error("Request for latest-release of '%s' repository could not be completed." - .formatted(this.repository)); - } - }).thenApply(response -> CodecProviderImpl.INSTANCE.from(LatestReleaseModel.class, response)); + return responseJsonBody.thenApply(response -> CodecProviderImpl.INSTANCE.from(LatestReleaseModel.class, response)); + } + + @Override + public CompletableFuture<@Nullable LatestReleaseModel> requestUsingThen(final HttpClient httpClient, final int timeout, + final Consumer consumer) { + final var responseJsonBody = this.response(httpClient, timeout); + return responseJsonBody.thenApply(response -> { + final var releaseModel = CodecProviderImpl.INSTANCE.from(LatestReleaseModel.class, response); + if (releaseModel != null) consumer.accept(releaseModel); + return releaseModel; + }); } private CompletableFuture response(final HttpClient httpClient, final int timeout) { @@ -62,6 +69,10 @@ private CompletableFuture response(final HttpClient httpClient, final in } catch (final IOException | InterruptedException | URISyntaxException exception) { return null; } + }, EXECUTOR).exceptionally(exception -> { + LoggerUtils.error("Request for latest-release of '%s' repository could not be completed." + .formatted(this.repository)); + return null; }); } } diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java index 302aa73..f561234 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java @@ -17,7 +17,6 @@ package io.github.aivruu.repoviewer; import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; -import io.github.aivruu.repoviewer.api.logger.LoggerUtils; import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,6 +29,7 @@ import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; /** * This {@link GithubHttpRequestModel} implementation is used to handle http-request for the @@ -42,11 +42,18 @@ public record RepositoryHttpRequestModel(@NotNull String repository) implements @Override public CompletableFuture<@Nullable GithubRepositoryModel> requestUsing(final HttpClient httpClient, final int timeout) { final var responseJsonBody = this.response(httpClient, timeout); - return responseJsonBody.whenComplete((response, exception) -> { - if (exception != null) { - LoggerUtils.error("Request for '%s' repository could not be completed.".formatted(this.repository)); - } - }).thenApply(response -> CodecProviderImpl.INSTANCE.from(GithubRepositoryModel.class, response)); + return responseJsonBody.thenApply(response -> CodecProviderImpl.INSTANCE.from(GithubRepositoryModel.class, response)); + } + + @Override + public CompletableFuture<@Nullable GithubRepositoryModel> requestUsingThen(final HttpClient httpClient, final int timeout, + final Consumer consumer) { + final var responsesJsonBody = this.response(httpClient, timeout); + return responsesJsonBody.thenApply(response -> { + final var repositoryModel = CodecProviderImpl.INSTANCE.from(GithubRepositoryModel.class, response); + if (repositoryModel != null) consumer.accept(repositoryModel); + return repositoryModel; + }); } private CompletableFuture response(final HttpClient httpClient, final int timeout) { @@ -61,6 +68,9 @@ private CompletableFuture response(final HttpClient httpClient, final in } catch (final IOException | InterruptedException | URISyntaxException exception) { return null; } + }, EXECUTOR).exceptionally(exception -> { + exception.printStackTrace(); + return null; }); } } From 03dca441fe384ad0aa53220a0f66134adef5b4bd Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 14:21:02 -0400 Subject: [PATCH 23/39] chore(tests): Adapt them to the functions structures --- .../io/github/aivruu/repoviewer/AssetsDownloadTest.java | 8 +++----- .../io/github/aivruu/repoviewer/ReleaseRequestTest.java | 7 ++++--- .../github/aivruu/repoviewer/RepositoryRequestTest.java | 7 ++----- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java b/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java index 2555727..995b6cf 100644 --- a/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java +++ b/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java @@ -27,13 +27,11 @@ public class AssetsDownloadTest { void downloadTest() { // We create a custom release-model for this test. final var latestReleaseModel = new LatestReleaseModel("1.3.4", - new String[]{ "https://github.com/aivruu/repo-viewer/releases/download/1.3.4/release-downloader-1.3.4.jar" }); + new String[]{ "release-downloader->https://github.com/aivruu/repo-viewer/releases/download/1.3.4/release-downloader-1.3.4.jar" }); final var destinationDirectory = new File("downloads"); if (!destinationDirectory.exists()) destinationDirectory.mkdir(); - latestReleaseModel.downloadAll(destinationDirectory) - .whenComplete((downloaded, exception) -> Assertions.assertNull(exception, "Failed to download assets.")) - .thenAccept(downloaded -> - Assertions.assertTrue(downloaded, "Appears that all, or some assets, where not downloaded.")); + latestReleaseModel.downloadFrom(destinationDirectory, 0).thenAccept(downloaded -> + Assertions.assertTrue(downloaded, "Appears that all, or some assets, where not downloaded.")); } } diff --git a/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java b/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java index b22b4cb..f0c1603 100644 --- a/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java +++ b/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java @@ -24,14 +24,15 @@ public class ReleaseRequestTest { void releaseRequest() { final var releaseHttpRequest = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); System.out.println("Requesting latest-release for repository: " + releaseHttpRequest.repository()); - final var requestResponse = releaseHttpRequest.request(10); + final var requestResponse = releaseHttpRequest.requestThen(10, latestReleaseModel -> { + System.out.println("Requested repository's latest-release %s deserialized correctly: %s" + .formatted(latestReleaseModel.version(), latestReleaseModel.assets()[0])); + }); System.out.println("Made request for repository correctly."); requestResponse.thenAccept(latestReleaseModel -> { if (latestReleaseModel == null) { Assertions.fail("Failed to deserialize json response into a LatestReleaseModel."); } - System.out.println("Requested repository's latest-release %s deserialized correctly: %s" - .formatted(latestReleaseModel.version(), latestReleaseModel.assets()[0])); }); } } diff --git a/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java b/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java index 5c5fa60..1dbff58 100644 --- a/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java +++ b/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java @@ -24,15 +24,12 @@ public class RepositoryRequestTest { void repositoryRequest() { final var repositoryHttpRequest = new RepositoryHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); System.out.print("Requesting repository from: " + repositoryHttpRequest.repository()); - final var requestResponse = repositoryHttpRequest.request(5); - Assertions.assertNotNull(requestResponse, - "Request response wasn't provided, check the given repository url for valid syntax."); - + final var requestResponse = repositoryHttpRequest.requestThen(5, repositoryModel -> + System.out.print("Requested repository %s deserialized correctly".formatted(repositoryModel.name()))); requestResponse.thenAccept(repositoryModel -> { if (repositoryModel == null) { Assertions.fail("Failed to deserialize the json response into a GithubRepositoryModel."); } - System.out.print("Requested repository %s deserialized correctly".formatted(repositoryModel.name())); }); } } From 3a004bc242f1b4cb2e1cbfe25d02cd08023855c4 Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:24:31 -0400 Subject: [PATCH 24/39] chore(request-guide): Implement made changes to GitHubHttpRequestModel and include them into the usage-guide --- docs/make-request-guide.md | 49 +++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/docs/make-request-guide.md b/docs/make-request-guide.md index 396e9b0..9895d14 100644 --- a/docs/make-request-guide.md +++ b/docs/make-request-guide.md @@ -3,9 +3,9 @@ For this, we have the [GithubHttpRequestModel](https://github.com/aivruu/repo-vi interface, which is used as main-model for another implementations responsable of handling the requests made to the specified GitHub repository, and the latest-release for that repository. You can create your own implementation for make custom-requests to the API. -This interface provides two methods (one default), `requestUsing(HttpClient, int)` or `request(int)`. The first requires an `HttpClient` object -which will be used for the request handling, and the max-timeout (on seconds) for this request. The second method only require the timeout (on seconds) -for the request. +This interface provides four methods (two default): `requestUsing(HttpClient, int)`, `request(int)`, `requestUsingThen(HttpClient, int, Consumer)`, `requestThen(int, Consumer)`. The methods that ends with ___Then___ will require a Consumer which their defined-type will correspond to the model +for the request, which can be a `GithubRepositoryModel` or `LatestReleaseModel`, that logic +will be executed only if a response was provided and the json-body was deserialized into respective model, otherwise, if due to some reason a `null-value` was returned, the consumer will be ignored. The functions that have ___Using___ will require a configured http-client that will be used to perform the requests to the specified repository/release. All these functions also will require an `int-type` parameter which is the max-timeout for every made http-request. This interface have two implementations, [ReleaseHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java) used for repository's latest-release request, this implementation will return a `LatestReleaseModel`. [RepositoryHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java) @@ -16,24 +16,18 @@ we use the [RepositoryUrlBuilder](https://github.com/aivruu/repo-viewer/blob/rec class, which provides the method `from(String, String)` that require the repository's creator username, and the repository's name. This examples also applies for the [RepositoryHttpRequestModel](https://github.com/aivruu/repo-viewer/blob/recode/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java), -where the process is the same for repository requests, with the difference that this implementation returns a `GithubRepositoryModel`. +where the process is the same for repository requests, with the difference that this implementation returns a `CompletableFuture`. ```java -// ... final var releaseHttpRequestModel = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); -releaseHttpRequest.request(5) // Timeout will be 5 seconds. - .thenAccept(latestReleaseModel -> { - // The deserialized-model could be null, so we need to check first. - if (latestReleaseModel == null) return; - - System.out.println("Viewing latest-release with version: " + latestReleaseModel.version()); - }); -// ... +releaseHttpRequest.request(/** Timeout will be 5 seconds. */ 5).thenAccept(latestReleaseModel -> { + // The deserialized-model could be null, so we need to check first. + if (latestReleaseModel == null) return; + System.out.println("Viewing latest-release with version: " + latestReleaseModel.version()); +}); ``` - An example using a pre-configured http-client for make the request: ```java -// ... final var customHttpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) // We define http-protocol 1.1 to use. .build(); @@ -42,9 +36,26 @@ releaseHttpRequest.requestUsing(customHttpClient, 5) // Timeout will be 5 second .thenAccept(latestReleaseModel -> { // The deserialized-model could be null, so we need to check first. if (latestReleaseModel == null) return; - System.out.println("Viewing latest-release with version: " + latestReleaseModel.version()); - }); -customHttpClient.close(); -// ... + }) + .thenRun(customHttpClient::close); // This http-client will be closed once the completable-future has been completed. +``` +Examples using a Consumer to define the logic to execute in case that a response was provided and a model was deserialized +with the `json-body` from that response. +```java +final var releaseHttpRequestModel = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); +releaseHttpRequest.requestThen(/** Timeout will be 5 seconds. */ 5, latestReleaseModel -> + System.out.println("Viewing latest-release with version: " + latestReleaseModel.version())); +``` +Example with a configured http-client: +```java +final var customHttpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) // We define http-protocol 1.1 to use. + .build(); +final var releaseHttpRequestModel = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); +releaseHttpRequest.requestUsingThen(customHttpClient, /** Timeout will be 5 seconds. */ 5, latestReleaseModel -> cache.put("release-http-request", latestReleaseModel)) + .thenAccept(latestReleaseModel -> + System.out.println("Repository's latest-release with version: " + latestReleaseModel.version()); + ) + .thenRun(customHttpClient::close); // This http-client will be closed once the completable-future has been completed. ``` From 99a8ad654ba53ea274910f7e4010cb799e62de20 Mon Sep 17 00:00:00 2001 From: Qekly <71404592+aivruu@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:27:24 -0400 Subject: [PATCH 25/39] chore(download-guide): Modify code-examples structure --- docs/download-assets-guide.md | 36 +++++++++++------------------------ 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/docs/download-assets-guide.md b/docs/download-assets-guide.md index 39c2576..f87dadf 100644 --- a/docs/download-assets-guide.md +++ b/docs/download-assets-guide.md @@ -9,39 +9,25 @@ given their required parameters, and handle them for possible results: ```java // ... -requestHttpModel.thenAccept(latestReleaseModel -> { - if (latestReleaseModel == null) return; +if (latestReleaseModel == null) return; +final var downloadedFileDirectory = new File("downloads"); +if (!downloadedFileDirectory.exists()) downloadedFileDirectory.mkdir(); - final var downloadedFileDirectory = new File("downloads"); - if (!downloadedFileDirectory.exists()) downloadedFileDirectory.mkdir(); - - latestReleaseModel.downloadFrom(downloadedFileDirectory, 0)} - .whenComplete((downloaded, exception) -> - if (exception != null) System.out.println("An exception has happened during download!"); - ) - .thenAccept(downloaded -> - if (downloaded) System.out.println("File downloaded"); - ); +latestReleaseModel.downloadFrom(downloadedFileDirectory, 0).thenAccept(downloaded -> { + if (downloaded) System.out.println("File downloaded"); }); // ... ``` An example downloading a single file into an expected directory. ```java // ... -requestHttpModel.thenAccept(latestReleaseModel -> { - if (latestReleaseModel == null) return; - - final var filesDirectory = new File("downloads"); - if (!filesDirectory.exists()) filesDirectory.mkdir(); +if (latestReleaseModel == null) return; +final var filesDirectory = new File("downloads"); +if (!filesDirectory.exists()) filesDirectory.mkdir(); - latestReleaseModel.downloadAll(downloadedFileDirectory)} - .whenComplete((downloaded, exception) -> - if (exception != null) System.out.println("An exception has happened during download!"); - ) - .thenAccept(downloaded -> { - if (downloaded) System.out.println("Files downloaded"); - else System.out.println("No files downloaded."); - }); +latestReleaseModel.downloadAll(downloadedFileDirectory).thenAccept(downloaded -> { + if (downloaded) System.out.println("Files downloaded"); + else System.out.println("No files downloaded."); }); // ... ``` From b7ebbd65f6772486e00f50cb6481e036c95145a6 Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 17:55:57 -0400 Subject: [PATCH 26/39] chore(codec): Modify package for codec-types and codec-provider impl --- .../github/aivruu/repoviewer/codec}/CodecProviderImpl.java | 4 +--- .../aivruu/repoviewer/codec/{type => }/RepositoryCodec.java | 2 +- .../repoviewer/codec/{type => }/RepositoryReleaseCodec.java | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) rename implementation/src/main/java/{io.github.aivruu.repoviewer => io/github/aivruu/repoviewer/codec}/CodecProviderImpl.java (93%) rename implementation/src/main/java/io/github/aivruu/repoviewer/codec/{type => }/RepositoryCodec.java (98%) rename implementation/src/main/java/io/github/aivruu/repoviewer/codec/{type => }/RepositoryReleaseCodec.java (98%) diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/CodecProviderImpl.java similarity index 93% rename from implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java rename to implementation/src/main/java/io/github/aivruu/repoviewer/codec/CodecProviderImpl.java index 6d5d8d9..761ab41 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/CodecProviderImpl.java +++ b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/CodecProviderImpl.java @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -package io.github.aivruu.repoviewer; +package io.github.aivruu.repoviewer.codec; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -22,8 +22,6 @@ import io.github.aivruu.repoviewer.api.http.request.RequestableModel; import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; -import io.github.aivruu.repoviewer.codec.type.RepositoryCodec; -import io.github.aivruu.repoviewer.codec.type.RepositoryReleaseCodec; import org.jetbrains.annotations.Nullable; /** diff --git a/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/RepositoryCodec.java similarity index 98% rename from implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java rename to implementation/src/main/java/io/github/aivruu/repoviewer/codec/RepositoryCodec.java index 8574d75..2dd3196 100644 --- a/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryCodec.java +++ b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/RepositoryCodec.java @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -package io.github.aivruu.repoviewer.codec.type; +package io.github.aivruu.repoviewer.codec; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/RepositoryReleaseCodec.java similarity index 98% rename from implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java rename to implementation/src/main/java/io/github/aivruu/repoviewer/codec/RepositoryReleaseCodec.java index 39f6c26..fb29b7e 100644 --- a/implementation/src/main/java/io/github/aivruu/repoviewer/codec/type/RepositoryReleaseCodec.java +++ b/implementation/src/main/java/io/github/aivruu/repoviewer/codec/RepositoryReleaseCodec.java @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -package io.github.aivruu.repoviewer.codec.type; +package io.github.aivruu.repoviewer.codec; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; From 8f5ffe468cc6c2bfda3d70afe490465f730dee35 Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 17:57:04 -0400 Subject: [PATCH 27/39] chore(http-model): Use requestUsingThen method on requestThen method --- .../repoviewer/api/http/GithubHttpRequestModel.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java index 09e272e..e4a39bb 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java @@ -79,21 +79,20 @@ public interface GithubHttpRequestModel { /** * Realizes an internal execution to {@link #requestUsingThen(HttpClient, int, Consumer)} using the - * given parameters, and a non-configured http-client for the request handling. + * given parameters, and a non-configured http-client for the request handling that will be closed + * once the request has been made. * * @param timeout the time-out for this request. * @param consumer the logic to execute with the given model, if the request produced a * response ({@code json-body}). * @return The {@link CompletableFuture} with a {@code nullable} model. + * @see #requestUsingThen(HttpClient, int, Consumer) * @since 2.3.4 */ default CompletableFuture<@Nullable Model> requestThen(final int timeout, Consumer consumer) { final var httpClient = HttpClient.newHttpClient(); - final var requestModelResponse = this.requestUsing(httpClient, timeout); - // Execute the consumer's logic if the model isn't null. - requestModelResponse.thenAccept(model -> { - if (model != null) consumer.accept(model); - }).thenRun(httpClient::close); // Close http-client after the request. + final var requestModelResponse = this.requestUsingThen(httpClient, timeout, consumer); + httpClient.close(); // Close http-client after the request. return requestModelResponse; } } From b0a05e3e3563b308e387ce95c9fd6fe52babeffd Mon Sep 17 00:00:00 2001 From: aivruu Date: Fri, 23 Aug 2024 17:57:41 -0400 Subject: [PATCH 28/39] chore(http): Import CodecProviderImpl for http-model implementations --- .../io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java | 1 + .../io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java | 1 + 2 files changed, 2 insertions(+) diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java index 59ca21e..6afe87e 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java @@ -19,6 +19,7 @@ import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; import io.github.aivruu.repoviewer.api.logger.LoggerUtils; import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; +import io.github.aivruu.repoviewer.codec.CodecProviderImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java index f561234..71c8016 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java @@ -18,6 +18,7 @@ import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; +import io.github.aivruu.repoviewer.codec.CodecProviderImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; From 0e19f7d64da00c087d7e402ac05f13a4150657c3 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 21:12:13 -0400 Subject: [PATCH 29/39] chore(git): Ignore 'api/downloads' test directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 1e20804..dbeeb4a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ build # Ignore IDE project settings directory. .idea + +# Ignore assets-download test directory. +api/downloads From 31dad29a73bd17458bb6886ef19452ba48ed02a5 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 21:19:18 -0400 Subject: [PATCH 30/39] chore(tests): Adapt tests for the new library structure --- .../aivruu/repoviewer/AssetsDownloadTest.java | 6 +++--- .../aivruu/repoviewer/ReleaseRequestTest.java | 13 ++++--------- .../aivruu/repoviewer/RepositoryRequestTest.java | 14 ++++++-------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java b/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java index 995b6cf..77d6774 100644 --- a/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java +++ b/api/src/test/java/io/github/aivruu/repoviewer/AssetsDownloadTest.java @@ -27,11 +27,11 @@ public class AssetsDownloadTest { void downloadTest() { // We create a custom release-model for this test. final var latestReleaseModel = new LatestReleaseModel("1.3.4", - new String[]{ "release-downloader->https://github.com/aivruu/repo-viewer/releases/download/1.3.4/release-downloader-1.3.4.jar" }); + new String[]{ "release-downloader-1.3.4.jar->https://github.com/aivruu/repo-viewer/releases/download/1.3.4/release-downloader-1.3.4.jar" }); final var destinationDirectory = new File("downloads"); if (!destinationDirectory.exists()) destinationDirectory.mkdir(); - latestReleaseModel.downloadFrom(destinationDirectory, 0).thenAccept(downloaded -> - Assertions.assertTrue(downloaded, "Appears that all, or some assets, where not downloaded.")); + final var fileDownloaded = latestReleaseModel.downloadFrom(destinationDirectory, 0).join(); + Assertions.assertTrue(fileDownloaded.finished(), "The asset couldn't be downloaded."); } } diff --git a/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java b/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java index f0c1603..cb82772 100644 --- a/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java +++ b/implementation/src/test/java/io/github/aivruu/repoviewer/ReleaseRequestTest.java @@ -24,15 +24,10 @@ public class ReleaseRequestTest { void releaseRequest() { final var releaseHttpRequest = new ReleaseHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); System.out.println("Requesting latest-release for repository: " + releaseHttpRequest.repository()); - final var requestResponse = releaseHttpRequest.requestThen(10, latestReleaseModel -> { + final var responseStatusProvider = releaseHttpRequest.requestThen(10, latestReleaseModel -> System.out.println("Requested repository's latest-release %s deserialized correctly: %s" - .formatted(latestReleaseModel.version(), latestReleaseModel.assets()[0])); - }); - System.out.println("Made request for repository correctly."); - requestResponse.thenAccept(latestReleaseModel -> { - if (latestReleaseModel == null) { - Assertions.fail("Failed to deserialize json response into a LatestReleaseModel."); - } - }); + .formatted(latestReleaseModel.version(), latestReleaseModel.assets()[0])) + ).join(); // Wait until future is complete and status-provider is got. + Assertions.assertTrue(responseStatusProvider.valid(), "Failed to get release-model for this repository."); } } diff --git a/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java b/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java index 1dbff58..536c2b9 100644 --- a/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java +++ b/implementation/src/test/java/io/github/aivruu/repoviewer/RepositoryRequestTest.java @@ -23,13 +23,11 @@ public class RepositoryRequestTest { @Test void repositoryRequest() { final var repositoryHttpRequest = new RepositoryHttpRequestModel(RepositoryUrlBuilder.from("aivruu", "repo-viewer")); - System.out.print("Requesting repository from: " + repositoryHttpRequest.repository()); - final var requestResponse = repositoryHttpRequest.requestThen(5, repositoryModel -> - System.out.print("Requested repository %s deserialized correctly".formatted(repositoryModel.name()))); - requestResponse.thenAccept(repositoryModel -> { - if (repositoryModel == null) { - Assertions.fail("Failed to deserialize the json response into a GithubRepositoryModel."); - } - }); + System.out.println("Requesting repository from: " + repositoryHttpRequest.repository()); + final var responseStatusProvider = repositoryHttpRequest.requestThen(10, repositoryModel -> + System.out.println("Requested repository %s deserialized correctly".formatted(repositoryModel.name())) + ).join(); + Assertions.assertTrue(responseStatusProvider.valid(), + "Failed to deserialize the json response into a GithubRepositoryModel."); } } From b2aff237840d37d2f8724b8195b0e99ee8067689 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 21:20:45 -0400 Subject: [PATCH 31/39] feat(status-provider): Implement status-providers for downloading-operations, and http-requests handling --- .../status/DownloadStatusProvider.java | 102 ++++++++++++++ .../http/status/ResponseStatusProvider.java | 129 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/download/status/DownloadStatusProvider.java create mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/http/status/ResponseStatusProvider.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/download/status/DownloadStatusProvider.java b/api/src/main/java/io/github/aivruu/repoviewer/api/download/status/DownloadStatusProvider.java new file mode 100644 index 0000000..c4afa59 --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/download/status/DownloadStatusProvider.java @@ -0,0 +1,102 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.download.status; + +/** + * This record is used as provider for status-code w/ results creation used for + * assets-download operations at {@link io.github.aivruu.repoviewer.api.download.DownloaderUtils}. + * + * @param status the status-code for the instance. + * @param result the read-bytes number to return as result for this instance. + * @since 2.3.4 + */ +public record DownloadStatusProvider(byte status, long result) { + /** The asset was downloaded correctly. */ + public static final byte ASSET_DOWNLOAD_FINISHED = 0; + /** The asset wasn't downloaded because of non-existing. */ + public static final byte UNKNOWN_ASSET_TO_DOWNLOAD = 1; + /** The asset wasn't downloaded due to an error. */ + public static final byte ASSET_DOWNLOAD_ERROR = 2; + /** Cached default-size to return for non-existing assets. */ + public static final long UNEXISTING_ASSET_DEFAULT_SIZE = 0; + /** Cached default-size to return for non-downloaded assets. */ + public static final long INVALID_ASSET_DEFAULT_SIZE = -1; + + /** + * Creates a new {@link DownloadStatusProvider} with the {@link #ASSET_DOWNLOAD_FINISHED} + * status-code. + * + * @param result the read-bytes amount for the downloaded asset. + * @return The {@link DownloadStatusProvider} with the {@link #ASSET_DOWNLOAD_FINISHED} status-code. + * @since 2.3.4 + */ + public static DownloadStatusProvider assetDownloadFinished(final long result) { + return new DownloadStatusProvider(ASSET_DOWNLOAD_FINISHED, result); + } + + /** + * Creates a new {@link DownloadStatusProvider} with the {@link #UNKNOWN_ASSET_TO_DOWNLOAD} + * status-code, and using the default {@link #UNEXISTING_ASSET_DEFAULT_SIZE}. + * + * @return The {@link DownloadStatusProvider} with the {@link #UNEXISTING_ASSET_DEFAULT_SIZE} status-code. + * @since 2.3.4 + */ + public static DownloadStatusProvider unknownAssetToDownload() { + return new DownloadStatusProvider(UNKNOWN_ASSET_TO_DOWNLOAD, UNEXISTING_ASSET_DEFAULT_SIZE); + } + + /** + * Creates a new {@link DownloadStatusProvider} with the {@link #ASSET_DOWNLOAD_ERROR} + * status-code, and using the default {@link #INVALID_ASSET_DEFAULT_SIZE}. + * + * @return The {@link DownloadStatusProvider} with the {@link #ASSET_DOWNLOAD_ERROR} status-code. + * @since 2.3.4 + */ + public static DownloadStatusProvider assetDownloadError() { + return new DownloadStatusProvider(ASSET_DOWNLOAD_ERROR, INVALID_ASSET_DEFAULT_SIZE); + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #ASSET_DOWNLOAD_FINISHED}. + * + * @return Whether this provider's current status-code is {@link #ASSET_DOWNLOAD_FINISHED}. + * @since 2.3.4 + */ + public boolean finished() { + return this.status == ASSET_DOWNLOAD_FINISHED; + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #UNKNOWN_ASSET_TO_DOWNLOAD}. + * + * @return Whether this provider's current status-code is {@link #UNKNOWN_ASSET_TO_DOWNLOAD}. + * @since 2.3.4 + */ + public boolean unknown() { + return this.status == UNKNOWN_ASSET_TO_DOWNLOAD; + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #ASSET_DOWNLOAD_ERROR}. + * + * @return Whether this provider's current status-code is {@link #ASSET_DOWNLOAD_ERROR}. + * @since 2.3.4 + */ + public boolean error() { + return this.status == ASSET_DOWNLOAD_ERROR; + } +} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/status/ResponseStatusProvider.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/status/ResponseStatusProvider.java new file mode 100644 index 0000000..722657f --- /dev/null +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/status/ResponseStatusProvider.java @@ -0,0 +1,129 @@ +// +// Copyright (C) 2024 Aivruu - repo-viewer +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +package io.github.aivruu.repoviewer.api.http.status; + +import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import org.jetbrains.annotations.Nullable; + +/** + * This record is used as provider for status-code w/ results creation used for http-requests' + * responses validation operations at {@link io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel}. + * + * @param status the status-code for the instance. + * @param result the model to return as result for this instance, it could be {@code null}. + * @param an object which implements the {@link RequestableModel} interface. + * @since 2.3.4 + */ +public record ResponseStatusProvider(byte status, @Nullable Model result) { + /** The request's response was valid. */ + public static final byte REQUEST_VALID_RESPONSE = 0; + /** The response indicates a 'bad' status-code. */ + public static final byte REQUEST_BAD_RESPONSE = 1; + /** The response indicates a 'unknown' status-code. */ + public static final byte REQUEST_UNKNOWN_RESPONSE = 2; + /** The response wasn't provided. */ + public static final byte REQUEST_INVALID_RESPONSE = 3; + + /** + * Creates a new instance of {@link ResponseStatusProvider} with the {@link #REQUEST_VALID_RESPONSE} + * status-code. + * + * @param result the model to return as result for the new provider instance. + * @param an object which implements the {@link RequestableModel} interface. + * @return The {@link ResponseStatusProvider} with the {@link #REQUEST_VALID_RESPONSE} status-code. + * @since 2.3.4 + */ + public static ResponseStatusProvider requestValidResponse(final Model result) { + return new ResponseStatusProvider<>(REQUEST_VALID_RESPONSE, result); + } + + /** + * Creates a new instance of {@link ResponseStatusProvider} with the {@link #REQUEST_BAD_RESPONSE} + * status-code, and a {@code null} model-type. + * + * @param an object which implements the {@link RequestableModel} interface. + * @return The {@link ResponseStatusProvider} with the {@link #REQUEST_BAD_RESPONSE} status-code. + * @since 2.3.4 + */ + public static ResponseStatusProvider<@Nullable Model> requestBadResponse() { + return new ResponseStatusProvider<>(REQUEST_BAD_RESPONSE, null); + } + + /** + * Creates a new instance of {@link ResponseStatusProvider} with the {@link #REQUEST_UNKNOWN_RESPONSE} + * status-code, and a {@code null} model-type. + * + * @param an object which implements the {@link RequestableModel} interface. + * @return The {@link ResponseStatusProvider} with the {@link #REQUEST_UNKNOWN_RESPONSE} status-code. + * @since 2.3.4 + */ + public static ResponseStatusProvider<@Nullable Model> requestUnknownResponse() { + return new ResponseStatusProvider<>(REQUEST_UNKNOWN_RESPONSE, null); + } + + /** + * Creates a new instance of {@link ResponseStatusProvider} with the {@link #REQUEST_INVALID_RESPONSE} + * status-code, and a {@code null} model-type. + * + * @param an object which implements the {@link RequestableModel} interface. + * @return The {@link ResponseStatusProvider} with the {@link #REQUEST_INVALID_RESPONSE} status-code. + * @since 2.3.4 + */ + public static ResponseStatusProvider<@Nullable Model> requestInvalidResponse() { + return new ResponseStatusProvider<>(REQUEST_INVALID_RESPONSE, null); + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #REQUEST_VALID_RESPONSE}. + * + * @return Whether this provider's current status-code is {@link #REQUEST_VALID_RESPONSE}. + * @since 2.3.4 + */ + public boolean valid() { + return this.status == REQUEST_VALID_RESPONSE; + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #REQUEST_BAD_RESPONSE}. + * + * @return Whether this provider's current status-code is {@link #REQUEST_BAD_RESPONSE}. + * @since 2.3.4 + */ + public boolean bad() { + return this.status == REQUEST_BAD_RESPONSE; + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #REQUEST_UNKNOWN_RESPONSE}. + * + * @return Whether this provider's current status-code is {@link #REQUEST_UNKNOWN_RESPONSE}. + * @since 2.3.4 + */ + public boolean unknown() { + return this.status == REQUEST_UNKNOWN_RESPONSE; + } + + /** + * Returns a boolean-state indicating if this provider's current status-code is {@link #REQUEST_INVALID_RESPONSE}. + * + * @return Whether this provider's current status-code is {@link #REQUEST_INVALID_RESPONSE}. + * @since 2.3.4 + */ + public boolean invalid() { + return this.status == REQUEST_INVALID_RESPONSE; + } +} From c63e707a952eb8ec55e9acfcd43c7103e5b172fd Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 21:47:35 -0400 Subject: [PATCH 32/39] chore(logger): Remove logger usage --- .../repoviewer/api/logger/LoggerUtils.java | 82 ------------------- .../repoviewer/api/logger/package-info.java | 6 -- 2 files changed, 88 deletions(-) delete mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java delete mode 100644 api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java b/api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java deleted file mode 100644 index cb63b89..0000000 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/logger/LoggerUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (C) 2024 Aivruu - repo-viewer -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -package io.github.aivruu.repoviewer.api.logger; - -import java.util.logging.Level; -import java.util.logging.Logger; -import org.jetbrains.annotations.NotNull; - -/** - * This class is used to function as a fast-utility - * for logs sending. - * - * @since 0.1.2 - */ -public final class LoggerUtils { - /** - * A Logger instance with the identifier "release-downloader" - * that will be used for lgs sending. - * - * @since 0.1.2 - */ - private static final Logger LOGGER = Logger.getLogger("release-downloader"); - - private LoggerUtils() { - throw new UnsupportedOperationException("This class is for utility and cannot be instantiated."); - } - - /** - * Sends a log message with a custom level type. - * - * @param levelType the log {@link Level} designed. - * @param message the log message. - * @since 0.1.2 - */ - public static void of(final @NotNull Level levelType, final @NotNull String message) { - LOGGER.log(levelType, message); - } - - /** - * Sends a log message and marks it as INFO. - * - * @param message the log message. - * @since 0.1.2 - */ - public static void info(final @NotNull String message) { - LOGGER.log(Level.INFO, message); - } - - /** - * Sends a log message and marks it as WARNING. - * - * @param message the log message. - * @since 0.1.2 - */ - public static void warn(final @NotNull String message) { - LOGGER.log(Level.WARNING, message); - } - - /** - * Sends a log message and marks it as ERROR. - * - * @param message the log message. - * @since 0.1.2 - */ - public static void error(final @NotNull String message) { - LOGGER.log(Level.SEVERE, message); - } -} diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java deleted file mode 100644 index b0abd82..0000000 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/logger/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Provides utility for in-runtime logs. - * - * @since 0.1.2 - */ -package io.github.aivruu.repoviewer.api.logger; \ No newline at end of file From bd4cfd830d5fc98075709f29f6228b5e28703cb3 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 21:51:25 -0400 Subject: [PATCH 33/39] chore(DownloaderUtils): Use DownloadStatusProvider for assets-download functions --- .../api/download/DownloaderUtils.java | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java b/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java index 374f381..d34a0d8 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/download/DownloaderUtils.java @@ -16,6 +16,8 @@ // package io.github.aivruu.repoviewer.api.download; +import io.github.aivruu.repoviewer.api.download.status.DownloadStatusProvider; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -38,14 +40,14 @@ private DownloaderUtils() { * * @param fileName the name of the file to be written. * @param from the url for the download. - * @return The amount of read bytes for this operation, following expected logic for the - * {@link #fromUrlToFile(File, String)} function. + * @return A {@link DownloadStatusProvider} with the {@code status-code} and the read {@code bytes-amount} + * for the operation. * @see #fromUrlToFile(File, String) * @since 2.3.4 */ - public static long fromUrlToFile(final File directory, final String fileName, final String from) { - final var expectedFile = new File(directory, fileName); - return fromUrlToFile(expectedFile, from); + public static DownloadStatusProvider fromUrlToFileWithDirectory(final File directory, final String fileName, + final String from) { + return fromUrlToFile(new File(directory, fileName), from); } /** @@ -53,11 +55,25 @@ public static long fromUrlToFile(final File directory, final String fileName, fi * * @param file the file where download's content will be written. * @param from the url from where the content is going to be downloaded. - * @return The amount of read bytes for this operation, could return {@code 0} if nothing - * was downloaded, or {@code -1} if something went wrong during the process. + * @return A {@link DownloadStatusProvider} with the {@code status-code} and the read {@code bytes-amount} + * for the operation ending. + *

+ * - {@link DownloadStatusProvider#assetDownloadFinished(long)} if the download was successful, will + * return it with the read-bytes amount. + *

+ * - {@link DownloadStatusProvider#unknownAssetToDownload()} if any asset was downloaded, nothing to + * download. + *

+ * - {@link DownloadStatusProvider#assetDownloadError()} if an error occurred during the downloading + * process. + * @see DownloadStatusProvider#ASSET_DOWNLOAD_FINISHED + * @see DownloadStatusProvider#UNKNOWN_ASSET_TO_DOWNLOAD + * @see DownloadStatusProvider#UNEXISTING_ASSET_DEFAULT_SIZE + * @see DownloadStatusProvider#ASSET_DOWNLOAD_ERROR + * @see DownloadStatusProvider#INVALID_ASSET_DEFAULT_SIZE * @since 2.3.4 */ - public static long fromUrlToFile(final File file, final String from) { + public static DownloadStatusProvider fromUrlToFile(final File file, final String from) { // Using the provided URL we create a new URI object with this // same url, this method will throw an exception if given url // syntax is not valid, and will provide a message for error @@ -68,10 +84,13 @@ public static long fromUrlToFile(final File file, final String from) { // at the given position. try (final var readableByteChannel = Channels.newChannel(uriFromGiven.toURL().openStream()); final var fileOutputStream = new FileOutputStream(file)) { - return fileOutputStream.getChannel().transferFrom(readableByteChannel, + final var transferBytesReat = fileOutputStream.getChannel().transferFrom(readableByteChannel, /* The initial position for bytes transfer. */ 0, Long.MAX_VALUE); + return (transferBytesReat == 0) + ? DownloadStatusProvider.unknownAssetToDownload() + : DownloadStatusProvider.assetDownloadFinished(transferBytesReat); } catch (final IOException exception) { - return -1; + return DownloadStatusProvider.assetDownloadError(); } } } From 7c62b4c75302ce6b55d75eb2260fffe0be261a74 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 21:56:48 -0400 Subject: [PATCH 34/39] chore(http-model): Include ResponseStatusProvider usage and implement static-function for responses' disponibility and their status-codes verification --- .../api/http/GithubHttpRequestModel.java | 83 ++++++++++++++++--- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java index e4a39bb..06892c3 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java @@ -16,10 +16,13 @@ // package io.github.aivruu.repoviewer.api.http; +roivimport io.github.aivruu.repoviewer.api.codec.CodecProvider; import io.github.aivruu.repoviewer.api.http.request.RequestableModel; +import io.github.aivruu.repoviewer.api.http.status.ResponseStatusProvider; import org.jetbrains.annotations.Nullable; import java.net.http.HttpClient; +import java.net.http.HttpResponse; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -41,10 +44,19 @@ public interface GithubHttpRequestModel { * * @param httpClient the {@link HttpClient} to use for this request. * @param timeout the time-out for the request. - * @return The {@link CompletableFuture} with a {@code nullable} value. + * @return The {@link CompletableFuture} with a {@link ResponseStatusProvider}, this provider will have + * a possible {@code null} model. + *

+ * - {@link ResponseStatusProvider#requestValidResponse(RequestableModel)} if the response is valid. + *

+ * - {@link ResponseStatusProvider#requestBadResponse()} if the response's status-code is '400' (bad). + *

+ * - {@link ResponseStatusProvider#requestUnknownResponse()} if the response's status-code is '404' (not found). + *

+ * - {@link ResponseStatusProvider#requestInvalidResponse()} if the response wasn't provided. * @since 2.3.4 */ - CompletableFuture<@Nullable Model> requestUsing(final HttpClient httpClient, final int timeout); + CompletableFuture> requestUsing(final HttpClient httpClient, final int timeout); /** * Executes the {@code GET} request-type using the specified {@link HttpClient}, then, if the @@ -56,25 +68,36 @@ public interface GithubHttpRequestModel { * @param timeout the time-out for the request. * @param consumer the logic to execute with the given model, if the request produced a * response ({@code json-body}). - * @return The {@link CompletableFuture} with a {@code nullable} model. + * @return The {@link CompletableFuture} with a {@link ResponseStatusProvider}, this provider will have + * a possible {@code null} model. + *

+ * - {@link ResponseStatusProvider#requestValidResponse(RequestableModel)} if the response is valid. + *

+ * - {@link ResponseStatusProvider#requestBadResponse()} if the response's status-code is '400' (bad). + *

+ * - {@link ResponseStatusProvider#requestUnknownResponse()} if the response's status-code is '404' (not found). + *

+ * - {@link ResponseStatusProvider#requestInvalidResponse()} if the response wasn't provided. * @since 2.3.4 */ - CompletableFuture<@Nullable Model> requestUsingThen(final HttpClient httpClient, final int timeout, Consumer consumer); + CompletableFuture> requestUsingThen(final HttpClient httpClient, final int timeout, + final Consumer consumer); /** * Executes the {@code GET} request-type using a default new {@link HttpClient}, once the * request has ended, the early created {@link HttpClient} is closed. * * @param timeout the time-out for this request. - * @return The {@link CompletableFuture} with a {@code nullable} value. + * @return The {@link CompletableFuture} with a {@link ResponseStatusProvider}, this provider will have + * a possible {@code null} model. * @see #requestUsing(HttpClient, int) * @since 2.3.4 */ - default CompletableFuture<@Nullable Model> request(final int timeout) { + default CompletableFuture> request(final int timeout) { final var httpClient = HttpClient.newHttpClient(); - final var requestModelResponse = this.requestUsing(httpClient, timeout); + final var requestResponseStatusProvider = this.requestUsing(httpClient, timeout); httpClient.close(); // Close http-client after the request. - return requestModelResponse; + return requestResponseStatusProvider; } /** @@ -85,14 +108,50 @@ public interface GithubHttpRequestModel { * @param timeout the time-out for this request. * @param consumer the logic to execute with the given model, if the request produced a * response ({@code json-body}). - * @return The {@link CompletableFuture} with a {@code nullable} model. + * @return The {@link CompletableFuture} with a {@link ResponseStatusProvider}, this provider will have + * a possible {@code null} model. * @see #requestUsingThen(HttpClient, int, Consumer) * @since 2.3.4 */ - default CompletableFuture<@Nullable Model> requestThen(final int timeout, Consumer consumer) { + default CompletableFuture> requestThen(final int timeout, final Consumer consumer) { final var httpClient = HttpClient.newHttpClient(); - final var requestModelResponse = this.requestUsingThen(httpClient, timeout, consumer); + final var requestResponseStatusProvider = this.requestUsingThen(httpClient, timeout, consumer); httpClient.close(); // Close http-client after the request. - return requestModelResponse; + return requestResponseStatusProvider; + } + + /** + * Returns a {@link ResponseStatusProvider} based on the response-disponibility, and their status-code. + * + * @param response the response provided by the http-request. + * @param consumer the consumer for the deserialized-model, {@code null} on functions that doesn't + * execute additional logic with deserialized-models. + * @param codecProvider a {@link CodecProvider} implementation. + * @param modelType the model-type to deserialize if response and status-code are valid. + * @param an object which implements the {@link RequestableModel} interface. + * @return The {@link ResponseStatusProvider} with the deserialized-model, or {@code null}. + * @since 2.3.4 + */ + static ResponseStatusProvider<@Nullable Model> verifyAndProvideResponse( + final @Nullable HttpResponse response, final @Nullable Consumer consumer, final CodecProvider codecProvider, + final Class modelType + ) { + if (response == null) { + return ResponseStatusProvider.requestInvalidResponse(); + } + final var statusCode = response.statusCode(); + if (statusCode == 404) { + return ResponseStatusProvider.requestUnknownResponse(); + } else if (statusCode == 400) { + return ResponseStatusProvider.requestBadResponse(); + } + final var model = codecProvider.from(modelType, response.body()); + if (model == null) { + return ResponseStatusProvider.requestInvalidResponse(); + } + if (consumer != null) { + consumer.accept(model); + } + return ResponseStatusProvider.requestValidResponse(model); } } From ae5cc401cac8b4e457c2d0574810e97857219bf8 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 22:01:43 -0400 Subject: [PATCH 35/39] chore(LatestReleaseModel): Use DownloadStatusProvider for single asset-download function, and return the download-assets amount for multiple assets-download function --- .../api/release/LatestReleaseModel.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java index 48bd46e..8d480b6 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/release/LatestReleaseModel.java @@ -16,6 +16,7 @@ // package io.github.aivruu.repoviewer.api.release; +import io.github.aivruu.repoviewer.api.download.status.DownloadStatusProvider; import io.github.aivruu.repoviewer.api.http.request.RequestableModel; import io.github.aivruu.repoviewer.api.download.DownloaderUtils; import org.jetbrains.annotations.Contract; @@ -34,7 +35,8 @@ * @since 0.0.1 */ public record LatestReleaseModel(String version, String[] assets) implements RequestableModel { - private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(3, + /** Used for download-operations for this release's assets. */ + private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2, r -> new Thread(r, "ReleaseAssetsDownloader-Thread")); @Override @@ -53,23 +55,21 @@ public String browserUrlForRequest() { * * @param directory the directory to where the asset is going to be downloaded. * @param position the asset's position at the array. - * @return The {@link CompletableFuture} with the boolean-result for this operation. + * @return The {@link CompletableFuture} with the {@link DownloadStatusProvider}. + * @see DownloaderUtils#fromUrlToFileWithDirectory(File, String, String) * @since 2.3.4 */ - public CompletableFuture downloadFrom(final File directory, final int position) { + public CompletableFuture downloadFrom(final File directory, final int position) { return CompletableFuture.supplyAsync(() -> { - if ((position < 0) || position > this.assets.length) { - return false; + if (position < 0 || position > this.assets.length) { + return DownloadStatusProvider.unknownAssetToDownload(); } final var parts = this.assets[position].split("->", 2); - final var readBytesAmount = DownloaderUtils.fromUrlToFile(directory, /* File-name with extension. */ parts[0], - /* URL without extra-spaces. */ parts[1].trim()); - // If something went wrong during the process, or there's nothing to download, - // will return false, otherwise, return true. - return readBytesAmount > 0; + return DownloaderUtils.fromUrlToFileWithDirectory(directory, + /* File-name with extension. */ parts[0], /* URL without extra-spaces. */ parts[1].trim()); }, EXECUTOR).exceptionally(exception -> { exception.printStackTrace(); - return false; + return DownloadStatusProvider.assetDownloadError(); }); } @@ -78,26 +78,26 @@ public CompletableFuture downloadFrom(final File directory, final int p * located at the array for this release of the specified repository. * * @param directory the directory to where assets is going to be downloaded. - * @return The {@link CompletableFuture} with the boolean-result for this operation. - * @see DownloaderUtils#fromUrlToFile(File, String, String) - * @since 1.0.0 + * @return The {@link CompletableFuture} with a boolean-state for this operation. + * @see DownloaderUtils#fromUrlToFileWithDirectory(File, String, String) + * @since 2.3.4 */ - public CompletableFuture downloadAll(final File directory) { + public CompletableFuture downloadAll(final File directory) { return CompletableFuture.supplyAsync(() -> { var downloadedAssetsAmount = 0; for (final var asset : this.assets) { final var assetParts = asset.split("->", 2); - final var readBytesAmount = DownloaderUtils.fromUrlToFile(directory, assetParts[0], assetParts[1].trim()); - if (readBytesAmount <= 0) { + final var downloadStatusProvider = DownloaderUtils.fromUrlToFileWithDirectory(directory, assetParts[0], assetParts[1].trim()); + if (!downloadStatusProvider.finished()) { continue; } - // Another asset was downloaded correctly. + // Current iterated asset was downloaded correctly. downloadedAssetsAmount++; } - return downloadedAssetsAmount > 0; + return downloadedAssetsAmount; }, EXECUTOR).exceptionally(exception -> { exception.printStackTrace(); - return false; + return -1; }); } From 1c2818a99cf16eb5c0e740bfd3942c4aefcc1488 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 22:03:02 -0400 Subject: [PATCH 36/39] chore(docs): Modify documentation for packages --- .../io/github/aivruu/repoviewer/api/download/package-info.java | 3 +-- .../io/github/aivruu/repoviewer/api/http/package-info.java | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java index b522682..6a9b52b 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/download/package-info.java @@ -1,6 +1,5 @@ /** - * Provides utilities for I/O async writing for - * file download process. + * Provides utilities and status-code providers for I/O operations. * * @since 0.0.1 */ diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java index bbdb393..305d818 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/package-info.java @@ -1,5 +1,6 @@ /** - * Provides base-interface and model for http-requests to GitHub's API. + * Provides request-interface, model-interface and status-provider for http-requests + * to the GitHub API. * * @since 0.0.1 */ From b82689f0be344f22dfd3413aef5b270b2f344d67 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 22:04:46 -0400 Subject: [PATCH 37/39] chore(RepositoryUrlBuilder): Remove NotNull annotations --- .../java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java index 1f7061c..12f74f9 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryUrlBuilder.java @@ -17,7 +17,6 @@ package io.github.aivruu.repoviewer; import io.github.aivruu.repoviewer.api.RequestConstants; -import org.jetbrains.annotations.NotNull; /** * This class is used to create formatted GitHub's API usable URLs. @@ -38,7 +37,7 @@ private RepositoryUrlBuilder() { * @return A URL formatted for HTTPS requests to GitHub API. * @since 2.3.4 */ - public static @NotNull String from(final @NotNull String user, final @NotNull String repository) { + public static String from(final String user, final String repository) { return RequestConstants.GITHUB_API_URL.formatted(user, repository); } } From 0c618fb5f7ba6d87e9fd9953d0482a2b104708e5 Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 22:10:18 -0400 Subject: [PATCH 38/39] chore(http-impl): Implement changes made on GithubHttpRequestModel interface, and improve structure and code-simplify --- .../ReleaseHttpRequestModel.java | 54 ++++++++----------- .../RepositoryHttpRequestModel.java | 49 ++++++++--------- 2 files changed, 43 insertions(+), 60 deletions(-) diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java index 6afe87e..7a9d553 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/ReleaseHttpRequestModel.java @@ -17,13 +17,11 @@ package io.github.aivruu.repoviewer; import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; -import io.github.aivruu.repoviewer.api.logger.LoggerUtils; +import io.github.aivruu.repoviewer.api.http.status.ResponseStatusProvider; import io.github.aivruu.repoviewer.api.release.LatestReleaseModel; import io.github.aivruu.repoviewer.codec.CodecProviderImpl; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; @@ -40,40 +38,32 @@ * @param repository the specified repository's url. * @since 0.0.1 */ -public record ReleaseHttpRequestModel(@NotNull String repository) implements GithubHttpRequestModel { +public record ReleaseHttpRequestModel(String repository) implements GithubHttpRequestModel { @Override - public CompletableFuture<@Nullable LatestReleaseModel> requestUsing(final HttpClient httpClient, final int timeout) { - final var responseJsonBody = this.response(httpClient, timeout); - return responseJsonBody.thenApply(response -> CodecProviderImpl.INSTANCE.from(LatestReleaseModel.class, response)); + public CompletableFuture> requestUsing(final HttpClient httpClient, final int timeout + ) { + return this.response(httpClient, timeout).thenApply(response -> + GithubHttpRequestModel.verifyAndProvideResponse(response, null, CodecProviderImpl.INSTANCE, LatestReleaseModel.class)); } @Override - public CompletableFuture<@Nullable LatestReleaseModel> requestUsingThen(final HttpClient httpClient, final int timeout, - final Consumer consumer) { - final var responseJsonBody = this.response(httpClient, timeout); - return responseJsonBody.thenApply(response -> { - final var releaseModel = CodecProviderImpl.INSTANCE.from(LatestReleaseModel.class, response); - if (releaseModel != null) consumer.accept(releaseModel); - return releaseModel; - }); + public CompletableFuture> requestUsingThen(final HttpClient httpClient, final int timeout, final Consumer consumer + ) { + return this.response(httpClient, timeout).thenApply(response -> + GithubHttpRequestModel.verifyAndProvideResponse(response, consumer, CodecProviderImpl.INSTANCE, LatestReleaseModel.class)); } - private CompletableFuture response(final HttpClient httpClient, final int timeout) { - return CompletableFuture.supplyAsync(() -> { - try { - final var request = HttpRequest.newBuilder().GET() - .timeout(Duration.ofSeconds(timeout)) - .uri(new URI(this.repository + "/releases/latest")) - .build(); - final var requestReceivedResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return requestReceivedResponse.body(); // Returns the json-body provided by the request's response. - } catch (final IOException | InterruptedException | URISyntaxException exception) { - return null; - } - }, EXECUTOR).exceptionally(exception -> { - LoggerUtils.error("Request for latest-release of '%s' repository could not be completed." - .formatted(this.repository)); - return null; - }); + private CompletableFuture<@Nullable HttpResponse> response(final HttpClient httpClient, final int timeout) { + try { + final var request = HttpRequest.newBuilder().GET() + .timeout(Duration.ofSeconds(timeout)) + .uri(new URI(this.repository + "/releases/latest")) + .build(); + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + } catch (final URISyntaxException exception) { + return CompletableFuture.supplyAsync(() -> null, EXECUTOR); + } } } diff --git a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java index 71c8016..4f0892c 100644 --- a/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java +++ b/implementation/src/main/java/io.github.aivruu.repoviewer/RepositoryHttpRequestModel.java @@ -17,12 +17,12 @@ package io.github.aivruu.repoviewer; import io.github.aivruu.repoviewer.api.http.GithubHttpRequestModel; +import io.github.aivruu.repoviewer.api.http.status.ResponseStatusProvider; import io.github.aivruu.repoviewer.api.repository.GithubRepositoryModel; import io.github.aivruu.repoviewer.codec.CodecProviderImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpClient; @@ -41,37 +41,30 @@ */ public record RepositoryHttpRequestModel(@NotNull String repository) implements GithubHttpRequestModel { @Override - public CompletableFuture<@Nullable GithubRepositoryModel> requestUsing(final HttpClient httpClient, final int timeout) { - final var responseJsonBody = this.response(httpClient, timeout); - return responseJsonBody.thenApply(response -> CodecProviderImpl.INSTANCE.from(GithubRepositoryModel.class, response)); + public CompletableFuture> requestUsing(final HttpClient httpClient, final int timeout + ) { + return this.response(httpClient, timeout).thenApply(response -> + GithubHttpRequestModel.verifyAndProvideResponse(response, null, CodecProviderImpl.INSTANCE, GithubRepositoryModel.class)); } @Override - public CompletableFuture<@Nullable GithubRepositoryModel> requestUsingThen(final HttpClient httpClient, final int timeout, - final Consumer consumer) { - final var responsesJsonBody = this.response(httpClient, timeout); - return responsesJsonBody.thenApply(response -> { - final var repositoryModel = CodecProviderImpl.INSTANCE.from(GithubRepositoryModel.class, response); - if (repositoryModel != null) consumer.accept(repositoryModel); - return repositoryModel; - }); + public CompletableFuture> requestUsingThen(final HttpClient httpClient, final int timeout, final Consumer consumer + ) { + return this.response(httpClient, timeout).thenApply(response -> + GithubHttpRequestModel.verifyAndProvideResponse(response, consumer, CodecProviderImpl.INSTANCE, GithubRepositoryModel.class)); } - private CompletableFuture response(final HttpClient httpClient, final int timeout) { - return CompletableFuture.supplyAsync(() -> { - try { - final var request = HttpRequest.newBuilder().GET() - .timeout(Duration.ofSeconds(timeout)) - .uri(new URI(this.repository)) - .build(); - final var requestReceivedResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return requestReceivedResponse.body(); // Returns the json-body provided by the request's response. - } catch (final IOException | InterruptedException | URISyntaxException exception) { - return null; - } - }, EXECUTOR).exceptionally(exception -> { - exception.printStackTrace(); - return null; - }); + private CompletableFuture<@Nullable HttpResponse> response(final HttpClient httpClient, final int timeout) { + try { + final var request = HttpRequest.newBuilder().GET() + .timeout(Duration.ofSeconds(timeout)) + .uri(new URI(this.repository)) + .build(); + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + } catch (final URISyntaxException exception) { + return CompletableFuture.supplyAsync(() -> null, EXECUTOR); + } } } From 1f344ee1fa2efe660ad2fd12f32bfbeb332138bf Mon Sep 17 00:00:00 2001 From: aivruu Date: Sat, 24 Aug 2024 22:21:21 -0400 Subject: [PATCH 39/39] fix(http-model): Invalid import for CodecProvider --- .../aivruu/repoviewer/api/http/GithubHttpRequestModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java index 06892c3..30ff6dc 100644 --- a/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java +++ b/api/src/main/java/io/github/aivruu/repoviewer/api/http/GithubHttpRequestModel.java @@ -16,7 +16,7 @@ // package io.github.aivruu.repoviewer.api.http; -roivimport io.github.aivruu.repoviewer.api.codec.CodecProvider; +import io.github.aivruu.repoviewer.api.codec.CodecProvider; import io.github.aivruu.repoviewer.api.http.request.RequestableModel; import io.github.aivruu.repoviewer.api.http.status.ResponseStatusProvider; import org.jetbrains.annotations.Nullable;