diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..c3fd157 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.multiplatform) apply false + alias(libs.plugins.kotlinx.serialization) apply false +} +allprojects { + if (version == "unspecified") { + version = "1.0.0-SNAPSHOT" + } + group = "pw.binom.device.torrent" +} +//tasks { +// val clean by register("clean") { +// delete(rootProject.layout.buildDirectory) +// } +//} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..45247e4 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,51 @@ +[versions] +kotlin = "2.1.20" +binom-io = "1.0.0-SNAPSHOT" +kotlinx-serialization = "1.8.1" +kotlinx-coroutines = "1.10.2" +telegram = "1.0.0-SNAPSHOT" +android = "8.9.2" +compose-plugin = "1.8.0" +binom-publish = "0.1.24" + +[libraries] +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } +kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +binom-io-core = { module = "pw.binom.io:core", version.ref = "binom-io" } +binom-io-file = { module = "pw.binom.io:file", version.ref = "binom-io" } +binom-io-date = { module = "pw.binom.io:date", version.ref = "binom-io" } +binom-io-nats = { module = "pw.binom.io:nats", version.ref = "binom-io" } +binom-telegram = { module = "pw.binom:telegramClient", version.ref = "telegram" } +binom-io-strong-webServer = { module = "pw.binom.io:strong-web-server", version.ref = "binom-io" } +binom-io-strong-properties-yaml = { module = "pw.binom.io:strong-properties-yaml", version.ref = "binom-io" } +binom-io-strong-properties-ini = { module = "pw.binom.io:strong-properties-ini", version.ref = "binom-io" } +binom-io-strong-application = { module = "pw.binom.io:strong-application", version.ref = "binom-io" } +binom-io-strong-nats-client = { module = "pw.binom.io:strong-nats-client", version.ref = "binom-io" } +binom-io-signal = { module = "pw.binom.io:signal", version.ref = "binom-io" } +binom-io-logger = { module = "pw.binom.io:logger", version.ref = "binom-io" } +binom-io-s3-client = { module = "pw.binom.io:s3", version.ref = "binom-io" } +binom-io-kmigrator = { module = "pw.binom.io:kmigrator", version.ref = "binom-io" } +binom-io-postgresql = { module = "pw.binom.io:postgresql-async", version.ref = "binom-io" } +binom-io-sqlite = { module = "pw.binom.io:sqlite", version.ref = "binom-io" } +binom-io-db-serialization-core = { module = "pw.binom.io:db-serialization", version.ref = "binom-io" } +binom-io-http-client = { module = "pw.binom.io:httpClient", version.ref = "binom-io" } +binom-io-http-ssl = { module = "pw.binom.io:http-ssl", version.ref = "binom-io" } +binom-io-http-server-rest = { module = "pw.binom.io:http-server-rest", version.ref = "binom-io" } +binom-io-http-server-core = { module = "pw.binom.io:httpServer", version.ref = "binom-io" } +binom-io-network = { module = "pw.binom.io:network", version.ref = "binom-io" } + +[plugins] +johnrengelman-shadow = { id = "com.github.johnrengelman.shadow", version = "5.2.0" } +jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +android-application = { id = "com.android.application", version.ref = "android" } +android-library = { id = "com.android.library", version.ref = "android" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +binom-publish = { id = "pw.binom.publish", version.ref = "binom-publish" } +bmuschko-docker = { id = "com.bmuschko.docker-remote-api", version = "9.4.0"} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c1f93d8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Apr 15 17:57:35 CST 2023 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 0000000..632774e --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,110 @@ +import com.bmuschko.gradle.docker.tasks.AbstractDockerRemoteApiTask +import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage +import com.bmuschko.gradle.docker.tasks.image.DockerPushImage + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.johnrengelman.shadow) + alias(libs.plugins.bmuschko.docker) +} + +kotlin { + jvm() + linuxX64 { + binaries { + executable { + entryPoint = "pw.binom.main" + } + } + } + mingwX64 { + binaries { + executable { + entryPoint = "pw.binom.main" + } + } + } + sourceSets { + commonMain.dependencies { + implementation(libs.binom.io.strong.properties.ini) + implementation(libs.binom.io.strong.properties.yaml) + implementation(libs.binom.io.strong.application) + implementation(libs.binom.io.sqlite) + implementation(libs.binom.io.http.server.core) + implementation(libs.binom.io.http.client) + } + commonTest.dependencies { + api(kotlin("test-common")) + api(kotlin("test-annotations-common")) + } + jvmTest.dependencies { + api(kotlin("test-junit")) + } + } +} +val dockerImage = System.getenv("DOCKER_IMAGE_NAME") +val dockerLogin = System.getenv("DOCKER_REGISTRY_USERNAME") +val dockerPassword = System.getenv("DOCKER_REGISTRY_PASSWORD") +val dockerHost = System.getenv("DOCKER_REGISTRY_HOST") + +docker { + registryCredentials { + url.set(dockerHost) + if (dockerLogin != null) { + username.set(dockerLogin) + } + if (dockerPassword != null) { + password.set(dockerPassword) + } + } +} +val jvmJar by tasks.getting(Jar::class) + +val shadowJar by tasks.register("shadowJar", com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar::class) { + from(jvmJar.archiveFile) + group = "build" + configurations = listOf(project.configurations["jvmRuntimeClasspath"]) + exclude( + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "META-INF/*.txt", + "META-INF/NOTICE", + "LICENSE", + ) + manifest { + attributes("Main-Class" to "pw.binom.JvmMain") + } + archiveFileName.set("full-application.jar") +} + +val buildImage by tasks.register("buildDockerImage", DockerBuildImage::class) { + group = "docker" + dependsOn(shadowJar) + inputDir.set(projectDir) + if (dockerImage != null) { + images.add(dockerImage) + buildArgs.put("--output", "type=oci,name=$dockerImage") + } + applyUrl() + doFirst { + dockerImage ?: throw IllegalArgumentException("DOCKER_IMAGE_NAME is not set") + println("Image name: \"$dockerImage\"") + } +} + +tasks.register("pushDockerImage", DockerPushImage::class) { + group = "docker" + applyUrl() + dependsOn(buildImage) + images.addAll(buildImage.images) +// buildImage.images +} + +fun AbstractDockerRemoteApiTask.applyUrl() { + val host = System.getenv("DOCKER_HOST") + if (host != null) { + url.set(host) + } +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/DataLogger.kt b/server/src/commonMain/kotlin/pw/binom/DataLogger.kt new file mode 100644 index 0000000..e37a11d --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/DataLogger.kt @@ -0,0 +1,15 @@ +package pw.binom + +import pw.binom.io.http.Headers + +interface DataLogger { + suspend fun save( + method: String, + request: String, + incomeHeader: Headers, + incomeData: ByteArray, + responseCode: Int, + outcomeHeader: Headers, + outcomeData: ByteArray, + ) +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/Main.kt b/server/src/commonMain/kotlin/pw/binom/Main.kt new file mode 100644 index 0000000..361f052 --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/Main.kt @@ -0,0 +1,13 @@ +@file:JvmName("JvmMain") + +package pw.binom + +import pw.binom.config.DefaultConfig +import pw.binom.strong.StrongApplication +import kotlin.jvm.JvmName + +fun main(args: Array) { + StrongApplication.run(args) { + +DefaultConfig(properties, networkManager) + } +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/config/DefaultConfig.kt b/server/src/commonMain/kotlin/pw/binom/config/DefaultConfig.kt new file mode 100644 index 0000000..2893738 --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/config/DefaultConfig.kt @@ -0,0 +1,28 @@ +package pw.binom.config + +import pw.binom.http.client.HttpClientRunnable +import pw.binom.http.client.factory.Https11ConnectionFactory +import pw.binom.http.client.factory.NativeNetChannelFactory +import pw.binom.network.NetworkManager +import pw.binom.services.ConsoleDataLogger +import pw.binom.services.HttpServer +import pw.binom.services.SQLiteDataLogger +import pw.binom.services.SpyHandler +import pw.binom.strong.Strong +import pw.binom.strong.bean +import pw.binom.strong.beanAsyncCloseable +import pw.binom.strong.properties.StrongProperties + +fun DefaultConfig(config: StrongProperties, networkManager: NetworkManager) = Strong.config { + it.beanAsyncCloseable { + HttpClientRunnable( + idleCoroutineContext = networkManager, + factory = Https11ConnectionFactory(), + source = NativeNetChannelFactory(networkManager) + ) + } + it.bean { HttpServer() } + it.bean { SpyHandler() } + it.bean { ConsoleDataLogger() } + it.bean { SQLiteDataLogger() } +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/properties/ApplicationProperties.kt b/server/src/commonMain/kotlin/pw/binom/properties/ApplicationProperties.kt new file mode 100644 index 0000000..0f7cd76 --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/properties/ApplicationProperties.kt @@ -0,0 +1,12 @@ +package pw.binom.properties + +import kotlinx.serialization.Serializable +import pw.binom.properties.serialization.annotations.PropertiesPrefix + +@PropertiesPrefix("app") +@Serializable +data class ApplicationProperties( + val port: Int, + val forward: String, + val database: String, +) \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/services/ConsoleDataLogger.kt b/server/src/commonMain/kotlin/pw/binom/services/ConsoleDataLogger.kt new file mode 100644 index 0000000..a8b1f8c --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/services/ConsoleDataLogger.kt @@ -0,0 +1,35 @@ +package pw.binom.services + +import pw.binom.DataLogger +import pw.binom.io.http.Headers +import pw.binom.io.http.forEachHeader +import pw.binom.logger.Logger +import pw.binom.logger.info + +class ConsoleDataLogger : DataLogger { + private val logger by Logger.ofThisOrGlobal + override suspend fun save( + method: String, + request: String, + incomeHeader: Headers, + incomeData: ByteArray, + responseCode: Int, + outcomeHeader: Headers, + outcomeData: ByteArray, + ) { + logger.info("Income: $method $request") + logger.info("Request headers:") + incomeHeader.forEachHeader { key, value -> + logger.info(" $key: $value") + } + logger.info("Request data [${incomeData.size} bytes]:") + logger.info(incomeData.decodeToString()) + logger.info("Response code: $responseCode") + logger.info("Response headers:") + outcomeHeader.forEachHeader { key, value -> + logger.info(" $key: $value") + } + logger.info("Request data [${outcomeData.size} bytes]:") + logger.info(outcomeData.decodeToString()) + } +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/services/HttpServer.kt b/server/src/commonMain/kotlin/pw/binom/services/HttpServer.kt new file mode 100644 index 0000000..0dbc400 --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/services/HttpServer.kt @@ -0,0 +1,37 @@ +package pw.binom.services + +import kotlinx.coroutines.cancelAndJoin +import pw.binom.DEFAULT_BUFFER_SIZE +import pw.binom.io.ByteBufferFactory +import pw.binom.io.httpServer.HttpHandler +import pw.binom.io.httpServer.HttpServer2 +import pw.binom.io.httpServer.ListenJob +import pw.binom.io.socket.InetSocketAddress +import pw.binom.network.NetworkManager +import pw.binom.pool.GenericObjectPool +import pw.binom.properties.ApplicationProperties +import pw.binom.strong.BeanLifeCycle +import pw.binom.strong.inject +import pw.binom.strong.properties.injectProperty + +class HttpServer { + private val httpHandler: HttpHandler by inject() + private val networkManager: NetworkManager by inject() + private val applicationProperties: ApplicationProperties by injectProperty() + private val bufferPool by lazy { GenericObjectPool(ByteBufferFactory(DEFAULT_BUFFER_SIZE)) } + + private var server: HttpServer2? = null + private var listenJob: ListenJob? = null + + init { + BeanLifeCycle.postConstruct { + val server = HttpServer2(handler = httpHandler, dispatcher = networkManager, byteBufferPool = bufferPool) + this.server = server + listenJob = server.listen(InetSocketAddress.resolve("0.0.0.0", applicationProperties.port)) + } + BeanLifeCycle.preDestroy { + listenJob?.cancelAndJoin() + server?.asyncCloseAnyway() + } + } +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/services/SQLiteDataLogger.kt b/server/src/commonMain/kotlin/pw/binom/services/SQLiteDataLogger.kt new file mode 100644 index 0000000..200c9fc --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/services/SQLiteDataLogger.kt @@ -0,0 +1,75 @@ +package pw.binom.services + +import pw.binom.DataLogger +import pw.binom.db.async.AsyncPreparedStatement +import pw.binom.db.sqlite.AsyncConnectionAdapter +import pw.binom.db.sqlite.AsyncSQLiteConnector +import pw.binom.io.file.File +import pw.binom.io.http.Headers +import pw.binom.properties.ApplicationProperties +import pw.binom.strong.BeanLifeCycle +import pw.binom.strong.properties.injectProperty + +class SQLiteDataLogger : DataLogger { + private val applicationProperties: ApplicationProperties by injectProperty() + private var connection: AsyncConnectionAdapter? = null + private var statement: AsyncPreparedStatement? = null + + private fun Headers.makeString() = + asSequence().map { (key, list) -> + list.asSequence().map { "$key: $it" } + }.flatten().joinToString("\n") + + override suspend fun save( + method: String, + request: String, + incomeHeader: Headers, + incomeData: ByteArray, + responseCode: Int, + outcomeHeader: Headers, + outcomeData: ByteArray, + ) { + val statement = statement!! + statement.set(0, method) + statement.set(1, request) + statement.set(2, incomeHeader.makeString()) + statement.set(3, incomeData) + statement.set(4, responseCode) + statement.set(5, outcomeHeader.makeString()) + statement.set(6, outcomeData) + statement.executeUpdate() + } + + init { + BeanLifeCycle.postConstruct { + val connection = AsyncSQLiteConnector.openFile(File(applicationProperties.database)) + this.connection = connection + connection.executeUpdate( + """ + create table if not exists requests( + id integer primary key autoincrement, + method text not null, + request text not null, + income_header text not null, + income_data blob not null, + response_code int not null, + outcome_header text not null, + outcome_data blob not null + ); + """.trimIndent() + ) + statement = connection.prepareStatement( + """ + insert into requests ( + method,request,income_header,income_data, + response_code,outcome_header,outcome_data + ) values(?,?,?,?,?,?,?) + """.trimIndent() + ) + } + BeanLifeCycle.preDestroy { + statement?.asyncCloseAnyway() + connection?.asyncCloseAnyway() + } + } +} \ No newline at end of file diff --git a/server/src/commonMain/kotlin/pw/binom/services/SpyHandler.kt b/server/src/commonMain/kotlin/pw/binom/services/SpyHandler.kt new file mode 100644 index 0000000..fd60075 --- /dev/null +++ b/server/src/commonMain/kotlin/pw/binom/services/SpyHandler.kt @@ -0,0 +1,74 @@ +package pw.binom.services + +import pw.binom.DataLogger +import pw.binom.asyncInput +import pw.binom.copyTo +import pw.binom.http.client.Http11ClientExchange +import pw.binom.http.client.HttpClientRunnable +import pw.binom.io.ByteArrayOutput +import pw.binom.io.http.headersOf +import pw.binom.io.httpServer.HttpHandler +import pw.binom.io.httpServer.HttpServerExchange +import pw.binom.io.useAsync +import pw.binom.logger.Logger +import pw.binom.logger.info +import pw.binom.properties.ApplicationProperties +import pw.binom.strong.inject +import pw.binom.strong.injectServiceList +import pw.binom.strong.properties.injectProperty +import pw.binom.url.toURL + +class SpyHandler : HttpHandler { + private val applicationProperties: ApplicationProperties by injectProperty() + private val client: HttpClientRunnable by inject() + private val loggers by injectServiceList() + override suspend fun handle(exchange: HttpServerExchange) { + val result = "${applicationProperties.forward}${exchange.requestURI}" + + val request = client.request( + method = exchange.requestMethod, + url = result.toURL() + ) + request.headers.clear() + request.headers.addAll(exchange.requestHeaders) + + val income = ByteArrayOutput() + val outcome = ByteArrayOutput() + var responseCode = 0 + var outcomeHeader = headersOf() + var outcomeData = byteArrayOf() + println("exchange.input.available->${exchange.input.available}") + exchange.input.useAsync { input -> + input.copyTo(income) + } + val incomeData = income.toByteArray() + val r = request.connect() as Http11ClientExchange + r.useAsync { response -> + response.getOutput().useAsync { output -> + outcome.writeFully(incomeData) + } + + outcomeHeader = response.getResponseHeaders() + responseCode = response.getResponseCode() + exchange.startResponse(response.getResponseCode(), outcomeHeader) + response.getInput().useAsync { input -> + input.copyTo(outcome) + } + outcomeData = outcome.toByteArray() + exchange.output.useAsync { output -> + output.writeFully(outcomeData) + } + } + loggers.forEach { l -> + l.save( + method = exchange.requestMethod, + request = exchange.requestURI.toString(), + incomeHeader = exchange.requestHeaders, + incomeData = incomeData, + responseCode = responseCode, + outcomeHeader = outcomeHeader, + outcomeData = outcomeData, + ) + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..25e8c82 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + mavenLocal() + maven(url = "https://repo.binom.pw") + mavenCentral() + gradlePluginPortal() + google() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenLocal() + maven(url = "https://repo.binom.pw") + google() + mavenCentral() + } +} +rootProject.name = "HttpSpy" +include(":server")