2016-11-24 21:42:01 +01:00
|
|
|
package eu.kanade.tachiyomi.util
|
|
|
|
|
2017-01-29 20:48:55 +01:00
|
|
|
import android.content.Context
|
2017-03-11 16:00:07 +01:00
|
|
|
import android.content.Intent
|
|
|
|
import android.net.Uri
|
2017-03-03 18:45:25 +01:00
|
|
|
import android.os.Build
|
2017-01-29 20:48:55 +01:00
|
|
|
import android.os.Environment
|
|
|
|
import android.support.v4.content.ContextCompat
|
|
|
|
import android.support.v4.os.EnvironmentCompat
|
2016-11-24 21:42:01 +01:00
|
|
|
import java.io.File
|
2017-01-29 20:48:55 +01:00
|
|
|
import java.io.InputStream
|
|
|
|
import java.net.URLConnection
|
2016-11-24 21:42:01 +01:00
|
|
|
import java.security.MessageDigest
|
|
|
|
import java.security.NoSuchAlgorithmException
|
|
|
|
|
|
|
|
object DiskUtil {
|
|
|
|
|
2017-01-29 20:48:55 +01:00
|
|
|
fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean {
|
2017-04-07 20:32:22 +02:00
|
|
|
val contentType = try {
|
|
|
|
URLConnection.guessContentTypeFromName(name)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
null
|
|
|
|
} ?: openStream?.let { findImageMime(it) }
|
2017-01-29 20:48:55 +01:00
|
|
|
|
2017-02-02 20:21:38 +01:00
|
|
|
return contentType?.startsWith("image/") ?: false
|
|
|
|
}
|
|
|
|
|
2017-02-02 23:44:25 +01:00
|
|
|
fun findImageMime(openStream: () -> InputStream): String? {
|
2017-02-02 20:21:38 +01:00
|
|
|
try {
|
|
|
|
openStream().buffered().use {
|
2017-02-02 23:44:25 +01:00
|
|
|
val bytes = ByteArray(8)
|
2017-01-29 20:48:55 +01:00
|
|
|
it.mark(bytes.size)
|
2017-02-02 20:21:38 +01:00
|
|
|
val length = it.read(bytes, 0, bytes.size)
|
2017-01-29 20:48:55 +01:00
|
|
|
it.reset()
|
|
|
|
if (length == -1)
|
2017-02-02 20:21:38 +01:00
|
|
|
return null
|
2017-01-29 20:48:55 +01:00
|
|
|
if (bytes[0] == 'G'.toByte() && bytes[1] == 'I'.toByte() && bytes[2] == 'F'.toByte() && bytes[3] == '8'.toByte()) {
|
2017-02-02 20:21:38 +01:00
|
|
|
return "image/gif"
|
2017-01-29 20:48:55 +01:00
|
|
|
} else if (bytes[0] == 0x89.toByte() && bytes[1] == 0x50.toByte() && bytes[2] == 0x4E.toByte()
|
|
|
|
&& bytes[3] == 0x47.toByte() && bytes[4] == 0x0D.toByte() && bytes[5] == 0x0A.toByte()
|
|
|
|
&& bytes[6] == 0x1A.toByte() && bytes[7] == 0x0A.toByte()) {
|
2017-02-02 20:21:38 +01:00
|
|
|
return "image/png"
|
2017-01-29 20:48:55 +01:00
|
|
|
} else if (bytes[0] == 0xFF.toByte() && bytes[1] == 0xD8.toByte() && bytes[2] == 0xFF.toByte()) {
|
2017-02-02 23:44:25 +01:00
|
|
|
return "image/jpeg"
|
2017-01-29 20:48:55 +01:00
|
|
|
} else if (bytes[0] == 'W'.toByte() && bytes[1] == 'E'.toByte() && bytes[2] == 'B'.toByte() && bytes[3] == 'P'.toByte()) {
|
2017-02-02 20:21:38 +01:00
|
|
|
return "image/webp"
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(e: Exception) {
|
|
|
|
}
|
2017-02-02 20:21:38 +01:00
|
|
|
return null
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
2016-11-24 21:42:01 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-01-29 20:48:55 +01:00
|
|
|
/**
|
|
|
|
* Returns the root folders of all the available external storages.
|
|
|
|
*/
|
2017-03-03 18:45:25 +01:00
|
|
|
fun getExternalStorages(context: Context): Collection<File> {
|
|
|
|
val directories = mutableSetOf<File>()
|
|
|
|
directories += ContextCompat.getExternalFilesDirs(context, null)
|
2017-01-29 20:48:55 +01:00
|
|
|
.filterNotNull()
|
|
|
|
.mapNotNull {
|
|
|
|
val file = File(it.absolutePath.substringBefore("/Android/"))
|
|
|
|
val state = EnvironmentCompat.getStorageState(file)
|
|
|
|
if (state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY) {
|
|
|
|
file
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
2017-03-03 18:45:25 +01:00
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < 21) {
|
|
|
|
val extStorages = System.getenv("SECONDARY_STORAGE")
|
|
|
|
if (extStorages != null) {
|
|
|
|
directories += extStorages.split(":").map(::File)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return directories
|
2017-01-29 20:48:55 +01:00
|
|
|
}
|
|
|
|
|
2017-03-11 16:00:07 +01:00
|
|
|
/**
|
|
|
|
* Scans the given file so that it can be shown in gallery apps, for example.
|
|
|
|
*/
|
|
|
|
fun scanMedia(context: Context, file: File) {
|
2017-05-14 00:45:14 +02:00
|
|
|
scanMedia(context, Uri.fromFile(file))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scans the given file so that it can be shown in gallery apps, for example.
|
|
|
|
*/
|
|
|
|
fun scanMedia(context: Context, uri: Uri) {
|
2017-03-11 16:00:07 +01:00
|
|
|
val action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
|
|
|
Intent.ACTION_MEDIA_MOUNTED
|
|
|
|
} else {
|
|
|
|
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
|
|
|
|
}
|
|
|
|
val mediaScanIntent = Intent(action)
|
2017-05-14 00:45:14 +02:00
|
|
|
mediaScanIntent.data = uri
|
2017-03-11 16:00:07 +01:00
|
|
|
context.sendBroadcast(mediaScanIntent)
|
|
|
|
}
|
|
|
|
|
2016-11-24 21:42:01 +01:00
|
|
|
/**
|
|
|
|
* Mutate the given filename to make it valid for a FAT filesystem,
|
2017-01-29 20:48:55 +01:00
|
|
|
* replacing any invalid characters with "_". This method doesn't allow hidden files (starting
|
2016-11-24 21:42:01 +01:00
|
|
|
* with a dot), but you can manually add it later.
|
|
|
|
*/
|
2016-11-24 21:50:02 +01:00
|
|
|
fun buildValidFilename(origName: String): String {
|
2016-11-24 21:42:01 +01:00
|
|
|
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
|
2016-11-26 12:34:54 +01:00
|
|
|
// ext4 through a FUSE layer, so use that limit minus 15 reserved characters.
|
|
|
|
return sb.toString().take(240)
|
2016-11-24 21:42:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|