From 7704c969553cc230e38e1fd17bcb9975d82ca3be Mon Sep 17 00:00:00 2001 From: tom5079 Date: Tue, 1 Sep 2020 18:07:16 +0900 Subject: [PATCH] what i got so far --- app/build.gradle | 10 +- app/proguard-rules.pro | 4 +- .../quaver/pupil/ExampleInstrumentedTest.kt | 6 + app/src/main/AndroidManifest.xml | 1 - app/src/main/java/xyz/quaver/pupil/Pupil.kt | 6 +- .../java/xyz/quaver/pupil/PupilGlideModule.kt | 1 - .../pupil/adapters/GalleryBlockAdapter.kt | 63 ++--- .../quaver/pupil/adapters/ReaderAdapter.kt | 24 +- .../quaver/pupil/services/DownloadService.kt | 244 ++++++++++++++++-- .../main/java/xyz/quaver/pupil/types/Tags.kt | 1 + .../java/xyz/quaver/pupil/ui/MainActivity.kt | 85 +++--- .../xyz/quaver/pupil/ui/ReaderActivity.kt | 95 ++++--- .../xyz/quaver/pupil/ui/SettingsActivity.kt | 19 +- .../pupil/ui/dialog/DownloadLocationDialog.kt | 37 ++- .../quaver/pupil/ui/dialog/GalleryDialog.kt | 45 ++-- .../pupil/ui/fragment/SettingsFragment.kt | 16 +- .../xyz/quaver/pupil/util/download/Cache.kt | 2 - .../xyz/quaver/pupil/util/downloader/Cache.kt | 129 +++++---- .../util/downloader/DownloadFolderManager.kt | 90 ++++--- .../main/java/xyz/quaver/pupil/util/file.kt | 79 +----- .../main/java/xyz/quaver/pupil/util/misc.kt | 55 +++- ...ocation.xml => dialog_download_folder.xml} | 0 ..._location.xml => item_download_folder.xml} | 0 app/src/main/res/values-ja/strings.xml | 12 +- app/src/main/res/values-ko/strings.xml | 12 +- app/src/main/res/values/strings.xml | 14 +- app/src/main/res/xml/root_preferences.xml | 4 +- build.gradle | 1 + 28 files changed, 611 insertions(+), 444 deletions(-) rename app/src/main/res/layout/{dialog_dl_location.xml => dialog_download_folder.xml} (100%) rename app/src/main/res/layout/{item_dl_location.xml => item_download_folder.xml} (100%) diff --git a/app/build.gradle b/app/build.gradle index 1c95935e..e6e112dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,7 +60,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" //implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC" @@ -96,15 +96,15 @@ dependencies { implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' //implementation 'com.andrognito.pinlockview:pinlockview:2.1.0' implementation "ru.noties.markwon:core:3.1.0" - implementation ("xyz.quaver:libpupil:1.1") { + implementation ("xyz.quaver:libpupil:1.3") { exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm' } - implementation "xyz.quaver:documentfilex:0.2.2" + implementation "xyz.quaver:documentfilex:0.2.11-alpha6" testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } androidExtensions { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 13d64938..05b244dd 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -21,6 +21,7 @@ #-renamesourcefileattribute SourceFile -dontobfuscate +-dontoptimize -keep public class * implements com.bumptech.glide.module.GlideModule -keep class * extends com.bumptech.glide.module.AppGlideModule { @@ -46,4 +47,5 @@ -keepclasseswithmembers class xyz.quaver.pupil.** { # <-- change package name to your app's kotlinx.serialization.KSerializer serializer(...); } --keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment \ No newline at end of file +-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment +-keep class xyz.quaver.pupil.util.Preferences \ No newline at end of file diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt index b3609476..b332fba7 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.runBlocking import org.junit.Test import org.junit.runner.RunWith import xyz.quaver.hitomi.getGalleryIDsFromNozomi +import xyz.quaver.hitomi.getSuggestionsForQuery import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.createImgList import xyz.quaver.hiyobi.getReader @@ -121,4 +122,9 @@ class ExampleInstrumentedTest { Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.title ?: "null") } + + @Test + fun test_suggestion() { + getSuggestionsForQuery("female:l") + } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee5f51b2..1c620557 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,6 @@ android:theme="@style/AppTheme" android:networkSecurityConfig="@xml/network_security_config" tools:replace="android:theme" - android:requestLegacyExternalStorage="true" tools:ignore="UnusedAttribute"> ("download_folder").also { - if (!File(it).canWrite()) + if (!FileX(this, it).canWrite()) throw Exception() } } catch (e: Exception) { - Preferences.remove("dl_location") + Preferences.remove("download_folder") } histories = GalleryList(File(ContextCompat.getDataDir(this), "histories.json")) diff --git a/app/src/main/java/xyz/quaver/pupil/PupilGlideModule.kt b/app/src/main/java/xyz/quaver/pupil/PupilGlideModule.kt index a46bf974..0f51f8b3 100644 --- a/app/src/main/java/xyz/quaver/pupil/PupilGlideModule.kt +++ b/app/src/main/java/xyz/quaver/pupil/PupilGlideModule.kt @@ -25,7 +25,6 @@ import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.module.AppGlideModule -import xyz.quaver.pupil.util.download.DownloadWorker import java.io.InputStream @GlideModule diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt index 8b0c1b50..60532f99 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt @@ -23,7 +23,6 @@ import android.graphics.Color import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.graphics.drawable.Drawable -import android.util.Base64 import android.util.SparseBooleanArray import android.view.LayoutInflater import android.view.View @@ -46,22 +45,20 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.getReader import xyz.quaver.pupil.BuildConfig -import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.favorites import xyz.quaver.pupil.types.Tag -import xyz.quaver.pupil.util.GalleryList import xyz.quaver.pupil.util.Preferences -import xyz.quaver.pupil.util.download.Cache +import xyz.quaver.pupil.util.downloader.Cache +import xyz.quaver.pupil.util.downloader.DownloadFolderManager import xyz.quaver.pupil.util.wordCapitalize import java.util.* import kotlin.collections.ArrayList import kotlin.concurrent.schedule -class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List) : RecyclerSwipeAdapter(), SwipeAdapterInterface { +class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List) : RecyclerSwipeAdapter(), SwipeAdapterInterface { enum class ViewType { NEXT, @@ -77,22 +74,23 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri var timerTask: TimerTask? = null private fun updateProgress(context: Context, galleryID: Int) { - val reader = Cache(context).getReaderOrNull(galleryID) + val cache = Cache.getInstance(context, galleryID) CoroutineScope(Dispatchers.Main).launch { - if (reader == null || Preferences["cache_disable"]) { + if (cache.metadata.reader == null || Preferences["cache_disable"]) { view.galleryblock_progressbar.visibility = View.GONE view.galleryblock_progress_complete.visibility = View.GONE return@launch } with(view.galleryblock_progressbar) { + val imageList = cache.metadata.imageList!! - progress = Cache(context).getImages(galleryID)?.size ?: 0 + progress = imageList.filterNotNull().size if (visibility == View.GONE) { visibility = View.VISIBLE - max = reader.galleryInfo.files.size + max = imageList.size } if (progress == max) { @@ -116,7 +114,11 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri } } - fun bind(galleryBlock: GalleryBlock) { + fun bind(galleryID: Int) { + val cache = Cache.getInstance(view.context, galleryID) + + val galleryBlock = cache.metadata.galleryBlock!! + with(view) { val resources = context.resources val languages = resources.getStringArray(R.array.languages).map { @@ -136,13 +138,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri it.start() }) - CoroutineScope(Dispatchers.Main).launch { - val thumbnail = Cache(context).getThumbnail(galleryBlock.id).let { - if (it != null) - Base64.decode(it, Base64.DEFAULT) - else - null - } + CoroutineScope(Dispatchers.IO).launch { + val thumbnail = cache.getThumbnail() galleryblock_thumbnail.post { glide @@ -158,27 +155,9 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri } } - //Check cache - val cache = Cache(context).getCachedGallery(galleryBlock.id) - val reader = Cache(context).getReaderOrNull(galleryBlock.id) - - if (reader != null) { - val count = cache.listFiles()?.count { - Regex("^[0-9]+.+\$").matches(it.name) - } ?: 0 - - with(galleryblock_progressbar) { - max = reader.galleryInfo.files.size - progress = count - - visibility = View.VISIBLE - } - } else - galleryblock_progressbar.visibility = View.GONE - if (timerTask == null) timerTask = timer.schedule(0, 1000) { - updateProgress(context, galleryBlock.id) + updateProgress(context, galleryID) } galleryblock_title.text = galleryBlock.title @@ -339,9 +318,9 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder is GalleryViewHolder) { - val gallery = galleries[position-(if (showPrev) 1 else 0)] + val galleryID = galleries[position-(if (showPrev) 1 else 0)] - holder.bind(gallery) + holder.bind(galleryID) with(holder.view.galleryblock_primary) { setOnClickListener { @@ -367,7 +346,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri mItemManger.closeAllExcept(layout) holder.view.galleryblock_download.text = - if (Cache(holder.view.context).isDownloading(gallery.id)) + if (DownloadFolderManager.getInstance(holder.view.context).isDownloading(galleryID)) holder.view.context.getString(android.R.string.cancel) else holder.view.context.getString(R.string.main_download) @@ -392,8 +371,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri } override fun getItemCount() = - (if (galleries.isEmpty()) 0 else galleries.size)+ - (if (showNext) 1 else 0)+ + galleries.size + + (if (showNext) 1 else 0) + (if (showPrev) 1 else 0) override fun getItemViewType(position: Int): Int { diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt index 4b4cb94c..6924238a 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt @@ -23,7 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.RequestManager +import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.LazyHeaders @@ -38,28 +38,29 @@ import xyz.quaver.hitomi.imageUrlFromImage import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.createImgList import xyz.quaver.hiyobi.user_agent +import xyz.quaver.io.util.readBytes import xyz.quaver.pupil.R +import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.util.Preferences -import xyz.quaver.pupil.util.download.Cache -import xyz.quaver.pupil.util.download.DownloadWorker +import xyz.quaver.pupil.util.downloader.Cache import java.util.* import kotlin.concurrent.schedule import kotlin.math.roundToInt -class ReaderAdapter(private val glide: RequestManager, +class ReaderAdapter(private val activity: ReaderActivity, private val galleryID: Int) : RecyclerView.Adapter() { var reader: Reader? = null val timer = Timer() + private val glide = Glide.with(activity) + var isFullScreen = false var onItemClickListener : ((Int) -> (Unit))? = null class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) - var downloadWorker: DownloadWorker? = null - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LayoutInflater.from(parent.context).inflate( R.layout.item_reader, parent, false @@ -68,11 +69,12 @@ class ReaderAdapter(private val glide: RequestManager, } } + private var cache: Cache? = null override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.view as ConstraintLayout - if (downloadWorker == null) - downloadWorker = DownloadWorker.getInstance(holder.view.context) + if (cache == null) + cache = Cache.getInstance(holder.view.context, galleryID) if (isFullScreen) { holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT @@ -124,15 +126,15 @@ class ReaderAdapter(private val glide: RequestManager, .into(holder.view.image) } } else { - val image = Cache(holder.view.context).getImage(galleryID, position) - val progress = downloadWorker!!.progress[galleryID]?.get(position) + val image = cache!!.getImage(position) + val progress = activity.downloader?.progress?.get(galleryID)?.get(position) if (progress?.isInfinite() == true && image != null) { holder.view.reader_item_progressbar.visibility = View.INVISIBLE holder.view.image.post { glide - .load(image) + .load(image.readBytes()) .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(true) .fitCenter() diff --git a/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt b/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt index 5406c95b..2f912f8d 100644 --- a/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt +++ b/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt @@ -18,24 +18,37 @@ package xyz.quaver.pupil.services +import android.app.PendingIntent import android.app.Service +import android.content.Context import android.content.Intent +import android.util.Log import android.util.SparseArray import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.app.TaskStackBuilder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import okhttp3.Call +import okhttp3.Callback +import okhttp3.Response import okhttp3.ResponseBody import okio.* import xyz.quaver.pupil.PupilInterceptor import xyz.quaver.pupil.R +import xyz.quaver.pupil.client import xyz.quaver.pupil.interceptors +import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.util.downloader.Cache +import xyz.quaver.pupil.util.downloader.DownloadFolderManager +import xyz.quaver.pupil.util.requestBuilders +import xyz.quaver.pupil.util.startForegroundServiceCompat +import java.io.IOException private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit class DownloadService : Service() { - data class Tag(val galleryID: Int, val index: Int) //region Notification @@ -50,13 +63,60 @@ class DownloadService : Service() { .setSmallIcon(R.drawable.ic_notification) .setOngoing(true) } + + private val notification = SparseArray() + + private fun initNotification(galleryID: Int) { + val intent = Intent(this, ReaderActivity::class.java).apply { + putExtra("galleryID", galleryID) + } + val pendingIntent = TaskStackBuilder.create(this).run { + addNextIntentWithParentStack(intent) + getPendingIntent(galleryID, PendingIntent.FLAG_UPDATE_CURRENT) + } + + notification.put(galleryID, NotificationCompat.Builder(this, "download").apply { + setContentTitle(getString(R.string.reader_loading)) + setContentText(getString(R.string.reader_notification_text)) + setSmallIcon(R.drawable.ic_notification) // had to use this because old android doesn't support VectorDrawable on Notification :P + setContentIntent(pendingIntent) + setProgress(0, 0, true) + setOngoing(true) + }) + + notify(galleryID) + } + + private fun notify(galleryID: Int) { + val max = progress[galleryID]?.size ?: 0 + val progress = progress[galleryID]?.count { it.isInfinite() } ?: 0 + + val notification = notification[galleryID] ?: return + + if (isCompleted(galleryID)) { + notification + .setContentText(getString(R.string.reader_notification_complete)) + .setProgress(0, 0, false) + .setOngoing(false) + + notificationManager.cancel(galleryID) + } else + notification + .setProgress(max, progress, false) + .setContentText("$progress/$max") + + if (DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) != null) + notification.let { notificationManager.notify(galleryID, it.build()) } + else + notificationManager.cancel(galleryID) + } //endregion //region ProgressListener @Suppress("UNCHECKED_CAST") private val progressListener: ProgressListener = { (galleryID, index), bytesRead, contentLength, done -> - if (!done && progress[galleryID]?.get(index)?.isFinite() == true) - progress[galleryID]?.set(index, bytesRead * 100F / contentLength) + if (!done && progress[galleryID]?.get(index)?.isFinite() == true) + progress[galleryID]?.set(index, bytesRead * 100F / contentLength) } private class ProgressResponseBody( @@ -107,6 +167,7 @@ class DownloadService : Service() { } //endregion + //region Downloader /** * KEY * primary galleryID @@ -120,11 +181,163 @@ class DownloadService : Service() { */ val progress = SparseArray?>() - override fun onCreate() { - startForeground(R.id.downloader_notification_id, serviceNotification.build()) - interceptors[Tag::class] = interceptor + fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it.isInfinite() } == true + + private val callback = object: Callback { + + override fun onFailure(call: Call, e: IOException) { + if (e.message?.contains("cancel", true) == false) { + val galleryID = (call.request().tag() as Tag).galleryID + + Log.i("PUPILD", "$galleryID ERR-RETRYING $e ${e.message}") + + // Retry + cancel(galleryID) + download(galleryID) + } + } + + override fun onResponse(call: Call, response: Response) { + val (galleryID, index) = call.request().tag() as Tag + val ext = call.request().url().encodedPath().split('.').last() + + kotlin.runCatching { + val image = response.body()?.use { it.bytes() } ?: throw Exception() + + CoroutineScope(Dispatchers.IO).launch { + kotlin.runCatching { + Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image) + }.onSuccess { + notify(galleryID) + progress[galleryID]?.set(index, Float.POSITIVE_INFINITY) + }.onFailure { + Log.i("PUPILD", "$galleryID-$index DLERR-RETRYING $it ${it.message}") + + cancel(galleryID) + download(galleryID) + } + } + } + } } + fun cancel() { + client.dispatcher().queuedCalls().filter { + it.request().tag() is Tag + }.forEach { + it.cancel() + } + client.dispatcher().runningCalls().filter { + it.request().tag() is Tag + }.forEach { + it.cancel() + } + + progress.clear() + notification.clear() + notificationManager.cancelAll() + } + + fun cancel(galleryID: Int) { + client.dispatcher().queuedCalls().filter { + (it.request().tag() as Tag).galleryID == galleryID + }.forEach { + it.cancel() + } + client.dispatcher().runningCalls().filter { + (it.request().tag() as Tag).galleryID == galleryID + }.forEach { + it.cancel() + } + + progress.remove(galleryID) + notification.remove(galleryID) + notificationManager.cancel(galleryID) + } + + fun delete(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { + cancel(galleryID) + Cache.delete(galleryID) + DownloadFolderManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID) + } + + fun download(galleryID: Int): Job = CoroutineScope(Dispatchers.IO).launch { + if (progress.indexOfKey(galleryID) >= 0) + cancel(galleryID) + + val cache = Cache.getInstance(this@DownloadService, galleryID) + + initNotification(galleryID) + + val reader = cache.getReader() + + // Gallery doesn't exist + if (reader == null) { + delete(galleryID) + progress.put(galleryID, null) + return@launch + } + + if (progress.indexOfKey(galleryID) < 0) + progress.put(galleryID, mutableListOf()) + + cache.metadata.imageList?.forEach { + progress[galleryID]?.add(if (it != null) Float.POSITIVE_INFINITY else 0F) + } + + notification[galleryID]?.setContentTitle(reader.galleryInfo.title) + notify(galleryID) + + reader.requestBuilders.filterIndexed { index, _ -> !progress[galleryID]!![index].isInfinite() }.forEachIndexed { index, it -> + val request = it.tag(Tag(galleryID, index)).build() + client.newCall(request).enqueue(callback) + } + } + //endregion + + companion object { + const val KEY_COMMAND = "COMMAND" // String + const val KEY_ID = "ID" // Int + + const val COMMAND_DOWNLOAD = "DOWNLOAD" + const val COMMAND_CANCEL = "CANCEL" + const val COMMAND_DELETE = "DELETE" + + private fun command(context: Context, extras: Intent.() -> Unit) { + context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras)) + } + + fun download(context: Context, galleryID: Int) { + command(context) { + putExtra(KEY_COMMAND, COMMAND_DOWNLOAD) + putExtra(KEY_ID, galleryID) + } + } + + fun cancel(context: Context, galleryID: Int? = null) { + command(context) { + putExtra(KEY_COMMAND, COMMAND_CANCEL) + galleryID?.let { putExtra(KEY_ID, it) } + } + } + + fun delete(context: Context, galleryID: Int) { + command(context) { + putExtra(KEY_COMMAND, COMMAND_DELETE) + putExtra(KEY_ID, galleryID) + } + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.getStringExtra(KEY_COMMAND)) { + COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) download(it) } + COMMAND_CANCEL -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) cancel(it) else cancel() } + COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it) } + } + + return START_NOT_STICKY + } inner class Binder : android.os.Binder() { val service = this@DownloadService @@ -133,20 +346,13 @@ class DownloadService : Service() { private val binder = Binder() override fun onBind(p0: Intent?) = binder - val cache = SparseArray() - fun load(galleryID: Int) { - if (progress.indexOfKey(galleryID) < 0) - progress.put(galleryID, mutableListOf()) - - if (cache.indexOfKey(galleryID) < 0) - cache.put(galleryID, Cache.getInstance(this, galleryID)) - - cache[galleryID].metadata.imageList?.forEach { - progress[galleryID]?.add(if (it == null) Float.POSITIVE_INFINITY else 0F) - } + override fun onCreate() { + startForeground(R.id.downloader_notification_id, serviceNotification.build()) + interceptors[Tag::class] = interceptor } - fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { - + override fun onDestroy() { + interceptors.remove(Tag::class) + cancel() } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt index b5810214..94ab1788 100644 --- a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt +++ b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt @@ -19,6 +19,7 @@ package xyz.quaver.pupil.types import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient @Serializable data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 30a6dccf..41a449f9 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -55,7 +55,6 @@ import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.doSearch import xyz.quaver.hitomi.getGalleryIDsFromNozomi import xyz.quaver.hitomi.getSuggestionsForQuery @@ -68,8 +67,8 @@ import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.ui.dialog.GalleryDialog import xyz.quaver.pupil.util.* -import xyz.quaver.pupil.util.download.Cache -import xyz.quaver.pupil.util.download.DownloadWorker +import xyz.quaver.pupil.util.downloader.Cache +import xyz.quaver.pupil.util.downloader.DownloadFolderManager import java.io.File import java.util.* import kotlin.collections.ArrayList @@ -92,7 +91,7 @@ class MainActivity : AppCompatActivity() { POPULAR } - private val galleries = ArrayList() + private val galleries = ArrayList() private var query = "" set(value) { @@ -112,6 +111,8 @@ class MainActivity : AppCompatActivity() { private var loadingJob: Job? = null private var currentPage = 0 + private lateinit var downloadFolderManager: DownloadFolderManager + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -146,15 +147,7 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) - Intent(this, DownloadService::class.java).let { - when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> - startForegroundService(it) - else -> - startService(it) - } - } - + downloadFolderManager = DownloadFolderManager.getInstance(this) checkUpdate(this) initView() @@ -336,7 +329,7 @@ class MainActivity : AppCompatActivity() { with(main_fab_cancel) { setImageResource(R.drawable.cancel) setOnClickListener { - DownloadWorker.getInstance(context).stop() + DownloadService.cancel(this@MainActivity) } } @@ -447,19 +440,15 @@ class MainActivity : AppCompatActivity() { } } onDownloadClickedHandler = { position -> - val galleryID = galleries[position].id - val worker = DownloadWorker.getInstance(context) + val galleryID = galleries[position] if (Preferences["cache_disable"]) Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show() else { - if (worker.progress.indexOfKey(galleryID) >= 0 && Cache(context).isDownloading(galleryID)) { //download in progress - Cache(context).setDownloading(galleryID, false) - worker.cancel(galleryID) + if (downloadFolderManager.isDownloading(galleryID)) { //download in progress + DownloadService.cancel(this@MainActivity, galleryID) } else { - Cache(context).setDownloading(galleryID, true) - - worker.queue.add(galleryID) + DownloadService.download(this@MainActivity, galleryID) } } @@ -467,25 +456,20 @@ class MainActivity : AppCompatActivity() { } onDeleteClickedHandler = { position -> - val galleryID = galleries[position].id + val galleryID = galleries[position] + DownloadService.delete(this@MainActivity, galleryID) - CoroutineScope(Dispatchers.Default).launch { - DownloadWorker.getInstance(context).cancel(galleryID) + histories.remove(galleryID) - Cache(context).getCachedGallery(galleryID).deleteRecursively() + if (this@MainActivity.mode != Mode.SEARCH) + runOnUiThread { + cancelFetch() + clearGalleries() + fetchGalleries(query, sortMode) + loadBlocks() + } - histories.remove(galleryID) - - if (this@MainActivity.mode != Mode.SEARCH) - runOnUiThread { - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() - } - - completeFlag.put(galleryID, false) - } + completeFlag.put(galleryID, false) closeAllItems() } @@ -496,8 +480,7 @@ class MainActivity : AppCompatActivity() { return@listener val intent = Intent(this@MainActivity, ReaderActivity::class.java) - val gallery = galleries[position] - intent.putExtra("galleryID", gallery.id) + intent.putExtra("galleryID", galleries[position]) //TODO: Maybe sprinkling some transitions will be nice :D startActivity(intent) @@ -507,7 +490,7 @@ class MainActivity : AppCompatActivity() { if (v !is CardView) return@listener false - val galleryID = galleries[position].id + val galleryID = galleries[position] GalleryDialog( this@MainActivity, @@ -762,7 +745,7 @@ class MainActivity : AppCompatActivity() { if (!favoritesFile.exists()) { favoritesFile.createNewFile() - favoritesFile.writeText(Json.encodeToString(Tags())) + favoritesFile.writeText("[]") } setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener { @@ -788,7 +771,7 @@ class MainActivity : AppCompatActivity() { } } R.id.main_menu_sort_newest -> { - sortMode = SortMode.NEWEST + sortMode = MainActivity.SortMode.NEWEST it.isChecked = true runOnUiThread { @@ -1023,7 +1006,7 @@ class MainActivity : AppCompatActivity() { totalItems = it.size } } - else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also { + else -> doSearch("$defaultQuery $query", sortMode == MainActivity.SortMode.POPULAR).also { totalItems = it.size } } @@ -1107,16 +1090,16 @@ class MainActivity : AppCompatActivity() { for (chunk in chunks) chunk.map { galleryID -> async { - Cache(this@MainActivity).getGalleryBlock(galleryID) + Cache.getInstance(this@MainActivity, galleryID).getGalleryBlock()?.let { + galleryID + } } }.forEach { - val galleryBlock = it.await() + it.await()?.also { + withContext(Dispatchers.Main) { + main_progressbar.hide() - withContext(Dispatchers.Main) { - main_progressbar.hide() - - if (galleryBlock != null) { - galleries.add(galleryBlock) + galleries.add(it) main_recyclerview.adapter!!.notifyItemInserted(galleries.size - 1) } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index 07480739..d2d6ee5b 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -18,10 +18,14 @@ package xyz.quaver.pupil.ui +import android.content.ComponentName import android.content.Intent +import android.content.ServiceConnection import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable import android.os.Bundle +import android.os.IBinder +import android.util.Log import android.view.* import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -47,9 +51,10 @@ import xyz.quaver.pupil.R import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.favorites import xyz.quaver.pupil.histories +import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.util.Preferences -import xyz.quaver.pupil.util.download.Cache -import xyz.quaver.pupil.util.download.DownloadWorker +import xyz.quaver.pupil.util.downloader.Cache +import xyz.quaver.pupil.util.downloader.DownloadFolderManager import java.util.* import kotlin.concurrent.schedule import kotlin.concurrent.timer @@ -72,6 +77,22 @@ class ReaderActivity : AppCompatActivity() { } } + private lateinit var cache: Cache + var downloader: DownloadService? = null + private val conn = object: ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + downloader = (service as DownloadService.Binder).service + Log.i("PUPILD", "CON") + } + + override fun onServiceDisconnected(name: ComponentName?) { + downloader = null + Log.i("PUPILD", "DIS") + } + } + + private var deleteOnExit = true + private val timer = Timer() private var autoTimer: Timer? = null @@ -81,6 +102,7 @@ class ReaderActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.activity_reader) title = getString(R.string.reader_loading) supportActionBar?.setDisplayHomeAsUpEnabled(false) @@ -89,23 +111,18 @@ class ReaderActivity : AppCompatActivity() { WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) - setContentView(R.layout.activity_reader) - handleIntent(intent) - - histories.add(galleryID) + cache = Cache.getInstance(this, galleryID) FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID) if (galleryID == 0) { onBackPressed() return } - - initView() if (Preferences["cache_disable"]) { reader_download_progressbar.visibility = View.GONE CoroutineScope(Dispatchers.IO).launch { - val reader = Cache(this@ReaderActivity).getReader(galleryID) + val reader = cache.getReader() launch(Dispatchers.Main) initDownloader@{ if (reader == null) { @@ -115,6 +132,7 @@ class ReaderActivity : AppCompatActivity() { return@initDownloader } + histories.add(galleryID) (reader_recyclerview.adapter as ReaderAdapter).apply { this.reader = reader notifyDataSetChanged() @@ -132,6 +150,8 @@ class ReaderActivity : AppCompatActivity() { } } else initDownloader() + + initView() } override fun onNewIntent(intent: Intent) { @@ -187,9 +207,9 @@ class ReaderActivity : AppCompatActivity() { R.id.reader_menu_page_indicator -> { val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false) with(view.dialog_number_picker) { - minValue=1 - maxValue=Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.size ?: 0 - value=currentPage + minValue = 1 + maxValue = cache.metadata.reader?.galleryInfo?.files?.size ?: 0 + value = currentPage } val dialog = AlertDialog.Builder(this).apply { setView(view) @@ -224,8 +244,12 @@ class ReaderActivity : AppCompatActivity() { timer.cancel() (reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel() - if (!Cache(this).isDownloading(galleryID)) - DownloadWorker.getInstance(this@ReaderActivity).cancel(galleryID) + if (deleteOnExit) { + downloader?.cancel(galleryID) + DownloadFolderManager.getInstance(this).deleteDownloadFolder(galleryID) + } + + unbindService(conn) } override fun onBackPressed() { @@ -261,16 +285,16 @@ class ReaderActivity : AppCompatActivity() { } private fun initDownloader() { - val worker = DownloadWorker.getInstance(this).apply { - cancel(galleryID) - queue.add(galleryID) - } + DownloadService.download(this, galleryID) + bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE) timer.schedule(1000, 1000) { - if (worker.progress.indexOfKey(galleryID) < 0) //loading + val downloader = downloader ?: return@schedule + + if (downloader.progress.indexOfKey(galleryID) < 0) //loading return@schedule - if (worker.progress[galleryID] == null) { //Gallery not found + if (downloader.progress[galleryID] == null) { //Gallery not found timer.cancel() Snackbar .make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE) @@ -279,14 +303,13 @@ class ReaderActivity : AppCompatActivity() { runOnUiThread { reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0 - reader_download_progressbar.progress = worker.progress[galleryID]?.count { it.isInfinite() } ?: 0 + reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0 reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0 if (title == getString(R.string.reader_loading)) { - val reader = Cache(this@ReaderActivity).getReaderOrNull(galleryID) + val reader = cache.metadata.reader if (reader != null) { - with (reader_recyclerview.adapter as ReaderAdapter) { this.reader = reader notifyDataSetChanged() @@ -304,7 +327,7 @@ class ReaderActivity : AppCompatActivity() { } } - if (worker.progress[galleryID]?.all { it.isInfinite() } == true) { //Download finished + if (downloader.isCompleted(galleryID)) { //Download finished reader_download_progressbar.visibility = View.GONE animateDownloadFAB(false) @@ -315,7 +338,7 @@ class ReaderActivity : AppCompatActivity() { private fun initView() { with(reader_recyclerview) { - adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID).apply { + adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply { onItemClickListener = { if (isScroll) { isScroll = false @@ -350,19 +373,18 @@ class ReaderActivity : AppCompatActivity() { } with(reader_fab_download) { - animateDownloadFAB(Cache(context).isDownloading(galleryID)) //If download in progress, animate button + animateDownloadFAB(DownloadFolderManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button setOnClickListener { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false)) Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show() else { - if (Cache(context).isDownloading(galleryID)) { - Cache(context).setDownloading(galleryID, false) - - animateDownloadFAB(false) - } else { - Cache(context).setDownloading(galleryID, true) + if (deleteOnExit) { + deleteOnExit = false + cache.moveToDownload() animateDownloadFAB(true) + } else { + animateDownloadFAB(false) } } } @@ -371,10 +393,8 @@ class ReaderActivity : AppCompatActivity() { with(reader_fab_retry) { setImageResource(R.drawable.refresh) setOnClickListener { - DownloadWorker.getInstance(context).let { - it.cancel(galleryID) - it.queue.add(galleryID) - } + downloader?.cancel(galleryID) + downloader?.download(galleryID) } } @@ -450,8 +470,7 @@ class ReaderActivity : AppCompatActivity() { icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() { override fun onAnimationEnd(drawable: Drawable?) { - val worker = DownloadWorker.getInstance(context) - if (worker.progress[galleryID]?.all { it.isInfinite() } == true) // If download is finished, stop animating + if (downloader?.isCompleted(galleryID) == true) // If download is finished, stop animating post { setImageResource(R.drawable.ic_download) labelText = getString(R.string.reader_fab_download_cancel) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt index bf87262d..cca9192d 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -27,15 +27,12 @@ import android.os.Bundle import android.view.MenuItem import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity -import androidx.preference.Preference -import androidx.preference.PreferenceManager import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.settings_activity.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import net.rdrei.android.dirchooser.DirectoryChooserActivity -import xyz.quaver.io.util.toFile -import xyz.quaver.pupil.Pupil +import xyz.quaver.io.FileX import xyz.quaver.pupil.R import xyz.quaver.pupil.favorites import xyz.quaver.pupil.ui.fragment.LockSettingsFragment @@ -126,16 +123,14 @@ class SettingsActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) contentResolver.takePersistableUriPermission(uri, takeFlags) - val file = uri.toFile(this) - - if (file?.canWrite() != true) + if (FileX(this, uri).canWrite()) + Preferences["download_folder"] = uri.toString() + else Snackbar.make( settings, - R.string.settings_dl_location_not_writable, + R.string.settings_download_folder_not_writable, Snackbar.LENGTH_LONG ).show() - else - Preferences["dl_location"] = file.canonicalPath } } } @@ -146,11 +141,11 @@ class SettingsActivity : AppCompatActivity() { if (!File(directory).canWrite()) Snackbar.make( settings, - R.string.settings_dl_location_not_writable, + R.string.settings_download_folder_not_writable, Snackbar.LENGTH_LONG ).show() else - Preferences["dl_location"] = File(directory).canonicalPath + Preferences["download_folder"] = File(directory).canonicalPath } } else -> super.onActivityResult(requestCode, resultCode, data) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt index e2f42e3e..a0075267 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialog.kt @@ -18,21 +18,19 @@ package xyz.quaver.pupil.ui.dialog -import android.Manifest import android.annotation.SuppressLint import android.app.Activity import android.app.Dialog import android.content.Intent -import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.view.View import android.widget.LinearLayout import android.widget.RadioButton import androidx.appcompat.app.AlertDialog -import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import kotlinx.android.synthetic.main.item_dl_location.view.* +import androidx.core.net.toUri +import kotlinx.android.synthetic.main.item_download_folder.view.* import net.rdrei.android.dirchooser.DirectoryChooserActivity import net.rdrei.android.dirchooser.DirectoryChooserConfig import xyz.quaver.pupil.R @@ -44,7 +42,7 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { private val buttons = mutableListOf>() override fun onCreate(savedInstanceState: Bundle?) { - setTitle(R.string.settings_dl_location) + setTitle(R.string.settings_download_folder) setView(build()) @@ -54,7 +52,7 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { } private fun build() : View { - val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout + val view = layoutInflater.inflate(R.layout.dialog_download_folder, null) as LinearLayout val externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null) @@ -62,13 +60,13 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { dir ?: return@forEachIndexed - view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply { + view.addView(layoutInflater.inflate(R.layout.item_download_folder, view, false).apply { location_type.text = context.getString(when (index) { - 0 -> R.string.settings_dl_location_internal - else -> R.string.settings_dl_location_removable + 0 -> R.string.settings_download_folder_internal + else -> R.string.settings_download_folder_removable }) location_available.text = context.getString( - R.string.settings_dl_location_available, + R.string.settings_download_folder_available, byteToString(dir.freeSpace) ) setOnClickListener { @@ -76,14 +74,14 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { pair.first.isChecked = false } button.performClick() - Preferences["dl_location"] = dir.canonicalPath + Preferences["download_folder"] = dir.toUri().toString() } buttons.add(button to dir) }) } - view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply { - location_type.text = context.getString(R.string.settings_dl_location_custom) + view.addView(layoutInflater.inflate(R.layout.item_download_folder, view, false).apply { + location_type.text = context.getString(R.string.settings_download_folder_custom) setOnClickListener { buttons.forEach { pair -> pair.first.isChecked = false @@ -91,17 +89,12 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { button.performClick() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - - if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) - ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), R.id.request_write_permission_and_saf.normalizeID()) - else { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - putExtra("android.content.extra.SHOW_ADVANCED", true) - } - - activity.startActivityForResult(intent, R.id.request_download_folder.normalizeID()) + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { + putExtra("android.content.extra.SHOW_ADVANCED", true) } + activity.startActivityForResult(intent, R.id.request_download_folder.normalizeID()) + dismiss() } else { // Can't use SAF on old Androids! val config = DirectoryChooserConfig.builder() diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt index ed2d58a7..350f16b1 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt @@ -22,6 +22,7 @@ import android.app.Dialog import android.content.Context import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout.LayoutParams @@ -36,15 +37,10 @@ import kotlinx.android.synthetic.main.dialog_gallery.* import kotlinx.android.synthetic.main.dialog_gallery_details.view.* import kotlinx.android.synthetic.main.dialog_gallery_dotindicator.view.* import kotlinx.android.synthetic.main.item_gallery_details.view.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import xyz.quaver.hitomi.Gallery -import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.getGallery import xyz.quaver.pupil.BuildConfig -import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.ThumbnailPageAdapter @@ -52,7 +48,7 @@ import xyz.quaver.pupil.histories import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.util.ItemClickSupport -import xyz.quaver.pupil.util.download.Cache +import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.wordCapitalize class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) { @@ -131,7 +127,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private private fun addDetails(gallery: Gallery) { val inflater = LayoutInflater.from(context) - + inflater.inflate(R.layout.dialog_gallery_details, gallery_contents, false).apply { gallery_details.setText(R.string.gallery_details) @@ -230,7 +226,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private private fun addRelated(gallery: Gallery) { val inflater = LayoutInflater.from(context) - val galleries = ArrayList() + val galleries = ArrayList() val adapter = GalleryBlockAdapter(glide, galleries).apply { onChipClickedHandler.add { tag -> @@ -240,19 +236,6 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private } } - CoroutineScope(Dispatchers.Main).launch { - gallery.related.forEachIndexed { i, galleryID -> - async(Dispatchers.IO) { - Cache(context).getGalleryBlock(galleryID) - }.let { - val galleryBlock = it.await() ?: return@let - - galleries.add(galleryBlock) - adapter.notifyItemInserted(i) - } - } - } - inflater.inflate(R.layout.dialog_gallery_details, gallery_contents, false).apply { gallery_details.setText(R.string.gallery_related) @@ -263,15 +246,15 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private ItemClickSupport.addTo(this).apply { onItemClickListener = { _, position, _ -> context.startActivity(Intent(context, ReaderActivity::class.java).apply { - putExtra("galleryID", galleries[position].id) + putExtra("galleryID", galleries[position]) }) - histories.add(galleries[position].id) + histories.add(galleries[position]) } onItemLongClickListener = { _, position, _ -> GalleryDialog( context, glide, - galleries[position].id + galleries[position] ).apply { onChipClickedHandler.add { tag -> this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) } @@ -287,6 +270,18 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private }.let { gallery_contents.addView(it) } + + CoroutineScope(Dispatchers.IO).launch { + gallery.related.forEach { galleryID -> + Cache.getInstance(context, galleryID).getGalleryBlock()?.let { + galleries.add(galleryID) + + withContext(Dispatchers.Main) { + adapter.notifyItemInserted(galleries.size-1) + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt index 7dbd761b..46a95128 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt @@ -29,6 +29,7 @@ import androidx.preference.PreferenceFragmentCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import xyz.quaver.io.FileX import xyz.quaver.pupil.R import xyz.quaver.pupil.histories import xyz.quaver.pupil.ui.LockActivity @@ -141,7 +142,7 @@ class SettingsFragment : setNegativeButton(android.R.string.no) { _, _ -> } }.show() } - "dl_location" -> { + "download_folder" -> { DownloadLocationDialog(requireActivity()).show() } "default_query" -> { @@ -208,9 +209,6 @@ class SettingsFragment : "proxy" -> { summary = context?.let { getProxyInfo().type.name } } - "dl_location" -> { - summary = context?.let { getDownloadDirectory(it).canonicalPath } - } } } } @@ -275,8 +273,14 @@ class SettingsFragment : onPreferenceClickListener = this@SettingsFragment } - "dl_location" -> { - summary = getDownloadDirectory(requireContext()).canonicalPath + "download_folder" -> { + setSummaryProvider { + val uri: String = Preferences[it.key] + + kotlin.runCatching { + FileX(context, uri).canonicalPath + }.getOrElse { "" } + } onPreferenceClickListener = this@SettingsFragment } diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt index 70d92b3a..f1b30b4a 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt @@ -25,8 +25,6 @@ import android.util.SparseArray import androidx.preference.PreferenceManager import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json diff --git a/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt index 7eedf8d8..dc11f95f 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt @@ -20,50 +20,58 @@ package xyz.quaver.pupil.util.downloader import android.content.Context import android.content.ContextWrapper -import android.util.Base64 +import android.util.Log import android.util.SparseArray import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.Request import xyz.quaver.Code -import xyz.quaver.hitomi.Gallery import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.Reader -import xyz.quaver.hitomi.getGallery import xyz.quaver.io.FileX -import xyz.quaver.io.util.getChild -import xyz.quaver.io.util.readBytes -import xyz.quaver.io.util.readText -import xyz.quaver.io.util.writeBytes +import xyz.quaver.io.util.* import xyz.quaver.pupil.client import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.formatDownloadFolder +import kotlin.io.deleteRecursively +import kotlin.io.writeText @Serializable data class Metadata( var galleryBlock: GalleryBlock? = null, - var gallery: Gallery? = null, - var thumbnail: String? = null, var reader: Reader? = null, var imageList: MutableList? = null ) { - fun copy(): Metadata = Metadata(galleryBlock, gallery, thumbnail, reader, imageList?.let { MutableList(it.size) { i -> it[i] } }) + fun copy(): Metadata = Metadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } }) } class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) { companion object { + private val mutex = Mutex() private val instances = SparseArray() fun getInstance(context: Context, galleryID: Int) = - instances[galleryID] ?: synchronized(this) { + instances[galleryID] ?: runBlocking { mutex.withLock { instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) } - } + } } + + fun delete(galleryID: Int) { runBlocking { mutex.withLock { + instances[galleryID]?.galleryFolder?.deleteRecursively() + instances.delete(galleryID) + } } } } + init { + galleryFolder.mkdirs() + } + + private val mutex = Mutex() var metadata = kotlin.runCatching { findFile(".metadata")?.readText()?.let { Json.decodeFromString(it) @@ -76,7 +84,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW val cacheFolder: FileX get() = FileX(this, cacheDir, "imageCache/$galleryID") - val cachedGallery: FileX + val galleryFolder: FileX get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) ?: FileX(this, cacheDir, "imageCache/$galleryID") @@ -87,16 +95,19 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW if (it.exists()) it else null } } - @Synchronized - fun setMetadata(change: (Metadata) -> Unit) { + @Suppress("BlockingMethodInNonBlockingContext") + suspend fun setMetadata(change: (Metadata) -> Unit) { mutex.withLock { change.invoke(metadata) - val file = cachedGallery.getChild(".metadata") + val file = galleryFolder.getChild(".metadata") CoroutineScope(Dispatchers.IO).launch { - file.writeText(Json.encodeToString(Metadata)) + kotlin.runCatching { + file.createNewFile() + file.writeText(Json.encodeToString(metadata)) + } } - } + } } suspend fun getGalleryBlock(): GalleryBlock? { val sources = listOf( @@ -123,59 +134,47 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW } } - suspend fun getGallery(): Gallery? = - metadata.gallery - ?: withContext(Dispatchers.IO) { - kotlin.runCatching { - getGallery(galleryID) - }.getOrNull()?.also { - launch { setMetadata { metadata -> - metadata.gallery = it - - if (metadata.imageList == null) - metadata.imageList = MutableList(it.thumbnails.size) { null } - } } - } - } - @Suppress("BlockingMethodInNonBlockingContext") - suspend fun getThumbnail(): String? = - metadata.thumbnail - ?: withContext(Dispatchers.IO) { - getGalleryBlock()?.thumbnails?.firstOrNull()?.let { thumbnail -> - kotlin.runCatching { - val request = Request.Builder() - .url(thumbnail) - .build() + suspend fun getThumbnail(): ByteArray? = + findFile(".thumbnail")?.readBytes() + ?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) { + val request = Request.Builder() + .url(it) + .build() - val image = client.newCall(request).execute().body()?.use { it.bytes() } + kotlin.runCatching { + client.newCall(request).execute().body()?.use { it.bytes() } + }.getOrNull()?.also { kotlin.run { + galleryFolder.getChild(".thumbnail").writeBytes(it) + } } + } } - Base64.encodeToString(image, Base64.DEFAULT) - }.getOrNull() - }?.also { - launch { setMetadata { metadata -> metadata.thumbnail = it } } - } - } - - suspend fun getReader(galleryID: Int): Reader? { - val mirrors = Preferences.get("mirrors").split('>') + suspend fun getReader(): Reader? { + val mirrors = Preferences.get("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') } val sources = mapOf( Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) }, Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) } - ).toSortedMap { o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) } + ).let { + if (mirrors.isNotEmpty()) + it.toSortedMap{ o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) } + else + it + } return metadata.reader ?: withContext(Dispatchers.IO) { var reader: Reader? = null for (source in sources) { - reader = try { withTimeoutOrNull(1000) { + reader = try { source.value.invoke() - } } catch (e: Exception) { null } + } catch (e: Exception) { + null + } - if (reader != null) - break + if (reader != null) + break } reader?.also { @@ -193,13 +192,11 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW metadata.imageList?.get(index)?.let { findFile(it) } @Suppress("BlockingMethodInNonBlockingContext") - fun putImage(index: Int, fileName: String, data: ByteArray) = CoroutineScope(Dispatchers.IO).launch { - val file = FileX(this@Cache, cachedGallery, fileName).also { - it.createNewFile() - } + suspend fun putImage(index: Int, fileName: String, data: ByteArray) { + val file = galleryFolder.getChild(fileName) + file.createNewFile() file.writeBytes(data) - setMetadata { metadata -> metadata.imageList!![index] = fileName } } @@ -208,11 +205,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW if (downloadFolder == null) DownloadFolderManager.getInstance(this@Cache).addDownloadFolder(galleryID, this@Cache.formatDownloadFolder()) - metadata.imageList?.forEach { - it ?: return@forEach + metadata.imageList?.forEach { imageName -> + imageName ?: return@forEach - val target = downloadFolder!!.getChild(it) - val source = cacheFolder.getChild(it) + Log.i("PUPIL", downloadFolder?.uri.toString()) + val target = downloadFolder!!.getChild(imageName) + val source = cacheFolder.getChild(imageName) if (!source.exists()) return@forEach @@ -223,6 +221,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW } } + Log.i("PUPIL", downloadFolder?.uri.toString()) val cacheMetadata = cacheFolder.getChild(".metadata") val downloadMetadata = downloadFolder!!.getChild(".metadata") diff --git a/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadFolderManager.kt b/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadFolderManager.kt index e179179d..da275c25 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadFolderManager.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadFolderManager.kt @@ -24,13 +24,18 @@ import android.webkit.URLUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import okhttp3.Call import xyz.quaver.io.FileX +import xyz.quaver.io.util.getChild import xyz.quaver.io.util.readText +import xyz.quaver.pupil.client +import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.util.Preferences class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) { @@ -46,59 +51,70 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp val defaultDownloadFolder = FileX(this, getExternalFilesDir(null)!!) - val downloadFolder = { - val uri: String = Preferences["download_directory"] - - if (!URLUtil.isValidUrl(uri)) - Preferences["download_directory"] = defaultDownloadFolder + val downloadFolder: FileX + get() = { + kotlin.runCatching { + FileX(this, Preferences.get("download_folder")) + }.getOrElse { + Preferences["download_folder"] = defaultDownloadFolder.uri.toString() + defaultDownloadFolder + } + }.invoke() + private val downloadFolderMapMutex = Mutex() + private val downloadFolderMap: MutableMap = runBlocking { downloadFolderMapMutex.withLock { kotlin.runCatching { - FileX(this, uri) - }.getOrElse { - Preferences["download_directory"] = defaultDownloadFolder - FileX(this, defaultDownloadFolder) - } - }.invoke() - - private val downloadFolderMap: MutableMap = - kotlin.runCatching { - FileX(this@DownloadFolderManager, downloadFolder, ".download").readText()?.let { + downloadFolder.getChild(".download").readText()?.let { Json.decodeFromString>(it) } }.getOrNull() ?: mutableMapOf() - private val downloadFolderMapMutex = Mutex() + } } - @Synchronized - fun getDownloadFolder(galleryID: Int): FileX? = - downloadFolderMap[galleryID]?.let { FileX(this, downloadFolder, it) } + fun isDownloading(galleryID: Int): Boolean { + val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID } - @Synchronized - fun addDownloadFolder(galleryID: Int, name: String) { - if (downloadFolderMap.containsKey(galleryID)) - return - - if (FileX(this@DownloadFolderManager, downloadFolder, name).mkdir()) { - downloadFolderMap[galleryID] = name - - CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { - FileX(this@DownloadFolderManager, downloadFolder, ".download").writeText(Json.encodeToString(downloadFolderMap)) - } } - } + return downloadFolderMap.containsKey(galleryID) + && client.dispatcher().let { it.queuedCalls().any(isThisGallery) || it.runningCalls().any(isThisGallery) } } - @Synchronized - fun removeDownloadFolder(galleryID: Int) { + fun getDownloadFolder(galleryID: Int): FileX? = runBlocking { downloadFolderMapMutex.withLock { + downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) } + } } + + fun addDownloadFolder(galleryID: Int, name: String) { runBlocking { downloadFolderMapMutex.withLock { + if (downloadFolderMap.containsKey(galleryID)) + return@withLock + + val folder = downloadFolder.getChild(name) + + if (!folder.exists()) + folder.mkdirs() + + downloadFolderMap[galleryID] = name + + CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { + downloadFolder.getChild(".download").let { + it.createNewFile() + it.writeText(Json.encodeToString(downloadFolderMap)) + } + } } + } } } + + fun deleteDownloadFolder(galleryID: Int) { runBlocking { downloadFolderMapMutex.withLock { if (!downloadFolderMap.containsKey(galleryID)) - return + return@withLock downloadFolderMap[galleryID]?.let { - if (FileX(this@DownloadFolderManager, downloadFolder, it).delete()) { + if (downloadFolder.getChild(it).delete()) { downloadFolderMap.remove(galleryID) CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { - FileX(this@DownloadFolderManager, downloadFolder, ".download").writeText(Json.encodeToString(downloadFolderMap)) + downloadFolder.getChild(".download").let { + it.createNewFile() + it.writeText(Json.encodeToString(downloadFolderMap)) + } } } } } - } + } } } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/file.kt b/app/src/main/java/xyz/quaver/pupil/util/file.kt index 7f6b7e82..3dbc6bd1 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/file.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt @@ -27,6 +27,7 @@ import java.io.FileOutputStream import java.lang.reflect.Array import java.net.URL +@Deprecated("Use downloader.Cache instead") fun getCachedGallery(context: Context, galleryID: Int) = File(getDownloadDirectory(context), galleryID.toString()).let { if (it.exists()) @@ -35,6 +36,7 @@ fun getCachedGallery(context: Context, galleryID: Int) = File(context.cacheDir, "imageCache/$galleryID") } +@Deprecated("Use downloader.Cache instead") fun getDownloadDirectory(context: Context) = Preferences.get("dl_location").let { if (it.isNotEmpty() && !it.startsWith("content")) @@ -43,81 +45,6 @@ fun getDownloadDirectory(context: Context) = context.getExternalFilesDir(null)!! } -fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) { - - if (to.parentFile?.exists() == false) - to.parentFile!!.mkdirs() - - if (!to.exists()) - to.createNewFile() - - FileOutputStream(to).use { out -> - - with(openConnection()) { - val fileSize = contentLength.toLong() - - getInputStream().use { - - var bytesCopied: Long = 0 - val buffer = ByteArray(DEFAULT_BUFFER_SIZE) - - var bytes = it.read(buffer) - while (bytes >= 0) { - out.write(buffer, 0, bytes) - bytesCopied += bytes - onDownloadProgress?.invoke(bytesCopied, fileSize) - bytes = it.read(buffer) - } - - } - } - - } -} - -fun getExtSdCardPaths(context: Context) = - ContextCompat.getExternalFilesDirs(context, null).drop(1).map { - it.absolutePath.substringBeforeLast("/Android/data").let { path -> - runCatching { - File(path).canonicalPath - }.getOrElse { - path - } - } - } - -const val PRIMARY_VOLUME_NAME = "primary" -fun getVolumePath(context: Context, volumeID: String?): String? { - return runCatching { - val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager - val storageVolumeClass = Class.forName("android.os.storage.StorageVolume") - - val getVolumeList = storageVolumeClass.javaClass.getMethod("getVolumeList") - val getUUID = storageVolumeClass.getMethod("getUuid") - val getPath = storageVolumeClass.getMethod("getPath") - val isPrimary = storageVolumeClass.getMethod("isPrimary") - - val result = getVolumeList.invoke(storageManager)!! - - val length = Array.getLength(result) - - for (i in 0 until length) { - val storageVolumeElement = Array.get(result, i) - val uuid = getUUID.invoke(storageVolumeElement) as? String - val primary = isPrimary.invoke(storageVolumeElement) as? Boolean - - // primary volume? - if (primary == true && volumeID == PRIMARY_VOLUME_NAME) - return@runCatching getPath.invoke(storageVolumeElement) as? String - - // other volumes? - if (volumeID == uuid) { - return@runCatching getPath.invoke(storageVolumeElement) as? String - } - } - return@runCatching null - }.getOrNull() -} - +@Deprecated("Use FileX instead") fun File.isParentOf(another: File) = another.absolutePath.startsWith(this.absolutePath) \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/misc.kt b/app/src/main/java/xyz/quaver/pupil/util/misc.kt index 4952021c..1ea3f8b3 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -19,12 +19,23 @@ package xyz.quaver.pupil.util import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.os.Build import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import okhttp3.OkHttpClient +import okhttp3.Request +import xyz.quaver.Code +import xyz.quaver.hitomi.Reader +import xyz.quaver.hitomi.getReferer +import xyz.quaver.hitomi.imageUrlFromImage +import xyz.quaver.hiyobi.cookie +import xyz.quaver.hiyobi.createImgList +import xyz.quaver.hiyobi.user_agent import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Metadata import java.util.* @@ -77,17 +88,47 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply { } val formatMap = mapOf (String)>( - "\$ID" to { runBlocking { it.getGalleryBlock()?.id.toString() } }, - "\$TITLE" to { runBlocking { it.getGalleryBlock()?.title.toString() } }, + "-id-" to { runBlocking { it.getGalleryBlock()?.id.toString() } }, + "-title-" to { runBlocking { it.getGalleryBlock()?.title.toString() } }, // TODO ) /** * Formats download folder name with given Metadata */ -fun Cache.formatDownloadFolder(): String { - return Preferences["download_folder_format", "\$ID"].apply { - formatMap.entries.forEach { (key, lambda) -> - this.replace(key, lambda.invoke(this@formatDownloadFolder)) +fun Cache.formatDownloadFolder(): String = + Preferences["download_folder_format", "-id-"].let { + formatMap.entries.fold(it) { str, (k, v) -> + str.replace(k, v.invoke(this), true) } } -} \ No newline at end of file + +fun Context.startForegroundServiceCompat(service: Intent) { + if (Build.VERSION.SDK_INT >= 26) + startForegroundService(service) + else + startService(service) +} + +val Reader.requestBuilders: List + get() { + val galleryID = this.galleryInfo.id ?: 0 + val lowQuality = Preferences["low_quality", true] + + return when(code) { + Code.HITOMI -> { + this.galleryInfo.files.map { + Request.Builder() + .url(imageUrlFromImage(galleryID, it, !lowQuality)) + .header("Referer", getReferer(galleryID)) + } + } + Code.HIYOBI -> { + createImgList(galleryID, this, lowQuality).map { + Request.Builder() + .url(it.path) + .header("User-Agent", user_agent) + .header("Cookie", cookie) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_dl_location.xml b/app/src/main/res/layout/dialog_download_folder.xml similarity index 100% rename from app/src/main/res/layout/dialog_dl_location.xml rename to app/src/main/res/layout/dialog_download_folder.xml diff --git a/app/src/main/res/layout/item_dl_location.xml b/app/src/main/res/layout/item_download_folder.xml similarity index 100% rename from app/src/main/res/layout/item_dl_location.xml rename to app/src/main/res/layout/item_download_folder.xml diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e6309d0f..6b3eb9fb 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -96,18 +96,18 @@ バックアップファイルを作成しました 復元に失敗しました %1$d項目を復元しました - ダウンロード場所 - 内部ストレージ - 外部SDカード - %s 使用可能 + ダウンロード場所 + 内部ストレージ + 外部SDカード + %s 使用可能 ダウンロードが完了しました ここをクリックしてアップデートを行えます ベータチャンネルでアップデートを受信 v%s 低解像度イメージ ロード速度とデータ使用料を改善するため低解像度イメージをロード - 手動で設定 - このフォルダにアクセスできません。他のフォルダを選択してください。 + 手動で設定 + このフォルダにアクセスできません。他のフォルダを選択してください。 プロクシ ID プロクシタイプ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b29c0d3a..07134430 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -94,10 +94,10 @@ 백업 파일을 생성하였습니다 복원에 실패했습니다 %1$d개 항목을 복원했습니다 - 다운로드 위치 - 내부 저장공간 - 외부 SD카드 - %s 사용 가능 + 다운로드 위치 + 내부 저장공간 + 외부 SD카드 + %s 사용 가능 다운로드가 완료되었습니다 여기를 클릭해서 업데이트를 진행할 수 있습니다 베타 채널에서 업데이트 @@ -106,8 +106,8 @@ 로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드 미러 서버에서 이미지 로드 미러 설정 - 직접 설정 - 이 폴더에 접근할 수 없습니다. 다른 폴더를 선택해주세요. + 직접 설정 + 이 폴더에 접근할 수 없습니다. 다른 폴더를 선택해주세요. 프록시 ID 프록시 타입 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 61e7191a..9e926c27 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,7 +36,7 @@ Shows update progress Unable to connect to hitomi.la - + Lock file corrupted! Please re-install Pupil No result @@ -135,12 +135,12 @@ Clear history Do you want to clear histories? %1$d histories saved - Download directory - Removable Storage - Internal Storage - %s available - Custom Location - This folder is not writable. Please select another folder. + Download directory + Removable Storage + Internal Storage + %s available + Custom Location + This folder is not writable. Please select another folder. Disable Cache Download is disabled when the cache is disabled Low quality images diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 5fafab5d..0655a0be 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -44,8 +44,8 @@ app:title="@string/settings_clear_history"/> + app:key="download_folder" + app:title="@string/settings_download_folder"/>