commit 960ffd222ecd4ae768aa86fd6c1b39c09d8f9469 Author: Aria Moradi Date: Wed Dec 23 17:52:05 2020 +0330 initial commit, eu.kanade.tachiyomi.srource packge build works diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..00a51aff --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0e13346b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Ignore Gradle project-specific cache directory +.gradle +.idea + +# Ignore Gradle build output directory +build diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..edfb28c6 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("org.jetbrains.kotlin.jvm") version "1.4.21" + application +} + +repositories { +// gradlePluginPortal() +// google() + mavenCentral() + jcenter() +} + +dependencies { +// implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + // Source models and interfaces from Tachiyomi 1.x + // using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi +// implementation("tachiyomi.sourceapi:source-api:1.1") + +// implementation("com.github.inorichi.injekt:injekt-core:65b0440") + + val okhttp_version = "4.10.0-RC1" + implementation( "com.squareup.okhttp3:okhttp:$okhttp_version") + implementation( "com.squareup.okhttp3:logging-interceptor:$okhttp_version") + implementation( "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version") + implementation ("com.squareup.okio:okio:2.9.0") + + implementation("io.reactivex:rxjava:1.3.8") +// implementation 'io.reactivex:rxandroid:1.2.1' +// implementation ("com.jakewharton.rxrelay:rxrelay:1.2.0") +// implementation ("com.github.pwittchen:reactivenetwork:0.13.0") + + implementation("org.jsoup:jsoup:1.13.1") + implementation("com.google.code.gson:gson:2.8.6") + implementation("com.github.salomonbrys.kotson:kotson:2.5.0") + implementation("com.squareup.duktape:duktape-android:1.3.0") + + + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") +} + +application { + mainClass.set("ir.armor.tachidesk.Main") +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt new file mode 100644 index 00000000..a91e237a --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi.network + +//import android.content.Context +//import eu.kanade.tachiyomi.BuildConfig +//import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import okhttp3.Cache +//import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +//import okhttp3.dnsoverhttps.DnsOverHttps +//import okhttp3.logging.HttpLoggingInterceptor +//import uy.kohesive.injekt.injectLazy +import java.io.File +import java.net.InetAddress +import java.util.concurrent.TimeUnit + +class NetworkHelper() { + +// private val preferences: PreferencesHelper by injectLazy() + +// private val cacheDir = File(context.cacheDir, "network_cache") + + private val cacheSize = 5L * 1024 * 1024 // 5 MiB + +// val cookieManager = AndroidCookieJar() + + val client by lazy { + val builder = OkHttpClient.Builder() +// .cookieJar(cookieManager) +// .cache(Cache(cacheDir, cacheSize)) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) +// .addInterceptor(UserAgentInterceptor()) + +// if (BuildConfig.DEBUG) { +// val httpLoggingInterceptor = HttpLoggingInterceptor().apply { +// level = HttpLoggingInterceptor.Level.HEADERS +// } +// builder.addInterceptor(httpLoggingInterceptor) +// } + +// if (preferences.enableDoh()) { +// builder.dns( +// DnsOverHttps.Builder().client(builder.build()) +// .url("https://cloudflare-dns.com/dns-query".toHttpUrl()) +// .bootstrapDnsHosts( +// listOf( +// InetAddress.getByName("162.159.36.1"), +// InetAddress.getByName("162.159.46.1"), +// InetAddress.getByName("1.1.1.1"), +// InetAddress.getByName("1.0.0.1"), +// InetAddress.getByName("162.159.132.53"), +// InetAddress.getByName("2606:4700:4700::1111"), +// InetAddress.getByName("2606:4700:4700::1001"), +// InetAddress.getByName("2606:4700:4700::0064"), +// InetAddress.getByName("2606:4700:4700::6400") +// ) +// ) +// .build() +// ) +// } + + builder.build() + } + +// val cloudflareClient by lazy { +// client.newBuilder() +// .addInterceptor(CloudflareInterceptor(context)) +// .build() +// } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt new file mode 100644 index 00000000..8038e5ef --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -0,0 +1,121 @@ +package eu.kanade.tachiyomi.network + +//import kotlinx.coroutines.suspendCancellableCoroutine +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import rx.Producer +import rx.Subscription +import java.io.IOException +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +fun Call.asObservable(): Observable { + return Observable.unsafeCreate { subscriber -> + // Since Call is a one-shot type, clone it for each new subscriber. + val call = clone() + + // Wrap the call in a helper which handles both unsubscription and backpressure. + val requestArbiter = object : AtomicBoolean(), Producer, Subscription { + override fun request(n: Long) { + if (n == 0L || !compareAndSet(false, true)) return + + try { + val response = call.execute() + if (!subscriber.isUnsubscribed) { + subscriber.onNext(response) + subscriber.onCompleted() + } + } catch (error: Exception) { + if (!subscriber.isUnsubscribed) { + subscriber.onError(error) + } + } + } + + override fun unsubscribe() { + call.cancel() + } + + override fun isUnsubscribed(): Boolean { + return call.isCanceled() + } + } + + subscriber.add(requestArbiter) + subscriber.setProducer(requestArbiter) + } +} + +// Based on https://github.com/gildor/kotlin-coroutines-okhttp +//suspend fun Call.await(assertSuccess: Boolean = false): Response { +// return suspendCancellableCoroutine { continuation -> +// enqueue( +// object : Callback { +// override fun onResponse(call: Call, response: Response) { +// if (assertSuccess && !response.isSuccessful) { +// continuation.resumeWithException(Exception("HTTP error ${response.code}")) +// return +// } +// +// continuation.resume(response) +// } +// +// override fun onFailure(call: Call, e: IOException) { +// // Don't bother with resuming the continuation if it is already cancelled. +// if (continuation.isCancelled) return +// continuation.resumeWithException(e) +// } +// } +// ) +// +// continuation.invokeOnCancellation { +// try { +// cancel() +// } catch (ex: Throwable) { +// // Ignore cancel exception +// } +// } +// } +//} + +fun Call.asObservableSuccess(): Observable { + return asObservable().doOnNext { response -> + if (!response.isSuccessful) { + response.close() + throw Exception("HTTP error ${response.code}") + } + } +} + +//fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { +// val progressClient = newBuilder() +// .cache(null) +// .addNetworkInterceptor { chain -> +// val originalResponse = chain.proceed(chain.request()) +// originalResponse.newBuilder() +// .body(ProgressResponseBody(originalResponse.body!!, listener)) +// .build() +// } +// .build() +// +// return progressClient.newCall(request) +//} + +fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { + val progressClient = newBuilder() + .cache(null) +// .addNetworkInterceptor { chain -> +// val originalResponse = chain.proceed(chain.request()) +// originalResponse.newBuilder() +// .body(ProgressResponseBody(originalResponse.body!!, listener)) +// .build() +// } + .build() + + return progressClient.newCall(request) +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt new file mode 100644 index 00000000..2e219895 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt @@ -0,0 +1,5 @@ +package eu.kanade.tachiyomi.network + +interface ProgressListener { + fun update(bytesRead: Long, contentLength: Long, done: Boolean) +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt new file mode 100644 index 00000000..3d3b88b5 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt @@ -0,0 +1,38 @@ +package eu.kanade.tachiyomi.network + +import okhttp3.CacheControl +import okhttp3.FormBody +import okhttp3.Headers +import okhttp3.Request +import okhttp3.RequestBody +import java.util.concurrent.TimeUnit.MINUTES + +private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() +private val DEFAULT_HEADERS = Headers.Builder().build() +private val DEFAULT_BODY: RequestBody = FormBody.Builder().build() + +fun GET( + url: String, + headers: Headers = DEFAULT_HEADERS, + cache: CacheControl = DEFAULT_CACHE_CONTROL +): Request { + return Request.Builder() + .url(url) + .headers(headers) + .cacheControl(cache) + .build() +} + +fun POST( + url: String, + headers: Headers = DEFAULT_HEADERS, + body: RequestBody = DEFAULT_BODY, + cache: CacheControl = DEFAULT_CACHE_CONTROL +): Request { + return Request.Builder() + .url(url) + .post(body) + .headers(headers) + .cacheControl(cache) + .build() +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt new file mode 100644 index 00000000..c78033ea --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -0,0 +1,46 @@ +package eu.kanade.tachiyomi.source + +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import rx.Observable + +interface CatalogueSource : Source { + + /** + * An ISO 639-1 compliant language code (two letters in lower case). + */ + val lang: String + + /** + * Whether the source has support for latest updates. + */ + val supportsLatest: Boolean + + /** + * Returns an observable containing a page with a list of manga. + * + * @param page the page number to retrieve. + */ + fun fetchPopularManga(page: Int): Observable + + /** + * Returns an observable containing a page with a list of manga. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable + + /** + * Returns an observable containing a page with a list of latest manga updates. + * + * @param page the page number to retrieve. + */ + fun fetchLatestUpdates(page: Int): Observable + + /** + * Returns the list of filters for the source. + */ + fun getFilterList(): FilterList +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt new file mode 100644 index 00000000..42a8dec2 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.source + +//import androidx.preference.PreferenceScreen + +interface ConfigurableSource : Source { + +// fun setupPreferenceScreen(screen: PreferenceScreen) +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt new file mode 100644 index 00000000..1f5b7890 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt @@ -0,0 +1,51 @@ +package eu.kanade.tachiyomi.source + +//import android.graphics.drawable.Drawable +//import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import rx.Observable +//import uy.kohesive.injekt.Injekt +//import uy.kohesive.injekt.api.get + +/** + * A basic interface for creating a source. It could be an online source, a local source, etc... + */ +interface Source { + + /** + * Id for the source. Must be unique. + */ + val id: Long + + /** + * Name of the source. + */ + val name: String + + /** + * Returns an observable with the updated details for a manga. + * + * @param manga the manga to update. + */ + fun fetchMangaDetails(manga: SManga): Observable + + /** + * Returns an observable with all the available chapters for a manga. + * + * @param manga the manga to update. + */ + fun fetchChapterList(manga: SManga): Observable> + + /** + * Returns an observable with the list of pages a chapter has. + * + * @param chapter the chapter. + */ + fun fetchPageList(chapter: SChapter): Observable> +} + +//fun Source.icon(): Drawable? = Injekt.get().getAppIconForSource(this) + +//fun Source.getPreferenceKey(): String = "source_$id" diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/SourceFactory.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/SourceFactory.kt new file mode 100644 index 00000000..d326c437 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/SourceFactory.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.source + +/** + * A factory for creating sources at runtime. + */ +interface SourceFactory { + /** + * Create a new copy of the sources + * @return The created sources + */ + fun createSources(): List +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/SourceManager.kt new file mode 100644 index 00000000..158bb08c --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/SourceManager.kt @@ -0,0 +1,78 @@ +package eu.kanade.tachiyomi.source + +//import android.content.Context +//import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource +import rx.Observable + +open class SourceManager() { + + private val sourcesMap = mutableMapOf() + + private val stubSourcesMap = mutableMapOf() + + init { + createInternalSources().forEach { registerSource(it) } + } + + open fun get(sourceKey: Long): Source? { + return sourcesMap[sourceKey] + } + + fun getOrStub(sourceKey: Long): Source { + return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) { + StubSource(sourceKey) + } + } + + fun getOnlineSources() = sourcesMap.values.filterIsInstance() + + fun getCatalogueSources() = sourcesMap.values.filterIsInstance() + + internal fun registerSource(source: Source, overwrite: Boolean = false) { + if (overwrite || !sourcesMap.containsKey(source.id)) { + sourcesMap[source.id] = source + } + if (overwrite || !stubSourcesMap.containsKey(source.id)) { + stubSourcesMap[source.id] = StubSource(source.id) + } + } + + internal fun unregisterSource(source: Source) { + sourcesMap.remove(source.id) + } + + private fun createInternalSources(): List = listOf( +// LocalSource(context) + ) + + private inner class StubSource(override val id: Long) : Source { + + override val name: String + get() = id.toString() + + override fun fetchMangaDetails(manga: SManga): Observable { + return Observable.error(getSourceNotInstalledException()) + } + + override fun fetchChapterList(manga: SManga): Observable> { + return Observable.error(getSourceNotInstalledException()) + } + + override fun fetchPageList(chapter: SChapter): Observable> { + return Observable.error(getSourceNotInstalledException()) + } + + override fun toString(): String { + return name + } + + private fun getSourceNotInstalledException(): Exception { +// return Exception(context.getString(R.string.source_not_installed, id.toString())) + return Exception("source not found") + } + } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt new file mode 100644 index 00000000..f30b2f52 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.source.model + +sealed class Filter(val name: String, var state: T) { + open class Header(name: String) : Filter(name, 0) + open class Separator(name: String = "") : Filter(name, 0) + abstract class Select(name: String, val values: Array, state: Int = 0) : Filter(name, state) + abstract class Text(name: String, state: String = "") : Filter(name, state) + abstract class CheckBox(name: String, state: Boolean = false) : Filter(name, state) + abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter(name, state) { + fun isIgnored() = state == STATE_IGNORE + fun isIncluded() = state == STATE_INCLUDE + fun isExcluded() = state == STATE_EXCLUDE + + companion object { + const val STATE_IGNORE = 0 + const val STATE_INCLUDE = 1 + const val STATE_EXCLUDE = 2 + } + } + + abstract class Group(name: String, state: List) : Filter>(name, state) + + abstract class Sort(name: String, val values: Array, state: Selection? = null) : + Filter(name, state) { + data class Selection(val index: Int, val ascending: Boolean) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Filter<*>) return false + + return name == other.name && state == other.state + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + (state?.hashCode() ?: 0) + return result + } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt new file mode 100644 index 00000000..42b6bc74 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -0,0 +1,6 @@ +package eu.kanade.tachiyomi.source.model + +data class FilterList(val list: List>) : List> by list { + + constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt new file mode 100644 index 00000000..a377c36e --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt @@ -0,0 +1,3 @@ +package eu.kanade.tachiyomi.source.model + +data class MangasPage(val mangas: List, val hasNextPage: Boolean) diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt new file mode 100644 index 00000000..16f099a3 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt @@ -0,0 +1,63 @@ +package eu.kanade.tachiyomi.source.model + +//import android.net.Uri +import eu.kanade.tachiyomi.network.ProgressListener +import rx.subjects.Subject + +open class Page( + val index: Int, + val url: String = "", + var imageUrl: String? = null, +// @Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions +): ProgressListener { + + val number: Int + get() = index + 1 + + @Transient + @Volatile + var status: Int = 0 + set(value) { + field = value + statusSubject?.onNext(value) + statusCallback?.invoke(this) + } + + @Transient + @Volatile + var progress: Int = 0 + set(value) { + field = value + statusCallback?.invoke(this) + } + + @Transient + private var statusSubject: Subject? = null + + @Transient + private var statusCallback: ((Page) -> Unit)? = null + + override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { + progress = if (contentLength > 0) { + (100 * bytesRead / contentLength).toInt() + } else { + -1 + } + } + + fun setStatusSubject(subject: Subject?) { + this.statusSubject = subject + } + + fun setStatusCallback(f: ((Page) -> Unit)?) { + statusCallback = f + } + + companion object { + const val QUEUE = 0 + const val LOAD_PAGE = 1 + const val DOWNLOAD_IMAGE = 2 + const val READY = 3 + const val ERROR = 4 + } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt new file mode 100644 index 00000000..f53bbe8f --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -0,0 +1,30 @@ +package eu.kanade.tachiyomi.source.model + +import java.io.Serializable + +interface SChapter : Serializable { + + var url: String + + var name: String + + var date_upload: Long + + var chapter_number: Float + + var scanlator: String? + + fun copyFrom(other: SChapter) { + name = other.name + url = other.url + date_upload = other.date_upload + chapter_number = other.chapter_number + scanlator = other.scanlator + } + + companion object { + fun create(): SChapter { + return SChapterImpl() + } + } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt new file mode 100644 index 00000000..4d5e43f1 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt @@ -0,0 +1,14 @@ +package eu.kanade.tachiyomi.source.model + +class SChapterImpl : SChapter { + + override lateinit var url: String + + override lateinit var name: String + + override var date_upload: Long = 0 + + override var chapter_number: Float = -1f + + override var scanlator: String? = null +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt new file mode 100644 index 00000000..63911e10 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -0,0 +1,63 @@ +package eu.kanade.tachiyomi.source.model + +import java.io.Serializable + +interface SManga : Serializable { + + var url: String + + var title: String + + var artist: String? + + var author: String? + + var description: String? + + var genre: String? + + var status: Int + + var thumbnail_url: String? + + var initialized: Boolean + + fun copyFrom(other: SManga) { + if (other.author != null) { + author = other.author + } + + if (other.artist != null) { + artist = other.artist + } + + if (other.description != null) { + description = other.description + } + + if (other.genre != null) { + genre = other.genre + } + + if (other.thumbnail_url != null) { + thumbnail_url = other.thumbnail_url + } + + status = other.status + + if (!initialized) { + initialized = other.initialized + } + } + + companion object { + const val UNKNOWN = 0 + const val ONGOING = 1 + const val COMPLETED = 2 + const val LICENSED = 3 + + fun create(): SManga { + return SMangaImpl() + } + } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt new file mode 100644 index 00000000..c944474a --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.source.model + +class SMangaImpl : SManga { + + override lateinit var url: String + + override lateinit var title: String + + override var artist: String? = null + + override var author: String? = null + + override var description: String? = null + + override var genre: String? = null + + override var status: Int = 0 + + override var thumbnail_url: String? = null + + override var initialized: Boolean = false +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt new file mode 100644 index 00000000..a9d4a200 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -0,0 +1,376 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.newCallWithProgress +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +//import uy.kohesive.injekt.injectLazy +import java.net.URI +import java.net.URISyntaxException +import java.security.MessageDigest + +/** + * A simple implementation for sources from a website. + */ +abstract class HttpSource : CatalogueSource { + + /** + * Network service. + */ + protected val network = NetworkHelper() + +// /** +// * Preferences that a source may need. +// */ +// val preferences: SharedPreferences by lazy { +// Injekt.get().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE) +// } + + /** + * Base url of the website without the trailing slash, like: http://mysite.com + */ + abstract val baseUrl: String + + /** + * Version id used to generate the source id. If the site completely changes and urls are + * incompatible, you may increase this value and it'll be considered as a new source. + */ + open val versionId = 1 + + /** + * Id of the source. By default it uses a generated id using the first 16 characters (64 bits) + * of the MD5 of the string: sourcename/language/versionId + * Note the generated id sets the sign bit to 0. + */ + override val id by lazy { + val key = "${name.toLowerCase()}/$lang/$versionId" + val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) + (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE + } + + /** + * Headers used for requests. + */ + val headers: Headers by lazy { headersBuilder().build() } + + /** + * Default network client for doing requests. + */ + open val client: OkHttpClient + get() = network.client + + /** + * Headers builder for requests. Implementations can override this method for custom headers. + */ + protected open fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", DEFAULT_USERAGENT) + } + + /** + * Visible name of the source. + */ + override fun toString() = "$name (${lang.toUpperCase()})" + + /** + * Returns an observable containing a page with a list of manga. Normally it's not needed to + * override this method. + * + * @param page the page number to retrieve. + */ + override fun fetchPopularManga(page: Int): Observable { + return client.newCall(popularMangaRequest(page)) + .asObservableSuccess() + .map { response -> + popularMangaParse(response) + } + } + + /** + * Returns the request for the popular manga given the page. + * + * @param page the page number to retrieve. + */ + protected abstract fun popularMangaRequest(page: Int): Request + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + protected abstract fun popularMangaParse(response: Response): MangasPage + + /** + * Returns an observable containing a page with a list of manga. Normally it's not needed to + * override this method. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + + /** + * Returns the request for the search manga given the page. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + protected abstract fun searchMangaParse(response: Response): MangasPage + + /** + * Returns an observable containing a page with a list of latest manga updates. + * + * @param page the page number to retrieve. + */ + override fun fetchLatestUpdates(page: Int): Observable { + return client.newCall(latestUpdatesRequest(page)) + .asObservableSuccess() + .map { response -> + latestUpdatesParse(response) + } + } + + /** + * Returns the request for latest manga given the page. + * + * @param page the page number to retrieve. + */ + protected abstract fun latestUpdatesRequest(page: Int): Request + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + protected abstract fun latestUpdatesParse(response: Response): MangasPage + + /** + * Returns an observable with the updated details for a manga. Normally it's not needed to + * override this method. + * + * @param manga the manga to be updated. + */ + override fun fetchMangaDetails(manga: SManga): Observable { + return client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess() + .map { response -> + mangaDetailsParse(response).apply { initialized = true } + } + } + + /** + * Returns the request for the details of a manga. Override only if it's needed to change the + * url, send different headers or request method like POST. + * + * @param manga the manga to be updated. + */ + open fun mangaDetailsRequest(manga: SManga): Request { + return GET(baseUrl + manga.url, headers) + } + + /** + * Parses the response from the site and returns the details of a manga. + * + * @param response the response from the site. + */ + protected abstract fun mangaDetailsParse(response: Response): SManga + + /** + * Returns an observable with the updated chapter list for a manga. Normally it's not needed to + * override this method. If a manga is licensed an empty chapter list observable is returned + * + * @param manga the manga to look for chapters. + */ + override fun fetchChapterList(manga: SManga): Observable> { + return if (manga.status != SManga.LICENSED) { + client.newCall(chapterListRequest(manga)) + .asObservableSuccess() + .map { response -> + chapterListParse(response) + } + } else { + Observable.error(Exception("Licensed - No chapters to show")) + } + } + + /** + * Returns the request for updating the chapter list. Override only if it's needed to override + * the url, send different headers or request method like POST. + * + * @param manga the manga to look for chapters. + */ + protected open fun chapterListRequest(manga: SManga): Request { + return GET(baseUrl + manga.url, headers) + } + + /** + * Parses the response from the site and returns a list of chapters. + * + * @param response the response from the site. + */ + protected abstract fun chapterListParse(response: Response): List + + /** + * Returns an observable with the page list for a chapter. + * + * @param chapter the chapter whose page list has to be fetched. + */ + override fun fetchPageList(chapter: SChapter): Observable> { + return client.newCall(pageListRequest(chapter)) + .asObservableSuccess() + .map { response -> + pageListParse(response) + } + } + + /** + * Returns the request for getting the page list. Override only if it's needed to override the + * url, send different headers or request method like POST. + * + * @param chapter the chapter whose page list has to be fetched. + */ + protected open fun pageListRequest(chapter: SChapter): Request { + return GET(baseUrl + chapter.url, headers) + } + + /** + * Parses the response from the site and returns a list of pages. + * + * @param response the response from the site. + */ + protected abstract fun pageListParse(response: Response): List + + /** + * Returns an observable with the page containing the source url of the image. If there's any + * error, it will return null instead of throwing an exception. + * + * @param page the page whose source image has to be fetched. + */ + open fun fetchImageUrl(page: Page): Observable { + return client.newCall(imageUrlRequest(page)) + .asObservableSuccess() + .map { imageUrlParse(it) } + } + + /** + * Returns the request for getting the url to the source image. Override only if it's needed to + * override the url, send different headers or request method like POST. + * + * @param page the chapter whose page list has to be fetched + */ + protected open fun imageUrlRequest(page: Page): Request { + return GET(page.url, headers) + } + + /** + * Parses the response from the site and returns the absolute url to the source image. + * + * @param response the response from the site. + */ + protected abstract fun imageUrlParse(response: Response): String + + /** + * Returns an observable with the response of the source image. + * + * @param page the page whose source image has to be downloaded. + */ + fun fetchImage(page: Page): Observable { + return client.newCallWithProgress(imageRequest(page), page) + .asObservableSuccess() + } + + /** + * Returns the request for getting the source image. Override only if it's needed to override + * the url, send different headers or request method like POST. + * + * @param page the chapter whose page list has to be fetched + */ + protected open fun imageRequest(page: Page): Request { + return GET(page.imageUrl!!, headers) + } + + /** + * Assigns the url of the chapter without the scheme and domain. It saves some redundancy from + * database and the urls could still work after a domain change. + * + * @param url the full url to the chapter. + */ + fun SChapter.setUrlWithoutDomain(url: String) { + this.url = getUrlWithoutDomain(url) + } + + /** + * Assigns the url of the manga without the scheme and domain. It saves some redundancy from + * database and the urls could still work after a domain change. + * + * @param url the full url to the manga. + */ + fun SManga.setUrlWithoutDomain(url: String) { + this.url = getUrlWithoutDomain(url) + } + + /** + * Returns the url of the given string without the scheme and domain. + * + * @param orig the full url. + */ + private fun getUrlWithoutDomain(orig: String): String { + return try { + val uri = URI(orig) + var out = uri.path + if (uri.query != null) { + out += "?" + uri.query + } + if (uri.fragment != null) { + out += "#" + uri.fragment + } + out + } catch (e: URISyntaxException) { + orig + } + } + + /** + * Called before inserting a new chapter into database. Use it if you need to override chapter + * fields, like the title or the chapter number. Do not change anything to [manga]. + * + * @param chapter the chapter to be added. + * @param manga the manga of the chapter. + */ + open fun prepareNewChapter(chapter: SChapter, manga: SManga) { + } + + /** + * Returns the list of filters for the source. + */ + override fun getFilterList() = FilterList() + + companion object { + const val DEFAULT_USERAGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64)" + } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt new file mode 100644 index 00000000..7b3ea4bd --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.model.Page +import rx.Observable + +fun HttpSource.getImageUrl(page: Page): Observable { + page.status = Page.LOAD_PAGE + return fetchImageUrl(page) + .doOnError { page.status = Page.ERROR } + .onErrorReturn { null } + .doOnNext { page.imageUrl = it } + .map { page } +} + +fun HttpSource.fetchAllImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { !it.imageUrl.isNullOrEmpty() } + .mergeWith(fetchRemainingImageUrlsFromPageList(pages)) +} + +fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { it.imageUrl.isNullOrEmpty() } + .concatMap { getImageUrl(it) } +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt new file mode 100644 index 00000000..941a3167 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt @@ -0,0 +1,200 @@ +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +/** + * A simple implementation for sources from a website using Jsoup, an HTML parser. + */ +abstract class ParsedHttpSource : HttpSource() { + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + + val hasNextPage = popularMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + protected abstract fun popularMangaSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [popularMangaSelector]. + */ + protected abstract fun popularMangaFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + protected abstract fun popularMangaNextPageSelector(): String? + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + + val hasNextPage = searchMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + protected abstract fun searchMangaSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [searchMangaSelector]. + */ + protected abstract fun searchMangaFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + protected abstract fun searchMangaNextPageSelector(): String? + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(latestUpdatesSelector()).map { element -> + latestUpdatesFromElement(element) + } + + val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + protected abstract fun latestUpdatesSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [latestUpdatesSelector]. + */ + protected abstract fun latestUpdatesFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + protected abstract fun latestUpdatesNextPageSelector(): String? + + /** + * Parses the response from the site and returns the details of a manga. + * + * @param response the response from the site. + */ + override fun mangaDetailsParse(response: Response): SManga { + return mangaDetailsParse(response.asJsoup()) + } + + /** + * Returns the details of the manga from the given [document]. + * + * @param document the parsed document. + */ + protected abstract fun mangaDetailsParse(document: Document): SManga + + /** + * Parses the response from the site and returns a list of chapters. + * + * @param response the response from the site. + */ + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + return document.select(chapterListSelector()).map { chapterFromElement(it) } + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. + */ + protected abstract fun chapterListSelector(): String + + /** + * Returns a chapter from the given element. + * + * @param element an element obtained from [chapterListSelector]. + */ + protected abstract fun chapterFromElement(element: Element): SChapter + + /** + * Parses the response from the site and returns the page list. + * + * @param response the response from the site. + */ + override fun pageListParse(response: Response): List { + return pageListParse(response.asJsoup()) + } + + /** + * Returns a page list from the given document. + * + * @param document the parsed document. + */ + protected abstract fun pageListParse(document: Document): List + + /** + * Parse the response from the site and returns the absolute url to the source image. + * + * @param response the response from the site. + */ + override fun imageUrlParse(response: Response): String { + return imageUrlParse(response.asJsoup()) + } + + /** + * Returns the absolute url to the source image from the document. + * + * @param document the parsed document. + */ + protected abstract fun imageUrlParse(document: Document): String +} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt new file mode 100644 index 00000000..2dc39e10 --- /dev/null +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.util + +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +fun Element.selectText(css: String, defaultValue: String? = null): String? { + return select(css).first()?.text() ?: defaultValue +} + +fun Element.selectInt(css: String, defaultValue: Int = 0): Int { + return select(css).first()?.text()?.toInt() ?: defaultValue +} + +fun Element.attrOrText(css: String): String { + return if (css != "text") attr(css) else text() +} + +/** + * Returns a Jsoup document for this response. + * @param html the body of the response. Use only if the body was read before calling this method. + */ +fun Response.asJsoup(html: String? = null): Document { + return Jsoup.parse(html ?: body!!.string(), request.url.toString()) +} diff --git a/app/src/main/kotlin/ir/armor/tachidesk/Main.kt b/app/src/main/kotlin/ir/armor/tachidesk/Main.kt new file mode 100644 index 00000000..4b37f750 --- /dev/null +++ b/app/src/main/kotlin/ir/armor/tachidesk/Main.kt @@ -0,0 +1,11 @@ +package ir.armor.tachidesk + +class Main{ + companion object { + @JvmStatic + fun main(args: Array) { + println("hello bitches") + } + } +} + diff --git a/app/src/test/kotlin/ir/armor/tachidesk/AppTest.kt b/app/src/test/kotlin/ir/armor/tachidesk/AppTest.kt new file mode 100644 index 00000000..aa2a59fa --- /dev/null +++ b/app/src/test/kotlin/ir/armor/tachidesk/AppTest.kt @@ -0,0 +1,13 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package ir.armor.tachidesk + +import kotlin.test.Test +import kotlin.test.assertTrue + +class AppTest { + @Test fun testAppHasAGreeting() { + assertTrue(true) + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 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 00000000..1f3fdbc5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..4f906e0c --- /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 00000000..107acd32 --- /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/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..7c81f89b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ +rootProject.name = "Tachidesk" + +include("app") \ No newline at end of file