@@ -7,7 +7,12 @@ import android.os.storage.StorageManager
77import androidx.core.net.toUri
88import androidx.documentfile.provider.DocumentFile
99import co.adityarajput.fileflow.data.models.Rule
10+ import kotlinx.coroutines.Dispatchers
11+ import kotlinx.coroutines.withContext
1012import java.net.URLDecoder
13+ import java.nio.file.FileAlreadyExistsException
14+ import java.nio.file.Files
15+ import java.nio.file.StandardCopyOption
1116import java.io.File as IOFile
1217
1318sealed class File {
@@ -31,9 +36,91 @@ sealed class File {
3136 }
3237 }
3338
34- class SAFFile (val documentFile : DocumentFile ) : File()
39+ class SAFFile (val documentFile : DocumentFile ) : File() {
40+ override suspend fun moveTo (
41+ destDir : File ,
42+ destFileName : String ,
43+ keepOriginal : Boolean ,
44+ overwriteExisting : Boolean ,
45+ context : Context ,
46+ ) {
47+ if (destDir !is SAFFile )
48+ throw IllegalArgumentException (" Destination directory must be a SAFFile" )
49+
50+ destDir.documentFile.findFile(destFileName)?.run {
51+ if (overwriteExisting) delete()
52+ else throw Exception (" $destFileName already exists" )
53+ }
54+
55+ val destFile =
56+ destDir.documentFile.createFile(type ? : " application/octet-stream" , destFileName)!!
57+
58+ val resolver = context.contentResolver
59+ resolver.openInputStream(this .documentFile.uri).use { srcStream ->
60+ resolver.openOutputStream(destFile.uri).use { destStream ->
61+ srcStream!! .copyTo(destStream!! )
62+ }
63+ }
64+
65+ if (! keepOriginal) delete()
66+ }
67+ }
68+
69+ class FSFile (val ioFile : IOFile ) : File() {
70+ override suspend fun moveTo (
71+ destDir : File ,
72+ destFileName : String ,
73+ keepOriginal : Boolean ,
74+ overwriteExisting : Boolean ,
75+ context : Context ,
76+ ) {
77+ if (destDir !is FSFile )
78+ throw IllegalArgumentException (" Destination directory must be a FSFile" )
79+
80+ val options = mutableListOf<StandardCopyOption >()
81+ if (keepOriginal) options.add(StandardCopyOption .COPY_ATTRIBUTES )
82+ if (overwriteExisting) options.add(StandardCopyOption .REPLACE_EXISTING )
3583
36- class FSFile (val ioFile : IOFile ) : File()
84+ try {
85+ withContext(Dispatchers .IO ) {
86+ if (keepOriginal) {
87+ Files .copy(
88+ this @FSFile.ioFile.toPath(),
89+ destDir.ioFile.toPath().resolve(destFileName),
90+ * options.toTypedArray(),
91+ )
92+ } else {
93+ Files .move(
94+ this @FSFile.ioFile.toPath(),
95+ destDir.ioFile.toPath().resolve(destFileName),
96+ * options.toTypedArray(),
97+ )
98+ }
99+ }
100+ } catch (e: Exception ) {
101+ if (e is FileAlreadyExistsException ) throw e
102+
103+ Logger .w(
104+ " Files" ,
105+ " Failed to move file, falling back to create + copy [+ delete]." ,
106+ e,
107+ )
108+
109+ val destFile = IOFile (destDir.ioFile, destFileName)
110+ withContext(Dispatchers .IO ) {
111+ destFile.createNewFile()
112+
113+ this @FSFile.ioFile.inputStream().use { srcStream ->
114+ destFile.outputStream().use { destStream ->
115+ srcStream.copyTo(destStream)
116+ }
117+ }
118+ }
119+
120+ if (! keepOriginal) delete()
121+ }
122+ }
123+ }
37124
38125 val name
39126 get() = when (this ) {
@@ -122,17 +209,6 @@ sealed class File {
122209 return false
123210 }
124211
125- fun createFile (type : String? , name : String ): File ? {
126- return when (this ) {
127- is SAFFile -> documentFile
128- .createFile(type ? : " application/octet-stream" , name)
129- ?.let { SAFFile (it) }
130-
131- is FSFile -> IOFile (ioFile, name)
132- .let { if (it.createNewFile()) FSFile (it) else null }
133- }
134- }
135-
136212 fun createDirectory (relativePath : String ): File ? {
137213 return when (this ) {
138214 is SAFFile -> {
@@ -160,40 +236,20 @@ sealed class File {
160236 }
161237 }
162238
239+ abstract suspend fun moveTo (
240+ destDir : File ,
241+ destFileName : String ,
242+ keepOriginal : Boolean ,
243+ overwriteExisting : Boolean ,
244+ context : Context ,
245+ )
246+
163247 fun delete () = when (this ) {
164248 is SAFFile -> documentFile.delete()
165249 is FSFile -> ioFile.delete()
166250 }
167251}
168252
169- fun Context.copyFile (src : File , dest : File ): Boolean {
170- val resolver = contentResolver
171-
172- if (src is File .SAFFile && dest is File .SAFFile ) {
173- resolver.openInputStream(src.documentFile.uri).use { srcStream ->
174- resolver.openOutputStream(dest.documentFile.uri).use { destStream ->
175- if (srcStream == null || destStream == null ) {
176- Logger .e(" Files" , " Failed to open file(s)" )
177- return false
178- }
179- Logger .i(" Files" , " Copying ${src.name} to ${dest.name} " )
180- srcStream.copyTo(destStream)
181- return true
182- }
183- }
184- } else if (src is File .FSFile && dest is File .FSFile ) {
185- src.ioFile.inputStream().use { srcStream ->
186- dest.ioFile.outputStream().use { destStream ->
187- Logger .i(" Files" , " Copying ${src.name} to ${dest.name} " )
188- srcStream.copyTo(destStream)
189- return true
190- }
191- }
192- }
193-
194- return false
195- }
196-
197253fun String.getGetDirectoryFromUri () =
198254 if (isBlank()) {
199255 this
0 commit comments