Fix #547
This commit is contained in:
parent
87281d34c1
commit
93e244b4c4
6 changed files with 86 additions and 115 deletions
|
@ -6,7 +6,7 @@ import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page
|
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 eu.kanade.tachiyomi.util.saveTo
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.Okio
|
import okio.Okio
|
||||||
|
@ -70,7 +70,7 @@ class ChapterCache(private val context: Context) {
|
||||||
* @return real size of directory.
|
* @return real size of directory.
|
||||||
*/
|
*/
|
||||||
private val realSize: Long
|
private val realSize: Long
|
||||||
get() = DiskUtils.getDirectorySize(cacheDir)
|
get() = DiskUtil.getDirectorySize(cacheDir)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns real size of directory in human readable format.
|
* Returns real size of directory in human readable format.
|
||||||
|
@ -107,7 +107,7 @@ class ChapterCache(private val context: Context) {
|
||||||
fun getPageListFromCache(chapterUrl: String): Observable<List<Page>> {
|
fun getPageListFromCache(chapterUrl: String): Observable<List<Page>> {
|
||||||
return Observable.fromCallable<List<Page>> {
|
return Observable.fromCallable<List<Page>> {
|
||||||
// Get the key for the chapter.
|
// 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
|
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
||||||
diskCache.get(key).use {
|
diskCache.get(key).use {
|
||||||
|
@ -130,7 +130,7 @@ class ChapterCache(private val context: Context) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get editor from md5 key.
|
// Get editor from md5 key.
|
||||||
val key = DiskUtils.hashKeyForDisk(chapterUrl)
|
val key = DiskUtil.hashKeyForDisk(chapterUrl)
|
||||||
editor = diskCache.edit(key) ?: return
|
editor = diskCache.edit(key) ?: return
|
||||||
|
|
||||||
// Write chapter urls to cache.
|
// Write chapter urls to cache.
|
||||||
|
@ -157,7 +157,7 @@ class ChapterCache(private val context: Context) {
|
||||||
*/
|
*/
|
||||||
fun isImageInCache(imageUrl: String): Boolean {
|
fun isImageInCache(imageUrl: String): Boolean {
|
||||||
try {
|
try {
|
||||||
return diskCache.get(DiskUtils.hashKeyForDisk(imageUrl)) != null
|
return diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)) != null
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ class ChapterCache(private val context: Context) {
|
||||||
fun getImagePath(imageUrl: String): File? {
|
fun getImagePath(imageUrl: String): File? {
|
||||||
try {
|
try {
|
||||||
// Get file from md5 key.
|
// Get file from md5 key.
|
||||||
val imageName = DiskUtils.hashKeyForDisk(imageUrl) + ".0"
|
val imageName = DiskUtil.hashKeyForDisk(imageUrl) + ".0"
|
||||||
return File(diskCache.directory, imageName)
|
return File(diskCache.directory, imageName)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
return null
|
return null
|
||||||
|
@ -191,7 +191,7 @@ class ChapterCache(private val context: Context) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get editor from md5 key.
|
// 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")
|
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key")
|
||||||
|
|
||||||
// Get OutputStream and write image with Okio.
|
// Get OutputStream and write image with Okio.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.data.cache
|
package eu.kanade.tachiyomi.data.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.util.DiskUtils
|
import eu.kanade.tachiyomi.util.DiskUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -29,7 +29,7 @@ class CoverCache(private val context: Context) {
|
||||||
* @return cover image.
|
* @return cover image.
|
||||||
*/
|
*/
|
||||||
fun getCoverFile(thumbnailUrl: String): File {
|
fun getCoverFile(thumbnailUrl: String): File {
|
||||||
return File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl))
|
return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.source.Source
|
import eu.kanade.tachiyomi.data.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.DiskUtil
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,7 +83,7 @@ class DownloadProvider(private val context: Context) {
|
||||||
* @param manga the manga to query.
|
* @param manga the manga to query.
|
||||||
*/
|
*/
|
||||||
fun getMangaDirName(manga: Manga): String {
|
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.
|
* @param chapter the chapter to query.
|
||||||
*/
|
*/
|
||||||
fun getChapterDirName(chapter: Chapter): String {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.data.source.model.Page
|
||||||
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
import eu.kanade.tachiyomi.data.source.online.OnlineSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.ui.reader.notification.ImageNotifier
|
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.RetryWithDelay
|
||||||
import eu.kanade.tachiyomi.util.SharedData
|
import eu.kanade.tachiyomi.util.SharedData
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
|
@ -577,8 +578,9 @@ class ReaderPresenter : BasePresenter<ReaderActivity>() {
|
||||||
val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg"
|
val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg"
|
||||||
|
|
||||||
// Destination file.
|
// 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 ->
|
context.contentResolver.openInputStream(page.uri).use { input ->
|
||||||
destFile.outputStream().use { output ->
|
destFile.outputStream().use { output ->
|
||||||
|
|
70
app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
Normal file
70
app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Reference in a new issue