archive operations rewritten
This commit is contained in:
parent
0a3f3e75e0
commit
c2865750c0
@ -15,7 +15,7 @@ import java.io.InputStreamReader;
|
|||||||
*/
|
*/
|
||||||
public class MainApp {
|
public class MainApp {
|
||||||
|
|
||||||
public static final String APP_VERSION = "1.4.5";
|
public static final String APP_VERSION = "1.4.6";
|
||||||
|
|
||||||
public enum OS {
|
public enum OS {
|
||||||
WINDOWS, LINUX, MACOS, UNKNOWN
|
WINDOWS, LINUX, MACOS, UNKNOWN
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import java.awt.event.KeyEvent;
|
|||||||
import java.awt.event.InputEvent;
|
import java.awt.event.InputEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -47,6 +48,8 @@ public class FileEditor extends JFrame {
|
|||||||
private final int pageSizeBytes = 64 * 1024; // 64 KB page size
|
private final int pageSizeBytes = 64 * 1024; // 64 KB page size
|
||||||
// Allow loading entire file into memory up to this limit (100 MB)
|
// Allow loading entire file into memory up to this limit (100 MB)
|
||||||
private final long maxFullLoadBytes = 100L * 1024L * 1024L; // 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 hexControlPanel = null;
|
||||||
private JPanel northPanel = null;
|
private JPanel northPanel = null;
|
||||||
private JButton prevPageBtn = null;
|
private JButton prevPageBtn = null;
|
||||||
@ -1073,7 +1076,7 @@ public class FileEditor extends JFrame {
|
|||||||
SwingUtilities.invokeLater(() -> textArea.requestFocusInWindow());
|
SwingUtilities.invokeLater(() -> textArea.requestFocusInWindow());
|
||||||
} else {
|
} else {
|
||||||
// Small or text file: load fully
|
// Small or text file: load fully
|
||||||
fileBytes = Files.readAllBytes(file.toPath());
|
fileBytes = readFileBytesWithChunking(file, size);
|
||||||
boolean binary = isBinary(fileBytes);
|
boolean binary = isBinary(fileBytes);
|
||||||
if (binary && readOnly) {
|
if (binary && readOnly) {
|
||||||
hexMode = true;
|
hexMode = true;
|
||||||
@ -1115,6 +1118,22 @@ public class FileEditor extends JFrame {
|
|||||||
return nonPrintable > (len / 4);
|
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) {
|
private boolean isImageFile(File f) {
|
||||||
String name = f.getName().toLowerCase();
|
String name = f.getName().toLowerCase();
|
||||||
return name.endsWith(".jpg") || name.endsWith(".jpeg") ||
|
return name.endsWith(".jpg") || name.endsWith(".jpeg") ||
|
||||||
|
|||||||
@ -87,6 +87,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
private Path currentArchiveTempDir = null;
|
private Path currentArchiveTempDir = null;
|
||||||
private File currentArchiveSourceFile = null;
|
private File currentArchiveSourceFile = null;
|
||||||
private String currentArchivePassword = null;
|
private String currentArchivePassword = null;
|
||||||
|
private long archiveExtractTime = 0; // Track time of extraction to detect changes
|
||||||
private File archiveReturnDirectory = null;
|
private File archiveReturnDirectory = null;
|
||||||
private Point archiveReturnViewPosition = null;
|
private Point archiveReturnViewPosition = null;
|
||||||
private boolean inlineRenameActive = false;
|
private boolean inlineRenameActive = false;
|
||||||
@ -1923,12 +1924,26 @@ public class FilePanelTab extends JPanel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup previous archive temp dir when navigating away from it.
|
* 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) {
|
private void cleanupArchiveTempDirIfNeeded(File newDirectory) {
|
||||||
try {
|
try {
|
||||||
if (currentArchiveTempDir != null) {
|
if (currentArchiveTempDir != null) {
|
||||||
Path newPath = (newDirectory != null) ? newDirectory.toPath().toAbsolutePath().normalize() : null;
|
Path newPath = (newDirectory != null) ? newDirectory.toPath().toAbsolutePath().normalize() : null;
|
||||||
if (newPath == null || !newPath.startsWith(currentArchiveTempDir)) {
|
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);
|
deleteTempDirRecursively(currentArchiveTempDir);
|
||||||
clearOpenedArchiveSession();
|
clearOpenedArchiveSession();
|
||||||
}
|
}
|
||||||
@ -1947,11 +1962,22 @@ public class FilePanelTab extends JPanel {
|
|||||||
temp -> {
|
temp -> {
|
||||||
try {
|
try {
|
||||||
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
|
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);
|
deleteTempDirRecursively(currentArchiveTempDir);
|
||||||
}
|
}
|
||||||
} catch (Exception ignore) {}
|
} catch (Exception ignore) {}
|
||||||
currentArchiveTempDir = temp;
|
currentArchiveTempDir = temp;
|
||||||
currentArchiveSourceFile = archive;
|
currentArchiveSourceFile = archive;
|
||||||
|
archiveExtractTime = System.currentTimeMillis(); // Track extraction time
|
||||||
|
|
||||||
if (finalEntryName == null || finalEntryName.isBlank()) {
|
if (finalEntryName == null || finalEntryName.isBlank()) {
|
||||||
loadDirectory(temp.toFile(), true, true);
|
loadDirectory(temp.toFile(), true, true);
|
||||||
@ -2046,11 +2072,22 @@ public class FilePanelTab extends JPanel {
|
|||||||
temp -> {
|
temp -> {
|
||||||
try {
|
try {
|
||||||
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
|
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);
|
deleteTempDirRecursively(currentArchiveTempDir);
|
||||||
}
|
}
|
||||||
} catch (Exception ignore) {}
|
} catch (Exception ignore) {}
|
||||||
currentArchiveTempDir = temp;
|
currentArchiveTempDir = temp;
|
||||||
currentArchiveSourceFile = archiveFile;
|
currentArchiveSourceFile = archiveFile;
|
||||||
|
archiveExtractTime = System.currentTimeMillis(); // Track extraction time
|
||||||
loadDirectory(temp.toFile());
|
loadDirectory(temp.toFile());
|
||||||
},
|
},
|
||||||
error -> {
|
error -> {
|
||||||
@ -2587,6 +2624,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
} catch (Exception ignore) {}
|
} catch (Exception ignore) {}
|
||||||
currentArchiveTempDir = temp;
|
currentArchiveTempDir = temp;
|
||||||
currentArchiveSourceFile = archiveFile;
|
currentArchiveSourceFile = archiveFile;
|
||||||
|
archiveExtractTime = System.currentTimeMillis(); // Track extraction time
|
||||||
loadDirectory(temp.toFile(), true, true);
|
loadDirectory(temp.toFile(), true, true);
|
||||||
},
|
},
|
||||||
error -> {
|
error -> {
|
||||||
@ -3001,6 +3039,18 @@ public class FilePanelTab extends JPanel {
|
|||||||
File parent = currentArchiveSourceFile.getParentFile();
|
File parent = currentArchiveSourceFile.getParentFile();
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
String archiveName = currentArchiveSourceFile.getName();
|
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
|
// cleanup temp dir before switching back
|
||||||
deleteTempDirRecursively(currentArchiveTempDir);
|
deleteTempDirRecursively(currentArchiveTempDir);
|
||||||
clearOpenedArchiveSession();
|
clearOpenedArchiveSession();
|
||||||
@ -3120,6 +3170,51 @@ public class FilePanelTab extends JPanel {
|
|||||||
currentArchiveTempDir = null;
|
currentArchiveTempDir = null;
|
||||||
currentArchiveSourceFile = null;
|
currentArchiveSourceFile = null;
|
||||||
currentArchivePassword = 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.
|
* Cleanup resources when tab is closed.
|
||||||
*/
|
*/
|
||||||
public void cleanup() {
|
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) {
|
if (isFtpTab && ftpProfile != null) {
|
||||||
FtpService.disconnect(ftpProfile);
|
FtpService.disconnect(ftpProfile);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2873,7 +2873,7 @@ public class MainWindow extends JFrame {
|
|||||||
|
|
||||||
if (forceInternalEditor) {
|
if (forceInternalEditor) {
|
||||||
FileEditor editor = new FileEditor(this, file, config, false);
|
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() {
|
editor.addWindowListener(new java.awt.event.WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosed(java.awt.event.WindowEvent e) {
|
public void windowClosed(java.awt.event.WindowEvent e) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user