Update to Kotlin 1.4.10, coroutines 1.3.9, Kotlinter 3.0.2 (#594)

pull/3963/head
arkon 4 years ago committed by GitHub
parent f10f8c6b64
commit 9f15f25f43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -26,11 +26,11 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
import java.security.Security
@ReportsCrashes(
formUri = "https://collector.tracepot.com/e90773ff",
reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT,
buildConfigClass = BuildConfig::class,
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
formUri = "https://collector.tracepot.com/e90773ff",
reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT,
buildConfigClass = BuildConfig::class,
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
)
open class App : Application(), LifecycleObserver {

@ -75,7 +75,8 @@ class BackupCreateService : Service() {
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
PowerManager.PARTIAL_WAKE_LOCK,
"${javaClass.name}:WakeLock"
)
wakeLock.acquire()
}

@ -14,7 +14,7 @@ import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
Worker(context, workerParams) {
override fun doWork(): Result {
val preferences = Injekt.get<PreferencesHelper>()
@ -37,10 +37,13 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.backupInterval().getOrDefault()
if (interval > 0) {
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
interval.toLong(), TimeUnit.HOURS,
10, TimeUnit.MINUTES)
.addTag(TAG)
.build()
interval.toLong(),
TimeUnit.HOURS,
10,
TimeUnit.MINUTES
)
.addTag(TAG)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
} else {

@ -103,7 +103,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
private fun initParser(): Gson = when (version) {
1 -> GsonBuilder().create()
2 -> GsonBuilder()
2 ->
GsonBuilder()
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
@ -176,14 +177,14 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Create new file to place backup
val newFile = dir.createFile(Backup.getDefaultFilename())
?: throw Exception("Couldn't create backup file")
?: throw Exception("Couldn't create backup file")
newFile.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
@ -192,7 +193,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
return newFile.uri.toString()
} else {
val file = UniFile.fromUri(context, uri)
?: throw Exception("Couldn't create backup file")
?: throw Exception("Couldn't create backup file")
file.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
}
@ -514,7 +515,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @return [Manga], null if not found
*/
internal fun getMangaFromDatabase(manga: Manga): Manga? =
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
/**
* Returns list containing manga from library
@ -522,7 +523,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @return [Manga] from library
*/
internal fun getFavoriteManga(): List<Manga> =
databaseHelper.getFavoriteMangas().executeAsBlocking()
databaseHelper.getFavoriteMangas().executeAsBlocking()
/**
* Inserts manga and returns id
@ -530,7 +531,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @return id of [Manga], null if not found
*/
internal fun insertManga(manga: Manga): Long? =
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
/**
* Inserts list of chapters

@ -121,7 +121,9 @@ class BackupRestoreService : Service() {
super.onCreate()
startForeground(Notifications.ID_RESTORE_PROGRESS, progressNotification.build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
PowerManager.PARTIAL_WAKE_LOCK,
"BackupRestoreService:WakeLock"
)
wakeLock.acquire(TimeUnit.HOURS.toMillis(3))
}
@ -358,13 +360,13 @@ class BackupRestoreService : Service() {
*/
private val progressNotification by lazy {
NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_tachi)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setAutoCancel(false)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_tachi)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setAutoCancel(false)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
}
/**
@ -382,12 +384,20 @@ class BackupRestoreService : Service() {
* @param total the total progress.
*/
private fun showProgressNotification(current: Int, total: Int, title: String) {
notificationManager.notify(Notifications.ID_RESTORE_PROGRESS, progressNotification
notificationManager.notify(
Notifications.ID_RESTORE_PROGRESS,
progressNotification
.setContentTitle(title.chop(30))
.setContentText(getString(R.string.restoring_progress, restoreProgress,
totalAmount))
.setContentText(
getString(
R.string.restoring_progress,
restoreProgress,
totalAmount
)
)
.setProgress(total, current, false)
.build())
.build()
)
}
/**
@ -395,8 +405,14 @@ class BackupRestoreService : Service() {
*/
private fun showResultNotification(path: String?, file: String?) {
val content = mutableListOf(getString(R.string.restore_completed_content, restoreProgress
.toString(), errors.size.toString()))
val content = mutableListOf(
getString(
R.string.restore_completed_content,
restoreProgress
.toString(),
errors.size.toString()
)
)
val sourceMissingCount = sourcesMissing.distinct().size
if (sourceMissingCount > 0) {
val sources = sourcesMissing.distinct().filter { it.toLongOrNull() == null }
@ -408,20 +424,29 @@ class BackupRestoreService : Service() {
if (sources.isEmpty()) {
content.add(
resources.getQuantityString(
R.plurals.sources_missing, sourceMissingCount, sourceMissingCount
R.plurals.sources_missing,
sourceMissingCount,
sourceMissingCount
)
)
} else {
content.add(
resources.getQuantityString(
R.plurals.sources_missing, sourceMissingCount, sourceMissingCount
R.plurals.sources_missing,
sourceMissingCount,
sourceMissingCount
) + ": " + missingSourcesString
)
}
}
if (lincensedManga > 0)
content.add(resources.getQuantityString(R.plurals.licensed_manga, lincensedManga,
lincensedManga))
content.add(
resources.getQuantityString(
R.plurals.licensed_manga,
lincensedManga,
lincensedManga
)
)
val trackingErrors = trackingErrors.distinct()
if (trackingErrors.isNotEmpty()) {
val trackingErrorsString = trackingErrors.distinct().joinToString("\n")
@ -433,15 +458,21 @@ class BackupRestoreService : Service() {
val restoreString = content.joinToString("\n")
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.restore_completed))
.setContentText(restoreString)
.setStyle(NotificationCompat.BigTextStyle().bigText(restoreString))
.setSmallIcon(R.drawable.ic_tachi)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.setContentTitle(getString(R.string.restore_completed))
.setContentText(restoreString)
.setStyle(NotificationCompat.BigTextStyle().bigText(restoreString))
.setSmallIcon(R.drawable.ic_tachi)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
if (errors.size > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) {
resultNotification.addAction(R.drawable.ic_close_24dp, getString(R.string
.view_all_errors), getErrorLogIntent(path, file))
resultNotification.addAction(
R.drawable.ic_close_24dp,
getString(
R.string
.view_all_errors
),
getErrorLogIntent(path, file)
)
}
notificationManager.notify(Notifications.ID_RESTORE_COMPLETE, resultNotification.build())
}
@ -451,11 +482,11 @@ class BackupRestoreService : Service() {
*/
private fun showErrorNotification(errorMessage: String) {
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.restore_error))
.setContentText(errorMessage)
.setSmallIcon(R.drawable.ic_error_24dp)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.md_red_500))
.setContentTitle(getString(R.string.restore_error))
.setContentText(errorMessage)
.setSmallIcon(R.drawable.ic_error_24dp)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(ContextCompat.getColor(this, R.color.md_red_500))
notificationManager.notify(Notifications.ID_RESTORE_ERROR, resultNotification.build())
}
@ -477,7 +508,7 @@ class BackupRestoreService : Service() {
* @return true if the service is running, false otherwise.
*/
fun isRunning(context: Context): Boolean =
context.isServiceRunning(BackupRestoreService::class.java)
context.isServiceRunning(BackupRestoreService::class.java)
/**
* Starts a service to restore a backup from Json

@ -46,10 +46,12 @@ class ChapterCache(private val context: Context) {
private val gson: Gson by injectLazy()
/** Cache class used for cache management. */
private val diskCache = DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT,
PARAMETER_CACHE_SIZE)
private val diskCache = DiskLruCache.open(
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT,
PARAMETER_CACHE_SIZE
)
/**
* Returns directory of cache.

@ -83,7 +83,8 @@ class CoverCache(val context: Context) {
withContext(Dispatchers.Main) {
context.toast(
context.getString(
R.string.deleted_, Formatter.formatFileSize(context, deletedSize)
R.string.deleted_,
Formatter.formatFileSize(context, deletedSize)
)
)
}
@ -111,7 +112,8 @@ class CoverCache(val context: Context) {
withContext(Dispatchers.Main) {
context.toast(
context.getString(
R.string.deleted_, Formatter.formatFileSize(context, deletedSize)
R.string.deleted_,
Formatter.formatFileSize(context, deletedSize)
)
)
}

@ -30,8 +30,13 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces.
*/
open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries,
HistoryQueries, SearchMetadataQueries {
MangaQueries,
ChapterQueries,
TrackQueries,
CategoryQueries,
MangaCategoryQueries,
HistoryQueries,
SearchMetadataQueries {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME)
@ -39,15 +44,15 @@ MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQuerie
.build()
override val db = DefaultStorIOSQLite.builder()
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
.addTypeMapping(Manga::class.java, MangaTypeMapping())
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
.addTypeMapping(Track::class.java, TrackTypeMapping())
.addTypeMapping(Category::class.java, CategoryTypeMapping())
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
.addTypeMapping(History::class.java, HistoryTypeMapping())
.build()
.sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration))
.addTypeMapping(Manga::class.java, MangaTypeMapping())
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
.addTypeMapping(Track::class.java, TrackTypeMapping())
.addTypeMapping(Category::class.java, CategoryTypeMapping())
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
.addTypeMapping(History::class.java, HistoryTypeMapping())
.build()
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)

