5bdb945406
* fix: truncate filenames by bytes instead of characters to avoid IOException File name too long * add a CHANGELOG.md entry.
81 lines
2.7 KiB
Kotlin
81 lines
2.7 KiB
Kotlin
package xyz.nulldev.androidcompat.util
|
|
|
|
/*
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/4cefbce7c34e724b409b6ba127f3c6c5c346ad8d/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt
|
|
object SafePath {
|
|
private const val MAX_FILENAME_CHARS = 240
|
|
private const val MAX_FILENAME_UTF8_BYTES = 240
|
|
|
|
/**
|
|
* Mutate the given filename to make it valid for a FAT filesystem,
|
|
* replacing any invalid characters with "_". This method doesn't allow hidden files (starting
|
|
* with a dot), but you can manually add it later.
|
|
*/
|
|
fun buildValidFilename(origName: String): String {
|
|
val name = origName.trim('.', ' ')
|
|
if (name.isEmpty()) {
|
|
return "(invalid)"
|
|
}
|
|
val sb = StringBuilder(name.length)
|
|
name.forEach { c ->
|
|
if (isValidFatFilenameChar(c)) {
|
|
sb.append(c)
|
|
} else {
|
|
sb.append('_')
|
|
}
|
|
}
|
|
|
|
return truncateFilename(sb.toString())
|
|
}
|
|
|
|
private fun truncateFilename(filename: String): String {
|
|
// Keep a safety margin under common filesystem limits and satisfy both
|
|
// character count and UTF-8 byte-length constraints.
|
|
val output = StringBuilder(minOf(filename.length, MAX_FILENAME_CHARS))
|
|
var usedBytes = 0
|
|
var index = 0
|
|
|
|
while (index < filename.length && output.length < MAX_FILENAME_CHARS) {
|
|
val codePoint = Character.codePointAt(filename, index)
|
|
val codePointBytes = utf8ByteCount(codePoint)
|
|
|
|
if (usedBytes + codePointBytes > MAX_FILENAME_UTF8_BYTES) {
|
|
break
|
|
}
|
|
|
|
output.appendCodePoint(codePoint)
|
|
usedBytes += codePointBytes
|
|
index += Character.charCount(codePoint)
|
|
}
|
|
|
|
return output.toString()
|
|
}
|
|
|
|
private fun utf8ByteCount(codePoint: Int): Int =
|
|
when {
|
|
codePoint <= 0x7f -> 1
|
|
codePoint <= 0x7ff -> 2
|
|
codePoint <= 0xffff -> 3
|
|
else -> 4
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
}
|
|
}
|