progress dialog added
This commit is contained in:
parent
2e2f4bd4f5
commit
69afa0c738
@ -23,26 +23,84 @@ public class FileOperations {
|
||||
throw new IOException("Target directory does not exist");
|
||||
}
|
||||
|
||||
int current = 0;
|
||||
int total = items.size();
|
||||
long totalSize = calculateTotalSize(items);
|
||||
final long[] currentCopied = {0};
|
||||
|
||||
for (FileItem item : items) {
|
||||
current++;
|
||||
if (callback != null && callback.isCancelled()) break;
|
||||
File source = item.getFile();
|
||||
File target = new File(targetDirectory, source.getName());
|
||||
|
||||
if (callback != null) {
|
||||
callback.onProgress(current, total, source.getName());
|
||||
}
|
||||
|
||||
if (source.isDirectory()) {
|
||||
copyDirectory(source.toPath(), target.toPath());
|
||||
copyDirectory(source.toPath(), target.toPath(), totalSize, currentCopied, callback);
|
||||
} else {
|
||||
copyFile(source.toPath(), target.toPath());
|
||||
copyFileWithProgress(source.toPath(), target.toPath(), totalSize, currentCopied, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long calculateTotalSize(List<FileItem> items) {
|
||||
long total = 0;
|
||||
for (FileItem item : items) {
|
||||
total += calculateSize(item.getFile().toPath());
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private static long calculateSize(Path path) {
|
||||
if (!Files.exists(path)) return 0;
|
||||
if (!Files.isDirectory(path)) {
|
||||
try { return Files.size(path); } catch (IOException e) { return 0; }
|
||||
}
|
||||
final long[] size = {0};
|
||||
try {
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
size[0] += attrs.size();
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException ignore) {}
|
||||
return size[0];
|
||||
}
|
||||
|
||||
private static void copyFileWithProgress(Path source, Path target, long totalSize, long[] totalCopied, ProgressCallback callback) throws IOException {
|
||||
try (InputStream in = Files.newInputStream(source);
|
||||
OutputStream out = Files.newOutputStream(target)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int length;
|
||||
while ((length = in.read(buffer)) > 0) {
|
||||
if (callback != null && callback.isCancelled()) return;
|
||||
out.write(buffer, 0, length);
|
||||
totalCopied[0] += length;
|
||||
if (callback != null) {
|
||||
callback.onProgress(totalCopied[0], totalSize, source.getFileName().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Files.setLastModifiedTime(target, Files.getLastModifiedTime(source));
|
||||
}
|
||||
|
||||
private static void copyDirectory(Path source, Path target, long totalSize, final long[] totalCopied, ProgressCallback callback) throws IOException {
|
||||
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
Path targetDir = target.resolve(source.relativize(dir));
|
||||
Files.createDirectories(targetDir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (callback != null && callback.isCancelled()) return FileVisitResult.TERMINATE;
|
||||
Path targetFile = target.resolve(source.relativize(file));
|
||||
copyFileWithProgress(file, targetFile, totalSize, totalCopied, callback);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Move files/directories to target directory
|
||||
*/
|
||||
@ -55,6 +113,7 @@ public class FileOperations {
|
||||
int total = items.size();
|
||||
|
||||
for (FileItem item : items) {
|
||||
if (callback != null && callback.isCancelled()) break;
|
||||
current++;
|
||||
File source = item.getFile();
|
||||
File target = new File(targetDirectory, source.getName());
|
||||
@ -75,6 +134,7 @@ public class FileOperations {
|
||||
int total = items.size();
|
||||
|
||||
for (FileItem item : items) {
|
||||
if (callback != null && callback.isCancelled()) break;
|
||||
current++;
|
||||
File file = item.getFile();
|
||||
|
||||
@ -108,34 +168,6 @@ public class FileOperations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file
|
||||
*/
|
||||
private static void copyFile(Path source, Path target) throws IOException {
|
||||
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory recursively
|
||||
*/
|
||||
private static void copyDirectory(Path source, Path target) throws IOException {
|
||||
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
Path targetDir = target.resolve(source.relativize(dir));
|
||||
Files.createDirectories(targetDir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Path targetFile = target.resolve(source.relativize(file));
|
||||
copyFile(file, targetFile);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete directory recursively
|
||||
*/
|
||||
@ -243,8 +275,8 @@ public class FileOperations {
|
||||
*/
|
||||
public static void zip(List<FileItem> items, File targetZipFile, ProgressCallback callback) throws IOException {
|
||||
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(targetZipFile))) {
|
||||
int current = 0;
|
||||
int total = items.size();
|
||||
long current = 0;
|
||||
long total = items.size();
|
||||
|
||||
for (FileItem item : items) {
|
||||
current++;
|
||||
@ -304,7 +336,7 @@ public class FileOperations {
|
||||
File newFile = new File(targetDirectory, entry.getName());
|
||||
|
||||
if (callback != null) {
|
||||
callback.onProgress(0, 0, entry.getName());
|
||||
callback.onProgress(0L, 0L, entry.getName());
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
@ -338,7 +370,8 @@ public class FileOperations {
|
||||
* Callback pro progress operací
|
||||
*/
|
||||
public interface ProgressCallback {
|
||||
void onProgress(int current, int total, String currentFile);
|
||||
void onProgress(long current, long total, String currentFile);
|
||||
default boolean isCancelled() { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -955,15 +955,35 @@ public class FilePanelTab extends JPanel {
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
if (res == JOptionPane.YES_OPTION) {
|
||||
try {
|
||||
java.util.List<FileItem> toDelete = new java.util.ArrayList<>();
|
||||
toDelete.add(item);
|
||||
com.kfmanager.service.FileOperations.delete(toDelete, null);
|
||||
// reload current directory
|
||||
loadDirectory(getCurrentDirectory());
|
||||
} catch (Exception ex) {
|
||||
try { JOptionPane.showMessageDialog(FilePanelTab.this, "Delete failed: " + ex.getMessage()); } catch (Exception ignore) {}
|
||||
}
|
||||
java.util.List<FileItem> toDelete = new java.util.ArrayList<>();
|
||||
toDelete.add(item);
|
||||
Window parentWindow = SwingUtilities.getWindowAncestor(FilePanelTab.this);
|
||||
ProgressDialog progressDialog = new ProgressDialog(parentWindow instanceof Frame ? (Frame)parentWindow : null, "Deleting");
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
com.kfmanager.service.FileOperations.delete(toDelete, new com.kfmanager.service.FileOperations.ProgressCallback() {
|
||||
@Override
|
||||
public void onProgress(long current, long total, String currentFile) {
|
||||
progressDialog.updateProgress(current, total, currentFile);
|
||||
}
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return progressDialog.isCancelled();
|
||||
}
|
||||
});
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
loadDirectory(getCurrentDirectory());
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
JOptionPane.showMessageDialog(FilePanelTab.this, "Delete failed: " + ex.getMessage());
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
progressDialog.setVisible(true);
|
||||
}
|
||||
});
|
||||
menu.add(deleteItem);
|
||||
|
||||
@ -652,9 +652,9 @@ public class MainWindow extends JFrame {
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
performFileOperation(() -> {
|
||||
FileOperations.copy(selectedItems, targetDir, null);
|
||||
}, "Kopírování dokončeno", targetPanel);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.copy(selectedItems, targetDir, callback);
|
||||
}, "Kopírování dokončeno", true, targetPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -680,9 +680,9 @@ public class MainWindow extends JFrame {
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
performFileOperation(() -> {
|
||||
FileOperations.move(selectedItems, targetDir, null);
|
||||
}, "Přesouvání dokončeno", activePanel, targetPanel);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.move(selectedItems, targetDir, callback);
|
||||
}, "Přesouvání dokončeno", false, activePanel, targetPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -718,9 +718,9 @@ public class MainWindow extends JFrame {
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
if (result == JOptionPane.YES_OPTION) {
|
||||
performFileOperation(() -> {
|
||||
FileOperations.delete(selectedItems, null);
|
||||
}, "Mazání dokončeno", activePanel);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.delete(selectedItems, callback);
|
||||
}, "Mazání dokončeno", false, activePanel);
|
||||
|
||||
// After deletion and refresh, restore selection: stay on same row if possible,
|
||||
// otherwise move selection one row up.
|
||||
@ -793,9 +793,9 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
final File finalTargetZip = targetZip;
|
||||
performFileOperation(() -> {
|
||||
FileOperations.zip(selectedItems, finalTargetZip, null);
|
||||
}, "Zabaleno do " + zipName, targetPanel);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.zip(selectedItems, finalTargetZip, callback);
|
||||
}, "Zabaleno do " + zipName, false, targetPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -829,9 +829,9 @@ public class MainWindow extends JFrame {
|
||||
JOptionPane.OK_CANCEL_OPTION);
|
||||
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
performFileOperation(() -> {
|
||||
FileOperations.unzip(zipFile, targetDir, null);
|
||||
}, "Rozbaleno do " + targetDir.getName(), targetPanel);
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.unzip(zipFile, targetDir, callback);
|
||||
}, "Rozbaleno do " + targetDir.getName(), false, targetPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -858,9 +858,9 @@ public class MainWindow extends JFrame {
|
||||
"New name:",
|
||||
item.getName());
|
||||
if (newName != null && !newName.trim().isEmpty() && !newName.equals(item.getName())) {
|
||||
performFileOperation(() -> {
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.rename(item.getFile(), newName.trim());
|
||||
}, "Přejmenování dokončeno", activePanel);
|
||||
}, "Přejmenování dokončeno", false, activePanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -875,9 +875,9 @@ public class MainWindow extends JFrame {
|
||||
"New directory");
|
||||
|
||||
if (dirName != null && !dirName.trim().isEmpty()) {
|
||||
performFileOperation(() -> {
|
||||
performFileOperation((callback) -> {
|
||||
FileOperations.createDirectory(activePanel.getCurrentDirectory(), dirName.trim());
|
||||
}, "Directory created", activePanel);
|
||||
}, "Directory created", false, activePanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1066,21 +1066,50 @@ public class MainWindow extends JFrame {
|
||||
/**
|
||||
* Execute file operation with error handling
|
||||
*/
|
||||
private void performFileOperation(FileOperation operation, String successMessage, FilePanel... panelsToRefresh) {
|
||||
try {
|
||||
operation.execute();
|
||||
for (FilePanel panel : panelsToRefresh) {
|
||||
if (panel.getCurrentDirectory() != null) {
|
||||
panel.loadDirectory(panel.getCurrentDirectory());
|
||||
}
|
||||
private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, FilePanel... panelsToRefresh) {
|
||||
ProgressDialog progressDialog = new ProgressDialog(this, "Operační systém");
|
||||
progressDialog.setDisplayAsBytes(showBytes);
|
||||
|
||||
FileOperations.ProgressCallback callback = new FileOperations.ProgressCallback() {
|
||||
@Override
|
||||
public void onProgress(long current, long total, String currentFile) {
|
||||
progressDialog.updateProgress(current, total, currentFile);
|
||||
}
|
||||
// Info okna o úspěchu zrušena - operace proběhne tiše
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Chyba: " + e.getMessage(),
|
||||
"Chyba",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return progressDialog.isCancelled();
|
||||
}
|
||||
};
|
||||
|
||||
// Run operation in a background thread
|
||||
new Thread(() -> {
|
||||
try {
|
||||
operation.execute(callback);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
for (FilePanel panel : panelsToRefresh) {
|
||||
if (panel.getCurrentDirectory() != null) {
|
||||
panel.loadDirectory(panel.getCurrentDirectory());
|
||||
}
|
||||
}
|
||||
if (callback.isCancelled()) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this, "Operace byla přerušena uživatelem.");
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
progressDialog.dispose();
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Chyba: " + e.getMessage(),
|
||||
"Chyba",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
progressDialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1142,6 +1171,6 @@ public class MainWindow extends JFrame {
|
||||
|
||||
@FunctionalInterface
|
||||
private interface FileOperation {
|
||||
void execute() throws Exception;
|
||||
void execute(FileOperations.ProgressCallback callback) throws Exception;
|
||||
}
|
||||
}
|
||||
|
||||
141
src/main/java/com/kfmanager/ui/ProgressDialog.java
Normal file
141
src/main/java/com/kfmanager/ui/ProgressDialog.java
Normal file
@ -0,0 +1,141 @@
|
||||
package com.kfmanager.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
public class ProgressDialog extends JDialog {
|
||||
private final JProgressBar progressBar;
|
||||
private final JLabel statusLabel;
|
||||
private final JLabel speedLabel;
|
||||
private final JButton pauseButton;
|
||||
private final JButton cancelButton;
|
||||
|
||||
private volatile boolean cancelled = false;
|
||||
private volatile boolean paused = false;
|
||||
private long startTime = -1;
|
||||
|
||||
public ProgressDialog(Frame owner, String title) {
|
||||
super(owner, title, true);
|
||||
|
||||
setLayout(new BorderLayout(10, 10));
|
||||
((JPanel)getContentPane()).setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||
|
||||
JPanel infoPanel = new JPanel(new GridLayout(2, 1, 5, 5));
|
||||
statusLabel = new JLabel("Starting...");
|
||||
speedLabel = new JLabel("Speed: 0 B/s");
|
||||
infoPanel.add(statusLabel);
|
||||
infoPanel.add(speedLabel);
|
||||
add(infoPanel, BorderLayout.NORTH);
|
||||
|
||||
progressBar = new JProgressBar(0, 100);
|
||||
progressBar.setStringPainted(true);
|
||||
progressBar.setPreferredSize(new Dimension(400, 25));
|
||||
add(progressBar, BorderLayout.CENTER);
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
pauseButton = new JButton("Pause");
|
||||
cancelButton = new JButton("Cancel");
|
||||
|
||||
pauseButton.addActionListener(e -> {
|
||||
paused = !paused;
|
||||
pauseButton.setText(paused ? "Resume" : "Pause");
|
||||
if (!paused) {
|
||||
synchronized(this) {
|
||||
this.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addActionListener(e -> {
|
||||
cancelled = true;
|
||||
paused = false;
|
||||
cancelButton.setEnabled(false);
|
||||
statusLabel.setText("Cancelling...");
|
||||
synchronized(this) {
|
||||
this.notifyAll();
|
||||
}
|
||||
});
|
||||
|
||||
buttonPanel.add(pauseButton);
|
||||
buttonPanel.add(cancelButton);
|
||||
add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
cancelled = true;
|
||||
}
|
||||
});
|
||||
|
||||
pack();
|
||||
setMinimumSize(new Dimension(450, getHeight()));
|
||||
setLocationRelativeTo(owner);
|
||||
}
|
||||
|
||||
private boolean displayAsBytes = false;
|
||||
|
||||
public void setDisplayAsBytes(boolean displayAsBytes) {
|
||||
this.displayAsBytes = displayAsBytes;
|
||||
}
|
||||
|
||||
public void updateProgress(long current, long total, String fileName) {
|
||||
if (startTime == -1) {
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (total > 0) {
|
||||
progressBar.setIndeterminate(false);
|
||||
int percent = (int) ((double) current / total * 100);
|
||||
progressBar.setValue(percent);
|
||||
if (displayAsBytes) {
|
||||
progressBar.setString(formatSize(current) + " / " + formatSize(total) + " (" + percent + "%)");
|
||||
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
if (elapsed > 0) {
|
||||
long bytesPerSec = (long) (current / (elapsed / 1000.0));
|
||||
speedLabel.setText("Speed: " + formatSize(bytesPerSec) + "/s");
|
||||
}
|
||||
} else {
|
||||
progressBar.setString(current + " / " + total + " (" + percent + "%)");
|
||||
speedLabel.setText("");
|
||||
}
|
||||
} else {
|
||||
progressBar.setIndeterminate(true);
|
||||
speedLabel.setText("");
|
||||
}
|
||||
statusLabel.setText(fileName);
|
||||
});
|
||||
|
||||
checkState();
|
||||
}
|
||||
|
||||
private String formatSize(long bytes) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
||||
char pre = "KMGTPE".charAt(exp - 1);
|
||||
return String.format("%.1f %cB", bytes / Math.pow(1024, exp), pre);
|
||||
}
|
||||
|
||||
private void checkState() {
|
||||
if (paused && !cancelled) {
|
||||
synchronized(this) {
|
||||
while (paused && !cancelled) {
|
||||
try {
|
||||
this.wait(100);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user