From 604929d00224114268108778fab0f6a772a20821 Mon Sep 17 00:00:00 2001 From: inorichi Date: Tue, 28 Nov 2017 00:21:38 +0100 Subject: [PATCH 1/8] Update support library and kotlin --- .travis/build.sh | 2 +- app/build.gradle | 10 +++++----- .../kanade/tachiyomi/ui/library/LibraryAdapter.kt | 2 +- .../tachiyomi/ui/reader/ReaderCustomFilterDialog.kt | 2 +- .../tachiyomi/ui/reader/ReaderSettingsDialog.kt | 9 ++++++--- .../tachiyomi/ui/reader/viewer/pager/PagerReader.kt | 4 ++-- .../viewer/pager/horizontal/HorizontalPager.kt | 4 ++-- .../viewer/pager/horizontal/LeftToRightReader.kt | 2 +- .../viewer/pager/horizontal/RightToLeftReader.kt | 2 +- .../reader/viewer/pager/vertical/VerticalReader.kt | 2 +- .../pager/vertical/VerticalViewPagerImpl.java | 13 ++++--------- .../ui/reader/viewer/webtoon/WebtoonReader.kt | 4 ++-- .../ui/setting/SettingsDownloadController.kt | 2 +- build.gradle | 2 +- 14 files changed, 29 insertions(+), 31 deletions(-) diff --git a/.travis/build.sh b/.travis/build.sh index d0c376bef..049fd015b 100755 --- a/.travis/build.sh +++ b/.travis/build.sh @@ -12,7 +12,7 @@ if [ -z "$TRAVIS_TAG" ]; then else ./gradlew clean assembleStandardRelease - TOOLS="${ANDROID_HOME}/build-tools/26.0.1" + TOOLS="$(ls -d ${ANDROID_HOME}/build-tools/* | tail -1)" export ARTIFACT="tachiyomi-${TRAVIS_TAG}.apk" ${TOOLS}/zipalign -v -p 4 app/build/outputs/apk/standard/release/app-standard-release-unsigned.apk app-aligned.apk diff --git a/app/build.gradle b/app/build.gradle index 4994c0755..b8dbf6ea5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,7 +30,7 @@ ext { android { compileSdkVersion 26 - buildToolsVersion "26.0.2" + buildToolsVersion "27.0.1" publishNonDefault true defaultConfig { @@ -106,7 +106,7 @@ dependencies { implementation 'com.github.inorichi:junrar-android:634c1f5' // Android support library - final support_library_version = '26.1.0' + final support_library_version = '27.0.1' implementation "com.android.support:support-v4:$support_library_version" implementation "com.android.support:appcompat-v7:$support_library_version" implementation "com.android.support:cardview-v7:$support_library_version" @@ -122,13 +122,13 @@ dependencies { // ReactiveX implementation 'io.reactivex:rxandroid:1.2.1' - implementation 'io.reactivex:rxjava:1.3.3' + implementation 'io.reactivex:rxjava:1.3.4' implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0' implementation 'com.f2prateek.rx.preferences:rx-preferences:1.0.2' implementation 'com.github.pwittchen:reactivenetwork:0.7.0' // Network client - implementation "com.squareup.okhttp3:okhttp:3.9.0" + implementation "com.squareup.okhttp3:okhttp:3.9.1" implementation 'com.squareup.okio:okio:1.13.0' // REST @@ -232,7 +232,7 @@ dependencies { } buildscript { - ext.kotlin_version = '1.1.51' + ext.kotlin_version = '1.1.61' repositories { mavenCentral() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index ae19c08ad..247b113ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -79,7 +79,7 @@ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPa /** * Returns the position of the view. */ - override fun getItemPosition(obj: Any?): Int { + override fun getItemPosition(obj: Any): Int { val view = obj as? LibraryCategoryView ?: return POSITION_NONE val index = categories.indexOfFirst { it.id == view.category.id } return if (index == -1) POSITION_NONE else index diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt index 7e3ac1003..d76a5156f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt @@ -64,7 +64,7 @@ class ReaderCustomFilterDialog : DialogFragment() { * @param savedState The last saved instance state of the Fragment. */ override fun onCreateDialog(savedState: Bundle?): Dialog { - val dialog = MaterialDialog.Builder(activity) + val dialog = MaterialDialog.Builder(activity!!) .customView(R.layout.reader_custom_filter_dialog, false) .positiveText(android.R.string.ok) .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt index fa09cc120..8927ad4fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt @@ -24,7 +24,7 @@ class ReaderSettingsDialog : DialogFragment() { private lateinit var subscriptions: CompositeSubscription override fun onCreateDialog(savedState: Bundle?): Dialog { - val dialog = MaterialDialog.Builder(activity) + val dialog = MaterialDialog.Builder(activity!!) .title(R.string.label_settings) .customView(R.layout.reader_settings_dialog, true) .positiveText(android.R.string.ok) @@ -40,8 +40,11 @@ class ReaderSettingsDialog : DialogFragment() { viewer.onItemSelectedListener = IgnoreFirstSpinnerListener { position -> subscriptions += Observable.timer(250, MILLISECONDS, AndroidSchedulers.mainThread()) .subscribe { - (activity as ReaderActivity).presenter.updateMangaViewer(position) - activity.recreate() + val readerActivity = activity as? ReaderActivity + if (readerActivity != null) { + readerActivity.presenter.updateMangaViewer(position) + readerActivity.recreate() + } } } viewer.setSelection((activity as ReaderActivity).presenter.manga.viewer, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt index ab12c872a..a5a4e71d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -100,12 +100,12 @@ abstract class PagerReader : BaseReader() { /** * Text color for black theme. */ - val whiteColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryDark) } + val whiteColor by lazy { ContextCompat.getColor(context!!, R.color.textColorSecondaryDark) } /** * Text color for white theme. */ - val blackColor by lazy { ContextCompat.getColor(context, R.color.textColorSecondaryLight) } + val blackColor by lazy { ContextCompat.getColor(context!!, R.color.textColorSecondaryLight) } /** * Initializes the pager. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt index e12f773a7..e359b7fbf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.kt @@ -24,7 +24,7 @@ class HorizontalPager(context: Context) : ViewPager(context), Pager { override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { try { if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_DOWN) { - if (currentItem == 0 || currentItem == adapter.count - 1) { + if (currentItem == 0 || currentItem == adapter!!.count - 1) { startDragX = ev.x } } @@ -50,7 +50,7 @@ class HorizontalPager(context: Context) : ViewPager(context), Pager { startDragX = 0f } - } else if (currentItem == adapter.count - 1) { + } else if (currentItem == adapter!!.count - 1) { if (ev.action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_UP) { val displacement = startDragX - ev.x diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt index 39f533c3f..28d170deb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/LeftToRightReader.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader class LeftToRightReader : PagerReader() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { - return HorizontalPager(activity).apply { initializePager(this) } + return HorizontalPager(activity!!).apply { initializePager(this) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt index 7815bc66b..ab866cbd6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/RightToLeftReader.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader class RightToLeftReader : PagerReader() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { - return HorizontalPager(activity).apply { + return HorizontalPager(activity!!).apply { rotation = 180f initializePager(this) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt index 3a5716025..028e9b187 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalReader.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader class VerticalReader : PagerReader() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { - return VerticalPager(activity).apply { initializePager(this) } + return VerticalPager(activity!!).apply { initializePager(this) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalViewPagerImpl.java b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalViewPagerImpl.java index d435dd24e..d9174ff3d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalViewPagerImpl.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalViewPagerImpl.java @@ -33,7 +33,6 @@ import android.support.annotation.DrawableRes; import android.support.v4.os.ParcelableCompat; import android.support.v4.os.ParcelableCompatCreatorCallbacks; import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.KeyEventCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.PagerAdapter; import android.support.v4.view.VelocityTrackerCompat; @@ -2598,14 +2597,10 @@ public class VerticalViewPagerImpl extends ViewGroup { handled = arrowScroll(FOCUS_RIGHT); break; case KeyEvent.KEYCODE_TAB: - if (Build.VERSION.SDK_INT >= 11) { - // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD - // before Android 3.0. Ignore the tab key on those devices. - if (KeyEventCompat.hasNoModifiers(event)) { - handled = arrowScroll(FOCUS_FORWARD); - } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { - handled = arrowScroll(FOCUS_BACKWARD); - } + if (event.hasNoModifiers()) { + handled = arrowScroll(FOCUS_FORWARD); + } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { + handled = arrowScroll(FOCUS_BACKWARD); } break; default: diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt index d10cb41b1..83d3f88df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt @@ -73,7 +73,7 @@ class WebtoonReader : BaseReader() { private var scrollDistance: Int = 0 val screenHeight by lazy { - val display = activity.windowManager.defaultDisplay + val display = activity!!.windowManager.defaultDisplay if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { val metrics = DisplayMetrics() @@ -91,7 +91,7 @@ class WebtoonReader : BaseReader() { val screenHeight = resources.displayMetrics.heightPixels scrollDistance = screenHeight * 3 / 4 - layoutManager = PreCachingLayoutManager(activity) + layoutManager = PreCachingLayoutManager(activity!!) layoutManager.extraLayoutSpace = screenHeight / 2 recycler = RecyclerView(activity).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index 39485d6ec..04ec17f60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -194,7 +194,7 @@ class SettingsDownloadController : SettingsController() { File.separator + "downloads" return mutableListOf(File(defaultDir)) + - ContextCompat.getExternalFilesDirs(activity, "").filterNotNull() + ContextCompat.getExternalFilesDirs(activity!!, "").filterNotNull() } } diff --git a/build.gradle b/build.gradle index 1115873b6..7bdf0497e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.1' // NOTE: Do not place your application dependencies here; they belong From bff329a3299ce98f85e4bfc75067e4dc8a0d2bcd Mon Sep 17 00:00:00 2001 From: inorichi Date: Tue, 28 Nov 2017 00:32:51 +0100 Subject: [PATCH 2/8] Implement a download cache --- .../tachiyomi/data/download/DownloadCache.kt | 253 ++++++++++++++++++ .../data/download/DownloadManager.kt | 85 +++--- .../data/download/DownloadProvider.kt | 16 +- .../tachiyomi/data/download/Downloader.kt | 17 +- .../tachiyomi/ui/library/LibraryPresenter.kt | 53 +--- .../ui/manga/chapter/ChaptersPresenter.kt | 14 +- .../ui/manga/info/MangaInfoPresenter.kt | 4 +- .../tachiyomi/ui/reader/ChapterLoader.kt | 2 +- .../tachiyomi/ui/reader/ReaderPresenter.kt | 2 +- .../recent_updates/RecentChaptersPresenter.kt | 30 +-- 10 files changed, 327 insertions(+), 149 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt new file mode 100644 index 000000000..7846ff7ae --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -0,0 +1,253 @@ +package eu.kanade.tachiyomi.data.download + +import android.content.Context +import android.net.Uri +import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.source.SourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit + +/** + * Cache where we dump the downloads directory from the filesystem. This class is needed because + * directory checking is expensive and it slowdowns the app. The cache is invalidated by the time + * defined in [renewInterval] as we don't have any control over the filesystem and the user can + * delete the folders at any time without the app noticing. + * + * @param context the application context. + * @param provider the downloads directories provider. + * @param sourceManager the source manager. + * @param preferences the preferences of the app. + */ +class DownloadCache(private val context: Context, + private val provider: DownloadProvider, + private val sourceManager: SourceManager = Injekt.get(), + preferences: PreferencesHelper = Injekt.get()) { + + /** + * The interval after which this cache should be invalidated. 1 hour shouldn't cause major + * issues, as the cache is only used for UI feedback. + */ + private val renewInterval = TimeUnit.HOURS.toMillis(1) + + /** + * The last time the cache was refreshed. + */ + private var lastRenew = 0L + + /** + * The root directory for downloads. + */ + private var rootDir = setRootDir(preferences.downloadsDirectory().getOrDefault()) + + init { + setRootDir(preferences.downloadsDirectory().getOrDefault()) + preferences.downloadsDirectory().asObservable() + .skip(1) + .subscribe { setRootDir(it) } + } + + /** + * Sets the root downloads directory and invalidates the cache. + * + * @param directory the downloads directory in [Uri] format. + */ + private fun setRootDir(directory: String): RootDirectory { + rootDir = RootDirectory(UniFile.fromUri(context, Uri.parse(directory))) + lastRenew = 0L + return rootDir + } + + /** + * Returns true if the chapter is downloaded. + * + * @param chapter the chapter to check. + * @param manga the manga of the chapter. + * @param skipCache whether to skip the directory cache and check in the filesystem. + */ + fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean): Boolean { + if (skipCache) { + val source = sourceManager.get(manga.source) ?: return false + return provider.findChapterDir(chapter, manga, source) != null + } + + checkRenew() + + val sourceDir = rootDir.files[manga.source] + if (sourceDir != null) { + val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] + if (mangaDir != null) { + return provider.getChapterDirName(chapter) in mangaDir.files + } + } + return false + } + + /** + * Returns the amount of downloaded chapters for a manga. + * + * @param manga the manga to check. + */ + fun getDownloadCount(manga: Manga): Int { + checkRenew() + + val sourceDir = rootDir.files[manga.source] + if (sourceDir != null) { + val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] + if (mangaDir != null) { + return mangaDir.files.size + } + } + return 0 + } + + /** + * Checks if the cache needs a renewal and performs it if needed. + */ + @Synchronized + private fun checkRenew() { + if (lastRenew + renewInterval < System.currentTimeMillis()) { + renew() + lastRenew = System.currentTimeMillis() + } + } + + /** + * Renews the downloads cache. + */ + private fun renew() { + val onlineSources = sourceManager.getOnlineSources() + + val sourceDirs = rootDir.dir.listFiles() + .orEmpty() + .associate { it.name to SourceDirectory(it) } + .mapNotNullKeys { entry -> + onlineSources.find { provider.getSourceDirName(it) == entry.key }?.id + } + + rootDir.files = sourceDirs + + sourceDirs.values.forEach { sourceDir -> + val mangaDirs = sourceDir.dir.listFiles() + .orEmpty() + .associateNotNullKeys { it.name to MangaDirectory(it) } + + sourceDir.files = mangaDirs + + mangaDirs.values.forEach { mangaDir -> + val chapterDirs = mangaDir.dir.listFiles() + .orEmpty() + .mapNotNull { it.name } + .toHashSet() + + mangaDir.files = chapterDirs + } + } + } + + /** + * Adds a chapter that has just been download to this cache. + * + * @param chapterDirName the downloaded chapter's directory name. + * @param mangaUniFile the directory of the manga. + * @param manga the manga of the chapter. + */ + @Synchronized + fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) { + // Retrieve the cached source directory or cache a new one + var sourceDir = rootDir.files[manga.source] + if (sourceDir == null) { + val source = sourceManager.get(manga.source) ?: return + val sourceUniFile = provider.findSourceDir(source) ?: return + sourceDir = SourceDirectory(sourceUniFile) + rootDir.files += manga.source to sourceDir + } + + // Retrieve the cached manga directory or cache a new one + val mangaDirName = provider.getMangaDirName(manga) + var mangaDir = sourceDir.files[mangaDirName] + if (mangaDir == null) { + mangaDir = MangaDirectory(mangaUniFile) + sourceDir.files += mangaDirName to mangaDir + } + + // Save the chapter directory + mangaDir.files += chapterDirName + } + + /** + * Removes a chapter that has been deleted from this cache. + * + * @param chapter the chapter to remove. + * @param manga the manga of the chapter. + */ + @Synchronized + fun removeChapter(chapter: Chapter, manga: Manga) { + val sourceDir = rootDir.files[manga.source] ?: return + val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return + val chapterDirName = provider.getChapterDirName(chapter) + if (chapterDirName in mangaDir.files) { + mangaDir.files -= chapterDirName + } + } + + /** + * Removes a manga that has been deleted from this cache. + * + * @param manga the manga to remove. + */ + @Synchronized + fun removeManga(manga: Manga) { + val sourceDir = rootDir.files[manga.source] ?: return + val mangaDirName = provider.getMangaDirName(manga) + if (mangaDirName in sourceDir.files) { + sourceDir.files -= mangaDirName + } + } + + /** + * Class to store the files under the root downloads directory. + */ + private class RootDirectory(val dir: UniFile, + var files: Map = hashMapOf()) + + /** + * Class to store the files under a source directory. + */ + private class SourceDirectory(val dir: UniFile, + var files: Map = hashMapOf()) + + /** + * Class to store the files under a manga directory. + */ + private class MangaDirectory(val dir: UniFile, + var files: Set = hashSetOf()) + + /** + * Returns a new map containing only the key entries of [transform] that are not null. + */ + private inline fun Map.mapNotNullKeys(transform: (Map.Entry) -> R?): Map { + val destination = LinkedHashMap() + forEach { element -> transform(element)?.let { destination.put(it, element.value) } } + return destination + } + + /** + * Returns a map from a list containing only the key entries of [transform] that are not null. + */ + private inline fun Array.associateNotNullKeys(transform: (T) -> Pair): Map { + val destination = LinkedHashMap() + for (element in this) { + val (key, value) = transform(element) + if (key != null) { + destination.put(key, value) + } + } + return destination + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 0d4e79048..de74f1e07 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -24,10 +24,15 @@ class DownloadManager(context: Context) { */ private val provider = DownloadProvider(context) + /** + * Cache of downloaded chapters. + */ + private val cache = DownloadCache(context, provider) + /** * Downloader whose only task is to download chapters. */ - private val downloader = Downloader(context, provider) + private val downloader = Downloader(context, provider, cache) /** * Downloads queue, where the pending chapters are stored. @@ -94,7 +99,7 @@ class DownloadManager(context: Context) { * @return an observable containing the list of pages from the chapter. */ fun buildPageList(source: Source, manga: Manga, chapter: Chapter): Observable> { - return buildPageList(provider.findChapterDir(source, manga, chapter)) + return buildPageList(provider.findChapterDir(chapter, manga, source)) } /** @@ -120,61 +125,45 @@ class DownloadManager(context: Context) { } /** - * Returns the directory name for a manga. + * Returns true if the chapter is downloaded. * - * @param manga the manga to query. - */ - fun getMangaDirName(manga: Manga): String { - return provider.getMangaDirName(manga) - } - - /** - * Returns the directory name for the given chapter. - * - * @param chapter the chapter to query. - */ - fun getChapterDirName(chapter: Chapter): String { - return provider.getChapterDirName(chapter) - } - - /** - * Returns the download directory for a source if it exists. - * - * @param source the source to query. - */ - fun findSourceDir(source: Source): UniFile? { - return provider.findSourceDir(source) - } - - /** - * Returns the directory for the given manga, if it exists. - * - * @param source the source of the manga. - * @param manga the manga to query. - */ - fun findMangaDir(source: Source, manga: Manga): UniFile? { - return provider.findMangaDir(source, manga) - } - - /** - * Returns the directory for the given chapter, if it exists. - * - * @param source the source of the chapter. + * @param chapter the chapter to check. * @param manga the manga of the chapter. - * @param chapter the chapter to query. + * @param skipCache whether to skip the directory cache and check in the filesystem. */ - fun findChapterDir(source: Source, manga: Manga, chapter: Chapter): UniFile? { - return provider.findChapterDir(source, manga, chapter) + fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean = false): Boolean { + return cache.isChapterDownloaded(chapter, manga, skipCache) + } + + /** + * Returns the amount of downloaded chapters for a manga. + * + * @param manga the manga to check. + */ + fun getDownloadCount(manga: Manga): Int { + return cache.getDownloadCount(manga) } /** * Deletes the directory of a downloaded chapter. * - * @param source the source of the chapter. - * @param manga the manga of the chapter. * @param chapter the chapter to delete. + * @param manga the manga of the chapter. + * @param source the source of the chapter. */ - fun deleteChapter(source: Source, manga: Manga, chapter: Chapter) { - provider.findChapterDir(source, manga, chapter)?.delete() + fun deleteChapter(chapter: Chapter, manga: Manga, source: Source) { + provider.findChapterDir(chapter, manga, source)?.delete() + cache.removeChapter(chapter, manga) + } + + /** + * Deletes the directory of a downloaded manga. + * + * @param manga the manga to delete. + * @param source the source of the manga. + */ + fun deleteManga(manga: Manga, source: Source) { + provider.findMangaDir(manga, source)?.delete() + cache.removeManga(manga) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index 894395d11..e70563777 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -40,10 +40,10 @@ class DownloadProvider(private val context: Context) { /** * Returns the download directory for a manga. For internal use only. * - * @param source the source of the manga. * @param manga the manga to query. + * @param source the source of the manga. */ - internal fun getMangaDir(source: Source, manga: Manga): UniFile { + internal fun getMangaDir(manga: Manga, source: Source): UniFile { return downloadsDir .createDirectory(getSourceDirName(source)) .createDirectory(getMangaDirName(manga)) @@ -61,10 +61,10 @@ class DownloadProvider(private val context: Context) { /** * Returns the download directory for a manga if it exists. * - * @param source the source of the manga. * @param manga the manga to query. + * @param source the source of the manga. */ - fun findMangaDir(source: Source, manga: Manga): UniFile? { + fun findMangaDir(manga: Manga, source: Source): UniFile? { val sourceDir = findSourceDir(source) return sourceDir?.findFile(getMangaDirName(manga)) } @@ -72,12 +72,12 @@ class DownloadProvider(private val context: Context) { /** * Returns the download directory for a chapter if it exists. * - * @param source the source of the chapter. - * @param manga the manga of the chapter. * @param chapter the chapter to query. + * @param manga the manga of the chapter. + * @param source the source of the chapter. */ - fun findChapterDir(source: Source, manga: Manga, chapter: Chapter): UniFile? { - val mangaDir = findMangaDir(source, manga) + fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? { + val mangaDir = findMangaDir(manga, source) return mangaDir?.findFile(getChapterDirName(chapter)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 7ffe8c4f8..cbcb2045c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -37,8 +37,11 @@ import uy.kohesive.injekt.injectLazy * * @param context the application context. * @param provider the downloads directory provider. + * @param cache the downloads cache, used to add the downloads to the cache after their completion. */ -class Downloader(private val context: Context, private val provider: DownloadProvider) { +class Downloader(private val context: Context, + private val provider: DownloadProvider, + private val cache: DownloadCache) { /** * Store for persisting downloads across restarts. @@ -222,7 +225,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro // Called in background thread, the operation can be slow with SAF. val chaptersWithoutDir = async { - val mangaDir = provider.findMangaDir(source, manga) + val mangaDir = provider.findMangaDir(manga, source) chapters // Avoid downloading chapters with the same name. @@ -269,7 +272,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro */ private fun downloadChapter(download: Download): Observable { val chapterDirname = provider.getChapterDirName(download.chapter) - val mangaDir = provider.getMangaDir(download.source, download.manga) + val mangaDir = provider.getMangaDir(download.manga, download.source) val tmpDir = mangaDir.createDirectory("${chapterDirname}_tmp") val pageListObservable = if (download.pages == null) { @@ -305,7 +308,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro .toList() .map { _ -> download } // Do after download completes - .doOnNext { ensureSuccessfulDownload(download, tmpDir, chapterDirname) } + .doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) } // If the page list threw, it will resume here .onErrorReturn { error -> download.status = Download.ERROR @@ -411,10 +414,13 @@ class Downloader(private val context: Context, private val provider: DownloadPro * Checks if the download was successful. * * @param download the download to check. + * @param mangaDir the manga directory of the download. * @param tmpDir the directory where the download is currently stored. * @param dirname the real (non temporary) directory name of the download. */ - private fun ensureSuccessfulDownload(download: Download, tmpDir: UniFile, dirname: String) { + private fun ensureSuccessfulDownload(download: Download, mangaDir: UniFile, + tmpDir: UniFile, dirname: String) { + // Ensure that the chapter folder has all the images. val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") } @@ -427,6 +433,7 @@ class Downloader(private val context: Context, private val provider: DownloadPro // Only rename the directory if it's downloaded. if (download.status == Download.DOWNLOADED) { tmpDir.renameTo(dirname) + cache.addChapter(dirname, mangaDir, download.manga) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 44d5f2522..dbb2d606e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.library import android.os.Bundle -import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -107,12 +106,6 @@ class LibraryPresenter( * @param map the map to filter. */ private fun applyFilters(map: LibraryMap): LibraryMap { - // Cached list of downloaded manga directories given a source id. - val mangaDirsForSource = mutableMapOf>() - - // Cached list of downloaded chapter directories for a manga. - val chapterDirectories = mutableMapOf() - val filterDownloaded = preferences.filterDownloaded().getOrDefault() val filterUnread = preferences.filterUnread().getOrDefault() @@ -121,7 +114,7 @@ class LibraryPresenter( val filterFn: (LibraryItem) -> Boolean = f@ { item -> // Filter out manga without source. - val source = sourceManager.get(item.manga.source) ?: return@f false + sourceManager.get(item.manga.source) ?: return@f false // Filter when there isn't unread chapters. if (filterUnread && item.manga.unread == 0) { @@ -132,28 +125,14 @@ class LibraryPresenter( return@f false } - // Filter when the download directory doesn't exist or is null. + // Filter when there are no downloads. if (filterDownloaded) { // Don't bother with directory checking if download count has been set. if (item.downloadCount != -1) { return@f item.downloadCount > 0 } - // Get the directories for the source of the manga. - val dirsForSource = mangaDirsForSource.getOrPut(source.id) { - val sourceDir = downloadManager.findSourceDir(source) - sourceDir?.listFiles()?.associateBy { it.name }.orEmpty() - } - - val mangaDirName = downloadManager.getMangaDirName(item.manga) - val mangaDir = dirsForSource[mangaDirName] ?: return@f false - - val hasDirs = chapterDirectories.getOrPut(item.manga.id!!) { - mangaDir.listFiles()?.isNotEmpty() ?: false - } - if (!hasDirs) { - return@f false - } + return@f downloadManager.getDownloadCount(item.manga) > 0 } true } @@ -177,31 +156,9 @@ class LibraryPresenter( return } - // Cached list of downloaded manga directories given a source id. - val mangaDirsForSource = mutableMapOf>() - - // Cached list of downloaded chapter directories for a manga. - val chapterDirectories = mutableMapOf() - - val downloadCountFn: (LibraryItem) -> Int = f@ { item -> - val source = sourceManager.get(item.manga.source) ?: return@f 0 - - // Get the directories for the source of the manga. - val dirsForSource = mangaDirsForSource.getOrPut(source.id) { - val sourceDir = downloadManager.findSourceDir(source) - sourceDir?.listFiles()?.associateBy { it.name }.orEmpty() - } - val mangaDirName = downloadManager.getMangaDirName(item.manga) - val mangaDir = dirsForSource[mangaDirName] ?: return@f 0 - - chapterDirectories.getOrPut(item.manga.id!!) { - mangaDir.listFiles()?.size ?: 0 - } - } - for ((_, itemList) in map) { for (item in itemList) { - item.downloadCount = downloadCountFn(item) + item.downloadCount = downloadManager.getDownloadCount(item.manga) } } } @@ -360,7 +317,7 @@ class LibraryPresenter( if (deleteChapters) { val source = sourceManager.get(manga.source) as? HttpSource if (source != null) { - downloadManager.findMangaDir(source, manga)?.delete() + downloadManager.deleteManga(manga, source) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 17ca3e055..923aec866 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -128,13 +128,11 @@ class ChaptersPresenter( * @param chapters the list of chapter from the database. */ private fun setDownloadedChapters(chapters: List) { - val files = downloadManager.findMangaDir(source, manga)?.listFiles() ?: return - val cached = mutableMapOf() - files.mapNotNull { it.name } - .mapNotNull { name -> chapters.find { - name == cached.getOrPut(it) { downloadManager.getChapterDirName(it) } - } } - .forEach { it.status = Download.DOWNLOADED } + for (chapter in chapters) { + if (downloadManager.isChapterDownloaded(chapter, manga)) { + chapter.status = Download.DOWNLOADED + } + } } /** @@ -283,7 +281,7 @@ class ChaptersPresenter( */ private fun deleteChapter(chapter: ChapterItem) { downloadManager.queue.remove(chapter) - downloadManager.deleteChapter(source, manga, chapter) + downloadManager.deleteChapter(chapter, manga, source) chapter.status = Download.NOT_DOWNLOADED chapter.download = null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index 40bd97b48..2f97c7e68 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -115,14 +115,14 @@ class MangaInfoPresenter( * Returns true if the manga has any downloads. */ fun hasDownloads(): Boolean { - return downloadManager.findMangaDir(source, manga) != null + return downloadManager.getDownloadCount(manga) > 0 } /** * Deletes all the downloads for the manga. */ fun deleteDownloads() { - downloadManager.findMangaDir(source, manga)?.delete() + downloadManager.deleteManga(manga, source) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt index 88b2b184f..5c937715b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt @@ -79,7 +79,7 @@ class ChapterLoader( private fun retrievePageList(chapter: ReaderChapter) = Observable.just(chapter) .flatMap { // Check if the chapter is downloaded. - chapter.isDownloaded = downloadManager.findChapterDir(source, manga, chapter) != null + chapter.isDownloaded = downloadManager.isChapterDownloaded(chapter, manga, true) if (chapter.isDownloaded) { // Fetch the page list from disk. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 973d57330..ef654de44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -411,7 +411,7 @@ class ReaderPresenter( fun deleteChapter(chapter: ReaderChapter, manga: Manga) { chapter.isDownloaded = false chapter.pages?.forEach { it.status == Page.QUEUE } - downloadManager.deleteChapter(source, manga, chapter) + downloadManager.deleteChapter(chapter, manga, source) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index 3564a8ba1..3902ad9b0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.os.Bundle -import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.MangaChapter import eu.kanade.tachiyomi.data.download.DownloadManager @@ -114,36 +113,11 @@ class RecentChaptersPresenter( * @param items the list of chapter from the database. */ private fun setDownloadedChapters(items: List) { - // Cached list of downloaded manga directories. Directory name is also cached because - // it's slow when using SAF. - val mangaDirsForSource = mutableMapOf>() - - // Cached list of downloaded chapter directories for a manga. - val chapterDirsForManga = mutableMapOf>() - for (item in items) { val manga = item.manga val chapter = item.chapter - val source = sourceManager.get(manga.source) ?: continue - // Get the directories for the source of the manga. - val dirsForSource = mangaDirsForSource.getOrPut(source.id) { - val sourceDir = downloadManager.findSourceDir(source) - sourceDir?.listFiles()?.associateBy { it.name }.orEmpty() - } - - // Get the manga directory in the source or continue. - val mangaDirName = downloadManager.getMangaDirName(manga) - val mangaDir = dirsForSource[mangaDirName] ?: continue - - // Get the directories for the manga. - val chapterDirs = chapterDirsForManga.getOrPut(manga.id!!) { - mangaDir.listFiles()?.associateBy { it.name }.orEmpty() - } - - // Assign the download if the directory exists. - val chapterDirName = downloadManager.getChapterDirName(chapter) - if (chapterDirName in chapterDirs) { + if (downloadManager.isChapterDownloaded(chapter, manga)) { item.status = Download.DOWNLOADED } } @@ -216,7 +190,7 @@ class RecentChaptersPresenter( private fun deleteChapter(item: RecentChapterItem) { val source = sourceManager.get(item.manga.source) ?: return downloadManager.queue.remove(item.chapter) - downloadManager.deleteChapter(source, item.manga, item.chapter) + downloadManager.deleteChapter(item.chapter, item.manga, source) item.status = Download.NOT_DOWNLOADED item.download = null } From 34eb1331a32f34bef86cdfafb0a1c939c741c665 Mon Sep 17 00:00:00 2001 From: inorichi Date: Tue, 28 Nov 2017 01:12:45 +0100 Subject: [PATCH 3/8] Update build tools in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c67ceaa11..d425de4f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: android android: components: - - build-tools-26.0.2 + - build-tools-27.0.1 - android-26 - extra-android-m2repository - extra-google-m2repository From 80fd49d60bd23d8c2969ca6338504b70f783dee9 Mon Sep 17 00:00:00 2001 From: ddmgy Date: Tue, 28 Nov 2017 03:48:27 -0500 Subject: [PATCH 4/8] FIx Batoto issues with logging in and loading lists/pages. (#1088) --- .../java/eu/kanade/tachiyomi/source/online/english/Batoto.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt index 468663c38..047393f99 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.selectText import okhttp3.FormBody import okhttp3.HttpUrl +import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document @@ -48,6 +49,8 @@ class Batoto : ParsedHttpSource(), LoginSource { private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE) + override val client: OkHttpClient = network.cloudflareClient + override fun headersBuilder() = super.headersBuilder() .add("Cookie", "lang_option=English") From ac17335dbe002a8ffd49ecae0a8fd9838fcd0dd8 Mon Sep 17 00:00:00 2001 From: Bram van de Kerkhof Date: Tue, 28 Nov 2017 22:55:50 +0100 Subject: [PATCH 5/8] Fix automatic backups (#1074) * Fix automatic backups * Small fixes * small fixes --- .../data/backup/BackupCreateService.kt | 9 ++++--- .../tachiyomi/data/backup/BackupCreatorJob.kt | 3 +-- .../ui/setting/SettingsBackupController.kt | 25 +++++++++---------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt index 9f3d577e7..cf3d2535c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt @@ -46,7 +46,7 @@ class BackupCreateService : IntentService(NAME) { * Make a backup from library * * @param context context of application - * @param path path of Uri + * @param uri path of Uri * @param flags determines what to backup * @param isJob backup called from job */ @@ -80,7 +80,7 @@ class BackupCreateService : IntentService(NAME) { * @param uri path of Uri * @param isJob backup called from job */ - fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) { + private fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) { // Create root object val root = JsonObject() @@ -113,8 +113,9 @@ class BackupCreateService : IntentService(NAME) { try { // When BackupCreatorJob if (isJob) { - // Get dir of file - val dir = UniFile.fromUri(this, uri) + // Get dir of file and create + var dir = UniFile.fromUri(this, uri) + dir = dir.createDirectory("automatic") // Delete older backups val numberOfBackups = backupManager.numberOfBackups() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index 2e33f642c..49c9aaecf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt @@ -8,13 +8,12 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.io.File class BackupCreatorJob : Job() { override fun onRunJob(params: Params): Result { val preferences = Injekt.get() - val uri = Uri.fromFile(File(preferences.backupsDirectory().getOrDefault())) + val uri = Uri.parse(preferences.backupsDirectory().getOrDefault()) val flags = BackupCreateService.BACKUP_ALL BackupCreateService.makeBackup(context, uri, flags, true) return Result.SUCCESS diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index 6c1994835..f8c1c930b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.setting -import android.Manifest.permission.READ_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.app.Dialog @@ -135,7 +134,7 @@ class SettingsBackupController : SettingsController() { preferences.backupsDirectory().asObservable() .subscribeUntilDestroy { path -> val dir = UniFile.fromUri(context, Uri.parse(path)) - summary = dir.filePath ?: path + summary = dir.filePath + "/automatic" } } val backupNumber = intListPreference { @@ -160,19 +159,19 @@ class SettingsBackupController : SettingsController() { when (requestCode) { CODE_BACKUP_DIR -> if (data != null && resultCode == Activity.RESULT_OK) { val activity = activity ?: return - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val uri = Uri.fromFile(File(data.data.path)) - preferences.backupsDirectory().set(uri.toString()) - } else { - val uri = data.data + // Get uri of backup folder. + val uri = data.data + + // Get UriPermission so it's possible to write files post kitkat. + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION activity.contentResolver.takePersistableUriPermission(uri, flags) - - val file = UniFile.fromUri(activity, uri) - preferences.backupsDirectory().set(file.uri.toString()) } + + // Set backup Uri. + preferences.backupsDirectory().set(uri.toString()) } CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) { val activity = activity ?: return @@ -240,7 +239,7 @@ class SettingsBackupController : SettingsController() { .itemsDisabledIndices(0) .itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ -> var flags = 0 - for (i in 1..positions.size - 1) { + for (i in 1 until positions.size) { when (positions[i]) { 1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY 2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER @@ -281,7 +280,7 @@ class SettingsBackupController : SettingsController() { override fun onCreateDialog(savedViewState: Bundle?): Dialog { val activity = activity!! - val unifile = UniFile.fromUri(activity, args.getParcelable(KEY_URI)) + val unifile = UniFile.fromUri(activity, args.getParcelable(KEY_URI)) return MaterialDialog.Builder(activity) .title(R.string.backup_created) .content(activity.getString(R.string.file_saved, unifile.filePath)) @@ -315,7 +314,7 @@ class SettingsBackupController : SettingsController() { val context = applicationContext if (context != null) { RestoringBackupDialog().showDialog(router, TAG_RESTORING_BACKUP_DIALOG) - BackupRestoreService.start(context, args.getParcelable(KEY_URI)) + BackupRestoreService.start(context, args.getParcelable(KEY_URI)) } } .build() From e1582bd73af126edeadc4e10935b83b17f9529f0 Mon Sep 17 00:00:00 2001 From: inorichi Date: Tue, 28 Nov 2017 23:58:31 +0100 Subject: [PATCH 6/8] Minor changes to download cache. Also keep the library view, as recreation is expensive --- .../tachiyomi/data/download/DownloadCache.kt | 21 +++++++++---------- .../tachiyomi/ui/library/LibraryController.kt | 2 +- .../main/res/layout/chapters_controller.xml | 10 ++++----- app/src/main/res/layout/manga_controller.xml | 3 ++- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index 7846ff7ae..d4b80ad65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit class DownloadCache(private val context: Context, private val provider: DownloadProvider, private val sourceManager: SourceManager = Injekt.get(), - preferences: PreferencesHelper = Injekt.get()) { + private val preferences: PreferencesHelper = Injekt.get()) { /** * The interval after which this cache should be invalidated. 1 hour shouldn't cause major @@ -42,24 +42,23 @@ class DownloadCache(private val context: Context, /** * The root directory for downloads. */ - private var rootDir = setRootDir(preferences.downloadsDirectory().getOrDefault()) + private var rootDir = RootDirectory(getDirectoryFromPreference()) init { - setRootDir(preferences.downloadsDirectory().getOrDefault()) preferences.downloadsDirectory().asObservable() .skip(1) - .subscribe { setRootDir(it) } + .subscribe { + lastRenew = 0L // invalidate cache + rootDir = RootDirectory(getDirectoryFromPreference()) + } } /** - * Sets the root downloads directory and invalidates the cache. - * - * @param directory the downloads directory in [Uri] format. + * Returns the downloads directory from the user's preferences. */ - private fun setRootDir(directory: String): RootDirectory { - rootDir = RootDirectory(UniFile.fromUri(context, Uri.parse(directory))) - lastRenew = 0L - return rootDir + private fun getDirectoryFromPreference(): UniFile { + val dir = preferences.downloadsDirectory().getOrDefault() + return UniFile.fromUri(context, Uri.parse(dir)) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index a670a54c3..99c4fa897 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -429,7 +429,7 @@ class LibraryController( presenter.onOpenManga() router.pushController(RouterTransaction.with(MangaController(manga)) - .pushChangeHandler(FadeChangeHandler()) + .pushChangeHandler(FadeChangeHandler(false)) .popChangeHandler(FadeChangeHandler())) } diff --git a/app/src/main/res/layout/chapters_controller.xml b/app/src/main/res/layout/chapters_controller.xml index f3f521de9..2f2d19d56 100644 --- a/app/src/main/res/layout/chapters_controller.xml +++ b/app/src/main/res/layout/chapters_controller.xml @@ -1,11 +1,11 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> \ No newline at end of file + android:layout_height="match_parent" + android:background="?android:attr/colorBackground" /> \ No newline at end of file From 4853ff7eee720eaf02531c53a0ed9b963797d881 Mon Sep 17 00:00:00 2001 From: inorichi Date: Wed, 29 Nov 2017 10:05:33 +0100 Subject: [PATCH 7/8] Resubscribe to library when a change of type enter occurs. Resolves #1093 --- .../eu/kanade/tachiyomi/ui/library/LibraryController.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 99c4fa897..fce944223 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -167,14 +167,10 @@ class LibraryController( super.onChangeStarted(handler, type) if (type.isEnter) { activity?.tabs?.setupWithViewPager(view?.view_pager) + presenter.subscribeLibrary() } } - override fun onAttach(view: View) { - super.onAttach(view) - presenter.subscribeLibrary() - } - override fun onDestroyView(view: View) { super.onDestroyView(view) adapter = null From b91d8e4c193c20a37ad790c4a804981250acc2de Mon Sep 17 00:00:00 2001 From: inorichi Date: Wed, 29 Nov 2017 18:49:22 +0100 Subject: [PATCH 8/8] Release 0.6.5 --- app/build.gradle | 4 ++-- app/src/main/res/raw/changelog_release.xml | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b8dbf6ea5..8a54be230 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,8 +38,8 @@ android { minSdkVersion 16 targetSdkVersion 26 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - versionCode 27 - versionName "0.6.4" + versionCode 28 + versionName "0.6.5" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" diff --git a/app/src/main/res/raw/changelog_release.xml b/app/src/main/res/raw/changelog_release.xml index a7897d3c3..05e14ae52 100644 --- a/app/src/main/res/raw/changelog_release.xml +++ b/app/src/main/res/raw/changelog_release.xml @@ -1,5 +1,15 @@ + + Added a download cache for faster navigation. + + Enabled Cloudflare for Batoto. + + Fixed some issues with automatic backups. + + Fixed a bootloop issue with devices running Cyanogenmod 12 or 13. + + Added a global search feature with a new catalogue screen.