diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java b/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java index 2677a48..c98b700 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java @@ -18,6 +18,8 @@ public class FileOperationQueue { private volatile OperationStatus status = OperationStatus.QUEUED; private volatile long currentProgress = 0; private volatile long totalProgress = 0; + private volatile long currentFileProgress = 0; + private volatile long totalFileProgress = 0; private volatile String currentFile = ""; private volatile String errorMessage = ""; @@ -32,6 +34,8 @@ public class FileOperationQueue { public OperationStatus getStatus() { return status; } public long getCurrentProgress() { return currentProgress; } public long getTotalProgress() { return totalProgress; } + public long getCurrentFileProgress() { return currentFileProgress; } + public long getTotalFileProgress() { return totalFileProgress; } public String getCurrentFile() { return currentFile; } public String getErrorMessage() { return errorMessage; } @@ -116,6 +120,13 @@ public class FileOperationQueue { notifyListeners(); } + @Override + public void onFileProgress(long current, long total) { + task.currentFileProgress = current; + task.totalFileProgress = total; + notifyListeners(); + } + @Override public boolean isCancelled() { return task.status == OperationStatus.CANCELLED; diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java index 8973fb9..5f9d29d 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java @@ -118,6 +118,8 @@ public class FileOperations { } private static void copyFileWithProgress(Path source, Path target, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { + long fileSize = Files.size(source); + long bytesCopied = 0; try (InputStream in = Files.newInputStream(source); OutputStream out = Files.newOutputStream(target)) { byte[] buffer = new byte[24576]; @@ -125,11 +127,16 @@ public class FileOperations { while ((length = in.read(buffer)) > 0) { if (callback != null && callback.isCancelled()) return; out.write(buffer, 0, length); + bytesCopied += length; + if (callback != null) { + callback.onFileProgress(bytesCopied, fileSize); + } } } currentItem[0]++; if (callback != null) { callback.onProgress(currentItem[0], totalItems, source.getFileName().toString()); + callback.onFileProgress(fileSize, fileSize); } Files.setLastModifiedTime(target, Files.getLastModifiedTime(source)); } @@ -751,10 +758,19 @@ public class FileOperations { try (FileInputStream fis = new FileInputStream(fileToZip)) { ZipEntry zipEntry = new ZipEntry(fileName); zos.putNextEntry(zipEntry); + long fileSize = fileToZip.length(); + long bytesCopied = 0; byte[] bytes = new byte[24576]; int length; while ((length = fis.read(bytes)) >= 0) { zos.write(bytes, 0, length); + bytesCopied += length; + if (callback != null) { + callback.onFileProgress(bytesCopied, fileSize); + } + } + if (callback != null) { + callback.onFileProgress(fileSize, fileSize); } zos.closeEntry(); } @@ -802,13 +818,22 @@ public class FileOperations { } // write file content + long fileSize = entry.getSize(); + long bytesCopied = 0; try (FileOutputStream fos = new FileOutputStream(newFile)) { byte[] buffer = new byte[24576]; int len; while ((len = zis.read(buffer)) > 0) { fos.write(buffer, 0, len); + bytesCopied += len; + if (callback != null && fileSize > 0) { + callback.onFileProgress(bytesCopied, fileSize); + } } } + if (callback != null && fileSize > 0) { + callback.onFileProgress(fileSize, fileSize); + } } zis.closeEntry(); } @@ -830,6 +855,7 @@ public class FileOperations { public interface ProgressCallback { void onProgress(long current, long total, String currentFile); + default void onFileProgress(long current, long total) {} default boolean isCancelled() { return false; } default OverwriteResponse confirmOverwrite(File file) { return OverwriteResponse.YES; } default ErrorResponse onError(File file, Exception e) { return ErrorResponse.ABORT; } diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index f76ffea..0b22687 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -1898,6 +1898,11 @@ public class FilePanelTab extends JPanel { progressDialog.updateProgress(current, total, currentFile); } + @Override + public void onFileProgress(long current, long total) { + progressDialog.updateFileProgress(current, total); + } + @Override public boolean isCancelled() { return progressDialog.isCancelled(); diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index 73ee592..0de3df9 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -2139,7 +2139,6 @@ public class MainWindow extends JFrame { */ 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() { @Override @@ -2147,6 +2146,11 @@ public class MainWindow extends JFrame { progressDialog.updateProgress(current, total, currentFile); } + @Override + public void onFileProgress(long current, long total) { + progressDialog.updateFileProgress(current, total); + } + @Override public boolean isCancelled() { return progressDialog.isCancelled(); diff --git a/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java b/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java index 9d6535a..579e3c0 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java +++ b/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java @@ -4,9 +4,11 @@ import javax.swing.*; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.util.LinkedList; public class ProgressDialog extends JDialog { private final JProgressBar progressBar; + private final JProgressBar fileProgressBar; private final JLabel statusLabel; private final JLabel speedLabel; private final JButton pauseButton; @@ -15,6 +17,12 @@ public class ProgressDialog extends JDialog { private volatile boolean cancelled = false; private volatile boolean paused = false; private long startTime = -1; + private long totalBytesProcessed = 0; + private long lastFileBytes = 0; + private final LinkedList speedSamples = new LinkedList<>(); + private static final int MAX_SAMPLES = 10; + private static final long SAMPLE_INTERVAL_MS = 200; + private long lastSampleTime = -1; public ProgressDialog(Frame owner, String title) { this(owner, title, true); @@ -28,15 +36,23 @@ public class ProgressDialog extends JDialog { JPanel infoPanel = new JPanel(new GridLayout(2, 1, 5, 5)); statusLabel = new JLabel("Starting..."); - speedLabel = new JLabel("Speed: 0 B/s"); + speedLabel = new JLabel(" "); infoPanel.add(statusLabel); infoPanel.add(speedLabel); add(infoPanel, BorderLayout.NORTH); + JPanel progressPanel = new JPanel(new GridLayout(2, 1, 5, 5)); progressBar = new JProgressBar(0, 100); progressBar.setStringPainted(true); progressBar.setPreferredSize(new Dimension(400, 25)); - add(progressBar, BorderLayout.CENTER); + progressPanel.add(progressBar); + + fileProgressBar = new JProgressBar(0, 100); + fileProgressBar.setStringPainted(true); + fileProgressBar.setPreferredSize(new Dimension(400, 25)); + progressPanel.add(fileProgressBar); + + add(progressPanel, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); pauseButton = new JButton("Pause"); @@ -83,37 +99,20 @@ public class ProgressDialog extends JDialog { cancelled = true; } - 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(); } + lastFileBytes = 0; SwingUtilities.invokeLater(() -> { if (total > 0) { progressBar.setIndeterminate(false); int percent = (int) Math.min(100, ((double) current / total * 100)); progressBar.setValue(percent); - if (displayAsBytes) { - progressBar.setString(formatSize(Math.min(current, total)) + " / " + 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(""); - } + progressBar.setString("Total: " + current + " / " + total + " (" + percent + "%)"); } else { progressBar.setIndeterminate(true); - speedLabel.setText(""); } statusLabel.setText(fileName); }); @@ -121,6 +120,55 @@ public class ProgressDialog extends JDialog { checkState(); } + public void updateFileProgress(long current, long total) { + long currentTime = System.currentTimeMillis(); + if (startTime == -1) { + startTime = currentTime; + lastSampleTime = startTime; + speedSamples.add(new long[]{startTime, 0}); + } + + long delta = current - lastFileBytes; + if (delta > 0) { + totalBytesProcessed += delta; + } + lastFileBytes = current; + if (current >= total) { + lastFileBytes = 0; + } + + if (currentTime - lastSampleTime >= SAMPLE_INTERVAL_MS) { + speedSamples.add(new long[]{currentTime, totalBytesProcessed}); + while (speedSamples.size() > MAX_SAMPLES) { + speedSamples.removeFirst(); + } + lastSampleTime = currentTime; + } + + SwingUtilities.invokeLater(() -> { + if (total > 0) { + fileProgressBar.setIndeterminate(false); + int percent = (int) Math.min(100, ((double) current / total * 100)); + fileProgressBar.setValue(percent); + fileProgressBar.setString("File: " + formatSize(Math.min(current, total)) + " / " + formatSize(total) + " (" + percent + "%)"); + + if (speedSamples.size() >= 2) { + long[] first = speedSamples.getFirst(); + long[] last = speedSamples.getLast(); + long timeDiff = last[0] - first[0]; + long byteDiff = last[1] - first[1]; + if (timeDiff > 0) { + long bytesPerSec = (long) (byteDiff / (timeDiff / 1000.0)); + speedLabel.setText("Speed: " + formatSize(bytesPerSec) + "/s"); + } + } + } else { + fileProgressBar.setIndeterminate(true); + fileProgressBar.setString(""); + } + }); + } + private String formatSize(long bytes) { if (bytes < 1024) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(1024));