diff --git a/app/build.gradle b/app/build.gradle index e6e112dd..f9b707a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -99,7 +99,7 @@ dependencies { implementation ("xyz.quaver:libpupil:1.3") { exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm' } - implementation "xyz.quaver:documentfilex:0.2.11-alpha6" + implementation "xyz.quaver:documentfilex:0.2.11" testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test:rules:1.3.0' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 05b244dd..d5e3055f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -21,7 +21,6 @@ #-renamesourcefileattribute SourceFile -dontobfuscate --dontoptimize -keep public class * implements com.bumptech.glide.module.GlideModule -keep class * extends com.bumptech.glide.module.AppGlideModule { diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 4f84ef79..eead367e 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -23,7 +23,6 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context -import android.content.Intent import android.os.Build import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.ContextCompat @@ -74,7 +73,10 @@ class Pupil : Application() { override fun onCreate() { preferences = PreferenceManager.getDefaultSharedPreferences(this) - val userID = Preferences["user_id", UUID.randomUUID().toString(), true] + val userID = Preferences["user_id", ""].let { userID -> + if (userID.isEmpty()) UUID.randomUUID().toString().also { Preferences["user_id"] = it } + else userID + } FirebaseCrashlytics.getInstance().setUserId(userID) 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 60532f99..f775243c 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt @@ -94,14 +94,24 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri } if (progress == max) { + val downloadManager = DownloadFolderManager.getInstance(context) + if (completeFlag.get(galleryID, false)) { with(view.galleryblock_progress_complete) { - setImageResource(R.drawable.ic_progressbar) + setImageResource( + if (downloadManager.getDownloadFolder(galleryID) != null) + R.drawable.ic_progressbar + else R.drawable.ic_progressbar_cache + ) visibility = View.VISIBLE } } else { with(view.galleryblock_progress_complete) { - setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply { + setImageDrawable(AnimatedVectorDrawableCompat.create(context, + if (downloadManager.getDownloadFolder(galleryID) != null) + R.drawable.ic_progressbar_complete + else R.drawable.ic_progressbar_complete_cache + ).apply { this?.start() }) visibility = View.VISIBLE 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 2f912f8d..0ff43a82 100644 --- a/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt +++ b/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt @@ -43,6 +43,7 @@ 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.ellipsize import xyz.quaver.pupil.util.requestBuilders import xyz.quaver.pupil.util.startForegroundServiceCompat import java.io.IOException @@ -208,8 +209,12 @@ class DownloadService : Service() { kotlin.runCatching { Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image) }.onSuccess { - notify(galleryID) progress[galleryID]?.set(index, Float.POSITIVE_INFINITY) + notify(galleryID) + + if (isCompleted(galleryID)) + if (DownloadFolderManager.getInstance(this@DownloadService).getDownloadFolder(galleryID) != null) + Cache.getInstance(this@DownloadService, galleryID).moveToDownload() }.onFailure { Log.i("PUPILD", "$galleryID-$index DLERR-RETRYING $it ${it.message}") @@ -240,12 +245,12 @@ class DownloadService : Service() { fun cancel(galleryID: Int) { client.dispatcher().queuedCalls().filter { - (it.request().tag() as Tag).galleryID == galleryID + (it.request().tag() as? Tag)?.galleryID == galleryID }.forEach { it.cancel() } client.dispatcher().runningCalls().filter { - (it.request().tag() as Tag).galleryID == galleryID + (it.request().tag() as? Tag)?.galleryID == galleryID }.forEach { it.cancel() } @@ -257,11 +262,11 @@ class DownloadService : Service() { fun delete(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch { cancel(galleryID) - Cache.delete(galleryID) DownloadFolderManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID) + Cache.delete(galleryID) } - fun download(galleryID: Int): Job = CoroutineScope(Dispatchers.IO).launch { + fun download(galleryID: Int, priority: Boolean = false): Job = CoroutineScope(Dispatchers.IO).launch { if (progress.indexOfKey(galleryID) >= 0) cancel(galleryID) @@ -271,6 +276,8 @@ class DownloadService : Service() { val reader = cache.getReader() + Log.i("PUPILD", "READER") + // Gallery doesn't exist if (reader == null) { delete(galleryID) @@ -278,6 +285,8 @@ class DownloadService : Service() { return@launch } + Log.i("PUPILD", "READER OK") + if (progress.indexOfKey(galleryID) < 0) progress.put(galleryID, mutableListOf()) @@ -285,19 +294,39 @@ class DownloadService : Service() { progress[galleryID]?.add(if (it != null) Float.POSITIVE_INFINITY else 0F) } - notification[galleryID]?.setContentTitle(reader.galleryInfo.title) + Log.i("PUPILD", "LOADED") + + notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30)) notify(galleryID) + Log.i("PUPILD", "NOTIFY") + + val queued = mutableSetOf() + + if (priority) { + client.dispatcher().queuedCalls().forEach { + val queuedID = (it.request().tag() as? Tag)?.galleryID ?: return@forEach + + if (queued.add(queuedID)) + cancel(queuedID) + } + } + reader.requestBuilders.filterIndexed { index, _ -> !progress[galleryID]!![index].isInfinite() }.forEachIndexed { index, it -> val request = it.tag(Tag(galleryID, index)).build() client.newCall(request).enqueue(callback) } + + queued.forEach { download(it) } + + Log.i("PUPILD", "OK") } //endregion companion object { const val KEY_COMMAND = "COMMAND" // String const val KEY_ID = "ID" // Int + const val KEY_PRIORITY = "PRIORITY" // Boolean const val COMMAND_DOWNLOAD = "DOWNLOAD" const val COMMAND_CANCEL = "CANCEL" @@ -307,9 +336,10 @@ class DownloadService : Service() { context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras)) } - fun download(context: Context, galleryID: Int) { + fun download(context: Context, galleryID: Int, priority: Boolean = false) { command(context) { putExtra(KEY_COMMAND, COMMAND_DOWNLOAD) + putExtra(KEY_PRIORITY, priority) putExtra(KEY_ID, galleryID) } } @@ -331,7 +361,9 @@ class DownloadService : Service() { 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_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) + download(it, intent.getBooleanExtra(KEY_PRIORITY, false)) + } 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) } } 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 41a449f9..bb80d3d0 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -23,7 +23,6 @@ import android.app.Activity import android.content.Intent import android.graphics.drawable.Animatable import android.net.Uri -import android.os.Build import android.os.Bundle import android.text.* import android.text.style.AlignmentSpan @@ -448,6 +447,7 @@ class MainActivity : AppCompatActivity() { DownloadService.cancel(this@MainActivity, galleryID) } else { + downloadFolderManager.addDownloadFolder(galleryID) DownloadService.download(this@MainActivity, galleryID) } } @@ -771,7 +771,7 @@ class MainActivity : AppCompatActivity() { } } R.id.main_menu_sort_newest -> { - sortMode = MainActivity.SortMode.NEWEST + sortMode = SortMode.NEWEST it.isChecked = true runOnUiThread { @@ -1006,7 +1006,7 @@ class MainActivity : AppCompatActivity() { totalItems = it.size } } - else -> doSearch("$defaultQuery $query", sortMode == MainActivity.SortMode.POPULAR).also { + else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also { totalItems = it.size } } @@ -1027,13 +1027,7 @@ class MainActivity : AppCompatActivity() { } } Mode.DOWNLOAD -> { - val downloads = getDownloadDirectory(this@MainActivity).listFiles()?.filter { file -> - file.isDirectory && file.name.toIntOrNull() != null - }?.sortedByDescending { - it.lastModified() - }?.map { - it.name.toInt() - } ?: emptyList() + val downloads = downloadFolderManager.downloadFolderMap.keys.toList() when { query.isEmpty() -> downloads.also { 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 d2d6ee5b..50147dcd 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -18,6 +18,7 @@ package xyz.quaver.pupil.ui +import android.app.DownloadManager import android.content.ComponentName import android.content.Intent import android.content.ServiceConnection @@ -91,8 +92,6 @@ class ReaderActivity : AppCompatActivity() { } } - private var deleteOnExit = true - private val timer = Timer() private var autoTimer: Timer? = null @@ -244,12 +243,13 @@ class ReaderActivity : AppCompatActivity() { timer.cancel() (reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel() - if (deleteOnExit) { + if (!DownloadFolderManager.getInstance(this).isDownloading(galleryID)) { downloader?.cancel(galleryID) DownloadFolderManager.getInstance(this).deleteDownloadFolder(galleryID) } - unbindService(conn) + if (downloader != null) + unbindService(conn) } override fun onBackPressed() { @@ -285,7 +285,7 @@ class ReaderActivity : AppCompatActivity() { } private fun initDownloader() { - DownloadService.download(this, galleryID) + DownloadService.download(this, galleryID, true) bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE) timer.schedule(1000, 1000) { @@ -379,12 +379,14 @@ class ReaderActivity : AppCompatActivity() { 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 (deleteOnExit) { - deleteOnExit = false - cache.moveToDownload() - animateDownloadFAB(true) - } else { + val downloadManager = DownloadFolderManager.getInstance(this@ReaderActivity) + + if (downloadManager.isDownloading(galleryID)) { + downloadManager.deleteDownloadFolder(galleryID) animateDownloadFAB(false) + } else { + downloadManager.addDownloadFolder(galleryID) + animateDownloadFAB(true) } } } 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 a0075267..8c05da09 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 @@ -35,6 +35,7 @@ import net.rdrei.android.dirchooser.DirectoryChooserActivity import net.rdrei.android.dirchooser.DirectoryChooserConfig import xyz.quaver.pupil.R import xyz.quaver.pupil.util.* +import xyz.quaver.pupil.util.downloader.DownloadFolderManager import java.io.File @SuppressLint("InflateParams") @@ -114,10 +115,10 @@ class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) { }) externalFilesDirs.indexOfFirst { - it.canonicalPath == getDownloadDirectory(context).canonicalPath + it.canonicalPath == DownloadFolderManager.getInstance(context).downloadFolder.canonicalPath }.let { index -> if (index < 0) - buttons.first().first.isChecked = true + buttons.last().first.isChecked = true else buttons[index].first.isChecked = true } 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 46a95128..7624f2cf 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 @@ -209,6 +209,9 @@ class SettingsFragment : "proxy" -> { summary = context?.let { getProxyInfo().type.name } } + "download_folder" -> { + summary = FileX(context, Preferences.get("download_folder")).canonicalPath + } } } } @@ -221,6 +224,11 @@ class SettingsFragment : initPreferences() } + override fun onDestroy() { + Preferences.unregisterOnSharedPreferenceChangeListener(this) + super.onDestroy() + } + private fun initPreferences() { for (i in 0 until preferenceScreen.preferenceCount) { @@ -274,13 +282,7 @@ class SettingsFragment : onPreferenceClickListener = this@SettingsFragment } "download_folder" -> { - setSummaryProvider { - val uri: String = Preferences[it.key] - - kotlin.runCatching { - FileX(context, uri).canonicalPath - }.getOrElse { "" } - } + summary = FileX(context, Preferences.get("download_folder")).canonicalPath onPreferenceClickListener = this@SettingsFragment } diff --git a/app/src/main/java/xyz/quaver/pupil/util/Preferences.kt b/app/src/main/java/xyz/quaver/pupil/util/Preferences.kt index 8d979435..3256683d 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/Preferences.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/Preferences.kt @@ -25,15 +25,6 @@ lateinit var preferences: SharedPreferences object Preferences: SharedPreferences by preferences { - @Suppress("UNCHECKED_CAST") - val putMap = mapOf, (String, Any) -> Unit>( - String::class to { k, v -> edit().putString(k, v as String).apply() }, - Int::class to { k, v -> edit().putBoolean(k, v as Boolean).apply() }, - Long::class to { k, v -> edit().putLong(k, v as Long).apply() }, - Boolean::class to { k, v -> edit().putBoolean(k, v as Boolean).apply() }, - Set::class to { k, v -> edit().putStringSet(k, v as Set).apply() } - ) - val defMap = mapOf( String::class to "", Int::class to -1, @@ -42,12 +33,14 @@ object Preferences: SharedPreferences by preferences { Set::class to emptySet() ) - inline operator fun set(key: String, value: T) { - putMap[T::class]?.invoke(key, value) - } + operator fun set(key: String, value: String) = edit().putString(key, value).apply() + operator fun set(key: String, value: Int) = edit().putInt(key, value).apply() + operator fun set(key: String, value: Long) = edit().putLong(key, value).apply() + operator fun set(key: String, value: Boolean) = edit().putBoolean(key, value).apply() + operator fun set(key: String, value: Set) = edit().putStringSet(key, value).apply() - inline operator fun get(key: String, defaultVal: T = defMap[T::class] as T, setIfNull: Boolean = false): T = - (all[key] as? T) ?: defaultVal.also { if (setIfNull) set(key, defaultVal) } + @Suppress("UNCHECKED_CAST") + inline operator fun get(key: String, defaultVal: T = defMap[T::class] as T): T = (all[key] as? T) ?: defaultVal fun remove(key: String) { edit().remove(key).apply() 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 dc11f95f..47314d30 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 @@ -22,9 +22,10 @@ import android.content.Context import android.content.ContextWrapper import android.util.Log import android.util.SparseArray -import kotlinx.coroutines.* -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString @@ -34,12 +35,12 @@ import xyz.quaver.Code import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.Reader import xyz.quaver.io.FileX -import xyz.quaver.io.util.* +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.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( @@ -53,25 +54,23 @@ data class Metadata( 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] ?: runBlocking { mutex.withLock { + instances[galleryID] ?: synchronized(this) { instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) } - } } + } - fun delete(galleryID: Int) { runBlocking { mutex.withLock { - instances[galleryID]?.galleryFolder?.deleteRecursively() + fun delete(galleryID: Int) { + instances[galleryID]?.cacheFolder?.deleteRecursively() instances.delete(galleryID) - } } } + } } init { - galleryFolder.mkdirs() + cacheFolder.mkdirs() } - private val mutex = Mutex() var metadata = kotlin.runCatching { findFile(".metadata")?.readText()?.let { Json.decodeFromString(it) @@ -82,11 +81,10 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) val cacheFolder: FileX - get() = FileX(this, cacheDir, "imageCache/$galleryID") - - val galleryFolder: FileX - get() = DownloadFolderManager.getInstance(this).getDownloadFolder(galleryID) - ?: FileX(this, cacheDir, "imageCache/$galleryID") + get() = FileX(this, cacheDir, "imageCache/$galleryID").also { + if (!it.exists()) + it.mkdirs() + } fun findFile(fileName: String): FileX? = cacheFolder.getChild(fileName).let { @@ -96,18 +94,19 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW } } @Suppress("BlockingMethodInNonBlockingContext") - suspend fun setMetadata(change: (Metadata) -> Unit) { mutex.withLock { + fun setMetadata(change: (Metadata) -> Unit) { change.invoke(metadata) - val file = galleryFolder.getChild(".metadata") + val file = cacheFolder.getChild(".metadata") - CoroutineScope(Dispatchers.IO).launch { - kotlin.runCatching { + kotlin.runCatching { + if (!file.exists()) { + Log.i("PUPILD", "$file") file.createNewFile() - file.writeText(Json.encodeToString(metadata)) } + file.writeText(Json.encodeToString(metadata)) } - } } + } suspend fun getGalleryBlock(): GalleryBlock? { val sources = listOf( @@ -129,7 +128,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW } galleryBlock?.also { - launch { setMetadata { metadata -> metadata.galleryBlock = it } } + setMetadata { metadata -> metadata.galleryBlock = it } } } } @@ -145,7 +144,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW kotlin.runCatching { client.newCall(request).execute().body()?.use { it.bytes() } }.getOrNull()?.also { kotlin.run { - galleryFolder.getChild(".thumbnail").writeBytes(it) + cacheFolder.getChild(".thumbnail").writeBytes(it) } } } } @@ -178,12 +177,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW } reader?.also { - launch { setMetadata { metadata -> + setMetadata { metadata -> metadata.reader = it if (metadata.imageList == null) metadata.imageList = MutableList(reader.galleryInfo.files.size) { null } - } } + } } } } @@ -192,8 +191,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW metadata.imageList?.get(index)?.let { findFile(it) } @Suppress("BlockingMethodInNonBlockingContext") - suspend fun putImage(index: Int, fileName: String, data: ByteArray) { - val file = galleryFolder.getChild(fileName) + fun putImage(index: Int, fileName: String, data: ByteArray) { + val file = cacheFolder.getChild(fileName) file.createNewFile() file.writeBytes(data) @@ -202,14 +201,12 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW @Suppress("BlockingMethodInNonBlockingContext") fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch { - if (downloadFolder == null) - DownloadFolderManager.getInstance(this@Cache).addDownloadFolder(galleryID, this@Cache.formatDownloadFolder()) + val downloadFolder = downloadFolder ?: return@launch + Log.i("PUPILD", "MOVING $galleryID") metadata.imageList?.forEach { imageName -> imageName ?: return@forEach - - Log.i("PUPIL", downloadFolder?.uri.toString()) - val target = downloadFolder!!.getChild(imageName) + val target = downloadFolder.getChild(imageName) val source = cacheFolder.getChild(imageName) if (!source.exists()) @@ -221,14 +218,13 @@ 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") + val downloadMetadata = downloadFolder.getChild(".metadata") if (cacheMetadata.exists()) { kotlin.runCatching { downloadMetadata.createNewFile() - cacheMetadata.readBytes()?.let { downloadMetadata.writeBytes(it) } + downloadMetadata.writeText(Json.encodeToString(metadata)) cacheMetadata.delete() } } 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 da275c25..65c7eba9 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 @@ -20,23 +20,17 @@ package xyz.quaver.pupil.util.downloader import android.content.Context import android.content.ContextWrapper -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.io.util.* import xyz.quaver.pupil.client import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.util.Preferences +import xyz.quaver.pupil.util.formatDownloadFolder class DownloadFolderManager private constructor(context: Context) : ContextWrapper(context) { @@ -61,15 +55,24 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp } }.invoke() - private val downloadFolderMapMutex = Mutex() - private val downloadFolderMap: MutableMap = runBlocking { downloadFolderMapMutex.withLock { - kotlin.runCatching { - downloadFolder.getChild(".download").readText()?.let { - Json.decodeFromString>(it) - } - }.getOrNull() ?: mutableMapOf() - } } + val downloadFolderMap: MutableMap = { + val file = downloadFolder.getChild(".download") + val data = if (file.exists()) + kotlin.runCatching { + file.readText()?.let { Json.decodeFromString>(it) } + }.onFailure { file.delete() }.getOrNull() + else + null + + data ?: { + file.createNewFile() + file.writeText("{}") + mutableMapOf() + }.invoke() + }.invoke() + + @Synchronized fun isDownloading(galleryID: Int): Boolean { val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID } @@ -77,13 +80,18 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp && client.dispatcher().let { it.queuedCalls().any(isThisGallery) || it.runningCalls().any(isThisGallery) } } - fun getDownloadFolder(galleryID: Int): FileX? = runBlocking { downloadFolderMapMutex.withLock { + @Synchronized + fun getDownloadFolder(galleryID: Int): FileX? = downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) } - } } - fun addDownloadFolder(galleryID: Int, name: String) { runBlocking { downloadFolderMapMutex.withLock { + @Synchronized + fun addDownloadFolder(galleryID: Int) { if (downloadFolderMap.containsKey(galleryID)) - return@withLock + return + + val name = runBlocking { + Cache.getInstance(this@DownloadFolderManager, galleryID).getGalleryBlock() + }?.formatDownloadFolder() ?: return val folder = downloadFolder.getChild(name) @@ -92,29 +100,21 @@ class DownloadFolderManager private constructor(context: Context) : ContextWrapp downloadFolderMap[galleryID] = name - CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { - downloadFolder.getChild(".download").let { - it.createNewFile() - it.writeText(Json.encodeToString(downloadFolderMap)) - } - } } - } } } + downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap)) + } - fun deleteDownloadFolder(galleryID: Int) { runBlocking { downloadFolderMapMutex.withLock { + @Synchronized + fun deleteDownloadFolder(galleryID: Int) { if (!downloadFolderMap.containsKey(galleryID)) - return@withLock + return downloadFolderMap[galleryID]?.let { - if (downloadFolder.getChild(it).delete()) { + kotlin.runCatching { + downloadFolder.getChild(it).delete() downloadFolderMap.remove(galleryID) - CoroutineScope(Dispatchers.IO).launch { downloadFolderMapMutex.withLock { - downloadFolder.getChild(".download").let { - it.createNewFile() - it.writeText(Json.encodeToString(downloadFolderMap)) - } - } } + downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap)) } } - } } } + } } \ 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 1ea3f8b3..cb625834 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.sync.withLock import okhttp3.OkHttpClient import okhttp3.Request import xyz.quaver.Code +import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.getReferer import xyz.quaver.hitomi.imageUrlFromImage @@ -87,15 +88,15 @@ 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() } }, +val formatMap = mapOf (String)>( + "-id-" to { id.toString() }, + "-title-" to { title }, // TODO ) /** * Formats download folder name with given Metadata */ -fun Cache.formatDownloadFolder(): String = +fun GalleryBlock.formatDownloadFolder(): String = Preferences["download_folder_format", "-id-"].let { formatMap.entries.fold(it) { str, (k, v) -> str.replace(k, v.invoke(this), true) @@ -131,4 +132,10 @@ val Reader.requestBuilders: List } } } - } \ No newline at end of file + } + +fun String.ellipsize(n: Int): String = + if (this.length > n) + this.slice(0 until n) + "…" + else + this \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_progressbar_cache.xml b/app/src/main/res/drawable/ic_progressbar_cache.xml new file mode 100644 index 00000000..7b4eebff --- /dev/null +++ b/app/src/main/res/drawable/ic_progressbar_cache.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_progressbar_complete_cache.xml b/app/src/main/res/drawable/ic_progressbar_complete_cache.xml new file mode 100644 index 00000000..416aad39 --- /dev/null +++ b/app/src/main/res/drawable/ic_progressbar_complete_cache.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 7d4c79b7..f7c2c182 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,6 @@ allprojects { repositories { google() jcenter() - mavenLocal() maven { url "https://jitpack.io" } maven { url 'https://guardian.github.com/maven/repo-releases' } }