forked from hardcoresushi/DroidFS
Natural file name sorting
This commit is contained in:
parent
f15b17c936
commit
95eed07719
@ -151,7 +151,8 @@ Thanks to these open source projects that DroidFS uses:
|
||||
|
||||
### Modified code:
|
||||
- [gocryptfs](https://github.com/rfjakob/gocryptfs) to encrypt your data
|
||||
### Borrowed code:
|
||||
- [MaterialFiles](https://github.com/zhanghai/MaterialFiles) for kotlin natural sorting implementation
|
||||
### Libraries:
|
||||
- [Cyanea](https://github.com/jaredrummler/Cyanea) to customize UI
|
||||
- [Glide](https://github.com/bumptech/glide/) to display pictures
|
||||
- [ExoPlayer](https://github.com/google/ExoPlayer) to play media files
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Code borrowed from the awesome Material Files app (https://github.com/zhanghai/MaterialFiles)
|
||||
*
|
||||
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
|
||||
package sushi.hardcore.droidfs.collation
|
||||
|
||||
import kotlin.math.min
|
||||
|
||||
class ByteString internal constructor(
|
||||
private val bytes: ByteArray
|
||||
) : Comparable<ByteString> {
|
||||
|
||||
fun borrowBytes(): ByteArray = bytes
|
||||
|
||||
private var stringCache: String? = null
|
||||
|
||||
override fun toString(): String {
|
||||
// We are okay with the potential race condition here.
|
||||
var string = stringCache
|
||||
if (string == null) {
|
||||
// String() uses replacement char instead of throwing exception.
|
||||
string = String(bytes)
|
||||
stringCache = string
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
if (javaClass != other?.javaClass) {
|
||||
return false
|
||||
}
|
||||
other as ByteString
|
||||
return bytes contentEquals other.bytes
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = bytes.contentHashCode()
|
||||
|
||||
override fun compareTo(other: ByteString): Int = bytes.compareTo(other.bytes)
|
||||
|
||||
private fun ByteArray.compareTo(other: ByteArray): Int {
|
||||
val size = size
|
||||
val otherSize = other.size
|
||||
for (index in 0 until min(size, otherSize)) {
|
||||
val byte = this[index]
|
||||
val otherByte = other[index]
|
||||
val result = byte - otherByte
|
||||
if (result != 0) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return size - otherSize
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromBytes(bytes: ByteArray, start: Int = 0, end: Int = bytes.size): ByteString =
|
||||
ByteString(bytes.copyOfRange(start, end))
|
||||
|
||||
fun fromString(string: String): ByteString =
|
||||
ByteString(string.toByteArray()).apply { stringCache = string }
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.toByteString(start: Int = 0, end: Int = size): ByteString =
|
||||
ByteString.fromBytes(this, start, end)
|
||||
|
||||
fun String.toByteString(): ByteString = ByteString.fromString(this)
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Code borrowed from the awesome Material Files app (https://github.com/zhanghai/MaterialFiles)
|
||||
*
|
||||
* Copyright (c) 2019 Hai Zhang <dreaming.in.code.zh@gmail.com>
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
|
||||
package sushi.hardcore.droidfs.collation
|
||||
|
||||
class ByteStringBuilder(capacity: Int = 16) {
|
||||
private var bytes = ByteArray(capacity)
|
||||
|
||||
var length = 0
|
||||
private set
|
||||
|
||||
fun append(byte: Byte): ByteStringBuilder {
|
||||
ensureCapacity(length + 1)
|
||||
bytes[length] = byte
|
||||
++length
|
||||
return this
|
||||
}
|
||||
|
||||
fun append(bytes: ByteArray, start: Int = 0, end: Int = bytes.size): ByteStringBuilder {
|
||||
val newLength = length + (end - start)
|
||||
ensureCapacity(newLength)
|
||||
bytes.copyInto(this.bytes, length, start, end)
|
||||
length = newLength
|
||||
return this
|
||||
}
|
||||
|
||||
fun append(byteString: ByteString): ByteStringBuilder = append(byteString.borrowBytes())
|
||||
|
||||
private fun ensureCapacity(minimumCapacity: Int) {
|
||||
val capacity = bytes.size
|
||||
if (minimumCapacity > capacity) {
|
||||
var newCapacity = (capacity shl 1) + 2
|
||||
if (newCapacity < minimumCapacity) {
|
||||
newCapacity = minimumCapacity
|
||||
}
|
||||
bytes = bytes.copyOf(newCapacity)
|
||||
}
|
||||
}
|
||||
|
||||
fun toByteString(): ByteString = bytes.toByteString(0, length)
|
||||
|
||||
override fun toString(): String = String(bytes, 0, length)
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Code borrowed from the awesome Material Files app (https://github.com/zhanghai/MaterialFiles)
|
||||
*
|
||||
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
|
||||
package sushi.hardcore.droidfs.collation
|
||||
|
||||
import java.text.CollationKey
|
||||
import java.text.Collator
|
||||
import kotlin.math.min
|
||||
|
||||
private val COLLATION_SENTINEL = byteArrayOf(1, 1, 1)
|
||||
|
||||
// @see https://github.com/GNOME/glib/blob/mainline/glib/gunicollate.c
|
||||
// g_utf8_collate_key_for_filename()
|
||||
fun Collator.getCollationKeyForFileName(source: String): CollationKey {
|
||||
val result = ByteStringBuilder()
|
||||
val suffix = ByteStringBuilder()
|
||||
var previousIndex = 0
|
||||
var index = 0
|
||||
val endIndex = source.length
|
||||
while (index < endIndex) {
|
||||
when {
|
||||
source[index] == '.' -> {
|
||||
if (previousIndex != index) {
|
||||
val collationKey = getCollationKey(source.substring(previousIndex, index))
|
||||
result.append(collationKey.toByteArray())
|
||||
}
|
||||
result.append(COLLATION_SENTINEL).append(1)
|
||||
previousIndex = index + 1
|
||||
}
|
||||
source[index].isAsciiDigit() -> {
|
||||
if (previousIndex != index) {
|
||||
val collationKey = getCollationKey(source.substring(previousIndex, index))
|
||||
result.append(collationKey.toByteArray())
|
||||
}
|
||||
result.append(COLLATION_SENTINEL).append(2)
|
||||
previousIndex = index
|
||||
var leadingZeros: Int
|
||||
var digits: Int
|
||||
if (source[index] == '0') {
|
||||
leadingZeros = 1
|
||||
digits = 0
|
||||
} else {
|
||||
leadingZeros = 0
|
||||
digits = 1
|
||||
}
|
||||
while (++index < endIndex) {
|
||||
if (source[index] == '0' && digits == 0) {
|
||||
++leadingZeros
|
||||
} else if (source[index].isAsciiDigit()) {
|
||||
++digits
|
||||
} else {
|
||||
if (digits == 0) {
|
||||
++digits
|
||||
--leadingZeros
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
while (digits > 1) {
|
||||
result.append(':'.code.toByte())
|
||||
--digits
|
||||
}
|
||||
if (leadingZeros > 0) {
|
||||
suffix.append(leadingZeros.toByte())
|
||||
previousIndex += leadingZeros
|
||||
}
|
||||
result.append(source.substring(previousIndex, index).toByteString())
|
||||
previousIndex = index
|
||||
--index
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
++index
|
||||
}
|
||||
if (previousIndex != index) {
|
||||
val collationKey = getCollationKey(source.substring(previousIndex, index))
|
||||
result.append(collationKey.toByteArray())
|
||||
}
|
||||
result.append(suffix.toByteString())
|
||||
return ByteArrayCollationKey(source, result.toByteString().borrowBytes())
|
||||
}
|
||||
|
||||
private fun Char.isAsciiDigit(): Boolean = this in '0'..'9'
|
||||
|
||||
private class ByteArrayCollationKey(
|
||||
@Suppress("CanBeParameter")
|
||||
private val source: String,
|
||||
private val bytes: ByteArray
|
||||
) : CollationKey(source) {
|
||||
override fun compareTo(other: CollationKey): Int {
|
||||
other as ByteArrayCollationKey
|
||||
return bytes.unsignedCompareTo(other.bytes)
|
||||
}
|
||||
|
||||
override fun toByteArray(): ByteArray = bytes.copyOf()
|
||||
}
|
||||
|
||||
private fun ByteArray.unsignedCompareTo(other: ByteArray): Int {
|
||||
val size = size
|
||||
val otherSize = other.size
|
||||
for (index in 0 until min(size, otherSize)) {
|
||||
val byte = this[index].toInt() and 0xFF
|
||||
val otherByte = other[index].toInt() and 0xFF
|
||||
if (byte < otherByte) {
|
||||
return -1
|
||||
} else if (byte > otherByte) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return size - otherSize
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import sushi.hardcore.droidfs.collation.getCollationKeyForFileName
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
|
||||
class ExplorerElement(val name: String, val elementType: Short, var size: Long = -1, mTime: Long = -1, val parentPath: String) {
|
||||
val mTime = Date((mTime * 1000).toString().toLong())
|
||||
val fullPath: String = PathUtils.pathJoin(parentPath, name)
|
||||
val collationKey = Collator.getInstance().getCollationKeyForFileName(fullPath)
|
||||
|
||||
val isDirectory: Boolean
|
||||
get() = elementType.toInt() == DIRECTORY_TYPE
|
||||
@ -53,7 +56,7 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
|
||||
when (sortOrder) {
|
||||
"name" -> {
|
||||
explorerElements.sortWith { a, b ->
|
||||
doSort(a, b, foldersFirst) { a.fullPath.compareTo(b.fullPath, true) }
|
||||
doSort(a, b, foldersFirst) { a.collationKey.compareTo(b.collationKey) }
|
||||
}
|
||||
}
|
||||
"size" -> {
|
||||
@ -68,7 +71,7 @@ class ExplorerElement(val name: String, val elementType: Short, var size: Long =
|
||||
}
|
||||
"name_desc" -> {
|
||||
explorerElements.sortWith { a, b ->
|
||||
doSort(a, b, foldersFirst) { b.fullPath.compareTo(a.fullPath, true) }
|
||||
doSort(a, b, foldersFirst) { b.collationKey.compareTo(a.collationKey) }
|
||||
}
|
||||
}
|
||||
"size_desc" -> {
|
||||
|
Loading…
Reference in New Issue
Block a user