working
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
} } }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user