archive operations rewritten
This commit is contained in:
parent
0a3f3e75e0
commit
c2865750c0
@ -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
|
||||
|
||||
@ -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") ||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user