diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 00000000..59037bbf --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 4222e57a..9a15bce4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -6,6 +6,7 @@ + diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 895a947e..4c701164 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -84,7 +84,6 @@ lateinit var webView: WebView val _webViewFlow = MutableSharedFlow>() val webViewFlow = _webViewFlow.asSharedFlow() var webViewReady = false -var webViewFailed = false var oldWebView = false private var reloadJob: Job? = null @@ -93,7 +92,6 @@ fun reloadWebView() { reloadJob = CoroutineScope(Dispatchers.IO).launch { webViewReady = false - webViewFailed = false oldWebView = false evaluationContext.cancelChildren(CancellationException("reload")) @@ -105,8 +103,6 @@ fun reloadWebView() { else "https://tom5079.github.io/PupilSources/hitomi.html" ).readText() - }.onFailure { - webViewFailed = true }.getOrNull()?.let { html -> launch(Dispatchers.Main) { webView.loadDataWithBaseURL( @@ -125,7 +121,7 @@ private var htmlVersion: String = "" fun reloadWhenFailedOrUpdate() = CoroutineScope(Dispatchers.Default).launch { while (true) { if ( - (webViewFailed && !oldWebView) || + (!webViewReady && !oldWebView) || runCatching { URL( if (BuildConfig.DEBUG) @@ -142,7 +138,7 @@ fun reloadWhenFailedOrUpdate() = CoroutineScope(Dispatchers.Default).launch { reloadWebView() } - delay(if (webViewReady && !webViewFailed) 10000 else 1000) + delay(if (webViewReady) 10000 else 1000) } } @@ -160,12 +156,11 @@ fun initWebView(context: Context) { webViewClient = object: WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { - webView.evaluateJavascript("self_test()") { + webView.evaluateJavascript("try { self_test() } catch (err) { 'err' }") { val result: String = Json.decodeFromString(it) oldWebView = result == "es2020_unsupported"; webViewReady = result == "OK"; - webViewFailed = result != "OK"; } } diff --git a/app/src/main/java/xyz/quaver/pupil/hitomi/common.kt b/app/src/main/java/xyz/quaver/pupil/hitomi/common.kt index f78007bb..0c1e79f9 100644 --- a/app/src/main/java/xyz/quaver/pupil/hitomi/common.kt +++ b/app/src/main/java/xyz/quaver/pupil/hitomi/common.kt @@ -48,12 +48,12 @@ val json = Json { useArrayPolymorphism = true } -suspend inline fun WebView.evaluate(script: String): T = coroutineScope { +suspend inline fun WebView.evaluate(script: String): T = coroutineScope { withTimeout(60000) { var result: String? = null while (result == null) { try { - while (!oldWebView && !(webViewReady && !webViewFailed)) delay(100) + while (!oldWebView && !webViewReady) delay(1000) result = if (oldWebView) "null" @@ -71,18 +71,18 @@ suspend inline fun WebView.evaluate(script: String): T = coroutineSc } json.decodeFromString(result) -} +} } @OptIn(ExperimentalCoroutinesApi::class) suspend inline fun WebView.evaluatePromise( script: String, then: String = ".then(result => Callback.onResult(%uid, JSON.stringify(result))).catch(err => Callback.onError(%uid, String.raw`$script`, err.message, err.stack))" -): T = coroutineScope { +): T = coroutineScope { withTimeout(60000) { var result: String? = null while (result == null) { try { - while (!oldWebView && !(webViewReady && !webViewFailed)) delay(100) + while (!oldWebView && !webViewReady) delay(1000) result = if (oldWebView) "null" @@ -108,7 +108,7 @@ suspend inline fun WebView.evaluatePromise( } json.decodeFromString(result) -} +} } @Suppress("EXPERIMENTAL_API_USAGE") suspend fun getGalleryInfo(galleryID: Int): GalleryInfo = 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 c0cad248..1263b54b 100644 --- a/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt +++ b/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt @@ -329,9 +329,8 @@ class DownloadService : Service() { } if (isCompleted(galleryID)) { - if (DownloadManager.getInstance(this@DownloadService) - .getDownloadFolder(galleryID) != null ) - Cache.getInstance(this@DownloadService, galleryID).moveToDownload() + DownloadManager.getInstance(this@DownloadService).addDownloadFolder(galleryID) + Cache.getInstance(this@DownloadService, galleryID).moveToDownload() notificationManager.cancel(galleryID) startId?.let { stopSelf(it) } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageStorageFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageStorageFragment.kt index fbaca241..d1a69da9 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageStorageFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageStorageFragment.kt @@ -18,22 +18,37 @@ package xyz.quaver.pupil.ui.fragment +import android.graphics.ColorFilter +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.os.Bundle +import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import androidx.swiperefreshlayout.widget.CircularProgressDrawable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import xyz.quaver.io.FileX +import xyz.quaver.io.SAFileX import xyz.quaver.io.util.deleteRecursively +import xyz.quaver.io.util.getChild +import xyz.quaver.io.util.readText +import xyz.quaver.io.util.writeText import xyz.quaver.pupil.R import xyz.quaver.pupil.histories import xyz.quaver.pupil.util.byteToString import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.DownloadManager +import xyz.quaver.pupil.util.downloader.Metadata import java.io.File +import kotlin.math.roundToInt class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener { @@ -80,6 +95,46 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc setNegativeButton(android.R.string.cancel) { _, _ -> } }.show() } + "recover_downloads" -> { + val density = context.resources.displayMetrics.density + this.icon = object: CircularProgressDrawable(context) { + override fun getIntrinsicHeight() = (24*density).roundToInt() + override fun getIntrinsicWidth() = (24*density).roundToInt() + }.apply { + setStyle(CircularProgressDrawable.DEFAULT) + colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorAccent), PorterDuff.Mode.SRC_IN) + start() + } + + val downloadManager = DownloadManager.getInstance(context) + + val downloadFolderMap = downloadManager.downloadFolderMap + + downloadFolderMap.clear() + + downloadManager.downloadFolder.listFiles { file -> file.isDirectory }?.forEach { folder -> + val metadataFile = FileX(context, folder, ".metadata") + + if (!metadataFile.exists()) return@forEach + + val metadata = metadataFile.readText()?.let { + Json.decodeFromString(it) + } ?: return@forEach + + val galleryID = metadata.galleryBlock?.id ?: metadata.galleryInfo?.id ?: return@forEach + + downloadFolderMap[galleryID] = folder.name + } + + downloadManager.downloadFolderMap.putAll(downloadFolderMap) + val downloads = FileX(context, downloadManager.downloadFolder, ".download") + + if (!downloads.exists()) downloads.createNewFile() + downloads.writeText(Json.encodeToString(downloadFolderMap)) + + this.icon = null + Toast.makeText(context, android.R.string.ok, Toast.LENGTH_SHORT).show() + } "delete_downloads" -> { val dir = DownloadManager.getInstance(context).downloadFolder @@ -191,6 +246,12 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc onPreferenceClickListener = this@ManageStorageFragment } + + with(findPreference("recover_downloads")) { + this ?: return@with + + onPreferenceClickListener = this@ManageStorageFragment + } } override fun onDestroy() { 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 72ff0ee8..6dac5f15 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 @@ -21,7 +21,6 @@ package xyz.quaver.pupil.util.downloader import android.content.Context import android.content.ContextWrapper import android.net.Uri -import android.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -40,7 +39,6 @@ import xyz.quaver.pupil.hitomi.GalleryBlock import xyz.quaver.pupil.hitomi.GalleryInfo import xyz.quaver.pupil.hitomi.getGalleryBlock import xyz.quaver.pupil.hitomi.getGalleryInfo -import xyz.quaver.pupil.userAgent import java.io.File import java.io.IOException import java.io.InputStream @@ -231,6 +229,9 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW return@launch (lock[galleryID] ?: Mutex().also { lock[galleryID] = it }).withLock { + if (downloadFolder.exists()) downloadFolder.deleteRecursively() + downloadFolder.mkdir() + val cacheMetadata = cacheFolder.getChild(".metadata") val downloadMetadata = downloadFolder.getChild(".metadata") diff --git a/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt b/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt index df559d66..ed8f088f 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt @@ -48,14 +48,12 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con val defaultDownloadFolder = FileX(this, getExternalFilesDir(null)!!) val downloadFolder: FileX - get() = { - kotlin.runCatching { - FileX(this, Preferences.get("download_folder")) - }.getOrElse { - Preferences["download_folder"] = defaultDownloadFolder.uri.toString() - defaultDownloadFolder - } - }.invoke() + get() = kotlin.runCatching { + FileX(this, Preferences.get("download_folder")) + }.getOrElse { + Preferences["download_folder"] = defaultDownloadFolder.uri.toString() + defaultDownloadFolder + } private var prevDownloadFolder: FileX? = null private var downloadFolderMapInstance: MutableMap? = null @@ -64,21 +62,19 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con get() { if (prevDownloadFolder != downloadFolder) { prevDownloadFolder = downloadFolder - downloadFolderMapInstance = { + downloadFolderMapInstance = run { val file = downloadFolder.getChild(".download") - val data = if (file.exists()) kotlin.runCatching { - file.readText()?.let { Json.decodeFromString>(it) } + file.readText()?.let{ Json.decodeFromString>(it) } }.onFailure { file.delete() }.getOrNull() else null - - data ?: { + data ?: run { file.createNewFile() - mutableMapOf() - }.invoke() - }.invoke() + mutableMapOf() + } + } } return downloadFolderMapInstance ?: mutableMapOf() @@ -103,14 +99,13 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con val folder = downloadFolder.getChild(name) - if (folder.exists()) return@launch - - folder.mkdir() - - downloadFolderMap[galleryID] = folder.name + downloadFolderMap[galleryID] = name downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile() } downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap)) + + if (folder.exists()) return@launch + folder.mkdir() } @Synchronized diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index db2ebac9..8008573f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -158,4 +158,5 @@ アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか? ネットワーク WebViewのアップデートが必要です + ダウンロードデータベースを再構築 \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 3a00fd9a..9d4ebb0c 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -158,4 +158,5 @@ 안드로이드 11 이상에서는 외부에서 현재 다운로드 폴더에 접근할 수 없습니다. 변경하시겠습니까? 네트워크 WebView 업데이트가 필요합니다 + 다운로드 데이터베이스 복구 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af69a1db..8fa0a73d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -152,6 +152,7 @@ Calculating storage usage… Clear cache Deleting cache can affect image loading speed. Do you want to continue? + Reconstruct download database Clear downloads Delete all downloaded galleries.\nDo you want to continue? Clear history diff --git a/app/src/main/res/xml/manage_storage_preferences.xml b/app/src/main/res/xml/manage_storage_preferences.xml index f6413aaa..45c2feda 100644 --- a/app/src/main/res/xml/manage_storage_preferences.xml +++ b/app/src/main/res/xml/manage_storage_preferences.xml @@ -27,6 +27,11 @@ app:key="delete_downloads" app:title="@string/settings_clear_downloads"/> + +