Minor improvements for sync services

This commit is contained in:
len 2016-05-31 16:08:46 +02:00
parent 02a697031f
commit 18cdddf433
12 changed files with 156 additions and 162 deletions

View File

@ -6,7 +6,7 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
import java.io.Serializable; import java.io.Serializable;
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable; import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService; import eu.kanade.tachiyomi.data.mangasync.MangaSyncService;
@StorIOSQLiteType(table = MangaSyncTable.TABLE) @StorIOSQLiteType(table = MangaSyncTable.TABLE)
public class MangaSync implements Serializable { public class MangaSync implements Serializable {

View File

@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaSync import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
interface MangaSyncQueries : DbProvider { interface MangaSyncQueries : DbProvider {

View File

@ -1,23 +1,18 @@
package eu.kanade.tachiyomi.data.mangasync package eu.kanade.tachiyomi.data.mangasync
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService import eu.kanade.tachiyomi.data.mangasync.myanimelist.MyAnimeList
import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList
class MangaSyncManager(private val context: Context) { class MangaSyncManager(private val context: Context) {
val services: List<MangaSyncService>
val myAnimeList: MyAnimeList
companion object { companion object {
const val MYANIMELIST = 1 const val MYANIMELIST = 1
} }
init { val myAnimeList = MyAnimeList(context, MYANIMELIST)
myAnimeList = MyAnimeList(context, MYANIMELIST)
services = listOf(myAnimeList)
}
fun getService(id: Int): MangaSyncService = services.find { it.id == id }!! val services = listOf(myAnimeList)
fun getService(id: Int) = services.find { it.id == id }
} }

View File

@ -0,0 +1,56 @@
package eu.kanade.tachiyomi.data.mangasync
import android.content.Context
import android.support.annotation.CallSuper
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.data.network.NetworkHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import okhttp3.OkHttpClient
import rx.Completable
import rx.Observable
import javax.inject.Inject
abstract class MangaSyncService(private val context: Context, val id: Int) {
@Inject lateinit var preferences: PreferencesHelper
@Inject lateinit var networkService: NetworkHelper
init {
App.get(context).component.inject(this)
}
open val client: OkHttpClient
get() = networkService.client
// Name of the manga sync service to display
abstract val name: String
abstract fun login(username: String, password: String): Completable
open val isLogged: Boolean
get() = !getUsername().isEmpty() &&
!getPassword().isEmpty()
abstract fun add(manga: MangaSync): Observable<MangaSync>
abstract fun update(manga: MangaSync): Observable<MangaSync>
abstract fun bind(manga: MangaSync): Observable<MangaSync>
abstract fun getStatus(status: Int): String
fun saveCredentials(username: String, password: String) {
preferences.setMangaSyncCredentials(this, username, password)
}
@CallSuper
open fun logout() {
preferences.setMangaSyncCredentials(this, "", "")
}
fun getUsername() = preferences.mangaSyncUsername(this)
fun getPassword() = preferences.mangaSyncPassword(this)
}

View File

@ -48,15 +48,13 @@ class UpdateMangaSyncService : Service() {
private fun updateLastChapterRead(mangaSync: MangaSync, startId: Int) { private fun updateLastChapterRead(mangaSync: MangaSync, startId: Int) {
val sync = syncManager.getService(mangaSync.sync_id) val sync = syncManager.getService(mangaSync.sync_id)
if (sync == null) {
stopSelf(startId)
return
}
subscriptions.add(Observable.defer { sync.update(mangaSync) } subscriptions.add(Observable.defer { sync.update(mangaSync) }
.flatMap { .flatMap { db.insertMangaSync(mangaSync).asRxObservable() }
if (it.isSuccessful) {
db.insertMangaSync(mangaSync).asRxObservable()
} else {
Observable.error(Exception("Could not update manga in remote service"))
}
}
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ stopSelf(startId) }, .subscribe({ stopSelf(startId) },

View File

@ -1,42 +0,0 @@
package eu.kanade.tachiyomi.data.mangasync.base
import android.content.Context
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.data.network.NetworkHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import okhttp3.OkHttpClient
import okhttp3.Response
import rx.Observable
import javax.inject.Inject
abstract class MangaSyncService(private val context: Context, val id: Int) {
@Inject lateinit var preferences: PreferencesHelper
@Inject lateinit var networkService: NetworkHelper
init {
App.get(context).component.inject(this)
}
open val client: OkHttpClient
get() = networkService.client
// Name of the manga sync service to display
abstract val name: String
abstract fun login(username: String, password: String): Observable<Boolean>
open val isLogged: Boolean
get() = !preferences.mangaSyncUsername(this).isEmpty() &&
!preferences.mangaSyncPassword(this).isEmpty()
abstract fun update(manga: MangaSync): Observable<Response>
abstract fun add(manga: MangaSync): Observable<Response>
abstract fun bind(manga: MangaSync): Observable<Response>
abstract fun getStatus(status: Int): String
}

View File

@ -1,26 +1,29 @@
package eu.kanade.tachiyomi.data.mangasync.services package eu.kanade.tachiyomi.data.mangasync.myanimelist
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Xml import android.util.Xml
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaSync import eu.kanade.tachiyomi.data.database.models.MangaSync
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.data.network.GET
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.data.network.POST
import eu.kanade.tachiyomi.data.network.asObservable import eu.kanade.tachiyomi.data.network.asObservable
import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText import eu.kanade.tachiyomi.util.selectText
import okhttp3.* import okhttp3.Credentials
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.RequestBody
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.xmlpull.v1.XmlSerializer import org.xmlpull.v1.XmlSerializer
import rx.Completable
import rx.Observable import rx.Observable
import java.io.StringWriter import java.io.StringWriter
class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(context, id) { class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(context, id) {
private lateinit var headers: Headers private lateinit var headers: Headers
private lateinit var username: String
companion object { companion object {
val BASE_URL = "http://myanimelist.net" val BASE_URL = "http://myanimelist.net"
@ -41,8 +44,8 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
} }
init { init {
val username = preferences.mangaSyncUsername(this) val username = getUsername()
val password = preferences.mangaSyncPassword(this) val password = getPassword()
if (!username.isEmpty() && !password.isEmpty()) { if (!username.isEmpty() && !password.isEmpty()) {
createHeaders(username, password) createHeaders(username, password)
@ -52,25 +55,39 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
override val name: String override val name: String
get() = "MyAnimeList" get() = "MyAnimeList"
fun getLoginUrl(): String { fun getLoginUrl() = Uri.parse(BASE_URL).buildUpon()
return Uri.parse(BASE_URL).buildUpon() .appendEncodedPath("api/account/verify_credentials.xml")
.appendEncodedPath("api/account/verify_credentials.xml") .toString()
.toString()
}
override fun login(username: String, password: String): Observable<Boolean> { fun getSearchUrl(query: String) = Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/manga/search.xml")
.appendQueryParameter("q", query)
.toString()
fun getListUrl(username: String) = Uri.parse(BASE_URL).buildUpon()
.appendPath("malappinfo.php")
.appendQueryParameter("u", username)
.appendQueryParameter("status", "all")
.appendQueryParameter("type", "manga")
.toString()
fun getUpdateUrl(manga: MangaSync) = Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/mangalist/update")
.appendPath("${manga.remote_id}.xml")
.toString()
fun getAddUrl(manga: MangaSync) = Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/mangalist/add")
.appendPath("${manga.remote_id}.xml")
.toString()
override fun login(username: String, password: String): Completable {
createHeaders(username, password) createHeaders(username, password)
return client.newCall(GET(getLoginUrl(), headers)) return client.newCall(GET(getLoginUrl(), headers))
.asObservable() .asObservable()
.doOnNext { it.close() } .doOnNext { it.close() }
.map { it.code() == 200 } .doOnNext { if (it.code() != 200) throw Exception("Login error") }
} .toCompletable()
fun getSearchUrl(query: String): String {
return Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/manga/search.xml")
.appendQueryParameter("q", query)
.toString()
} }
fun search(query: String): Observable<List<MangaSync>> { fun search(query: String): Observable<List<MangaSync>> {
@ -80,73 +97,56 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
.flatMap { Observable.from(it.select("entry")) } .flatMap { Observable.from(it.select("entry")) }
.filter { it.select("type").text() != "Novel" } .filter { it.select("type").text() != "Novel" }
.map { .map {
val manga = MangaSync.create(this) MangaSync.create(this).apply {
manga.title = it.selectText("title") title = it.selectText("title")
manga.remote_id = it.selectInt("id") remote_id = it.selectInt("id")
manga.total_chapters = it.selectInt("chapters") total_chapters = it.selectInt("chapters")
manga }
} }
.toList() .toList()
} }
fun getListUrl(username: String): String {
return Uri.parse(BASE_URL).buildUpon()
.appendPath("malappinfo.php")
.appendQueryParameter("u", username)
.appendQueryParameter("status", "all")
.appendQueryParameter("type", "manga")
.toString()
}
// MAL doesn't support score with decimals // MAL doesn't support score with decimals
fun getList(): Observable<List<MangaSync>> { fun getList(): Observable<List<MangaSync>> {
return networkService.forceCacheClient return networkService.forceCacheClient
.newCall(GET(getListUrl(username), headers)) .newCall(GET(getListUrl(getUsername()), headers))
.asObservable() .asObservable()
.map { Jsoup.parse(it.body().string()) } .map { Jsoup.parse(it.body().string()) }
.flatMap { Observable.from(it.select("manga")) } .flatMap { Observable.from(it.select("manga")) }
.map { .map {
val manga = MangaSync.create(this) MangaSync.create(this).apply {
manga.title = it.selectText("series_title") title = it.selectText("series_title")
manga.remote_id = it.selectInt("series_mangadb_id") remote_id = it.selectInt("series_mangadb_id")
manga.last_chapter_read = it.selectInt("my_read_chapters") last_chapter_read = it.selectInt("my_read_chapters")
manga.status = it.selectInt("my_status") status = it.selectInt("my_status")
manga.score = it.selectInt("my_score").toFloat() score = it.selectInt("my_score").toFloat()
manga.total_chapters = it.selectInt("series_chapters") total_chapters = it.selectInt("series_chapters")
manga }
} }
.toList() .toList()
} }
fun getUpdateUrl(manga: MangaSync): String { override fun update(manga: MangaSync): Observable<MangaSync> {
return Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/mangalist/update")
.appendPath(manga.remote_id.toString() + ".xml")
.toString()
}
override fun update(manga: MangaSync): Observable<Response> {
return Observable.defer { return Observable.defer {
if (manga.total_chapters != 0 && manga.last_chapter_read == manga.total_chapters) { if (manga.total_chapters != 0 && manga.last_chapter_read == manga.total_chapters) {
manga.status = COMPLETED manga.status = COMPLETED
} }
client.newCall(POST(getUpdateUrl(manga), headers, getMangaPostPayload(manga))) client.newCall(POST(getUpdateUrl(manga), headers, getMangaPostPayload(manga)))
.asObservable() .asObservable()
.doOnNext { it.close() }
.doOnNext { if (!it.isSuccessful) throw Exception("Could not update manga") }
.map { manga }
} }
} }
fun getAddUrl(manga: MangaSync): String { override fun add(manga: MangaSync): Observable<MangaSync> {
return Uri.parse(BASE_URL).buildUpon()
.appendEncodedPath("api/mangalist/add")
.appendPath(manga.remote_id.toString() + ".xml")
.toString()
}
override fun add(manga: MangaSync): Observable<Response> {
return Observable.defer { return Observable.defer {
client.newCall(POST(getAddUrl(manga), headers, getMangaPostPayload(manga))) client.newCall(POST(getAddUrl(manga), headers, getMangaPostPayload(manga)))
.asObservable() .asObservable()
.doOnNext { it.close() }
.doOnNext { if (!it.isSuccessful) throw Exception("Could not add manga") }
.map { manga }
} }
} }
@ -184,21 +184,20 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
endTag(namespace, tag) endTag(namespace, tag)
} }
override fun bind(manga: MangaSync): Observable<Response> { override fun bind(manga: MangaSync): Observable<MangaSync> {
return getList() return getList()
.flatMap { .flatMap { userlist ->
manga.sync_id = id manga.sync_id = id
for (remoteManga in it) { val mangaFromList = userlist.find { it.remote_id == manga.remote_id }
if (remoteManga.remote_id == manga.remote_id) { if (mangaFromList != null) {
// Manga is already in the list manga.copyPersonalFrom(mangaFromList)
manga.copyPersonalFrom(remoteManga) update(manga)
return@flatMap update(manga) } else {
} // Set default fields if it's not found in the list
manga.score = DEFAULT_SCORE.toFloat()
manga.status = DEFAULT_STATUS
add(manga)
} }
// Set default fields if it's not found in the list
manga.score = DEFAULT_SCORE.toFloat()
manga.status = DEFAULT_STATUS
return@flatMap add(manga)
} }
} }
@ -214,7 +213,6 @@ class MyAnimeList(private val context: Context, id: Int) : MangaSyncService(cont
} }
fun createHeaders(username: String, password: String) { fun createHeaders(username: String, password: String) {
this.username = username
val builder = Headers.Builder() val builder = Headers.Builder()
builder.add("Authorization", Credentials.basic(username, password)) builder.add("Authorization", Credentials.basic(username, password))
builder.add("User-Agent", "api-indiv-9F93C52A963974CF674325391990191C") builder.add("User-Agent", "api-indiv-9F93C52A963974CF674325391990191C")

View File

@ -6,7 +6,7 @@ import android.preference.PreferenceManager
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.f2prateek.rx.preferences.RxSharedPreferences import com.f2prateek.rx.preferences.RxSharedPreferences
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.Source
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException

View File

@ -6,8 +6,8 @@ import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.glide.AppGlideModule import eu.kanade.tachiyomi.data.glide.AppGlideModule
import eu.kanade.tachiyomi.data.glide.MangaModelLoader import eu.kanade.tachiyomi.data.glide.MangaModelLoader
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService import eu.kanade.tachiyomi.data.mangasync.UpdateMangaSyncService
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.data.updater.UpdateDownloader import eu.kanade.tachiyomi.data.updater.UpdateDownloader

View File

@ -98,7 +98,7 @@ class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
mangaSync?.let { mangaSync -> mangaSync?.let { mangaSync ->
add(myAnimeList.update(mangaSync) add(myAnimeList.update(mangaSync)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.flatMap { response -> db.insertMangaSync(mangaSync).asRxObservable() } .flatMap { db.insertMangaSync(mangaSync).asRxObservable() }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ next -> }, .subscribe({ next -> },
{ error -> { error ->
@ -126,13 +126,7 @@ class MyAnimeListPresenter : BasePresenter<MyAnimeListFragment>() {
if (sync != null) { if (sync != null) {
sync.manga_id = manga.id sync.manga_id = manga.id
add(myAnimeList.bind(sync) add(myAnimeList.bind(sync)
.flatMap { response -> .flatMap { db.insertMangaSync(sync).asRxObservable() }
if (response.isSuccessful) {
db.insertMangaSync(sync).asRxObservable()
} else {
Observable.error(Exception("Could not bind manga"))
}
}
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ }, .subscribe({ },

View File

@ -374,7 +374,7 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
fun updateMangaSyncLastChapterRead() { fun updateMangaSyncLastChapterRead() {
for (mangaSync in mangaSyncList ?: emptyList()) { for (mangaSync in mangaSyncList ?: emptyList()) {
val service = syncManager.getService(mangaSync.sync_id) val service = syncManager.getService(mangaSync.sync_id) ?: continue
if (service.isLogged && mangaSync.update) { if (service.isLogged && mangaSync.update) {
UpdateMangaSyncService.start(context, mangaSync) UpdateMangaSyncService.start(context, mangaSync)
} }

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.widget.preference
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService import eu.kanade.tachiyomi.data.mangasync.MangaSyncService
import eu.kanade.tachiyomi.ui.setting.SettingsActivity import eu.kanade.tachiyomi.ui.setting.SettingsActivity
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.pref_account_login.view.* import kotlinx.android.synthetic.main.pref_account_login.view.*
@ -29,13 +29,13 @@ class MangaSyncLoginDialog : LoginDialogPreference() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val syncId = arguments.getInt("key") val syncId = arguments.getInt("key")
sync = (activity as SettingsActivity).syncManager.getService(syncId) sync = (activity as SettingsActivity).syncManager.getService(syncId)!!
} }
override fun setCredentialsOnView(view: View) = with(view) { override fun setCredentialsOnView(view: View) = with(view) {
dialog_title.text = getString(R.string.login_title, sync.name) dialog_title.text = getString(R.string.login_title, sync.name)
username.setText(preferences.mangaSyncUsername(sync)) username.setText(sync.getUsername())
password.setText(preferences.mangaSyncPassword(sync)) password.setText(sync.getPassword())
} }
override fun checkLogin() { override fun checkLogin() {
@ -46,25 +46,20 @@ class MangaSyncLoginDialog : LoginDialogPreference() {
return return
login.progress = 1 login.progress = 1
val user = username.text.toString()
val pass = password.text.toString()
requestSubscription = sync.login(username.text.toString(), password.text.toString()) requestSubscription = sync.login(user, pass)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ logged -> .subscribe({ error ->
if (logged) { sync.logout()
preferences.setMangaSyncCredentials(sync,
username.text.toString(),
password.text.toString())
dialog.dismiss()
context.toast(R.string.login_success)
} else {
preferences.setMangaSyncCredentials(sync, "", "")
login.progress = -1
}
}, { error ->
login.progress = -1 login.progress = -1
login.setText(R.string.unknown_error) login.setText(R.string.unknown_error)
}, {
sync.saveCredentials(user, pass)
dialog.dismiss()
context.toast(R.string.login_success)
}) })
} }