mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 16:41:51 +01:00
Use kapt, remove retrolambda, migrate database and source to Kotlin
This commit is contained in:
parent
0d519b3d16
commit
14f248546a
@ -4,11 +4,6 @@ apply plugin: 'com.android.application'
|
|||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'com.neenbedankt.android-apt'
|
apply plugin: 'com.neenbedankt.android-apt'
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
|
||||||
|
|
||||||
retrolambda {
|
|
||||||
jvmArgs '-noverify'
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
// Git is needed in your system PATH for these commands to work.
|
// Git is needed in your system PATH for these commands to work.
|
||||||
@ -55,11 +50,6 @@ android {
|
|||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
@ -93,10 +83,8 @@ android {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apt {
|
kapt {
|
||||||
arguments {
|
generateStubs = true
|
||||||
eventBusIndex "eu.kanade.tachiyomi.EventBusIndex"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -146,8 +134,8 @@ dependencies {
|
|||||||
apt "org.greenrobot:eventbus-annotation-processor:3.0.1"
|
apt "org.greenrobot:eventbus-annotation-processor:3.0.1"
|
||||||
|
|
||||||
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
apt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
||||||
provided 'org.glassfish:javax.annotation:10.0-b28'
|
provided 'org.glassfish:javax.annotation:10.0-b28'
|
||||||
|
|
||||||
compile('com.github.afollestad.material-dialogs:core:0.8.5.7@aar') {
|
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)
|
Intent(this, CancelUpdateReceiver::class.java), 0)
|
||||||
|
|
||||||
// Get the manga list that is going to be updated.
|
// 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())
|
val toUpdate = if (!preferences.updateOnlyNonCompleted())
|
||||||
allLibraryMangas
|
allLibraryMangas
|
||||||
else
|
else
|
||||||
|
@ -4,8 +4,6 @@ import android.content.Context;
|
|||||||
|
|
||||||
public abstract class LoginSource extends Source {
|
public abstract class LoginSource extends Source {
|
||||||
|
|
||||||
public LoginSource() {}
|
|
||||||
|
|
||||||
public LoginSource(Context context) {
|
public LoginSource(Context context) {
|
||||||
super(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.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
|
import rx.functions.Func1;
|
||||||
|
|
||||||
public class Batoto extends LoginSource {
|
public class Batoto extends LoginSource {
|
||||||
|
|
||||||
@ -106,13 +107,13 @@ public class Batoto extends LoginSource {
|
|||||||
@Override
|
@Override
|
||||||
protected Request mangaDetailsRequest(String mangaUrl) {
|
protected Request mangaDetailsRequest(String mangaUrl) {
|
||||||
String mangaId = mangaUrl.substring(mangaUrl.lastIndexOf("r") + 1);
|
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
|
@Override
|
||||||
protected Request pageListRequest(String pageUrl) {
|
protected Request pageListRequest(String pageUrl) {
|
||||||
String id = pageUrl.substring(pageUrl.indexOf("#") + 1);
|
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
|
@Override
|
||||||
@ -121,7 +122,7 @@ public class Batoto extends LoginSource {
|
|||||||
int start = pageUrl.indexOf("#") + 1;
|
int start = pageUrl.indexOf("#") + 1;
|
||||||
int end = pageUrl.indexOf("_", start);
|
int end = pageUrl.indexOf("_", start);
|
||||||
String id = pageUrl.substring(start, end);
|
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) {
|
private List<Manga> parseMangasFromHtml(Document parsedHtml) {
|
||||||
@ -293,7 +294,7 @@ public class Batoto extends LoginSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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?")) {
|
if (!unparsedHtml.contains("Want to see this chapter per page instead?")) {
|
||||||
String firstImage = parseHtmlToImageUrl(unparsedHtml);
|
String firstImage = parseHtmlToImageUrl(unparsedHtml);
|
||||||
pages.get(0).setImageUrl(firstImage);
|
pages.get(0).setImageUrl(firstImage);
|
||||||
@ -305,7 +306,7 @@ public class Batoto extends LoginSource {
|
|||||||
pages.get(i).setImageUrl(imageUrls.get(i).attr("src"));
|
pages.get(i).setImageUrl(imageUrls.get(i).attr("src"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pages;
|
return (List<Page>) pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -320,10 +321,16 @@ public class Batoto extends LoginSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Observable<Boolean> login(String username, String password) {
|
public Observable<Boolean> login(final String username, final String password) {
|
||||||
return networkService.requestBody(ReqKt.get(LOGIN_URL, requestHeaders))
|
return getNetworkService().requestBody(ReqKt.get(LOGIN_URL, getRequestHeaders()))
|
||||||
.flatMap(response -> doLogin(response, username, password))
|
.flatMap(new Func1<String, Observable<Response>>() {
|
||||||
.map(this::isAuthenticationSuccessful);
|
@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) {
|
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("invisible", "1");
|
||||||
formBody.add("rememberMe", "1");
|
formBody.add("rememberMe", "1");
|
||||||
|
|
||||||
return networkService.request(ReqKt.post(postUrl, requestHeaders, formBody.build()));
|
return getNetworkService().request(ReqKt.post(postUrl, getRequestHeaders(), formBody.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -351,7 +358,7 @@ public class Batoto extends LoginSource {
|
|||||||
@Override
|
@Override
|
||||||
public boolean isLogged() {
|
public boolean isLogged() {
|
||||||
try {
|
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"))
|
if (cookie.getName().equals("pass_hash"))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -363,16 +370,19 @@ public class Batoto extends LoginSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Observable<List<Chapter>> pullChaptersFromNetwork(String mangaUrl) {
|
public Observable<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
|
||||||
Observable<List<Chapter>> observable;
|
Observable<List<Chapter>> observable;
|
||||||
String username = prefs.getSourceUsername(this);
|
String username = getPrefs().getSourceUsername(this);
|
||||||
String password = prefs.getSourcePassword(this);
|
String password = getPrefs().getSourcePassword(this);
|
||||||
if (username.isEmpty() && password.isEmpty()) {
|
if (username.isEmpty() && password.isEmpty()) {
|
||||||
observable = Observable.error(new Exception("User not logged"));
|
observable = Observable.error(new Exception("User not logged"));
|
||||||
}
|
}
|
||||||
else if (!isLogged()) {
|
else if (!isLogged()) {
|
||||||
observable = login(username, password)
|
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 {
|
else {
|
||||||
observable = super.pullChaptersFromNetwork(mangaUrl);
|
observable = super.pullChaptersFromNetwork(mangaUrl);
|
||||||
|
@ -84,12 +84,12 @@ public class Kissmanga extends Source {
|
|||||||
form.add("status", "");
|
form.add("status", "");
|
||||||
form.add("genres", "");
|
form.add("genres", "");
|
||||||
|
|
||||||
return ReqKt.post(page.url, requestHeaders, form.build());
|
return ReqKt.post(page.url, getRequestHeaders(), form.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Request pageListRequest(String chapterUrl) {
|
protected Request pageListRequest(String chapterUrl) {
|
||||||
return ReqKt.post(getBaseUrl() + chapterUrl, requestHeaders);
|
return ReqKt.post(getBaseUrl() + chapterUrl, getRequestHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -215,7 +215,7 @@ public class Kissmanga extends Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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\\(\"(.+?)\"");
|
Pattern p = Pattern.compile("lstImages.push\\(\"(.+?)\"");
|
||||||
Matcher m = p.matcher(unparsedHtml);
|
Matcher m = p.matcher(unparsedHtml);
|
||||||
|
|
||||||
@ -223,7 +223,7 @@ public class Kissmanga extends Source {
|
|||||||
while (m.find()) {
|
while (m.find()) {
|
||||||
pages.get(i++).setImageUrl(m.group(1));
|
pages.get(i++).setImageUrl(m.group(1));
|
||||||
}
|
}
|
||||||
return pages;
|
return (List<Page>) pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -218,7 +218,7 @@ public class Mangachan extends Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 beginIndex = unparsedHtml.indexOf("fullimg\":[");
|
||||||
int endIndex = unparsedHtml.indexOf("]", beginIndex);
|
int endIndex = unparsedHtml.indexOf("]", beginIndex);
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ public class Mangachan extends Source {
|
|||||||
pages.get(i).setImageUrl(pageUrls[i].replaceAll("im.?\\.", ""));
|
pages.get(i).setImageUrl(pageUrls[i].replaceAll("im.?\\.", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages;
|
return (List<Page>) pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -203,7 +203,7 @@ public class Mintmanga extends Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 beginIndex = unparsedHtml.indexOf("rm_h.init( [");
|
||||||
int endIndex = unparsedHtml.indexOf("], 0, false);", beginIndex);
|
int endIndex = unparsedHtml.indexOf("], 0, false);", beginIndex);
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ public class Mintmanga extends Source {
|
|||||||
String page = urlParts[1] + urlParts[0] + urlParts[2];
|
String page = urlParts[1] + urlParts[0] + urlParts[2];
|
||||||
pages.get(i).setImageUrl(page);
|
pages.get(i).setImageUrl(page);
|
||||||
}
|
}
|
||||||
return pages;
|
return (List<Page>) pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -203,7 +203,7 @@ public class Readmanga extends Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 beginIndex = unparsedHtml.indexOf("rm_h.init( [");
|
||||||
int endIndex = unparsedHtml.indexOf("], 0, false);", beginIndex);
|
int endIndex = unparsedHtml.indexOf("], 0, false);", beginIndex);
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ public class Readmanga extends Source {
|
|||||||
String page = urlParts[1] + urlParts[0] + urlParts[2];
|
String page = urlParts[1] + urlParts[0] + urlParts[2];
|
||||||
pages.get(i).setImageUrl(page);
|
pages.get(i).setImageUrl(page);
|
||||||
}
|
}
|
||||||
return pages;
|
return (List<Page>) pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
final PresenterFactory<P> superFactory = getPresenterFactory();
|
final PresenterFactory<P> superFactory = getPresenterFactory();
|
||||||
setPresenterFactory(() -> {
|
setPresenterFactory(new PresenterFactory<P>() {
|
||||||
P presenter = superFactory.createPresenter();
|
@Override
|
||||||
App app = (App) getApplication();
|
public P createPresenter() {
|
||||||
app.getComponentReflection().inject(presenter);
|
P presenter = superFactory.createPresenter();
|
||||||
((BasePresenter)presenter).setContext(app.getApplicationContext());
|
App app = (App) BaseRxActivity.this.getApplication();
|
||||||
return presenter;
|
app.getComponentReflection().inject(presenter);
|
||||||
|
((BasePresenter) presenter).setContext(app.getApplicationContext());
|
||||||
|
return presenter;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -56,12 +56,15 @@ public abstract class BaseRxFragment<P extends Presenter> extends BaseFragment i
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
final PresenterFactory<P> superFactory = getPresenterFactory();
|
final PresenterFactory<P> superFactory = getPresenterFactory();
|
||||||
setPresenterFactory(() -> {
|
setPresenterFactory(new PresenterFactory<P>() {
|
||||||
P presenter = superFactory.createPresenter();
|
@Override
|
||||||
App app = (App) getActivity().getApplication();
|
public P createPresenter() {
|
||||||
app.getComponentReflection().inject(presenter);
|
P presenter = superFactory.createPresenter();
|
||||||
((BasePresenter)presenter).setContext(app.getApplicationContext());
|
App app = (App) BaseRxFragment.this.getActivity().getApplication();
|
||||||
return presenter;
|
app.getComponentReflection().inject(presenter);
|
||||||
|
((BasePresenter) presenter).setContext(app.getApplicationContext());
|
||||||
|
return presenter;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
super.onCreate(bundle);
|
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.
|
* @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) {
|
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,
|
public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory,
|
||||||
final Action1<T> onNext, final Action1<Throwable> onError) {
|
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.
|
* @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) {
|
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())
|
val obs = if (query.isNullOrEmpty())
|
||||||
source.pullPopularMangasFromNetwork(nextMangasPage)
|
source.pullPopularMangasFromNetwork(nextMangasPage)
|
||||||
else
|
else
|
||||||
source.searchMangasFromNetwork(nextMangasPage, query)
|
source.searchMangasFromNetwork(nextMangasPage, query!!)
|
||||||
|
|
||||||
return obs.subscribeOn(Schedulers.io())
|
return obs.subscribeOn(Schedulers.io())
|
||||||
.doOnNext { lastMangasPage = it }
|
.doOnNext { lastMangasPage = it }
|
||||||
|
@ -37,7 +37,7 @@ class CategoryPresenter : BasePresenter<CategoryActivity>() {
|
|||||||
// Get categories as list
|
// Get categories as list
|
||||||
restartableLatestCache(GET_CATEGORIES,
|
restartableLatestCache(GET_CATEGORIES,
|
||||||
{
|
{
|
||||||
db.categories.asRxObservable()
|
db.getCategories().asRxObservable()
|
||||||
.doOnNext { categories -> this.categories = categories }
|
.doOnNext { categories -> this.categories = categories }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}, CategoryActivity::setCategories)
|
}, CategoryActivity::setCategories)
|
||||||
@ -76,7 +76,7 @@ class CategoryPresenter : BasePresenter<CategoryActivity>() {
|
|||||||
*
|
*
|
||||||
* @param categories list of categories
|
* @param categories list of categories
|
||||||
*/
|
*/
|
||||||
fun deleteCategories(categories: List<Category?>?) {
|
fun deleteCategories(categories: List<Category>) {
|
||||||
db.deleteCategories(categories).asRxObservable().subscribe()
|
db.deleteCategories(categories).asRxObservable().subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
* @return an observable of the categories.
|
* @return an observable of the categories.
|
||||||
*/
|
*/
|
||||||
fun getCategoriesObservable(): Observable<List<Category>> {
|
fun getCategoriesObservable(): Observable<List<Category>> {
|
||||||
return db.categories.asRxObservable()
|
return db.getCategories().asRxObservable()
|
||||||
.doOnNext { categories -> this.categories = categories }
|
.doOnNext { categories -> this.categories = categories }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
* value.
|
* value.
|
||||||
*/
|
*/
|
||||||
fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
|
fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
|
||||||
return db.libraryMangas.asRxObservable()
|
return db.getLibraryMangas().asRxObservable()
|
||||||
.flatMap { mangas ->
|
.flatMap { mangas ->
|
||||||
Observable.from(mangas)
|
Observable.from(mangas)
|
||||||
.filter {
|
.filter {
|
||||||
|
@ -10,6 +10,8 @@ import rx.Observable;
|
|||||||
import rx.Observable.Operator;
|
import rx.Observable.Operator;
|
||||||
import rx.Subscriber;
|
import rx.Subscriber;
|
||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
|
import rx.functions.Action0;
|
||||||
|
import rx.functions.Action1;
|
||||||
import rx.functions.Func1;
|
import rx.functions.Func1;
|
||||||
import rx.subscriptions.CompositeSubscription;
|
import rx.subscriptions.CompositeSubscription;
|
||||||
import rx.subscriptions.Subscriptions;
|
import rx.subscriptions.Subscriptions;
|
||||||
@ -58,29 +60,35 @@ public class DynamicConcurrentMergeOperator<T, R> implements Operator<R, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void init(Observable<Integer> workerCount) {
|
public void init(Observable<Integer> workerCount) {
|
||||||
Subscription wc = workerCount.subscribe(n -> {
|
Subscription wc = workerCount.subscribe(new Action1<Integer>() {
|
||||||
int n0 = workers.size();
|
@Override
|
||||||
if (n0 < n) {
|
public void call(Integer n) {
|
||||||
for (int i = n0; i < n; i++) {
|
int n0 = workers.size();
|
||||||
DynamicWorker<T, R> dw = new DynamicWorker<>(++id, this);
|
if (n0 < n) {
|
||||||
workers.add(dw);
|
for (int i = n0; i < n; i++) {
|
||||||
request(1);
|
DynamicWorker<T, R> dw = new DynamicWorker<>(++id, DynamicConcurrentMerge.this);
|
||||||
dw.tryNext();
|
workers.add(dw);
|
||||||
}
|
DynamicConcurrentMerge.this.request(1);
|
||||||
} else if (n0 > n) {
|
dw.tryNext();
|
||||||
for (int i = 0; i < n; i++) {
|
}
|
||||||
workers.get(i).start();
|
} 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--) {
|
if (!once.get() && once.compareAndSet(false, true)) {
|
||||||
workers.get(i).stop();
|
DynamicConcurrentMerge.this.request(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, new Action1<Throwable>() {
|
||||||
if (!once.get() && once.compareAndSet(false, true)) {
|
@Override
|
||||||
request(n);
|
public void call(Throwable e) {DynamicConcurrentMerge.this.onError(e);}
|
||||||
}
|
});
|
||||||
}, this::onError);
|
|
||||||
|
|
||||||
composite.add(wc);
|
composite.add(wc);
|
||||||
}
|
}
|
||||||
@ -138,9 +146,9 @@ public class DynamicConcurrentMergeOperator<T, R> implements Operator<R, T> {
|
|||||||
return;
|
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
|
@Override
|
||||||
public void onNext(R t) {
|
public void onNext(R t) {
|
||||||
parent.actual.onNext(t);
|
parent.actual.onNext(t);
|
||||||
@ -163,9 +171,11 @@ public class DynamicConcurrentMergeOperator<T, R> implements Operator<R, T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
parent.composite.add(s);
|
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);
|
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 {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
|
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
|
||||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
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'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
Loading…
Reference in New Issue
Block a user