mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Use kapt, remove retrolambda, migrate database and source to Kotlin
This commit is contained in:
		@@ -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') {
 | 
			
		||||
 
 | 
			
		||||
@@ -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<AppComponent> 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<AppComponent> getComponentReflection() {
 | 
			
		||||
        return componentInjector;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getAppTheme() {
 | 
			
		||||
        return theme;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setAppTheme(int theme) {
 | 
			
		||||
        this.theme = theme;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								app/src/main/java/eu/kanade/tachiyomi/App.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								app/src/main/java/eu/kanade/tachiyomi/App.kt
									
									
									
									
									
										Normal file
									
								
							@@ -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<AppComponent>
 | 
			
		||||
        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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<Manga> getMangas() {
 | 
			
		||||
        return db.get()
 | 
			
		||||
                .listOfObjects(Manga.class)
 | 
			
		||||
                .withQuery(Query.builder()
 | 
			
		||||
                        .table(MangaTable.TABLE)
 | 
			
		||||
                        .build())
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedGetListOfObjects<Manga> 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<Manga> 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<Manga> 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<Manga> 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<Manga> insertManga(Manga manga) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .object(manga)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedPutCollectionOfObjects<Manga> insertMangas(List<Manga> mangas) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .objects(mangas)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteObject<Manga> deleteManga(Manga manga) {
 | 
			
		||||
        return db.delete()
 | 
			
		||||
                .object(manga)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteCollectionOfObjects<Manga> deleteMangas(List<Manga> 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<Chapter> 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<MangaChapter> 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<Chapter> 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<Chapter> 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 + "<? AND " +
 | 
			
		||||
                                ChapterTable.COLUMN_CHAPTER_NUMBER + ">=?")
 | 
			
		||||
                        .whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
 | 
			
		||||
                        .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + " DESC")
 | 
			
		||||
                        .limit(1)
 | 
			
		||||
                        .build())
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedGetObject<Chapter> 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<Chapter> insertChapter(Chapter chapter) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .object(chapter)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedPutCollectionOfObjects<Chapter> insertChapters(List<Chapter> chapters) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .objects(chapters)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add new chapters or delete if the source deletes them
 | 
			
		||||
    public Observable<Pair<Integer, Integer>> insertOrRemoveChapters(Manga manga, List<Chapter> sourceChapters, Source source) {
 | 
			
		||||
        List<Chapter> dbChapters = getChapters(manga).executeAsBlocking();
 | 
			
		||||
 | 
			
		||||
        Observable<List<Chapter>> 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<List<Chapter>> 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<Float> 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<Chapter> deleteChapter(Chapter chapter) {
 | 
			
		||||
        return db.delete()
 | 
			
		||||
                .object(chapter)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteCollectionOfObjects<Chapter> deleteChapters(List<Chapter> chapters) {
 | 
			
		||||
        return db.delete()
 | 
			
		||||
                .objects(chapters)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Manga sync related queries
 | 
			
		||||
 | 
			
		||||
    public PreparedGetObject<MangaSync> 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<MangaSync> 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<MangaSync> insertMangaSync(MangaSync manga) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .object(manga)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteObject<MangaSync> deleteMangaSync(MangaSync manga) {
 | 
			
		||||
        return db.delete()
 | 
			
		||||
                .object(manga)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Categories related queries
 | 
			
		||||
 | 
			
		||||
    public PreparedGetListOfObjects<Category> getCategories() {
 | 
			
		||||
        return db.get()
 | 
			
		||||
                .listOfObjects(Category.class)
 | 
			
		||||
                .withQuery(Query.builder()
 | 
			
		||||
                        .table(CategoryTable.TABLE)
 | 
			
		||||
                        .orderBy(CategoryTable.COLUMN_ORDER)
 | 
			
		||||
                        .build())
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedPutObject<Category> insertCategory(Category category) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .object(category)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedPutCollectionOfObjects<Category> insertCategories(List<Category> categories) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .objects(categories)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteObject<Category> deleteCategory(Category category) {
 | 
			
		||||
        return db.delete()
 | 
			
		||||
                .object(category)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteCollectionOfObjects<Category> deleteCategories(List<Category> categories) {
 | 
			
		||||
        return db.delete()
 | 
			
		||||
                .objects(categories)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedPutObject<MangaCategory> insertMangaCategory(MangaCategory mangaCategory) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .object(mangaCategory)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedPutCollectionOfObjects<MangaCategory> insertMangasCategories(List<MangaCategory> mangasCategories) {
 | 
			
		||||
        return db.put()
 | 
			
		||||
                .objects(mangasCategories)
 | 
			
		||||
                .prepare();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PreparedDeleteByQuery deleteOldMangasCategories(List<Manga> mangas) {
 | 
			
		||||
        List<Long> 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<MangaCategory> mangasCategories, List<Manga> mangas) {
 | 
			
		||||
        db.internal().beginTransaction();
 | 
			
		||||
        try {
 | 
			
		||||
            deleteOldMangasCategories(mangas).executeAsBlocking();
 | 
			
		||||
            insertMangasCategories(mangasCategories).executeAsBlocking();
 | 
			
		||||
            db.internal().setTransactionSuccessful();
 | 
			
		||||
        } finally {
 | 
			
		||||
            db.internal().endTransaction();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<Manga>) = db.put().objects(mangas).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteMangasNotInLibrary() = db.delete()
 | 
			
		||||
            .byQuery(DeleteQuery.builder()
 | 
			
		||||
                    .table(MangaTable.TABLE)
 | 
			
		||||
                    .where("${MangaTable.COLUMN_FAVORITE} = ?")
 | 
			
		||||
                    .whereArgs(0)
 | 
			
		||||
                    .build())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Chapters related queries
 | 
			
		||||
 | 
			
		||||
    fun getChapters(manga: Manga) = db.get()
 | 
			
		||||
            .listOfObjects(Chapter::class.java)
 | 
			
		||||
            .withQuery(Query.builder()
 | 
			
		||||
                    .table(ChapterTable.TABLE)
 | 
			
		||||
                    .where("${ChapterTable.COLUMN_MANGA_ID} = ?")
 | 
			
		||||
                    .whereArgs(manga.id)
 | 
			
		||||
                    .build())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun getRecentChapters(date: Date) = db.get()
 | 
			
		||||
            .listOfObjects(MangaChapter::class.java)
 | 
			
		||||
            .withQuery(RawQuery.builder()
 | 
			
		||||
                    .query(getRecentsQuery(date))
 | 
			
		||||
                    .observesTables(ChapterTable.TABLE)
 | 
			
		||||
                    .build())
 | 
			
		||||
            .withGetResolver(MangaChapterGetResolver.INSTANCE)
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun getNextChapter(chapter: Chapter): PreparedGetObject<Chapter> {
 | 
			
		||||
        // Add a delta to the chapter number, because binary decimal representation
 | 
			
		||||
        // can retrieve the same chapter again
 | 
			
		||||
        val chapterNumber = chapter.chapter_number + 0.00001
 | 
			
		||||
 | 
			
		||||
        return db.get()
 | 
			
		||||
                .`object`(Chapter::class.java)
 | 
			
		||||
                .withQuery(Query.builder()
 | 
			
		||||
                        .table(ChapterTable.TABLE)
 | 
			
		||||
                        .where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
 | 
			
		||||
                                "${ChapterTable.COLUMN_CHAPTER_NUMBER} > ? AND " +
 | 
			
		||||
                                "${ChapterTable.COLUMN_CHAPTER_NUMBER} <= ?")
 | 
			
		||||
                        .whereArgs(chapter.manga_id, chapterNumber, chapterNumber + 1)
 | 
			
		||||
                        .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
 | 
			
		||||
                        .limit(1)
 | 
			
		||||
                        .build())
 | 
			
		||||
                .prepare()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getPreviousChapter(chapter: Chapter): PreparedGetObject<Chapter> {
 | 
			
		||||
        // Add a delta to the chapter number, because binary decimal representation
 | 
			
		||||
        // can retrieve the same chapter again
 | 
			
		||||
        val chapterNumber = chapter.chapter_number - 0.00001
 | 
			
		||||
 | 
			
		||||
        return db.get()
 | 
			
		||||
                .`object`(Chapter::class.java)
 | 
			
		||||
                .withQuery(Query.builder().table(ChapterTable.TABLE)
 | 
			
		||||
                        .where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
 | 
			
		||||
                                "${ChapterTable.COLUMN_CHAPTER_NUMBER} < ? AND " +
 | 
			
		||||
                                "${ChapterTable.COLUMN_CHAPTER_NUMBER} >= ?")
 | 
			
		||||
                        .whereArgs(chapter.manga_id, chapterNumber, chapterNumber - 1)
 | 
			
		||||
                        .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + " DESC")
 | 
			
		||||
                        .limit(1)
 | 
			
		||||
                        .build())
 | 
			
		||||
                .prepare()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getNextUnreadChapter(manga: Manga) = db.get()
 | 
			
		||||
            .`object`(Chapter::class.java)
 | 
			
		||||
            .withQuery(Query.builder()
 | 
			
		||||
                    .table(ChapterTable.TABLE)
 | 
			
		||||
                    .where("${ChapterTable.COLUMN_MANGA_ID} = ? AND " +
 | 
			
		||||
                            "${ChapterTable.COLUMN_READ} = ? AND " +
 | 
			
		||||
                            "${ChapterTable.COLUMN_CHAPTER_NUMBER} >= ?")
 | 
			
		||||
                    .whereArgs(manga.id, 0, 0)
 | 
			
		||||
                    .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER)
 | 
			
		||||
                    .limit(1)
 | 
			
		||||
                    .build())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
 | 
			
		||||
 | 
			
		||||
    fun insertChapters(chapters: List<Chapter>) = db.put().objects(chapters).prepare()
 | 
			
		||||
 | 
			
		||||
    // Add new chapters or delete if the source deletes them
 | 
			
		||||
    fun insertOrRemoveChapters(manga: Manga, sourceChapters: List<Chapter>, source: Source): Observable<Pair<Int, Int>> {
 | 
			
		||||
        val dbChapters = getChapters(manga).executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        val newChapters = Observable.from(sourceChapters)
 | 
			
		||||
                .filter { it !in dbChapters }
 | 
			
		||||
                .doOnNext { c ->
 | 
			
		||||
                    c.manga_id = manga.id
 | 
			
		||||
                    source.parseChapterNumber(c)
 | 
			
		||||
                    ChapterRecognition.parseChapterNumber(c, manga)
 | 
			
		||||
                }.toList()
 | 
			
		||||
 | 
			
		||||
        val deletedChapters = Observable.from(dbChapters)
 | 
			
		||||
                .filter { it !in sourceChapters }
 | 
			
		||||
                .toList()
 | 
			
		||||
 | 
			
		||||
        return Observable.zip(newChapters, deletedChapters) { toAdd, toDelete ->
 | 
			
		||||
            var added = 0
 | 
			
		||||
            var deleted = 0
 | 
			
		||||
            var readded = 0
 | 
			
		||||
 | 
			
		||||
            inTransaction {
 | 
			
		||||
                val deletedReadChapterNumbers = TreeSet<Float>()
 | 
			
		||||
                if (!toDelete.isEmpty()) {
 | 
			
		||||
                    for (c in toDelete) {
 | 
			
		||||
                        if (c.read) {
 | 
			
		||||
                            deletedReadChapterNumbers.add(c.chapter_number)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    deleted = deleteChapters(toDelete).executeAsBlocking().results().size
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!toAdd.isEmpty()) {
 | 
			
		||||
                    // Set the date fetch for new items in reverse order to allow another sorting method.
 | 
			
		||||
                    // Sources MUST return the chapters from most to less recent, which is common.
 | 
			
		||||
                    var now = Date().time
 | 
			
		||||
 | 
			
		||||
                    for (i in toAdd.indices.reversed()) {
 | 
			
		||||
                        val c = toAdd[i]
 | 
			
		||||
                        c.date_fetch = now++
 | 
			
		||||
                        // Try to mark already read chapters as read when the source deletes them
 | 
			
		||||
                        if (c.chapter_number != -1f && c.chapter_number in deletedReadChapterNumbers) {
 | 
			
		||||
                            c.read = true
 | 
			
		||||
                            readded++
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    added = insertChapters(toAdd).executeAsBlocking().numberOfInserts()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Pair.create(added - readded, deleted - readded)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun deleteChapter(chapter: Chapter) = db.delete().`object`(chapter).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
 | 
			
		||||
 | 
			
		||||
    // Manga sync related queries
 | 
			
		||||
 | 
			
		||||
    fun getMangaSync(manga: Manga, sync: MangaSyncService) = db.get()
 | 
			
		||||
            .`object`(MangaSync::class.java)
 | 
			
		||||
            .withQuery(Query.builder()
 | 
			
		||||
                    .table(MangaSyncTable.TABLE)
 | 
			
		||||
                    .where("${MangaSyncTable.COLUMN_MANGA_ID} = ? AND " +
 | 
			
		||||
                            "${MangaSyncTable.COLUMN_SYNC_ID} = ?")
 | 
			
		||||
                    .whereArgs(manga.id, sync.id)
 | 
			
		||||
                    .build())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun getMangasSync(manga: Manga) = db.get()
 | 
			
		||||
            .listOfObjects(MangaSync::class.java)
 | 
			
		||||
            .withQuery(Query.builder()
 | 
			
		||||
                    .table(MangaSyncTable.TABLE)
 | 
			
		||||
                    .where("${MangaSyncTable.COLUMN_MANGA_ID} = ?")
 | 
			
		||||
                    .whereArgs(manga.id)
 | 
			
		||||
                    .build())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun insertMangaSync(manga: MangaSync) = db.put().`object`(manga).prepare()
 | 
			
		||||
 | 
			
		||||
    fun 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<Category>) = db.put().objects(categories).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteCategory(category: Category) = db.delete().`object`(category).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteCategories(categories: List<Category>) = db.delete().objects(categories).prepare()
 | 
			
		||||
 | 
			
		||||
    fun insertMangaCategory(mangaCategory: MangaCategory) = db.put().`object`(mangaCategory).prepare()
 | 
			
		||||
 | 
			
		||||
    fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteOldMangasCategories(mangas: List<Manga>): 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<MangaCategory>, mangas: List<Manga>) {
 | 
			
		||||
        inTransaction {
 | 
			
		||||
            deleteOldMangasCategories(mangas).executeAsBlocking()
 | 
			
		||||
            insertMangasCategories(mangasCategories).executeAsBlocking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@ import android.content.Context;
 | 
			
		||||
 | 
			
		||||
public abstract class LoginSource extends Source {
 | 
			
		||||
 | 
			
		||||
    public LoginSource() {}
 | 
			
		||||
 | 
			
		||||
    public LoginSource(Context context) {
 | 
			
		||||
        super(context);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<MangasPage> 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<MangasPage> 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<Manga> 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<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
 | 
			
		||||
        return networkService
 | 
			
		||||
                .requestBody(chapterListRequest(mangaUrl))
 | 
			
		||||
                .flatMap(unparsedHtml -> {
 | 
			
		||||
                    List<Chapter> chapters = parseHtmlToChapters(unparsedHtml);
 | 
			
		||||
                    return !chapters.isEmpty() ?
 | 
			
		||||
                            Observable.just(chapters) :
 | 
			
		||||
                            Observable.error(new Exception("No chapters found"));
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Observable<List<Page>> getCachedPageListOrPullFromNetwork(final String chapterUrl) {
 | 
			
		||||
        return chapterCache.getPageListFromCache(getChapterCacheKey(chapterUrl))
 | 
			
		||||
                .onErrorResumeNext(throwable -> {
 | 
			
		||||
                    return pullPageListFromNetwork(chapterUrl);
 | 
			
		||||
                })
 | 
			
		||||
                .onBackpressureBuffer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) {
 | 
			
		||||
        return networkService
 | 
			
		||||
                .requestBody(pageListRequest(chapterUrl))
 | 
			
		||||
                .flatMap(unparsedHtml -> {
 | 
			
		||||
                    List<Page> pages = convertToPages(parseHtmlToPageUrls(unparsedHtml));
 | 
			
		||||
                    return !pages.isEmpty() ?
 | 
			
		||||
                            Observable.just(parseFirstPage(pages, unparsedHtml)) :
 | 
			
		||||
                            Observable.error(new Exception("Page list is empty"));
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Observable<Page> getAllImageUrlsFromPageList(final List<Page> pages) {
 | 
			
		||||
        return Observable.from(pages)
 | 
			
		||||
                .filter(page -> page.getImageUrl() != null)
 | 
			
		||||
                .mergeWith(getRemainingImageUrlsFromPageList(pages));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get the URLs of the images of a chapter
 | 
			
		||||
    public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) {
 | 
			
		||||
        return Observable.from(pages)
 | 
			
		||||
                .filter(page -> page.getImageUrl() == null)
 | 
			
		||||
                .concatMap(this::getImageUrlFromPage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Observable<Page> 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<Page> getCachedImage(final Page page) {
 | 
			
		||||
        Observable<Page> 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<Page> 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<Response> getImageProgressResponse(final Page page) {
 | 
			
		||||
        return networkService.requestBodyProgress(imageRequest(page), page);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void savePageList(String chapterUrl, List<Page> pages) {
 | 
			
		||||
        if (pages != null)
 | 
			
		||||
            chapterCache.putPageListToCache(getChapterCacheKey(chapterUrl), pages);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected List<Page> convertToPages(List<String> pageUrls) {
 | 
			
		||||
        List<Page> pages = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < pageUrls.size(); i++) {
 | 
			
		||||
            pages.add(new Page(i, pageUrls.get(i)));
 | 
			
		||||
        }
 | 
			
		||||
        return pages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected List<Page> parseFirstPage(List<Page> 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<String, List<String>> entry : requestHeaders.toMultimap().entrySet()) {
 | 
			
		||||
            builder.addHeader(entry.getKey(), entry.getValue().get(0));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return builder;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public LazyHeaders getGlideHeaders() {
 | 
			
		||||
        return glideHeaders;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										230
									
								
								app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								app/src/main/java/eu/kanade/tachiyomi/data/source/base/Source.kt
									
									
									
									
									
										Normal file
									
								
							@@ -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<MangasPage> {
 | 
			
		||||
        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<MangasPage> {
 | 
			
		||||
        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<Manga> {
 | 
			
		||||
        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<List<Chapter>> {
 | 
			
		||||
        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<List<Page>> {
 | 
			
		||||
        return chapterCache.getPageListFromCache(getChapterCacheKey(chapterUrl))
 | 
			
		||||
                .onErrorResumeNext { pullPageListFromNetwork(chapterUrl) }
 | 
			
		||||
                .onBackpressureBuffer()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun pullPageListFromNetwork(chapterUrl: String): Observable<List<Page>> {
 | 
			
		||||
        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<Page>): Observable<Page> {
 | 
			
		||||
        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<Page>): Observable<Page> {
 | 
			
		||||
        return Observable.from(pages)
 | 
			
		||||
                .filter { page -> page.imageUrl == null }
 | 
			
		||||
                .concatMap { getImageUrlFromPage(it) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getImageUrlFromPage(page: Page): Observable<Page> {
 | 
			
		||||
        page.status = Page.LOAD_PAGE
 | 
			
		||||
        return networkService.requestBody(imageUrlRequest(page))
 | 
			
		||||
                .flatMap { unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)) }
 | 
			
		||||
                .onErrorResumeNext { e ->
 | 
			
		||||
                    page.status = Page.ERROR
 | 
			
		||||
                    Observable.just<String>(null)
 | 
			
		||||
                }
 | 
			
		||||
                .flatMap { imageUrl ->
 | 
			
		||||
                    page.imageUrl = imageUrl
 | 
			
		||||
                    Observable.just(page)
 | 
			
		||||
                }
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getCachedImage(page: Page): Observable<Page> {
 | 
			
		||||
        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> {
 | 
			
		||||
        page.status = Page.DOWNLOAD_IMAGE
 | 
			
		||||
        return getImageProgressResponse(page)
 | 
			
		||||
                .flatMap { resp ->
 | 
			
		||||
                    chapterCache.putImageToCache(page.imageUrl, resp)
 | 
			
		||||
                    Observable.just(page)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getImageProgressResponse(page: Page): Observable<Response> {
 | 
			
		||||
        return networkService.requestBodyProgress(imageRequest(page), page)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun savePageList(chapterUrl: String, pages: List<Page>?) {
 | 
			
		||||
        if (pages != null)
 | 
			
		||||
            chapterCache.putPageListToCache(getChapterCacheKey(chapterUrl), pages)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected fun convertToPages(pageUrls: List<String>): List<Page> {
 | 
			
		||||
        val pages = ArrayList<Page>()
 | 
			
		||||
        for (i in pageUrls.indices) {
 | 
			
		||||
            pages.add(Page(i, pageUrls[i]))
 | 
			
		||||
        }
 | 
			
		||||
        return pages
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected open fun parseFirstPage(pages: List<Page>, unparsedHtml: String): List<Page> {
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<Manga> parseMangasFromHtml(Document parsedHtml) {
 | 
			
		||||
@@ -293,7 +294,7 @@ public class Batoto extends LoginSource {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected List<Page> parseFirstPage(List<Page> pages, String unparsedHtml) {
 | 
			
		||||
    protected List<Page> parseFirstPage(List<? extends Page> 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<Page>) pages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -320,10 +321,16 @@ public class Batoto extends LoginSource {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Observable<Boolean> login(String username, String password) {
 | 
			
		||||
        return networkService.requestBody(ReqKt.get(LOGIN_URL, requestHeaders))
 | 
			
		||||
                .flatMap(response -> doLogin(response, username, password))
 | 
			
		||||
                .map(this::isAuthenticationSuccessful);
 | 
			
		||||
    public Observable<Boolean> login(final String username, final String password) {
 | 
			
		||||
        return getNetworkService().requestBody(ReqKt.get(LOGIN_URL, getRequestHeaders()))
 | 
			
		||||
                .flatMap(new Func1<String, Observable<Response>>() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public Observable<Response> call(String response) {return doLogin(response, username, password);}
 | 
			
		||||
                })
 | 
			
		||||
                .map(new Func1<Response, Boolean>() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public Boolean call(Response resp) {return isAuthenticationSuccessful(resp);}
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Observable<Response> 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<List<Chapter>> pullChaptersFromNetwork(String mangaUrl) {
 | 
			
		||||
    public Observable<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
 | 
			
		||||
        Observable<List<Chapter>> 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<Boolean, Observable<? extends List<Chapter>>>() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public Observable<? extends List<Chapter>> call(Boolean result) {return Batoto.super.pullChaptersFromNetwork(mangaUrl);}
 | 
			
		||||
                    });
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            observable = super.pullChaptersFromNetwork(mangaUrl);
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Page> parseFirstPage(List<Page> pages, String unparsedHtml) {
 | 
			
		||||
    protected List<Page> parseFirstPage(List<? extends Page> 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<Page>) pages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -218,7 +218,7 @@ public class Mangachan extends Source {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected List<Page> parseFirstPage(List<Page> pages, String unparsedHtml) {
 | 
			
		||||
    protected List<Page> parseFirstPage(List<? extends Page> 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<Page>) pages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -203,7 +203,7 @@ public class Mintmanga extends Source {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected List<Page> parseFirstPage(List<Page> pages, String unparsedHtml) {
 | 
			
		||||
    protected List<Page> parseFirstPage(List<? extends Page> 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<Page>) pages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -203,7 +203,7 @@ public class Readmanga extends Source {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected List<Page> parseFirstPage(List<Page> pages, String unparsedHtml) {
 | 
			
		||||
    protected List<Page> parseFirstPage(List<? extends Page> 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<Page>) pages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -58,12 +58,15 @@ public abstract class BaseRxActivity<P extends Presenter> extends BaseActivity i
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        final PresenterFactory<P> 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<P>() {
 | 
			
		||||
            @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);
 | 
			
		||||
 
 | 
			
		||||
@@ -56,12 +56,15 @@ public abstract class BaseRxFragment<P extends Presenter> extends BaseFragment i
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreate(Bundle bundle) {
 | 
			
		||||
        final PresenterFactory<P> 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<P>() {
 | 
			
		||||
            @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);
 | 
			
		||||
 
 | 
			
		||||
@@ -220,7 +220,10 @@ public class RxPresenter<View> extends Presenter<View> {
 | 
			
		||||
     * @param observableFactory a factory that should return an Observable when the startable should run.
 | 
			
		||||
     */
 | 
			
		||||
    public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory) {
 | 
			
		||||
        restartables.put(startableId, () -> observableFactory.call().subscribe());
 | 
			
		||||
        restartables.put(startableId, new Func0<Subscription>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Subscription call() {return observableFactory.call().subscribe();}
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -234,7 +237,10 @@ public class RxPresenter<View> extends Presenter<View> {
 | 
			
		||||
    public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory,
 | 
			
		||||
        final Action1<T> onNext, final Action1<Throwable> onError) {
 | 
			
		||||
 | 
			
		||||
        restartables.put(startableId, () -> observableFactory.call().subscribe(onNext, onError));
 | 
			
		||||
        restartables.put(startableId, new Func0<Subscription>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Subscription call() {return observableFactory.call().subscribe(onNext, onError);}
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -245,7 +251,10 @@ public class RxPresenter<View> extends Presenter<View> {
 | 
			
		||||
     * @param onNext            a callback that will be called when received data should be delivered to view.
 | 
			
		||||
     */
 | 
			
		||||
    public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory, final Action1<T> onNext) {
 | 
			
		||||
        restartables.put(startableId, () -> observableFactory.call().subscribe(onNext));
 | 
			
		||||
        restartables.put(startableId, new Func0<Subscription>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Subscription call() {return observableFactory.call().subscribe(onNext);}
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -211,7 +211,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
 | 
			
		||||
        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 }
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ class CategoryPresenter : BasePresenter<CategoryActivity>() {
 | 
			
		||||
        // 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<CategoryActivity>() {
 | 
			
		||||
     *
 | 
			
		||||
     * @param categories list of categories
 | 
			
		||||
     */
 | 
			
		||||
    fun deleteCategories(categories: List<Category?>?) {
 | 
			
		||||
    fun deleteCategories(categories: List<Category>) {
 | 
			
		||||
        db.deleteCategories(categories).asRxObservable().subscribe()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -127,7 +127,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
 | 
			
		||||
     * @return an observable of the categories.
 | 
			
		||||
     */
 | 
			
		||||
    fun getCategoriesObservable(): Observable<List<Category>> {
 | 
			
		||||
        return db.categories.asRxObservable()
 | 
			
		||||
        return db.getCategories().asRxObservable()
 | 
			
		||||
                .doOnNext { categories -> this.categories = categories }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -138,7 +138,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
 | 
			
		||||
     * value.
 | 
			
		||||
     */
 | 
			
		||||
    fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
 | 
			
		||||
        return db.libraryMangas.asRxObservable()
 | 
			
		||||
        return db.getLibraryMangas().asRxObservable()
 | 
			
		||||
                .flatMap { mangas ->
 | 
			
		||||
                    Observable.from(mangas)
 | 
			
		||||
                            .filter {
 | 
			
		||||
 
 | 
			
		||||
@@ -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<T, R> implements Operator<R, T> {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void init(Observable<Integer> workerCount) {
 | 
			
		||||
            Subscription wc = workerCount.subscribe(n -> {
 | 
			
		||||
                int n0 = workers.size();
 | 
			
		||||
                if (n0 < n) {
 | 
			
		||||
                    for (int i = n0; i < n; i++) {
 | 
			
		||||
                        DynamicWorker<T, R> 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<Integer>() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void call(Integer n) {
 | 
			
		||||
                    int n0 = workers.size();
 | 
			
		||||
                    if (n0 < n) {
 | 
			
		||||
                        for (int i = n0; i < n; i++) {
 | 
			
		||||
                            DynamicWorker<T, R> 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<Throwable>() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void call(Throwable e) {DynamicConcurrentMerge.this.onError(e);}
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            composite.add(wc);
 | 
			
		||||
        }
 | 
			
		||||
@@ -138,9 +146,9 @@ public class DynamicConcurrentMergeOperator<T, R> implements Operator<R, T> {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Observable out = parent.mapper.call(t);
 | 
			
		||||
                Observable<? extends R> out = parent.mapper.call(t);
 | 
			
		||||
 | 
			
		||||
                Subscriber<R> s = new Subscriber<R>() {
 | 
			
		||||
                final Subscriber<R> s = new Subscriber<R>() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void onNext(R t) {
 | 
			
		||||
                        parent.actual.onNext(t);
 | 
			
		||||
@@ -163,9 +171,11 @@ public class DynamicConcurrentMergeOperator<T, R> implements Operator<R, T> {
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<T> {
 | 
			
		||||
 | 
			
		||||
    private final PublishSubject<List<T>> results = PublishSubject.create();
 | 
			
		||||
    private int requestedCount;
 | 
			
		||||
 | 
			
		||||
    public Observable<Pair<Integer, List<T>>> results() {
 | 
			
		||||
        requestedCount = 0;
 | 
			
		||||
        return results.map(list -> Pair.create(requestedCount++, list));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Observable<List<T>> request(Func1<Integer, Observable<List<T>>> networkObservable) {
 | 
			
		||||
        return networkObservable.call(requestedCount).doOnNext(results::onNext);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/src/main/java/eu/kanade/tachiyomi/util/RxPager.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package eu.kanade.tachiyomi.util
 | 
			
		||||
 | 
			
		||||
import android.util.Pair
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import rx.subjects.PublishSubject
 | 
			
		||||
 | 
			
		||||
class RxPager<T> {
 | 
			
		||||
 | 
			
		||||
    private val results = PublishSubject.create<List<T>>()
 | 
			
		||||
    private var requestedCount: Int = 0
 | 
			
		||||
 | 
			
		||||
    fun results(): Observable<Pair<Int, List<T>>> {
 | 
			
		||||
        requestedCount = 0
 | 
			
		||||
        return results.map { Pair(requestedCount++, it) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun request(networkObservable: (Int) -> Observable<List<T>>) =
 | 
			
		||||
        networkObservable(requestedCount).doOnNext { results.onNext(it) }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user