Compare commits
2 commits
v1.13.6-fo
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7afd275eef | ||
|
|
354877c6c1 |
6 changed files with 49 additions and 63 deletions
|
|
@ -413,33 +413,6 @@ jobs:
|
|||
sha256sum "${signed_apk}"
|
||||
done
|
||||
|
||||
- name: Create Obtainium-friendly aliases
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
alias_asset() {
|
||||
src="$1"
|
||||
dst="$2"
|
||||
if [ -e "$src" ]; then
|
||||
cp -f "$src" "$dst"
|
||||
fi
|
||||
}
|
||||
|
||||
# Only publish signed APK aliases.
|
||||
alias_asset "$(ls -1t Komikku-arm64-v8a-*.apk 2>/dev/null | head -n1)" "Komikku-arm64-v8a.apk"
|
||||
|
||||
for candidate in Komikku-*.apk; do
|
||||
case "$candidate" in
|
||||
*arm64-v8a*|*armeabi-v7a*|*x86_64*|*x86*|*-unsigned.apk)
|
||||
;;
|
||||
*)
|
||||
alias_asset "$candidate" "Komikku-universal.apk"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
- name: Create or update release
|
||||
id: release
|
||||
shell: bash
|
||||
|
|
@ -561,7 +534,7 @@ jobs:
|
|||
"<!-->" \
|
||||
"> [!TIP]" \
|
||||
">" \
|
||||
"> ### If you are unsure which version to download then go with \`Komikku-universal.apk\`"
|
||||
"> ### If you are unsure which version to download then go with \`Komikku-${VERSION_TAG}.apk\`"
|
||||
} > "${release_body_file}"
|
||||
|
||||
# Build JSON — body from file only (jq --arg body "$huge" hits ARG_MAX).
|
||||
|
|
|
|||
|
|
@ -468,11 +468,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
newUpdates.add(manga to newChapters.toTypedArray())
|
||||
}
|
||||
|
||||
val libraryUpdateChapters = getChaptersForLibraryUpdateDownload.await(manga)
|
||||
if (libraryUpdateChapters.isNotEmpty()) {
|
||||
downloadChapters(manga, libraryUpdateChapters)
|
||||
hasDownloads.store(true)
|
||||
}
|
||||
clearErrorFromDB(mangaId = manga.id)
|
||||
} catch (e: Throwable) {
|
||||
val errorMessage = when (e) {
|
||||
|
|
@ -489,6 +484,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
writeErrorToDB(manga to errorMessage)
|
||||
failedUpdates.add(manga to errorMessage)
|
||||
}
|
||||
|
||||
try {
|
||||
val libraryUpdateChapters = getChaptersForLibraryUpdateDownload.await(manga)
|
||||
if (libraryUpdateChapters.isNotEmpty()) {
|
||||
downloadChapters(manga, libraryUpdateChapters)
|
||||
hasDownloads.store(true)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (e is CancellationException) throw e
|
||||
xLogE("Failed to queue library update downloads for ${manga.title}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,10 +98,10 @@ import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId
|
|||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.history.interactor.GetNextChapters
|
||||
import tachiyomi.domain.chapter.service.getChapterSort
|
||||
import tachiyomi.domain.history.interactor.GetCrossMangaPageRateHistorySamples
|
||||
import tachiyomi.domain.history.interactor.GetHistoryRevision
|
||||
import tachiyomi.domain.history.interactor.GetNextChapters
|
||||
import tachiyomi.domain.history.interactor.GetReadDurationEntriesByMangaIds
|
||||
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
|
@ -110,9 +110,6 @@ import tachiyomi.domain.library.model.LibraryManga
|
|||
import tachiyomi.domain.library.model.LibrarySort
|
||||
import tachiyomi.domain.library.model.sort
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.reading.ReadingEtaLimits
|
||||
import tachiyomi.domain.reading.SeriesReadingEtaCalculator
|
||||
import tachiyomi.domain.reading.service.ReadingEtaPreferences
|
||||
import tachiyomi.domain.manga.interactor.GetIdsOfFavoriteMangaWithMetadata
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetMergedMangaById
|
||||
|
|
@ -123,6 +120,9 @@ import tachiyomi.domain.manga.model.CustomMangaInfo
|
|||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.model.applyFilter
|
||||
import tachiyomi.domain.reading.ReadingEtaLimits
|
||||
import tachiyomi.domain.reading.SeriesReadingEtaCalculator
|
||||
import tachiyomi.domain.reading.service.ReadingEtaPreferences
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
|
|
@ -1028,7 +1028,7 @@ class LibraryScreenModel(
|
|||
if (manga.source == MERGED_SOURCE_ID) {
|
||||
val mergedMangas = getMergedMangaById.await(manga.id)
|
||||
.associateBy { it.id }
|
||||
getNextChapters.await(manga.id)
|
||||
getNextChapters.awaitFromLastRead(manga.id)
|
||||
.let { if (amount != null) it.take(amount) else it }
|
||||
.groupBy { it.mangaId }
|
||||
.forEach ab@{ (mangaId, chapters) ->
|
||||
|
|
@ -1051,7 +1051,7 @@ class LibraryScreenModel(
|
|||
}
|
||||
|
||||
// SY <--
|
||||
val chapters = getNextChapters.await(manga.id)
|
||||
val chapters = getNextChapters.awaitFromLastRead(manga.id)
|
||||
.fastFilterNot { chapter ->
|
||||
downloadManager.getQueuedDownloadOrNull(chapter.id) != null ||
|
||||
downloadManager.isChapterDownloaded(
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ Forgejo Actions workflow: [`.forgejo/workflows/build_release.yml`](../.forgejo/w
|
|||
- Builds release APKs (`assembleRelease -Penable-updater -Pdisable-code-shrink`, no telemetry)
|
||||
- Signs APKs with your release keystore
|
||||
- Creates or updates a Forgejo release and uploads APK assets
|
||||
- Publishes stable Obtainium-friendly asset names alongside versioned files
|
||||
|
||||
The workflow uses shell + Forgejo API calls only (no GitHub-only release actions), so it runs on self-hosted Forgejo runners.
|
||||
|
||||
## Cut a release
|
||||
|
|
@ -90,7 +88,7 @@ export KOMIKKU_GRADLE_CACHE="$HOME/.cache/komikku-ci-gradle"
|
|||
|
||||
## Release assets
|
||||
|
||||
Versioned (one set per tag):
|
||||
One APK per architecture per tag:
|
||||
|
||||
- `Komikku-v1.13.6.apk` — universal
|
||||
- `Komikku-arm64-v8a-v1.13.6.apk`
|
||||
|
|
@ -99,11 +97,6 @@ Versioned (one set per tag):
|
|||
- `Komikku-x86_64-v1.13.6.apk`
|
||||
- `Komikku-mapping-v1.13.6.zip` — ProGuard mapping (when present)
|
||||
|
||||
Stable aliases (same filename every release; best for Obtainium):
|
||||
|
||||
- `Komikku-universal.apk`
|
||||
- `Komikku-arm64-v8a.apk`
|
||||
|
||||
## Obtainium
|
||||
|
||||
Obtainium can install directly from Forgejo release assets.
|
||||
|
|
@ -112,14 +105,14 @@ Obtainium can install directly from Forgejo release assets.
|
|||
2. **Add app** → **Override source**: **Forgejo** (required for self-hosted instances).
|
||||
3. Repository URL, e.g. `https://forgejo.example/youruser/komikku` (no `/releases` suffix).
|
||||
4. Enable **Allow insecure HTTP** or disable SSL verification if Forgejo uses a self-signed certificate.
|
||||
5. **APK filter** (regex): `Komikku-arm64-v8a\.apk` (or `Komikku-universal\.apk`).
|
||||
5. **APK filter** (regex): `Komikku-arm64-v8a-v.*\.apk` (or `Komikku-v.*\.apk` for universal).
|
||||
6. **Trim version string (regex):** `^v?([0-9]+\.[0-9]+\.[0-9]+)` — needed for fork tags like `v1.13.6-forgejo.39`.
|
||||
7. Wait until the Forgejo release workflow has finished before adding or updating the app.
|
||||
|
||||
Direct download URL pattern (for reference):
|
||||
|
||||
```text
|
||||
https://forgejo.example/<owner>/<repo>/releases/download/<tag>/Komikku-arm64-v8a.apk
|
||||
https://forgejo.example/<owner>/<repo>/releases/download/<tag>/Komikku-arm64-v8a-v1.13.6.apk
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class GetChaptersForLibraryUpdateDownload(
|
|||
val mode = downloadPreferences.libraryUpdateDownloadMode().get()
|
||||
if (mode == LibraryUpdateDownloadMode.DISABLED) return emptyList()
|
||||
|
||||
val chapters = getNextChapters.await(manga.id)
|
||||
val chapters = getNextChapters.awaitFromLastRead(manga.id)
|
||||
if (chapters.isEmpty()) return emptyList()
|
||||
|
||||
return when (mode) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import tachiyomi.domain.chapter.model.Chapter
|
|||
import tachiyomi.domain.chapter.service.getChapterSort
|
||||
import tachiyomi.domain.history.repository.HistoryRepository
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import kotlin.math.max
|
||||
|
||||
class GetNextChapters(
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
|
|
@ -65,22 +64,37 @@ class GetNextChapters(
|
|||
fromChapterId: Long,
|
||||
onlyUnread: Boolean = true,
|
||||
): List<Chapter> {
|
||||
val chapters = await(mangaId, onlyUnread)
|
||||
val currChapterIndex = chapters.indexOfFirst { it.id == fromChapterId }
|
||||
val nextChapters = chapters.subList(max(0, currChapterIndex), chapters.size)
|
||||
val allChapters = await(mangaId, onlyUnread = false)
|
||||
val currChapterIndex = allChapters.indexOfFirst { it.id == fromChapterId }
|
||||
|
||||
if (onlyUnread) {
|
||||
return nextChapters
|
||||
if (currChapterIndex == -1) {
|
||||
return if (onlyUnread) await(mangaId, onlyUnread = true) else allChapters
|
||||
}
|
||||
|
||||
// The "next chapter" is either:
|
||||
// - The current chapter if it isn't completely read
|
||||
// - The chapters after the current chapter if the current one is completely read
|
||||
val fromChapter = chapters.getOrNull(currChapterIndex)
|
||||
return if (fromChapter != null && !fromChapter.read) {
|
||||
nextChapters
|
||||
val fromChapter = allChapters[currChapterIndex]
|
||||
val startIndex = when {
|
||||
fromChapter.read -> currChapterIndex + 1
|
||||
else -> currChapterIndex
|
||||
}.coerceAtMost(allChapters.size)
|
||||
|
||||
val fromPosition = allChapters.subList(startIndex, allChapters.size)
|
||||
|
||||
return if (onlyUnread) {
|
||||
fromPosition.filterNot { it.read }
|
||||
} else {
|
||||
nextChapters.drop(1)
|
||||
fromPosition
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun awaitFromLastRead(mangaId: Long, onlyUnread: Boolean = true): List<Chapter> {
|
||||
val lastReadChapterId = historyRepository.getHistoryByMangaId(mangaId)
|
||||
.maxByOrNull { it.readAt?.time ?: 0L }
|
||||
?.chapterId
|
||||
|
||||
return if (lastReadChapterId != null) {
|
||||
await(mangaId, lastReadChapterId, onlyUnread)
|
||||
} else {
|
||||
await(mangaId, onlyUnread)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue