From 14f248546a1d9372e6f5a0d44e8e8099f0c46457 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 19 Mar 2016 17:48:55 +0100 Subject: [PATCH] Use kapt, remove retrolambda, migrate database and source to Kotlin --- app/build.gradle | 20 +- .../main/java/eu/kanade/tachiyomi/App.java | 85 ---- app/src/main/java/eu/kanade/tachiyomi/App.kt | 70 +++ .../data/database/DatabaseHelper.java | 424 ------------------ .../tachiyomi/data/database/DatabaseHelper.kt | 303 +++++++++++++ .../data/library/LibraryUpdateService.kt | 2 +- .../data/source/base/LoginSource.java | 2 - .../tachiyomi/data/source/base/Source.java | 256 ----------- .../tachiyomi/data/source/base/Source.kt | 230 ++++++++++ .../data/source/online/english/Batoto.java | 40 +- .../data/source/online/english/Kissmanga.java | 8 +- .../data/source/online/russian/Mangachan.java | 4 +- .../data/source/online/russian/Mintmanga.java | 4 +- .../data/source/online/russian/Readmanga.java | 4 +- .../injection/component/AppComponent.java | 62 --- .../injection/component/AppComponent.kt | 55 +++ .../tachiyomi/injection/module/AppModule.java | 28 -- .../tachiyomi/injection/module/AppModule.kt | 21 + .../injection/module/DataModule.java | 73 --- .../tachiyomi/injection/module/DataModule.kt | 70 +++ .../ui/base/activity/BaseRxActivity.java | 15 +- .../ui/base/fragment/BaseRxFragment.java | 15 +- .../ui/base/presenter/RxPresenter.java | 15 +- .../ui/catalogue/CataloguePresenter.kt | 2 +- .../ui/category/CategoryPresenter.kt | 4 +- .../tachiyomi/ui/library/LibraryPresenter.kt | 4 +- .../util/DynamicConcurrentMergeOperator.java | 56 ++- .../eu/kanade/tachiyomi/util/RxPager.java | 26 -- .../java/eu/kanade/tachiyomi/util/RxPager.kt | 21 + build.gradle | 1 - 30 files changed, 878 insertions(+), 1042 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/App.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/App.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/RxPager.java create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt diff --git a/app/build.gradle b/app/build.gradle index cb52096b36..fc95120474 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,11 +4,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'com.neenbedankt.android-apt' -apply plugin: 'me.tatarka.retrolambda' - -retrolambda { - jvmArgs '-noverify' -} ext { // Git is needed in your system PATH for these commands to work. @@ -55,11 +50,6 @@ android { vectorDrawables.useSupportLibrary = true } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - buildTypes { debug { applicationIdSuffix ".debug" @@ -93,10 +83,8 @@ android { } -apt { - arguments { - eventBusIndex "eu.kanade.tachiyomi.EventBusIndex" - } +kapt { + generateStubs = true } dependencies { @@ -146,8 +134,8 @@ dependencies { apt "org.greenrobot:eventbus-annotation-processor:3.0.1" compile "com.google.dagger:dagger:$DAGGER_VERSION" - apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" - apt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION" + kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" + kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION" provided 'org.glassfish:javax.annotation:10.0-b28' compile('com.github.afollestad.material-dialogs:core:0.8.5.7@aar') { diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.java b/app/src/main/java/eu/kanade/tachiyomi/App.java deleted file mode 100644 index e6cb1424eb..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/App.java +++ /dev/null @@ -1,85 +0,0 @@ -package eu.kanade.tachiyomi; - -import android.app.Application; -import android.content.Context; - -import org.acra.ACRA; -import org.acra.annotation.ReportsCrashes; -import org.greenrobot.eventbus.EventBus; - -import eu.kanade.tachiyomi.data.preference.PreferencesHelper; -import eu.kanade.tachiyomi.injection.ComponentReflectionInjector; -import eu.kanade.tachiyomi.injection.component.AppComponent; -import eu.kanade.tachiyomi.injection.component.DaggerAppComponent; -import eu.kanade.tachiyomi.injection.module.AppModule; -import timber.log.Timber; - -@ReportsCrashes( - formUri = "http://tachiyomi.kanade.eu/crash_report", - reportType = org.acra.sender.HttpSender.Type.JSON, - httpMethod = org.acra.sender.HttpSender.Method.PUT, - buildConfigClass = BuildConfig.class, - excludeMatchingSharedPreferencesKeys={".*username.*",".*password.*"} -) -public class App extends Application { - - AppComponent applicationComponent; - ComponentReflectionInjector componentInjector; - - private int theme = 0; - - public static App get(Context context) { - return (App) context.getApplicationContext(); - } - - @Override - public void onCreate() { - super.onCreate(); - if (BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree()); - - applicationComponent = prepareAppComponent().build(); - - componentInjector = - new ComponentReflectionInjector<>(AppComponent.class, applicationComponent); - - setupTheme(); - setupEventBus(); - setupAcra(); - } - - private void setupTheme() { - theme = PreferencesHelper.getTheme(this); - } - - protected DaggerAppComponent.Builder prepareAppComponent() { - return DaggerAppComponent.builder() - .appModule(new AppModule(this)); - } - - protected void setupEventBus() { - EventBus.builder() -// .addIndex(new EventBusIndex()) - .logNoSubscriberMessages(false) - .installDefaultEventBus(); - } - - protected void setupAcra() { - ACRA.init(this); - } - - public AppComponent getComponent() { - return applicationComponent; - } - - public ComponentReflectionInjector getComponentReflection() { - return componentInjector; - } - - public int getAppTheme() { - return theme; - } - - public void setAppTheme(int theme) { - this.theme = theme; - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt new file mode 100644 index 0000000000..54f81002a5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi + +import android.app.Application +import android.content.Context +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.injection.ComponentReflectionInjector +import eu.kanade.tachiyomi.injection.component.AppComponent +import eu.kanade.tachiyomi.injection.component.DaggerAppComponent +import eu.kanade.tachiyomi.injection.module.AppModule +import org.acra.ACRA +import org.acra.annotation.ReportsCrashes +import org.greenrobot.eventbus.EventBus +import timber.log.Timber + +@ReportsCrashes( + formUri = "http://tachiyomi.kanade.eu/crash_report", + reportType = org.acra.sender.HttpSender.Type.JSON, + httpMethod = org.acra.sender.HttpSender.Method.PUT, + buildConfigClass = BuildConfig::class, + excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*") +) +open class App : Application() { + + lateinit var component: AppComponent + private set + + lateinit var componentReflection: ComponentReflectionInjector + private set + + var appTheme = 0 + + override fun onCreate() { + super.onCreate() + if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) + + component = prepareAppComponent().build() + + componentReflection = ComponentReflectionInjector(AppComponent::class.java, component) + + setupTheme() + setupEventBus() + setupAcra() + } + + private fun setupTheme() { + appTheme = PreferencesHelper.getTheme(this) + } + + protected open fun prepareAppComponent(): DaggerAppComponent.Builder { + return DaggerAppComponent.builder() + .appModule(AppModule(this)) + } + + protected open fun setupEventBus() { + EventBus.builder() + .logNoSubscriberMessages(false) + .installDefaultEventBus() + } + + protected open fun setupAcra() { + ACRA.init(this) + } + + companion object { + @JvmStatic + fun get(context: Context): App { + return context.applicationContext as App + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.java b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.java deleted file mode 100644 index 6de7b66891..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.java +++ /dev/null @@ -1,424 +0,0 @@ -package eu.kanade.tachiyomi.data.database; - -import android.content.Context; -import android.util.Pair; - -import com.pushtorefresh.storio.Queries; -import com.pushtorefresh.storio.sqlite.StorIOSQLite; -import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite; -import com.pushtorefresh.storio.sqlite.operations.delete.PreparedDeleteByQuery; -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.get.PreparedGetObject; -import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutCollectionOfObjects; -import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutObject; -import com.pushtorefresh.storio.sqlite.queries.DeleteQuery; -import com.pushtorefresh.storio.sqlite.queries.Query; -import com.pushtorefresh.storio.sqlite.queries.RawQuery; - -import java.util.Date; -import java.util.List; -import java.util.TreeSet; - -import eu.kanade.tachiyomi.data.database.models.Category; -import eu.kanade.tachiyomi.data.database.models.CategorySQLiteTypeMapping; -import eu.kanade.tachiyomi.data.database.models.Chapter; -import eu.kanade.tachiyomi.data.database.models.ChapterSQLiteTypeMapping; -import eu.kanade.tachiyomi.data.database.models.Manga; -import eu.kanade.tachiyomi.data.database.models.MangaCategory; -import eu.kanade.tachiyomi.data.database.models.MangaCategorySQLiteTypeMapping; -import eu.kanade.tachiyomi.data.database.models.MangaChapter; -import eu.kanade.tachiyomi.data.database.models.MangaSQLiteTypeMapping; -import eu.kanade.tachiyomi.data.database.models.MangaSync; -import eu.kanade.tachiyomi.data.database.models.MangaSyncSQLiteTypeMapping; -import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver; -import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver; -import eu.kanade.tachiyomi.data.database.tables.CategoryTable; -import eu.kanade.tachiyomi.data.database.tables.ChapterTable; -import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable; -import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable; -import eu.kanade.tachiyomi.data.database.tables.MangaTable; -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; - -public class DatabaseHelper { - - private StorIOSQLite db; - - public DatabaseHelper(Context context) { - - db = DefaultStorIOSQLite.builder() - .sqliteOpenHelper(new DbOpenHelper(context)) - .addTypeMapping(Manga.class, new MangaSQLiteTypeMapping()) - .addTypeMapping(Chapter.class, new ChapterSQLiteTypeMapping()) - .addTypeMapping(MangaSync.class, new MangaSyncSQLiteTypeMapping()) - .addTypeMapping(Category.class, new CategorySQLiteTypeMapping()) - .addTypeMapping(MangaCategory.class, new MangaCategorySQLiteTypeMapping()) - .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(RawQueriesKt.getLibraryQuery()) - .observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.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) - .orderBy(MangaTable.COLUMN_TITLE) - .build()) - .prepare(); - } - - public PreparedGetObject getManga(String url, int sourceId) { - return db.get() - .object(Manga.class) - .withQuery(Query.builder() - .table(MangaTable.TABLE) - .where(MangaTable.COLUMN_URL + "=? AND " + MangaTable.COLUMN_SOURCE + "=?") - .whereArgs(url, sourceId) - .build()) - .prepare(); - } - - public PreparedGetObject getManga(long id) { - return db.get() - .object(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(); - } - - public PreparedDeleteByQuery deleteMangasNotInLibrary() { - return db.delete() - .byQuery(DeleteQuery.builder() - .table(MangaTable.TABLE) - .where(MangaTable.COLUMN_FAVORITE + "=?") - .whereArgs(0) - .build()) - .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 getRecentChapters(Date date) { - return db.get() - .listOfObjects(MangaChapter.class) - .withQuery(RawQuery.builder() - .query(RawQueriesKt.getRecentsQuery(date)) - .observesTables(ChapterTable.TABLE) - .build()) - .withGetResolver(MangaChapterGetResolver.INSTANCE) - .prepare(); - } - - public PreparedGetObject 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() - .object(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 PreparedGetObject 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() - .object(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 PreparedGetObject getNextUnreadChapter(Manga manga) { - return db.get() - .object(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 sourceChapters, Source source) { - List dbChapters = getChapters(manga).executeAsBlocking(); - - Observable> newChapters = Observable.from(sourceChapters) - .filter(c -> !dbChapters.contains(c)) - .doOnNext(c -> { - c.manga_id = manga.id; - source.parseChapterNumber(c); - ChapterRecognition.parseChapterNumber(c, manga); - }) - .toList(); - - Observable> deletedChapters = Observable.from(dbChapters) - .filter(c -> !sourceChapters.contains(c)) - .toList(); - - return Observable.zip(newChapters, deletedChapters, (toAdd, toDelete) -> { - int added = 0; - int deleted = 0; - int readded = 0; - db.internal().beginTransaction(); - try { - TreeSet deletedReadChapterNumbers = new TreeSet<>(); - if (!toDelete.isEmpty()) { - for (Chapter c : 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. - long now = new Date().getTime(); - - for (int i = toAdd.size() - 1; i >= 0; i--) { - Chapter c = toAdd.get(i); - c.date_fetch = now++; - // Try to mark already read chapters as read when the source deletes them - if (c.chapter_number != -1 && deletedReadChapterNumbers.contains(c.chapter_number)) { - c.read = true; - readded++; - } - } - added = insertChapters(toAdd).executeAsBlocking().numberOfInserts(); - } - - db.internal().setTransactionSuccessful(); - } finally { - db.internal().endTransaction(); - } - return Pair.create(added - readded, deleted - readded); - }); - } - - 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 PreparedGetObject getMangaSync(Manga manga, MangaSyncService sync) { - return db.get() - .object(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 getMangasSync(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) - .orderBy(CategoryTable.COLUMN_ORDER) - .build()) - .prepare(); - } - - public PreparedPutObject insertCategory(Category category) { - return db.put() - .object(category) - .prepare(); - } - - public PreparedPutCollectionOfObjects insertCategories(List categories) { - return db.put() - .objects(categories) - .prepare(); - } - - public PreparedDeleteObject deleteCategory(Category category) { - return db.delete() - .object(category) - .prepare(); - } - - public PreparedDeleteCollectionOfObjects deleteCategories(List categories) { - return db.delete() - .objects(categories) - .prepare(); - } - - public PreparedPutObject insertMangaCategory(MangaCategory mangaCategory) { - return db.put() - .object(mangaCategory) - .prepare(); - } - - public PreparedPutCollectionOfObjects insertMangasCategories(List mangasCategories) { - return db.put() - .objects(mangasCategories) - .prepare(); - } - - public PreparedDeleteByQuery deleteOldMangasCategories(List mangas) { - List mangaIds = Observable.from(mangas) - .map(manga -> manga.id) - .toList().toBlocking().single(); - - return db.delete() - .byQuery(DeleteQuery.builder() - .table(MangaCategoryTable.TABLE) - .where(MangaCategoryTable.COLUMN_MANGA_ID + " IN (" - + Queries.placeholders(mangas.size()) + ")") - .whereArgs(mangaIds.toArray()) - .build()) - .prepare(); - } - - public void setMangaCategories(List mangasCategories, List mangas) { - db.internal().beginTransaction(); - try { - deleteOldMangasCategories(mangas).executeAsBlocking(); - insertMangasCategories(mangasCategories).executeAsBlocking(); - db.internal().setTransactionSuccessful(); - } finally { - db.internal().endTransaction(); - } - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt new file mode 100644 index 0000000000..d73b93ce3d --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -0,0 +1,303 @@ +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.delete.PreparedDeleteByQuery +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.* + +class DatabaseHelper(context: Context) { + + val db = DefaultStorIOSQLite.builder() + .sqliteOpenHelper(DbOpenHelper(context)) + .addTypeMapping(Manga::class.java, MangaSQLiteTypeMapping()) + .addTypeMapping(Chapter::class.java, ChapterSQLiteTypeMapping()) + .addTypeMapping(MangaSync::class.java, MangaSyncSQLiteTypeMapping()) + .addTypeMapping(Category::class.java, CategorySQLiteTypeMapping()) + .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() + + 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) = db.put().objects(mangas).prepare() + + fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare() + + fun deleteMangas(mangas: List) = 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 { + // 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 { + // 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) = db.put().objects(chapters).prepare() + + // Add new chapters or delete if the source deletes them + fun insertOrRemoveChapters(manga: Manga, sourceChapters: List, source: Source): Observable> { + 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() + 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) = 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 deleteMangaSync(manga: MangaSync) = db.delete().`object`(manga).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 insertCategory(category: Category) = db.put().`object`(category).prepare() + + fun insertCategories(categories: List) = db.put().objects(categories).prepare() + + fun deleteCategory(category: Category) = db.delete().`object`(category).prepare() + + fun deleteCategories(categories: List) = db.delete().objects(categories).prepare() + + fun insertMangaCategory(mangaCategory: MangaCategory) = db.put().`object`(mangaCategory).prepare() + + fun insertMangasCategories(mangasCategories: List) = db.put().objects(mangasCategories).prepare() + + fun deleteOldMangasCategories(mangas: List): PreparedDeleteByQuery { + val mangaIds = Observable.from(mangas).map { manga -> manga.id }.toList().toBlocking().single() + + return db.delete() + .byQuery(DeleteQuery.builder() + .table(MangaCategoryTable.TABLE) + .where("${MangaCategoryTable.COLUMN_MANGA_ID} IN (${Queries.placeholders(mangas.size)})") + .whereArgs(mangaIds) + .build()) + .prepare() + } + + fun setMangaCategories(mangasCategories: List, mangas: List) { + inTransaction { + deleteOldMangasCategories(mangas).executeAsBlocking() + insertMangasCategories(mangasCategories).executeAsBlocking() + } + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 761eccacac..4f10f5e200 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -168,7 +168,7 @@ class LibraryUpdateService : Service() { Intent(this, CancelUpdateReceiver::class.java), 0) // Get the manga list that is going to be updated. - val allLibraryMangas = db.favoriteMangas.executeAsBlocking() + val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking() val toUpdate = if (!preferences.updateOnlyNonCompleted()) allLibraryMangas else diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/LoginSource.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/LoginSource.java index f6b90ffc5a..3865373a0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/LoginSource.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/LoginSource.java @@ -4,8 +4,6 @@ import android.content.Context; public abstract class LoginSource extends Source { - public LoginSource() {} - public LoginSource(Context context) { super(context); } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.java deleted file mode 100644 index 0b4680a082..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.java +++ /dev/null @@ -1,256 +0,0 @@ -package eu.kanade.tachiyomi.data.source.base; - -import android.content.Context; - -import com.bumptech.glide.load.model.LazyHeaders; - -import org.jsoup.Jsoup; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -import eu.kanade.tachiyomi.App; -import eu.kanade.tachiyomi.data.cache.ChapterCache; -import eu.kanade.tachiyomi.data.database.models.Chapter; -import eu.kanade.tachiyomi.data.database.models.Manga; -import eu.kanade.tachiyomi.data.network.NetworkHelper; -import eu.kanade.tachiyomi.data.network.ReqKt; -import eu.kanade.tachiyomi.data.preference.PreferencesHelper; -import eu.kanade.tachiyomi.data.source.model.MangasPage; -import eu.kanade.tachiyomi.data.source.model.Page; -import okhttp3.Headers; -import okhttp3.Request; -import okhttp3.Response; -import rx.Observable; -import rx.schedulers.Schedulers; - -public abstract class Source extends BaseSource { - - @Inject protected NetworkHelper networkService; - @Inject protected ChapterCache chapterCache; - @Inject protected PreferencesHelper prefs; - protected Headers requestHeaders; - protected LazyHeaders glideHeaders; - - public Source() {} - - public Source(Context context) { - App.get(context).getComponent().inject(this); - requestHeaders = headersBuilder().build(); - glideHeaders = glideHeadersBuilder().build(); - } - - @Override - public boolean isLoginRequired() { - return false; - } - - protected Request popularMangaRequest(MangasPage page) { - if (page.page == 1) { - page.url = getInitialPopularMangasUrl(); - } - - return ReqKt.get(page.url, requestHeaders); - } - - protected Request searchMangaRequest(MangasPage page, String query) { - if (page.page == 1) { - page.url = getInitialSearchUrl(query); - } - - return ReqKt.get(page.url, requestHeaders); - } - - protected Request mangaDetailsRequest(String mangaUrl) { - return ReqKt.get(getBaseUrl() + mangaUrl, requestHeaders); - } - - protected Request chapterListRequest(String mangaUrl) { - return ReqKt.get(getBaseUrl() + mangaUrl, requestHeaders); - } - - protected Request pageListRequest(String chapterUrl) { - return ReqKt.get(getBaseUrl() + chapterUrl, requestHeaders); - } - - protected Request imageUrlRequest(Page page) { - return ReqKt.get(page.getUrl(), requestHeaders); - } - - protected Request imageRequest(Page page) { - return ReqKt.get(page.getImageUrl(), requestHeaders); - } - - // Get the most popular mangas from the source - public Observable pullPopularMangasFromNetwork(MangasPage page) { - return networkService - .requestBody(popularMangaRequest(page), true) - .map(Jsoup::parse) - .doOnNext(doc -> page.mangas = parsePopularMangasFromHtml(doc)) - .doOnNext(doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page)) - .map(response -> page); - } - - // Get mangas from the source with a query - public Observable searchMangasFromNetwork(MangasPage page, String query) { - return networkService - .requestBody(searchMangaRequest(page, query), true) - .map(Jsoup::parse) - .doOnNext(doc -> page.mangas = parseSearchFromHtml(doc)) - .doOnNext(doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query)) - .map(response -> page); - } - - // Get manga details from the source - public Observable pullMangaFromNetwork(final String mangaUrl) { - return networkService - .requestBody(mangaDetailsRequest(mangaUrl)) - .flatMap(unparsedHtml -> Observable.just(parseHtmlToManga(mangaUrl, unparsedHtml))); - } - - // Get chapter list of a manga from the source - public Observable> pullChaptersFromNetwork(final String mangaUrl) { - return networkService - .requestBody(chapterListRequest(mangaUrl)) - .flatMap(unparsedHtml -> { - List chapters = parseHtmlToChapters(unparsedHtml); - return !chapters.isEmpty() ? - Observable.just(chapters) : - Observable.error(new Exception("No chapters found")); - }); - } - - public Observable> getCachedPageListOrPullFromNetwork(final String chapterUrl) { - return chapterCache.getPageListFromCache(getChapterCacheKey(chapterUrl)) - .onErrorResumeNext(throwable -> { - return pullPageListFromNetwork(chapterUrl); - }) - .onBackpressureBuffer(); - } - - public Observable> pullPageListFromNetwork(final String chapterUrl) { - return networkService - .requestBody(pageListRequest(chapterUrl)) - .flatMap(unparsedHtml -> { - List pages = convertToPages(parseHtmlToPageUrls(unparsedHtml)); - return !pages.isEmpty() ? - Observable.just(parseFirstPage(pages, unparsedHtml)) : - Observable.error(new Exception("Page list is empty")); - }); - } - - public Observable getAllImageUrlsFromPageList(final List pages) { - return Observable.from(pages) - .filter(page -> page.getImageUrl() != null) - .mergeWith(getRemainingImageUrlsFromPageList(pages)); - } - - // Get the URLs of the images of a chapter - public Observable getRemainingImageUrlsFromPageList(final List pages) { - return Observable.from(pages) - .filter(page -> page.getImageUrl() == null) - .concatMap(this::getImageUrlFromPage); - } - - public Observable getImageUrlFromPage(final Page page) { - page.setStatus(Page.LOAD_PAGE); - return networkService - .requestBody(imageUrlRequest(page)) - .flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml))) - .onErrorResumeNext(e -> { - page.setStatus(Page.ERROR); - return Observable.just(null); - }) - .flatMap(imageUrl -> { - page.setImageUrl(imageUrl); - return Observable.just(page); - }) - .subscribeOn(Schedulers.io()); - } - - public Observable getCachedImage(final Page page) { - Observable pageObservable = Observable.just(page); - if (page.getImageUrl() == null) - return pageObservable; - - return pageObservable - .flatMap(p -> { - if (!chapterCache.isImageInCache(page.getImageUrl())) { - return cacheImage(page); - } - return Observable.just(page); - }) - .flatMap(p -> { - page.setImagePath(chapterCache.getImagePath(page.getImageUrl())); - page.setStatus(Page.READY); - return Observable.just(page); - }) - .onErrorResumeNext(e -> { - page.setStatus(Page.ERROR); - return Observable.just(page); - }); - } - - private Observable cacheImage(final Page page) { - page.setStatus(Page.DOWNLOAD_IMAGE); - return getImageProgressResponse(page) - .flatMap(resp -> { - try { - chapterCache.putImageToCache(page.getImageUrl(), resp); - } catch (IOException e) { - return Observable.error(e); - } - return Observable.just(page); - }); - } - - public Observable getImageProgressResponse(final Page page) { - return networkService.requestBodyProgress(imageRequest(page), page); - } - - public void savePageList(String chapterUrl, List pages) { - if (pages != null) - chapterCache.putPageListToCache(getChapterCacheKey(chapterUrl), pages); - } - - protected List convertToPages(List pageUrls) { - List pages = new ArrayList<>(); - for (int i = 0; i < pageUrls.size(); i++) { - pages.add(new Page(i, pageUrls.get(i))); - } - return pages; - } - - protected List parseFirstPage(List pages, String unparsedHtml) { - String firstImage = parseHtmlToImageUrl(unparsedHtml); - pages.get(0).setImageUrl(firstImage); - return pages; - } - - protected String getChapterCacheKey(String chapterUrl) { - return getId() + chapterUrl; - } - - // Overridable method to allow custom parsing. - public void parseChapterNumber(Chapter chapter) { - - } - - protected LazyHeaders.Builder glideHeadersBuilder() { - LazyHeaders.Builder builder = new LazyHeaders.Builder(); - for (Map.Entry> entry : requestHeaders.toMultimap().entrySet()) { - builder.addHeader(entry.getKey(), entry.getValue().get(0)); - } - - return builder; - } - - public LazyHeaders getGlideHeaders() { - return glideHeaders; - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt new file mode 100644 index 0000000000..d817e23d27 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt @@ -0,0 +1,230 @@ +package eu.kanade.tachiyomi.data.source.base + +import android.content.Context +import com.bumptech.glide.load.model.LazyHeaders +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.data.network.get +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.source.model.MangasPage +import eu.kanade.tachiyomi.data.source.model.Page +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import rx.Observable +import rx.schedulers.Schedulers +import java.util.* +import javax.inject.Inject + +abstract class Source(context: Context) : BaseSource() { + + @Inject protected lateinit var networkService: NetworkHelper + @Inject protected lateinit var chapterCache: ChapterCache + @Inject protected lateinit var prefs: PreferencesHelper + + val requestHeaders by lazy { headersBuilder().build() } + + val glideHeaders by lazy { glideHeadersBuilder().build() } + + init { + App.get(context).component.inject(this) + } + + override fun isLoginRequired(): Boolean { + return false + } + + protected fun popularMangaRequest(page: MangasPage): Request { + if (page.page == 1) { + page.url = initialPopularMangasUrl + } + + return get(page.url, requestHeaders) + } + + protected open fun searchMangaRequest(page: MangasPage, query: String): Request { + if (page.page == 1) { + page.url = getInitialSearchUrl(query) + } + + return get(page.url, requestHeaders) + } + + protected open fun mangaDetailsRequest(mangaUrl: String): Request { + return get(baseUrl + mangaUrl, requestHeaders) + } + + protected fun chapterListRequest(mangaUrl: String): Request { + return get(baseUrl + mangaUrl, requestHeaders) + } + + protected open fun pageListRequest(chapterUrl: String): Request { + return get(baseUrl + chapterUrl, requestHeaders) + } + + protected open fun imageUrlRequest(page: Page): Request { + return get(page.url, requestHeaders) + } + + protected open fun imageRequest(page: Page): Request { + return get(page.imageUrl, requestHeaders) + } + + // Get the most popular mangas from the source + fun pullPopularMangasFromNetwork(page: MangasPage): Observable { + return networkService.requestBody(popularMangaRequest(page), true) + .map { Jsoup.parse(it) } + .doOnNext { doc -> page.mangas = parsePopularMangasFromHtml(doc) } + .doOnNext { doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page) } + .map { response -> page } + } + + // Get mangas from the source with a query + fun searchMangasFromNetwork(page: MangasPage, query: String): Observable { + return networkService.requestBody(searchMangaRequest(page, query), true) + .map { Jsoup.parse(it) } + .doOnNext { doc -> page.mangas = parseSearchFromHtml(doc) } + .doOnNext { doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query) } + .map { response -> page } + } + + // Get manga details from the source + fun pullMangaFromNetwork(mangaUrl: String): Observable { + return networkService.requestBody(mangaDetailsRequest(mangaUrl)) + .flatMap { Observable.just(parseHtmlToManga(mangaUrl, it)) } + } + + // Get chapter list of a manga from the source + open fun pullChaptersFromNetwork(mangaUrl: String): Observable> { + return networkService.requestBody(chapterListRequest(mangaUrl)) + .flatMap { unparsedHtml -> + val chapters = parseHtmlToChapters(unparsedHtml) + if (!chapters.isEmpty()) + Observable.just(chapters) + else + Observable.error(Exception("No chapters found")) + } + } + + fun getCachedPageListOrPullFromNetwork(chapterUrl: String): Observable> { + return chapterCache.getPageListFromCache(getChapterCacheKey(chapterUrl)) + .onErrorResumeNext { pullPageListFromNetwork(chapterUrl) } + .onBackpressureBuffer() + } + + fun pullPageListFromNetwork(chapterUrl: String): Observable> { + return networkService.requestBody(pageListRequest(chapterUrl)) + .flatMap { unparsedHtml -> + val pages = convertToPages(parseHtmlToPageUrls(unparsedHtml)) + if (!pages.isEmpty()) + Observable.just(parseFirstPage(pages, unparsedHtml)) + else + Observable.error(Exception("Page list is empty")) + } + } + + fun getAllImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { page -> page.imageUrl != null } + .mergeWith(getRemainingImageUrlsFromPageList(pages)) + } + + // Get the URLs of the images of a chapter + fun getRemainingImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { page -> page.imageUrl == null } + .concatMap { getImageUrlFromPage(it) } + } + + fun getImageUrlFromPage(page: Page): Observable { + page.status = Page.LOAD_PAGE + return networkService.requestBody(imageUrlRequest(page)) + .flatMap { unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)) } + .onErrorResumeNext { e -> + page.status = Page.ERROR + Observable.just(null) + } + .flatMap { imageUrl -> + page.imageUrl = imageUrl + Observable.just(page) + } + .subscribeOn(Schedulers.io()) + } + + fun getCachedImage(page: Page): Observable { + val pageObservable = Observable.just(page) + if (page.imageUrl == null) + return pageObservable + + return pageObservable + .flatMap { p -> + if (!chapterCache.isImageInCache(page.imageUrl)) { + return@flatMap cacheImage(page) + } + Observable.just(page) + } + .flatMap { p -> + page.imagePath = chapterCache.getImagePath(page.imageUrl) + page.status = Page.READY + Observable.just(page) + } + .onErrorResumeNext { e -> + page.status = Page.ERROR + Observable.just(page) + } + } + + private fun cacheImage(page: Page): Observable { + page.status = Page.DOWNLOAD_IMAGE + return getImageProgressResponse(page) + .flatMap { resp -> + chapterCache.putImageToCache(page.imageUrl, resp) + Observable.just(page) + } + } + + fun getImageProgressResponse(page: Page): Observable { + return networkService.requestBodyProgress(imageRequest(page), page) + } + + fun savePageList(chapterUrl: String, pages: List?) { + if (pages != null) + chapterCache.putPageListToCache(getChapterCacheKey(chapterUrl), pages) + } + + protected fun convertToPages(pageUrls: List): List { + val pages = ArrayList() + for (i in pageUrls.indices) { + pages.add(Page(i, pageUrls[i])) + } + return pages + } + + protected open fun parseFirstPage(pages: List, unparsedHtml: String): List { + val firstImage = parseHtmlToImageUrl(unparsedHtml) + pages[0].imageUrl = firstImage + return pages + } + + protected fun getChapterCacheKey(chapterUrl: String): String { + return "$id$chapterUrl" + } + + // Overridable method to allow custom parsing. + open fun parseChapterNumber(chapter: Chapter) { + + } + + protected fun glideHeadersBuilder(): LazyHeaders.Builder { + val builder = LazyHeaders.Builder() + for ((key, value) in requestHeaders.toMultimap()) { + builder.addHeader(key, value[0]) + } + + return builder + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java index f1610b1a73..9926d03e4c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java @@ -39,6 +39,7 @@ import okhttp3.Headers; import okhttp3.Request; import okhttp3.Response; import rx.Observable; +import rx.functions.Func1; public class Batoto extends LoginSource { @@ -106,13 +107,13 @@ public class Batoto extends LoginSource { @Override protected Request mangaDetailsRequest(String mangaUrl) { String mangaId = mangaUrl.substring(mangaUrl.lastIndexOf("r") + 1); - return ReqKt.get(String.format(MANGA_URL, mangaId), requestHeaders); + return ReqKt.get(String.format(MANGA_URL, mangaId), getRequestHeaders()); } @Override protected Request pageListRequest(String pageUrl) { String id = pageUrl.substring(pageUrl.indexOf("#") + 1); - return ReqKt.get(String.format(CHAPTER_URL, id), requestHeaders); + return ReqKt.get(String.format(CHAPTER_URL, id), getRequestHeaders()); } @Override @@ -121,7 +122,7 @@ public class Batoto extends LoginSource { int start = pageUrl.indexOf("#") + 1; int end = pageUrl.indexOf("_", start); String id = pageUrl.substring(start, end); - return ReqKt.get(String.format(PAGE_URL, id, pageUrl.substring(end+1)), requestHeaders); + return ReqKt.get(String.format(PAGE_URL, id, pageUrl.substring(end+1)), getRequestHeaders()); } private List parseMangasFromHtml(Document parsedHtml) { @@ -293,7 +294,7 @@ public class Batoto extends LoginSource { } @Override - protected List parseFirstPage(List pages, String unparsedHtml) { + protected List parseFirstPage(List pages, String unparsedHtml) { if (!unparsedHtml.contains("Want to see this chapter per page instead?")) { String firstImage = parseHtmlToImageUrl(unparsedHtml); pages.get(0).setImageUrl(firstImage); @@ -305,7 +306,7 @@ public class Batoto extends LoginSource { pages.get(i).setImageUrl(imageUrls.get(i).attr("src")); } } - return pages; + return (List) pages; } @Override @@ -320,10 +321,16 @@ public class Batoto extends LoginSource { } @Override - public Observable login(String username, String password) { - return networkService.requestBody(ReqKt.get(LOGIN_URL, requestHeaders)) - .flatMap(response -> doLogin(response, username, password)) - .map(this::isAuthenticationSuccessful); + public Observable login(final String username, final String password) { + return getNetworkService().requestBody(ReqKt.get(LOGIN_URL, getRequestHeaders())) + .flatMap(new Func1>() { + @Override + public Observable call(String response) {return doLogin(response, username, password);} + }) + .map(new Func1() { + @Override + public Boolean call(Response resp) {return isAuthenticationSuccessful(resp);} + }); } private Observable doLogin(String response, String username, String password) { @@ -340,7 +347,7 @@ public class Batoto extends LoginSource { formBody.add("invisible", "1"); formBody.add("rememberMe", "1"); - return networkService.request(ReqKt.post(postUrl, requestHeaders, formBody.build())); + return getNetworkService().request(ReqKt.post(postUrl, getRequestHeaders(), formBody.build())); } @Override @@ -351,7 +358,7 @@ public class Batoto extends LoginSource { @Override public boolean isLogged() { try { - for ( HttpCookie cookie : networkService.getCookies().get(new URI(BASE_URL)) ) { + for ( HttpCookie cookie : getNetworkService().getCookies().get(new URI(BASE_URL)) ) { if (cookie.getName().equals("pass_hash")) return true; } @@ -363,16 +370,19 @@ public class Batoto extends LoginSource { } @Override - public Observable> pullChaptersFromNetwork(String mangaUrl) { + public Observable> pullChaptersFromNetwork(final String mangaUrl) { Observable> observable; - String username = prefs.getSourceUsername(this); - String password = prefs.getSourcePassword(this); + String username = getPrefs().getSourceUsername(this); + String password = getPrefs().getSourcePassword(this); if (username.isEmpty() && password.isEmpty()) { observable = Observable.error(new Exception("User not logged")); } else if (!isLogged()) { observable = login(username, password) - .flatMap(result -> super.pullChaptersFromNetwork(mangaUrl)); + .flatMap(new Func1>>() { + @Override + public Observable> call(Boolean result) {return Batoto.super.pullChaptersFromNetwork(mangaUrl);} + }); } else { observable = super.pullChaptersFromNetwork(mangaUrl); diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java index f73477989d..0da058bad0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java @@ -84,12 +84,12 @@ public class Kissmanga extends Source { form.add("status", ""); form.add("genres", ""); - return ReqKt.post(page.url, requestHeaders, form.build()); + return ReqKt.post(page.url, getRequestHeaders(), form.build()); } @Override protected Request pageListRequest(String chapterUrl) { - return ReqKt.post(getBaseUrl() + chapterUrl, requestHeaders); + return ReqKt.post(getBaseUrl() + chapterUrl, getRequestHeaders()); } @Override @@ -215,7 +215,7 @@ public class Kissmanga extends Source { } @Override - protected List parseFirstPage(List pages, String unparsedHtml) { + protected List parseFirstPage(List pages, String unparsedHtml) { Pattern p = Pattern.compile("lstImages.push\\(\"(.+?)\""); Matcher m = p.matcher(unparsedHtml); @@ -223,7 +223,7 @@ public class Kissmanga extends Source { while (m.find()) { pages.get(i++).setImageUrl(m.group(1)); } - return pages; + return (List) pages; } @Override diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.java index 66aad02ea0..015d90361e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.java @@ -218,7 +218,7 @@ public class Mangachan extends Source { } @Override - protected List parseFirstPage(List pages, String unparsedHtml) { + protected List parseFirstPage(List pages, String unparsedHtml) { int beginIndex = unparsedHtml.indexOf("fullimg\":["); int endIndex = unparsedHtml.indexOf("]", beginIndex); @@ -230,7 +230,7 @@ public class Mangachan extends Source { pages.get(i).setImageUrl(pageUrls[i].replaceAll("im.?\\.", "")); } - return pages; + return (List) pages; } @Override diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.java index cfa9e83c3d..2a9b4ef5d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.java @@ -203,7 +203,7 @@ public class Mintmanga extends Source { } @Override - protected List parseFirstPage(List pages, String unparsedHtml) { + protected List parseFirstPage(List pages, String unparsedHtml) { int beginIndex = unparsedHtml.indexOf("rm_h.init( ["); int endIndex = unparsedHtml.indexOf("], 0, false);", beginIndex); @@ -215,7 +215,7 @@ public class Mintmanga extends Source { String page = urlParts[1] + urlParts[0] + urlParts[2]; pages.get(i).setImageUrl(page); } - return pages; + return (List) pages; } @Override diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.java b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.java index 7ea30a1793..408eca9cc9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.java @@ -203,7 +203,7 @@ public class Readmanga extends Source { } @Override - protected List parseFirstPage(List pages, String unparsedHtml) { + protected List parseFirstPage(List pages, String unparsedHtml) { int beginIndex = unparsedHtml.indexOf("rm_h.init( ["); int endIndex = unparsedHtml.indexOf("], 0, false);", beginIndex); @@ -215,7 +215,7 @@ public class Readmanga extends Source { String page = urlParts[1] + urlParts[0] + urlParts[2]; pages.get(i).setImageUrl(page); } - return pages; + return (List) pages; } @Override diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java deleted file mode 100644 index f05b164c05..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.injection.component; - -import android.app.Application; - -import javax.inject.Singleton; - -import dagger.Component; -import eu.kanade.tachiyomi.data.download.DownloadService; -import eu.kanade.tachiyomi.data.library.LibraryUpdateService; -import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService; -import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService; -import eu.kanade.tachiyomi.data.source.base.Source; -import eu.kanade.tachiyomi.data.updater.UpdateDownloader; -import eu.kanade.tachiyomi.injection.module.AppModule; -import eu.kanade.tachiyomi.injection.module.DataModule; -import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter; -import eu.kanade.tachiyomi.ui.category.CategoryPresenter; -import eu.kanade.tachiyomi.ui.download.DownloadPresenter; -import eu.kanade.tachiyomi.ui.library.LibraryPresenter; -import eu.kanade.tachiyomi.ui.manga.MangaActivity; -import eu.kanade.tachiyomi.ui.manga.MangaPresenter; -import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter; -import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter; -import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter; -import eu.kanade.tachiyomi.ui.reader.ReaderPresenter; -import eu.kanade.tachiyomi.ui.recent.RecentChaptersPresenter; -import eu.kanade.tachiyomi.ui.setting.SettingsActivity; - -@Singleton -@Component( - modules = { - AppModule.class, - DataModule.class - } -) -public interface AppComponent { - - void inject(LibraryPresenter libraryPresenter); - void inject(MangaPresenter mangaPresenter); - void inject(CataloguePresenter cataloguePresenter); - void inject(MangaInfoPresenter mangaInfoPresenter); - void inject(ChaptersPresenter chaptersPresenter); - void inject(ReaderPresenter readerPresenter); - void inject(DownloadPresenter downloadPresenter); - void inject(MyAnimeListPresenter myAnimeListPresenter); - void inject(CategoryPresenter categoryPresenter); - void inject(RecentChaptersPresenter recentChaptersPresenter); - - void inject(MangaActivity mangaActivity); - void inject(SettingsActivity settingsActivity); - - void inject(Source source); - void inject(MangaSyncService mangaSyncService); - - void inject(LibraryUpdateService libraryUpdateService); - void inject(DownloadService downloadService); - void inject(UpdateMangaSyncService updateMangaSyncService); - - void inject(UpdateDownloader updateDownloader); - Application application(); - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt new file mode 100644 index 0000000000..ced38255bf --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.kt @@ -0,0 +1,55 @@ +package eu.kanade.tachiyomi.injection.component + +import android.app.Application +import dagger.Component +import eu.kanade.tachiyomi.data.download.DownloadService +import eu.kanade.tachiyomi.data.library.LibraryUpdateService +import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService +import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService +import eu.kanade.tachiyomi.data.source.base.Source +import eu.kanade.tachiyomi.data.updater.UpdateDownloader +import eu.kanade.tachiyomi.injection.module.AppModule +import eu.kanade.tachiyomi.injection.module.DataModule +import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter +import eu.kanade.tachiyomi.ui.category.CategoryPresenter +import eu.kanade.tachiyomi.ui.download.DownloadPresenter +import eu.kanade.tachiyomi.ui.library.LibraryPresenter +import eu.kanade.tachiyomi.ui.manga.MangaActivity +import eu.kanade.tachiyomi.ui.manga.MangaPresenter +import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter +import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter +import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter +import eu.kanade.tachiyomi.ui.reader.ReaderPresenter +import eu.kanade.tachiyomi.ui.recent.RecentChaptersPresenter +import eu.kanade.tachiyomi.ui.setting.SettingsActivity +import javax.inject.Singleton + +@Singleton +@Component(modules = arrayOf(AppModule::class, DataModule::class)) +interface AppComponent { + + fun inject(libraryPresenter: LibraryPresenter) + fun inject(mangaPresenter: MangaPresenter) + fun inject(cataloguePresenter: CataloguePresenter) + fun inject(mangaInfoPresenter: MangaInfoPresenter) + fun inject(chaptersPresenter: ChaptersPresenter) + fun inject(readerPresenter: ReaderPresenter) + fun inject(downloadPresenter: DownloadPresenter) + fun inject(myAnimeListPresenter: MyAnimeListPresenter) + fun inject(categoryPresenter: CategoryPresenter) + fun inject(recentChaptersPresenter: RecentChaptersPresenter) + + fun inject(mangaActivity: MangaActivity) + fun inject(settingsActivity: SettingsActivity) + + fun inject(source: Source) + fun inject(mangaSyncService: MangaSyncService) + + fun inject(libraryUpdateService: LibraryUpdateService) + fun inject(downloadService: DownloadService) + fun inject(updateMangaSyncService: UpdateMangaSyncService) + + fun inject(updateDownloader: UpdateDownloader) + fun application(): Application + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.java b/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.java deleted file mode 100644 index c855cf8e5c..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.java +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.injection.module; - -import android.app.Application; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; - -/** - * Provide application-level dependencies. Mainly singleton object that can be injected from - * anywhere in the app. - */ -@Module -public class AppModule { - protected final Application mApplication; - - public AppModule(Application application) { - mApplication = application; - } - - @Provides - @Singleton - Application provideApplication() { - return mApplication; - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt new file mode 100644 index 0000000000..ae9e82f425 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/injection/module/AppModule.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.injection.module + +import android.app.Application +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + +/** + * Provide application-level dependencies. Mainly singleton object that can be injected from + * anywhere in the app. + */ +@Module +class AppModule(private val application: Application) { + + @Provides + @Singleton + fun provideApplication(): Application { + return application + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.java b/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.java deleted file mode 100644 index 3aa9cf97a3..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.java +++ /dev/null @@ -1,73 +0,0 @@ -package eu.kanade.tachiyomi.injection.module; - -import android.app.Application; - -import javax.inject.Singleton; - -import dagger.Module; -import dagger.Provides; -import eu.kanade.tachiyomi.data.cache.ChapterCache; -import eu.kanade.tachiyomi.data.cache.CoverCache; -import eu.kanade.tachiyomi.data.database.DatabaseHelper; -import eu.kanade.tachiyomi.data.download.DownloadManager; -import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager; -import eu.kanade.tachiyomi.data.network.NetworkHelper; -import eu.kanade.tachiyomi.data.preference.PreferencesHelper; -import eu.kanade.tachiyomi.data.source.SourceManager; - -/** - * Provide dependencies to the DataManager, mainly Helper classes and Retrofit services. - */ -@Module -public class DataModule { - - @Provides - @Singleton - PreferencesHelper providePreferencesHelper(Application app) { - return new PreferencesHelper(app); - } - - @Provides - @Singleton - DatabaseHelper provideDatabaseHelper(Application app) { - return new DatabaseHelper(app); - } - - @Provides - @Singleton - ChapterCache provideChapterCache(Application app) { - return new ChapterCache(app); - } - - @Provides - @Singleton - CoverCache provideCoverCache(Application app) { - return new CoverCache(app); - } - - @Provides - @Singleton - NetworkHelper provideNetworkHelper(Application app) { - return new NetworkHelper(app); - } - - @Provides - @Singleton - SourceManager provideSourceManager(Application app) { - return new SourceManager(app); - } - - @Provides - @Singleton - DownloadManager provideDownloadManager( - Application app, SourceManager sourceManager, PreferencesHelper preferences) { - return new DownloadManager(app, sourceManager, preferences); - } - - @Provides - @Singleton - MangaSyncManager provideMangaSyncManager(Application app) { - return new MangaSyncManager(app); - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt b/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt new file mode 100644 index 0000000000..08512e612c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi.injection.module + +import android.app.Application +import dagger.Module +import dagger.Provides +import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.cache.CoverCache +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.download.DownloadManager +import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager +import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.source.SourceManager +import javax.inject.Singleton + +/** + * Provide dependencies to the DataManager, mainly Helper classes and Retrofit services. + */ +@Module +open class DataModule { + + @Provides + @Singleton + fun providePreferencesHelper(app: Application): PreferencesHelper { + return PreferencesHelper(app) + } + + @Provides + @Singleton + open fun provideDatabaseHelper(app: Application): DatabaseHelper { + return DatabaseHelper(app) + } + + @Provides + @Singleton + fun provideChapterCache(app: Application): ChapterCache { + return ChapterCache(app) + } + + @Provides + @Singleton + fun provideCoverCache(app: Application): CoverCache { + return CoverCache(app) + } + + @Provides + @Singleton + open fun provideNetworkHelper(app: Application): NetworkHelper { + return NetworkHelper(app) + } + + @Provides + @Singleton + open fun provideSourceManager(app: Application): SourceManager { + return SourceManager(app) + } + + @Provides + @Singleton + fun provideDownloadManager(app: Application, sourceManager: SourceManager, preferences: PreferencesHelper): DownloadManager { + return DownloadManager(app, sourceManager, preferences) + } + + @Provides + @Singleton + fun provideMangaSyncManager(app: Application): MangaSyncManager { + return MangaSyncManager(app) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java index 8b3117e9a1..0f6a5e4867 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java @@ -58,12 +58,15 @@ public abstract class BaseRxActivity

extends BaseActivity i @Override protected void onCreate(Bundle savedInstanceState) { final PresenterFactory

superFactory = getPresenterFactory(); - setPresenterFactory(() -> { - P presenter = superFactory.createPresenter(); - App app = (App) getApplication(); - app.getComponentReflection().inject(presenter); - ((BasePresenter)presenter).setContext(app.getApplicationContext()); - return presenter; + setPresenterFactory(new PresenterFactory

() { + @Override + public P createPresenter() { + P presenter = superFactory.createPresenter(); + App app = (App) BaseRxActivity.this.getApplication(); + app.getComponentReflection().inject(presenter); + ((BasePresenter) presenter).setContext(app.getApplicationContext()); + return presenter; + } }); super.onCreate(savedInstanceState); diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java index ad50e0b353..6752ce448a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java @@ -56,12 +56,15 @@ public abstract class BaseRxFragment

extends BaseFragment i @Override public void onCreate(Bundle bundle) { final PresenterFactory

superFactory = getPresenterFactory(); - setPresenterFactory(() -> { - P presenter = superFactory.createPresenter(); - App app = (App) getActivity().getApplication(); - app.getComponentReflection().inject(presenter); - ((BasePresenter)presenter).setContext(app.getApplicationContext()); - return presenter; + setPresenterFactory(new PresenterFactory

() { + @Override + public P createPresenter() { + P presenter = superFactory.createPresenter(); + App app = (App) BaseRxFragment.this.getActivity().getApplication(); + app.getComponentReflection().inject(presenter); + ((BasePresenter) presenter).setContext(app.getApplicationContext()); + return presenter; + } }); super.onCreate(bundle); diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/RxPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/RxPresenter.java index 96028c4467..7789e13e2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/RxPresenter.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/RxPresenter.java @@ -220,7 +220,10 @@ public class RxPresenter extends Presenter { * @param observableFactory a factory that should return an Observable when the startable should run. */ public void startable(int startableId, final Func0> observableFactory) { - restartables.put(startableId, () -> observableFactory.call().subscribe()); + restartables.put(startableId, new Func0() { + @Override + public Subscription call() {return observableFactory.call().subscribe();} + }); } /** @@ -234,7 +237,10 @@ public class RxPresenter extends Presenter { public void startable(int startableId, final Func0> observableFactory, final Action1 onNext, final Action1 onError) { - restartables.put(startableId, () -> observableFactory.call().subscribe(onNext, onError)); + restartables.put(startableId, new Func0() { + @Override + public Subscription call() {return observableFactory.call().subscribe(onNext, onError);} + }); } /** @@ -245,7 +251,10 @@ public class RxPresenter extends Presenter { * @param onNext a callback that will be called when received data should be delivered to view. */ public void startable(int startableId, final Func0> observableFactory, final Action1 onNext) { - restartables.put(startableId, () -> observableFactory.call().subscribe(onNext)); + restartables.put(startableId, new Func0() { + @Override + public Subscription call() {return observableFactory.call().subscribe(onNext);} + }); } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index c77acb19cb..a598512875 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -211,7 +211,7 @@ class CataloguePresenter : BasePresenter() { val obs = if (query.isNullOrEmpty()) source.pullPopularMangasFromNetwork(nextMangasPage) else - source.searchMangasFromNetwork(nextMangasPage, query) + source.searchMangasFromNetwork(nextMangasPage, query!!) return obs.subscribeOn(Schedulers.io()) .doOnNext { lastMangasPage = it } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index 9a67159ebd..599621f813 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -37,7 +37,7 @@ class CategoryPresenter : BasePresenter() { // Get categories as list restartableLatestCache(GET_CATEGORIES, { - db.categories.asRxObservable() + db.getCategories().asRxObservable() .doOnNext { categories -> this.categories = categories } .observeOn(AndroidSchedulers.mainThread()) }, CategoryActivity::setCategories) @@ -76,7 +76,7 @@ class CategoryPresenter : BasePresenter() { * * @param categories list of categories */ - fun deleteCategories(categories: List?) { + fun deleteCategories(categories: List) { db.deleteCategories(categories).asRxObservable().subscribe() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 78d72e8635..f64ac12ef2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -127,7 +127,7 @@ class LibraryPresenter : BasePresenter() { * @return an observable of the categories. */ fun getCategoriesObservable(): Observable> { - return db.categories.asRxObservable() + return db.getCategories().asRxObservable() .doOnNext { categories -> this.categories = categories } } @@ -138,7 +138,7 @@ class LibraryPresenter : BasePresenter() { * value. */ fun getLibraryMangasObservable(): Observable>> { - return db.libraryMangas.asRxObservable() + return db.getLibraryMangas().asRxObservable() .flatMap { mangas -> Observable.from(mangas) .filter { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DynamicConcurrentMergeOperator.java b/app/src/main/java/eu/kanade/tachiyomi/util/DynamicConcurrentMergeOperator.java index 843e6e42b5..c72d52e170 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/DynamicConcurrentMergeOperator.java +++ b/app/src/main/java/eu/kanade/tachiyomi/util/DynamicConcurrentMergeOperator.java @@ -10,6 +10,8 @@ import rx.Observable; import rx.Observable.Operator; import rx.Subscriber; import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; import rx.functions.Func1; import rx.subscriptions.CompositeSubscription; import rx.subscriptions.Subscriptions; @@ -58,29 +60,35 @@ public class DynamicConcurrentMergeOperator implements Operator { } public void init(Observable workerCount) { - Subscription wc = workerCount.subscribe(n -> { - int n0 = workers.size(); - if (n0 < n) { - for (int i = n0; i < n; i++) { - DynamicWorker dw = new DynamicWorker<>(++id, this); - workers.add(dw); - request(1); - dw.tryNext(); - } - } else if (n0 > n) { - for (int i = 0; i < n; i++) { - workers.get(i).start(); + Subscription wc = workerCount.subscribe(new Action1() { + @Override + public void call(Integer n) { + int n0 = workers.size(); + if (n0 < n) { + for (int i = n0; i < n; i++) { + DynamicWorker dw = new DynamicWorker<>(++id, DynamicConcurrentMerge.this); + workers.add(dw); + DynamicConcurrentMerge.this.request(1); + dw.tryNext(); + } + } else if (n0 > n) { + for (int i = 0; i < n; i++) { + workers.get(i).start(); + } + + for (int i = n0 - 1; i >= n; i--) { + workers.get(i).stop(); + } } - for (int i = n0 - 1; i >= n; i--) { - workers.get(i).stop(); + if (!once.get() && once.compareAndSet(false, true)) { + DynamicConcurrentMerge.this.request(n); } } - - if (!once.get() && once.compareAndSet(false, true)) { - request(n); - } - }, this::onError); + }, new Action1() { + @Override + public void call(Throwable e) {DynamicConcurrentMerge.this.onError(e);} + }); composite.add(wc); } @@ -138,9 +146,9 @@ public class DynamicConcurrentMergeOperator implements Operator { return; } - Observable out = parent.mapper.call(t); + Observable out = parent.mapper.call(t); - Subscriber s = new Subscriber() { + final Subscriber s = new Subscriber() { @Override public void onNext(R t) { parent.actual.onNext(t); @@ -163,9 +171,11 @@ public class DynamicConcurrentMergeOperator implements Operator { }; parent.composite.add(s); - s.add(Subscriptions.create(() -> parent.composite.remove(s))); + s.add(Subscriptions.create(new Action0() { + @Override + public void call() {parent.composite.remove(s);} + })); - // Unchecked assignment to avoid weird Android Studio errors out.subscribe(s); } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.java b/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.java deleted file mode 100644 index a553b06575..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.java +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.util; - -import android.util.Pair; - -import java.util.List; - -import rx.Observable; -import rx.functions.Func1; -import rx.subjects.PublishSubject; - -public class RxPager { - - private final PublishSubject> results = PublishSubject.create(); - private int requestedCount; - - public Observable>> results() { - requestedCount = 0; - return results.map(list -> Pair.create(requestedCount++, list)); - } - - public Observable> request(Func1>> networkObservable) { - return networkObservable.call(requestedCount).doOnNext(results::onNext); - } - -} - diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt b/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt new file mode 100644 index 0000000000..c46d68e1ae --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.util + +import android.util.Pair +import rx.Observable +import rx.subjects.PublishSubject + +class RxPager { + + private val results = PublishSubject.create>() + private var requestedCount: Int = 0 + + fun results(): Observable>> { + requestedCount = 0 + return results.map { Pair(requestedCount++, it) } + } + + fun request(networkObservable: (Int) -> Observable>) = + networkObservable(requestedCount).doOnNext { results.onNext(it) } + +} + diff --git a/build.gradle b/build.gradle index 5094a06ed3..922dc04c83 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.1.0-alpha3' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - classpath 'me.tatarka:gradle-retrolambda:3.2.4' classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files