@@ -10,11 +10,15 @@ import com.raival.compose.file.explorer.screen.main.tab.files.holder.LocalFileHo
1010import com.raival.compose.file.explorer.screen.main.tab.files.holder.ZipFileHolder
1111import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.apkFileType
1212import com.reandroid.archive.ZipAlign
13- import net.lingala.zip4j.ZipFile
1413import java.io.File
14+ import java.io.FileOutputStream
15+ import java.io.IOException
1516import java.text.SimpleDateFormat
1617import java.util.Date
1718import java.util.Locale
19+ import java.util.zip.ZipEntry
20+ import java.util.zip.ZipFile
21+ import java.util.zip.ZipOutputStream
1822
1923class RenameTask (val sourceContent : List <ContentHolder >) : Task() {
2024 private var parameters: RenameTaskParameters ? = null
@@ -111,6 +115,54 @@ class RenameTask(val sourceContent: List<ContentHolder>) : Task() {
111115 processName = globalClass.getString(R .string.renaming)
112116 }
113117
118+ // Check if we're dealing with zip files and handle them separately
119+ val firstPendingItem = pendingContent.firstOrNull { it.status == TaskContentStatus .PENDING }
120+ if (firstPendingItem?.source is ZipFileHolder ) {
121+ try {
122+ handleZipFileRenaming()
123+ } catch (e: Exception ) {
124+ logger.logError(e)
125+ markAsFailed(
126+ globalClass.resources.getString(
127+ R .string.task_summary_failed,
128+ e.message ? : emptyString
129+ )
130+ )
131+ return
132+ }
133+ } else {
134+ // Handle local files
135+ handleLocalFileRenaming()
136+ }
137+
138+ if (progressMonitor.status == TaskStatus .RUNNING ) {
139+ val sample = sourceContent.first()
140+ if (sample is ZipFileHolder ) {
141+ if (sample.zipTree.source.extension == apkFileType) {
142+ progressMonitor.apply {
143+ processName = globalClass.resources.getString(R .string.aligning_apk)
144+ progress = - 1f
145+ contentName = emptyString
146+ }
147+ ZipAlign .alignApk(sample.zipTree.source.file)
148+ }
149+ }
150+ }
151+
152+ if (progressMonitor.status == TaskStatus .RUNNING ) {
153+ progressMonitor.status = TaskStatus .SUCCESS
154+ progressMonitor.summary = buildString {
155+ pendingContent.forEach { content ->
156+ append(content.source.displayName)
157+ append(" -> " )
158+ append(content.status.name)
159+ append(" \n " )
160+ }
161+ }
162+ }
163+ }
164+
165+ private suspend fun handleLocalFileRenaming () {
114166 pendingContent.forEachIndexed { index, itemToRename ->
115167 if (aborted) {
116168 progressMonitor.status = TaskStatus .PAUSED
@@ -127,13 +179,8 @@ class RenameTask(val sourceContent: List<ContentHolder>) : Task() {
127179 try {
128180 if (itemToRename.source is LocalFileHolder ) {
129181 itemToRename.source.file.renameTo(File (itemToRename.newPath))
130- } else if (itemToRename.source is ZipFileHolder ) {
131- ZipFile (itemToRename.source.zipTree.source.file).renameFile(
132- itemToRename.source.uniquePath + if (itemToRename.source.isFolder) " /" else emptyString,
133- itemToRename.newPath
134- )
182+ itemToRename.status = TaskContentStatus .SUCCESS
135183 }
136- itemToRename.status = TaskContentStatus .SUCCESS
137184 } catch (e: Exception ) {
138185 logger.logError(e)
139186 markAsFailed(
@@ -146,30 +193,140 @@ class RenameTask(val sourceContent: List<ContentHolder>) : Task() {
146193 }
147194 }
148195 }
196+ }
149197
150- if (progressMonitor.status == TaskStatus .RUNNING ) {
151- val sample = sourceContent.first()
152- if (sample is ZipFileHolder ) {
153- if (sample.zipTree.source.extension == apkFileType) {
154- progressMonitor.apply {
155- processName = globalClass.resources.getString(R .string.aligning_apk)
156- progress = - 1f
157- contentName = emptyString
198+ private suspend fun handleZipFileRenaming () {
199+ val zipFileHolder = pendingContent.first().source as ZipFileHolder
200+ val sourceZipFile = zipFileHolder.zipTree.source.file
201+ val tempFile = File (sourceZipFile.parent, " ${sourceZipFile.nameWithoutExtension} _temp.zip" )
202+
203+ try {
204+ // Create a map of old paths to new paths for all items to rename
205+ val renameMap = mutableMapOf<String , String >()
206+ val foldersToRename = mutableSetOf<String >()
207+
208+ pendingContent.filter { it.status == TaskContentStatus .PENDING }.forEach { item ->
209+ val zipHolder = item.source as ZipFileHolder
210+ val oldPath = zipHolder.uniquePath
211+ val newPath = item.newPath
212+
213+ if (zipHolder.isFolder) {
214+ foldersToRename.add(oldPath)
215+ }
216+
217+ renameMap[oldPath] = newPath
218+ }
219+
220+ // Open the source zip file for reading
221+ ZipFile (sourceZipFile).use { sourceZip ->
222+ // Create a new zip file
223+ ZipOutputStream (FileOutputStream (tempFile)).use { zipOut ->
224+
225+ val entries = sourceZip.entries().toList()
226+ var processedCount = 0
227+
228+ entries.forEach { entry ->
229+ if (aborted) {
230+ progressMonitor.status = TaskStatus .PAUSED
231+ return
232+ }
233+
234+ val entryName = entry.name.removeSuffix(" /" )
235+ var newEntryName = entryName
236+ var shouldRename = false
237+
238+ // Check direct rename
239+ if (renameMap.containsKey(entryName)) {
240+ newEntryName = renameMap[entryName]!!
241+ shouldRename = true
242+ } else {
243+ // Check if this entry is inside a folder being renamed
244+ for (folderPath in foldersToRename) {
245+ if (entryName.startsWith(" $folderPath /" )) {
246+ val relativePath = entryName.substring(folderPath.length + 1 )
247+ newEntryName = " ${renameMap[folderPath]} /$relativePath "
248+ shouldRename = true
249+ break
250+ }
251+ }
252+ }
253+
254+ // Restore trailing slash for directories
255+ if (entry.isDirectory) {
256+ newEntryName + = " /"
257+ }
258+
259+ // Create new entry with the new name
260+ val newEntry = ZipEntry (newEntryName).apply {
261+ time = entry.time
262+ if (! entry.isDirectory) {
263+ method = entry.method
264+ if (entry.method == ZipEntry .STORED ) {
265+ size = entry.size
266+ crc = entry.crc
267+ }
268+ }
269+ }
270+
271+ zipOut.putNextEntry(newEntry)
272+
273+ // Copy entry data if it's not a directory
274+ if (! entry.isDirectory) {
275+ sourceZip.getInputStream(entry).use { inputStream ->
276+ inputStream.copyTo(zipOut)
277+ }
278+ }
279+
280+ zipOut.closeEntry()
281+
282+ // Update progress
283+ processedCount++
284+ progressMonitor.apply {
285+ contentName = entry.name
286+ progress = processedCount.toFloat() / entries.size
287+ }
288+
289+ // Mark corresponding pending items as successful
290+ if (shouldRename) {
291+ pendingContent.find {
292+ (it.source as ZipFileHolder ).uniquePath == entryName
293+ }?.status = TaskContentStatus .SUCCESS
294+ }
158295 }
159- ZipAlign .alignApk(sample.zipTree.source.file)
160296 }
161297 }
162- }
163298
164- if (progressMonitor.status == TaskStatus .RUNNING ) {
165- progressMonitor.status = TaskStatus .SUCCESS
166- progressMonitor.summary = buildString {
167- pendingContent.forEach { content ->
168- append(content.source.displayName)
169- append(" -> " )
170- append(content.status.name)
299+ // Replace the original file with the new one
300+ if (sourceZipFile.delete()) {
301+ if (! tempFile.renameTo(sourceZipFile)) {
302+ throw IOException (globalClass.getString(R .string.failed_to_replace_original_zip_file))
171303 }
304+ } else {
305+ throw IOException (globalClass.getString(R .string.failed_to_delete_original_zip_file))
306+ }
307+
308+ // Mark any remaining pending items as successful (for folders without zip entries)
309+ pendingContent.filter { it.status == TaskContentStatus .PENDING }.forEach { item ->
310+ val zipHolder = item.source as ZipFileHolder
311+ if (zipHolder.isFolder) {
312+ // Check if this folder has any children that were successfully renamed
313+ val hasRenamedChildren = pendingContent.any { other ->
314+ other.status == TaskContentStatus .SUCCESS &&
315+ (other.source as ZipFileHolder ).uniquePath.startsWith(" ${zipHolder.uniquePath} /" )
316+ }
317+
318+ if (hasRenamedChildren) {
319+ item.status = TaskContentStatus .SUCCESS
320+ }
321+ }
322+ }
323+
324+ } catch (e: Exception ) {
325+ // Clean up temp file if it exists
326+ if (tempFile.exists()) {
327+ tempFile.delete()
172328 }
329+ throw e
173330 }
174331 }
175332
0 commit comments