Skip to content

Commit

Permalink
Implement filename renaming library
Browse files Browse the repository at this point in the history
It is a port of https://github.com/jonschlinkert/add-filename-increment for node.js into Kotlin, with some tweaks and tricks specific to Kotlin.

Addresses #2078
  • Loading branch information
TranceLove committed Aug 19, 2023
1 parent 8b86e7c commit 072355e
Show file tree
Hide file tree
Showing 9 changed files with 1,136 additions and 4 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ dependencies {
testImplementation "androidx.test:rules:$androidXTestVersion"
testImplementation "androidx.test.ext:junit:$androidXTestExtVersion"
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito:mockito-inline:$mockitoVersion"
testImplementation "org.mockito:mockito-inline:$mockitoInlineVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.apache.sshd:sshd-core:1.7.0"
testImplementation "org.awaitility:awaitility:$awaitilityVersion"
Expand Down
235 changes: 235 additions & 0 deletions app/src/main/java/com/amaze/filemanager/filesystem/FilenameHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* Copyright (C) 2014-2023 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
*
* This file is part of Amaze File Manager.
*
* Amaze File Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.amaze.filemanager.filesystem

import com.amaze.filemanager.application.AppConfig
import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.SLASH
import kotlin.math.absoluteValue

/**
* Convenient extension to return path element of a path string = the part before the last slash.
*/
fun String.pathDirname(): String = if (contains(SLASH)) {
substringBeforeLast(SLASH)
} else {
""
}

/**
* Convenient extension to return the name element of a path = the part after the last slash.
*/
fun String.pathBasename(): String = if (contains(SLASH)) {
substringAfterLast(SLASH)
} else {
this
}

/**
* Convenient extension to return the basename element of a filename = the part after the last
* slash and before the extension (.).
*/
fun String.pathFileBasename(): String = if (contains('.')) {
pathBasename().substringBeforeLast('.')
} else {
pathBasename()
}

/**
* Convenient extension to return the extension element of a filename = the part after the last
* slash and after the extension (.). Returns empty string if no extension dot exist.
*/
fun String.pathFileExtension(): String = if (contains('.')) {
pathBasename().substringAfterLast('.')
} else {
""
}

enum class FilenameFormatFlag {
DARWIN, DEFAULT, WINDOWS, LINUX
}

object FilenameHelper {

/* Don't split complex regexs into multiple lines. */

/* ktlint-disable max-line-length */
private const val REGEX_RAW_NUMBERS = "| [0-9]+"
private const val REGEX_SOURCE = " \\((?:(another|[0-9]+(th|st|nd|rd)) )?copy\\)|copy( [0-9]+)?|\\.\\(incomplete\\)| \\([0-9]+\\)|[- ]+"
/* ktlint-enable max-line-length */

private val ordinals = arrayOf("th", "st", "nd", "rd")

/**
* Strip the file path to one without increments or numbers.
*
* Default will not strip the raw numbers; specify removeRawNumbers = true to do so.
*/
@JvmStatic
fun strip(input: String, removeRawNumbers: Boolean = false): String {
val filepath = stripIncrementInternal(input, removeRawNumbers)
val extension = filepath.pathFileExtension()
val dirname = stripIncrementInternal(filepath.pathDirname(), removeRawNumbers)
val stem = stem(filepath, removeRawNumbers)
return StringBuilder().run {
if (dirname.isNotBlank()) {
append(dirname).append(SLASH)
}
append(stem)
if (extension.isNotBlank()) {
append('.').append(extension)
}
toString()
}
}

/**
* Returns the ordinals of the given number. So that
*
* - toOrdinal(1) returns "1st"
* - toOrdinal(2) returns "2nd"
* - toOrdinal(10) returns "10th"
* - toOrdinal(11) returns "11th"
* - toOrdinal(12) returns "12th"
* - toOrdinal(21) returns "21st"
* - toOrdinal(22) returns "22nd"
* - toOrdinal(23) returns "23rd"
*
* etc.
*/
@JvmStatic
fun toOrdinal(n: Int): String = "$n${ordinal(n.absoluteValue)}"

/**
* Increment the filename of a given [HybridFile].
*
* Uses [HybridFile.exists] to check file existence and if it exists, returns a HybridFile
* with new filename which does not exist.
*/
@JvmStatic
fun increment(
file: HybridFile,
platform: FilenameFormatFlag = FilenameFormatFlag.DEFAULT,
strip: Boolean = true,
removeRawNumbers: Boolean = false,
startArg: Int = 1
): HybridFile {
var filename = file.getName(AppConfig.getInstance())
var dirname = file.path.pathDirname()
var basename = filename.pathFileBasename()
val extension = filename.pathFileExtension()

var start: Int = startArg

if (strip) {
filename = stripIncrementInternal(filename, removeRawNumbers)
dirname = stripIncrementInternal(dirname, removeRawNumbers)
basename = strip(basename, removeRawNumbers)
}

var retval = HybridFile(
file.mode,
dirname,
filename,
file.isDirectory(AppConfig.getInstance())
)

while (retval.exists(AppConfig.getInstance())) {
filename = format(platform, basename, start++) + ".$extension"
retval = HybridFile(
file.mode,
dirname,
filename,
file.isDirectory(AppConfig.getInstance())
)
}

return retval
}

private fun stripIncrementInternal(input: String, removeRawNumbers: Boolean = false): String {
val source = StringBuilder().run {
append(REGEX_SOURCE)
if (removeRawNumbers) {
append(REGEX_RAW_NUMBERS)
}
toString()
}
return Regex("($source)+$", RegexOption.IGNORE_CASE).replace(input, "")
}

private fun stem(filepath: String, removeRawNumbers: Boolean = false): String {
val extension = filepath.pathFileExtension()
return stripIncrementInternal(
filepath.pathBasename().substringBefore(".$extension"),
removeRawNumbers
)
}

private fun ordinal(n: Int): String {
var retval = ordinals.getOrNull(((n % 100) - 20) % 10)
if (retval == null) {
retval = ordinals.getOrNull(n % 100)
}
if (retval == null) {
retval = ordinals[0]
}
return retval
}

// TODO: i18n
private fun format(flag: FilenameFormatFlag, stem: String, n: Int): String {
return when (flag) {
FilenameFormatFlag.DARWIN -> {
if (n == 1) {
"$stem copy"
} else if (n > 1) {
"$stem copy $n"
} else {
stem
}
}
FilenameFormatFlag.LINUX -> {
when (n) {
0 -> {
stem
}
1 -> {
"$stem (copy)"
}
2 -> {
"$stem (another copy)"
}
else -> {
"$stem (${toOrdinal(n)} copy)"
}
}
}
// Windows and default formatting are the same.
else -> {
if (n > 1) {
"$stem ($n)"
} else {
stem
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public int compare(LayoutElementParcelable file1, LayoutElementParcelable file2)
return 0;
}

private static String getExtension(String a) {
public static String getExtension(String a) {
return a.substring(a.lastIndexOf(".") + 1).toLowerCase();
}
}
Loading

0 comments on commit 072355e

Please sign in to comment.