mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-14 07:15:08 +01:00
More tracker clean up
This commit is contained in:
parent
f83a6bd489
commit
7bc12c04c4
@ -181,7 +181,7 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private suspend fun restoreBackup(uri: Uri) {
|
private suspend fun restoreBackup(uri: Uri) {
|
||||||
val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
|
val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
val json = JsonParser().parse(reader).asJsonObject
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
|
||||||
// Get parser version
|
// Get parser version
|
||||||
val version = json.get(VERSION)?.asInt ?: 1
|
val version = json.get(VERSION)?.asInt ?: 1
|
||||||
@ -296,16 +296,16 @@ class BackupRestoreService : Service() {
|
|||||||
* @param manga manga that needs updating.
|
* @param manga manga that needs updating.
|
||||||
* @param tracks list containing tracks from restore file.
|
* @param tracks list containing tracks from restore file.
|
||||||
*/
|
*/
|
||||||
private fun trackingFetch(manga: Manga, tracks: List<Track>) {
|
private suspend fun trackingFetch(manga: Manga, tracks: List<Track>) {
|
||||||
tracks.forEach { track ->
|
tracks.forEach { track ->
|
||||||
val service = trackManager.getService(track.sync_id)
|
val service = trackManager.getService(track.sync_id)
|
||||||
if (service != null && service.isLogged) {
|
if (service != null && service.isLogged) {
|
||||||
service.refresh(track)
|
try {
|
||||||
.doOnNext { db.insertTrack(it).executeAsBlocking() }
|
service.refresh(track)
|
||||||
.onErrorReturn {
|
db.insertTrack(track).executeAsBlocking()
|
||||||
errors.add("${manga.title} - ${it.message}")
|
}catch (e : Exception){
|
||||||
track
|
errors.add("${manga.title} - ${e.message}")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add("${manga.title} - ${service?.name} not logged in")
|
errors.add("${manga.title} - ${service?.name} not logged in")
|
||||||
val notLoggedIn = getString(R.string.not_logged_into, service?.name)
|
val notLoggedIn = getString(R.string.not_logged_into, service?.name)
|
||||||
|
@ -64,11 +64,11 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||||||
* destroyed.
|
* destroyed.
|
||||||
*/
|
*/
|
||||||
class LibraryUpdateService(
|
class LibraryUpdateService(
|
||||||
val db: DatabaseHelper = Injekt.get(),
|
val db: DatabaseHelper = Injekt.get(),
|
||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val preferences: PreferencesHelper = Injekt.get(),
|
val preferences: PreferencesHelper = Injekt.get(),
|
||||||
val downloadManager: DownloadManager = Injekt.get(),
|
val downloadManager: DownloadManager = Injekt.get(),
|
||||||
val trackManager: TrackManager = Injekt.get()
|
val trackManager: TrackManager = Injekt.get()
|
||||||
) : Service() {
|
) : Service() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,7 +81,6 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
private var subscription: Subscription? = null
|
private var subscription: Subscription? = null
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pending intent of action that cancels the library update
|
* Pending intent of action that cancels the library update
|
||||||
*/
|
*/
|
||||||
@ -96,7 +95,7 @@ class LibraryUpdateService(
|
|||||||
BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var job:Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
private val mangaToUpdate = mutableListOf<LibraryManga>()
|
private val mangaToUpdate = mutableListOf<LibraryManga>()
|
||||||
|
|
||||||
@ -108,14 +107,19 @@ class LibraryUpdateService(
|
|||||||
/**
|
/**
|
||||||
* Cached progress notification to avoid creating a lot.
|
* Cached progress notification to avoid creating a lot.
|
||||||
*/
|
*/
|
||||||
private val progressNotification by lazy { NotificationCompat.Builder(this, Notifications.CHANNEL_LIBRARY)
|
private val progressNotification by lazy {
|
||||||
|
NotificationCompat.Builder(this, Notifications.CHANNEL_LIBRARY)
|
||||||
.setContentTitle(getString(R.string.app_name))
|
.setContentTitle(getString(R.string.app_name))
|
||||||
.setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
.setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
|
||||||
.setLargeIcon(notificationBitmap)
|
.setLargeIcon(notificationBitmap)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
||||||
.addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
|
.addAction(
|
||||||
|
R.drawable.ic_clear_grey_24dp_img,
|
||||||
|
getString(android.R.string.cancel),
|
||||||
|
cancelIntent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,8 +176,7 @@ class LibraryUpdateService(
|
|||||||
} else {
|
} else {
|
||||||
context.startForegroundService(intent)
|
context.startForegroundService(intent)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (target == Target.CHAPTERS) category?.id?.let {
|
if (target == Target.CHAPTERS) category?.id?.let {
|
||||||
instance?.addCategory(it)
|
instance?.addCategory(it)
|
||||||
}
|
}
|
||||||
@ -190,7 +193,7 @@ class LibraryUpdateService(
|
|||||||
context.stopService(Intent(context, LibraryUpdateService::class.java))
|
context.stopService(Intent(context, LibraryUpdateService::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var listener:LibraryServiceListener? = null
|
private var listener: LibraryServiceListener? = null
|
||||||
|
|
||||||
fun setListener(listener: LibraryServiceListener) {
|
fun setListener(listener: LibraryServiceListener) {
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
@ -212,7 +215,8 @@ class LibraryUpdateService(
|
|||||||
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
|
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
|
||||||
val mangas =
|
val mangas =
|
||||||
getMangaToUpdate(categoryId, Target.CHAPTERS).sortedWith(
|
getMangaToUpdate(categoryId, Target.CHAPTERS).sortedWith(
|
||||||
rankingScheme[selectedScheme])
|
rankingScheme[selectedScheme]
|
||||||
|
)
|
||||||
categoryIds.add(categoryId)
|
categoryIds.add(categoryId)
|
||||||
addManga(mangas)
|
addManga(mangas)
|
||||||
}
|
}
|
||||||
@ -228,9 +232,9 @@ class LibraryUpdateService(
|
|||||||
var listToUpdate = if (categoryId != -1) {
|
var listToUpdate = if (categoryId != -1) {
|
||||||
categoryIds.add(categoryId)
|
categoryIds.add(categoryId)
|
||||||
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
|
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
|
||||||
}
|
} else {
|
||||||
else {
|
val categoriesToUpdate =
|
||||||
val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map(String::toInt)
|
preferences.libraryUpdateCategories().getOrDefault().map(String::toInt)
|
||||||
categoryIds.addAll(categoriesToUpdate)
|
categoryIds.addAll(categoriesToUpdate)
|
||||||
if (categoriesToUpdate.isNotEmpty())
|
if (categoriesToUpdate.isNotEmpty())
|
||||||
db.getLibraryMangas().executeAsBlocking()
|
db.getLibraryMangas().executeAsBlocking()
|
||||||
@ -259,7 +263,8 @@ class LibraryUpdateService(
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotification.build())
|
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotification.build())
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock")
|
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
|
||||||
|
)
|
||||||
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
|
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +302,7 @@ class LibraryUpdateService(
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent == null) return START_NOT_STICKY
|
if (intent == null) return START_NOT_STICKY
|
||||||
val target = intent.getSerializableExtra(KEY_TARGET) as? Target
|
val target = intent.getSerializableExtra(KEY_TARGET) as? Target
|
||||||
?: return START_NOT_STICKY
|
?: return START_NOT_STICKY
|
||||||
|
|
||||||
// Unsubscribe from any previous subscription if needed.
|
// Unsubscribe from any previous subscription if needed.
|
||||||
subscription?.unsubscribe()
|
subscription?.unsubscribe()
|
||||||
@ -307,41 +312,44 @@ class LibraryUpdateService(
|
|||||||
val mangaList =
|
val mangaList =
|
||||||
getMangaToUpdate(intent, target).sortedWith(rankingScheme[selectedScheme])
|
getMangaToUpdate(intent, target).sortedWith(rankingScheme[selectedScheme])
|
||||||
// Update favorite manga. Destroy service when completed or in case of an error.
|
// Update favorite manga. Destroy service when completed or in case of an error.
|
||||||
if (target == Target.CHAPTERS) {
|
if (target == Target.DETAILS) {
|
||||||
updateChapters(mangaList, startId)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Update either chapter list or manga details.
|
// Update either chapter list or manga details.
|
||||||
subscription = Observable.defer {
|
subscription = Observable.defer {
|
||||||
when (target) {
|
updateDetails(mangaList)
|
||||||
Target.DETAILS -> updateDetails(mangaList)
|
|
||||||
else -> updateTrackings(mangaList)
|
|
||||||
}
|
|
||||||
}.subscribeOn(Schedulers.io()).subscribe({}, {
|
}.subscribeOn(Schedulers.io()).subscribe({}, {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
}, {
|
}, {
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
launchTarget(target, mangaList, startId)
|
||||||
}
|
}
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateChapters(mangaToAdd: List<LibraryManga>, startId: Int) {
|
private fun launchTarget(target: Target, mangaToAdd: List<LibraryManga>, startId: Int) {
|
||||||
val handler = CoroutineExceptionHandler { _, exception ->
|
val handler = CoroutineExceptionHandler { _, exception ->
|
||||||
Timber.e(exception)
|
Timber.e(exception)
|
||||||
// Boolean to determine if user wants to automatically download new chapters.
|
|
||||||
stopSelf(startId)
|
stopSelf(startId)
|
||||||
}
|
}
|
||||||
job = GlobalScope.launch(handler) {
|
if (target == Target.CHAPTERS) {
|
||||||
updateChaptersJob(mangaToAdd)
|
job = GlobalScope.launch(handler) {
|
||||||
|
updateChaptersJob(mangaToAdd)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
job = GlobalScope.launch(handler) {
|
||||||
|
updateTrackings(mangaToAdd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
job?.invokeOnCompletion { stopSelf(startId) }
|
job?.invokeOnCompletion { stopSelf(startId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateChaptersJob(mangaToAdd: List<LibraryManga>) {
|
private suspend fun updateChaptersJob(mangaToAdd: List<LibraryManga>) {
|
||||||
// List containing categories that get included in downloads.
|
// List containing categories that get included in downloads.
|
||||||
val categoriesToDownload = preferences.downloadNewCategories().getOrDefault().map(String::toInt)
|
val categoriesToDownload =
|
||||||
|
preferences.downloadNewCategories().getOrDefault().map(String::toInt)
|
||||||
// Boolean to determine if user wants to automatically download new chapters.
|
// Boolean to determine if user wants to automatically download new chapters.
|
||||||
val downloadNew = preferences.downloadNew().getOrDefault()
|
val downloadNew = preferences.downloadNew().getOrDefault()
|
||||||
// Boolean to determine if DownloadManager has downloads
|
// Boolean to determine if DownloadManager has downloads
|
||||||
@ -352,7 +360,7 @@ class LibraryUpdateService(
|
|||||||
mangaToUpdate.addAll(mangaToAdd)
|
mangaToUpdate.addAll(mangaToAdd)
|
||||||
while (count < mangaToUpdate.size) {
|
while (count < mangaToUpdate.size) {
|
||||||
val shouldDownload = (downloadNew && (categoriesToDownload.isEmpty() ||
|
val shouldDownload = (downloadNew && (categoriesToDownload.isEmpty() ||
|
||||||
mangaToUpdate[count].category in categoriesToDownload))
|
mangaToUpdate[count].category in categoriesToDownload))
|
||||||
if (updateMangaChapters(mangaToUpdate[count], count, shouldDownload)) {
|
if (updateMangaChapters(mangaToUpdate[count], count, shouldDownload)) {
|
||||||
hasDownloads = true
|
hasDownloads = true
|
||||||
}
|
}
|
||||||
@ -370,8 +378,7 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io()).subscribe {}
|
.subscribeOn(Schedulers.io()).subscribe {}
|
||||||
}
|
} else if (downloadNew && hasDownloads) {
|
||||||
else if (downloadNew && hasDownloads) {
|
|
||||||
DownloadService.start(this)
|
DownloadService.start(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,8 +386,12 @@ class LibraryUpdateService(
|
|||||||
cancelProgressNotification()
|
cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateMangaChapters(manga: LibraryManga, progess: Int, shouldDownload: Boolean):
|
private suspend fun updateMangaChapters(
|
||||||
Boolean {
|
manga: LibraryManga,
|
||||||
|
progess: Int,
|
||||||
|
shouldDownload: Boolean
|
||||||
|
):
|
||||||
|
Boolean {
|
||||||
try {
|
try {
|
||||||
var hasDownloads = false
|
var hasDownloads = false
|
||||||
if (job?.isCancelled == true) {
|
if (job?.isCancelled == true) {
|
||||||
@ -389,7 +400,7 @@ class LibraryUpdateService(
|
|||||||
showProgressNotification(manga, progess, mangaToUpdate.size)
|
showProgressNotification(manga, progess, mangaToUpdate.size)
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
|
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
|
||||||
val fetchedChapters = withContext(Dispatchers.IO) {
|
val fetchedChapters = withContext(Dispatchers.IO) {
|
||||||
source.fetchChapterList(manga).toBlocking().single()
|
source.fetchChapterList(manga).toBlocking().single()
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
if (fetchedChapters.isNotEmpty()) {
|
if (fetchedChapters.isNotEmpty()) {
|
||||||
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
|
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
|
||||||
@ -406,8 +417,7 @@ class LibraryUpdateService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return hasDownloads
|
return hasDownloads
|
||||||
}
|
} catch (e: Exception) {
|
||||||
catch (e: Exception) {
|
|
||||||
Timber.e("Failed updating: ${manga.title}: $e")
|
Timber.e("Failed updating: ${manga.title}: $e")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -433,7 +443,7 @@ class LibraryUpdateService(
|
|||||||
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return Observable.empty()
|
val source = sourceManager.get(manga.source) as? HttpSource ?: return Observable.empty()
|
||||||
return source.fetchChapterList(manga)
|
return source.fetchChapterList(manga)
|
||||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -449,62 +459,57 @@ class LibraryUpdateService(
|
|||||||
|
|
||||||
// Emit each manga and update it sequentially.
|
// Emit each manga and update it sequentially.
|
||||||
return Observable.from(mangaToUpdate)
|
return Observable.from(mangaToUpdate)
|
||||||
// Notify manga that will update.
|
// Notify manga that will update.
|
||||||
.doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
.doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
||||||
// Update the details of the manga.
|
// Update the details of the manga.
|
||||||
.concatMap { manga ->
|
.concatMap { manga ->
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
?: return@concatMap Observable.empty<LibraryManga>()
|
?: return@concatMap Observable.empty<LibraryManga>()
|
||||||
source.fetchMangaDetails(manga)
|
source.fetchMangaDetails(manga)
|
||||||
.map { networkManga ->
|
.map { networkManga ->
|
||||||
val thumbnailUrl = manga.thumbnail_url
|
val thumbnailUrl = manga.thumbnail_url
|
||||||
manga.copyFrom(networkManga)
|
manga.copyFrom(networkManga)
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
if (thumbnailUrl != networkManga.thumbnail_url)
|
if (thumbnailUrl != networkManga.thumbnail_url)
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||||
manga
|
manga
|
||||||
}
|
}
|
||||||
.onErrorReturn { manga }
|
.onErrorReturn { manga }
|
||||||
}
|
}
|
||||||
.doOnCompleted {
|
.doOnCompleted {
|
||||||
cancelProgressNotification()
|
cancelProgressNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that updates the metadata of the connected tracking services. It's called in a
|
* Method that updates the metadata of the connected tracking services. It's called in a
|
||||||
* background thread, so it's safe to do heavy operations or network calls here.
|
* background thread, so it's safe to do heavy operations or network calls here.
|
||||||
*/
|
*/
|
||||||
private fun updateTrackings(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
|
|
||||||
|
private suspend fun updateTrackings(mangaToUpdate: List<LibraryManga>) {
|
||||||
// Initialize the variables holding the progress of the updates.
|
// Initialize the variables holding the progress of the updates.
|
||||||
var count = 0
|
var count = 0
|
||||||
|
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||||
|
|
||||||
// Emit each manga and update it sequentially.
|
mangaToUpdate.forEach { manga ->
|
||||||
return Observable.from(mangaToUpdate)
|
showProgressNotification(manga, count++, mangaToUpdate.size)
|
||||||
// Notify manga that will update.
|
|
||||||
.doOnNext { showProgressNotification(it, count++, mangaToUpdate.size) }
|
|
||||||
// Update the tracking details.
|
|
||||||
.concatMap { manga ->
|
|
||||||
val tracks = db.getTracks(manga).executeAsBlocking()
|
|
||||||
|
|
||||||
Observable.from(tracks)
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
.concatMap { track ->
|
|
||||||
val service = trackManager.getService(track.sync_id)
|
tracks.forEach { track ->
|
||||||
if (service != null && service in loggedServices) {
|
val service = trackManager.getService(track.sync_id)
|
||||||
service.refresh(track)
|
if (service != null && service in loggedServices) {
|
||||||
.doOnNext { db.insertTrack(it).executeAsBlocking() }
|
try {
|
||||||
.onErrorReturn { track }
|
service.refresh(track)
|
||||||
} else {
|
db.insertTrack(track).executeAsBlocking()
|
||||||
Observable.empty()
|
} catch (e: Exception) {
|
||||||
}
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
.map { manga }
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
cancelProgressNotification()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -515,10 +520,12 @@ class LibraryUpdateService(
|
|||||||
* @param total the total progress.
|
* @param total the total progress.
|
||||||
*/
|
*/
|
||||||
private fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
private fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
||||||
notificationManager.notify(Notifications.ID_LIBRARY_PROGRESS, progressNotification
|
notificationManager.notify(
|
||||||
|
Notifications.ID_LIBRARY_PROGRESS, progressNotification
|
||||||
.setContentTitle(manga.currentTitle())
|
.setContentTitle(manga.currentTitle())
|
||||||
.setProgress(total, current, false)
|
.setProgress(total, current, false)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -539,15 +546,17 @@ class LibraryUpdateService(
|
|||||||
.asBitmap().load(manga).dontTransform().centerCrop().circleCrop()
|
.asBitmap().load(manga).dontTransform().centerCrop().circleCrop()
|
||||||
.override(256, 256).submit().get()
|
.override(256, 256).submit().get()
|
||||||
setLargeIcon(icon)
|
setLargeIcon(icon)
|
||||||
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
catch (e: Exception) { }
|
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
||||||
setContentTitle(manga.currentTitle())
|
setContentTitle(manga.currentTitle())
|
||||||
color = ContextCompat.getColor(this@LibraryUpdateService, R.color.colorAccent)
|
color = ContextCompat.getColor(this@LibraryUpdateService, R.color.colorAccent)
|
||||||
val chaptersNames = if (chapterNames.size > 5) {
|
val chaptersNames = if (chapterNames.size > 5) {
|
||||||
"${chapterNames.take(4).joinToString(", ")}, " +
|
"${chapterNames.take(4).joinToString(", ")}, " +
|
||||||
resources.getQuantityString(R.plurals.notification_and_n_more,
|
resources.getQuantityString(
|
||||||
(chapterNames.size - 4), (chapterNames.size - 4))
|
R.plurals.notification_and_n_more,
|
||||||
|
(chapterNames.size - 4), (chapterNames.size - 4)
|
||||||
|
)
|
||||||
} else chapterNames.joinToString(", ")
|
} else chapterNames.joinToString(", ")
|
||||||
setContentText(chaptersNames)
|
setContentText(chaptersNames)
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
|
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
|
||||||
@ -558,41 +567,57 @@ class LibraryUpdateService(
|
|||||||
this@LibraryUpdateService, manga, chapters.first()
|
this@LibraryUpdateService, manga, chapters.first()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
addAction(R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
|
addAction(
|
||||||
NotificationReceiver.markAsReadPendingBroadcast(this@LibraryUpdateService,
|
R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
|
||||||
manga, chapters, Notifications.ID_NEW_CHAPTERS))
|
NotificationReceiver.markAsReadPendingBroadcast(
|
||||||
addAction(R.drawable.ic_book_white_24dp, getString(R.string.action_view_chapters),
|
this@LibraryUpdateService,
|
||||||
NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService,
|
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
||||||
manga, Notifications.ID_NEW_CHAPTERS))
|
)
|
||||||
|
)
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_book_white_24dp, getString(R.string.action_view_chapters),
|
||||||
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
|
this@LibraryUpdateService,
|
||||||
|
manga, Notifications.ID_NEW_CHAPTERS
|
||||||
|
)
|
||||||
|
)
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
}, manga.id.hashCode()))
|
}, manga.id.hashCode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationManagerCompat.from(this).apply {
|
NotificationManagerCompat.from(this).apply {
|
||||||
|
|
||||||
notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
notify(
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
Notifications.ID_NEW_CHAPTERS,
|
||||||
setLargeIcon(notificationBitmap)
|
notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
setContentTitle(getString(R.string.notification_new_chapters))
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
color = ContextCompat.getColor(applicationContext, R.color.colorAccent)
|
setLargeIcon(notificationBitmap)
|
||||||
if (updates.size > 1) {
|
setContentTitle(getString(R.string.notification_new_chapters))
|
||||||
setContentText(resources.getQuantityString(R.plurals
|
color = ContextCompat.getColor(applicationContext, R.color.colorAccent)
|
||||||
.notification_new_chapters_text,
|
if (updates.size > 1) {
|
||||||
updates.size, updates.size))
|
setContentText(
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(updates.keys.joinToString("\n") {
|
resources.getQuantityString(
|
||||||
it.currentTitle().chop(45)
|
R.plurals
|
||||||
}))
|
.notification_new_chapters_text,
|
||||||
}
|
updates.size, updates.size
|
||||||
else {
|
)
|
||||||
setContentText(updates.keys.first().currentTitle().chop(45))
|
)
|
||||||
}
|
setStyle(
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
NotificationCompat.BigTextStyle()
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
.bigText(updates.keys.joinToString("\n") {
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
it.currentTitle().chop(45)
|
||||||
setGroupSummary(true)
|
})
|
||||||
setContentIntent(getNotificationIntent())
|
)
|
||||||
setAutoCancel(true)
|
} else {
|
||||||
})
|
setContentText(updates.keys.first().currentTitle().chop(45))
|
||||||
|
}
|
||||||
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
|
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
||||||
|
setGroupSummary(true)
|
||||||
|
setContentIntent(getNotificationIntent())
|
||||||
|
setAutoCancel(true)
|
||||||
|
})
|
||||||
|
|
||||||
notifications.forEach {
|
notifications.forEach {
|
||||||
notify(it.second, it.first)
|
notify(it.second, it.first)
|
||||||
|
@ -37,8 +37,6 @@ abstract class TrackService(val id: Int) {
|
|||||||
|
|
||||||
abstract fun displayScore(track: Track): String
|
abstract fun displayScore(track: Track): String
|
||||||
|
|
||||||
abstract suspend fun add(track: Track): Track
|
|
||||||
|
|
||||||
abstract suspend fun update(track: Track): Track
|
abstract suspend fun update(track: Track): Track
|
||||||
|
|
||||||
abstract suspend fun bind(track: Track): Track
|
abstract suspend fun bind(track: Track): Track
|
||||||
|
@ -8,28 +8,11 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val READING = 1
|
|
||||||
const val COMPLETED = 2
|
|
||||||
const val PAUSED = 3
|
|
||||||
const val DROPPED = 4
|
|
||||||
const val PLANNING = 5
|
|
||||||
const val REPEATING = 6
|
|
||||||
|
|
||||||
const val DEFAULT_STATUS = READING
|
|
||||||
const val DEFAULT_SCORE = 0
|
|
||||||
|
|
||||||
const val POINT_100 = "POINT_100"
|
|
||||||
const val POINT_10 = "POINT_10"
|
|
||||||
const val POINT_10_DECIMAL = "POINT_10_DECIMAL"
|
|
||||||
const val POINT_5 = "POINT_5"
|
|
||||||
const val POINT_3 = "POINT_3"
|
|
||||||
}
|
|
||||||
|
|
||||||
override val name = "AniList"
|
override val name = "AniList"
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
@ -54,9 +37,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList() = listOf(READING, PLANNING, COMPLETED, REPEATING, PAUSED, DROPPED)
|
||||||
return listOf(READING, PLANNING, COMPLETED, REPEATING, PAUSED, DROPPED)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getStatus(status: Int): String = with(context) {
|
override fun getStatus(status: Int): String = with(context) {
|
||||||
when (status) {
|
when (status) {
|
||||||
@ -93,13 +74,13 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
// 100 point
|
// 100 point
|
||||||
POINT_100 -> index.toFloat()
|
POINT_100 -> index.toFloat()
|
||||||
// 5 stars
|
// 5 stars
|
||||||
POINT_5 -> when {
|
POINT_5 -> when (index) {
|
||||||
index == 0 -> 0f
|
0 -> 0f
|
||||||
else -> index * 20f - 10f
|
else -> index * 20f - 10f
|
||||||
}
|
}
|
||||||
// Smiley
|
// Smiley
|
||||||
POINT_3 -> when {
|
POINT_3 -> when (index) {
|
||||||
index == 0 -> 0f
|
0 -> 0f
|
||||||
else -> index * 25f + 10f
|
else -> index * 25f + 10f
|
||||||
}
|
}
|
||||||
// 10 point decimal
|
// 10 point decimal
|
||||||
@ -112,8 +93,8 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
val score = track.score
|
val score = track.score
|
||||||
|
|
||||||
return when (scorePreference.getOrDefault()) {
|
return when (scorePreference.getOrDefault()) {
|
||||||
POINT_5 -> when {
|
POINT_5 -> when (score) {
|
||||||
score == 0f -> "0 ★"
|
0f -> "0 ★"
|
||||||
else -> "${((score + 10) / 20).toInt()} ★"
|
else -> "${((score + 10) / 20).toInt()} ★"
|
||||||
}
|
}
|
||||||
POINT_3 -> when {
|
POINT_3 -> when {
|
||||||
@ -126,10 +107,6 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun add(track: Track): Track {
|
|
||||||
return api.addLibManga(track)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(track: Track): Track {
|
override suspend fun update(track: Track): Track {
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
@ -137,34 +114,30 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
// If user was using API v1 fetch library_id
|
// If user was using API v1 fetch library_id
|
||||||
if (track.library_id == null || track.library_id!! == 0L) {
|
if (track.library_id == null || track.library_id!! == 0L) {
|
||||||
val libManga = api.findLibManga(track, getUsername().toInt())
|
val libManga = api.findLibManga(track, getUsername().toInt())
|
||||||
|
?: throw Exception("$track not found on user library")
|
||||||
|
|
||||||
if (libManga == null) {
|
|
||||||
throw Exception("$track not found on user library")
|
|
||||||
}
|
|
||||||
track.library_id = libManga.library_id
|
track.library_id = libManga.library_id
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.updateLibManga(track)
|
return api.updateLibraryManga(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun bind(track: Track): Track {
|
override suspend fun bind(track: Track): Track {
|
||||||
val remoteTrack = api.findLibManga(track, getUsername().toInt())
|
val remoteTrack = api.findLibManga(track, getUsername().toInt())
|
||||||
|
|
||||||
if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
track.copyPersonalFrom(remoteTrack)
|
track.copyPersonalFrom(remoteTrack)
|
||||||
track.library_id = remoteTrack.library_id
|
track.library_id = remoteTrack.library_id
|
||||||
return update(track)
|
update(track)
|
||||||
} else {
|
} else {
|
||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.score = DEFAULT_SCORE.toFloat()
|
track.score = DEFAULT_SCORE.toFloat()
|
||||||
track.status = DEFAULT_STATUS
|
track.status = DEFAULT_STATUS
|
||||||
return add(track)
|
api.addLibManga(track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(query: String): List<TrackSearch> {
|
override suspend fun search(query: String) = api.search(query)
|
||||||
return api.search(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun refresh(track: Track): Track {
|
override suspend fun refresh(track: Track): Track {
|
||||||
val remoteTrack = api.getLibManga(track, getUsername().toInt())
|
val remoteTrack = api.getLibManga(track, getUsername().toInt())
|
||||||
@ -180,15 +153,16 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
val oauth = api.createOAuth(token)
|
val oauth = api.createOAuth(token)
|
||||||
interceptor.setAuth(oauth)
|
interceptor.setAuth(oauth)
|
||||||
|
|
||||||
try {
|
return try {
|
||||||
val currentUser = api.getCurrentUser()
|
val currentUser = api.getCurrentUser()
|
||||||
scorePreference.set(currentUser.second)
|
scorePreference.set(currentUser.second)
|
||||||
saveCredentials(currentUser.first.toString(), oauth.access_token)
|
saveCredentials(currentUser.first.toString(), oauth.access_token)
|
||||||
return true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logout()
|
Timber.e(e)
|
||||||
return false
|
logout()
|
||||||
}
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logout() {
|
override fun logout() {
|
||||||
@ -205,9 +179,29 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
return try {
|
return try {
|
||||||
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val READING = 1
|
||||||
|
const val COMPLETED = 2
|
||||||
|
const val PAUSED = 3
|
||||||
|
const val DROPPED = 4
|
||||||
|
const val PLANNING = 5
|
||||||
|
const val REPEATING = 6
|
||||||
|
|
||||||
|
const val DEFAULT_STATUS = READING
|
||||||
|
const val DEFAULT_SCORE = 0
|
||||||
|
|
||||||
|
const val POINT_100 = "POINT_100"
|
||||||
|
const val POINT_10 = "POINT_10"
|
||||||
|
const val POINT_10_DECIMAL = "POINT_10_DECIMAL"
|
||||||
|
const val POINT_5 = "POINT_5"
|
||||||
|
const val POINT_3 = "POINT_3"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,234 +11,156 @@ import com.google.gson.JsonObject
|
|||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.jsonType
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.MediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
|
|
||||||
private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull()
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track): Track {
|
suspend fun addLibManga(track: Track): Track {
|
||||||
val query = """
|
return withContext(Dispatchers.IO) {
|
||||||
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
|
||||||
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|
|
||||||
| id
|
|
||||||
| status
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|""".trimMargin()
|
|
||||||
val variables = jsonObject(
|
|
||||||
"mangaId" to track.media_id,
|
|
||||||
"progress" to track.last_chapter_read,
|
|
||||||
"status" to track.toAnilistStatus()
|
|
||||||
)
|
|
||||||
val payload = jsonObject(
|
|
||||||
"query" to query,
|
|
||||||
"variables" to variables
|
|
||||||
)
|
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
val netResponse = authClient.newCall(request).await()
|
|
||||||
|
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
val variables = jsonObject(
|
||||||
netResponse.close()
|
"mangaId" to track.media_id,
|
||||||
if (responseBody.isEmpty()) {
|
"progress" to track.last_chapter_read,
|
||||||
throw Exception("Null Response")
|
"status" to track.toAnilistStatus()
|
||||||
}
|
)
|
||||||
val response = JsonParser().parse(responseBody).obj
|
val payload = jsonObject(
|
||||||
track.library_id = response["data"]["SaveMediaListEntry"]["id"].asLong
|
"query" to addToLibraryQuery(),
|
||||||
|
"variables" to variables
|
||||||
|
)
|
||||||
|
val body = payload.toString().toRequestBody(MediaType.jsonType())
|
||||||
|
val request = Request.Builder().url(apiUrl).post(body).build()
|
||||||
|
|
||||||
return track
|
val netResponse = authClient.newCall(request).execute()
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateLibManga(track: Track): Track {
|
val responseBody = netResponse.body?.string().orEmpty()
|
||||||
val query = """
|
netResponse.close()
|
||||||
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|
|
||||||
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|
|
||||||
|id
|
|
||||||
|status
|
|
||||||
|progress
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|""".trimMargin()
|
|
||||||
val variables = jsonObject(
|
|
||||||
"listId" to track.library_id,
|
|
||||||
"progress" to track.last_chapter_read,
|
|
||||||
"status" to track.toAnilistStatus(),
|
|
||||||
"score" to track.score.toInt()
|
|
||||||
)
|
|
||||||
val payload = jsonObject(
|
|
||||||
"query" to query,
|
|
||||||
"variables" to variables
|
|
||||||
)
|
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
authClient.newCall(request).execute()
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun search(search: String): List<TrackSearch> {
|
|
||||||
val query = """
|
|
||||||
|query Search(${'$'}query: String) {
|
|
||||||
|Page (perPage: 50) {
|
|
||||||
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
|
||||||
|id
|
|
||||||
|title {
|
|
||||||
|romaji
|
|
||||||
|}
|
|
||||||
|coverImage {
|
|
||||||
|large
|
|
||||||
|}
|
|
||||||
|type
|
|
||||||
|status
|
|
||||||
|chapters
|
|
||||||
|description
|
|
||||||
|startDate {
|
|
||||||
|year
|
|
||||||
|month
|
|
||||||
|day
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|""".trimMargin()
|
|
||||||
val variables = jsonObject(
|
|
||||||
"query" to search
|
|
||||||
)
|
|
||||||
val payload = jsonObject(
|
|
||||||
"query" to query,
|
|
||||||
"variables" to variables
|
|
||||||
)
|
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
val netResponse = authClient.newCall(request).await()
|
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
|
||||||
if (responseBody.isEmpty()) {
|
|
||||||
throw Exception("Null Response")
|
|
||||||
}
|
|
||||||
val response = JsonParser().parse(responseBody).obj
|
|
||||||
val data = response["data"]!!.obj
|
|
||||||
val page = data["Page"].obj
|
|
||||||
val media = page["media"].array
|
|
||||||
val entries = media.map { jsonToALManga(it.obj) }
|
|
||||||
return entries.map { it.toTrack() }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun findLibManga(track: Track, userid: Int): Track? {
|
|
||||||
val query = """
|
|
||||||
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|
|
||||||
|Page {
|
|
||||||
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|
|
||||||
|id
|
|
||||||
|status
|
|
||||||
|scoreRaw: score(format: POINT_100)
|
|
||||||
|progress
|
|
||||||
|media {
|
|
||||||
|id
|
|
||||||
|title {
|
|
||||||
|romaji
|
|
||||||
|}
|
|
||||||
|coverImage {
|
|
||||||
|large
|
|
||||||
|}
|
|
||||||
|type
|
|
||||||
|status
|
|
||||||
|chapters
|
|
||||||
|description
|
|
||||||
|startDate {
|
|
||||||
|year
|
|
||||||
|month
|
|
||||||
|day
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|""".trimMargin()
|
|
||||||
val variables = jsonObject(
|
|
||||||
"id" to userid,
|
|
||||||
"manga_id" to track.media_id
|
|
||||||
)
|
|
||||||
val payload = jsonObject(
|
|
||||||
"query" to query,
|
|
||||||
"variables" to variables
|
|
||||||
)
|
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
val result = authClient.newCall(request).await()
|
|
||||||
return result.let { resp ->
|
|
||||||
val responseBody = resp.body?.string().orEmpty()
|
|
||||||
if (responseBody.isEmpty()) {
|
if (responseBody.isEmpty()) {
|
||||||
throw Exception("Null Response")
|
throw Exception("Null Response")
|
||||||
}
|
}
|
||||||
val response = JsonParser().parse(responseBody).obj
|
val response = JsonParser.parseString(responseBody).obj
|
||||||
val data = response["data"]!!.obj
|
track.library_id = response["data"]["SaveMediaListEntry"]["id"].asLong
|
||||||
val page = data["Page"].obj
|
track
|
||||||
val media = page["mediaList"].array
|
}
|
||||||
val entries = media.map { jsonToALUserManga(it.obj) }
|
}
|
||||||
entries.firstOrNull()?.toTrack()
|
|
||||||
|
suspend fun updateLibraryManga(track: Track): Track {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val variables = jsonObject(
|
||||||
|
"listId" to track.library_id,
|
||||||
|
"progress" to track.last_chapter_read,
|
||||||
|
"status" to track.toAnilistStatus(),
|
||||||
|
"score" to track.score.toInt()
|
||||||
|
)
|
||||||
|
val payload = jsonObject(
|
||||||
|
"query" to updateInLibraryQuery(),
|
||||||
|
"variables" to variables
|
||||||
|
)
|
||||||
|
val body = payload.toString().toRequestBody(MediaType.jsonType())
|
||||||
|
val request = Request.Builder().url(apiUrl).post(body).build()
|
||||||
|
val response = authClient.newCall(request).execute()
|
||||||
|
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun search(search: String): List<TrackSearch> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val variables = jsonObject(
|
||||||
|
"query" to search
|
||||||
|
)
|
||||||
|
val payload = jsonObject(
|
||||||
|
"query" to searchQuery(),
|
||||||
|
"variables" to variables
|
||||||
|
)
|
||||||
|
val body = payload.toString().toRequestBody(MediaType.jsonType())
|
||||||
|
val request = Request.Builder().url(apiUrl).post(body).build()
|
||||||
|
val netResponse = authClient.newCall(request).execute()
|
||||||
|
val response = responseToJson(netResponse)
|
||||||
|
|
||||||
|
val media = response["data"]!!.obj["Page"].obj["mediaList"].array
|
||||||
|
val entries = media.map { jsonToALManga(it.obj) }
|
||||||
|
entries.map { it.toTrack() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun findLibManga(track: Track, userid: Int): Track? {
|
||||||
|
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val variables = jsonObject(
|
||||||
|
"id" to userid,
|
||||||
|
"manga_id" to track.media_id
|
||||||
|
)
|
||||||
|
val payload = jsonObject(
|
||||||
|
"query" to findLibraryMangaQuery(),
|
||||||
|
"variables" to variables
|
||||||
|
)
|
||||||
|
val body = payload.toString().toRequestBody(MediaType.jsonType())
|
||||||
|
val request = Request.Builder().url(apiUrl).post(body).build()
|
||||||
|
val result = authClient.newCall(request).execute()
|
||||||
|
|
||||||
|
result.let { resp ->
|
||||||
|
val response = responseToJson(resp)
|
||||||
|
val media = response["data"]!!.obj["Page"].obj["mediaList"].array
|
||||||
|
val entries = media.map { jsonToALUserManga(it.obj) }
|
||||||
|
|
||||||
|
entries.firstOrNull()?.toTrack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLibManga(track: Track, userid: Int): Track {
|
suspend fun getLibManga(track: Track, userid: Int): Track {
|
||||||
val track = findLibManga(track, userid)
|
val remoteTrack = findLibManga(track, userid)
|
||||||
if (track == null) {
|
if (remoteTrack == null) {
|
||||||
throw Exception("Could not find manga")
|
throw Exception("Could not find manga")
|
||||||
} else {
|
} else {
|
||||||
return track
|
return remoteTrack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createOAuth(token: String): OAuth {
|
fun createOAuth(token: String): OAuth {
|
||||||
return OAuth(token, "Bearer", System.currentTimeMillis() + 31536000000, 31536000000)
|
return OAuth(
|
||||||
|
token,
|
||||||
|
"Bearer",
|
||||||
|
System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365),
|
||||||
|
TimeUnit.DAYS.toMillis(365)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentUser(): Pair<Int, String> {
|
suspend fun getCurrentUser(): Pair<Int, String> {
|
||||||
val query = """
|
return withContext(Dispatchers.IO) {
|
||||||
|query User {
|
val payload = jsonObject(
|
||||||
|Viewer {
|
"query" to currentUserQuery()
|
||||||
|id
|
)
|
||||||
|mediaListOptions {
|
val body = payload.toString().toRequestBody(MediaType.jsonType())
|
||||||
|scoreFormat
|
val request = Request.Builder().url(apiUrl).post(body).build()
|
||||||
|}
|
val netResponse = authClient.newCall(request).execute()
|
||||||
|}
|
|
||||||
|}
|
|
||||||
|""".trimMargin()
|
|
||||||
val payload = jsonObject(
|
|
||||||
"query" to query
|
|
||||||
)
|
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
val netResponse = authClient.newCall(request).await()
|
|
||||||
|
|
||||||
|
val response = responseToJson(netResponse)
|
||||||
|
val viewer = response["data"]!!.obj["Viewer"].obj
|
||||||
|
|
||||||
|
Pair(viewer["id"].asInt, viewer["mediaListOptions"]["scoreFormat"].asString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun responseToJson(netResponse: Response): JsonObject {
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
val responseBody = netResponse.body?.string().orEmpty()
|
||||||
|
|
||||||
if (responseBody.isEmpty()) {
|
if (responseBody.isEmpty()) {
|
||||||
throw Exception("Null Response")
|
throw Exception("Null Response")
|
||||||
}
|
}
|
||||||
val response = JsonParser().parse(responseBody).obj
|
|
||||||
val data = response["data"]!!.obj
|
return JsonParser.parseString(responseBody).obj
|
||||||
val viewer = data["Viewer"].obj
|
|
||||||
return Pair(viewer["id"].asInt, viewer["mediaListOptions"]["scoreFormat"].asString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun jsonToALManga(struct: JsonObject): ALManga {
|
private fun jsonToALManga(struct: JsonObject): ALManga {
|
||||||
@ -289,6 +211,92 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
fun authUrl() = Uri.parse("${baseUrl}oauth/authorize").buildUpon()
|
fun authUrl() = Uri.parse("${baseUrl}oauth/authorize").buildUpon()
|
||||||
.appendQueryParameter("client_id", clientId)
|
.appendQueryParameter("client_id", clientId)
|
||||||
.appendQueryParameter("response_type", "token")
|
.appendQueryParameter("response_type", "token")
|
||||||
.build()
|
.build()!!
|
||||||
|
|
||||||
|
fun addToLibraryQuery() = """
|
||||||
|
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
||||||
|
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|
||||||
|
| id
|
||||||
|
| status
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|""".trimMargin()
|
||||||
|
|
||||||
|
fun updateInLibraryQuery() = """
|
||||||
|
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|
||||||
|
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|
||||||
|
|id
|
||||||
|
|status
|
||||||
|
|progress
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|""".trimMargin()
|
||||||
|
|
||||||
|
fun searchQuery() = """
|
||||||
|
|query Search(${'$'}query: String) {
|
||||||
|
|Page (perPage: 50) {
|
||||||
|
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
||||||
|
|id
|
||||||
|
|title {
|
||||||
|
|romaji
|
||||||
|
|}
|
||||||
|
|coverImage {
|
||||||
|
|large
|
||||||
|
|}
|
||||||
|
|type
|
||||||
|
|status
|
||||||
|
|chapters
|
||||||
|
|description
|
||||||
|
|startDate {
|
||||||
|
|year
|
||||||
|
|month
|
||||||
|
|day
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|""".trimMargin()
|
||||||
|
|
||||||
|
fun findLibraryMangaQuery() = """
|
||||||
|
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|
||||||
|
|Page {
|
||||||
|
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|
||||||
|
|id
|
||||||
|
|status
|
||||||
|
|scoreRaw: score(format: POINT_100)
|
||||||
|
|progress
|
||||||
|
|media {
|
||||||
|
|id
|
||||||
|
|title {
|
||||||
|
|romaji
|
||||||
|
|}
|
||||||
|
|coverImage {
|
||||||
|
|large
|
||||||
|
|}
|
||||||
|
|type
|
||||||
|
|status
|
||||||
|
|chapters
|
||||||
|
|description
|
||||||
|
|startDate {
|
||||||
|
|year
|
||||||
|
|month
|
||||||
|
|day
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|""".trimMargin()
|
||||||
|
|
||||||
|
fun currentUserQuery() = """
|
||||||
|
|query User {
|
||||||
|
|Viewer {
|
||||||
|
|id
|
||||||
|
|mediaListOptions {
|
||||||
|
|scoreFormat
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|""".trimMargin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import okhttp3.Interceptor
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
|
||||||
|
|
||||||
class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Interceptor {
|
class AnilistInterceptor(private val anilist: Anilist, private var token: String?) : Interceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth object used for authenticated requests.
|
* OAuth object used for authenticated requests.
|
||||||
|
@ -9,6 +9,15 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
data class OAuth(
|
||||||
|
val access_token: String,
|
||||||
|
val token_type: String,
|
||||||
|
val expires: Long,
|
||||||
|
val expires_in: Long) {
|
||||||
|
|
||||||
|
fun isExpired() = System.currentTimeMillis() > expires
|
||||||
|
}
|
||||||
|
|
||||||
data class ALManga(
|
data class ALManga(
|
||||||
val media_id: Int,
|
val media_id: Int,
|
||||||
val title_romaji: String,
|
val title_romaji: String,
|
||||||
@ -56,7 +65,7 @@ data class ALUserManga(
|
|||||||
total_chapters = manga.total_chapters
|
total_chapters = manga.total_chapters
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toTrackStatus() = when (list_status) {
|
private fun toTrackStatus() = when (list_status) {
|
||||||
"CURRENT" -> Anilist.READING
|
"CURRENT" -> Anilist.READING
|
||||||
"COMPLETED" -> Anilist.COMPLETED
|
"COMPLETED" -> Anilist.COMPLETED
|
||||||
"PAUSED" -> Anilist.PAUSED
|
"PAUSED" -> Anilist.PAUSED
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.anilist
|
|
||||||
|
|
||||||
data class OAuth(
|
|
||||||
val access_token: String,
|
|
||||||
val token_type: String,
|
|
||||||
val expires: Long,
|
|
||||||
val expires_in: Long) {
|
|
||||||
|
|
||||||
fun isExpired() = System.currentTimeMillis() > expires
|
|
||||||
}
|
|
@ -28,10 +28,6 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
return track.score.toInt().toString()
|
return track.score.toInt().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun add(track: Track): Track {
|
|
||||||
return api.addLibManga(track)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(track: Track): Track {
|
override suspend fun update(track: Track): Track {
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
@ -51,7 +47,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
} else {
|
} else {
|
||||||
track.score = DEFAULT_SCORE.toFloat()
|
track.score = DEFAULT_SCORE.toFloat()
|
||||||
track.status = DEFAULT_STATUS
|
track.status = DEFAULT_STATUS
|
||||||
add(track)
|
api.addLibManga(track)
|
||||||
update(track)
|
update(track)
|
||||||
}
|
}
|
||||||
return track
|
return track
|
||||||
|
@ -69,10 +69,6 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
return df.format(track.score)
|
return df.format(track.score)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun add(track: Track): Track {
|
|
||||||
return api.addLibManga(track, getUserId())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(track: Track): Track {
|
override suspend fun update(track: Track): Track {
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
@ -90,7 +86,7 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
} else {
|
} else {
|
||||||
track.score = DEFAULT_SCORE
|
track.score = DEFAULT_SCORE
|
||||||
track.status = DEFAULT_STATUS
|
track.status = DEFAULT_STATUS
|
||||||
return add(track)
|
return api.addLibManga(track, getUserId())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,29 +8,14 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val READING = 1
|
|
||||||
const val COMPLETED = 2
|
|
||||||
const val ON_HOLD = 3
|
|
||||||
const val DROPPED = 4
|
|
||||||
const val PLAN_TO_READ = 6
|
|
||||||
|
|
||||||
const val DEFAULT_STATUS = READING
|
|
||||||
const val DEFAULT_SCORE = 0
|
|
||||||
|
|
||||||
const val BASE_URL = "https://myanimelist.net"
|
|
||||||
const val USER_SESSION_COOKIE = "MALSESSIONID"
|
|
||||||
const val LOGGED_IN_COOKIE = "is_logged_in"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
||||||
private val api by lazy { MyAnimeListApi(client, interceptor) }
|
private val api by lazy { MyAnimeListApi(client, interceptor) }
|
||||||
|
|
||||||
override val name: String
|
override val name = "MyAnimeList"
|
||||||
get() = "MyAnimeList"
|
|
||||||
|
|
||||||
override fun getLogo() = R.drawable.tracker_mal
|
override fun getLogo() = R.drawable.tracker_mal
|
||||||
|
|
||||||
@ -59,10 +44,6 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
return track.score.toInt().toString()
|
return track.score.toInt().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun add(track: Track): Track {
|
|
||||||
return api.addLibManga(track)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(track: Track): Track {
|
override suspend fun update(track: Track): Track {
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
track.status = COMPLETED
|
track.status = COMPLETED
|
||||||
@ -80,7 +61,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
// Set default fields if it's not found in the list
|
// Set default fields if it's not found in the list
|
||||||
track.score = DEFAULT_SCORE.toFloat()
|
track.score = DEFAULT_SCORE.toFloat()
|
||||||
track.status = DEFAULT_STATUS
|
track.status = DEFAULT_STATUS
|
||||||
add(track)
|
return api.addLibManga(track)
|
||||||
}
|
}
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
@ -98,18 +79,19 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
|
|
||||||
override suspend fun login(username: String, password: String): Boolean {
|
override suspend fun login(username: String, password: String): Boolean {
|
||||||
logout()
|
logout()
|
||||||
try {
|
return try {
|
||||||
val csrf = api.login(username, password)
|
val csrf = api.login(username, password)
|
||||||
saveCSRF(csrf)
|
saveCSRF(csrf)
|
||||||
saveCredentials(username, password)
|
saveCredentials(username, password)
|
||||||
return true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
logout()
|
logout()
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshLogin() {
|
private suspend fun refreshLogin() {
|
||||||
val username = getUsername()
|
val username = getUsername()
|
||||||
val password = getPassword()
|
val password = getPassword()
|
||||||
logout()
|
logout()
|
||||||
@ -119,13 +101,14 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
saveCSRF(csrf)
|
saveCSRF(csrf)
|
||||||
saveCredentials(username, password)
|
saveCredentials(username, password)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
logout()
|
logout()
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to login again if cookies have been cleared but credentials are still filled
|
// Attempt to login again if cookies have been cleared but credentials are still filled
|
||||||
fun ensureLoggedIn() {
|
suspend fun ensureLoggedIn() {
|
||||||
if (isAuthorized) return
|
if (isAuthorized) return
|
||||||
if (!isLogged) throw Exception("MAL Login Credentials not found")
|
if (!isLogged) throw Exception("MAL Login Credentials not found")
|
||||||
|
|
||||||
@ -138,10 +121,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
networkService.cookieManager.remove(BASE_URL.toHttpUrlOrNull()!!)
|
networkService.cookieManager.remove(BASE_URL.toHttpUrlOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isAuthorized: Boolean
|
private val isAuthorized = super.isLogged && getCSRF().isNotEmpty() && checkCookies()
|
||||||
get() = super.isLogged &&
|
|
||||||
getCSRF().isNotEmpty() &&
|
|
||||||
checkCookies()
|
|
||||||
|
|
||||||
fun getCSRF(): String = preferences.trackToken(this).getOrDefault()
|
fun getCSRF(): String = preferences.trackToken(this).getOrDefault()
|
||||||
|
|
||||||
@ -157,4 +137,19 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
|
|
||||||
return ckCount == 2
|
return ckCount == 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val READING = 1
|
||||||
|
const val COMPLETED = 2
|
||||||
|
const val ON_HOLD = 3
|
||||||
|
const val DROPPED = 4
|
||||||
|
const val PLAN_TO_READ = 6
|
||||||
|
|
||||||
|
const val DEFAULT_STATUS = READING
|
||||||
|
const val DEFAULT_SCORE = 0
|
||||||
|
|
||||||
|
const val BASE_URL = "https://myanimelist.net"
|
||||||
|
const val USER_SESSION_COOKIE = "MALSESSIONID"
|
||||||
|
const val LOGGED_IN_COOKIE = "is_logged_in"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.network.consumeBody
|
|||||||
import eu.kanade.tachiyomi.network.consumeXmlBody
|
import eu.kanade.tachiyomi.network.consumeXmlBody
|
||||||
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 kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -27,35 +29,41 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun search(query: String): List<TrackSearch> {
|
suspend fun search(query: String): List<TrackSearch> {
|
||||||
if (query.startsWith(PREFIX_MY)) {
|
return withContext(Dispatchers.IO) {
|
||||||
val realQuery = query.removePrefix(PREFIX_MY)
|
if (query.startsWith(PREFIX_MY)) {
|
||||||
return getList().filter { it.title.contains(realQuery, true) }.toList()
|
queryUsersList(query)
|
||||||
} else {
|
} else {
|
||||||
val realQuery = query.take(100)
|
val realQuery = query.take(100)
|
||||||
val response = client.newCall(GET(searchUrl(realQuery))).await()
|
val response = client.newCall(GET(searchUrl(realQuery))).await()
|
||||||
val matches = Jsoup.parse(response.consumeBody())
|
val matches = Jsoup.parse(response.consumeBody())
|
||||||
.select("div.js-categories-seasonal.js-block-list.list")
|
.select("div.js-categories-seasonal.js-block-list.list")
|
||||||
.select("table").select("tbody")
|
.select("table").select("tbody")
|
||||||
.select("tr").drop(1)
|
.select("tr").drop(1)
|
||||||
|
|
||||||
return matches.filter { row -> row.select(TD)[2].text() != "Novel" }
|
matches.filter { row -> row.select(TD)[2].text() != "Novel" }
|
||||||
.map { row ->
|
.map { row ->
|
||||||
TrackSearch.create(TrackManager.MYANIMELIST).apply {
|
TrackSearch.create(TrackManager.MYANIMELIST).apply {
|
||||||
title = row.searchTitle()
|
title = row.searchTitle()
|
||||||
media_id = row.searchMediaId()
|
media_id = row.searchMediaId()
|
||||||
total_chapters = row.searchTotalChapters()
|
total_chapters = row.searchTotalChapters()
|
||||||
summary = row.searchSummary()
|
summary = row.searchSummary()
|
||||||
cover_url = row.searchCoverUrl()
|
cover_url = row.searchCoverUrl()
|
||||||
tracking_url = mangaUrl(media_id)
|
tracking_url = mangaUrl(media_id)
|
||||||
publishing_status = row.searchPublishingStatus()
|
publishing_status = row.searchPublishingStatus()
|
||||||
publishing_type = row.searchPublishingType()
|
publishing_type = row.searchPublishingType()
|
||||||
start_date = row.searchStartDate()
|
start_date = row.searchStartDate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.toList()
|
||||||
.toList()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun queryUsersList(query: String): List<TrackSearch> {
|
||||||
|
val realQuery = query.removePrefix(PREFIX_MY).take(100)
|
||||||
|
return getList().filter { it.title.contains(realQuery, true) }.toList()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track): Track {
|
suspend fun addLibManga(track: Track): Track {
|
||||||
authClient.newCall(POST(url = addUrl(), body = mangaPostPayload(track))).await()
|
authClient.newCall(POST(url = addUrl(), body = mangaPostPayload(track))).await()
|
||||||
return track
|
return track
|
||||||
@ -67,24 +75,26 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun findLibManga(track: Track): Track? {
|
suspend fun findLibManga(track: Track): Track? {
|
||||||
val response = authClient.newCall(GET(url = listEntryUrl(track.media_id))).await()
|
return withContext(Dispatchers.IO) {
|
||||||
var libTrack: Track? = null
|
val response = authClient.newCall(GET(url = listEntryUrl(track.media_id))).await()
|
||||||
response.use {
|
var remoteTrack: Track? = null
|
||||||
if (it.priorResponse?.isRedirect != true) {
|
response.use {
|
||||||
val trackForm = Jsoup.parse(it.consumeBody())
|
if (it.priorResponse?.isRedirect != true) {
|
||||||
|
val trackForm = Jsoup.parse(it.consumeBody())
|
||||||
|
|
||||||
libTrack = Track.create(TrackManager.MYANIMELIST).apply {
|
remoteTrack = Track.create(TrackManager.MYANIMELIST).apply {
|
||||||
last_chapter_read =
|
last_chapter_read =
|
||||||
trackForm.select("#add_manga_num_read_chapters").`val`().toInt()
|
trackForm.select("#add_manga_num_read_chapters").`val`().toInt()
|
||||||
total_chapters = trackForm.select("#totalChap").text().toInt()
|
total_chapters = trackForm.select("#totalChap").text().toInt()
|
||||||
status =
|
status =
|
||||||
trackForm.select("#add_manga_status > option[selected]").`val`().toInt()
|
trackForm.select("#add_manga_status > option[selected]").`val`().toInt()
|
||||||
score = trackForm.select("#add_manga_score > option[selected]").`val`()
|
score = trackForm.select("#add_manga_score > option[selected]").`val`()
|
||||||
.toFloatOrNull() ?: 0f
|
.toFloatOrNull() ?: 0f
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
remoteTrack
|
||||||
}
|
}
|
||||||
return libTrack
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getLibManga(track: Track): Track {
|
suspend fun getLibManga(track: Track): Track {
|
||||||
@ -96,15 +106,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun login(username: String, password: String): String {
|
suspend fun login(username: String, password: String): String {
|
||||||
val csrf = getSessionInfo()
|
return withContext(Dispatchers.IO) {
|
||||||
|
val csrf = getSessionInfo()
|
||||||
login(username, password, csrf)
|
login(username, password, csrf)
|
||||||
|
csrf
|
||||||
return csrf
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSessionInfo(): String {
|
private suspend fun getSessionInfo(): String {
|
||||||
val response = client.newCall(GET(loginUrl())).execute()
|
val response = client.newCall(GET(loginUrl())).execute()
|
||||||
|
|
||||||
return Jsoup.parse(response.consumeBody())
|
return Jsoup.parse(response.consumeBody())
|
||||||
@ -112,13 +122,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.attr("content")
|
.attr("content")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun login(username: String, password: String, csrf: String) {
|
private suspend fun login(username: String, password: String, csrf: String) {
|
||||||
val response =
|
withContext(Dispatchers.IO) {
|
||||||
client.newCall(POST(url = loginUrl(), body = loginPostBody(username, password, csrf)))
|
val response =
|
||||||
.execute()
|
client.newCall(POST(loginUrl(), body = loginPostBody(username, password, csrf)))
|
||||||
|
.execute()
|
||||||
|
|
||||||
response.use {
|
response.use {
|
||||||
if (response.priorResponse?.code != 302) throw Exception("Authentication error")
|
if (response.priorResponse?.code != 302) throw Exception("Authentication error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,13 +152,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getListUrl(): String {
|
private suspend fun getListUrl(): String {
|
||||||
val response =
|
return withContext(Dispatchers.IO) {
|
||||||
authClient.newCall(POST(url = exportListUrl(), body = exportPostBody())).await()
|
val response =
|
||||||
|
authClient.newCall(POST(url = exportListUrl(), body = exportPostBody())).execute()
|
||||||
|
|
||||||
return baseUrl + Jsoup.parse(response.consumeBody())
|
baseUrl + Jsoup.parse(response.consumeBody())
|
||||||
.select("div.goodresult")
|
.select("div.goodresult")
|
||||||
.select("a")
|
.select("a")
|
||||||
.attr("href")
|
.attr("href")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getListXml(url: String): Document {
|
private suspend fun getListXml(url: String): Document {
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.myanimelist
|
package eu.kanade.tachiyomi.data.track.myanimelist
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
@ -8,20 +12,17 @@ import okhttp3.Response
|
|||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor {
|
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor {
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
myanimelist.ensureLoggedIn()
|
scope.launch {
|
||||||
|
myanimelist.ensureLoggedIn()
|
||||||
val request = chain.request()
|
|
||||||
var response = chain.proceed(updateRequest(request))
|
|
||||||
|
|
||||||
if (response.code == 400) {
|
|
||||||
myanimelist.refreshLogin()
|
|
||||||
response = chain.proceed(updateRequest(request))
|
|
||||||
}
|
}
|
||||||
|
val request = chain.request()
|
||||||
|
return chain.proceed(updateRequest(request))
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRequest(request: Request): Request {
|
private fun updateRequest(request: Request): Request {
|
||||||
@ -46,13 +47,15 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
|
|||||||
private fun updateFormBody(requestBody: RequestBody): RequestBody {
|
private fun updateFormBody(requestBody: RequestBody): RequestBody {
|
||||||
val formString = bodyToString(requestBody)
|
val formString = bodyToString(requestBody)
|
||||||
|
|
||||||
return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody(requestBody.contentType())
|
return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody(
|
||||||
|
requestBody.contentType()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateJsonBody(requestBody: RequestBody): RequestBody {
|
private fun updateJsonBody(requestBody: RequestBody): RequestBody {
|
||||||
val jsonString = bodyToString(requestBody)
|
val jsonString = bodyToString(requestBody)
|
||||||
val newBody = JSONObject(jsonString)
|
val newBody = JSONObject(jsonString)
|
||||||
.put(MyAnimeListApi.CSRF, myanimelist.getCSRF())
|
.put(MyAnimeListApi.CSRF, myanimelist.getCSRF())
|
||||||
|
|
||||||
return newBody.toString().toRequestBody(requestBody.contentType())
|
return newBody.toString().toRequestBody(requestBody.contentType())
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikimori
|
|
||||||
|
|
||||||
data class OAuth(
|
|
||||||
val access_token: String,
|
|
||||||
val token_type: String,
|
|
||||||
val created_at: Long,
|
|
||||||
val expires_in: Long,
|
|
||||||
val refresh_token: String?) {
|
|
||||||
|
|
||||||
// Access token lives 1 day
|
|
||||||
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
|
||||||
}
|
|
||||||
|
|
@ -12,67 +12,6 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
|
|
||||||
class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
override fun getScoreList(): List<String> {
|
|
||||||
return IntRange(0, 10).map(Int::toString)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun displayScore(track: Track): String {
|
|
||||||
return track.score.toInt().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun add(track: Track): Track {
|
|
||||||
return api.addLibManga(track, getUsername())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun update(track: Track): Track {
|
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
|
||||||
track.status = COMPLETED
|
|
||||||
}
|
|
||||||
return api.updateLibManga(track, getUsername())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun bind(track: Track): Track {
|
|
||||||
val remoteTrack = api.findLibManga(track, getUsername())
|
|
||||||
|
|
||||||
if (remoteTrack != null) {
|
|
||||||
track.copyPersonalFrom(remoteTrack)
|
|
||||||
track.library_id = remoteTrack.library_id
|
|
||||||
update(track)
|
|
||||||
} else {
|
|
||||||
// Set default fields if it's not found in the list
|
|
||||||
track.score = DEFAULT_SCORE.toFloat()
|
|
||||||
track.status = DEFAULT_STATUS
|
|
||||||
add(track)
|
|
||||||
}
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun search(query: String): List<TrackSearch> {
|
|
||||||
return api.search(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun refresh(track: Track): Track {
|
|
||||||
val remoteTrack = api.findLibManga(track, getUsername())
|
|
||||||
|
|
||||||
if (remoteTrack != null) {
|
|
||||||
track.copyPersonalFrom(remoteTrack)
|
|
||||||
track.total_chapters = remoteTrack.total_chapters
|
|
||||||
}
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val READING = 1
|
|
||||||
const val COMPLETED = 2
|
|
||||||
const val ON_HOLD = 3
|
|
||||||
const val DROPPED = 4
|
|
||||||
const val PLANNING = 5
|
|
||||||
const val REPEATING = 6
|
|
||||||
|
|
||||||
const val DEFAULT_STATUS = READING
|
|
||||||
const val DEFAULT_SCORE = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override val name = "Shikimori"
|
override val name = "Shikimori"
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
@ -101,6 +40,49 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getScoreList(): List<String> {
|
||||||
|
return IntRange(0, 10).map(Int::toString)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun displayScore(track: Track): String {
|
||||||
|
return track.score.toInt().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(track: Track): Track {
|
||||||
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
|
track.status = COMPLETED
|
||||||
|
}
|
||||||
|
return api.updateLibManga(track, getUsername())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun bind(track: Track): Track {
|
||||||
|
val remoteTrack = api.findLibManga(track, getUsername())
|
||||||
|
|
||||||
|
if (remoteTrack != null) {
|
||||||
|
track.copyPersonalFrom(remoteTrack)
|
||||||
|
track.library_id = remoteTrack.library_id
|
||||||
|
update(track)
|
||||||
|
} else {
|
||||||
|
// Set default fields if it's not found in the list
|
||||||
|
track.score = DEFAULT_SCORE.toFloat()
|
||||||
|
track.status = DEFAULT_STATUS
|
||||||
|
return api.addLibManga(track, getUsername())
|
||||||
|
}
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String) = api.search(query)
|
||||||
|
|
||||||
|
override suspend fun refresh(track: Track): Track {
|
||||||
|
val remoteTrack = api.findLibManga(track, getUsername())
|
||||||
|
|
||||||
|
if (remoteTrack != null) {
|
||||||
|
track.copyPersonalFrom(remoteTrack)
|
||||||
|
track.total_chapters = remoteTrack.total_chapters
|
||||||
|
}
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun login(username: String, password: String) = login(password)
|
override suspend fun login(username: String, password: String) = login(password)
|
||||||
|
|
||||||
suspend fun login(code: String): Boolean {
|
suspend fun login(code: String): Boolean {
|
||||||
@ -136,4 +118,16 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
preferences.trackToken(this).set(null)
|
preferences.trackToken(this).set(null)
|
||||||
interceptor.newAuth(null)
|
interceptor.newAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val READING = 1
|
||||||
|
const val COMPLETED = 2
|
||||||
|
const val ON_HOLD = 3
|
||||||
|
const val DROPPED = 4
|
||||||
|
const val PLANNING = 5
|
||||||
|
const val REPEATING = 6
|
||||||
|
|
||||||
|
const val DEFAULT_STATUS = READING
|
||||||
|
const val DEFAULT_SCORE = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,3 +22,15 @@ fun toTrackStatus(status: String) = when (status) {
|
|||||||
|
|
||||||
else -> throw Exception("Unknown status")
|
else -> throw Exception("Unknown status")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class OAuth(
|
||||||
|
val access_token: String,
|
||||||
|
val token_type: String,
|
||||||
|
val created_at: Long,
|
||||||
|
val expires_in: Long,
|
||||||
|
val refresh_token: String?) {
|
||||||
|
|
||||||
|
// Access token lives 1 day
|
||||||
|
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.network
|
|||||||
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Producer
|
import rx.Producer
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -98,6 +99,8 @@ fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListene
|
|||||||
return progressClient.newCall(request)
|
return progressClient.newCall(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MediaType.Companion.jsonType() : MediaType = "application/json; charset=utf-8".toMediaTypeOrNull()!!
|
||||||
|
|
||||||
fun Response.consumeBody(): String? {
|
fun Response.consumeBody(): String? {
|
||||||
use {
|
use {
|
||||||
if (it.code != 200) throw Exception("HTTP error ${it.code}")
|
if (it.code != 200) throw Exception("HTTP error ${it.code}")
|
||||||
|
@ -101,6 +101,7 @@ import jp.wasabeef.glide.transformations.MaskTransformation
|
|||||||
import kotlinx.android.synthetic.main.main_activity.*
|
import kotlinx.android.synthetic.main.main_activity.*
|
||||||
import kotlinx.android.synthetic.main.manga_details_controller.*
|
import kotlinx.android.synthetic.main.manga_details_controller.*
|
||||||
import kotlinx.android.synthetic.main.manga_header_item.*
|
import kotlinx.android.synthetic.main.manga_header_item.*
|
||||||
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -929,10 +930,12 @@ open class MangaDetailsController : BaseController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun trackRefreshError(error: Exception) {
|
fun trackRefreshError(error: Exception) {
|
||||||
|
Timber.e(error)
|
||||||
trackingBottomSheet?.onRefreshError(error)
|
trackingBottomSheet?.onRefreshError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trackSearchError(error: Exception) {
|
fun trackSearchError(error: Exception) {
|
||||||
|
Timber.e(error)
|
||||||
trackingBottomSheet?.onSearchResultsError(error)
|
trackingBottomSheet?.onSearchResultsError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@ -40,6 +41,11 @@ class AnilistLoginActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
private fun returnToSettings() {
|
private fun returnToSettings() {
|
||||||
finish()
|
finish()
|
||||||
|
|
||||||
|
@ -13,24 +13,27 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.login
|
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.password
|
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.show_password
|
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.username_label
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
abstract class LoginDialogPreference(private val usernameLabel: String? = null, bundle: Bundle? = null) :
|
abstract class LoginDialogPreference(
|
||||||
DialogController(bundle), CoroutineScope {
|
private val usernameLabel: String? = null,
|
||||||
|
bundle: Bundle? = null
|
||||||
|
) :
|
||||||
|
DialogController(bundle) {
|
||||||
|
|
||||||
var v: View? = null
|
var v: View? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val preferences: PreferencesHelper by injectLazy()
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
var requestSubscription: Subscription? = null
|
var requestSubscription: Subscription? = null
|
||||||
|
|
||||||
open var canLogout = false
|
open var canLogout = false
|
||||||
@ -49,7 +52,7 @@ abstract class LoginDialogPreference(private val usernameLabel: String? = null,
|
|||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun logout() { }
|
open fun logout() {}
|
||||||
|
|
||||||
fun onViewCreated(view: View) {
|
fun onViewCreated(view: View) {
|
||||||
v = view.apply {
|
v = view.apply {
|
||||||
@ -79,7 +82,6 @@ abstract class LoginDialogPreference(private val usernameLabel: String? = null,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||||
@ -90,11 +92,11 @@ abstract class LoginDialogPreference(private val usernameLabel: String? = null,
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun onDialogClosed() {
|
open fun onDialogClosed() {
|
||||||
|
scope.cancel()
|
||||||
requestSubscription?.unsubscribe()
|
requestSubscription?.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun checkLogin()
|
protected abstract fun checkLogin()
|
||||||
|
|
||||||
protected abstract fun setCredentialsOnView(view: View)
|
protected abstract fun setCredentialsOnView(view: View)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,9 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
import kotlinx.android.synthetic.main.pref_account_login.view.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
|
class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
|
||||||
LoginDialogPreference(usernameLabel, bundle) {
|
LoginDialogPreference(usernameLabel, bundle) {
|
||||||
@ -32,11 +29,7 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
|
|||||||
password.setText(service.getPassword())
|
password.setText(service.getPassword())
|
||||||
}
|
}
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext
|
|
||||||
get() = TODO("Not yet implemented")
|
|
||||||
|
|
||||||
override fun checkLogin() {
|
override fun checkLogin() {
|
||||||
requestSubscription?.unsubscribe()
|
|
||||||
|
|
||||||
v?.apply {
|
v?.apply {
|
||||||
if (username.text.isEmpty() || password.text.isEmpty())
|
if (username.text.isEmpty() || password.text.isEmpty())
|
||||||
@ -46,24 +39,30 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
|
|||||||
val user = username.text.toString()
|
val user = username.text.toString()
|
||||||
val pass = password.text.toString()
|
val pass = password.text.toString()
|
||||||
|
|
||||||
launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
withContext(Dispatchers.IO) {
|
val result = service.login(user, pass)
|
||||||
service.login(user, pass)
|
if (result) {
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
dialog?.dismiss()
|
dialog?.dismiss()
|
||||||
context.toast(R.string.login_success)
|
context.toast(R.string.login_success)
|
||||||
|
} else {
|
||||||
|
errorResult(this@apply)
|
||||||
}
|
}
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
login.progress = -1
|
errorResult(this@apply)
|
||||||
login.setText(R.string.unknown_error)
|
|
||||||
error.message?.let { context.toast(it) }
|
error.message?.let { context.toast(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun errorResult(view: View?) {
|
||||||
|
v?.apply {
|
||||||
|
login.progress = -1
|
||||||
|
login.setText(R.string.unknown_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun logout() {
|
override fun logout() {
|
||||||
if (service.isLogged) {
|
if (service.isLogged) {
|
||||||
service.logout()
|
service.logout()
|
||||||
|
Loading…
Reference in New Issue
Block a user