mirror of https://github.com/deniscerri/ytdlnis
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
13 KiB
Kotlin
299 lines
13 KiB
Kotlin
package com.deniscerri.ytdlnis.util
|
|
|
|
import android.content.ContentResolver
|
|
import android.content.Context
|
|
import android.database.Cursor
|
|
import android.media.MediaScannerConnection
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Environment
|
|
import android.provider.DocumentsContract
|
|
import android.provider.MediaStore
|
|
import android.util.Log
|
|
import android.webkit.MimeTypeMap
|
|
import androidx.core.net.toUri
|
|
import androidx.documentfile.provider.DocumentFile
|
|
import com.anggrayudi.storage.callback.FileCallback
|
|
import com.anggrayudi.storage.callback.FolderCallback
|
|
import com.anggrayudi.storage.file.copyFolderTo
|
|
import com.anggrayudi.storage.file.getAbsolutePath
|
|
import com.anggrayudi.storage.file.moveFileTo
|
|
import com.deniscerri.ytdlnis.App
|
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.withContext
|
|
import okhttp3.internal.closeQuietly
|
|
import java.io.File
|
|
import java.net.URLDecoder
|
|
import java.nio.charset.StandardCharsets
|
|
import java.nio.file.Files
|
|
import java.nio.file.StandardCopyOption
|
|
import java.text.DecimalFormat
|
|
import java.text.DecimalFormatSymbols
|
|
import java.util.*
|
|
import kotlin.io.path.absolutePathString
|
|
import kotlin.math.log10
|
|
import kotlin.math.pow
|
|
|
|
|
|
object FileUtil {
|
|
|
|
fun deleteFile(path: String){
|
|
runCatching {
|
|
if (!File(path).delete()){
|
|
DocumentFile.fromSingleUri(App.instance, Uri.parse(path))?.delete()
|
|
}
|
|
}
|
|
}
|
|
|
|
fun exists(path: String) : Boolean {
|
|
val file = File(path)
|
|
if (path.isEmpty()) return false
|
|
return file.exists()
|
|
}
|
|
|
|
fun formatPath(path: String) : String {
|
|
var dataValue = path
|
|
if (dataValue.startsWith("/storage/")) return dataValue
|
|
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "")
|
|
dataValue = dataValue.replace("^/document/".toRegex(), "")
|
|
dataValue = dataValue.replace("^primary:".toRegex(), "primary/")
|
|
dataValue = dataValue.replace("%3A".toRegex(), "/")
|
|
try {
|
|
dataValue = URLDecoder.decode(dataValue, StandardCharsets.UTF_8.name())
|
|
} catch (ignored: Exception) {
|
|
}
|
|
val pieces = dataValue.split("/").toTypedArray()
|
|
val formattedPath = StringBuilder("/storage/")
|
|
if (pieces[0] == "primary"){
|
|
formattedPath.append("emulated/0/")
|
|
}else{
|
|
formattedPath.append(pieces[0]).append("/")
|
|
}
|
|
pieces.forEachIndexed { i, it ->
|
|
if (i > 0 && it.isNotEmpty()){
|
|
formattedPath.append(it).append("/")
|
|
}
|
|
}
|
|
return formattedPath.toString()
|
|
}
|
|
|
|
|
|
@Throws(Exception::class)
|
|
suspend fun moveFile(originDir: File, context: Context, destDir: String, keepCache: Boolean, progress: (p: Int) -> Unit) : List<String> {
|
|
return withContext(Dispatchers.Main){
|
|
val fileList = mutableListOf<String>()
|
|
val dir = File(formatPath(destDir))
|
|
if (!dir.exists()) dir.mkdirs()
|
|
originDir.walk().forEach {
|
|
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
|
var destFile: DocumentFile
|
|
try {
|
|
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(.*.part-Frag)|(.*.live_chat)".toRegex())){
|
|
return@forEach
|
|
}
|
|
|
|
runCatching {
|
|
if (File(formatPath(destDir)).canWrite()){
|
|
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
|
|
for (ff in files){
|
|
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
|
|
runCatching {
|
|
newFile.parentFile?.mkdirs()
|
|
}
|
|
if (Build.VERSION.SDK_INT >= 26 ) {
|
|
var newFileName = newFile.absolutePath
|
|
var counter = 1
|
|
while (Files.exists(File(newFileName).toPath())) {
|
|
// If the file already exists in the destination directory, add a number to differentiate it
|
|
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
|
counter++
|
|
}
|
|
|
|
fileList.add(Files.move(
|
|
ff.toPath(),
|
|
File(newFileName).toPath(),
|
|
StandardCopyOption.REPLACE_EXISTING
|
|
).absolutePathString())
|
|
ff.delete()
|
|
fileList.add(newFileName)
|
|
}else{
|
|
var newFileName = newFile.absolutePath
|
|
var counter = 1
|
|
while (File(newFileName).exists()) {
|
|
// If the file already exists in the destination directory, add a number to differentiate it
|
|
newFileName = newFile.absolutePath.replace(newFile.nameWithoutExtension, newFile.nameWithoutExtension+" ($counter)")
|
|
counter++
|
|
}
|
|
|
|
ff.copyTo(File(newFileName),false)
|
|
ff.delete()
|
|
fileList.add(newFileName)
|
|
}
|
|
}
|
|
return@forEach
|
|
}
|
|
}
|
|
|
|
val curr = DocumentFile.fromFile(it)
|
|
val dst = DocumentFile.fromTreeUri(context, destDir.toUri())
|
|
|
|
if (it.isDirectory){
|
|
withContext(Dispatchers.IO){
|
|
curr.copyFolderTo(context, dst!!, skipEmptyFiles = false, callback = object : FolderCallback() {
|
|
override fun onStart(folder: DocumentFile, totalFilesToCopy: Int, workerThread: Thread): Long {
|
|
return 500 // update progress every half second
|
|
}
|
|
|
|
override fun onParentConflict(destinationFolder: DocumentFile, action: ParentFolderConflictAction, canMerge: Boolean) {
|
|
if (canMerge){
|
|
action.confirmResolution(ConflictResolution.MERGE)
|
|
}else{
|
|
action.confirmResolution(ConflictResolution.CREATE_NEW)
|
|
}
|
|
}
|
|
|
|
override fun onReport(report: Report) {
|
|
progress(report.progress.toInt())
|
|
}
|
|
|
|
override fun onCompleted(result: Result) {
|
|
fileList.addAll(result.folder.listFiles().map { f -> f.getAbsolutePath(context) })
|
|
it.deleteRecursively()
|
|
}
|
|
|
|
override fun onFailed(errorCode: ErrorCode) {
|
|
//if its usb?
|
|
runCatching {
|
|
it.walkTopDown().forEach { f ->
|
|
if (f.isDirectory) return@forEach
|
|
val destUri = moveFileInputStream(it, context, dst) ?: return@forEach
|
|
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
|
}
|
|
|
|
it.deleteRecursively()
|
|
}
|
|
super.onFailed(errorCode)
|
|
}
|
|
|
|
})
|
|
}
|
|
}else{
|
|
withContext(Dispatchers.IO){
|
|
curr.moveFileTo(context, dst!!, callback = object : FileCallback() {
|
|
override fun onFailed(errorCode: ErrorCode) {
|
|
//if its usb?
|
|
runCatching {
|
|
val destUri = moveFileInputStream(it, context, dst) ?: return
|
|
fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context))
|
|
it.delete()
|
|
}
|
|
super.onFailed(errorCode)
|
|
}
|
|
|
|
override fun onConflict(
|
|
destinationFile: DocumentFile,
|
|
action: FileConflictAction
|
|
) {
|
|
action.confirmResolution(ConflictResolution.CREATE_NEW)
|
|
}
|
|
|
|
override fun onStart(file: Any, workerThread: Thread): Long {
|
|
return 500 // update progress every 1 second
|
|
}
|
|
|
|
override fun onReport(report: Report) {
|
|
progress(report.progress.toInt())
|
|
}
|
|
|
|
override fun onCompleted(result: Any) {
|
|
destFile = (result as DocumentFile)
|
|
fileList.add(destFile.getAbsolutePath(context))
|
|
it.delete()
|
|
super.onCompleted(result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}catch (e: Exception) {
|
|
Log.e("error", e.message.toString())
|
|
}
|
|
|
|
}
|
|
if (!keepCache){
|
|
originDir.deleteRecursively()
|
|
}
|
|
return@withContext scanMedia(fileList, context)
|
|
}
|
|
}
|
|
|
|
private fun moveFileInputStream(it: File, context: Context, dst: DocumentFile) : Uri? {
|
|
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
|
|
|
val destUri = DocumentsContract.createDocument(
|
|
context.contentResolver,
|
|
dst.uri,
|
|
mimeType,
|
|
it.name
|
|
) ?: return null
|
|
|
|
val inputStream = it.inputStream()
|
|
val outputStream =
|
|
context.contentResolver.openOutputStream(destUri) ?: return null
|
|
inputStream.copyTo(outputStream)
|
|
inputStream.closeQuietly()
|
|
outputStream.closeQuietly()
|
|
|
|
return destUri
|
|
}
|
|
|
|
fun scanMedia(files: List<String>, context: Context) : List<String> {
|
|
try {
|
|
val paths = files.sortedByDescending { File(it).length() }
|
|
runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) }
|
|
return paths
|
|
}catch (e: Exception){
|
|
e.printStackTrace()
|
|
}
|
|
|
|
return listOf()
|
|
}
|
|
|
|
fun getCachePath(context: Context) : String {
|
|
return context.cacheDir.absolutePath + "/downloads/"
|
|
}
|
|
|
|
fun deleteConfigFiles(request: YoutubeDLRequest) {
|
|
request.getArguments("--config")?.forEach {
|
|
if (it != null) File(it).delete()
|
|
}
|
|
request.getArguments("--config-locations")?.forEach {
|
|
if (it != null) File(it).delete()
|
|
}
|
|
}
|
|
|
|
fun getDefaultAudioPath() : String{
|
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Audio"
|
|
}
|
|
|
|
fun getDefaultVideoPath() : String{
|
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Video"
|
|
}
|
|
|
|
fun getDefaultCommandPath() : String {
|
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Command"
|
|
}
|
|
|
|
fun getCookieFile(context : Context, path: (path: String) -> Unit){
|
|
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
|
if (cookiesFile.exists()) path(cookiesFile.absolutePath)
|
|
}
|
|
|
|
fun convertFileSize(s: Long): String{
|
|
if (s <= 0) return "?"
|
|
val units = arrayOf("B", "kB", "MB", "GB", "TB")
|
|
val digitGroups = (log10(s.toDouble()) / log10(1024.0)).toInt()
|
|
val symbols = DecimalFormatSymbols(Locale.US)
|
|
return "${DecimalFormat("#,##0.#", symbols).format(s / 1024.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}"
|
|
}
|
|
} |