diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index cc594d7fb..8609097eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -6,7 +6,7 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.jakewharton.disklrucache.DiskLruCache import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.util.DiskUtils +import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.saveTo import okhttp3.Response import okio.Okio @@ -70,7 +70,7 @@ class ChapterCache(private val context: Context) { * @return real size of directory. */ private val realSize: Long - get() = DiskUtils.getDirectorySize(cacheDir) + get() = DiskUtil.getDirectorySize(cacheDir) /** * Returns real size of directory in human readable format. @@ -107,7 +107,7 @@ class ChapterCache(private val context: Context) { fun getPageListFromCache(chapterUrl: String): Observable> { return Observable.fromCallable> { // Get the key for the chapter. - val key = DiskUtils.hashKeyForDisk(chapterUrl) + val key = DiskUtil.hashKeyForDisk(chapterUrl) // Convert JSON string to list of objects. Throws an exception if snapshot is null diskCache.get(key).use { @@ -130,7 +130,7 @@ class ChapterCache(private val context: Context) { try { // Get editor from md5 key. - val key = DiskUtils.hashKeyForDisk(chapterUrl) + val key = DiskUtil.hashKeyForDisk(chapterUrl) editor = diskCache.edit(key) ?: return // Write chapter urls to cache. @@ -157,7 +157,7 @@ class ChapterCache(private val context: Context) { */ fun isImageInCache(imageUrl: String): Boolean { try { - return diskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null + return diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)) != null } catch (e: IOException) { return false } @@ -171,7 +171,7 @@ class ChapterCache(private val context: Context) { fun getImagePath(imageUrl: String): File? { try { // Get file from md5 key. - val imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0" + val imageName = DiskUtil.hashKeyForDisk(imageUrl) + ".0" return File(diskCache.directory, imageName) } catch (e: IOException) { return null @@ -191,7 +191,7 @@ class ChapterCache(private val context: Context) { try { // Get editor from md5 key. - val key = DiskUtils.hashKeyForDisk(imageUrl) + val key = DiskUtil.hashKeyForDisk(imageUrl) editor = diskCache.edit(key) ?: throw IOException("Unable to edit key") // Get OutputStream and write image with Okio. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index 9e1b1556c..ca7a1de50 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.cache import android.content.Context -import eu.kanade.tachiyomi.util.DiskUtils +import eu.kanade.tachiyomi.util.DiskUtil import java.io.File import java.io.IOException import java.io.InputStream @@ -29,7 +29,7 @@ class CoverCache(private val context: Context) { * @return cover image. */ fun getCoverFile(thumbnailUrl: String): File { - return File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl)) + return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl)) } /** 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 2e9182156..78230041d 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 @@ -7,6 +7,7 @@ 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.source.Source +import eu.kanade.tachiyomi.util.DiskUtil import uy.kohesive.injekt.injectLazy /** @@ -82,7 +83,7 @@ class DownloadProvider(private val context: Context) { * @param manga the manga to query. */ fun getMangaDirName(manga: Manga): String { - return buildValidFatFilename(manga.title.trim('.', ' ')) + return DiskUtil.buildValidFatFilename(manga.title) } /** @@ -91,40 +92,7 @@ class DownloadProvider(private val context: Context) { * @param chapter the chapter to query. */ fun getChapterDirName(chapter: Chapter): String { - return buildValidFatFilename(chapter.name.trim('.', ' ')) + return DiskUtil.buildValidFatFilename(chapter.name) } - /** - * Mutate the given filename to make it valid for a FAT filesystem, - * replacing any invalid characters with "_". - */ - private fun buildValidFatFilename(name: String): String { - if (name.isNullOrEmpty()) { - return "(invalid)" - } - val res = StringBuilder(name.length) - name.forEach { c -> - if (isValidFatFilenameChar(c)) { - res.append(c) - } else { - res.append('_') - } - } - // Even though vfat allows 255 UCS-2 chars, we might eventually write to - // ext4 through a FUSE layer, so use that limit minus 5 reserved characters. - return res.toString().take(250) - } - - /** - * Returns true if the given character is a valid filename character, false otherwise. - */ - private fun isValidFatFilenameChar(c: Char): Boolean { - if (0x00.toChar() <= c && c <= 0x1f.toChar()) { - return false - } - when (c) { - '"', '*', '/', ':', '<', '>', '?', '\\', '|', 0x7f.toChar() -> return false - else -> return true - } - } } \ No newline at end of file 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 516959261..f99b7ada9 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 @@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.reader.notification.ImageNotifier +import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.RetryWithDelay import eu.kanade.tachiyomi.util.SharedData import eu.kanade.tachiyomi.util.toast @@ -577,8 +578,9 @@ class ReaderPresenter : BasePresenter() { val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg" // Destination file. - val destFile = File(destDir, manga.title + " - " + chapter.name + - " - " + (page.index + 1) + ".$ext") + + val filename = "${manga.title} - ${chapter.name} - ${page.index + 1}.$ext" + val destFile = File(destDir, DiskUtil.buildValidFatFilename(filename)) context.contentResolver.openInputStream(page.uri).use { input -> destFile.outputStream().use { output -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt new file mode 100644 index 000000000..13c8126e5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt @@ -0,0 +1,70 @@ +package eu.kanade.tachiyomi.util + +import java.io.File +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + +object DiskUtil { + + fun hashKeyForDisk(key: String): String { + return try { + val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) + val sb = StringBuilder() + bytes.forEach { byte -> + sb.append(Integer.toHexString(byte.toInt() and 0xFF or 0x100).substring(1, 3)) + } + sb.toString() + } catch (e: NoSuchAlgorithmException) { + key.hashCode().toString() + } + } + + fun getDirectorySize(f: File): Long { + var size: Long = 0 + if (f.isDirectory) { + for (file in f.listFiles()) { + size += getDirectorySize(file) + } + } else { + size = f.length() + } + return size + } + + /** + * Mutate the given filename to make it valid for a FAT filesystem, + * replacing any invalid characters with "_". This method doesn't allow private files (starting + * with a dot), but you can manually add it later. + */ + fun buildValidFatFilename(origName: String): String { + val name = origName.trim('.', ' ') + if (name.isNullOrEmpty()) { + return "(invalid)" + } + val sb = StringBuilder(name.length) + name.forEach { c -> + if (isValidFatFilenameChar(c)) { + sb.append(c) + } else { + sb.append('_') + } + } + // Even though vfat allows 255 UCS-2 chars, we might eventually write to + // ext4 through a FUSE layer, so use that limit minus 5 reserved characters. + return sb.toString().take(250) + } + + /** + * Returns true if the given character is a valid filename character, false otherwise. + */ + private fun isValidFatFilenameChar(c: Char): Boolean { + if (0x00.toChar() <= c && c <= 0x1f.toChar()) { + return false + } + return when (c) { + '"', '*', '/', ':', '<', '>', '?', '\\', '|', 0x7f.toChar() -> false + else -> true + } + } +} + diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtils.java b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtils.java deleted file mode 100644 index 53011786b..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package eu.kanade.tachiyomi.util; - -import java.io.File; -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public final class DiskUtils { - - private DiskUtils() { - throw new AssertionError(); - } - - public static String hashKeyForDisk(String key) { - String cacheKey; - try { - final MessageDigest mDigest = MessageDigest.getInstance("MD5"); - mDigest.update(key.getBytes()); - cacheKey = bytesToHexString(mDigest.digest()); - } catch (NoSuchAlgorithmException e) { - cacheKey = String.valueOf(key.hashCode()); - } - return cacheKey; - } - - private static String bytesToHexString(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - String hex = Integer.toHexString(0xFF & bytes[i]); - if (hex.length() == 1) { - sb.append('0'); - } - sb.append(hex); - } - return sb.toString(); - } - - public static void deleteFiles(File inputFile) { - if (inputFile.isDirectory()) { - for (File childFile : inputFile.listFiles()) { - deleteFiles(childFile); - } - } - - //noinspection ResultOfMethodCallIgnored - inputFile.delete(); - } - - public static synchronized void createDirectory(File directory) throws IOException { - if (!directory.exists() && !directory.mkdirs()) { - throw new IOException("Failed creating directory"); - } - - } - - public static long getDirectorySize(File f) { - long size = 0; - if (f.isDirectory()) { - for (File file : f.listFiles()) { - size += getDirectorySize(file); - } - } else { - size=f.length(); - } - return size; - } - -} -