package eu.kanade.mangafeed.data.database; import android.content.Context; import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping; import com.pushtorefresh.storio.sqlite.StorIOSQLite; import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite; import com.pushtorefresh.storio.sqlite.operations.delete.PreparedDeleteCollectionOfObjects; import com.pushtorefresh.storio.sqlite.operations.delete.PreparedDeleteObject; import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects; import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutCollectionOfObjects; import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutObject; import com.pushtorefresh.storio.sqlite.operations.put.PutResults; import com.pushtorefresh.storio.sqlite.queries.Query; import com.pushtorefresh.storio.sqlite.queries.RawQuery; import java.util.List; import eu.kanade.mangafeed.data.database.models.Category; import eu.kanade.mangafeed.data.database.models.CategoryStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.CategoryStorIOSQLiteGetResolver; import eu.kanade.mangafeed.data.database.models.CategoryStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.models.Chapter; import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteGetResolver; import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.models.Manga; import eu.kanade.mangafeed.data.database.models.MangaCategory; import eu.kanade.mangafeed.data.database.models.MangaCategoryStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.MangaCategoryStorIOSQLiteGetResolver; import eu.kanade.mangafeed.data.database.models.MangaCategoryStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteGetResolver; import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.models.MangaSync; import eu.kanade.mangafeed.data.database.models.MangaSyncStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.MangaSyncStorIOSQLiteGetResolver; import eu.kanade.mangafeed.data.database.models.MangaSyncStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.resolvers.LibraryMangaGetResolver; import eu.kanade.mangafeed.data.database.tables.CategoryTable; import eu.kanade.mangafeed.data.database.tables.ChapterTable; import eu.kanade.mangafeed.data.database.tables.MangaSyncTable; import eu.kanade.mangafeed.data.database.tables.MangaTable; import eu.kanade.mangafeed.data.mangasync.base.MangaSyncService; import eu.kanade.mangafeed.util.ChapterRecognition; import eu.kanade.mangafeed.util.PostResult; import rx.Observable; public class DatabaseHelper { private StorIOSQLite db; public DatabaseHelper(Context context) { db = DefaultStorIOSQLite.builder() .sqliteOpenHelper(new DbOpenHelper(context)) .addTypeMapping(Manga.class, SQLiteTypeMapping.builder() .putResolver(new MangaStorIOSQLitePutResolver()) .getResolver(new MangaStorIOSQLiteGetResolver()) .deleteResolver(new MangaStorIOSQLiteDeleteResolver()) .build()) .addTypeMapping(Chapter.class, SQLiteTypeMapping.builder() .putResolver(new ChapterStorIOSQLitePutResolver()) .getResolver(new ChapterStorIOSQLiteGetResolver()) .deleteResolver(new ChapterStorIOSQLiteDeleteResolver()) .build()) .addTypeMapping(MangaSync.class, SQLiteTypeMapping.builder() .putResolver(new MangaSyncStorIOSQLitePutResolver()) .getResolver(new MangaSyncStorIOSQLiteGetResolver()) .deleteResolver(new MangaSyncStorIOSQLiteDeleteResolver()) .build()) .addTypeMapping(Category.class, SQLiteTypeMapping.builder() .putResolver(new CategoryStorIOSQLitePutResolver()) .getResolver(new CategoryStorIOSQLiteGetResolver()) .deleteResolver(new CategoryStorIOSQLiteDeleteResolver()) .build()) .addTypeMapping(MangaCategory.class, SQLiteTypeMapping.builder() .putResolver(new MangaCategoryStorIOSQLitePutResolver()) .getResolver(new MangaCategoryStorIOSQLiteGetResolver()) .deleteResolver(new MangaCategoryStorIOSQLiteDeleteResolver()) .build()) .build(); } // Mangas related queries public PreparedGetListOfObjects getMangas() { return db.get() .listOfObjects(Manga.class) .withQuery(Query.builder() .table(MangaTable.TABLE) .build()) .prepare(); } public PreparedGetListOfObjects getLibraryMangas() { return db.get() .listOfObjects(Manga.class) .withQuery(RawQuery.builder() .query(LibraryMangaGetResolver.QUERY) .observesTables(MangaTable.TABLE, ChapterTable.TABLE, CategoryTable.TABLE) .build()) .withGetResolver(LibraryMangaGetResolver.INSTANCE) .prepare(); } public PreparedGetListOfObjects getFavoriteMangas() { return db.get() .listOfObjects(Manga.class) .withQuery(Query.builder() .table(MangaTable.TABLE) .where(MangaTable.COLUMN_FAVORITE + "=?") .whereArgs(1) .build()) .prepare(); } public PreparedGetListOfObjects getManga(String url, int sourceId) { return db.get() .listOfObjects(Manga.class) .withQuery(Query.builder() .table(MangaTable.TABLE) .where(MangaTable.COLUMN_URL + "=? AND " + MangaTable.COLUMN_SOURCE + "=?") .whereArgs(url, sourceId) .build()) .prepare(); } public PreparedGetListOfObjects getManga(long id) { return db.get() .listOfObjects(Manga.class) .withQuery(Query.builder() .table(MangaTable.TABLE) .where(MangaTable.COLUMN_ID + "=?") .whereArgs(id) .build()) .prepare(); } public PreparedPutObject insertManga(Manga manga) { return db.put() .object(manga) .prepare(); } public PreparedPutCollectionOfObjects insertMangas(List mangas) { return db.put() .objects(mangas) .prepare(); } public PreparedDeleteObject deleteManga(Manga manga) { return db.delete() .object(manga) .prepare(); } public PreparedDeleteCollectionOfObjects deleteMangas(List mangas) { return db.delete() .objects(mangas) .prepare(); } // Chapters related queries public PreparedGetListOfObjects getChapters(Manga manga) { return db.get() .listOfObjects(Chapter.class) .withQuery(Query.builder() .table(ChapterTable.TABLE) .where(ChapterTable.COLUMN_MANGA_ID + "=?") .whereArgs(manga.id) .build()) .prepare(); } public PreparedGetListOfObjects getChapters(long manga_id, boolean sortAToZ, boolean onlyUnread) { Query.CompleteBuilder query = Query.builder() .table(ChapterTable.TABLE) .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + (sortAToZ ? " ASC" : " DESC")); if (onlyUnread) { query = query.where(ChapterTable.COLUMN_MANGA_ID + "=? AND " + ChapterTable.COLUMN_READ + "=?") .whereArgs(manga_id, 0); } else { query = query.where(ChapterTable.COLUMN_MANGA_ID + "=?") .whereArgs(manga_id); } return db.get() .listOfObjects(Chapter.class) .withQuery(query.build()) .prepare(); } public PreparedGetListOfObjects getNextChapter(Chapter chapter) { // Add a delta to the chapter number, because binary decimal representation // can retrieve the same chapter again double chapterNumber = chapter.chapter_number + 0.00001; return db.get() .listOfObjects(Chapter.class) .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(); } public PreparedGetListOfObjects getPreviousChapter(Chapter chapter) { // Add a delta to the chapter number, because binary decimal representation // can retrieve the same chapter again double chapterNumber = chapter.chapter_number - 0.00001; return db.get() .listOfObjects(Chapter.class) .withQuery(Query.builder() .table(ChapterTable.TABLE) .where(ChapterTable.COLUMN_MANGA_ID + "=? AND " + ChapterTable.COLUMN_CHAPTER_NUMBER + "=?") .whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1) .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + " DESC") .limit(1) .build()) .prepare(); } public PreparedGetListOfObjects getNextUnreadChapter(Manga manga) { return db.get() .listOfObjects(Chapter.class) .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(); } public PreparedPutObject insertChapter(Chapter chapter) { return db.put() .object(chapter) .prepare(); } public PreparedPutCollectionOfObjects insertChapters(List chapters) { return db.put() .objects(chapters) .prepare(); } // Add new chapters or delete if the source deletes them public Observable insertOrRemoveChapters(Manga manga, List chapters) { for (Chapter chapter : chapters) { chapter.manga_id = manga.id; } Observable> chapterList = Observable.create(subscriber -> { subscriber.onNext(getChapters(manga).executeAsBlocking()); subscriber.onCompleted(); }); Observable newChaptersObs = chapterList .flatMap(dbChapters -> Observable.from(chapters) .filter(c -> !dbChapters.contains(c)) .map(c -> { ChapterRecognition.parseChapterNumber(c, manga); return c; }) .toList() .flatMap(newChapters -> insertChapters(newChapters).createObservable()) .map(PutResults::numberOfInserts)); Observable deletedChaptersObs = chapterList .flatMap(dbChapters -> Observable.from(dbChapters) .filter(c -> !chapters.contains(c)) .toList() .flatMap(deletedChapters -> deleteChapters(deletedChapters).createObservable()) .map(d -> d.results().size())); return Observable.zip(newChaptersObs, deletedChaptersObs, (insertions, deletions) -> new PostResult(0, insertions, deletions) ); } public PreparedDeleteObject deleteChapter(Chapter chapter) { return db.delete() .object(chapter) .prepare(); } public PreparedDeleteCollectionOfObjects deleteChapters(List chapters) { return db.delete() .objects(chapters) .prepare(); } // Manga sync related queries public PreparedGetListOfObjects getMangaSync(Manga manga, MangaSyncService sync) { return db.get() .listOfObjects(MangaSync.class) .withQuery(Query.builder() .table(MangaSyncTable.TABLE) .where(MangaSyncTable.COLUMN_MANGA_ID + "=? AND " + MangaSyncTable.COLUMN_SYNC_ID + "=?") .whereArgs(manga.id, sync.getId()) .build()) .prepare(); } public PreparedGetListOfObjects getMangaSync(Manga manga) { return db.get() .listOfObjects(MangaSync.class) .withQuery(Query.builder() .table(MangaSyncTable.TABLE) .where(MangaSyncTable.COLUMN_MANGA_ID + "=?") .whereArgs(manga.id) .build()) .prepare(); } public PreparedPutObject insertMangaSync(MangaSync manga) { return db.put() .object(manga) .prepare(); } public PreparedDeleteObject deleteMangaSync(MangaSync manga) { return db.delete() .object(manga) .prepare(); } // Categories related queries public PreparedGetListOfObjects getCategories() { return db.get() .listOfObjects(Category.class) .withQuery(Query.builder() .table(CategoryTable.TABLE) .build()) .prepare(); } public PreparedPutObject insertCategory(Category category) { return db.put() .object(category) .prepare(); } public PreparedDeleteObject deleteCategory(Category category) { return db.delete() .object(category) .prepare(); } public PreparedPutObject insertMangaCategory(MangaCategory mangaCategory) { return db.put() .object(mangaCategory) .prepare(); } public PreparedPutCollectionOfObjects insertMangasCategory(List mangasCategory) { return db.put() .objects(mangasCategory) .prepare(); } }