mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-12-24 15:51:49 +01:00
chapter updates when pressing UI buttons
This commit is contained in:
parent
866b01f865
commit
01d5c2540d
@ -15,7 +15,9 @@ import ir.armor.tachidesk.impl.util.awaitSingle
|
||||
import ir.armor.tachidesk.model.database.table.ChapterTable
|
||||
import ir.armor.tachidesk.model.database.table.MangaTable
|
||||
import ir.armor.tachidesk.model.database.table.PageTable
|
||||
import ir.armor.tachidesk.model.database.table.toDataClass
|
||||
import ir.armor.tachidesk.model.dataclass.ChapterDataClass
|
||||
import org.jetbrains.exposed.sql.SortOrder.DESC
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
@ -26,42 +28,51 @@ import org.jetbrains.exposed.sql.update
|
||||
|
||||
object Chapter {
|
||||
/** get chapter list when showing a manga */
|
||||
suspend fun getChapterList(mangaId: Int): List<ChapterDataClass> {
|
||||
val mangaDetails = getManga(mangaId)
|
||||
val source = getHttpSource(mangaDetails.sourceId.toLong())
|
||||
|
||||
val chapterList = source.fetchChapterList(
|
||||
SManga.create().apply {
|
||||
title = mangaDetails.title
|
||||
url = mangaDetails.url
|
||||
}
|
||||
).awaitSingle()
|
||||
|
||||
val chapterCount = chapterList.count()
|
||||
|
||||
return transaction {
|
||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||
val chapterEntry = ChapterTable.select { ChapterTable.url eq fetchedChapter.url }.firstOrNull()
|
||||
if (chapterEntry == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = fetchedChapter.url
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean): List<ChapterDataClass> {
|
||||
return if (!onlineFetch) {
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }.orderBy(ChapterTable.chapterIndex to DESC)
|
||||
.map {
|
||||
ChapterTable.toDataClass(it)
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
}
|
||||
} else {
|
||||
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
val mangaDetails = getManga(mangaId)
|
||||
val source = getHttpSource(mangaDetails.sourceId.toLong())
|
||||
val chapterList = source.fetchChapterList(
|
||||
SManga.create().apply {
|
||||
title = mangaDetails.title
|
||||
url = mangaDetails.url
|
||||
}
|
||||
).awaitSingle()
|
||||
|
||||
val chapterCount = chapterList.count()
|
||||
|
||||
transaction {
|
||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||
val chapterEntry = ChapterTable.select { ChapterTable.url eq fetchedChapter.url }.firstOrNull()
|
||||
if (chapterEntry == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = fetchedChapter.url
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
|
||||
it[name] = fetchedChapter.name
|
||||
it[date_upload] = fetchedChapter.date_upload
|
||||
it[chapter_number] = fetchedChapter.chapter_number
|
||||
it[scanlator] = fetchedChapter.scanlator
|
||||
|
||||
it[chapterIndex] = index + 1
|
||||
it[manga] = mangaId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,10 +94,12 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
val dbChapterMap = transaction { ChapterTable.selectAll() }
|
||||
.associateBy({ it[ChapterTable.url] }, { it })
|
||||
val dbChapterMap = transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.associateBy({ it[ChapterTable.url] }, { it })
|
||||
}
|
||||
|
||||
chapterList.mapIndexed { index, it ->
|
||||
return chapterList.mapIndexed { index, it ->
|
||||
|
||||
val dbChapter = dbChapterMap.getValue(it.url)
|
||||
|
||||
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import ir.armor.tachidesk.impl.MangaList.proxyThumbnailUrl
|
||||
import ir.armor.tachidesk.impl.Source.getSource
|
||||
import ir.armor.tachidesk.impl.util.CachedImageResponse.clearCachedImage
|
||||
import ir.armor.tachidesk.impl.util.CachedImageResponse.getCachedImageResponse
|
||||
import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource
|
||||
import ir.armor.tachidesk.impl.util.await
|
||||
@ -35,17 +36,17 @@ object Manga {
|
||||
text
|
||||
}
|
||||
|
||||
suspend fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
|
||||
suspend fun getManga(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
|
||||
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
|
||||
return if (mangaEntry[MangaTable.initialized]) {
|
||||
return if (mangaEntry[MangaTable.initialized] && !onlineFetch) {
|
||||
MangaDataClass(
|
||||
mangaId,
|
||||
mangaEntry[MangaTable.sourceReference].toString(),
|
||||
|
||||
mangaEntry[MangaTable.url],
|
||||
mangaEntry[MangaTable.title],
|
||||
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url],
|
||||
proxyThumbnailUrl(mangaId),
|
||||
|
||||
true,
|
||||
|
||||
@ -55,7 +56,8 @@ object Manga {
|
||||
mangaEntry[MangaTable.genre],
|
||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||
mangaEntry[MangaTable.inLibrary],
|
||||
getSource(mangaEntry[MangaTable.sourceReference])
|
||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||
false
|
||||
)
|
||||
} else { // initialize manga
|
||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
@ -81,8 +83,9 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
clearMangaThumbnail(mangaId)
|
||||
|
||||
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
val newThumbnail = mangaEntry[MangaTable.thumbnail_url]
|
||||
|
||||
MangaDataClass(
|
||||
mangaId,
|
||||
@ -90,7 +93,7 @@ object Manga {
|
||||
|
||||
mangaEntry[MangaTable.url],
|
||||
mangaEntry[MangaTable.title],
|
||||
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail,
|
||||
proxyThumbnailUrl(mangaId),
|
||||
|
||||
true,
|
||||
|
||||
@ -100,28 +103,37 @@ object Manga {
|
||||
fetchedManga.genre,
|
||||
MangaStatus.valueOf(fetchedManga.status).name,
|
||||
false,
|
||||
getSource(mangaEntry[MangaTable.sourceReference])
|
||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
val saveDir = applicationDirs.thumbnailsRoot
|
||||
val fileName = mangaId.toString()
|
||||
|
||||
return getCachedImageResponse(saveDir, fileName) {
|
||||
getManga(mangaId) // make sure is initialized
|
||||
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
val source = getHttpSource(sourceId)
|
||||
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
|
||||
thumbnailUrl = getManga(mangaId, proxyThumbnail = false).thumbnailUrl!!
|
||||
}
|
||||
|
||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]!!
|
||||
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers)
|
||||
).await()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearMangaThumbnail(mangaId: Int) {
|
||||
val saveDir = applicationDirs.thumbnailsRoot
|
||||
val fileName = mangaId.toString()
|
||||
|
||||
clearCachedImage(saveDir, fileName)
|
||||
}
|
||||
}
|
||||
|
@ -64,4 +64,11 @@ object CachedImageResponse {
|
||||
throw Exception("request error! ${response.code}")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearCachedImage(saveDir: String, fileName: String) {
|
||||
val cachedFile = findFileNameStartingWith(saveDir, fileName)
|
||||
cachedFile?.also {
|
||||
File(it).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ package ir.armor.tachidesk.model.database.table
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import ir.armor.tachidesk.model.dataclass.ChapterDataClass
|
||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
|
||||
object ChapterTable : IntIdTable() {
|
||||
val url = varchar("url", 2048)
|
||||
@ -25,3 +27,17 @@ object ChapterTable : IntIdTable() {
|
||||
|
||||
val manga = reference("manga", MangaTable)
|
||||
}
|
||||
|
||||
fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
|
||||
ChapterDataClass(
|
||||
chapterEntry[ChapterTable.url],
|
||||
chapterEntry[ChapterTable.name],
|
||||
chapterEntry[ChapterTable.date_upload],
|
||||
chapterEntry[ChapterTable.chapter_number],
|
||||
chapterEntry[ChapterTable.scanlator],
|
||||
chapterEntry[ChapterTable.manga].value,
|
||||
chapterEntry[ChapterTable.isRead],
|
||||
chapterEntry[ChapterTable.isBookmarked],
|
||||
chapterEntry[ChapterTable.lastPageRead],
|
||||
chapterEntry[ChapterTable.chapterIndex],
|
||||
)
|
||||
|
@ -25,7 +25,9 @@ data class MangaDataClass(
|
||||
val genre: String? = null,
|
||||
val status: String = MangaStatus.UNKNOWN.name,
|
||||
val inLibrary: Boolean = false,
|
||||
val source: SourceDataClass? = null
|
||||
val source: SourceDataClass? = null,
|
||||
|
||||
val freshData: Boolean = false
|
||||
)
|
||||
|
||||
data class PagedMangaListDataClass(
|
||||
|
@ -191,9 +191,11 @@ object JavalinSetup {
|
||||
// get manga info
|
||||
app.get("/api/v1/manga/:mangaId/") { ctx ->
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
||||
|
||||
ctx.json(
|
||||
future {
|
||||
getManga(mangaId)
|
||||
getManga(mangaId, onlineFetch)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -254,7 +256,10 @@ object JavalinSetup {
|
||||
// get chapter list when showing a manga
|
||||
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
ctx.json(future { getChapterList(mangaId) })
|
||||
|
||||
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
||||
|
||||
ctx.json(future { getChapterList(mangaId, onlineFetch) })
|
||||
}
|
||||
|
||||
// used to display a chapter, get a chapter in order to show it's pages
|
||||
|
@ -50,13 +50,14 @@ const useStyles = makeStyles((theme) => ({
|
||||
|
||||
interface IProps{
|
||||
chapter: IChapter
|
||||
triggerChaptersUpdate: () => void
|
||||
}
|
||||
|
||||
export default function ChapterCard(props: IProps) {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
const theme = useTheme();
|
||||
const { chapter } = props;
|
||||
const { chapter, triggerChaptersUpdate } = props;
|
||||
|
||||
const dateStr = chapter.uploadDate && new Date(chapter.uploadDate).toISOString().slice(0, 10);
|
||||
|
||||
@ -71,12 +72,12 @@ export default function ChapterCard(props: IProps) {
|
||||
};
|
||||
|
||||
const sendChange = (key: string, value: any) => {
|
||||
console.log(`${key} -> ${value}`);
|
||||
handleClose();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append(key, value);
|
||||
client.patch(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`, formData);
|
||||
client.patch(`/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}`, formData)
|
||||
.then(() => triggerChaptersUpdate());
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -198,7 +198,7 @@ export default function MangaDetails(props: IProps) {
|
||||
<div className={classes.top}>
|
||||
<div className={classes.leftRight}>
|
||||
<div className={classes.leftSide}>
|
||||
<img src={serverAddress + manga.thumbnailUrl} alt="Manga Thumbnail" />
|
||||
<img src={`${serverAddress}${manga.thumbnailUrl}?x=${Math.random()}`} alt="Manga Thumbnail" />
|
||||
</div>
|
||||
<div className={classes.rightSide}>
|
||||
<h1>
|
||||
|
@ -43,9 +43,9 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const InnerItem = React.memo(({ chapters, index }: any) => (
|
||||
<ChapterCard chapter={chapters[index]} />
|
||||
));
|
||||
// const InnerItem = React.memo(({ chapters, index }: any) => (
|
||||
// <ChapterCard chapter={chapters[index]} />
|
||||
// ));
|
||||
|
||||
export default function Manga() {
|
||||
const classes = useStyles();
|
||||
@ -58,23 +58,37 @@ export default function Manga() {
|
||||
|
||||
const [manga, setManga] = useState<IManga>();
|
||||
const [chapters, setChapters] = useState<IChapter[]>([]);
|
||||
const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0);
|
||||
|
||||
function triggerChaptersUpdate() {
|
||||
setChapterUpdateTriggerer(chapterUpdateTriggerer + 1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
client.get(`/api/v1/manga/${id}/`)
|
||||
.then((response) => response.data)
|
||||
.then((data: IManga) => {
|
||||
setManga(data);
|
||||
setTitle(data.title);
|
||||
});
|
||||
}, []);
|
||||
if (manga === undefined || !manga.freshData) {
|
||||
client.get(`/api/v1/manga/${id}/?onlineFetch=${manga !== undefined}`)
|
||||
.then((response) => response.data)
|
||||
.then((data: IManga) => {
|
||||
setManga(data);
|
||||
setTitle(data.title);
|
||||
});
|
||||
}
|
||||
}, [manga]);
|
||||
|
||||
useEffect(() => {
|
||||
client.get(`/api/v1/manga/${id}/chapters`)
|
||||
const shouldFetchOnline = chapters.length > 0 && chapterUpdateTriggerer === 0;
|
||||
client.get(`/api/v1/manga/${id}/chapters?onlineFetch=${shouldFetchOnline}`)
|
||||
.then((response) => response.data)
|
||||
.then((data) => setChapters(data));
|
||||
}, []);
|
||||
}, [chapters.length, chapterUpdateTriggerer]);
|
||||
|
||||
const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />;
|
||||
// const itemContent = (index:any) => <InnerItem chapters={chapters} index={index} />;
|
||||
const itemContent = (index:any) => (
|
||||
<ChapterCard
|
||||
chapter={chapters[index]}
|
||||
triggerChaptersUpdate={triggerChaptersUpdate}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
2
webUI/react/src/typings.d.ts
vendored
2
webUI/react/src/typings.d.ts
vendored
@ -50,6 +50,8 @@ interface IManga {
|
||||
|
||||
inLibrary: boolean
|
||||
source: ISource
|
||||
|
||||
freshData: boolean
|
||||
}
|
||||
|
||||
interface IChapter {
|
||||
|
Loading…
Reference in New Issue
Block a user