Added toasts to cleanup download

This commit is contained in:
Jay 2020-01-13 00:03:22 -08:00
parent 0577c45194
commit 24f5351701
7 changed files with 79 additions and 30 deletions

View File

@ -43,6 +43,7 @@ import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit
/** /**
* Restores backup from json file * Restores backup from json file
@ -117,7 +118,7 @@ class BackupRestoreService : Service() {
startForeground(Notifications.ID_RESTORE_PROGRESS, progressNotification.build()) startForeground(Notifications.ID_RESTORE_PROGRESS, progressNotification.build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock") PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
wakeLock.acquire() wakeLock.acquire(TimeUnit.HOURS.toMillis(3))
} }
/** /**

View File

@ -217,6 +217,17 @@ class DownloadCache(
} }
} }
fun removeFolders(folders: List<String>, manga: Manga) {
val sourceDir = rootDir.files[manga.source] ?: return
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
for (chapter in folders) {
if (chapter in mangaDir.files) {
mangaDir.files -= chapter
}
}
}
/** /**
* Removes a manga that has been deleted from this cache. * Removes a manga that has been deleted from this cache.
* *

View File

@ -219,16 +219,21 @@ class DownloadManager(val context: Context) {
* @param manga the manga of the chapters. * @param manga the manga of the chapters.
* @param source the source of the chapters. * @param source the source of the chapters.
*/ */
fun cleanupChapters(allChapters: List<Chapter>, manga: Manga, source: Source) { fun cleanupChapters(allChapters: List<Chapter>, manga: Manga, source: Source): Int {
var cleaned = 0
val filesWithNoChapter = provider.findUnmatchedChapterDirs(allChapters, manga, source) val filesWithNoChapter = provider.findUnmatchedChapterDirs(allChapters, manga, source)
cleaned += filesWithNoChapter.size
cache.removeFolders(filesWithNoChapter.mapNotNull { it.name }, manga)
filesWithNoChapter.forEach { it.delete() } filesWithNoChapter.forEach { it.delete() }
val readChapters = allChapters.filter { it.read } val readChapters = allChapters.filter { it.read }
val readChapterDirs = provider.findChapterDirs(readChapters, manga, source) val readChapterDirs = provider.findChapterDirs(readChapters, manga, source)
readChapterDirs.forEach { it.delete() } readChapterDirs.forEach { it.delete() }
cleaned += readChapterDirs.size
cache.removeChapters(readChapters, manga) cache.removeChapters(readChapters, manga)
if (cache.getDownloadCount(manga) == 0) { if (cache.getDownloadCount(manga) == 0) {
provider.findChapterDirs(allChapters, manga, source).firstOrNull()?.parentFile?.delete()// Delete manga directory if empty provider.findChapterDirs(allChapters, manga, source).firstOrNull()?.parentFile?.delete()// Delete manga directory if empty
} }
return cleaned
} }
/** /**

View File

@ -142,13 +142,11 @@ class DownloadProvider(private val context: Context) {
fun findUnmatchedChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): List<UniFile> { fun findUnmatchedChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): List<UniFile> {
val mangaDir = findMangaDir(manga, source) ?: return emptyList() val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return mangaDir.listFiles()!!.asList().filter { return mangaDir.listFiles()!!.asList().filter {
chapters.find { chp -> (chapters.find { chp ->
(getValidChapterDirNames(chp) + "${getChapterDirName(chp)}_tmp").any { dir -> getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile( mangaDir.findFile(dir) != null
dir
) != null
} }
} == null } == null) || it.name?.endsWith("_tmp") == true
} }
} }

View File

@ -40,9 +40,6 @@ import eu.kanade.tachiyomi.util.notification
import eu.kanade.tachiyomi.util.notificationManager import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.syncChaptersWithSource import eu.kanade.tachiyomi.util.syncChaptersWithSource
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -52,6 +49,7 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.ArrayList import java.util.ArrayList
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
/** /**
@ -80,8 +78,6 @@ class LibraryUpdateService(
*/ */
private var subscription: Subscription? = null private var subscription: Subscription? = null
var job: Job? = null
/** /**
* Pending intent of action that cancels the library update * Pending intent of action that cancels the library update
@ -116,8 +112,7 @@ class LibraryUpdateService(
enum class Target { enum class Target {
CHAPTERS, // Manga chapters CHAPTERS, // Manga chapters
DETAILS, // Manga metadata DETAILS, // Manga metadata
TRACKING, // Tracking metadata TRACKING // Tracking metadata
CLEANUP // Clean up downloads
} }
companion object { companion object {
@ -184,7 +179,7 @@ class LibraryUpdateService(
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotification.build()) startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotification.build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock") PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock")
wakeLock.acquire() wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
} }
/** /**
@ -222,22 +217,16 @@ class LibraryUpdateService(
// Unsubscribe from any previous subscription if needed. // Unsubscribe from any previous subscription if needed.
subscription?.unsubscribe() subscription?.unsubscribe()
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
// Update favorite manga. Destroy service when completed or in case of an error.
val mangaList = getMangaToUpdate(intent, target)
.sortedWith(rankingScheme[selectedScheme])
val handler = CoroutineExceptionHandler { _, exception -> val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception) Timber.e(exception)
stopSelf(startId) stopSelf(startId)
} }
// Update either chapter list or manga details. // Update either chapter list or manga details.
if (target == Target.CLEANUP) { val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
job = GlobalScope.launch(handler) { // Update favorite manga. Destroy service when completed or in case of an error.
cleanupDownloads() val mangaList = getMangaToUpdate(intent, target)
} .sortedWith(rankingScheme[selectedScheme])
job?.invokeOnCompletion { stopSelf(startId) }
} else {
subscription = Observable.defer { subscription = Observable.defer {
when (target) { when (target) {
Target.CHAPTERS -> updateChapterList(mangaList) Target.CHAPTERS -> updateChapterList(mangaList)
@ -250,7 +239,6 @@ class LibraryUpdateService(
}, { }, {
stopSelf(startId) stopSelf(startId)
}) })
}
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
@ -360,11 +348,13 @@ class LibraryUpdateService(
private fun cleanupDownloads() { private fun cleanupDownloads() {
val mangaList = db.getMangas().executeAsBlocking() val mangaList = db.getMangas().executeAsBlocking()
var foldersCleared = 0
for (manga in mangaList) { for (manga in mangaList) {
val chapterList = db.getChapters(manga).executeAsBlocking() val chapterList = db.getChapters(manga).executeAsBlocking()
val source = sourceManager.getOrStub(manga.source) val source = sourceManager.getOrStub(manga.source)
downloadManager.cleanupChapters(chapterList, manga, source) foldersCleared += downloadManager.cleanupChapters(chapterList, manga, source)
} }
} }
fun downloadChapters(manga: Manga, chapters: List<Chapter>) { fun downloadChapters(manga: Manga, chapters: List<Chapter>) {

View File

@ -4,21 +4,33 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import android.view.View import android.view.View
import android.widget.Toast
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.util.launchUI
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class SettingsAdvancedController : SettingsController() { class SettingsAdvancedController : SettingsController() {
@ -74,10 +86,33 @@ class SettingsAdvancedController : SettingsController() {
summaryRes = R.string.pref_clean_downloads_summary summaryRes = R.string.pref_clean_downloads_summary
onClick { LibraryUpdateService.start(context, target = Target.CLEANUP) } onClick { cleanupDownloads() }
} }
} }
private fun cleanupDownloads() {
if (job?.isActive == true) return
activity?.toast(R.string.starting_cleanup)
job = GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val mangaList = db.getMangas().executeAsBlocking()
val sourceManager: SourceManager = Injekt.get()
val downloadManager: DownloadManager = Injekt.get()
var foldersCleared = 0
for (manga in mangaList) {
val chapterList = db.getChapters(manga).executeAsBlocking()
val source = sourceManager.getOrStub(manga.source)
foldersCleared += downloadManager.cleanupChapters(chapterList, manga, source)
}
launchUI {
val activity = activity ?: return@launchUI
val cleanupString = if (foldersCleared == 0) activity.getString(R.string.no_cleanup_done)
else resources!!.getQuantityString(R.plurals.cleanup_done, foldersCleared, foldersCleared)
activity.toast(cleanupString, Toast.LENGTH_LONG)
}
}
}
private fun clearChapterCache() { private fun clearChapterCache() {
if (activity == null) return if (activity == null) return
val files = chapterCache.cacheDir.listFiles() ?: return val files = chapterCache.cacheDir.listFiles() ?: return
@ -128,5 +163,7 @@ class SettingsAdvancedController : SettingsController() {
private companion object { private companion object {
const val CLEAR_CACHE_KEY = "pref_clear_cache_key" const val CLEAR_CACHE_KEY = "pref_clear_cache_key"
private var job: Job? = null
} }
} }

View File

@ -355,9 +355,16 @@
<string name="pref_refresh_library_tracking">Refresh tracking metadata</string> <string name="pref_refresh_library_tracking">Refresh tracking metadata</string>
<string name="pref_refresh_library_tracking_summary">Updates status, score and last chapter read from the tracking services</string> <string name="pref_refresh_library_tracking_summary">Updates status, score and last chapter read from the tracking services</string>
<string name="pref_clean_downloads">Clean up downloaded chapters</string> <string name="pref_clean_downloads">Clean up downloaded chapters</string>
<string name="pref_clean_downloads_summary">Deletes non-existent, partially downloaded, <string name="pref_clean_downloads_summary">Delete non-existent, partially downloaded,
and read chapter folders</string> and read chapter folders</string>
<string name="starting_cleanup">Starting cleanup</string>
<string name="no_cleanup_done">No folders to cleanup</string>
<plurals name="cleanup_done">
<item quantity="one">Cleanup done. Removed %d folder</item>
<item quantity="other">Cleanup done. Removed %d folders</item>
</plurals>
<!-- About section --> <!-- About section -->
<string name="version">Version</string> <string name="version">Version</string>
<string name="build_time">Build time</string> <string name="build_time">Build time</string>