mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Database queries are now separated by table. Improve how the app creates downloads
This commit is contained in:
		| @@ -1,26 +1,17 @@ | ||||
| package eu.kanade.tachiyomi.data.database | ||||
|  | ||||
| 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.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.resolvers.LibraryMangaGetResolver | ||||
| 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.* | ||||
| import eu.kanade.tachiyomi.data.database.queries.* | ||||
|  | ||||
| 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)) | ||||
|             .addTypeMapping(Manga::class.java, MangaSQLiteTypeMapping()) | ||||
|             .addTypeMapping(Chapter::class.java, ChapterSQLiteTypeMapping()) | ||||
| @@ -29,287 +20,6 @@ open class DatabaseHelper(context: Context) { | ||||
|             .addTypeMapping(MangaCategory::class.java, MangaCategorySQLiteTypeMapping()) | ||||
|             .build() | ||||
|  | ||||
|     inline fun inTransaction(func: DatabaseHelper.() -> Unit) { | ||||
|         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() | ||||
|         } | ||||
|     } | ||||
|     inline fun inTransaction(block: () -> Unit) = db.inTransaction(block) | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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.ChapterTable as Chapter | ||||
| 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. | ||||
|  * | ||||
|  * @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} " + | ||||
|     "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" | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Query to get the categorias for a manga. | ||||
|  * | ||||
|  * @param manga the manga. | ||||
|  * Query to get the categories for a manga. | ||||
|  */ | ||||
| fun getCategoriesForMangaQuery(manga: MangaModel) = | ||||
| fun getCategoriesForMangaQuery() = | ||||
|     "SELECT ${Category.TABLE}.* FROM ${Category.TABLE} " + | ||||
|     "JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COLUMN_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.base.Source | ||||
| import eu.kanade.tachiyomi.data.source.model.Page | ||||
| import eu.kanade.tachiyomi.event.DownloadChaptersEvent | ||||
| import eu.kanade.tachiyomi.util.* | ||||
| import rx.Observable | ||||
| import rx.Subscription | ||||
| @@ -45,7 +44,8 @@ class DownloadManager(private val context: Context, private val sourceManager: S | ||||
|  | ||||
|     val PAGE_LIST_FILE = "index.json" | ||||
|  | ||||
|     @Volatile private var isRunning: Boolean = false | ||||
|     @Volatile var isRunning: Boolean = false | ||||
|         private set | ||||
|  | ||||
|     private fun initializeSubscriptions() { | ||||
|         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 | ||||
|     fun onDownloadChaptersEvent(event: DownloadChaptersEvent) { | ||||
|         val manga = event.manga | ||||
|     // Create a download object for every chapter and add them to the downloads queue | ||||
|     fun downloadChapters(manga: Manga, chapters: List<Chapter>) { | ||||
|         val source = sourceManager.get(manga.source) | ||||
|  | ||||
|         // Used to avoid downloading chapters with the same name | ||||
|         val addedChapters = ArrayList<String>() | ||||
|         val pending = ArrayList<Download>() | ||||
|  | ||||
|         for (chapter in event.chapters) { | ||||
|         for (chapter in chapters) { | ||||
|             if (addedChapters.contains(chapter.name)) | ||||
|                 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.content.Intent | ||||
| import android.os.Bundle | ||||
| import android.support.v4.app.DialogFragment | ||||
| import android.support.v7.view.ActionMode | ||||
| import android.view.* | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| @@ -11,7 +12,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| 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.download.DownloadService | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
| import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder | ||||
| 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.getResourceDrawable | ||||
| import eu.kanade.tachiyomi.util.toast | ||||
| import eu.kanade.tachiyomi.widget.DeletingChaptersDialog | ||||
| import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager | ||||
| import kotlinx.android.synthetic.main.fragment_manga_chapters.* | ||||
| import nucleus.factory.RequiresPresenter | ||||
| import rx.Observable | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.schedulers.Schedulers | ||||
| import timber.log.Timber | ||||
|  | ||||
| @RequiresPresenter(ChaptersPresenter::class) | ||||
| class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callback, FlexibleViewHolder.OnListItemClickListener { | ||||
| @@ -40,6 +39,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac | ||||
|         fun newInstance(): ChaptersFragment { | ||||
|             return ChaptersFragment() | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -73,7 +73,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac | ||||
|  | ||||
|         swipe_refresh.setOnRefreshListener { fetchChapters() } | ||||
|  | ||||
|         fab.setOnClickListener { v -> | ||||
|         fab.setOnClickListener { | ||||
|             val chapter = presenter.getNextUnreadChapter() | ||||
|             if (chapter != null) { | ||||
|                 // Create animation listener | ||||
| @@ -252,7 +252,7 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac | ||||
|                                     chapters = chapters.subList(0, 10) | ||||
|                             } | ||||
|                         } | ||||
|                         onDownload(Observable.from(chapters)) | ||||
|                         downloadChapters(chapters) | ||||
|                     } | ||||
|                 } | ||||
|                 .show() | ||||
| @@ -278,11 +278,11 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac | ||||
|  | ||||
|     override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { | ||||
|         when (item.itemId) { | ||||
|             R.id.action_select_all -> onSelectAll() | ||||
|             R.id.action_mark_as_read -> onMarkAsRead(getSelectedChapters()) | ||||
|             R.id.action_mark_as_unread -> onMarkAsUnread(getSelectedChapters()) | ||||
|             R.id.action_download -> onDownload(getSelectedChapters()) | ||||
|             R.id.action_delete -> onDelete(getSelectedChapters()) | ||||
|             R.id.action_select_all -> selectAll() | ||||
|             R.id.action_mark_as_read -> markAsRead(getSelectedChapters()) | ||||
|             R.id.action_mark_as_unread -> markAsUnread(getSelectedChapters()) | ||||
|             R.id.action_download -> downloadChapters(getSelectedChapters()) | ||||
|             R.id.action_delete -> deleteChapters(getSelectedChapters()) | ||||
|             else -> return false | ||||
|         } | ||||
|         return true | ||||
| @@ -294,66 +294,57 @@ class ChaptersFragment : BaseRxFragment<ChaptersPresenter>(), ActionMode.Callbac | ||||
|         actionMode = null | ||||
|     } | ||||
|  | ||||
|     fun getSelectedChapters(): Observable<Chapter> { | ||||
|         val chapters = adapter.selectedItems.map { adapter.getItem(it) } | ||||
|         return Observable.from(chapters) | ||||
|     fun getSelectedChapters(): List<Chapter> { | ||||
|         return adapter.selectedItems.map { adapter.getItem(it) } | ||||
|     } | ||||
|  | ||||
|     fun destroyActionModeIfNeeded() { | ||||
|         actionMode?.finish() | ||||
|     } | ||||
|  | ||||
|     protected fun onSelectAll() { | ||||
|     fun selectAll() { | ||||
|         adapter.selectAll() | ||||
|         setContextTitle(adapter.selectedItemCount) | ||||
|     } | ||||
|  | ||||
|     fun onMarkAsRead(chapters: Observable<Chapter>) { | ||||
|     fun markAsRead(chapters: List<Chapter>) { | ||||
|         presenter.markChaptersRead(chapters, true) | ||||
|         if (presenter.preferences.removeAfterMarkedAsRead()) { | ||||
|             deleteChapters(chapters) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun onMarkAsUnread(chapters: Observable<Chapter>) { | ||||
|     fun markAsUnread(chapters: List<Chapter>) { | ||||
|         presenter.markChaptersRead(chapters, false) | ||||
|     } | ||||
|  | ||||
|     fun onMarkPreviousAsRead(chapter: Chapter) { | ||||
|     fun markPreviousAsRead(chapter: Chapter) { | ||||
|         presenter.markPreviousChaptersAsRead(chapter) | ||||
|     } | ||||
|  | ||||
|     fun onDownload(chapters: Observable<Chapter>) { | ||||
|         DownloadService.start(activity) | ||||
|  | ||||
|         val observable = chapters.doOnCompleted { adapter.notifyDataSetChanged() } | ||||
|  | ||||
|         presenter.downloadChapters(observable) | ||||
|     fun downloadChapters(chapters: List<Chapter>) { | ||||
|         destroyActionModeIfNeeded() | ||||
|         presenter.downloadChapters(chapters) | ||||
|     } | ||||
|  | ||||
|     fun onDelete(chapters: Observable<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) | ||||
|     fun deleteChapters(chapters: List<Chapter>) { | ||||
|         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 { | ||||
|   | ||||
| @@ -10,14 +10,16 @@ import eu.kanade.tachiyomi.data.download.model.Download | ||||
| import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder | ||||
| import eu.kanade.tachiyomi.util.getResourceColor | ||||
| import kotlinx.android.synthetic.main.item_chapter.view.* | ||||
| import rx.Observable | ||||
| import java.text.DateFormat | ||||
| import java.text.DecimalFormat | ||||
| import java.text.DecimalFormatSymbols | ||||
| import java.util.* | ||||
|  | ||||
| class ChaptersHolder(private val view: View, private val adapter: ChaptersAdapter, listener: FlexibleViewHolder.OnListItemClickListener) : | ||||
|         FlexibleViewHolder(view, adapter, listener) { | ||||
| class ChaptersHolder( | ||||
|         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 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 | ||||
|  | ||||
|     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) { | ||||
| @@ -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 | ||||
|         popup.setOnMenuItemClickListener { menuItem -> | ||||
|             val chapter = Observable.just(item) | ||||
|             val chapter = listOf(item) | ||||
|  | ||||
|             when (menuItem.itemId) { | ||||
|                 R.id.action_download -> adapter.fragment.onDownload(chapter) | ||||
|                 R.id.action_delete -> adapter.fragment.onDelete(chapter) | ||||
|                 R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapter) | ||||
|                 R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapter) | ||||
|                 R.id.action_mark_previous_as_read -> adapter.fragment.onMarkPreviousAsRead(item) | ||||
|                 R.id.action_download -> adapter.fragment.downloadChapters(chapter) | ||||
|                 R.id.action_delete -> adapter.fragment.deleteChapters(chapter) | ||||
|                 R.id.action_mark_as_read -> adapter.fragment.markAsRead(chapter) | ||||
|                 R.id.action_mark_as_unread -> adapter.fragment.markAsUnread(chapter) | ||||
|                 R.id.action_mark_previous_as_read -> adapter.fragment.markPreviousAsRead(item) | ||||
|             } | ||||
|             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.Manga | ||||
| 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.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.source.SourceManager | ||||
| import eu.kanade.tachiyomi.data.source.base.Source | ||||
| import eu.kanade.tachiyomi.event.ChapterCountEvent | ||||
| import eu.kanade.tachiyomi.event.DownloadChaptersEvent | ||||
| import eu.kanade.tachiyomi.event.MangaEvent | ||||
| import eu.kanade.tachiyomi.event.ReaderEvent | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import eu.kanade.tachiyomi.util.SharedData | ||||
| import rx.Observable | ||||
| @@ -163,50 +162,59 @@ class ChaptersPresenter : BasePresenter<ChaptersFragment>() { | ||||
|         return db.getNextUnreadChapter(manga).executeAsBlocking() | ||||
|     } | ||||
|  | ||||
|     fun markChaptersRead(selectedChapters: Observable<Chapter>, read: Boolean) { | ||||
|         add(selectedChapters.subscribeOn(Schedulers.io()) | ||||
|     fun markChaptersRead(selectedChapters: List<Chapter>, read: Boolean) { | ||||
|         Observable.from(selectedChapters) | ||||
|                 .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) | ||||
|                     if (!read) { | ||||
|                         chapter.last_page_read = 0 | ||||
|                     } | ||||
|                 } | ||||
|                 .toList() | ||||
|                 .flatMap { chapters -> db.insertChapters(chapters).asRxObservable() } | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe()) | ||||
|                 .flatMap { db.insertChapters(it).asRxObservable() } | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .subscribe() | ||||
|     } | ||||
|  | ||||
|     fun markPreviousChaptersAsRead(selected: Chapter) { | ||||
|         Observable.from(chapters) | ||||
|                 .filter { c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number } | ||||
|                 .doOnNext { c -> c.read = true } | ||||
|                 .filter { it.chapter_number > -1 && it.chapter_number < selected.chapter_number } | ||||
|                 .doOnNext { it.read = true } | ||||
|                 .toList() | ||||
|                 .flatMap { chapters -> db.insertChapters(chapters).asRxObservable() } | ||||
|                 .flatMap { db.insertChapters(it).asRxObservable() } | ||||
|                 .subscribe() | ||||
|     } | ||||
|  | ||||
|     fun downloadChapters(selectedChapters: Observable<Chapter>) { | ||||
|         add(selectedChapters.toList() | ||||
|     fun downloadChapters(chapters: List<Chapter>) { | ||||
|         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()) | ||||
|                 .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>) { | ||||
|         add(selectedChapters.subscribe( | ||||
|                 { chapter -> downloadManager.queue.del(chapter) }, | ||||
|                 { error -> Timber.e(error.message) }, | ||||
|                 { | ||||
|                     if (onlyDownloaded()) | ||||
|                         refreshChapters() | ||||
|                 })) | ||||
|     } | ||||
|  | ||||
|     fun deleteChapter(chapter: Chapter) { | ||||
|     private fun deleteChapter(chapter: Chapter) { | ||||
|         downloadManager.queue.del(chapter) | ||||
|         downloadManager.deleteChapter(source, manga, chapter) | ||||
|         chapter.status = Download.NOT_DOWNLOADED | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.support.v4.app.DialogFragment | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| 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.download.DownloadService | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
| import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder | ||||
| 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.reader.ReaderActivity | ||||
| import eu.kanade.tachiyomi.util.getResourceDrawable | ||||
| import eu.kanade.tachiyomi.widget.DeletingChaptersDialog | ||||
| import eu.kanade.tachiyomi.widget.NpaLinearLayoutManager | ||||
| import kotlinx.android.synthetic.main.fragment_recent_chapters.* | ||||
| import nucleus.factory.RequiresPresenter | ||||
| import rx.Observable | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.schedulers.Schedulers | ||||
| import timber.log.Timber | ||||
|  | ||||
| /** | ||||
|  * Fragment that shows recent chapters. | ||||
| @@ -143,78 +140,57 @@ class RecentChaptersFragment : BaseRxFragment<RecentChaptersPresenter>(), Flexib | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start downloading chapter | ||||
|  | ||||
|      * @param chapters selected chapters | ||||
|      * @param manga    manga that belongs to chapter | ||||
|      * @return true | ||||
|      * Mark chapter as read | ||||
|      * | ||||
|      * @param item selected chapter with manga | ||||
|      */ | ||||
|     fun onDownload(chapters: Observable<Chapter>, manga: Manga): Boolean { | ||||
|         // Start the download service. | ||||
|         DownloadService.start(activity) | ||||
|     fun markAsRead(item: MangaChapter) { | ||||
|         presenter.markChapterRead(item.chapter, true) | ||||
|         if (presenter.preferences.removeAfterMarkedAsRead()) { | ||||
|             deleteChapter(item) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         // Refresh data on download competition. | ||||
|         val observable = chapters | ||||
|                 .doOnCompleted({ | ||||
|                     adapter.notifyDataSetChanged() | ||||
|                     presenter.start(presenter.CHAPTER_STATUS_CHANGES) | ||||
|                 }) | ||||
|     /** | ||||
|      * Mark chapter as unread | ||||
|      * | ||||
|      * @param item selected chapter with manga | ||||
|      */ | ||||
|     fun markAsUnread(item: MangaChapter) { | ||||
|         presenter.markChapterRead(item.chapter, false) | ||||
|     } | ||||
|  | ||||
|         // Download chapter. | ||||
|         presenter.downloadChapter(observable, manga) | ||||
|         return true | ||||
|     /** | ||||
|      * Start downloading chapter | ||||
|      * | ||||
|      * @param item selected chapter with manga | ||||
|      */ | ||||
|     fun downloadChapter(item: MangaChapter) { | ||||
|         presenter.downloadChapter(item) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start deleting chapter | ||||
|      * | ||||
|      * @param chapters selected chapters | ||||
|      * @param manga manga that belongs to chapter | ||||
|      * @return success of deletion. | ||||
|      * @param item selected chapter with manga | ||||
|      */ | ||||
|     fun onDelete(chapters: Observable<Chapter>, manga: Manga): Boolean { | ||||
|         //Create observable | ||||
|         val observable = chapters | ||||
|                 .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 deleteChapter(item: MangaChapter) { | ||||
|         DeletingChaptersDialog().show(childFragmentManager, DeletingChaptersDialog.TAG) | ||||
|         presenter.deleteChapter(item) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mark chapter as read | ||||
|  | ||||
|      * @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 onChaptersDeleted() { | ||||
|         dismissDeletingDialog() | ||||
|         adapter.notifyDataSetChanged() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mark chapter as unread | ||||
|  | ||||
|      * @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 onChaptersDeletedError(error: Throwable) { | ||||
|         dismissDeletingDialog() | ||||
|         Timber.e(error, error.message) | ||||
|     } | ||||
|  | ||||
|     fun dismissDeletingDialog() { | ||||
|         (childFragmentManager.findFragmentByTag(DeletingChaptersDialog.TAG) as? DialogFragment)?.dismiss() | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,16 +1,13 @@ | ||||
| package eu.kanade.tachiyomi.ui.recent | ||||
|  | ||||
| import android.content.Context | ||||
| import android.view.View | ||||
| import android.widget.PopupMenu | ||||
| 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.download.model.Download | ||||
| import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder | ||||
| import eu.kanade.tachiyomi.util.getResourceColor | ||||
| import kotlinx.android.synthetic.main.item_recent_chapter.view.* | ||||
| import rx.Observable | ||||
|  | ||||
| /** | ||||
|  * Holder that contains chapter item | ||||
| @@ -32,7 +29,7 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte | ||||
|     /** | ||||
|      * 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 | ||||
| @@ -40,9 +37,10 @@ class RecentChaptersHolder(view: View, private val adapter: RecentChaptersAdapte | ||||
|     private var mangaChapter: MangaChapter? = null | ||||
|  | ||||
|     init { | ||||
|         //Set OnClickListener for download menu | ||||
|         itemView.chapterMenu.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. | ||||
|         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 | ||||
|             popup.setOnMenuItemClickListener { menuItem -> | ||||
|                 val chapterObservable = Observable.just<Chapter>(it.chapter) | ||||
|  | ||||
|                 when (menuItem.itemId) { | ||||
|                     R.id.action_download -> adapter.fragment.onDownload(chapterObservable, it.manga) | ||||
|                     R.id.action_delete -> adapter.fragment.onDelete(chapterObservable, it.manga) | ||||
|                     R.id.action_mark_as_read -> adapter.fragment.onMarkAsRead(chapterObservable, it.manga); | ||||
|                     R.id.action_mark_as_unread -> adapter.fragment.onMarkAsUnread(chapterObservable, it.manga); | ||||
|                     R.id.action_download -> adapter.fragment.downloadChapter(it) | ||||
|                     R.id.action_delete -> adapter.fragment.deleteChapter(it) | ||||
|                     R.id.action_mark_as_read -> adapter.fragment.markAsRead(it) | ||||
|                     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.MangaChapter | ||||
| 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.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.source.SourceManager | ||||
| import eu.kanade.tachiyomi.event.DownloadChaptersEvent | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import rx.Observable | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| @@ -250,59 +250,69 @@ class RecentChaptersPresenter : BasePresenter<RecentChaptersFragment>() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Download selected chapter | ||||
|      * @param selectedChapter chapter that is selected | ||||
|      * * | ||||
|      * @param manga manga that belongs to chapter | ||||
|      * Mark selected chapter as read | ||||
|      * | ||||
|      * @param chapter selected chapter | ||||
|      * @param read read status | ||||
|      */ | ||||
|     fun downloadChapter(selectedChapter: Observable<Chapter>, manga: Manga) { | ||||
|         add(selectedChapter.toList() | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe { downloadManager.onDownloadChaptersEvent(DownloadChaptersEvent(manga, it)) }) | ||||
|     fun markChapterRead(chapter: Chapter, read: Boolean) { | ||||
|         Observable.just(chapter) | ||||
|                 .doOnNext { chapter -> | ||||
|                     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 | ||||
|      * | ||||
|      * @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 manga manga that belongs to chapter | ||||
|      */ | ||||
|     fun deleteChapter(chapter: Chapter, manga: Manga) { | ||||
|         val source = sourceManager.get(manga.source)!! | ||||
|     private fun deleteChapter(chapter: Chapter, manga: Manga) { | ||||
|         val source = sourceManager.get(manga.source) ?: return | ||||
|         downloadManager.queue.del(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() | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user