mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-27 08:16:36 +03:00
Database queries are now separated by table. Improve how the app creates downloads
This commit is contained in:
parent
af2b886599
commit
5e24054a0b
20 changed files with 657 additions and 532 deletions
|
@ -1,26 +1,17 @@
|
||||||
package eu.kanade.tachiyomi.data.database
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Pair
|
|
||||||
import com.pushtorefresh.storio.Queries
|
|
||||||
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
||||||
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.Query
|
|
||||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.*
|
import eu.kanade.tachiyomi.data.database.models.*
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.queries.*
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.*
|
|
||||||
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService
|
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source
|
|
||||||
import eu.kanade.tachiyomi.util.ChapterRecognition
|
|
||||||
import rx.Observable
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
open class DatabaseHelper(context: Context) {
|
/**
|
||||||
|
* This class provides operations to manage the database through its interfaces.
|
||||||
|
*/
|
||||||
|
open class DatabaseHelper(context: Context)
|
||||||
|
: MangaQueries, ChapterQueries, MangaSyncQueries, CategoryQueries, MangaCategoryQueries {
|
||||||
|
|
||||||
val db = DefaultStorIOSQLite.builder()
|
override val db = DefaultStorIOSQLite.builder()
|
||||||
.sqliteOpenHelper(DbOpenHelper(context))
|
.sqliteOpenHelper(DbOpenHelper(context))
|
||||||
.addTypeMapping(Manga::class.java, MangaSQLiteTypeMapping())
|
.addTypeMapping(Manga::class.java, MangaSQLiteTypeMapping())
|
||||||
.addTypeMapping(Chapter::class.java, ChapterSQLiteTypeMapping())
|
.addTypeMapping(Chapter::class.java, ChapterSQLiteTypeMapping())
|
||||||
|
@ -29,287 +20,6 @@ open class DatabaseHelper(context: Context) {
|
||||||
.addTypeMapping(MangaCategory::class.java, MangaCategorySQLiteTypeMapping())
|
.addTypeMapping(MangaCategory::class.java, MangaCategorySQLiteTypeMapping())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
inline fun inTransaction(func: DatabaseHelper.() -> Unit) {
|
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
|
||||||
db.internal().beginTransaction()
|
|
||||||
try {
|
|
||||||
func()
|
|
||||||
db.internal().setTransactionSuccessful()
|
|
||||||
} finally {
|
|
||||||
db.internal().endTransaction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mangas related queries
|
|
||||||
|
|
||||||
fun getMangas() = db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getLibraryMangas() = db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(libraryQuery)
|
|
||||||
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
open fun getFavoriteMangas() = db.get()
|
|
||||||
.listOfObjects(Manga::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COLUMN_FAVORITE} = ?")
|
|
||||||
.whereArgs(1)
|
|
||||||
.orderBy(MangaTable.COLUMN_TITLE)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getManga(url: String, sourceId: Int) = db.get()
|
|
||||||
.`object`(Manga::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COLUMN_URL} = ? AND ${MangaTable.COLUMN_SOURCE} = ?")
|
|
||||||
.whereArgs(url, sourceId)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getManga(id: Long) = db.get()
|
|
||||||
.`object`(Manga::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COLUMN_ID} = ?")
|
|
||||||
.whereArgs(id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
|
||||||
|
|
||||||
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
|
||||||
|
|
||||||
fun deleteMangasNotInLibrary() = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(MangaTable.TABLE)
|
|
||||||
.where("${MangaTable.COLUMN_FAVORITE} = ?")
|
|
||||||
.whereArgs(0)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
|
|
||||||
// Chapters related queries
|
|
||||||
|
|
||||||
fun getChapters(manga: Manga) = db.get()
|
|
||||||
.listOfObjects(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COLUMN_MANGA_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getRecentChapters(date: Date) = db.get()
|
|
||||||
.listOfObjects(MangaChapter::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(getRecentsQuery(date))
|
|
||||||
.observesTables(ChapterTable.TABLE)
|
|
||||||
.build())
|
|
||||||
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getNextChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
|
||||||
// Add a delta to the chapter number, because binary decimal representation
|
|
||||||
// can retrieve the same chapter again
|
|
||||||
val chapterNumber = chapter.chapter_number + 0.00001
|
|
||||||
|
|
||||||
return db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
|
|
||||||
"${ChapterTable.COLUMN_CHAPTER_NUMBER} > ? AND " +
|
|
||||||
"${ChapterTable.COLUMN_CHAPTER_NUMBER} <= ?")
|
|
||||||
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
|
|
||||||
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPreviousChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
|
||||||
// Add a delta to the chapter number, because binary decimal representation
|
|
||||||
// can retrieve the same chapter again
|
|
||||||
val chapterNumber = chapter.chapter_number - 0.00001
|
|
||||||
|
|
||||||
return db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder().table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
|
|
||||||
"${ChapterTable.COLUMN_CHAPTER_NUMBER} < ? AND " +
|
|
||||||
"${ChapterTable.COLUMN_CHAPTER_NUMBER} >= ?")
|
|
||||||
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
|
|
||||||
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + " DESC")
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNextUnreadChapter(manga: Manga) = db.get()
|
|
||||||
.`object`(Chapter::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(ChapterTable.TABLE)
|
|
||||||
.where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
|
|
||||||
"${ChapterTable.COLUMN_READ} = ? AND " +
|
|
||||||
"${ChapterTable.COLUMN_CHAPTER_NUMBER} >= ?")
|
|
||||||
.whereArgs(manga.id, 0, 0)
|
|
||||||
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
|
|
||||||
.limit(1)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
|
||||||
|
|
||||||
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
|
||||||
|
|
||||||
// Add new chapters or delete if the source deletes them
|
|
||||||
open fun insertOrRemoveChapters(manga: Manga, sourceChapters: List<Chapter>, source: Source): Observable<Pair<Int, Int>> {
|
|
||||||
val dbChapters = getChapters(manga).executeAsBlocking()
|
|
||||||
|
|
||||||
val newChapters = Observable.from(sourceChapters)
|
|
||||||
.filter { it !in dbChapters }
|
|
||||||
.doOnNext { c ->
|
|
||||||
c.manga_id = manga.id
|
|
||||||
source.parseChapterNumber(c)
|
|
||||||
ChapterRecognition.parseChapterNumber(c, manga)
|
|
||||||
}.toList()
|
|
||||||
|
|
||||||
val deletedChapters = Observable.from(dbChapters)
|
|
||||||
.filter { it !in sourceChapters }
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
return Observable.zip(newChapters, deletedChapters) { toAdd, toDelete ->
|
|
||||||
var added = 0
|
|
||||||
var deleted = 0
|
|
||||||
var readded = 0
|
|
||||||
|
|
||||||
inTransaction {
|
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
|
||||||
if (!toDelete.isEmpty()) {
|
|
||||||
for (c in toDelete) {
|
|
||||||
if (c.read) {
|
|
||||||
deletedReadChapterNumbers.add(c.chapter_number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deleted = deleteChapters(toDelete).executeAsBlocking().results().size
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!toAdd.isEmpty()) {
|
|
||||||
// Set the date fetch for new items in reverse order to allow another sorting method.
|
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
|
||||||
var now = Date().time
|
|
||||||
|
|
||||||
for (i in toAdd.indices.reversed()) {
|
|
||||||
val c = toAdd[i]
|
|
||||||
c.date_fetch = now++
|
|
||||||
// Try to mark already read chapters as read when the source deletes them
|
|
||||||
if (c.chapter_number != -1f && c.chapter_number in deletedReadChapterNumbers) {
|
|
||||||
c.read = true
|
|
||||||
readded++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
added = insertChapters(toAdd).executeAsBlocking().numberOfInserts()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Pair.create(added - readded, deleted - readded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteChapter(chapter: Chapter) = db.delete().`object`(chapter).prepare()
|
|
||||||
|
|
||||||
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
|
||||||
|
|
||||||
// Manga sync related queries
|
|
||||||
|
|
||||||
fun getMangaSync(manga: Manga, sync: MangaSyncService) = db.get()
|
|
||||||
.`object`(MangaSync::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(MangaSyncTable.TABLE)
|
|
||||||
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ? AND " +
|
|
||||||
"${MangaSyncTable.COLUMN_SYNC_ID} = ?")
|
|
||||||
.whereArgs(manga.id, sync.id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getMangasSync(manga: Manga) = db.get()
|
|
||||||
.listOfObjects(MangaSync::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(MangaSyncTable.TABLE)
|
|
||||||
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun insertMangaSync(manga: MangaSync) = db.put().`object`(manga).prepare()
|
|
||||||
|
|
||||||
fun insertMangasSync(mangas: List<MangaSync>) = db.put().objects(mangas).prepare()
|
|
||||||
|
|
||||||
fun deleteMangaSync(manga: MangaSync) = db.delete().`object`(manga).prepare()
|
|
||||||
|
|
||||||
fun deleteMangaSyncForManga(manga: Manga) = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(MangaSyncTable.TABLE)
|
|
||||||
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ?")
|
|
||||||
.whereArgs(manga.id)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
// Categories related queries
|
|
||||||
|
|
||||||
fun getCategories() = db.get()
|
|
||||||
.listOfObjects(Category::class.java)
|
|
||||||
.withQuery(Query.builder()
|
|
||||||
.table(CategoryTable.TABLE)
|
|
||||||
.orderBy(CategoryTable.COLUMN_ORDER)
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun getCategoriesForManga(manga: Manga) = db.get()
|
|
||||||
.listOfObjects(Category::class.java)
|
|
||||||
.withQuery(RawQuery.builder()
|
|
||||||
.query(getCategoriesForMangaQuery(manga))
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
|
||||||
|
|
||||||
fun insertCategories(categories: List<Category>) = db.put().objects(categories).prepare()
|
|
||||||
|
|
||||||
fun deleteCategory(category: Category) = db.delete().`object`(category).prepare()
|
|
||||||
|
|
||||||
fun deleteCategories(categories: List<Category>) = db.delete().objects(categories).prepare()
|
|
||||||
|
|
||||||
fun insertMangaCategory(mangaCategory: MangaCategory) = db.put().`object`(mangaCategory).prepare()
|
|
||||||
|
|
||||||
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
|
|
||||||
|
|
||||||
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
|
|
||||||
.byQuery(DeleteQuery.builder()
|
|
||||||
.table(MangaCategoryTable.TABLE)
|
|
||||||
.where("${MangaCategoryTable.COLUMN_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
|
||||||
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
|
||||||
.build())
|
|
||||||
.prepare()
|
|
||||||
|
|
||||||
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
|
||||||
inTransaction {
|
|
||||||
deleteOldMangasCategories(mangas).executeAsBlocking()
|
|
||||||
insertMangasCategories(mangasCategories).executeAsBlocking()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
|
||||||
|
inline fun StorIOSQLite.inTransaction(block: () -> Unit) {
|
||||||
|
internal().beginTransaction()
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
internal().setTransactionSuccessful()
|
||||||
|
} finally {
|
||||||
|
internal().endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> StorIOSQLite.inTransactionReturn(block: () -> T): T {
|
||||||
|
internal().beginTransaction()
|
||||||
|
try {
|
||||||
|
val result = block()
|
||||||
|
internal().setTransactionSuccessful()
|
||||||
|
return result
|
||||||
|
} finally {
|
||||||
|
internal().endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
|
||||||
|
|
||||||
|
interface DbProvider {
|
||||||
|
|
||||||
|
val db: DefaultStorIOSQLite
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
|
|
||||||
|
interface CategoryQueries : DbProvider {
|
||||||
|
|
||||||
|
fun getCategories() = db.get()
|
||||||
|
.listOfObjects(Category::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(CategoryTable.TABLE)
|
||||||
|
.orderBy(CategoryTable.COLUMN_ORDER)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getCategoriesForManga(manga: Manga) = db.get()
|
||||||
|
.listOfObjects(Category::class.java)
|
||||||
|
.withQuery(RawQuery.builder()
|
||||||
|
.query(getCategoriesForMangaQuery())
|
||||||
|
.args(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertCategory(category: Category) = db.put().`object`(category).prepare()
|
||||||
|
|
||||||
|
fun insertCategories(categories: List<Category>) = db.put().objects(categories).prepare()
|
||||||
|
|
||||||
|
fun deleteCategory(category: Category) = db.delete().`object`(category).prepare()
|
||||||
|
|
||||||
|
fun deleteCategories(categories: List<Category>) = db.delete().objects(categories).prepare()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import android.util.Pair
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransaction
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
|
import eu.kanade.tachiyomi.util.ChapterRecognition
|
||||||
|
import rx.Observable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
interface ChapterQueries : DbProvider {
|
||||||
|
|
||||||
|
fun getChapters(manga: Manga) = db.get()
|
||||||
|
.listOfObjects(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COLUMN_MANGA_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getRecentChapters(date: Date) = db.get()
|
||||||
|
.listOfObjects(MangaChapter::class.java)
|
||||||
|
.withQuery(RawQuery.builder()
|
||||||
|
.query(getRecentsQuery())
|
||||||
|
.args(date.time)
|
||||||
|
.observesTables(ChapterTable.TABLE)
|
||||||
|
.build())
|
||||||
|
.withGetResolver(MangaChapterGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getNextChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
||||||
|
// Add a delta to the chapter number, because binary decimal representation
|
||||||
|
// can retrieve the same chapter again
|
||||||
|
val chapterNumber = chapter.chapter_number + 0.00001
|
||||||
|
|
||||||
|
return db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
|
||||||
|
"${ChapterTable.COLUMN_CHAPTER_NUMBER} > ? AND " +
|
||||||
|
"${ChapterTable.COLUMN_CHAPTER_NUMBER} <= ?")
|
||||||
|
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
|
||||||
|
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
|
||||||
|
.limit(1)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPreviousChapter(chapter: Chapter): PreparedGetObject<Chapter> {
|
||||||
|
// Add a delta to the chapter number, because binary decimal representation
|
||||||
|
// can retrieve the same chapter again
|
||||||
|
val chapterNumber = chapter.chapter_number - 0.00001
|
||||||
|
|
||||||
|
return db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder().table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
|
||||||
|
"${ChapterTable.COLUMN_CHAPTER_NUMBER} < ? AND " +
|
||||||
|
"${ChapterTable.COLUMN_CHAPTER_NUMBER} >= ?")
|
||||||
|
.whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
|
||||||
|
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + " DESC")
|
||||||
|
.limit(1)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextUnreadChapter(manga: Manga) = db.get()
|
||||||
|
.`object`(Chapter::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
|
||||||
|
"${ChapterTable.COLUMN_READ} = ? AND " +
|
||||||
|
"${ChapterTable.COLUMN_CHAPTER_NUMBER} >= ?")
|
||||||
|
.whereArgs(manga.id, 0, 0)
|
||||||
|
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
|
||||||
|
.limit(1)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
|
||||||
|
|
||||||
|
fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
|
||||||
|
|
||||||
|
// TODO this logic shouldn't be here
|
||||||
|
// Add new chapters or delete if the source deletes them
|
||||||
|
open fun insertOrRemoveChapters(manga: Manga, sourceChapters: List<Chapter>, source: Source): Observable<Pair<Int, Int>> {
|
||||||
|
val dbChapters = getChapters(manga).executeAsBlocking()
|
||||||
|
|
||||||
|
val newChapters = Observable.from(sourceChapters)
|
||||||
|
.filter { it !in dbChapters }
|
||||||
|
.doOnNext { c ->
|
||||||
|
c.manga_id = manga.id
|
||||||
|
source.parseChapterNumber(c)
|
||||||
|
ChapterRecognition.parseChapterNumber(c, manga)
|
||||||
|
}.toList()
|
||||||
|
|
||||||
|
val deletedChapters = Observable.from(dbChapters)
|
||||||
|
.filter { it !in sourceChapters }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
return Observable.zip(newChapters, deletedChapters) { toAdd, toDelete ->
|
||||||
|
var added = 0
|
||||||
|
var deleted = 0
|
||||||
|
var readded = 0
|
||||||
|
|
||||||
|
db.inTransaction {
|
||||||
|
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||||
|
if (!toDelete.isEmpty()) {
|
||||||
|
for (c in toDelete) {
|
||||||
|
if (c.read) {
|
||||||
|
deletedReadChapterNumbers.add(c.chapter_number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted = deleteChapters(toDelete).executeAsBlocking().results().size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toAdd.isEmpty()) {
|
||||||
|
// Set the date fetch for new items in reverse order to allow another sorting method.
|
||||||
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
|
var now = Date().time
|
||||||
|
|
||||||
|
for (i in toAdd.indices.reversed()) {
|
||||||
|
val c = toAdd[i]
|
||||||
|
c.date_fetch = now++
|
||||||
|
// Try to mark already read chapters as read when the source deletes them
|
||||||
|
if (c.chapter_number != -1f && c.chapter_number in deletedReadChapterNumbers) {
|
||||||
|
c.read = true
|
||||||
|
readded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
added = insertChapters(toAdd).executeAsBlocking().numberOfInserts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pair.create(added - readded, deleted - readded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteChapter(chapter: Chapter) = db.delete().`object`(chapter).prepare()
|
||||||
|
|
||||||
|
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
|
||||||
|
|
||||||
|
fun updateChapterProgress(chapter: Chapter) = db.put()
|
||||||
|
.`object`(chapter)
|
||||||
|
.withPutResolver(ChapterProgressPutResolver.instance)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.Queries
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransaction
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
|
|
||||||
|
interface MangaCategoryQueries : DbProvider {
|
||||||
|
|
||||||
|
fun insertMangaCategory(mangaCategory: MangaCategory) = db.put().`object`(mangaCategory).prepare()
|
||||||
|
|
||||||
|
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
|
||||||
|
|
||||||
|
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
|
||||||
|
.byQuery(DeleteQuery.builder()
|
||||||
|
.table(MangaCategoryTable.TABLE)
|
||||||
|
.where("${MangaCategoryTable.COLUMN_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
|
||||||
|
.whereArgs(*mangas.map { it.id }.toTypedArray())
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
|
||||||
|
db.inTransaction {
|
||||||
|
deleteOldMangasCategories(mangas).executeAsBlocking()
|
||||||
|
insertMangasCategories(mangasCategories).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
interface MangaQueries : DbProvider {
|
||||||
|
|
||||||
|
fun getMangas() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getLibraryMangas() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(RawQuery.builder()
|
||||||
|
.query(libraryQuery)
|
||||||
|
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE)
|
||||||
|
.build())
|
||||||
|
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
open fun getFavoriteMangas() = db.get()
|
||||||
|
.listOfObjects(Manga::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COLUMN_FAVORITE} = ?")
|
||||||
|
.whereArgs(1)
|
||||||
|
.orderBy(MangaTable.COLUMN_TITLE)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getManga(url: String, sourceId: Int) = db.get()
|
||||||
|
.`object`(Manga::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COLUMN_URL} = ? AND ${MangaTable.COLUMN_SOURCE} = ?")
|
||||||
|
.whereArgs(url, sourceId)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getManga(id: Long) = db.get()
|
||||||
|
.`object`(Manga::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COLUMN_ID} = ?")
|
||||||
|
.whereArgs(id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
|
||||||
|
|
||||||
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
|
|
||||||
|
fun deleteMangasNotInLibrary() = db.delete()
|
||||||
|
.byQuery(DeleteQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COLUMN_FAVORITE} = ?")
|
||||||
|
.whereArgs(0)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaSync
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable
|
||||||
|
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService
|
||||||
|
|
||||||
|
interface MangaSyncQueries : DbProvider {
|
||||||
|
|
||||||
|
fun getMangaSync(manga: Manga, sync: MangaSyncService) = db.get()
|
||||||
|
.`object`(MangaSync::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ? AND " +
|
||||||
|
"${MangaSyncTable.COLUMN_SYNC_ID} = ?")
|
||||||
|
.whereArgs(manga.id, sync.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getMangasSync(manga: Manga) = db.get()
|
||||||
|
.listOfObjects(MangaSync::class.java)
|
||||||
|
.withQuery(Query.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertMangaSync(manga: MangaSync) = db.put().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun insertMangasSync(mangas: List<MangaSync>) = db.put().objects(mangas).prepare()
|
||||||
|
|
||||||
|
fun deleteMangaSync(manga: MangaSync) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
|
fun deleteMangaSyncForManga(manga: Manga) = db.delete()
|
||||||
|
.byQuery(DeleteQuery.builder()
|
||||||
|
.table(MangaSyncTable.TABLE)
|
||||||
|
.where("${MangaSyncTable.COLUMN_MANGA_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.data.database
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga as MangaModel
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
|
||||||
|
@ -32,23 +30,19 @@ val libraryQuery =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the recent chapters of manga from the library up to a date.
|
* Query to get the recent chapters of manga from the library up to a date.
|
||||||
*
|
|
||||||
* @param date the delimiting date.
|
|
||||||
*/
|
*/
|
||||||
fun getRecentsQuery(date: Date): String =
|
fun getRecentsQuery() =
|
||||||
"SELECT ${Manga.TABLE}.${Manga.COLUMN_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} " +
|
"SELECT ${Manga.TABLE}.${Manga.COLUMN_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} " +
|
||||||
"ON ${Manga.TABLE}.${Manga.COLUMN_ID} = ${Chapter.TABLE}.${Chapter.COLUMN_MANGA_ID} " +
|
"ON ${Manga.TABLE}.${Manga.COLUMN_ID} = ${Chapter.TABLE}.${Chapter.COLUMN_MANGA_ID} " +
|
||||||
"WHERE ${Manga.COLUMN_FAVORITE} = 1 AND ${Chapter.COLUMN_DATE_UPLOAD} > ${date.time} " +
|
"WHERE ${Manga.COLUMN_FAVORITE} = 1 AND ${Chapter.COLUMN_DATE_UPLOAD} > ? " +
|
||||||
"ORDER BY ${Chapter.COLUMN_DATE_UPLOAD} DESC"
|
"ORDER BY ${Chapter.COLUMN_DATE_UPLOAD} DESC"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the categorias for a manga.
|
* Query to get the categories for a manga.
|
||||||
*
|
|
||||||
* @param manga the manga.
|
|
||||||
*/
|
*/
|
||||||
fun getCategoriesForMangaQuery(manga: MangaModel) =
|
fun getCategoriesForMangaQuery() =
|
||||||
"SELECT ${Category.TABLE}.* FROM ${Category.TABLE} " +
|
"SELECT ${Category.TABLE}.* FROM ${Category.TABLE} " +
|
||||||
"JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COLUMN_ID} = " +
|
"JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COLUMN_ID} = " +
|
||||||
"${MangaCategory.TABLE}.${MangaCategory.COLUMN_CATEGORY_ID} " +
|
"${MangaCategory.TABLE}.${MangaCategory.COLUMN_CATEGORY_ID} " +
|
||||||
"WHERE ${MangaCategory.COLUMN_MANGA_ID} = ${manga.id}"
|
"WHERE ${MangaCategory.COLUMN_MANGA_ID} = ?"
|
|
@ -0,0 +1,38 @@
|
||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
|
|
||||||
|
class ChapterProgressPutResolver : PutResolver<Chapter>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val instance = ChapterProgressPutResolver()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(chapter)
|
||||||
|
val contentValues = mapToContentValues(chapter)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.internal().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
|
||||||
|
.table(ChapterTable.TABLE)
|
||||||
|
.where("${ChapterTable.COLUMN_ID} = ?")
|
||||||
|
.whereArgs(chapter.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(chapter: Chapter) = ContentValues(2).apply {
|
||||||
|
put(ChapterTable.COLUMN_READ, chapter.read)
|
||||||
|
put(ChapterTable.COLUMN_LAST_PAGE_READ, chapter.last_page_read)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
|
||||||
import eu.kanade.tachiyomi.util.*
|
import eu.kanade.tachiyomi.util.*
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
@ -45,7 +44,8 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||||
|
|
||||||
val PAGE_LIST_FILE = "index.json"
|
val PAGE_LIST_FILE = "index.json"
|
||||||
|
|
||||||
@Volatile private var isRunning: Boolean = false
|
@Volatile var isRunning: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
private fun initializeSubscriptions() {
|
private fun initializeSubscriptions() {
|
||||||
downloadsSubscription?.unsubscribe()
|
downloadsSubscription?.unsubscribe()
|
||||||
|
@ -91,16 +91,15 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a download object for every chapter in the event and add them to the downloads queue
|
// Create a download object for every chapter and add them to the downloads queue
|
||||||
fun onDownloadChaptersEvent(event: DownloadChaptersEvent) {
|
fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||||
val manga = event.manga
|
|
||||||
val source = sourceManager.get(manga.source)
|
val source = sourceManager.get(manga.source)
|
||||||
|
|
||||||
// Used to avoid downloading chapters with the same name
|
// Used to avoid downloading chapters with the same name
|
||||||
val addedChapters = ArrayList<String>()
|
val addedChapters = ArrayList<String>()
|
||||||
val pending = ArrayList<Download>()
|
val pending = ArrayList<Download>()
|
||||||
|
|
||||||
for (chapter in event.chapters) {
|
for (chapter in chapters) {
|
||||||
if (addedChapters.contains(chapter.name))
|
if (addedChapters.contains(chapter.name))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.event
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
|
|
||||||
class DownloadChaptersEvent(val manga: Manga, val chapters: List<Chapter>)
|
|
|
@ -4,6 +4,7 @@ import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
import android.support.v7.view.ActionMode
|
import android.support.v7.view.ActionMode
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
@ -11,7 +12,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
|
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
|
||||||
|
@ -21,12 +21,11 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.getCoordinates
|
import eu.kanade.tachiyomi.util.getCoordinates
|
||||||
import eu.kanade.tachiyomi.util.getResourceDrawable
|
import eu.kanade.tachiyomi.util.getResourceDrawable
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
|
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
|
||||||
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
||||||
import kotlinx.android.synthetic.main.fragment_manga_chapters.*
|
import kotlinx.android.synthetic.main.fragment_manga_chapters.*
|
||||||
import nucleus.factory.RequiresPresenter
|
import nucleus.factory.RequiresPresenter
|
||||||
import rx.Observable
|
import timber.log.Timber
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
|
|
||||||
@RequiresPresenter(ChaptersPresenter::class)
|
@RequiresPresenter(ChaptersPresenter::class)
|
||||||
class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
|
class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener {
|
||||||
|
@ -40,6 +39,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
||||||
fun newInstance(): ChaptersFragment {
|
fun newInstance(): ChaptersFragment {
|
||||||
return ChaptersFragment()
|
return ChaptersFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +73,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
||||||
|
|
||||||
swipe_refresh.setOnRefreshListener { fetchChapters() }
|
swipe_refresh.setOnRefreshListener { fetchChapters() }
|
||||||
|
|
||||||
fab.setOnClickListener { v ->
|
fab.setOnClickListener {
|
||||||
val chapter = presenter.getNextUnreadChapter()
|
val chapter = presenter.getNextUnreadChapter()
|
||||||
if (chapter != null) {
|
if (chapter != null) {
|
||||||
// Create animation listener
|
// Create animation listener
|
||||||
|
@ -252,7 +252,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
||||||
chapters = chapters.subList(0, 10)
|
chapters = chapters.subList(0, 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDownload(Observable.from(chapters))
|
downloadChapters(chapters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
@ -278,11 +278,11 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_select_all -> onSelectAll()
|
R.id.action_select_all -> selectAll()
|
||||||
R.id.action_mark_as_read -> onMarkAsRead(getSelectedChapters())
|
R.id.action_mark_as_read -> markAsRead(getSelectedChapters())
|
||||||
R.id.action_mark_as_unread -> onMarkAsUnread(getSelectedChapters())
|
R.id.action_mark_as_unread -> markAsUnread(getSelectedChapters())
|
||||||
R.id.action_download -> onDownload(getSelectedChapters())
|
R.id.action_download -> downloadChapters(getSelectedChapters())
|
||||||
R.id.action_delete -> onDelete(getSelectedChapters())
|
R.id.action_delete -> deleteChapters(getSelectedChapters())
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -294,66 +294,57 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac
|
||||||
actionMode = null
|
actionMode = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedChapters(): Observable<Chapter> {
|
fun getSelectedChapters(): List<Chapter> {
|
||||||
val chapters = adapter.selectedItems.map { adapter.getItem(it) }
|
return adapter.selectedItems.map { adapter.getItem(it) }
|
||||||
return Observable.from(chapters)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroyActionModeIfNeeded() {
|
fun destroyActionModeIfNeeded() {
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun onSelectAll() {
|
fun selectAll() {
|
||||||
adapter.selectAll()
|
adapter.selectAll()
|
||||||
setContextTitle(adapter.selectedItemCount)
|
setContextTitle(adapter.selectedItemCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMarkAsRead(chapters: Observable<Chapter>) {
|
fun markAsRead(chapters: List<Chapter>) {
|
||||||
presenter.markChaptersRead(chapters, true)
|
presenter.markChaptersRead(chapters, true)
|
||||||
|
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
||||||
|
deleteChapters(chapters)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMarkAsUnread(chapters: Observable<Chapter>) {
|
fun markAsUnread(chapters: List<Chapter>) {
|
||||||
presenter.markChaptersRead(chapters, false)
|
presenter.markChaptersRead(chapters, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMarkPreviousAsRead(chapter: Chapter) {
|
fun markPreviousAsRead(chapter: Chapter) {
|
||||||
presenter.markPreviousChaptersAsRead(chapter)
|
presenter.markPreviousChaptersAsRead(chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDownload(chapters: Observable<Chapter>) {
|
fun downloadChapters(chapters: List<Chapter>) {
|
||||||
DownloadService.start(activity)
|
|
||||||
|
|
||||||
val observable = chapters.doOnCompleted { adapter.notifyDataSetChanged() }
|
|
||||||
|
|
||||||
presenter.downloadChapters(observable)
|
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
|
presenter.downloadChapters(chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDelete(chapters: Observable<Chapter>) {
|
fun deleteChapters(chapters: List<Chapter>) {
|
||||||
val size = adapter.selectedItemCount
|
|
||||||
|
|
||||||
val dialog = MaterialDialog.Builder(activity)
|
|
||||||
.title(R.string.deleting)
|
|
||||||
.progress(false, size, true)
|
|
||||||
.cancelable(false)
|
|
||||||
.show()
|
|
||||||
|
|
||||||
val observable = chapters
|
|
||||||
.concatMap { chapter ->
|
|
||||||
presenter.deleteChapter(chapter)
|
|
||||||
Observable.just(chapter)
|
|
||||||
}
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext { chapter ->
|
|
||||||
dialog.incrementProgress(1)
|
|
||||||
chapter.status = Download.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
.doOnCompleted { adapter.notifyDataSetChanged() }
|
|
||||||
.doAfterTerminate { dialog.dismiss() }
|
|
||||||
|
|
||||||
presenter.deleteChapters(observable)
|
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
|
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
||||||
|
presenter.deleteChapters(chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onChaptersDeleted() {
|
||||||
|
dismissDeletingDialog()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onChaptersDeletedError(error: Throwable) {
|
||||||
|
dismissDeletingDialog()
|
||||||
|
Timber.e(error, error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissDeletingDialog() {
|
||||||
|
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onListItemClick(position: Int): Boolean {
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
|
|
|
@ -10,14 +10,16 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
import kotlinx.android.synthetic.main.item_chapter.view.*
|
import kotlinx.android.synthetic.main.item_chapter.view.*
|
||||||
import rx.Observable
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapter, listener: FlexibleViewHolder.OnListItemClickListener) :
|
class ChaptersHolder(
|
||||||
FlexibleViewHolder(view, adapter, listener) {
|
private val view: View,
|
||||||
|
private val adapter: ChaptersAdapter,
|
||||||
|
listener: FlexibleViewHolder.OnListItemClickListener)
|
||||||
|
: FlexibleViewHolder(view, adapter, listener) {
|
||||||
|
|
||||||
private val readColor = view.context.theme.getResourceColor(android.R.attr.textColorHint)
|
private val readColor = view.context.theme.getResourceColor(android.R.attr.textColorHint)
|
||||||
private val unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
|
private val unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
|
||||||
|
@ -27,7 +29,10 @@ class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapte
|
||||||
private var item: Chapter? = null
|
private var item: Chapter? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
view.chapter_menu.setOnClickListener { v -> v.post { showPopupMenu(v) } }
|
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
||||||
|
// correctly positioned. The reason being that the view may change position before the
|
||||||
|
// PopupMenu is shown.
|
||||||
|
view.chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) {
|
fun onSetValues(chapter: Chapter, manga: Manga?) = with(view) {
|
||||||
|
@ -101,14 +106,14 @@ class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapte
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
val chapter = Observable.just(item)
|
val chapter = listOf(item)
|
||||||
|
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_download -> adapter.fragment.onDownload(chapter)
|
R.id.action_download -> adapter.fragment.downloadChapters(chapter)
|
||||||
R.id.action_delete -> adapter.fragment.onDelete(chapter)
|
R.id.action_delete -> adapter.fragment.deleteChapters(chapter)
|
||||||
R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapter)
|
R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter)
|
||||||
R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapter)
|
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter)
|
||||||
R.id.action_mark_previous_as_read -> adapter.fragment.onMarkPreviousAsRead(item)
|
R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,13 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source
|
import eu.kanade.tachiyomi.data.source.base.Source
|
||||||
import eu.kanade.tachiyomi.event.ChapterCountEvent
|
import eu.kanade.tachiyomi.event.ChapterCountEvent
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent
|
import eu.kanade.tachiyomi.event.MangaEvent
|
||||||
import eu.kanade.tachiyomi.event.ReaderEvent
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
@ -163,50 +162,59 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() {
|
||||||
return db.getNextUnreadChapter(manga).executeAsBlocking()
|
return db.getNextUnreadChapter(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markChaptersRead(selectedChapters: Observable<Chapter>, read: Boolean) {
|
fun markChaptersRead(selectedChapters: List<Chapter>, read: Boolean) {
|
||||||
add(selectedChapters.subscribeOn(Schedulers.io())
|
Observable.from(selectedChapters)
|
||||||
.doOnNext { chapter ->
|
.doOnNext { chapter ->
|
||||||
chapter.read = read
|
chapter.read = read
|
||||||
if (!read) chapter.last_page_read = 0
|
if (!read) {
|
||||||
|
chapter.last_page_read = 0
|
||||||
// Delete chapter when marked as read if desired by user.
|
|
||||||
if (preferences.removeAfterMarkedAsRead() && read) {
|
|
||||||
deleteChapter(chapter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toList()
|
.toList()
|
||||||
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
|
.flatMap { db.insertChapters(it).asRxObservable() }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.subscribeOn(Schedulers.io())
|
||||||
.subscribe())
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markPreviousChaptersAsRead(selected: Chapter) {
|
fun markPreviousChaptersAsRead(selected: Chapter) {
|
||||||
Observable.from(chapters)
|
Observable.from(chapters)
|
||||||
.filter { c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number }
|
.filter { it.chapter_number > -1 && it.chapter_number < selected.chapter_number }
|
||||||
.doOnNext { c -> c.read = true }
|
.doOnNext { it.read = true }
|
||||||
.toList()
|
.toList()
|
||||||
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
|
.flatMap { db.insertChapters(it).asRxObservable() }
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapters(selectedChapters: Observable<Chapter>) {
|
fun downloadChapters(chapters: List<Chapter>) {
|
||||||
add(selectedChapters.toList()
|
DownloadService.start(context)
|
||||||
|
downloadManager.downloadChapters(manga, chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteChapters(chapters: List<Chapter>) {
|
||||||
|
val wasRunning = downloadManager.isRunning
|
||||||
|
if (wasRunning) {
|
||||||
|
DownloadService.stop(context)
|
||||||
|
}
|
||||||
|
Observable.from(chapters)
|
||||||
|
.doOnNext { deleteChapter(it) }
|
||||||
|
.toList()
|
||||||
|
.doOnNext { if (onlyDownloaded()) refreshChapters() }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { downloadManager.onDownloadChaptersEvent(DownloadChaptersEvent(manga, it)) })
|
.subscribeFirst({ view, result ->
|
||||||
|
view.onChaptersDeleted()
|
||||||
|
if (wasRunning) {
|
||||||
|
DownloadService.start(context)
|
||||||
|
}
|
||||||
|
}, { view, error ->
|
||||||
|
view.onChaptersDeletedError(error)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteChapters(selectedChapters: Observable<Chapter>) {
|
private fun deleteChapter(chapter: Chapter) {
|
||||||
add(selectedChapters.subscribe(
|
downloadManager.queue.del(chapter)
|
||||||
{ chapter -> downloadManager.queue.del(chapter) },
|
|
||||||
{ error -> Timber.e(error.message) },
|
|
||||||
{
|
|
||||||
if (onlyDownloaded())
|
|
||||||
refreshChapters()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteChapter(chapter: Chapter) {
|
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(source, manga, chapter)
|
||||||
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun revertSortOrder() {
|
fun revertSortOrder() {
|
||||||
|
|
|
@ -336,7 +336,7 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.insertChapter(chapter).asRxObservable().subscribe()
|
db.updateChapterProgress(chapter).asRxObservable().subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package eu.kanade.tachiyomi.ui.recent
|
package eu.kanade.tachiyomi.ui.recent
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
|
import eu.kanade.tachiyomi.ui.base.decoration.DividerItemDecoration
|
||||||
|
@ -16,12 +14,11 @@ import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.getResourceDrawable
|
import eu.kanade.tachiyomi.util.getResourceDrawable
|
||||||
|
import eu.kanade.tachiyomi.widget.DeletingChaptersDialog
|
||||||
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager
|
||||||
import kotlinx.android.synthetic.main.fragment_recent_chapters.*
|
import kotlinx.android.synthetic.main.fragment_recent_chapters.*
|
||||||
import nucleus.factory.RequiresPresenter
|
import nucleus.factory.RequiresPresenter
|
||||||
import rx.Observable
|
import timber.log.Timber
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that shows recent chapters.
|
* Fragment that shows recent chapters.
|
||||||
|
@ -143,78 +140,57 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Flexib
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start downloading chapter
|
* Mark chapter as read
|
||||||
|
*
|
||||||
* @param chapters selected chapters
|
* @param item selected chapter with manga
|
||||||
* @param manga manga that belongs to chapter
|
|
||||||
* @return true
|
|
||||||
*/
|
*/
|
||||||
fun onDownload(chapters: Observable<Chapter>, manga: Manga): Boolean {
|
fun markAsRead(item: MangaChapter) {
|
||||||
// Start the download service.
|
presenter.markChapterRead(item.chapter, true)
|
||||||
DownloadService.start(activity)
|
if (presenter.preferences.removeAfterMarkedAsRead()) {
|
||||||
|
deleteChapter(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh data on download competition.
|
/**
|
||||||
val observable = chapters
|
* Mark chapter as unread
|
||||||
.doOnCompleted({
|
*
|
||||||
adapter.notifyDataSetChanged()
|
* @param item selected chapter with manga
|
||||||
presenter.start(presenter.CHAPTER_STATUS_CHANGES)
|
*/
|
||||||
})
|
fun markAsUnread(item: MangaChapter) {
|
||||||
|
presenter.markChapterRead(item.chapter, false)
|
||||||
|
}
|
||||||
|
|
||||||
// Download chapter.
|
/**
|
||||||
presenter.downloadChapter(observable, manga)
|
* Start downloading chapter
|
||||||
return true
|
*
|
||||||
|
* @param item selected chapter with manga
|
||||||
|
*/
|
||||||
|
fun downloadChapter(item: MangaChapter) {
|
||||||
|
presenter.downloadChapter(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start deleting chapter
|
* Start deleting chapter
|
||||||
*
|
*
|
||||||
* @param chapters selected chapters
|
* @param item selected chapter with manga
|
||||||
* @param manga manga that belongs to chapter
|
|
||||||
* @return success of deletion.
|
|
||||||
*/
|
*/
|
||||||
fun onDelete(chapters: Observable<Chapter>, manga: Manga): Boolean {
|
fun deleteChapter(item: MangaChapter) {
|
||||||
//Create observable
|
DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG)
|
||||||
val observable = chapters
|
presenter.deleteChapter(item)
|
||||||
.concatMap { chapter ->
|
|
||||||
presenter.deleteChapter(chapter, manga)
|
|
||||||
Observable.just(chapter)
|
|
||||||
}
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext { chapter ->
|
|
||||||
chapter.status = Download.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
.doOnCompleted { adapter.notifyDataSetChanged() }
|
|
||||||
|
|
||||||
// Delete chapters with observable
|
|
||||||
presenter.deleteChapters(observable)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun onChaptersDeleted() {
|
||||||
* Mark chapter as read
|
dismissDeletingDialog()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
* @param chapters selected chapter
|
|
||||||
* @return true
|
|
||||||
*/
|
|
||||||
fun onMarkAsRead(chapters: Observable<Chapter>, manga : Manga): Boolean {
|
|
||||||
// Set marked as read
|
|
||||||
presenter.markChaptersRead(chapters, manga, true)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun onChaptersDeletedError(error: Throwable) {
|
||||||
* Mark chapter as unread
|
dismissDeletingDialog()
|
||||||
|
Timber.e(error, error.message)
|
||||||
* @param chapters selected chapter
|
|
||||||
* @return true
|
|
||||||
*/
|
|
||||||
fun onMarkAsUnread(chapters: Observable<Chapter> , manga : Manga): Boolean {
|
|
||||||
// Set marked as unread
|
|
||||||
presenter.markChaptersRead(chapters, manga, false)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dismissDeletingDialog() {
|
||||||
|
(childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,16 +1,13 @@
|
||||||
package eu.kanade.tachiyomi.ui.recent
|
package eu.kanade.tachiyomi.ui.recent
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
import kotlinx.android.synthetic.main.item_recent_chapter.view.*
|
import kotlinx.android.synthetic.main.item_recent_chapter.view.*
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holder that contains chapter item
|
* Holder that contains chapter item
|
||||||
|
@ -32,7 +29,7 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
|
||||||
/**
|
/**
|
||||||
* Color of unread chapter
|
* Color of unread chapter
|
||||||
*/
|
*/
|
||||||
private var unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
|
private var unreadColor = view.context.theme.getResourceColor(android.R.attr.textColorPrimary)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing chapter information
|
* Object containing chapter information
|
||||||
|
@ -40,9 +37,10 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
|
||||||
private var mangaChapter: MangaChapter? = null
|
private var mangaChapter: MangaChapter? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//Set OnClickListener for download menu
|
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
|
||||||
itemView.chapterMenu.setOnClickListener { v -> v.post({ showPopupMenu(v) }) }
|
// correctly positioned. The reason being that the view may change position before the
|
||||||
|
// PopupMenu is shown.
|
||||||
|
itemView.chapterMenu.setOnClickListener { it.post({ showPopupMenu(it) }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,15 +118,14 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
popup.setOnMenuItemClickListener { menuItem ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
val chapterObservable = Observable.just<Chapter>(it.chapter)
|
|
||||||
|
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_download -> adapter.fragment.onDownload(chapterObservable, it.manga)
|
R.id.action_download -> adapter.fragment.downloadChapter(it)
|
||||||
R.id.action_delete -> adapter.fragment.onDelete(chapterObservable, it.manga)
|
R.id.action_delete -> adapter.fragment.deleteChapter(it)
|
||||||
R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapterObservable, it.manga);
|
R.id.action_mark_as_read -> adapter.fragment.markAsRead(it)
|
||||||
R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapterObservable, it.manga);
|
R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(it)
|
||||||
}
|
}
|
||||||
false
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
@ -250,59 +250,69 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download selected chapter
|
* Mark selected chapter as read
|
||||||
* @param selectedChapter chapter that is selected
|
*
|
||||||
* *
|
* @param chapter selected chapter
|
||||||
* @param manga manga that belongs to chapter
|
* @param read read status
|
||||||
*/
|
*/
|
||||||
fun downloadChapter(selectedChapter: Observable<Chapter>, manga: Manga) {
|
fun markChapterRead(chapter: Chapter, read: Boolean) {
|
||||||
add(selectedChapter.toList()
|
Observable.just(chapter)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.doOnNext { chapter ->
|
||||||
.subscribe { downloadManager.onDownloadChaptersEvent(DownloadChaptersEvent(manga, it)) })
|
chapter.read = read
|
||||||
|
if (!read) {
|
||||||
|
chapter.last_page_read = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.flatMap { db.updateChapterProgress(it).asRxObservable() }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download selected chapter
|
||||||
|
*
|
||||||
|
* @param item chapter that is selected
|
||||||
|
*/
|
||||||
|
fun downloadChapter(item: MangaChapter) {
|
||||||
|
DownloadService.start(context)
|
||||||
|
downloadManager.downloadChapters(item.manga, listOf(item.chapter))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete selected chapter
|
* Delete selected chapter
|
||||||
|
*
|
||||||
|
* @param item chapter that are selected
|
||||||
|
*/
|
||||||
|
fun deleteChapter(item: MangaChapter) {
|
||||||
|
val wasRunning = downloadManager.isRunning
|
||||||
|
if (wasRunning) {
|
||||||
|
DownloadService.stop(context)
|
||||||
|
}
|
||||||
|
Observable.just(item)
|
||||||
|
.doOnNext { deleteChapter(it.chapter, it.manga) }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeFirst({ view, result ->
|
||||||
|
view.onChaptersDeleted()
|
||||||
|
if (wasRunning) {
|
||||||
|
DownloadService.start(context)
|
||||||
|
}
|
||||||
|
}, { view, error ->
|
||||||
|
view.onChaptersDeletedError(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected chapter
|
||||||
|
*
|
||||||
* @param chapter chapter that is selected
|
* @param chapter chapter that is selected
|
||||||
* *
|
|
||||||
* @param manga manga that belongs to chapter
|
* @param manga manga that belongs to chapter
|
||||||
*/
|
*/
|
||||||
fun deleteChapter(chapter: Chapter, manga: Manga) {
|
private fun deleteChapter(chapter: Chapter, manga: Manga) {
|
||||||
val source = sourceManager.get(manga.source)!!
|
val source = sourceManager.get(manga.source) ?: return
|
||||||
|
downloadManager.queue.del(chapter)
|
||||||
downloadManager.deleteChapter(source, manga, chapter)
|
downloadManager.deleteChapter(source, manga, chapter)
|
||||||
|
chapter.status = Download.NOT_DOWNLOADED
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete selected chapter observable
|
|
||||||
* @param selectedChapters chapter that are selected
|
|
||||||
*/
|
|
||||||
fun deleteChapters(selectedChapters: Observable<Chapter>) {
|
|
||||||
add(selectedChapters
|
|
||||||
.subscribe(
|
|
||||||
{ chapter -> downloadManager.queue.del(chapter) })
|
|
||||||
{ error -> Timber.e(error.message) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark selected chapter as read
|
|
||||||
* @param selectedChapters chapter that is selected
|
|
||||||
* *
|
|
||||||
* @param read read status
|
|
||||||
*/
|
|
||||||
fun markChaptersRead(selectedChapters: Observable<Chapter>, manga: Manga, read: Boolean) {
|
|
||||||
add(selectedChapters.subscribeOn(Schedulers.io())
|
|
||||||
.doOnNext { chapter ->
|
|
||||||
chapter.read = read
|
|
||||||
if (!read) chapter.last_page_read = 0
|
|
||||||
|
|
||||||
// Delete chapter when marked as read if desired by user.
|
|
||||||
if (preferences.removeAfterMarkedAsRead() && read) {
|
|
||||||
deleteChapter(chapter,manga)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
.flatMap { chapters -> db.insertChapters(chapters).asRxObservable() }
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe())
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
class DeletingChaptersDialog : DialogFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "deleting_dialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
||||||
|
return MaterialDialog.Builder(activity)
|
||||||
|
.progress(true, 0)
|
||||||
|
.content(R.string.deleting)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue