DatabaseHelper.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. package eu.kanade.mangafeed.data.database;
  2. import android.content.Context;
  3. import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping;
  4. import com.pushtorefresh.storio.sqlite.StorIOSQLite;
  5. import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite;
  6. import com.pushtorefresh.storio.sqlite.operations.delete.PreparedDeleteCollectionOfObjects;
  7. import com.pushtorefresh.storio.sqlite.operations.delete.PreparedDeleteObject;
  8. import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects;
  9. import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutCollectionOfObjects;
  10. import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutObject;
  11. import com.pushtorefresh.storio.sqlite.operations.put.PutResults;
  12. import com.pushtorefresh.storio.sqlite.queries.Query;
  13. import com.pushtorefresh.storio.sqlite.queries.RawQuery;
  14. import java.util.List;
  15. import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync;
  16. import eu.kanade.mangafeed.data.database.models.Chapter;
  17. import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteDeleteResolver;
  18. import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteGetResolver;
  19. import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLitePutResolver;
  20. import eu.kanade.mangafeed.data.database.models.ChapterSync;
  21. import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLiteDeleteResolver;
  22. import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLiteGetResolver;
  23. import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLitePutResolver;
  24. import eu.kanade.mangafeed.data.database.models.Manga;
  25. import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteDeleteResolver;
  26. import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteGetResolver;
  27. import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLitePutResolver;
  28. import eu.kanade.mangafeed.data.database.resolvers.MangaWithUnreadGetResolver;
  29. import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable;
  30. import eu.kanade.mangafeed.data.database.tables.ChapterTable;
  31. import eu.kanade.mangafeed.data.database.tables.MangaTable;
  32. import eu.kanade.mangafeed.util.ChapterRecognition;
  33. import eu.kanade.mangafeed.util.PostResult;
  34. import rx.Observable;
  35. public class DatabaseHelper {
  36. private StorIOSQLite db;
  37. public DatabaseHelper(Context context) {
  38. db = DefaultStorIOSQLite.builder()
  39. .sqliteOpenHelper(new DbOpenHelper(context))
  40. .addTypeMapping(Manga.class, SQLiteTypeMapping.<Manga>builder()
  41. .putResolver(new MangaStorIOSQLitePutResolver())
  42. .getResolver(new MangaStorIOSQLiteGetResolver())
  43. .deleteResolver(new MangaStorIOSQLiteDeleteResolver())
  44. .build())
  45. .addTypeMapping(Chapter.class, SQLiteTypeMapping.<Chapter>builder()
  46. .putResolver(new ChapterStorIOSQLitePutResolver())
  47. .getResolver(new ChapterStorIOSQLiteGetResolver())
  48. .deleteResolver(new ChapterStorIOSQLiteDeleteResolver())
  49. .build())
  50. .addTypeMapping(ChapterSync.class, SQLiteTypeMapping.<ChapterSync>builder()
  51. .putResolver(new ChapterSyncStorIOSQLitePutResolver())
  52. .getResolver(new ChapterSyncStorIOSQLiteGetResolver())
  53. .deleteResolver(new ChapterSyncStorIOSQLiteDeleteResolver())
  54. .build())
  55. .build();
  56. }
  57. // Mangas related queries
  58. private final String favoriteMangasWithUnreadQuery = String.format(
  59. "SELECT %1$s.*, COUNT(C.%4$s) AS %5$s FROM %1$s LEFT JOIN " +
  60. "(SELECT %4$s FROM %2$s WHERE %6$s = 0) AS C ON %3$s = C.%4$s " +
  61. "WHERE %7$s = 1 GROUP BY %3$s ORDER BY %1$s.%8$s",
  62. MangaTable.TABLE,
  63. ChapterTable.TABLE,
  64. MangaTable.TABLE + "." + MangaTable.COLUMN_ID,
  65. ChapterTable.COLUMN_MANGA_ID,
  66. MangaTable.COLUMN_UNREAD,
  67. ChapterTable.COLUMN_READ,
  68. MangaTable.COLUMN_FAVORITE,
  69. MangaTable.COLUMN_TITLE
  70. );
  71. public PreparedGetListOfObjects<Manga> getMangas() {
  72. return db.get()
  73. .listOfObjects(Manga.class)
  74. .withQuery(Query.builder()
  75. .table(MangaTable.TABLE)
  76. .build())
  77. .prepare();
  78. }
  79. public PreparedGetListOfObjects<Manga> getMangasWithUnread() {
  80. return db.get()
  81. .listOfObjects(Manga.class)
  82. .withQuery(RawQuery.builder()
  83. .query(favoriteMangasWithUnreadQuery)
  84. .observesTables(MangaTable.TABLE, ChapterTable.TABLE)
  85. .build())
  86. .withGetResolver(MangaWithUnreadGetResolver.instance)
  87. .prepare();
  88. }
  89. public PreparedGetListOfObjects<Manga> getFavoriteMangas() {
  90. return db.get()
  91. .listOfObjects(Manga.class)
  92. .withQuery(Query.builder()
  93. .table(MangaTable.TABLE)
  94. .where(MangaTable.COLUMN_FAVORITE + "=?")
  95. .whereArgs(1)
  96. .build())
  97. .prepare();
  98. }
  99. public PreparedGetListOfObjects<Manga> getManga(String url, int sourceId) {
  100. return db.get()
  101. .listOfObjects(Manga.class)
  102. .withQuery(Query.builder()
  103. .table(MangaTable.TABLE)
  104. .where(MangaTable.COLUMN_URL + "=? AND " + MangaTable.COLUMN_SOURCE + "=?")
  105. .whereArgs(url, sourceId)
  106. .build())
  107. .prepare();
  108. }
  109. public PreparedGetListOfObjects<Manga> getManga(long id) {
  110. return db.get()
  111. .listOfObjects(Manga.class)
  112. .withQuery(Query.builder()
  113. .table(MangaTable.TABLE)
  114. .where(MangaTable.COLUMN_ID + "=?")
  115. .whereArgs(id)
  116. .build())
  117. .prepare();
  118. }
  119. public PreparedPutObject<Manga> insertManga(Manga manga) {
  120. return db.put()
  121. .object(manga)
  122. .prepare();
  123. }
  124. public PreparedPutCollectionOfObjects<Manga> insertMangas(List<Manga> mangas) {
  125. return db.put()
  126. .objects(mangas)
  127. .prepare();
  128. }
  129. public PreparedDeleteObject<Manga> deleteManga(Manga manga) {
  130. return db.delete()
  131. .object(manga)
  132. .prepare();
  133. }
  134. public PreparedDeleteCollectionOfObjects<Manga> deleteMangas(List<Manga> mangas) {
  135. return db.delete()
  136. .objects(mangas)
  137. .prepare();
  138. }
  139. // Chapters related queries
  140. public PreparedGetListOfObjects<Chapter> getChapters(Manga manga) {
  141. return db.get()
  142. .listOfObjects(Chapter.class)
  143. .withQuery(Query.builder()
  144. .table(ChapterTable.TABLE)
  145. .where(ChapterTable.COLUMN_MANGA_ID + "=?")
  146. .whereArgs(manga.id)
  147. .build())
  148. .prepare();
  149. }
  150. public PreparedGetListOfObjects<Chapter> getChapters(long manga_id, boolean sortAToZ, boolean onlyUnread) {
  151. Query.CompleteBuilder query = Query.builder()
  152. .table(ChapterTable.TABLE)
  153. .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + (sortAToZ ? " ASC" : " DESC"));
  154. if (onlyUnread) {
  155. query = query.where(ChapterTable.COLUMN_MANGA_ID + "=? AND " + ChapterTable.COLUMN_READ + "=?")
  156. .whereArgs(manga_id, 0);
  157. } else {
  158. query = query.where(ChapterTable.COLUMN_MANGA_ID + "=?")
  159. .whereArgs(manga_id);
  160. }
  161. return db.get()
  162. .listOfObjects(Chapter.class)
  163. .withQuery(query.build())
  164. .prepare();
  165. }
  166. public PreparedGetListOfObjects<Chapter> getNextChapter(Chapter chapter) {
  167. // Add a delta to the chapter number, because binary decimal representation
  168. // can retrieve the same chapter again
  169. double chapterNumber = chapter.chapter_number + 0.00001;
  170. return db.get()
  171. .listOfObjects(Chapter.class)
  172. .withQuery(Query.builder()
  173. .table(ChapterTable.TABLE)
  174. .where(ChapterTable.COLUMN_MANGA_ID + "=? AND " +
  175. ChapterTable.COLUMN_CHAPTER_NUMBER + ">? AND " +
  176. ChapterTable.COLUMN_CHAPTER_NUMBER + "<=?")
  177. .whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
  178. .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
  179. .limit(1)
  180. .build())
  181. .prepare();
  182. }
  183. public PreparedGetListOfObjects<Chapter> getPreviousChapter(Chapter chapter) {
  184. // Add a delta to the chapter number, because binary decimal representation
  185. // can retrieve the same chapter again
  186. double chapterNumber = chapter.chapter_number - 0.00001;
  187. return db.get()
  188. .listOfObjects(Chapter.class)
  189. .withQuery(Query.builder()
  190. .table(ChapterTable.TABLE)
  191. .where(ChapterTable.COLUMN_MANGA_ID + "=? AND " +
  192. ChapterTable.COLUMN_CHAPTER_NUMBER + "<? AND " +
  193. ChapterTable.COLUMN_CHAPTER_NUMBER + ">=?")
  194. .whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
  195. .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + " DESC")
  196. .limit(1)
  197. .build())
  198. .prepare();
  199. }
  200. public PreparedGetListOfObjects<Chapter> getNextUnreadChapter(Manga manga) {
  201. return db.get()
  202. .listOfObjects(Chapter.class)
  203. .withQuery(Query.builder()
  204. .table(ChapterTable.TABLE)
  205. .where(ChapterTable.COLUMN_MANGA_ID + "=? AND " +
  206. ChapterTable.COLUMN_READ + "=? AND " +
  207. ChapterTable.COLUMN_CHAPTER_NUMBER + ">=?")
  208. .whereArgs(manga.id, 0, 0)
  209. .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
  210. .limit(1)
  211. .build())
  212. .prepare();
  213. }
  214. public PreparedPutObject<Chapter> insertChapter(Chapter chapter) {
  215. return db.put()
  216. .object(chapter)
  217. .prepare();
  218. }
  219. public PreparedPutCollectionOfObjects<Chapter> insertChapters(List<Chapter> chapters) {
  220. return db.put()
  221. .objects(chapters)
  222. .prepare();
  223. }
  224. // Add new chapters or delete if the source deletes them
  225. public Observable<PostResult> insertOrRemoveChapters(Manga manga, List<Chapter> chapters) {
  226. for (Chapter chapter : chapters) {
  227. chapter.manga_id = manga.id;
  228. }
  229. Observable<List<Chapter>> chapterList = Observable.create(subscriber -> {
  230. subscriber.onNext(getChapters(manga).executeAsBlocking());
  231. subscriber.onCompleted();
  232. });
  233. Observable<Integer> newChaptersObs = chapterList
  234. .flatMap(dbChapters -> Observable.from(chapters)
  235. .filter(c -> !dbChapters.contains(c))
  236. .map(c -> {
  237. ChapterRecognition.parseChapterNumber(c, manga);
  238. return c;
  239. })
  240. .toList()
  241. .flatMap(newChapters -> insertChapters(newChapters).createObservable())
  242. .map(PutResults::numberOfInserts));
  243. Observable<Integer> deletedChaptersObs = chapterList
  244. .flatMap(dbChapters -> Observable.from(dbChapters)
  245. .filter(c -> !chapters.contains(c))
  246. .toList()
  247. .flatMap(deletedChapters -> deleteChapters(deletedChapters).createObservable())
  248. .map(d -> d.results().size()));
  249. return Observable.zip(newChaptersObs, deletedChaptersObs,
  250. (insertions, deletions) -> new PostResult(0, insertions, deletions)
  251. );
  252. }
  253. public PreparedDeleteObject<Chapter> deleteChapter(Chapter chapter) {
  254. return db.delete()
  255. .object(chapter)
  256. .prepare();
  257. }
  258. public PreparedDeleteCollectionOfObjects<Chapter> deleteChapters(List<Chapter> chapters) {
  259. return db.delete()
  260. .objects(chapters)
  261. .prepare();
  262. }
  263. // Chapter sync related queries
  264. public PreparedGetListOfObjects<ChapterSync> getChapterSync(Manga manga, BaseChapterSync sync) {
  265. return db.get()
  266. .listOfObjects(ChapterSync.class)
  267. .withQuery(Query.builder()
  268. .table(ChapterSyncTable.TABLE)
  269. .where(ChapterSyncTable.COLUMN_MANGA_ID + "=? AND " +
  270. ChapterSyncTable.COLUMN_SYNC_ID + "=?")
  271. .whereArgs(manga.id, sync.getId())
  272. .build())
  273. .prepare();
  274. }
  275. public PreparedPutObject<ChapterSync> insertChapterSync(ChapterSync chapter) {
  276. return db.put()
  277. .object(chapter)
  278. .prepare();
  279. }
  280. public PreparedDeleteObject<ChapterSync> deleteChapterSync(ChapterSync chapter) {
  281. return db.delete()
  282. .object(chapter)
  283. .prepare();
  284. }
  285. }