@ -44,8 +44,10 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(ChapterTable.sourceOrderUpdateQuery)
// Fix kissmanga covers after supporting cloudflare
db.execSQL("""UPDATE mangas SET thumbnail_url =
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""")
db.execSQL(
"""UPDATE mangas SET thumbnail_url =
REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4"""
)
}
if (oldVersion < 3) {
// Initialize history tables

@ -19,22 +19,22 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
CategoryPutResolver(),
CategoryGetResolver(),
CategoryDeleteResolver()
CategoryPutResolver(),
CategoryGetResolver(),
CategoryDeleteResolver()
)
class CategoryPutResolver : DefaultPutResolver<Category>() {
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Category) = ContentValues(4).apply {
put(COL_ID, obj.id)
@ -76,8 +76,8 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

@ -27,22 +27,22 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
ChapterPutResolver(),
ChapterGetResolver(),
ChapterDeleteResolver()
ChapterPutResolver(),
ChapterGetResolver(),
ChapterDeleteResolver()
)
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply {
put(COL_ID, obj.id)
@ -83,8 +83,8 @@ class ChapterGetResolver : DefaultGetResolver<Chapter>() {
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

@ -18,22 +18,22 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
class HistoryTypeMapping : SQLiteTypeMapping<History>(
HistoryPutResolver(),
HistoryGetResolver(),
HistoryDeleteResolver()
HistoryPutResolver(),
HistoryGetResolver(),
HistoryDeleteResolver()
)
open class HistoryPutResolver : DefaultPutResolver<History>() {
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: History) = ContentValues(4).apply {
put(COL_ID, obj.id)
@ -56,8 +56,8 @@ class HistoryGetResolver : DefaultGetResolver<History>() {
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

@ -16,22 +16,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
MangaCategoryPutResolver(),
MangaCategoryGetResolver(),
MangaCategoryDeleteResolver()
MangaCategoryPutResolver(),
MangaCategoryGetResolver(),
MangaCategoryDeleteResolver()
)
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply {
put(COL_ID, obj.id)
@ -52,8 +52,8 @@ class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

@ -31,22 +31,22 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
MangaPutResolver(),
MangaGetResolver(),
MangaDeleteResolver()
MangaPutResolver(),
MangaGetResolver(),
MangaDeleteResolver()
)
class MangaPutResolver : DefaultPutResolver<Manga>() {
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Manga) = ContentValues(15).apply {
put(COL_ID, obj.id)
@ -101,8 +101,8 @@ open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

@ -25,22 +25,22 @@ import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
class TrackTypeMapping : SQLiteTypeMapping<Track>(
TrackPutResolver(),
TrackGetResolver(),
TrackDeleteResolver()
TrackPutResolver(),
TrackGetResolver(),
TrackDeleteResolver()
)
class TrackPutResolver : DefaultPutResolver<Track>() {
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Track) = ContentValues(10).apply {
put(COL_ID, obj.id)
@ -77,8 +77,8 @@ class TrackGetResolver : DefaultGetResolver<Track>() {
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

@ -22,8 +22,8 @@ class LibraryManga : MangaImpl() {
fun createHide(categoryId: Int, title: String): LibraryManga =
createBlank(categoryId).apply {
this.title = title
status = -1
}
this.title = title
status = -1
}
}
}

@ -73,13 +73,14 @@ interface Manga : SManga {
genre?.split(",")?.map { it.trim().toLowerCase(Locale.US) } ?: emptyList()
return if (currentTags.any { tag -> tag.startsWith("japanese") || isMangaTag(tag) }) {
TYPE_MANGA
} else if (currentTags.any { tag -> tag.startsWith("english") || isComicTag(tag) } || isComicSource(
sourceName
)) {
} else if (currentTags.any { tag -> tag.startsWith("english") || isComicTag(tag) } ||
isComicSource(sourceName)
) {
TYPE_COMIC
} else if (currentTags.any { tag ->
tag.startsWith("chinese") || isManhuaTag(tag)
} || sourceName.contains("manhua", true)) {
tag.startsWith("chinese") || isManhuaTag(tag)
} || sourceName.contains("manhua", true)
) {
TYPE_MANHUA
} else if (currentTags.any { tag -> isManhwaTag(tag) } || isWebtoonSource(sourceName)) {
TYPE_MANHWA

@ -74,7 +74,8 @@ open class MangaImpl : Manga {
override fun copyFrom(other: SManga) {
if (other is MangaImpl && other::ogTitle.isInitialized &&
!other.title.isBlank() && other.ogTitle != ogTitle) {
!other.title.isBlank() && other.ogTitle != ogTitle
) {
val oldTitle = ogTitle
title = other.ogTitle
val db: DownloadManager by injectLazy()

@ -10,20 +10,24 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
interface CategoryQueries : DbProvider {
fun getCategories() = db.get()
.listOfObjects(Category::class.java)
.withQuery(Query.builder()
.table(CategoryTable.TABLE)
.orderBy(CategoryTable.COL_ORDER)
.build())
.prepare()
.listOfObjects(Category::class.java)
.withQuery(
Query.builder()
.table(CategoryTable.TABLE)
.orderBy(CategoryTable.COL_ORDER)
.build()
)
.prepare()
fun getCategoriesForManga(manga: Manga) = db.get()
.listOfObjects(Category::class.java)
.withQuery(RawQuery.builder()
.query(getCategoriesForMangaQuery())
.args(manga.id)
.build())
.prepare()
.listOfObjects(Category::class.java)
.withQuery(
RawQuery.builder()
.query(getCategoriesForMangaQuery())
.args(manga.id)
.build()
)
.prepare()
fun insertCategory(category: Category) = db.put().`object`(category).prepare()

@ -19,68 +19,82 @@ import java.util.Date
interface ChapterQueries : DbProvider {
fun getChapters(manga: Manga) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id)
.build())
.prepare()
.listOfObjects(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id)
.build()
)
.prepare()
fun getRecentChapters(date: Date) = db.get()
.listOfObjects(MangaChapter::class.java)
.withQuery(RawQuery.builder()
.query(getRecentsQuery())
.args(date.time)
.observesTables(ChapterTable.TABLE)
.build())
.withGetResolver(MangaChapterGetResolver.INSTANCE)
.prepare()
.listOfObjects(MangaChapter::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentsQuery())
.args(date.time)
.observesTables(ChapterTable.TABLE)
.build()
)
.withGetResolver(MangaChapterGetResolver.INSTANCE)
.prepare()
fun getUpdatedManga(date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder()
.query(getRecentsQueryDistinct(search.sqLite, endless))
.args(date.time)
.observesTables(ChapterTable.TABLE)
.build())
.withQuery(
RawQuery.builder()
.query(getRecentsQueryDistinct(search.sqLite, endless))
.args(date.time)
.observesTables(ChapterTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
fun getChapter(id: Long) = db.get()
.`object`(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(id)
.build())
.prepare()
.`object`(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(id)
.build()
)
.prepare()
fun getChapter(url: String) = db.get()
.`object`(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build())
.prepare()
.`object`(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build()
)
.prepare()
fun getChapters(url: String) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build())
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build()
)
.prepare()
fun getChapter(url: String, mangaId: Long) = db.get()
.`object`(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(url, mangaId)
.build())
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(url, mangaId)
.build()
)
.prepare()
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
@ -92,22 +106,22 @@ interface ChapterQueries : DbProvider {
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
fun updateChaptersBackup(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterBackupPutResolver())
.prepare()
.objects(chapters)
.withPutResolver(ChapterBackupPutResolver())
.prepare()
fun updateChapterProgress(chapter: Chapter) = db.put()
.`object`(chapter)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
.`object`(chapter)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
.objects(chapters)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
fun fixChaptersSourceOrder(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterSourceOrderPutResolver())
.prepare()
.objects(chapters)
.withPutResolver(ChapterSourceOrderPutResolver())
.prepare()
}

@ -26,14 +26,16 @@ interface HistoryQueries : DbProvider {
* @offset offset the db by
*/
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder()
.query(getRecentMangasQuery(offset, search.sqLite))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build())
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentMangasQuery(offset, search.sqLite))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
/**
* Returns history of recent manga containing last read chapter in 25s
@ -42,11 +44,13 @@ interface HistoryQueries : DbProvider {
*/
fun getRecentlyAdded(date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder()
.query(getRecentAdditionsQuery(search.sqLite, endless))
.args(date.time)
.observesTables(MangaTable.TABLE)
.build())
.withQuery(
RawQuery.builder()
.query(getRecentAdditionsQuery(search.sqLite, endless))
.args(date.time)
.observesTables(MangaTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
@ -57,11 +61,13 @@ interface HistoryQueries : DbProvider {
*/
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder()
.query(getRecentMangasLimitQuery(limit, search.sqLite))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build())
.withQuery(
RawQuery.builder()
.query(getRecentMangasLimitQuery(limit, search.sqLite))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
@ -72,31 +78,37 @@ interface HistoryQueries : DbProvider {
*/
fun getRecentsWithUnread(date: Date, search: String = "", endless: Boolean) = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(RawQuery.builder()
.query(getRecentReadWithUnreadChapters(search.sqLite, endless))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build())
.withQuery(
RawQuery.builder()
.query(getRecentReadWithUnreadChapters(search.sqLite, endless))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
fun getHistoryByMangaId(mangaId: Long) = db.get()
.listOfObjects(History::class.java)
.withQuery(RawQuery.builder()
.query(getHistoryByMangaId())
.args(mangaId)
.observesTables(HistoryTable.TABLE)
.build())
.prepare()
.listOfObjects(History::class.java)
.withQuery(
RawQuery.builder()
.query(getHistoryByMangaId())
.args(mangaId)
.observesTables(HistoryTable.TABLE)
.build()
)
.prepare()
fun getHistoryByChapterUrl(chapterUrl: String) = db.get()
.`object`(History::class.java)
.withQuery(RawQuery.builder()
.query(getHistoryByChapterUrl())
.args(chapterUrl)
.observesTables(HistoryTable.TABLE)
.build())
.prepare()
.`object`(History::class.java)
.withQuery(
RawQuery.builder()
.query(getHistoryByChapterUrl())
.args(chapterUrl)
.observesTables(HistoryTable.TABLE)
.build()
)
.prepare()
/**
* Updates the history last read.
@ -104,9 +116,9 @@ interface HistoryQueries : DbProvider {
* @param history history object
*/
fun updateHistoryLastRead(history: History) = db.put()
.`object`(history)
.withPutResolver(HistoryLastReadPutResolver())
.prepare()
.`object`(history)
.withPutResolver(HistoryLastReadPutResolver())
.prepare()
/**
* Updates the history last read.
@ -114,21 +126,25 @@ interface HistoryQueries : DbProvider {
* @param historyList history object list
*/
fun updateHistoryLastRead(historyList: List<History>) = db.put()
.objects(historyList)
.withPutResolver(HistoryLastReadPutResolver())
.prepare()
.objects(historyList)
.withPutResolver(HistoryLastReadPutResolver())
.prepare()
fun deleteHistory() = db.delete()
.byQuery(DeleteQuery.builder()
.table(HistoryTable.TABLE)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(HistoryTable.TABLE)
.build()
)
.prepare()
fun deleteHistoryNoLastRead() = db.delete()
.byQuery(DeleteQuery.builder()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_LAST_READ} = ?")
.whereArgs(0)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_LAST_READ} = ?")
.whereArgs(0)
.build()
)
.prepare()
}

@ -15,12 +15,14 @@ interface MangaCategoryQueries : DbProvider {
fun insertMangasCategories(mangasCategories: List<MangaCategory>) = db.put().objects(mangasCategories).prepare()
fun deleteOldMangasCategories(mangas: List<Manga>) = db.delete()
.byQuery(DeleteQuery.builder()
.table(MangaCategoryTable.TABLE)
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
.whereArgs(*mangas.map { it.id }.toTypedArray())
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(MangaCategoryTable.TABLE)
.where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})")
.whereArgs(*mangas.map { it.id }.toTypedArray())
.build()
)
.prepare()
fun setMangaCategories(mangasCategories: List<MangaCategory>, mangas: List<Manga>) {
db.inTransaction {

@ -22,67 +22,77 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable
interface MangaQueries : DbProvider {
fun getMangas() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(Query.builder()
.table(MangaTable.TABLE)
.build())
.prepare()
.listOfObjects(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.build()
)
.prepare()
fun getLibraryMangas() = db.get()
.listOfObjects(LibraryManga::class.java)
.withQuery(RawQuery.builder()
.query(libraryQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
.build())
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare()
.listOfObjects(LibraryManga::class.java)
.withQuery(
RawQuery.builder()
.query(libraryQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
.build()
)
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare()
fun getFavoriteMangas() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(1)
.orderBy(MangaTable.COL_TITLE)
.build())
.prepare()
.listOfObjects(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(1)
.orderBy(MangaTable.COL_TITLE)
.build()
)
.prepare()
fun getManga(url: String, sourceId: Long) = db.get()
.`object`(Manga::class.java)
.withQuery(Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
.whereArgs(url, sourceId)
.build())
.prepare()
.`object`(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
.whereArgs(url, sourceId)
.build()
)
.prepare()
fun getManga(id: Long) = db.get()
.`object`(Manga::class.java)
.withQuery(Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(id)
.build())
.prepare()
.`object`(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(id)
.build()
)
.prepare()
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
fun updateFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver())
.prepare()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver())
.prepare()
fun updateLastUpdated(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver())
.prepare()
.`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver())
.prepare()
fun updateMangaFavorite(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFavoritePutResolver())
.prepare()
.`object`(manga)
.withPutResolver(MangaFavoritePutResolver())
.prepare()
fun updateMangaAdded(manga: Manga) = db.put()
.`object`(manga)
@ -90,14 +100,14 @@ interface MangaQueries : DbProvider {
.prepare()
fun updateMangaViewer(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaViewerPutResolver())
.prepare()
.`object`(manga)
.withPutResolver(MangaViewerPutResolver())
.prepare()
fun updateMangaTitle(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaTitlePutResolver())
.prepare()
.`object`(manga)
.withPutResolver(MangaTitlePutResolver())
.prepare()
fun updateMangaInfo(manga: Manga) = db.put()
.`object`(manga)
@ -114,27 +124,33 @@ interface MangaQueries : DbProvider {
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
fun deleteMangasNotInLibrary() = db.delete()
.byQuery(DeleteQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(0)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(0)
.build()
)
.prepare()
fun deleteMangas() = db.delete()
.byQuery(DeleteQuery.builder()
.table(MangaTable.TABLE)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE)
.build()
)
.prepare()
fun getLastReadManga() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder()
.query(getLastReadMangaQuery())
.observesTables(MangaTable.TABLE)
.build())
.prepare()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getLastReadMangaQuery())
.observesTables(MangaTable.TABLE)
.build()
)
.prepare()
fun getTotalChapterManga() = db.get().listOfObjects(Manga::class.java)
.withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare()
.withQuery(RawQuery.builder().query(getTotalChapterMangaQuery()).observesTables(MangaTable.TABLE).build()).prepare()
}

@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
/**
* Query to get the manga from the library, with their categories and unread count.
*/
val libraryQuery = """
val libraryQuery =
"""
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.hasread, 0) AS ${Manga.COL_HAS_READ}
@ -40,7 +41,8 @@ val libraryQuery = """
/**
* Query to get the recent chapters of manga from the library up to a date.
*/
fun getRecentsQuery() = """
fun getRecentsQuery() =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1
@ -52,7 +54,8 @@ fun getRecentsQuery() = """
/**
* Query to get the recently added manga
*/
fun getRecentAdditionsQuery(search: String, endless: Boolean) = """
fun getRecentAdditionsQuery(search: String, endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE}
WHERE ${Manga.COL_FAVORITE} = 1
AND ${Manga.COL_DATE_ADDED} > ?
@ -64,7 +67,8 @@ fun getRecentAdditionsQuery(search: String, endless: Boolean) = """
/**
* Query to get the manga with recently uploaded chapters
*/
fun getRecentsQueryDistinct(search: String, endless: Boolean) = """
fun getRecentsQueryDistinct(search: String, endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
@ -92,7 +96,8 @@ fun getRecentsQueryDistinct(search: String, endless: Boolean) = """
* and are read after the given time period
* @return return limit is 25
*/
fun getRecentMangasQuery(offset: Int = 0, search: String = "") = """
fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
@ -118,7 +123,8 @@ fun getRecentMangasQuery(offset: Int = 0, search: String = "") = """
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period
*/
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = """
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
@ -145,7 +151,8 @@ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = """
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period
*/
fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) = """
fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM (
SELECT ${Manga.TABLE}.*
@ -178,7 +185,8 @@ fun getRecentReadWithUnreadChapters(search: String = "", endless: Boolean) = """
${if (endless) "" else "LIMIT 8"}
"""
fun getHistoryByMangaId() = """
fun getHistoryByMangaId() =
"""
SELECT ${History.TABLE}.*
FROM ${History.TABLE}
JOIN ${Chapter.TABLE}
@ -186,7 +194,8 @@ fun getHistoryByMangaId() = """
WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
"""
fun getHistoryByChapterUrl() = """
fun getHistoryByChapterUrl() =
"""
SELECT ${History.TABLE}.*
FROM ${History.TABLE}
JOIN ${Chapter.TABLE}
@ -194,7 +203,8 @@ fun getHistoryByChapterUrl() = """
WHERE ${Chapter.TABLE}.${Chapter.COL_URL} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
"""
fun getLastReadMangaQuery() = """
fun getLastReadMangaQuery() =
"""
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
@ -206,7 +216,8 @@ fun getLastReadMangaQuery() = """
ORDER BY max DESC
"""
fun getTotalChapterMangaQuery() = """
fun getTotalChapterMangaQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
@ -218,7 +229,8 @@ fun getTotalChapterMangaQuery() = """
/**
* Query to get the categories for a manga.
*/
fun getCategoriesForMangaQuery() = """
fun getCategoriesForMangaQuery() =
"""
SELECT ${Category.TABLE}.* FROM ${Category.TABLE}
JOIN ${MangaCategory.TABLE} ON ${Category.TABLE}.${Category.COL_ID} =
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}

@ -10,35 +10,43 @@ interface SearchMetadataQueries : DbProvider {
fun getSearchMetadataForManga(mangaId: Long) = db.get()
.`object`(SearchMetadata::class.java)
.withQuery(Query.builder()
.table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build())
.withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build()
)
.prepare()
fun getSearchMetadata() = db.get()
.listOfObjects(SearchMetadata::class.java)
.withQuery(Query.builder()
.table(SearchMetadataTable.TABLE)
.build())
.withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE)
.build()
)
.prepare()
fun getSearchMetadataByIndexedExtra(extra: String) = db.get()
.listOfObjects(SearchMetadata::class.java)
.withQuery(Query.builder()
.table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
.whereArgs(extra)
.build())
.withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
.whereArgs(extra)
.build()
)
.prepare()
fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare()
fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare()
fun deleteAllSearchMetadata() = db.delete().byQuery(DeleteQuery.builder()
.table(SearchMetadataTable.TABLE)
.build())
fun deleteAllSearchMetadata() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchMetadataTable.TABLE)
.build()
)
.prepare()
}

@ -11,23 +11,27 @@ import eu.kanade.tachiyomi.data.track.TrackService
interface TrackQueries : DbProvider {
fun getTracks(manga: Manga) = db.get()
.listOfObjects(Track::class.java)
.withQuery(Query.builder()
.table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id)
.build())
.prepare()
.listOfObjects(Track::class.java)
.withQuery(
Query.builder()
.table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id)
.build()
)
.prepare()
fun insertTrack(track: Track) = db.put().`object`(track).prepare()
fun insertTracks(tracks: List<Track>) = db.put().objects(tracks).prepare()
fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete()
.byQuery(DeleteQuery.builder()
.table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
.whereArgs(manga.id, sync.id)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ? AND ${TrackTable.COL_SYNC_ID} = ?")
.whereArgs(manga.id, sync.id)
.build()
)
.prepare()
}

@ -20,10 +20,10 @@ class ChapterBackupPutResolver : PutResolver<Chapter>() {
}
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(chapter.url)
.build()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(chapter.url)
.build()
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
put(ChapterTable.COL_READ, chapter.read)

@ -20,10 +20,10 @@ class ChapterProgressPutResolver : PutResolver<Chapter>() {
}
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(chapter.id)
.build()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(chapter.id)
.build()
fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply {
put(ChapterTable.COL_READ, chapter.read)

@ -20,10 +20,10 @@ class ChapterSourceOrderPutResolver : PutResolver<Chapter>() {
}
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(chapter.url, chapter.manga_id)
.build()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(chapter.url, chapter.manga_id)
.build()
fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply {
put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order)

@ -19,11 +19,13 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(history)
val cursor = db.lowLevel().query(Query.builder()
val cursor = db.lowLevel().query(
Query.builder()
.table(updateQuery.table())
.where(updateQuery.where())
.whereArgs(updateQuery.whereArgs())
.build())
.build()
)
val putResult: PutResult
@ -48,10 +50,10 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
* @param obj history object
*/
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
.whereArgs(obj.chapter_id)
.build()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
.whereArgs(obj.chapter_id)
.build()
/**
* Create content query

@ -42,8 +42,8 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
val chapter =
if (!cursor.isNull(cursor.getColumnIndex(ChapterTable.COL_MANGA_ID))) chapterResolver
.mapFromCursor(
cursor
) else ChapterImpl()
cursor
) else ChapterImpl()
// Get history object
val history =

@ -20,10 +20,10 @@ class MangaFavoritePutResolver : PutResolver<Manga>() {
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_FAVORITE, manga.favorite)

@ -20,10 +20,10 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)

@ -20,10 +20,10 @@ class MangaLastUpdatedPutResolver : PutResolver<Manga>() {
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_LAST_UPDATE, manga.last_update)

@ -20,10 +20,10 @@ class MangaTitlePutResolver : PutResolver<Manga>() {
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_TITLE, manga.title)

@ -20,10 +20,10 @@ class MangaViewerPutResolver : PutResolver<Manga>() {
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_VIEWER, manga.viewer)

@ -15,7 +15,8 @@ object CategoryTable {
const val COL_MANGA_ORDER = "manga_order"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_NAME TEXT NOT NULL,
$COL_ORDER INTEGER NOT NULL,

@ -31,7 +31,8 @@ object ChapterTable {
const val COL_SOURCE_ORDER = "source_order"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL,
$COL_URL TEXT NOT NULL,
@ -54,7 +55,7 @@ object ChapterTable {
val createUnreadChaptersIndexQuery: String
get() = "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " +
"WHERE $COL_READ = 0"
"WHERE $COL_READ = 0"
val sourceOrderUpdateQuery: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0"

@ -31,7 +31,8 @@ object HistoryTable {
* query to create history table
*/
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_CHAPTER_ID INTEGER NOT NULL UNIQUE,
$COL_LAST_READ LONG,

@ -11,7 +11,8 @@ object MangaCategoryTable {
const val COL_CATEGORY_ID = "category_id"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL,
$COL_CATEGORY_ID INTEGER NOT NULL,

@ -45,7 +45,8 @@ object MangaTable {
const val COL_DATE_ADDED = "date_added"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_SOURCE INTEGER NOT NULL,
$COL_URL TEXT NOT NULL,
@ -71,7 +72,7 @@ object MangaTable {
val createLibraryIndexQuery: String
get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " +
"WHERE $COL_FAVORITE = 1"
"WHERE $COL_FAVORITE = 1"
val addHideTitle: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_HIDE_TITLE INTEGER DEFAULT 0"

@ -15,7 +15,8 @@ object SearchMetadataTable {
// Insane foreign, primary key to avoid touch manga table
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
$COL_UPLOADER TEXT,
$COL_EXTRA TEXT NOT NULL,

@ -27,7 +27,8 @@ object TrackTable {
const val COL_TRACKING_URL = "remote_url"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL,
$COL_SYNC_ID INTEGER NOT NULL,

@ -251,7 +251,9 @@ class DownloadManager(val context: Context) {
queue.remove(chapters)
val chapterDirs =
provider.findChapterDirs(chapters, manga, source) + provider.findTempChapterDirs(
chapters, manga, source
chapters,
manga,
source
)
chapterDirs.forEach { it.delete() }
cache.removeChapters(chapters, manga)

@ -24,7 +24,7 @@ internal class DownloadNotifier(private val context: Context) {
*/
private val notification by lazy {
NotificationCompat.Builder(context, Notifications.CHANNEL_DOWNLOADER)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
}
/**
@ -78,16 +78,21 @@ internal class DownloadNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
isDownloading = true
// Pause action
addAction(R.drawable.ic_pause_24dp,
addAction(
R.drawable.ic_pause_24dp,
context.getString(R.string.pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context))
NotificationReceiver.pauseDownloadsPendingBroadcast(context)
)
}
if (download != null) {
val title = download.manga.title.chop(15)
val quotedTitle = Pattern.quote(title)
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*"
.toRegex(RegexOption.IGNORE_CASE), "")
val chapter = download.chapter.name.replaceFirst(
"$quotedTitle[\\s]*[-]*[\\s]*"
.toRegex(RegexOption.IGNORE_CASE),
""
)
setContentTitle("$title - $chapter".chop(30))
setContentText(
context.getString(R.string.downloading)
@ -124,17 +129,21 @@ internal class DownloadNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
isDownloading = true
// Pause action
addAction(R.drawable.ic_pause_24dp,
context.getString(R.string.pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context))
addAction(
R.drawable.ic_pause_24dp,
context.getString(R.string.pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context)
)
}
val title = download.manga.title.chop(15)
val quotedTitle = Pattern.quote(title)
val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "")
setContentTitle("$title - $chapter".chop(30))
setContentText(context.getString(R.string.downloading_progress)
.format(download.downloadedImages, download.pages!!.size))
setContentText(
context.getString(R.string.downloading_progress)
.format(download.downloadedImages, download.pages!!.size)
)
setStyle(null)
setProgress(download.pages!!.size, download.downloadedImages, false)
}

@ -161,13 +161,17 @@ class DownloadService : Service() {
*/
private fun listenNetworkChanges() {
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ state -> onNetworkStateChanged(state)
}, {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ state ->
onNetworkStateChanged(state)
},
{
toast(R.string.could_not_download_chapter_can_try_again)
stopSelf()
})
}
)
}
/**

@ -80,9 +80,9 @@ class DownloadStore(
*/
fun restore(): List<Download> {
val objs = preferences.all
.mapNotNull { it.value as? String }
.mapNotNull { deserialize(it) }
.sortedBy { it.order }
.mapNotNull { it.value as? String }
.mapNotNull { deserialize(it) }
.sortedBy { it.order }
val downloads = mutableListOf<Download>()
if (objs.isNotEmpty()) {

@ -288,23 +288,23 @@ class Downloader(
val pageListObservable = if (download.pages == null) {
// Pull page list from network and add them to download object
download.source.fetchPageList(download.chapter).doOnNext { pages ->
if (pages.isEmpty()) {
throw Exception("Page list is empty")
}
download.pages = pages
if (pages.isEmpty()) {
throw Exception("Page list is empty")
}
download.pages = pages
}
} else {
// Or if the page list already exists, start from the file
Observable.just(download.pages!!)
}
pageListObservable.doOnNext { _ ->
// Delete all temporary (unfinished) files
tmpDir.listFiles()?.filter { it.name!!.endsWith(".tmp") }?.forEach { it.delete() }
// Delete all temporary (unfinished) files
tmpDir.listFiles()?.filter { it.name!!.endsWith(".tmp") }?.forEach { it.delete() }
download.downloadedImages = 0
download.status = Download.DOWNLOADING
}
download.downloadedImages = 0
download.status = Download.DOWNLOADING
}
// Get all the URLs to the source images, fetch pages if necessary
.flatMap { download.source.fetchAllImageUrlsFromPageList(it) }
// Start downloading images, consider we can have downloaded images already
@ -447,7 +447,7 @@ class Downloader(
private fun getImageExtension(response: Response, file: UniFile): String {
// Read content type if available.
val mime = response.body?.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" }
// Else guess from the uri.
// Else guess from the uri.
?: context.contentResolver.getType(file.uri)
// Else read magic numbers.
?: ImageUtil.findImageType { file.openInputStream() }?.mime

@ -13,7 +13,7 @@ class DownloadQueue(
private val store: DownloadStore,
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()
) :
List<Download> by queue {
List<Download> by queue {
private val statusSubject = PublishSubject.create<Download>()
@ -80,8 +80,8 @@ List<Download> by queue {
fun getStatusObservable(): Observable<Download> = statusSubject.onBackpressureBuffer()
fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer()
.startWith(Unit)
.map { this }
.startWith(Unit)
.map { this }
private fun setPagesFor(download: Download) {
if (download.status == Download.DOWNLOADING) {
@ -105,23 +105,23 @@ List<Download> by queue {
fun getProgressObservable(): Observable<Download> {
return statusSubject.onBackpressureBuffer()
.startWith(getActiveDownloads())
.flatMap { download ->
if (download.status == Download.DOWNLOADING) {
val pageStatusSubject = PublishSubject.create<Int>()
setPagesSubject(download.pages, pageStatusSubject)
downloadListeners.forEach { it.updateDownload(download) }
return@flatMap pageStatusSubject
.onBackpressureBuffer()
.filter { it == Page.READY }
.map { download }
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) }
}
Observable.just(download)
.startWith(getActiveDownloads())
.flatMap { download ->
if (download.status == Download.DOWNLOADING) {
val pageStatusSubject = PublishSubject.create<Int>()
setPagesSubject(download.pages, pageStatusSubject)
downloadListeners.forEach { it.updateDownload(download) }
return@flatMap pageStatusSubject
.onBackpressureBuffer()
.filter { it == Page.READY }
.map { download }
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
setPagesSubject(download.pages, null)
downloadListeners.forEach { it.updateDownload(download) }
}
.filter { it.status == Download.DOWNLOADING }
Observable.just(download)
}
.filter { it.status == Download.DOWNLOADING }
}
private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) {

@ -20,7 +20,9 @@ class CoverViewTarget(
progress?.gone()
view.scaleType = ImageView.ScaleType.CENTER
val vector = VectorDrawableCompat.create(
view.context.resources, R.drawable.ic_broken_image_24dp, null
view.context.resources,
R.drawable.ic_broken_image_24dp,
null
)
vector?.setTint(view.context.getResourceColor(android.R.attr.textColorSecondary))
view.setImageDrawable(vector)

@ -70,7 +70,8 @@ class MangaFetcher : Fetcher<Manga> {
return fileLoader(coverFile)
}
val (_, body) = awaitGetCall(
manga, if (manga.favorite) {
manga,
if (manga.favorite) {
!options.networkCachePolicy.readEnabled
} else {
false

@ -29,7 +29,8 @@ class CustomMangaManager(val context: Context) {
val json = try {
Gson().fromJson(
Scanner(editJson).useDelimiter("\\Z").next(), JsonObject::class.java
Scanner(editJson).useDelimiter("\\Z").next(),
JsonObject::class.java
)
} catch (e: Exception) {
null
@ -83,7 +84,12 @@ class CustomMangaManager(val context: Context) {
fun Manga.toJson(): MangaJson {
return MangaJson(
id!!, title, author, artist, description, genre?.split(", ")?.toTypedArray()
id!!,
title,
author,
artist,
description,
genre?.split(", ")?.toTypedArray()
)
}

@ -15,7 +15,7 @@ import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
Worker(context, workerParams) {
override fun doWork(): Result {
LibraryUpdateService.start(context)
@ -37,16 +37,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
NetworkType.CONNECTED
val constraints = Constraints.Builder()
.setRequiredNetworkType(wifiRestriction)
.setRequiresCharging(acRestriction)
.build()
.setRequiredNetworkType(wifiRestriction)
.setRequiresCharging(acRestriction)
.build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), TimeUnit.HOURS,
10, TimeUnit.MINUTES)
.addTag(TAG)
.setConstraints(constraints)
.build()
interval.toLong(),
TimeUnit.HOURS,
10,
TimeUnit.MINUTES
)
.addTag(TAG)
.setConstraints(constraints)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
} else {

@ -131,60 +131,73 @@ class LibraryUpdateNotifier(private val context: Context) {
val manga = it.key
val chapters = it.value
val chapterNames = chapters.map { chapter -> chapter.name }
notifications.add(Pair(context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi)
try {
val request = GetRequest.Builder(context).data(manga)
.networkCachePolicy(CachePolicy.DISABLED)
.transformations(CircleCropTransformation())
.size(width = ICON_SIZE, height = ICON_SIZE).build()
Coil.imageLoader(context).execute(request).drawable?.let { drawable ->
setLargeIcon((drawable as BitmapDrawable).bitmap)
notifications.add(
Pair(
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi)
try {
val request = GetRequest.Builder(context).data(manga)
.networkCachePolicy(CachePolicy.DISABLED)
.transformations(CircleCropTransformation())
.size(width = ICON_SIZE, height = ICON_SIZE).build()
Coil.imageLoader(context).execute(request).drawable?.let { drawable ->
setLargeIcon((drawable as BitmapDrawable).bitmap)
}
} catch (e: Exception) {
}
} catch (e: Exception) {
}
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
setContentTitle(manga.title)
color = ContextCompat.getColor(context, R.color.colorAccent)
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
"${chapterNames.take(MAX_CHAPTERS - 1)
.joinToString(", ")}, " + context.resources.getQuantityString(
R.plurals.notification_and_n_more,
(chapterNames.size - (MAX_CHAPTERS - 1)),
(chapterNames.size - (MAX_CHAPTERS - 1))
)
} else chapterNames.joinToString(", ")
setContentText(chaptersNames)
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
setContentIntent(
NotificationReceiver.openChapterPendingActivity(
context, manga, chapters.first()
)
)
addAction(
R.drawable.ic_eye_24dp,
context.getString(R.string.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context, manga, chapters, Notifications.ID_NEW_CHAPTERS
)
)
addAction(
R.drawable.ic_book_24dp,
context.getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity(
context, manga, Notifications.ID_NEW_CHAPTERS
)
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
setContentTitle(manga.title)
color = ContextCompat.getColor(context, R.color.colorAccent)
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
"${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
context.resources.getQuantityString(
R.plurals.notification_and_n_more,
(chapterNames.size - (MAX_CHAPTERS - 1)),
(chapterNames.size - (MAX_CHAPTERS - 1))
)
} else chapterNames.joinToString(", ")
setContentText(chaptersNames)
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
setContentIntent(
NotificationReceiver.openChapterPendingActivity(
context,
manga,
chapters.first()
)
)
addAction(
R.drawable.ic_eye_24dp,
context.getString(R.string.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context,
manga,
chapters,
Notifications.ID_NEW_CHAPTERS
)
)
addAction(
R.drawable.ic_book_24dp,
context.getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity(
context,
manga,
Notifications.ID_NEW_CHAPTERS
)
)
setAutoCancel(true)
},
manga.id.hashCode()
)
setAutoCancel(true)
}, manga.id.hashCode()))
)
}
NotificationManagerCompat.from(context).apply {
notify(Notifications.ID_NEW_CHAPTERS,
notify(
Notifications.ID_NEW_CHAPTERS,
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi)
setLargeIcon(notificationBitmap)
@ -193,14 +206,18 @@ class LibraryUpdateNotifier(private val context: Context) {
if (updates.size > 1) {
setContentText(
context.resources.getQuantityString(
R.plurals.for_n_titles, updates.size, updates.size
R.plurals.for_n_titles,
updates.size,
updates.size
)
)
setStyle(
NotificationCompat.BigTextStyle()
.bigText(updates.keys.joinToString("\n") {
it.title.chop(45)
})
.bigText(
updates.keys.joinToString("\n") {
it.title.chop(45)
}
)
)
} else {
setContentText(updates.keys.first().title.chop(45))
@ -211,7 +228,8 @@ class LibraryUpdateNotifier(private val context: Context) {
setGroupSummary(true)
setContentIntent(getNotificationIntent())
setAutoCancel(true)
})
}
)
notifications.forEach {
notify(it.second, it.first)

@ -8,8 +8,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
object LibraryUpdateRanker {
val rankingScheme = listOf(
(this::lexicographicRanking)(),
(this::latestFirstRanking)())
(this::lexicographicRanking)(),
(this::latestFirstRanking)()
)
/**
* Provides a total ordering over all the Mangas.
@ -22,7 +23,7 @@ object LibraryUpdateRanker {
*/
fun latestFirstRanking(): Comparator<Manga> {
return Comparator { mangaFirst: Manga,
mangaSecond: Manga ->
mangaSecond: Manga ->
compareValues(mangaSecond.last_update, mangaFirst.last_update)
}
}
@ -35,7 +36,7 @@ object LibraryUpdateRanker {
*/
fun lexicographicRanking(): Comparator<Manga> {
return Comparator { mangaFirst: Manga,
mangaSecond: Manga ->
mangaSecond: Manga ->
compareValues(mangaFirst.title, mangaSecond.title)
}
}

@ -134,16 +134,18 @@ class LibraryUpdateService(
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
val savedMangasList = intent.getLongArrayExtra(KEY_MANGAS)?.asList()
val mangaList = (if (savedMangasList != null) {
val mangas = db.getLibraryMangas().executeAsBlocking().filter {
it.id in savedMangasList
}.distinctBy { it.id }
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
if (categoryId > -1) categoryIds.add(categoryId)
mangas
} else {
getMangaToUpdate(intent, target)
}).sortedWith(rankingScheme[selectedScheme])
val mangaList = (
if (savedMangasList != null) {
val mangas = db.getLibraryMangas().executeAsBlocking().filter {
it.id in savedMangasList
}.distinctBy { it.id }
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
if (categoryId > -1) categoryIds.add(categoryId)
mangas
} else {
getMangaToUpdate(intent, target)
}
).sortedWith(rankingScheme[selectedScheme])
// Update favorite manga. Destroy service when completed or in case of an error.
launchTarget(target, mangaList, startId)
return START_REDELIVER_INTENT
@ -157,7 +159,8 @@ class LibraryUpdateService(
super.onCreate()
notifier = LibraryUpdateNotifier(this)
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))
startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
@ -247,7 +250,9 @@ class LibraryUpdateService(
val hasDLs = try {
requestSemaphore.withPermit {
updateMangaInSource(
it.key, downloadNew, categoriesToDownload
it.key,
downloadNew,
categoriesToDownload
)
}
} catch (e: Exception) {
@ -351,13 +356,15 @@ class LibraryUpdateService(
var hasDownloads = false
while (count < mangaToUpdateMap[source]!!.size) {
val shouldDownload =
(downloadNew && (categoriesToDownload.isEmpty() || mangaToUpdateMap[source]!![count].category in categoriesToDownload || db.getCategoriesForManga(
mangaToUpdateMap[source]!![count]
).executeOnIO().any { (it.id ?: -1) in categoriesToDownload }))
if (updateMangaChapters(
mangaToUpdateMap[source]!![count], this.count.andIncrement, shouldDownload
)
) {
(
downloadNew && (
categoriesToDownload.isEmpty() ||
mangaToUpdateMap[source]!![count].category in categoriesToDownload ||
db.getCategoriesForManga(mangaToUpdateMap[source]!![count])
.executeOnIO().any { (it.id ?: -1) in categoriesToDownload }
)
)
if (updateMangaChapters(mangaToUpdateMap[source]!![count], this.count.andIncrement, shouldDownload)) {
hasDownloads = true
}
count++
@ -372,47 +379,47 @@ class LibraryUpdateService(
shouldDownload: Boolean
):
Boolean {
try {
var hasDownloads = false
if (job?.isCancelled == true) {
return false
}
notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
val fetchedChapters = withContext(Dispatchers.IO) {
source.fetchChapterList(manga).toBlocking().single()
} ?: emptyList()
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (shouldDownload) {
downloadChapters(manga, newChapters.first.sortedBy { it.chapter_number })
hasDownloads = true
}
newUpdates[manga] =
newChapters.first.sortedBy { it.chapter_number }.toTypedArray()
try {
var hasDownloads = false
if (job?.isCancelled == true) {
return false
}
if (deleteRemoved && newChapters.second.isNotEmpty()) {
val removedChapters = newChapters.second.filter {
downloadManager.isChapterDownloaded(it, manga)
notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
val fetchedChapters = withContext(Dispatchers.IO) {
source.fetchChapterList(manga).toBlocking().single()
} ?: emptyList()
if (fetchedChapters.isNotEmpty()) {
val newChapters = syncChaptersWithSource(db, fetchedChapters, manga, source)
if (newChapters.first.isNotEmpty()) {
if (shouldDownload) {
downloadChapters(manga, newChapters.first.sortedBy { it.chapter_number })
hasDownloads = true
}
newUpdates[manga] =
newChapters.first.sortedBy { it.chapter_number }.toTypedArray()
}
if (removedChapters.isNotEmpty()) {
downloadManager.deleteChapters(removedChapters, manga, source)
if (deleteRemoved && newChapters.second.isNotEmpty()) {
val removedChapters = newChapters.second.filter {
downloadManager.isChapterDownloaded(it, manga)
}
if (removedChapters.isNotEmpty()) {
downloadManager.deleteChapters(removedChapters, manga, source)
}
}
if (newChapters.first.size + newChapters.second.size > 0) listener?.onUpdateManga(
manga
)
}
if (newChapters.first.size + newChapters.second.size > 0) listener?.onUpdateManga(
manga
)
}
return hasDownloads
} catch (e: Exception) {
if (e !is CancellationException) {
failedUpdates[manga] = e.message
Timber.e("Failed updating: ${manga.title}: $e")
return hasDownloads
} catch (e: Exception) {
if (e !is CancellationException) {
failedUpdates[manga] = e.message
Timber.e("Failed updating: ${manga.title}: $e")
}
return false
}
return false
}
}
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
// We don't want to start downloading while the library is updating, because websites

@ -58,29 +58,41 @@ class NotificationReceiver : BroadcastReceiver() {
// Clear the download queue
ACTION_CLEAR_DOWNLOADS -> downloadManager.clearQueue(true)
// Launch share activity and dismiss notification
ACTION_SHARE_IMAGE -> shareImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
ACTION_SHARE_IMAGE -> shareImage(
context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Delete image from path and dismiss notification
ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
ACTION_DELETE_IMAGE -> deleteImage(
context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context)
// Share backup file
ACTION_SHARE_BACKUP ->
shareBackup(
context, intent.getParcelableExtra(EXTRA_URI),
context,
intent.getParcelableExtra(EXTRA_URI),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Open reader activity
ACTION_OPEN_CHAPTER -> {
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1))
openChapter(
context,
intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
)
}
ACTION_MARK_AS_READ -> {
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
if (notificationId > -1) dismissNotification(
context, notificationId, intent.getIntExtra(EXTRA_GROUP_ID, 0)
context,
notificationId,
intent.getIntExtra(EXTRA_GROUP_ID, 0)
)
val urls = intent.getStringArrayExtra(EXTRA_CHAPTER_URL) ?: return
val mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1)
@ -342,7 +354,7 @@ class NotificationReceiver : BroadcastReceiver() {
context: Context,
notificationId: Int,
groupId: Int? =
null
null
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val groupKey = context.notificationManager.activeNotifications.find {
@ -377,8 +389,13 @@ class NotificationReceiver : BroadcastReceiver() {
clipData = ClipData.newRawUri(null, uri)
type = "image/*"
}
return PendingIntent.getActivity(context, 0, shareIntent, PendingIntent
.FLAG_CANCEL_CURRENT)
return PendingIntent.getActivity(
context,
0,
shareIntent,
PendingIntent
.FLAG_CANCEL_CURRENT
)
}
/**
@ -409,11 +426,16 @@ class NotificationReceiver : BroadcastReceiver() {
context: Context,
manga: Manga,
chapter:
Chapter
Chapter
): PendingIntent {
val newIntent = ReaderActivity.newIntent(context, manga, chapter)
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent
.FLAG_UPDATE_CURRENT)
return PendingIntent.getActivity(
context,
manga.id.hashCode(),
newIntent,
PendingIntent
.FLAG_UPDATE_CURRENT
)
}
/**
@ -424,16 +446,19 @@ class NotificationReceiver : BroadcastReceiver() {
*/
internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int):
PendingIntent {
val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(MangaDetailsController.MANGA_EXTRA, manga.id)
.putExtra("notificationId", manga.id.hashCode())
.putExtra("groupId", groupId)
return PendingIntent.getActivity(
context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT
)
}
val newIntent =
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra(MangaDetailsController.MANGA_EXTRA, manga.id)
.putExtra("notificationId", manga.id.hashCode())
.putExtra("groupId", groupId)
return PendingIntent.getActivity(
context,
manga.id.hashCode(),
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
/**
* Returns [PendingIntent] that opens the error log file in an external viewer
@ -462,7 +487,10 @@ class NotificationReceiver : BroadcastReceiver() {
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_EXTENSIONS)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
return PendingIntent.getActivity(
context, 0, newIntent, PendingIntent.FLAG_UPDATE_CURRENT
context,
0,
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
@ -473,7 +501,7 @@ class NotificationReceiver : BroadcastReceiver() {
val toLaunch = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "text/plain")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_GRANT_READ_URI_PERMISSION
Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return PendingIntent.getActivity(context, 0, toLaunch, 0)
}
@ -488,19 +516,19 @@ class NotificationReceiver : BroadcastReceiver() {
context: Context,
manga: Manga,
chapters:
Array<Chapter>,
Array<Chapter>,
groupId: Int
):
PendingIntent {
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_MARK_AS_READ
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
putExtra(EXTRA_MANGA_ID, manga.id)
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
putExtra(EXTRA_GROUP_ID, groupId)
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_MARK_AS_READ
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
putExtra(EXTRA_MANGA_ID, manga.id)
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
putExtra(EXTRA_GROUP_ID, groupId)
}
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**
* Returns [PendingIntent] that starts a service which stops the library update

@ -61,37 +61,44 @@ object Notifications {
fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val channels = listOf(NotificationChannel(
CHANNEL_COMMON,
context.getString(R.string.common),
NotificationManager.IMPORTANCE_LOW
), NotificationChannel(
CHANNEL_LIBRARY,
context.getString(R.string.updating_library),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
}, NotificationChannel(
CHANNEL_DOWNLOADER,
context.getString(R.string.downloads),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
}, NotificationChannel(
CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.extension_updates),
NotificationManager.IMPORTANCE_DEFAULT
), NotificationChannel(
CHANNEL_NEW_CHAPTERS,
context.getString(R.string.new_chapters),
NotificationManager.IMPORTANCE_DEFAULT
), NotificationChannel(
CHANNEL_BACKUP_RESTORE,
context.getString(R.string.restoring_backup),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
})
val channels = listOf(
NotificationChannel(
CHANNEL_COMMON,
context.getString(R.string.common),
NotificationManager.IMPORTANCE_LOW
),
NotificationChannel(
CHANNEL_LIBRARY,
context.getString(R.string.updating_library),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
},
NotificationChannel(
CHANNEL_DOWNLOADER,
context.getString(R.string.downloads),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
},
NotificationChannel(
CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.extension_updates),
NotificationManager.IMPORTANCE_DEFAULT
),
NotificationChannel(
CHANNEL_NEW_CHAPTERS,
context.getString(R.string.new_chapters),
NotificationManager.IMPORTANCE_DEFAULT
),
NotificationChannel(
CHANNEL_BACKUP_RESTORE,
context.getString(R.string.restoring_backup),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
}
)
context.notificationManager.createNotificationChannels(channels)
}
}

@ -43,12 +43,20 @@ class PreferencesHelper(val context: Context) {
private val flowPrefs = FlowSharedPreferences(prefs)
private val defaultDownloadsDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name), "downloads"))
File(
Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name),
"downloads"
)
)
private val defaultBackupDir = Uri.fromFile(
File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name), "backup"))
File(
Environment.getExternalStorageDirectory().absolutePath + File.separator +
context.getString(R.string.app_name),
"backup"
)
)
fun getInt(key: String, default: Int?) = rxPrefs.getInteger(key, default)
fun getStringPref(key: String, default: String?) = rxPrefs.getString(key, default)
@ -130,9 +138,9 @@ class PreferencesHelper(val context: Context) {
fun setTrackCredentials(sync: TrackService, username: String, password: String) {
prefs.edit()
.putString(Keys.trackUsername(sync.id), username)
.putString(Keys.trackPassword(sync.id), password)
.apply()
.putString(Keys.trackUsername(sync.id), username)
.putString(Keys.trackPassword(sync.id), password)
.apply()
}
fun trackToken(sync: TrackService) = rxPrefs.getString(Keys.trackToken(sync.id), "")

@ -60,7 +60,7 @@ abstract class TrackService(val id: Int) {
open val isLogged: Boolean
get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty()
getPassword().isNotEmpty()
fun getUsername() = preferences.trackUsername(this)!!

@ -237,7 +237,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
.appendQueryParameter("response_type", "token")
.build()!!
fun addToLibraryQuery() = """
fun addToLibraryQuery() =
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id
@ -246,7 +247,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|""".trimMargin()
fun deleteFromLibraryQuery() = """
fun deleteFromLibraryQuery() =
"""
|mutation DeleteManga(${'$'}listId: Int) {
|DeleteMediaListEntry (id: ${'$'}listId) {
|deleted
@ -254,7 +256,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|}""".trimMargin()
fun updateInLibraryQuery() = """
fun updateInLibraryQuery() =
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|id
@ -264,7 +267,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|""".trimMargin()
fun searchQuery() = """
fun searchQuery() =
"""
|query Search(${'$'}query: String) {
|Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
@ -289,7 +293,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|""".trimMargin()
fun findLibraryMangaQuery() = """
fun findLibraryMangaQuery() =
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
@ -320,7 +325,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|""".trimMargin()
fun currentUserQuery() = """
fun currentUserQuery() =
"""
|query User {
|Viewer {
|id

@ -38,8 +38,8 @@ class AnilistInterceptor(private val anilist: Anilist, private var token: String
// Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.build()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.build()
return chain.proceed(authRequest)
}

@ -36,25 +36,28 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
}
val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
.header("User-Agent", "Tachiyomi")
.url(originalRequest.url.newBuilder()
.addQueryParameter("access_token", currAuth.access_token).build())
.build() else originalRequest.newBuilder()
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
.header("User-Agent", "Tachiyomi")
.build()
.header("User-Agent", "Tachiyomi")
.url(
originalRequest.url.newBuilder()
.addQueryParameter("access_token", currAuth.access_token).build()
)
.build() else originalRequest.newBuilder()
.post(addTocken(currAuth.access_token, originalRequest.body as FormBody))
.header("User-Agent", "Tachiyomi")
.build()
return chain.proceed(authRequest)
}
fun newAuth(oauth: OAuth) {
this.oauth = OAuth(
oauth.access_token,
oauth.token_type,
System.currentTimeMillis() / 1000,
oauth.expires_in,
oauth.refresh_token,
this.oauth?.user_id)
oauth.access_token,
oauth.token_type,
System.currentTimeMillis() / 1000,
oauth.expires_in,
oauth.refresh_token,
this.oauth?.user_id
)
bangumi.saveToken(oauth)
}

@ -20,8 +20,8 @@ data class OAuth(
val refresh_token: String?,
val user_id: Long?
) {
// Access token refresh before expired
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
// Access token refresh before expired
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
}
data class Status(

@ -30,10 +30,10 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor {
// Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("Accept", "application/vnd.api+json")
.header("Content-Type", "application/vnd.api+json")
.build()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("Accept", "application/vnd.api+json")
.header("Content-Type", "application/vnd.api+json")
.build()
return chain.proceed(authRequest)
}

@ -103,7 +103,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
val request = Request.Builder().url(url.toString()).get().build()
val urlMangas = "$apiUrl/mangas".toUri().buildUpon().appendPath(track.media_id.toString())
.build()
.build()
val requestMangas = Request.Builder().url(urlMangas.toString()).get().build()
val requestMangasResponse = authClient.newCall(requestMangas).execute()

@ -29,9 +29,9 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept
}
// Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("User-Agent", "Tachiyomi")
.build()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("User-Agent", "Tachiyomi")
.build()
return chain.proceed(authRequest)
}

@ -44,7 +44,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
android.R.drawable.stat_sys_download_done,
context.getString(R.string.download),
PendingIntent.getService(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
@ -62,15 +65,18 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
fun setupTask() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
1, TimeUnit.DAYS,
1, TimeUnit.HOURS)
.addTag(TAG)
.setConstraints(constraints)
.build()
1,
TimeUnit.DAYS,
1,
TimeUnit.HOURS
)
.addTag(TAG)
.setConstraints(constraints)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
}

@ -75,13 +75,17 @@ internal class UpdaterNotifier(private val context: Context) {
setProgress(0, 0, false)
// Install action
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
addAction(R.drawable.ic_system_update_24dp,
context.getString(R.string.install),
NotificationHandler.installApkPendingActivity(context, uri))
addAction(
R.drawable.ic_system_update_24dp,
context.getString(R.string.install),
NotificationHandler.installApkPendingActivity(context, uri)
)
// Cancel action
addAction(R.drawable.ic_close_24dp,
context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
)
}
notification.show()
}
@ -99,13 +103,17 @@ internal class UpdaterNotifier(private val context: Context) {
setProgress(0, 0, false)
color = ContextCompat.getColor(context, R.color.colorAccent)
// Retry action
addAction(R.drawable.ic_refresh_24dp,
context.getString(R.string.retry),
UpdaterService.downloadApkPendingService(context, url))
addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.retry),
UpdaterService.downloadApkPendingService(context, url)
)
// Cancel action
addAction(R.drawable.ic_close_24dp,
context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER))
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_UPDATER)
)
}
notification.show(Notifications.ID_UPDATER)
}

@ -43,7 +43,8 @@ class UpdaterService : Service() {
startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted(getString(R.string.app_name)).build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
PowerManager.PARTIAL_WAKE_LOCK,
"${javaClass.name}:WakeLock"
)
wakeLock.acquire()
}

@ -15,10 +15,10 @@ interface GithubService {
companion object {
fun create(): GithubService {
val restAdapter = Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.client(Injekt.get<NetworkHelper>().client)
.build()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.client(Injekt.get<NetworkHelper>().client)
.build()
return restAdapter.create(GithubService::class.java)
}

@ -4,7 +4,7 @@ import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult
class GithubUpdateChecker : UpdateChecker() {
class GithubUpdateChecker : UpdateChecker() {
private val service: GithubService = GithubService.create()

@ -80,8 +80,7 @@ class ExtensionManager(
context.packageManager.getApplicationIcon(pkgName)
} catch (e: Exception) {
null
}
else null
} else null
}
/**
@ -136,16 +135,16 @@ class ExtensionManager(
val extensions = ExtensionLoader.loadExtensions(context)
installedExtensions = extensions
.filterIsInstance<LoadResult.Success>()
.map { it.extension }
.filterIsInstance<LoadResult.Success>()
.map { it.extension }
installedExtensions
.flatMap { it.sources }
// overwrite is needed until the bundled sources are removed
.forEach { sourceManager.registerSource(it, true) }
.flatMap { it.sources }
// overwrite is needed until the bundled sources are removed
.forEach { sourceManager.registerSource(it, true) }
untrustedExtensions = extensions
.filterIsInstance<LoadResult.Untrusted>()
.map { it.extension }
.filterIsInstance<LoadResult.Untrusted>()
.map { it.extension }
}
/**
@ -251,7 +250,7 @@ class ExtensionManager(
*/
fun updateExtension(extension: Extension.Installed): Observable<InstallStep> {
val availableExt = availableExtensions.find { it.pkgName == extension.pkgName }
?: return Observable.empty()
?: return Observable.empty()
return installExtension(availableExt)
}
@ -296,10 +295,10 @@ class ExtensionManager(
nowTrustedExtensions.map { extension ->
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
}.map { it.await() }.forEach { result ->
if (result is LoadResult.Success) {
registerNewExtension(result.extension)
}
if (result is LoadResult.Success) {
registerNewExtension(result.extension)
}
}
}
}

@ -45,12 +45,15 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
val preferences: PreferencesHelper by injectLazy()
preferences.extensionUpdatesCount().set(names.size)
NotificationManagerCompat.from(context).apply {
notify(Notifications.ID_UPDATES_TO_EXTS,
notify(
Notifications.ID_UPDATES_TO_EXTS,
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) {
setContentTitle(
context.resources.getQuantityString(
R.plurals.extension_updates_available, names
.size, names.size
R.plurals.extension_updates_available,
names
.size,
names.size
)
)
val extNames = names.joinToString(", ")
@ -64,7 +67,8 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
)
)
setAutoCancel(true)
})
}
)
}
}
@ -80,8 +84,11 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
.build()
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
12, TimeUnit.HOURS,
1, TimeUnit.HOURS)
12,
TimeUnit.HOURS,
1,
TimeUnit.HOURS
)
.addTag(TAG)
.setConstraints(constraints)
.build()

@ -18,9 +18,9 @@ class ExtensionInstallActivity : Activity() {
super.onCreate(savedInstanceState)
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
.setDataAndType(intent.data, intent.type)
.putExtra(Intent.EXTRA_RETURN_RESULT, true)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setDataAndType(intent.data, intent.type)
.putExtra(Intent.EXTRA_RETURN_RESULT, true)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivityForResult(installIntent, INSTALL_REQUEST_CODE)

@ -19,7 +19,7 @@ import kotlinx.coroutines.async
* @param listener The listener that should be notified of extension installation events.
*/
internal class ExtensionInstallReceiver(private val listener: Listener) :
BroadcastReceiver() {
BroadcastReceiver() {
/**
* Registers this broadcast receiver
@ -94,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
*/
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent)
?: return LoadResult.Error("Package name not found")
?: return LoadResult.Error("Package name not found")
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
}

@ -144,7 +144,7 @@ internal class ExtensionInstaller(private val context: Context) {
fun uninstallApk(pkgName: String) {
val packageUri = "package:$pkgName".toUri()
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}

@ -36,9 +36,9 @@ internal object ExtensionLoader {
* List of the trusted signatures.
*/
var trustedSignatures = mutableSetOf<String>() +
Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() +
// inorichi's key
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
Injekt.get<PreferencesHelper>().trustedSignatures().getOrDefault() +
// inorichi's key
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
/**
* Return a list of all the installed extensions initialized concurrently.
@ -103,8 +103,10 @@ internal object ExtensionLoader {
// Validate lib version
val majorLibVersion = versionName.substringBefore('.').toInt()
if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) {
val exception = Exception("Lib version is $majorLibVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed")
val exception = Exception(
"Lib version is $majorLibVersion, while only versions " +
"$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed"
)
Timber.w(exception)
return LoadResult.Error(exception)
}
@ -122,30 +124,30 @@ internal object ExtensionLoader {
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
.split(";")
.map {
val sourceClass = it.trim()
if (sourceClass.startsWith("."))
pkgInfo.packageName + sourceClass
else
sourceClass
}
.flatMap {
try {
val obj = Class.forName(it, false, classLoader).newInstance()
when (obj) {
is Source -> listOf(obj)
is SourceFactory -> obj.createSources()
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName.")
return LoadResult.Error(e)
.split(";")
.map {
val sourceClass = it.trim()
if (sourceClass.startsWith("."))
pkgInfo.packageName + sourceClass
else
sourceClass
}
.flatMap {
try {
val obj = Class.forName(it, false, classLoader).newInstance()
when (obj) {
is Source -> listOf(obj)
is SourceFactory -> obj.createSources()
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName.")
return LoadResult.Error(e)
}
}
val langs = sources.filterIsInstance<CatalogueSource>()
.map { it.lang }
.toSet()
.map { it.lang }
.toSet()
val lang = when (langs.size) {
0 -> ""

@ -58,17 +58,19 @@ fun Call.asObservable(): Observable<Response> {
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
}
})
)
continuation.invokeOnCancellation {
try {
@ -91,14 +93,14 @@ fun Call.asObservableSuccess(): Observable<Response> {
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder()
.cache(null)
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body!!, listener))
.build()
}
.build()
.cache(null)
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body!!, listener))
.build()
}
.build()
return progressClient.newCall(request)
}

@ -18,10 +18,10 @@ fun GET(
): Request {
return Request.Builder()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
}
fun POST(
@ -32,9 +32,9 @@ fun POST(
): Request {
return Request.Builder()
.url(url)
.post(body)
.headers(headers)
.cacheControl(cache)
.build()
.url(url)
.post(body)
.headers(headers)
.cacheControl(cache)
.build()
}

@ -10,10 +10,10 @@ class UserAgentInterceptor : Interceptor {
return if (originalRequest.header("User-Agent").isNullOrEmpty()) {
val newRequest = originalRequest
.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
.build()
.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", HttpSource.DEFAULT_USERAGENT)
.build()
chain.proceed(newRequest)
} else {
chain.proceed(originalRequest)

@ -143,7 +143,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
val baseDirs = getBaseDirectories(context)
baseDirs
baseDirs
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten()
.filter { it.extension == "json" }.firstOrNull()?.apply {
@ -241,10 +241,12 @@ class LocalSource(private val context: Context) : CatalogueSource {
date_upload = chapterFile.lastModified()
ChapterRecognition.parseChapterNumber(this, manga)
}
}.sortedWith(Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
})
}.sortedWith(
Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
}
)
return Observable.just(chapters)
}
@ -289,18 +291,22 @@ class LocalSource(private val context: Context) : CatalogueSource {
return when (format) {
is Format.Directory -> {
val entry = format.file.listFiles()
?.sortedWith(Comparator<File> { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
})
?.sortedWith(
Comparator<File> { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
}
)
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream()) }
}
is Format.Zip -> {
ZipFile(format.file).use { zip ->
val entry = zip.entries().toList().sortedWith(Comparator<ZipEntry> { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
}).find {
val entry = zip.entries().toList().sortedWith(
Comparator<ZipEntry> { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder(f2.name)
}
).find {
!it.isDirectory && ImageUtil.isImage(it.name) {
zip.getInputStream(it)
}
@ -311,9 +317,11 @@ class LocalSource(private val context: Context) : CatalogueSource {
}
is Format.Rar -> {
Archive(format.file).use { archive ->
val entry = archive.fileHeaders.sortedWith(Comparator<FileHeader> { f1, f2 ->
f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString)
}).find {
val entry = archive.fileHeaders.sortedWith(
Comparator<FileHeader> { f1, f2 ->
f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString)
}
).find {
!it.isDirectory && ImageUtil.isImage(it.fileNameString) {
archive.getInputStream(it)
}

@ -21,13 +21,24 @@ open class SourceManager(private val context: Context) {
private val delegatedSources = listOf(
DelegatedSource(
"reader.kireicake.com", 5509224355268673176, KireiCake()
), DelegatedSource(
"jaiminisbox.com", 9064882169246918586, FoolSlide("jaiminis", "/reader")
), DelegatedSource(
"mangadex.org", 2499283573021220255, MangaDex()
), DelegatedSource(
"mangaplus.shueisha.co.jp", 1998944621602463790, MangaPlus()
"reader.kireicake.com",
5509224355268673176,
KireiCake()
),
DelegatedSource(
"jaiminisbox.com",
9064882169246918586,
FoolSlide("jaiminis", "/reader")
),
DelegatedSource(
"mangadex.org",
2499283573021220255,
MangaDex()
),
DelegatedSource(
"mangaplus.shueisha.co.jp",
1998944621602463790,
MangaPlus()
)
).associateBy { it.sourceId }
@ -92,8 +103,10 @@ open class SourceManager(private val context: Context) {
private fun getSourceNotInstalledException(): Exception {
return SourceNotFoundException(
context.getString(
R.string.source_not_installed_, id.toString()
), id
R.string.source_not_installed_,
id.toString()
),
id
)
}

@ -90,10 +90,10 @@ abstract class HttpSource : CatalogueSource {
*/
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page))
.asObservableSuccess()
.map { response ->
popularMangaParse(response)
}
.asObservableSuccess()
.map { response ->
popularMangaParse(response)
}
}
/**
@ -120,10 +120,10 @@ abstract class HttpSource : CatalogueSource {
*/
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { response ->
searchMangaParse(response)
}
.asObservableSuccess()
.map { response ->
searchMangaParse(response)
}
}
/**
@ -149,10 +149,10 @@ abstract class HttpSource : CatalogueSource {
*/
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess()
.map { response ->
latestUpdatesParse(response)
}
.asObservableSuccess()
.map { response ->
latestUpdatesParse(response)
}
}
/**
@ -177,10 +177,10 @@ abstract class HttpSource : CatalogueSource {
*/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
/**
@ -209,10 +209,10 @@ abstract class HttpSource : CatalogueSource {
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.status != SManga.LICENSED) {
client.newCall(chapterListRequest(manga))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
}
.asObservableSuccess()
.map { response ->
chapterListParse(response)
}
} else {
Observable.error(Exception("Licensed - No chapters to show"))
}
@ -242,10 +242,10 @@ abstract class HttpSource : CatalogueSource {
*/
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
.asObservableSuccess()
.map { response ->
pageListParse(response)
}
.asObservableSuccess()
.map { response ->
pageListParse(response)
}
}
/**
@ -273,8 +273,8 @@ abstract class HttpSource : CatalogueSource {
*/
open fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page))
.asObservableSuccess()
.map { imageUrlParse(it) }
.asObservableSuccess()
.map { imageUrlParse(it) }
}
/**
@ -301,7 +301,7 @@ abstract class HttpSource : CatalogueSource {
*/
fun fetchImage(page: Page): Observable<Response> {
return client.newCallWithProgress(imageRequest(page), page)
.asObservableSuccess()
.asObservableSuccess()
}
/**

@ -14,12 +14,12 @@ fun HttpSource.getImageUrl(page: Page): Observable<Page> {
fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
return Observable.from(pages)
.filter { !it.imageUrl.isNullOrEmpty() }
.mergeWith(fetchRemainingImageUrlsFromPageList(pages))
.filter { !it.imageUrl.isNullOrEmpty() }
.mergeWith(fetchRemainingImageUrlsFromPageList(pages))
}
fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
return Observable.from(pages)
.filter { it.imageUrl.isNullOrEmpty() }
.concatMap { getImageUrl(it) }
.filter { it.imageUrl.isNullOrEmpty() }
.concatMap { getImageUrl(it) }
}

@ -21,7 +21,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
open class FoolSlide(override val domainName: String, private val urlModifier: String = "") :
DelegatedHttpSource
DelegatedHttpSource
() {
override fun canOpenUrl(uri: Uri): Boolean = true
@ -34,7 +34,11 @@ DelegatedHttpSource
val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null
val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString()
return "$urlModifier/read/" + listOfNotNull(
mangaName, lang, volume, chapterNumber, subChapterNumber
mangaName,
lang,
volume,
chapterNumber,
subChapterNumber
).joinToString("/") + "/"
}
@ -95,8 +99,11 @@ DelegatedHttpSource
private fun allowAdult(request: Request) = allowAdult(request.url.toString())
private fun allowAdult(url: String): Request {
return POST(url, body = FormBody.Builder()
.add("adult", "true")
.build())
return POST(
url,
body = FormBody.Builder()
.add("adult", "true")
.build()
)
}
}

@ -17,9 +17,8 @@ class KireiCake : FoolSlide("kireicake") {
this.url = url
source = delegate?.id ?: -1
title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first()
?.ownText()?.substringAfter(":")?.trim() ?: url.split("/").last().replace(
"_", " " + ""
).capitalizeWords()
?.ownText()?.substringAfter(":")?.trim()
?: url.split("/").last().replace("_", " " + "").capitalizeWords()
description =
document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first()
?.ownText()?.substringAfter(":")

@ -61,9 +61,13 @@ class MangaPlus : DelegatedHttpSource() {
context.getString(R.string.chapter_not_found)
)
if (manga != null) {
Triple(trueChapter, manga.apply {
this.title = trimmedTitle
}, chapters.orEmpty())
Triple(
trueChapter,
manga.apply {
this.title = trimmedTitle
},
chapters.orEmpty()
)
} else null
}
}

@ -33,12 +33,16 @@ class CenteredToolbar@JvmOverloads constructor(context: Context, attrs: Attribut
}
fun showDropdown(down: Boolean = true) {
toolbar_title.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_blank_24dp, 0,
if (down) {
R.drawable.ic_arrow_drop_down_24dp
} else {
R.drawable.ic_arrow_drop_up_24dp
}, 0)
toolbar_title.setCompoundDrawablesRelativeWithIntrinsicBounds(
R.drawable.ic_blank_24dp,
0,
if (down) {
R.drawable.ic_arrow_drop_down_24dp
} else {
R.drawable.ic_arrow_drop_up_24dp
},
0
)
}
fun hideDropdown() {

@ -21,7 +21,9 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
var scrollOffset = 0
init {
setViewsToUse(
R.layout.material_fastscroll, R.id.fast_scroller_bubble, R.id.fast_scroller_handle
R.layout.material_fastscroll,
R.id.fast_scroller_bubble,
R.id.fast_scroller_handle
)
autoHideEnabled = true
ignoreTouchesOutsideHandle = false
@ -85,7 +87,8 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
val targetPos = getTargetPos(y)
if (layoutManager is StaggeredGridLayoutManager) {
(layoutManager as StaggeredGridLayoutManager).scrollToPositionWithOffset(
targetPos, scrollOffset
targetPos,
scrollOffset
)
} else {
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset(targetPos, scrollOffset)

@ -119,7 +119,10 @@ class MaterialMenuSheet(
isElevated = elevate
elevationAnimator?.cancel()
elevationAnimator = ObjectAnimator.ofFloat(
title_layout, "elevation", title_layout.elevation, if (elevate) 10f else 0f
title_layout,
"elevation",
title_layout.elevation,
if (elevate) 10f else 0f
)
elevationAnimator?.start()
}

@ -14,31 +14,34 @@ import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.*
import timber.log.Timber
abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle),
LayoutContainer {
abstract class BaseController(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle),
LayoutContainer {
init {
addLifecycleListener(object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) {
onViewCreated(view)
addLifecycleListener(
object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) {
onViewCreated(view)
}
override fun preCreateView(controller: Controller) {
Timber.d("Create view for ${controller.instance()}")
}
override fun preAttach(controller: Controller, view: View) {
Timber.d("Attach view for ${controller.instance()}")
}
override fun preDetach(controller: Controller, view: View) {
Timber.d("Detach view for ${controller.instance()}")
}
override fun preDestroyView(controller: Controller, view: View) {
Timber.d("Destroy view for ${controller.instance()}")
}
}
override fun preCreateView(controller: Controller) {
Timber.d("Create view for ${controller.instance()}")
}
override fun preAttach(controller: Controller, view: View) {
Timber.d("Attach view for ${controller.instance()}")
}
override fun preDetach(controller: Controller, view: View) {
Timber.d("Detach view for ${controller.instance()}")
}
override fun preDestroyView(controller: Controller, view: View) {
Timber.d("Destroy view for ${controller.instance()}")
}
})
)
}
override val containerView: View?
@ -96,17 +99,19 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
*/
var expandActionViewFromInteraction = false
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return onExpand?.invoke(item) ?: true
}
setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return onExpand?.invoke(item) ?: true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
activity?.invalidateOptionsMenu()
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
activity?.invalidateOptionsMenu()
return onCollapse?.invoke(item) ?: true
return onCollapse?.invoke(item) ?: true
}
}
})
)
if (expandActionViewFromInteraction) {
expandActionViewFromInteraction = false

@ -87,10 +87,12 @@ abstract class DialogController : RestoreViewOnCreateController {
*/
fun showDialog(router: Router, tag: String?) {
dismissed = false
router.pushController(RouterTransaction.with(this)
router.pushController(
RouterTransaction.with(this)
.pushChangeHandler(SimpleSwapChangeHandler(false))
.popChangeHandler(SimpleSwapChangeHandler(false))
.tag(tag))
.tag(tag)
)
}
/**

@ -7,8 +7,9 @@ import nucleus.factory.PresenterFactory
import nucleus.presenter.Presenter
@Suppress("LeakingThis")
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) : RxController(bundle),
PresenterFactory<P> {
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) :
RxController(bundle),
PresenterFactory<P> {
private val delegate = NucleusConductorDelegate(this)

@ -10,7 +10,7 @@ abstract class BaseFlexibleViewHolder(
adapter: FlexibleAdapter<*>,
stickyHeader: Boolean = false
) :
FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer {
FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer {
override val containerView: View?
get() = itemView

@ -24,7 +24,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error.
*/
fun <T> Observable<T>.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) }
compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/**
* Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle
@ -34,7 +34,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error.
*/
fun <T> Observable<T>.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) }
compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/**
* Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle
@ -44,7 +44,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error.
*/
fun <T> Observable<T>.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
compose(deliverReplay<T>()).subscribe(split(onNext, onError)).apply { add(this) }
/**
* Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle
@ -54,7 +54,7 @@ open class BasePresenter<V> : RxPresenter<V>() {
* @param onError function to execute when the observable throws an error.
*/
fun <T> Observable<T>.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) }
compose(DeliverWithView<V, T>(view())).subscribe(split(onNext, onError)).apply { add(this) }
/**
* A deliverable that only emits to the view if attached, otherwise the event is ignored.
@ -63,11 +63,11 @@ open class BasePresenter<V> : RxPresenter<V>() {
override fun call(observable: Observable<T>): Observable<Delivery<View, T>> {
return observable
.materialize()
.filter { notification -> !notification.isOnCompleted }
.flatMap { notification ->
view.take(1).filter { it != null }.map { Delivery(it, notification) }
}
.materialize()
.filter { notification -> !notification.isOnCompleted }
.flatMap { notification ->
view.take(1).filter { it != null }.map { Delivery(it, notification) }
}
}
}
}

@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
* @param controller The containing controller.
*/
class CategoryAdapter(controller: CategoryController) :
FlexibleAdapter<CategoryItem>(null, controller, true) {
FlexibleAdapter<CategoryItem>(null, controller, true) {
/**
* Listener called when an item of the list is released.

@ -22,10 +22,11 @@ import kotlinx.android.synthetic.main.categories_controller.*
/**
* Controller to manage the categories for the users' library.
*/
class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemMoveListener,
CategoryAdapter.CategoryItemListener {
class CategoryController(bundle: Bundle? = null) :
BaseController(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemMoveListener,
CategoryAdapter.CategoryItemListener {
/**
* Adapter containing category items.
@ -147,12 +148,14 @@ class CategoryController(bundle: Bundle? = null) : BaseController(bundle),
adapter?.restoreDeletedItems()
undoing = true
}
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!undoing) confirmDelete()
addCallback(
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!undoing) confirmDelete()
}
}
})
)
}
(activity as? MainActivity)?.setUndoSnackBar(snack)
}

@ -51,16 +51,22 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
createCategory = category.order == CREATE_CATEGORY_ORDER
if (createCategory) {
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.text_color_hint))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_add_24dp)
regularDrawable = ContextCompat.getDrawable(
itemView.context,
R.drawable
.ic_add_24dp
)
image.gone()
edit_button.setImageDrawable(null)
edit_text.setText("")
edit_text.hint = title.text
} else {
title.setTextColor(ContextCompat.getColor(itemView.context, R.color.textColorPrimary))
regularDrawable = ContextCompat.getDrawable(itemView.context, R.drawable
.ic_drag_handle_24dp)
regularDrawable = ContextCompat.getDrawable(
itemView.context,
R.drawable
.ic_drag_handle_24dp
)
image.visible()
edit_text.setText(title.text)
}
@ -80,7 +86,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
if (!createCategory) {
reorder.setImageDrawable(
ContextCompat.getDrawable(
itemView.context, R.drawable.ic_delete_24dp
itemView.context,
R.drawable.ic_delete_24dp
)
)
reorder.setOnClickListener {
@ -96,8 +103,13 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
reorder.setOnTouchListener { _, _ -> true }
}
edit_text.clearFocus()
edit_button.drawable?.mutate()?.setTint(ContextCompat.getColor(itemView.context, R
.color.gray_button))
edit_button.drawable?.mutate()?.setTint(
ContextCompat.getColor(
itemView.context,
R
.color.gray_button
)
)
reorder.setImageDrawable(regularDrawable)
}
}
@ -105,7 +117,8 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
private fun submitChanges() {
if (edit_text.visibility == View.VISIBLE) {
if (adapter.categoryItemListener
.onCategoryRename(adapterPosition, edit_text.text.toString())) {
.onCategoryRename(adapterPosition, edit_text.text.toString())
) {
isEditing(false)
edit_text.inputType = InputType.TYPE_NULL
if (!createCategory)
@ -119,8 +132,11 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleVie
private fun showKeyboard() {
val inputMethodManager: InputMethodManager =
itemView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(edit_text, WindowManager.LayoutParams
.SOFT_INPUT_ADJUST_PAN)
inputMethodManager.showSoftInput(
edit_text,
WindowManager.LayoutParams
.SOFT_INPUT_ADJUST_PAN
)
}
/**

@ -70,7 +70,8 @@ class ManageCategoryDialog(bundle: Bundle? = null) :
preferences.downloadNew().set(true)
}
if (preferences.libraryUpdateInterval().getOrDefault() > 0 &&
!updatePref(preferences.libraryUpdateCategories(), view.include_global)) {
!updatePref(preferences.libraryUpdateCategories(), view.include_global)
) {
preferences.libraryUpdateInterval().set(0)
LibraryUpdateJob.setupTask(0)
}
@ -99,9 +100,11 @@ class ManageCategoryDialog(bundle: Bundle? = null) :
view.title.hint = category.name
view.title.append(category.name)
val downloadNew = preferences.downloadNew().getOrDefault()
setCheckbox(view.download_new,
setCheckbox(
view.download_new,
preferences.downloadNewCategories(),
true)
true
)
if (downloadNew && preferences.downloadNewCategories().getOrDefault().isEmpty())
view.download_new.gone()
else if (!downloadNew)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save