backgroun file operations, error handling
This commit is contained in:
parent
7ba25ed50a
commit
af98d9f112
@ -11,7 +11,7 @@ import java.awt.event.KeyEvent;
|
||||
*/
|
||||
public class MainApp {
|
||||
|
||||
public static final String APP_VERSION = "0.0.4";
|
||||
public static final String APP_VERSION = "0.0.5";
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Set application name for X11/Wayland WM_CLASS
|
||||
|
||||
@ -49,10 +49,24 @@ public class FileOperations {
|
||||
}
|
||||
}
|
||||
|
||||
if (source.isDirectory()) {
|
||||
copyDirectory(source.toPath(), target.toPath(), totalSize, currentCopied, callback, globalResponse);
|
||||
} else {
|
||||
copyFileWithProgress(source.toPath(), target.toPath(), totalSize, currentCopied, callback);
|
||||
while (true) {
|
||||
try {
|
||||
if (source.isDirectory()) {
|
||||
copyDirectory(source.toPath(), target.toPath(), totalSize, currentCopied, callback, globalResponse);
|
||||
} else {
|
||||
copyFileWithProgress(source.toPath(), target.toPath(), totalSize, currentCopied, callback);
|
||||
}
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(source, e);
|
||||
if (res == ErrorResponse.ABORT) throw e;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
break;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,6 +98,7 @@ public class FileOperations {
|
||||
}
|
||||
|
||||
private static void copyFileWithProgress(Path source, Path target, long totalSize, long[] totalCopied, ProgressCallback callback) throws IOException {
|
||||
long initialTotalCopied = totalCopied[0];
|
||||
try (InputStream in = Files.newInputStream(source);
|
||||
OutputStream out = Files.newOutputStream(target)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
@ -96,6 +111,9 @@ public class FileOperations {
|
||||
callback.onProgress(totalCopied[0], totalSize, source.getFileName().toString());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
totalCopied[0] = initialTotalCopied;
|
||||
throw e;
|
||||
}
|
||||
Files.setLastModifiedTime(target, Files.getLastModifiedTime(source));
|
||||
}
|
||||
@ -105,8 +123,20 @@ public class FileOperations {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
Path targetDir = target.resolve(source.relativize(dir));
|
||||
Files.createDirectories(targetDir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
while (true) {
|
||||
try {
|
||||
Files.createDirectories(targetDir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(dir.toFile(), e);
|
||||
if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,8 +158,20 @@ public class FileOperations {
|
||||
}
|
||||
}
|
||||
|
||||
copyFileWithProgress(file, targetFile, totalSize, totalCopied, callback);
|
||||
return FileVisitResult.CONTINUE;
|
||||
while (true) {
|
||||
try {
|
||||
copyFileWithProgress(file, targetFile, totalSize, totalCopied, callback);
|
||||
return FileVisitResult.CONTINUE;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(file.toFile(), e);
|
||||
if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -170,7 +212,21 @@ public class FileOperations {
|
||||
callback.onProgress(current, total, source.getName());
|
||||
}
|
||||
|
||||
Files.move(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
while (true) {
|
||||
try {
|
||||
Files.move(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(source, e);
|
||||
if (res == ErrorResponse.ABORT) throw e;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
break;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,10 +246,24 @@ public class FileOperations {
|
||||
callback.onProgress(current, total, file.getName());
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
deleteDirectory(file.toPath());
|
||||
} else {
|
||||
Files.delete(file.toPath());
|
||||
while (true) {
|
||||
try {
|
||||
if (file.isDirectory()) {
|
||||
deleteDirectory(file.toPath(), callback);
|
||||
} else {
|
||||
Files.delete(file.toPath());
|
||||
}
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(file, e);
|
||||
if (res == ErrorResponse.ABORT) throw e;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
break;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -219,18 +289,43 @@ public class FileOperations {
|
||||
/**
|
||||
* Delete directory recursively
|
||||
*/
|
||||
private static void deleteDirectory(Path directory) throws IOException {
|
||||
private static void deleteDirectory(Path directory, ProgressCallback callback) throws IOException {
|
||||
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
while (true) {
|
||||
try {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(file.toFile(), e);
|
||||
if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
if (exc != null) throw exc;
|
||||
while (true) {
|
||||
try {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
} catch (IOException e) {
|
||||
if (callback != null) {
|
||||
ErrorResponse res = callback.onError(dir.toFile(), e);
|
||||
if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE;
|
||||
if (res == ErrorResponse.RETRY) continue;
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -572,10 +667,15 @@ public class FileOperations {
|
||||
YES, NO, YES_TO_ALL, NO_TO_ALL, CANCEL
|
||||
}
|
||||
|
||||
public enum ErrorResponse {
|
||||
SKIP, RETRY, ABORT
|
||||
}
|
||||
|
||||
public interface ProgressCallback {
|
||||
void onProgress(long current, long total, String currentFile);
|
||||
default boolean isCancelled() { return false; }
|
||||
default OverwriteResponse confirmOverwrite(File file) { return OverwriteResponse.YES; }
|
||||
default ErrorResponse onError(File file, Exception e) { return ErrorResponse.ABORT; }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1387,6 +1387,35 @@ public class FilePanelTab extends JPanel {
|
||||
public boolean isCancelled() {
|
||||
return progressDialog.isCancelled();
|
||||
}
|
||||
@Override
|
||||
public FileOperations.ErrorResponse onError(File file, Exception e) {
|
||||
final FileOperations.ErrorResponse[] result = new FileOperations.ErrorResponse[1];
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
Object[] options = {"Skip", "Retry", "Abort"};
|
||||
int n = JOptionPane.showOptionDialog(progressDialog,
|
||||
"Error deleting file: " + file.getName() + "\n" + e.getMessage(),
|
||||
"Error",
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[0]);
|
||||
|
||||
switch (n) {
|
||||
case 0: result[0] = FileOperations.ErrorResponse.SKIP; break;
|
||||
case 1: result[0] = FileOperations.ErrorResponse.RETRY; break;
|
||||
default:
|
||||
result[0] = FileOperations.ErrorResponse.ABORT;
|
||||
progressDialog.cancel();
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
result[0] = FileOperations.ErrorResponse.ABORT;
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
});
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
@ -1781,14 +1810,44 @@ public class FilePanelTab extends JPanel {
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
};
|
||||
|
||||
if (action == ClipboardService.ClipboardAction.CUT) {
|
||||
FileOperations.move(itemsToPaste, targetDir, callback);
|
||||
} else {
|
||||
FileOperations.copy(itemsToPaste, targetDir, callback);
|
||||
@Override
|
||||
public FileOperations.ErrorResponse onError(File file, Exception e) {
|
||||
final FileOperations.ErrorResponse[] result = new FileOperations.ErrorResponse[1];
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
Object[] options = {"Skip", "Retry", "Abort"};
|
||||
int n = JOptionPane.showOptionDialog(progressDialog,
|
||||
"Error operating on file: " + file.getName() + "\n" + e.getMessage(),
|
||||
"Error",
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[0]);
|
||||
|
||||
switch (n) {
|
||||
case 0: result[0] = FileOperations.ErrorResponse.SKIP; break;
|
||||
case 1: result[0] = FileOperations.ErrorResponse.RETRY; break;
|
||||
default:
|
||||
result[0] = FileOperations.ErrorResponse.ABORT;
|
||||
progressDialog.cancel();
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
result[0] = FileOperations.ErrorResponse.ABORT;
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
return result[0];
|
||||
}
|
||||
};
|
||||
|
||||
if (action == ClipboardService.ClipboardAction.CUT) {
|
||||
FileOperations.move(itemsToPaste, targetDir, callback);
|
||||
} else {
|
||||
FileOperations.copy(itemsToPaste, targetDir, callback);
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
loadDirectory(targetDir, false);
|
||||
if (!itemsToPaste.isEmpty()) {
|
||||
|
||||
@ -1267,15 +1267,15 @@ public class MainWindow extends JFrame {
|
||||
FilePanel targetPanel = (activePanel == leftPanel) ? rightPanel : leftPanel;
|
||||
File targetDir = targetPanel.getCurrentDirectory();
|
||||
|
||||
int result = JOptionPane.showConfirmDialog(this,
|
||||
int result = showConfirmWithBackground(
|
||||
String.format("Copy %d items to:\n%s", selectedItems.size(), targetDir.getAbsolutePath()),
|
||||
"Copy",
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
"Copy");
|
||||
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
if (result == 0 || result == 1) {
|
||||
boolean modal = (result == 0);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.copy(selectedItems, targetDir, callback);
|
||||
}, "Copy completed", true, targetPanel);
|
||||
}, "Copy completed", true, modal, targetPanel);
|
||||
} else {
|
||||
if (activePanel != null && activePanel.getFileTable() != null) {
|
||||
activePanel.getFileTable().requestFocusInWindow();
|
||||
@ -1300,21 +1300,43 @@ public class MainWindow extends JFrame {
|
||||
FilePanel targetPanel = (activePanel == leftPanel) ? rightPanel : leftPanel;
|
||||
File targetDir = targetPanel.getCurrentDirectory();
|
||||
|
||||
int result = JOptionPane.showConfirmDialog(this,
|
||||
int result = showConfirmWithBackground(
|
||||
String.format("Move %d items to:\n%s", selectedItems.size(), targetDir.getAbsolutePath()),
|
||||
"Move",
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
"Move");
|
||||
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
if (result == 0 || result == 1) {
|
||||
boolean modal = (result == 0);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.move(selectedItems, targetDir, callback);
|
||||
}, "Move completed", false, activePanel, targetPanel);
|
||||
}, "Move completed", false, modal, activePanel, targetPanel);
|
||||
} else {
|
||||
if (activePanel != null && activePanel.getFileTable() != null) {
|
||||
activePanel.getFileTable().requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int showConfirmWithBackground(String message, String title) {
|
||||
Object[] options = {"OK", "Background (F2)", "Cancel"};
|
||||
JOptionPane pane = new JOptionPane(message, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[0]);
|
||||
JDialog dialog = pane.createDialog(this, title);
|
||||
|
||||
pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "background");
|
||||
pane.getActionMap().put("background", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
pane.setValue(options[1]);
|
||||
dialog.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.setVisible(true);
|
||||
Object selectedValue = pane.getValue();
|
||||
if (selectedValue == null) return 2; // Cancel
|
||||
if (selectedValue.equals(options[0])) return 0; // OK
|
||||
if (selectedValue.equals(options[1])) return 1; // Background
|
||||
return 2; // Cancel
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete selected files
|
||||
@ -1960,14 +1982,25 @@ public class MainWindow extends JFrame {
|
||||
* Execute file operation with error handling
|
||||
*/
|
||||
private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, FilePanel... panelsToRefresh) {
|
||||
performFileOperation(operation, successMessage, showBytes, null, panelsToRefresh);
|
||||
performFileOperation(operation, successMessage, showBytes, true, null, panelsToRefresh);
|
||||
}
|
||||
|
||||
private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, boolean modal, FilePanel... panelsToRefresh) {
|
||||
performFileOperation(operation, successMessage, showBytes, modal, null, panelsToRefresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute file operation with error handling and a task to run after completion and refresh.
|
||||
*/
|
||||
private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, Runnable postTask, FilePanel... panelsToRefresh) {
|
||||
ProgressDialog progressDialog = new ProgressDialog(this, "File Operation");
|
||||
performFileOperation(operation, successMessage, showBytes, true, postTask, panelsToRefresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute file operation with error handling and a task to run after completion and refresh.
|
||||
*/
|
||||
private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, boolean modal, Runnable postTask, FilePanel... panelsToRefresh) {
|
||||
ProgressDialog progressDialog = new ProgressDialog(this, "File Operation", modal);
|
||||
progressDialog.setDisplayAsBytes(showBytes);
|
||||
|
||||
FileOperations.ProgressCallback callback = new FileOperations.ProgressCallback() {
|
||||
@ -2012,6 +2045,36 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileOperations.ErrorResponse onError(File file, Exception e) {
|
||||
final FileOperations.ErrorResponse[] result = new FileOperations.ErrorResponse[1];
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
Object[] options = {"Skip", "Retry", "Abort"};
|
||||
int n = JOptionPane.showOptionDialog(progressDialog,
|
||||
"Error operating on file: " + file.getName() + "\n" + e.getMessage(),
|
||||
"Error",
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[0]);
|
||||
|
||||
switch (n) {
|
||||
case 0: result[0] = FileOperations.ErrorResponse.SKIP; break;
|
||||
case 1: result[0] = FileOperations.ErrorResponse.RETRY; break;
|
||||
default:
|
||||
result[0] = FileOperations.ErrorResponse.ABORT;
|
||||
progressDialog.cancel();
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
result[0] = FileOperations.ErrorResponse.ABORT;
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
};
|
||||
|
||||
// Run operation in a background thread
|
||||
|
||||
@ -17,7 +17,11 @@ public class ProgressDialog extends JDialog {
|
||||
private long startTime = -1;
|
||||
|
||||
public ProgressDialog(Frame owner, String title) {
|
||||
super(owner, title, true);
|
||||
this(owner, title, true);
|
||||
}
|
||||
|
||||
public ProgressDialog(Frame owner, String title, boolean modal) {
|
||||
super(owner, title, modal);
|
||||
|
||||
setLayout(new BorderLayout(10, 10));
|
||||
((JPanel)getContentPane()).setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user