archive operations rewritten

This commit is contained in:
Radek Davidek 2026-05-12 13:05:29 +02:00
parent 0a3f3e75e0
commit c2865750c0
4 changed files with 136 additions and 3 deletions

View File

@ -15,7 +15,7 @@ import java.io.InputStreamReader;
*/
public class MainApp {
public static final String APP_VERSION = "1.4.5";
public static final String APP_VERSION = "1.4.6";
public enum OS {
WINDOWS, LINUX, MACOS, UNKNOWN

View File

@ -9,6 +9,7 @@ import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.util.regex.Pattern;
@ -47,6 +48,8 @@ public class FileEditor extends JFrame {
private final int pageSizeBytes = 64 * 1024; // 64 KB page size
// Allow loading entire file into memory up to this limit (100 MB)
private final long maxFullLoadBytes = 100L * 1024L * 1024L; // 100 MB
private static final int CHUNK_SIZE_BYTES = 2 * 1024 * 1024; // 2 MB
private static final long CHUNK_THRESHOLD_BYTES = 2L * 1024L * 1024L; // 2 MB
private JPanel hexControlPanel = null;
private JPanel northPanel = null;
private JButton prevPageBtn = null;
@ -1073,7 +1076,7 @@ public class FileEditor extends JFrame {
SwingUtilities.invokeLater(() -> textArea.requestFocusInWindow());
} else {
// Small or text file: load fully
fileBytes = Files.readAllBytes(file.toPath());
fileBytes = readFileBytesWithChunking(file, size);
boolean binary = isBinary(fileBytes);
if (binary && readOnly) {
hexMode = true;
@ -1115,6 +1118,22 @@ public class FileEditor extends JFrame {
return nonPrintable > (len / 4);
}
private byte[] readFileBytesWithChunking(File source, long knownSize) throws IOException {
if (knownSize <= CHUNK_THRESHOLD_BYTES) {
return Files.readAllBytes(source.toPath());
}
try (InputStream in = Files.newInputStream(source.toPath());
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream((int) Math.min(Integer.MAX_VALUE, knownSize))) {
byte[] buffer = new byte[CHUNK_SIZE_BYTES];
int read;
while ((read = in.read(buffer)) > 0) {
out.write(buffer, 0, read);
}
return out.toByteArray();
}
}
private boolean isImageFile(File f) {
String name = f.getName().toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".jpeg") ||

View File

@ -87,6 +87,7 @@ public class FilePanelTab extends JPanel {
private Path currentArchiveTempDir = null;
private File currentArchiveSourceFile = null;
private String currentArchivePassword = null;
private long archiveExtractTime = 0; // Track time of extraction to detect changes
private File archiveReturnDirectory = null;
private Point archiveReturnViewPosition = null;
private boolean inlineRenameActive = false;
@ -1923,12 +1924,26 @@ public class FilePanelTab extends JPanel {
/**
* Cleanup previous archive temp dir when navigating away from it.
* If changes were made to the archive, save them back to the original file.
*/
private void cleanupArchiveTempDirIfNeeded(File newDirectory) {
try {
if (currentArchiveTempDir != null) {
Path newPath = (newDirectory != null) ? newDirectory.toPath().toAbsolutePath().normalize() : null;
if (newPath == null || !newPath.startsWith(currentArchiveTempDir)) {
// Before deleting temp dir, check if archive was modified and sync if needed
if (currentArchiveSourceFile != null && FileOperations.supportsArchiveRewrite(currentArchiveSourceFile)) {
if (hasArchiveBeenModified()) {
try {
// Synchronously rewrite the archive with all changes made to the temp directory
FileOperations.rewriteArchiveFromDirectory(currentArchiveTempDir.toFile(), currentArchiveSourceFile, currentArchivePassword, null);
} catch (IOException e) {
System.err.println("Warning: Failed to save changes to archive " + currentArchiveSourceFile.getName() + ": " + e.getMessage());
// Continue with cleanup even if sync fails
}
}
}
deleteTempDirRecursively(currentArchiveTempDir);
clearOpenedArchiveSession();
}
@ -1947,11 +1962,22 @@ public class FilePanelTab extends JPanel {
temp -> {
try {
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
// Save changes from previous archive before switching (only if modified)
if (currentArchiveSourceFile != null && FileOperations.supportsArchiveRewrite(currentArchiveSourceFile)) {
if (hasArchiveBeenModified()) {
try {
FileOperations.rewriteArchiveFromDirectory(currentArchiveTempDir.toFile(), currentArchiveSourceFile, currentArchivePassword, null);
} catch (IOException e) {
System.err.println("Warning: Failed to save changes to archive " + currentArchiveSourceFile.getName() + ": " + e.getMessage());
}
}
}
deleteTempDirRecursively(currentArchiveTempDir);
}
} catch (Exception ignore) {}
currentArchiveTempDir = temp;
currentArchiveSourceFile = archive;
archiveExtractTime = System.currentTimeMillis(); // Track extraction time
if (finalEntryName == null || finalEntryName.isBlank()) {
loadDirectory(temp.toFile(), true, true);
@ -2046,11 +2072,22 @@ public class FilePanelTab extends JPanel {
temp -> {
try {
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
// Save changes from previous archive before switching (only if modified)
if (currentArchiveSourceFile != null && FileOperations.supportsArchiveRewrite(currentArchiveSourceFile)) {
if (hasArchiveBeenModified()) {
try {
FileOperations.rewriteArchiveFromDirectory(currentArchiveTempDir.toFile(), currentArchiveSourceFile, currentArchivePassword, null);
} catch (IOException e) {
System.err.println("Warning: Failed to save changes to archive " + currentArchiveSourceFile.getName() + ": " + e.getMessage());
}
}
}
deleteTempDirRecursively(currentArchiveTempDir);
}
} catch (Exception ignore) {}
currentArchiveTempDir = temp;
currentArchiveSourceFile = archiveFile;
archiveExtractTime = System.currentTimeMillis(); // Track extraction time
loadDirectory(temp.toFile());
},
error -> {
@ -2587,6 +2624,7 @@ public class FilePanelTab extends JPanel {
} catch (Exception ignore) {}
currentArchiveTempDir = temp;
currentArchiveSourceFile = archiveFile;
archiveExtractTime = System.currentTimeMillis(); // Track extraction time
loadDirectory(temp.toFile(), true, true);
},
error -> {
@ -3001,6 +3039,18 @@ public class FilePanelTab extends JPanel {
File parent = currentArchiveSourceFile.getParentFile();
if (parent != null) {
String archiveName = currentArchiveSourceFile.getName();
// Save any changes made to the archive before leaving (only if modified)
if (FileOperations.supportsArchiveRewrite(currentArchiveSourceFile)) {
if (hasArchiveBeenModified()) {
try {
FileOperations.rewriteArchiveFromDirectory(currentArchiveTempDir.toFile(), currentArchiveSourceFile, currentArchivePassword, null);
} catch (IOException e) {
System.err.println("Warning: Failed to save changes to archive " + archiveName + ": " + e.getMessage());
}
}
}
// cleanup temp dir before switching back
deleteTempDirRecursively(currentArchiveTempDir);
clearOpenedArchiveSession();
@ -3120,6 +3170,51 @@ public class FilePanelTab extends JPanel {
currentArchiveTempDir = null;
currentArchiveSourceFile = null;
currentArchivePassword = null;
archiveExtractTime = 0;
}
/**
* Check if the archive content was modified since extraction.
* Uses recursive directory traversal to detect any changes:
* - Files added/deleted
* - File modifications (size or modification time changes)
* Returns true if changes detected, false otherwise.
*/
private boolean hasArchiveBeenModified() {
if (currentArchiveTempDir == null || archiveExtractTime <= 0) {
return false; // No archive loaded or timestamp not set
}
File tempDir = currentArchiveTempDir.toFile();
if (!tempDir.exists()) {
return false;
}
try {
return directoryHasChanges(tempDir, archiveExtractTime);
} catch (Exception e) {
// On error, assume changes to be safe
System.err.println("Warning: Could not determine if archive was modified: " + e.getMessage());
return true;
}
}
/**
* Recursively check if any file/directory in the tree has been modified after given time.
*/
private boolean directoryHasChanges(File dir, long extractTime) {
File[] files = dir.listFiles();
if (files == null) return false;
for (File file : files) {
if (file.lastModified() > extractTime) {
return true; // File was modified/created after extraction
}
if (file.isDirectory() && directoryHasChanges(file, extractTime)) {
return true; // Recursively check subdirectories
}
}
return false;
}
/**
@ -4431,6 +4526,25 @@ public class FilePanelTab extends JPanel {
* Cleanup resources when tab is closed.
*/
public void cleanup() {
// Save any changes made to currently open archive before cleanup (only if modified)
if (currentArchiveTempDir != null && currentArchiveSourceFile != null) {
if (FileOperations.supportsArchiveRewrite(currentArchiveSourceFile)) {
if (hasArchiveBeenModified()) {
try {
FileOperations.rewriteArchiveFromDirectory(currentArchiveTempDir.toFile(), currentArchiveSourceFile, currentArchivePassword, null);
} catch (IOException e) {
System.err.println("Warning: Failed to save changes to archive " + currentArchiveSourceFile.getName() + " on tab close: " + e.getMessage());
}
}
}
try {
deleteTempDirRecursively(currentArchiveTempDir);
} catch (Exception e) {
System.err.println("Warning: Failed to cleanup archive temp directory: " + e.getMessage());
}
clearOpenedArchiveSession();
}
if (isFtpTab && ftpProfile != null) {
FtpService.disconnect(ftpProfile);
}

View File

@ -2873,7 +2873,7 @@ public class MainWindow extends JFrame {
if (forceInternalEditor) {
FileEditor editor = new FileEditor(this, file, config, false);
editor.setOnSaveSuccess(() -> syncArchiveAfterInternalEdit(file));
// Archive will be synced automatically when user leaves it, not immediately after edit
editor.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosed(java.awt.event.WindowEvent e) {