mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-17 06:02:28 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9670d8a12 | ||
|
|
87e808548b | ||
|
|
51b61a36e8 | ||
|
|
010ac71a84 | ||
|
|
9dbb7148c5 | ||
|
|
a999e49f2f | ||
|
|
ae749bb4f8 | ||
|
|
649fc83ffa | ||
|
|
6dffca1593 | ||
|
|
07b9fb695c | ||
|
|
c279291e29 | ||
|
|
abb126ad20 | ||
|
|
8457fb91e0 | ||
|
|
143a060ac9 | ||
|
|
8912ef5b5c |
@@ -30,7 +30,7 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.21.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.21.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
|
||||
@@ -52,7 +52,7 @@ body:
|
||||
label: Mihon version
|
||||
description: You can find your Mihon version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.21.0"
|
||||
Example: "0.21.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -95,7 +95,7 @@ body:
|
||||
required: true
|
||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.21.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.21.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I have filled out all of the requested information in this form, including specific version numbers.
|
||||
required: true
|
||||
|
||||
+16
-1
@@ -12,6 +12,20 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.21.1] - 2026-10-12
|
||||
### Fixed
|
||||
- Fixed refreshing chapters failing with the app language set to Arabic or another language with its own digits ([@AntsyLich](https://github.com/AntsyLich)) ([`8457fb9`](https://github.com/mihonapp/mihon/commit/8457fb9))
|
||||
- Fixed a crash on launch on some Samsung devices running Android 9 to 11 ([@AntsyLich](https://github.com/AntsyLich)) ([`abb126a`](https://github.com/mihonapp/mihon/commit/abb126a))
|
||||
- Fixed the start and resume button going under the taskbar on tablets ([@AntsyLich](https://github.com/AntsyLich)) ([`c279291`](https://github.com/mihonapp/mihon/commit/c279291))
|
||||
- Fixed a crash when updating an extension while others were installing ([@AntsyLich](https://github.com/AntsyLich)) ([`07b9fb6`](https://github.com/mihonapp/mihon/commit/07b9fb6))
|
||||
- Fixed library entries without a date added getting their last update time as date added when updating from v0.20.x ([@AntsyLich](https://github.com/AntsyLich)) ([`6dffca1`](https://github.com/mihonapp/mihon/commit/6dffca1))
|
||||
- Fixed the app closing when WebView's page process crashed ([@AntsyLich](https://github.com/AntsyLich)) ([`649fc83`](https://github.com/mihonapp/mihon/commit/649fc83))
|
||||
- Fixed installing or updating an extension sometimes cancelling the install of another one ([@AntsyLich](https://github.com/AntsyLich)) ([`ae749bb`](https://github.com/mihonapp/mihon/commit/ae749bb))
|
||||
- Fixed updating from v0.20.x taking up to hours on libraries with many duplicate entries ([@AntsyLich](https://github.com/AntsyLich)) ([`a999e49`](https://github.com/mihonapp/mihon/commit/a999e49))
|
||||
- Fixed the app staying on the updating screen for good when updating its data failed, it now shows what went wrong, and updating failing on leftover data of deleted entries ([@AntsyLich](https://github.com/AntsyLich)) ([`9dbb714`](https://github.com/mihonapp/mihon/commit/9dbb714))
|
||||
- Fixed library updates ignoring the smart update restrictions for entries whose details were never fetched ([@AntsyLich](https://github.com/AntsyLich)) ([`010ac71`](https://github.com/mihonapp/mihon/commit/010ac71))
|
||||
- Fixed resuming a chapter sometimes opening the next chapter partway through, and saving progress to it, in the right to left reader ([@AntsyLich](https://github.com/AntsyLich)) ([`51b61a3`](https://github.com/mihonapp/mihon/commit/51b61a3))
|
||||
|
||||
## [v0.21.0] - 2026-10-12
|
||||
### Added
|
||||
- Add `id:` prefix search to remaining trackers (AniList, Bangumi, Kitsu, MangaUpdates, Shikimori, and Hikka) ([@MajorTanya](https://github.com/MajorTanya), [@BrutuZ](https://github.com/BrutuZ)) ([#3776](https://github.com/mihonapp/mihon/pull/3776))
|
||||
@@ -758,7 +772,8 @@ Same as v0.19.6
|
||||
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
|
||||
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
|
||||
|
||||
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.21.0...main
|
||||
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.21.1...main
|
||||
[v0.21.1]: https://github.com/mihonapp/mihon/compare/v0.21.0...v0.21.1
|
||||
[v0.21.0]: https://github.com/mihonapp/mihon/compare/v0.20.4...v0.21.0
|
||||
[v0.20.4]: https://github.com/mihonapp/mihon/compare/v0.20.3...v0.20.4
|
||||
[v0.20.3]: https://github.com/mihonapp/mihon/compare/v0.20.2...v0.20.3
|
||||
|
||||
@@ -37,8 +37,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "app.mihon"
|
||||
|
||||
versionCode = 36
|
||||
versionName = "0.21.0"
|
||||
versionCode = 37
|
||||
versionName = "0.21.1"
|
||||
|
||||
buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}")
|
||||
buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}")
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -535,20 +536,20 @@ fun MangaScreenLargeImpl(
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
) {
|
||||
SharedMangaBottomActionMenu(
|
||||
selected = state.selectedChapters,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
||||
fillFraction = 0.5f,
|
||||
)
|
||||
}
|
||||
// Aligned by its own modifier: a wrapper would always be laid out, and the scaffold then drops the
|
||||
// navigation bar inset under the FAB
|
||||
SharedMangaBottomActionMenu(
|
||||
selected = state.selectedChapters,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
||||
fillFraction = 0.5f,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.End),
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.graphics.Bitmap
|
||||
import android.os.Message
|
||||
import android.webkit.JsPromptResult
|
||||
import android.webkit.JsResult
|
||||
import android.webkit.RenderProcessGoneDetail
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -45,12 +47,15 @@ import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.util.system.getHtml
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.setUserAgent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import mihon.icons.materialsymbols.MaterialSymbols
|
||||
import mihon.icons.materialsymbols.automirroredrounded.ArrowBack
|
||||
import mihon.icons.materialsymbols.automirroredrounded.ArrowForward
|
||||
import mihon.icons.materialsymbols.rounded.Close
|
||||
import mihon.icons.materialsymbols.rounded.TabClose
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@@ -152,6 +157,17 @@ fun WebViewScreenContent(
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
|
||||
// Not handling it kills the app along with the page
|
||||
logcat(LogPriority.ERROR) {
|
||||
"WebView renderer gone: crashed=${detail.didCrash()}, " +
|
||||
"priority=${detail.rendererPriorityAtExit()}, url=$currentUrl"
|
||||
}
|
||||
view.context.toast(MR.strings.information_webview_crashed, Toast.LENGTH_LONG)
|
||||
onNavigateUp()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,9 +192,6 @@ class LibraryUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
mangaToUpdate = listToUpdate
|
||||
.filter {
|
||||
when {
|
||||
// Entries added without opening them have no details yet
|
||||
!it.manga.initialized -> true
|
||||
|
||||
it.manga.updateStrategy == UpdateStrategy.ONLY_FETCH_ONCE && it.totalChapters > 0L -> {
|
||||
skippedUpdates.add(
|
||||
it.manga to context.stringResource(MR.strings.skipped_reason_not_always_update),
|
||||
@@ -357,7 +354,7 @@ class LibraryUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
val update = updateMangaFromRemote(
|
||||
source = source,
|
||||
manga = manga,
|
||||
fetchDetails = !manga.initialized || libraryPreferences.autoUpdateMetadata.get(),
|
||||
fetchDetails = libraryPreferences.autoUpdateMetadata.get(),
|
||||
fetchChapters = true,
|
||||
fetchWindow = fetchWindow,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,8 @@ abstract class Installer(private val service: Service) {
|
||||
private val extensionManager: ExtensionManager by lazy { service.appGraph.extensionManager }
|
||||
|
||||
private var waitingInstall = AtomicReference<Entry?>(null)
|
||||
|
||||
// Guarded by itself: cancellation comes from any thread, not only main
|
||||
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
||||
|
||||
private val cancelListener: (Long) -> Unit = { downloadId -> cancelQueue(downloadId) }
|
||||
@@ -91,15 +93,15 @@ abstract class Installer(private val service: Service) {
|
||||
if (!ready) {
|
||||
return
|
||||
}
|
||||
if (queue.isEmpty()) {
|
||||
service.stopSelf()
|
||||
return
|
||||
}
|
||||
val nextEntry = queue.first()
|
||||
if (waitingInstall.compareAndSet(null, nextEntry)) {
|
||||
val nextEntry = synchronized(queue) {
|
||||
if (queue.isEmpty()) {
|
||||
service.stopSelf()
|
||||
return
|
||||
}
|
||||
if (!waitingInstall.compareAndSet(null, queue.first())) return
|
||||
queue.removeAt(0)
|
||||
processEntry(nextEntry)
|
||||
}
|
||||
processEntry(nextEntry)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,11 +110,11 @@ abstract class Installer(private val service: Service) {
|
||||
@CallSuper
|
||||
open fun onDestroy() {
|
||||
cancelListeners -= cancelListener
|
||||
queue.forEach {
|
||||
val remaining = synchronized(queue) { queue.toList().also { queue.clear() } }
|
||||
remaining.forEach {
|
||||
logcat(LogPriority.INFO) { "The installer stopped before installing ${it.downloadId}" }
|
||||
extensionManager.updateInstallStep(it.downloadId, InstallStep.Error)
|
||||
}
|
||||
queue.clear()
|
||||
waitingInstall.store(null)
|
||||
}
|
||||
|
||||
@@ -125,7 +127,10 @@ abstract class Installer(private val service: Service) {
|
||||
*/
|
||||
private fun cancelQueue(downloadId: Long) {
|
||||
val waitingInstall = this.waitingInstall.load()
|
||||
val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
|
||||
// Every new install cancels its own previous attempt first, which mustn't hit another extension's install
|
||||
val toCancel = synchronized(queue) { queue.find { it.downloadId == downloadId } }
|
||||
?: waitingInstall?.takeIf { it.downloadId == downloadId }
|
||||
?: return
|
||||
if (cancelEntry(toCancel)) {
|
||||
queue.remove(toCancel)
|
||||
if (waitingInstall == toCancel) {
|
||||
|
||||
@@ -179,6 +179,9 @@ class MainActivity : BaseActivity() {
|
||||
Migrator.awaitAndRelease()
|
||||
isAppMigrating.value = false
|
||||
}
|
||||
// Uncaught on purpose: a failed database migration otherwise leaves the migrating screen up for good,
|
||||
// while the crash screen tells what went wrong and lets the logs be shared
|
||||
lifecycleScope.launch { databaseRepository.awaitOpen() }
|
||||
|
||||
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
||||
if (!isTaskRoot) {
|
||||
@@ -337,6 +340,16 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun reportFullyDrawn() {
|
||||
try {
|
||||
super.reportFullyDrawn()
|
||||
} catch (e: SecurityException) {
|
||||
// Some Samsung Android 9 to 11 builds deny the platform's fully drawn broadcast
|
||||
// and the report only feeds startup metrics, so it shouldn't crash the app
|
||||
logcat(LogPriority.WARN, e)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HandleOnNewIntent(context: Context, navigator: Navigator) {
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
@@ -96,6 +96,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
|
||||
chapters.nextChapter?.pages?.let(newItems::addAll)
|
||||
|
||||
val currentItem = items.getOrNull(viewer.pager.currentItem)
|
||||
|
||||
// Resets double-page splits, else insert pages get misplaced
|
||||
items.filterIsInstance<InsertPage>().also { items.removeAll(it) }
|
||||
|
||||
@@ -107,6 +109,14 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
items = newItems
|
||||
notifyDataSetChanged()
|
||||
|
||||
// The pager only follows the pages it has created, so before its first layout it would stay at the same
|
||||
// position while pages are added in front, e.g. the next chapter's in right to left, and land in another
|
||||
// chapter
|
||||
val currentPosition = items.indexOf(currentItem)
|
||||
if (currentPosition != -1 && currentPosition != viewer.pager.currentItem) {
|
||||
viewer.pager.setCurrentItem(currentPosition, false)
|
||||
}
|
||||
|
||||
// Will skip insert page otherwise
|
||||
if (insertPageLastPage != null) {
|
||||
viewer.moveToPage(insertPageLastPage)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package mihon.core.migration
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
|
||||
class MigrationJobFactory(
|
||||
@@ -20,7 +22,18 @@ class MigrationJobFactory(
|
||||
}
|
||||
async {
|
||||
val prev = acc.await()
|
||||
migration(migrationContext) || prev
|
||||
// A failure would cancel the rest of the chain and leave the app waiting on it for good
|
||||
val ran = try {
|
||||
migration(migrationContext)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) {
|
||||
"Migration failed: { name = ${migration::class.simpleName}, version = ${migration.version} }"
|
||||
}
|
||||
false
|
||||
}
|
||||
ran || prev
|
||||
}
|
||||
} else {
|
||||
logcat {
|
||||
|
||||
@@ -69,7 +69,7 @@ class UpdateMangaFromRemote(
|
||||
fetchDetails = fetchDetails,
|
||||
fetchChapters = fetchChapters,
|
||||
)
|
||||
awaitUpdateFromSource(manga, update.manga, fetchDetails, manualFetch)
|
||||
awaitUpdateFromSource(manga, update.manga, manualFetch)
|
||||
val newChapters = syncChaptersWithSource.await(
|
||||
rawSourceChapters = update.chapters,
|
||||
manga = manga,
|
||||
@@ -89,7 +89,6 @@ class UpdateMangaFromRemote(
|
||||
private suspend fun awaitUpdateFromSource(
|
||||
localManga: Manga,
|
||||
remoteManga: SManga,
|
||||
fetchDetails: Boolean,
|
||||
manualFetch: Boolean,
|
||||
): Boolean {
|
||||
val remoteTitle = try {
|
||||
@@ -135,7 +134,7 @@ class UpdateMangaFromRemote(
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
status = remoteManga.status.toLong(),
|
||||
updateStrategy = remoteManga.update_strategy,
|
||||
initialized = localManga.initialized || fetchDetails,
|
||||
initialized = true,
|
||||
memo = remoteManga.memo,
|
||||
),
|
||||
)
|
||||
|
||||
+18
-2
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network.interceptor
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.RenderProcessGoneDetail
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebView
|
||||
@@ -13,12 +14,14 @@ import eu.kanade.tachiyomi.network.AndroidCookieJar
|
||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import logcat.LogPriority
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.i18n.MR
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@@ -73,6 +76,7 @@ class CloudflareInterceptor(
|
||||
var challengeFound = false
|
||||
var cloudflareBypassed = false
|
||||
var isWebViewOutdated = false
|
||||
var isRenderProcessGone = false
|
||||
|
||||
val origRequestUrl = originalRequest.url.toString()
|
||||
val headers = parseHeaders(originalRequest.headers)
|
||||
@@ -140,6 +144,17 @@ class CloudflareInterceptor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
|
||||
// Not handling it kills the app, failing the bypass is enough
|
||||
logcat(LogPriority.ERROR) {
|
||||
"WebView renderer gone during Cloudflare bypass: crashed=${detail.didCrash()}, " +
|
||||
"priority=${detail.rendererPriorityAtExit()}, url=$origRequestUrl"
|
||||
}
|
||||
isRenderProcessGone = true
|
||||
latch.countDown()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
webview.loadUrl(origRequestUrl, headers)
|
||||
@@ -148,12 +163,13 @@ class CloudflareInterceptor(
|
||||
latch.awaitFor30Seconds()
|
||||
|
||||
executor.execute {
|
||||
if (!cloudflareBypassed) {
|
||||
// Only destroy can be called on a WebView whose renderer is gone
|
||||
if (!cloudflareBypassed && !isRenderProcessGone) {
|
||||
isWebViewOutdated = webview?.isOutdated() == true
|
||||
}
|
||||
|
||||
webview?.run {
|
||||
stopLoading()
|
||||
if (!isRenderProcessGone) stopLoading()
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
package mihon.data.database
|
||||
|
||||
import app.cash.sqldelight.db.QueryResult
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import mihon.domain.database.repository.DatabaseRepository
|
||||
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DatabaseRepositoryImpl : DatabaseRepository {
|
||||
class DatabaseRepositoryImpl(
|
||||
// Lazy since the driver reports its migration state here
|
||||
private val driver: Lazy<SqlDriver>,
|
||||
) : DatabaseRepository {
|
||||
|
||||
override val isMigrating = MutableStateFlow(false)
|
||||
|
||||
override suspend fun awaitOpen() {
|
||||
withContext(Dispatchers.IO) {
|
||||
driver.value.executeQuery(null, "SELECT 1", { QueryResult.Value(Unit) }, 0).await()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ DROP INDEX library_favorite_index;
|
||||
|
||||
ALTER TABLE mangas ADD COLUMN favorite_at INTEGER;
|
||||
|
||||
-- favorite_modified_at is no fallback for a missing date_added: every manga update rewrote it
|
||||
UPDATE mangas
|
||||
SET favorite_at = coalesce(nullif(date_added, 0), favorite_modified_at * 1000, 0)
|
||||
SET favorite_at = coalesce(date_added, 0)
|
||||
WHERE favorite = 1;
|
||||
|
||||
ALTER TABLE mangas DROP COLUMN favorite;
|
||||
|
||||
@@ -4,6 +4,19 @@ import kotlin.String;
|
||||
import kotlin.collections.List;
|
||||
import kotlinx.serialization.json.JsonObject;
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------------------
|
||||
-- orphans
|
||||
-- ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- Rows pointing at an entry, chapter or category that no longer exists were written while foreign keys weren't
|
||||
-- enforced. Nothing can reach them, and copied over they'd fail the foreign key check that ends the migration.
|
||||
DELETE FROM chapters WHERE manga_id NOT IN (SELECT _id FROM mangas);
|
||||
DELETE FROM history WHERE chapter_id NOT IN (SELECT _id FROM chapters);
|
||||
DELETE FROM mangas_categories
|
||||
WHERE manga_id NOT IN (SELECT _id FROM mangas) OR category_id NOT IN (SELECT _id FROM categories);
|
||||
DELETE FROM manga_sync WHERE manga_id NOT IN (SELECT _id FROM mangas);
|
||||
DELETE FROM excluded_scanlators WHERE manga_id NOT IN (SELECT _id FROM mangas);
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------------------
|
||||
-- table - manga
|
||||
-- ---------------------------------------------------------------------------------------------------------
|
||||
@@ -31,6 +44,9 @@ WHERE (source, url) IN (
|
||||
HAVING count(*) > 1
|
||||
);
|
||||
|
||||
-- Moving the chapters looks each one's entry up here, which without an index scans the whole table per chapter
|
||||
CREATE INDEX duplicate_mangas_id_index ON duplicate_mangas(_id);
|
||||
|
||||
UPDATE mangas
|
||||
SET favorite_at = merged.favorite_at,
|
||||
initialized = merged.initialized,
|
||||
|
||||
@@ -9,4 +9,9 @@ interface DatabaseRepository {
|
||||
* app update.
|
||||
*/
|
||||
val isMigrating: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Opens the database, migrating it if needed, and throws what made the migration fail.
|
||||
*/
|
||||
suspend fun awaitOpen()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package tachiyomi.domain.chapter.service
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Locale
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.round
|
||||
|
||||
@@ -77,9 +78,9 @@ object ChapterRecognition {
|
||||
private val prologue = Regex("""\b(?:prologue|one.?shot|pilot)\b""")
|
||||
|
||||
/**
|
||||
* Formats numbers without trailing zeros and with a dot whatever the locale, e.g. 10.5, 3, 24.005
|
||||
* Formats numbers without trailing zeros, with ASCII digits and a dot whatever the locale, e.g. 10.5, 3, 24.005
|
||||
*/
|
||||
private val formatter = DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
|
||||
private val formatter = DecimalFormat("#.###", DecimalFormatSymbols(Locale.ROOT))
|
||||
|
||||
/**
|
||||
* The volume and chapter number recognized for a chapter, e.g. "10.5" or "19a", `null` when there is none.
|
||||
|
||||
@@ -994,6 +994,8 @@
|
||||
<string name="information_webview_required">WebView is required for the app to function</string>
|
||||
<!-- Do not translate "WebView" -->
|
||||
<string name="information_webview_outdated">Please update the WebView app for better compatibility</string>
|
||||
<!-- Do not translate "WebView" -->
|
||||
<string name="information_webview_crashed">WebView stopped working and the page was closed</string>
|
||||
<string name="chapter_settings_updated">Updated default chapter settings</string>
|
||||
|
||||
<!-- Download Notification -->
|
||||
|
||||
Reference in New Issue
Block a user