This commit is contained in:
tom5079
2020-09-02 00:13:25 +09:00
parent 7704c96955
commit 9583897ada
16 changed files with 237 additions and 140 deletions

View File

@@ -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'

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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

View File

@@ -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<Int>()
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) }
}

View File

@@ -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 {

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -209,6 +209,9 @@ class SettingsFragment :
"proxy" -> {
summary = context?.let { getProxyInfo().type.name }
}
"download_folder" -> {
summary = FileX(context, Preferences.get<String>("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<String>("download_folder")).canonicalPath
onPreferenceClickListener = this@SettingsFragment
}

View File

@@ -25,15 +25,6 @@ lateinit var preferences: SharedPreferences
object Preferences: SharedPreferences by preferences {
@Suppress("UNCHECKED_CAST")
val putMap = mapOf<KClass<out Any>, (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<String>).apply() }
)
val defMap = mapOf(
String::class to "",
Int::class to -1,
@@ -42,12 +33,14 @@ object Preferences: SharedPreferences by preferences {
Set::class to emptySet<Any>()
)
inline operator fun <reified T: Any> 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<String>) = edit().putStringSet(key, value).apply()
inline operator fun <reified T: Any> 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 <reified T: Any> get(key: String, defaultVal: T = defMap[T::class] as T): T = (all[key] as? T) ?: defaultVal
fun remove(key: String) {
edit().remove(key).apply()

View File

@@ -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<Cache>()
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<Metadata>(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()
}
}

View File

@@ -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<Int, String> = runBlocking { downloadFolderMapMutex.withLock {
kotlin.runCatching {
downloadFolder.getChild(".download").readText()?.let {
Json.decodeFromString<MutableMap<Int, String>>(it)
}
}.getOrNull() ?: mutableMapOf()
} }
val downloadFolderMap: MutableMap<Int, String> = {
val file = downloadFolder.getChild(".download")
val data = if (file.exists())
kotlin.runCatching {
file.readText()?.let { Json.decodeFromString<MutableMap<Int, String>>(it) }
}.onFailure { file.delete() }.getOrNull()
else
null
data ?: {
file.createNewFile()
file.writeText("{}")
mutableMapOf<Int, String>()
}.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))
}
}
} } }
}
}

View File

@@ -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, (Cache) -> (String)>(
"-id-" to { runBlocking { it.getGalleryBlock()?.id.toString() } },
"-title-" to { runBlocking { it.getGalleryBlock()?.title.toString() } },
val formatMap = mapOf<String, GalleryBlock.() -> (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<Request.Builder>
}
}
}
}
}
fun String.ellipsize(n: Int): String =
if (this.length > n)
this.slice(0 until n) + ""
else
this

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tintMode="multiply">
<path
android:name="path_1"
android:pathData="M 0 12 L 24 12"
android:fillColor="#000"
android:strokeColor="#80d8ff"
android:strokeWidth="24"/>
<path
android:name="path"
android:pathData="M 0 12 L 24 12"
android:fillColor="#000"
android:strokeColor="#0091ea"
android:strokeWidth="24"/>
</vector>

View File

@@ -0,0 +1,38 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:aapt="http://schemas.android.com/aapt"
tools:ignore="NewApi">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tintMode="multiply">
<path
android:name="path_1"
android:pathData="M 0 12 L 24 12"
android:fillColor="#000"
android:strokeColor="#80d8ff"
android:strokeWidth="24"/>
<path
android:name="path"
android:pathData="M 0 12 L 24 12"
android:fillColor="#000"
android:strokeColor="#0091ea"
android:strokeWidth="24"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="trimPathEnd"
android:duration="1000"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>