improved progress bar

This commit is contained in:
rdavidek 2026-01-20 22:16:16 +01:00
parent 571cf2cb68
commit d809d9968c
5 changed files with 116 additions and 22 deletions

View File

@ -18,6 +18,8 @@ public class FileOperationQueue {
private volatile OperationStatus status = OperationStatus.QUEUED; private volatile OperationStatus status = OperationStatus.QUEUED;
private volatile long currentProgress = 0; private volatile long currentProgress = 0;
private volatile long totalProgress = 0; private volatile long totalProgress = 0;
private volatile long currentFileProgress = 0;
private volatile long totalFileProgress = 0;
private volatile String currentFile = ""; private volatile String currentFile = "";
private volatile String errorMessage = ""; private volatile String errorMessage = "";
@ -32,6 +34,8 @@ public class FileOperationQueue {
public OperationStatus getStatus() { return status; } public OperationStatus getStatus() { return status; }
public long getCurrentProgress() { return currentProgress; } public long getCurrentProgress() { return currentProgress; }
public long getTotalProgress() { return totalProgress; } public long getTotalProgress() { return totalProgress; }
public long getCurrentFileProgress() { return currentFileProgress; }
public long getTotalFileProgress() { return totalFileProgress; }
public String getCurrentFile() { return currentFile; } public String getCurrentFile() { return currentFile; }
public String getErrorMessage() { return errorMessage; } public String getErrorMessage() { return errorMessage; }
@ -116,6 +120,13 @@ public class FileOperationQueue {
notifyListeners(); notifyListeners();
} }
@Override
public void onFileProgress(long current, long total) {
task.currentFileProgress = current;
task.totalFileProgress = total;
notifyListeners();
}
@Override @Override
public boolean isCancelled() { public boolean isCancelled() {
return task.status == OperationStatus.CANCELLED; return task.status == OperationStatus.CANCELLED;

View File

@ -118,6 +118,8 @@ public class FileOperations {
} }
private static void copyFileWithProgress(Path source, Path target, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { 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); try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(target)) { OutputStream out = Files.newOutputStream(target)) {
byte[] buffer = new byte[24576]; byte[] buffer = new byte[24576];
@ -125,11 +127,16 @@ public class FileOperations {
while ((length = in.read(buffer)) > 0) { while ((length = in.read(buffer)) > 0) {
if (callback != null && callback.isCancelled()) return; if (callback != null && callback.isCancelled()) return;
out.write(buffer, 0, length); out.write(buffer, 0, length);
bytesCopied += length;
if (callback != null) {
callback.onFileProgress(bytesCopied, fileSize);
}
} }
} }
currentItem[0]++; currentItem[0]++;
if (callback != null) { if (callback != null) {
callback.onProgress(currentItem[0], totalItems, source.getFileName().toString()); callback.onProgress(currentItem[0], totalItems, source.getFileName().toString());
callback.onFileProgress(fileSize, fileSize);
} }
Files.setLastModifiedTime(target, Files.getLastModifiedTime(source)); Files.setLastModifiedTime(target, Files.getLastModifiedTime(source));
} }
@ -751,10 +758,19 @@ public class FileOperations {
try (FileInputStream fis = new FileInputStream(fileToZip)) { try (FileInputStream fis = new FileInputStream(fileToZip)) {
ZipEntry zipEntry = new ZipEntry(fileName); ZipEntry zipEntry = new ZipEntry(fileName);
zos.putNextEntry(zipEntry); zos.putNextEntry(zipEntry);
long fileSize = fileToZip.length();
long bytesCopied = 0;
byte[] bytes = new byte[24576]; byte[] bytes = new byte[24576];
int length; int length;
while ((length = fis.read(bytes)) >= 0) { while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length); zos.write(bytes, 0, length);
bytesCopied += length;
if (callback != null) {
callback.onFileProgress(bytesCopied, fileSize);
}
}
if (callback != null) {
callback.onFileProgress(fileSize, fileSize);
} }
zos.closeEntry(); zos.closeEntry();
} }
@ -802,13 +818,22 @@ public class FileOperations {
} }
// write file content // write file content
long fileSize = entry.getSize();
long bytesCopied = 0;
try (FileOutputStream fos = new FileOutputStream(newFile)) { try (FileOutputStream fos = new FileOutputStream(newFile)) {
byte[] buffer = new byte[24576]; byte[] buffer = new byte[24576];
int len; int len;
while ((len = zis.read(buffer)) > 0) { while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len); 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(); zis.closeEntry();
} }
@ -830,6 +855,7 @@ public class FileOperations {
public interface ProgressCallback { public interface ProgressCallback {
void onProgress(long current, long total, String currentFile); void onProgress(long current, long total, String currentFile);
default void onFileProgress(long current, long total) {}
default boolean isCancelled() { return false; } default boolean isCancelled() { return false; }
default OverwriteResponse confirmOverwrite(File file) { return OverwriteResponse.YES; } default OverwriteResponse confirmOverwrite(File file) { return OverwriteResponse.YES; }
default ErrorResponse onError(File file, Exception e) { return ErrorResponse.ABORT; } default ErrorResponse onError(File file, Exception e) { return ErrorResponse.ABORT; }

View File

@ -1898,6 +1898,11 @@ public class FilePanelTab extends JPanel {
progressDialog.updateProgress(current, total, currentFile); progressDialog.updateProgress(current, total, currentFile);
} }
@Override
public void onFileProgress(long current, long total) {
progressDialog.updateFileProgress(current, total);
}
@Override @Override
public boolean isCancelled() { public boolean isCancelled() {
return progressDialog.isCancelled(); return progressDialog.isCancelled();

View File

@ -2139,7 +2139,6 @@ public class MainWindow extends JFrame {
*/ */
private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, boolean modal, Runnable postTask, FilePanel... panelsToRefresh) { private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, boolean modal, Runnable postTask, FilePanel... panelsToRefresh) {
ProgressDialog progressDialog = new ProgressDialog(this, "File Operation", modal); ProgressDialog progressDialog = new ProgressDialog(this, "File Operation", modal);
progressDialog.setDisplayAsBytes(showBytes);
FileOperations.ProgressCallback callback = new FileOperations.ProgressCallback() { FileOperations.ProgressCallback callback = new FileOperations.ProgressCallback() {
@Override @Override
@ -2147,6 +2146,11 @@ public class MainWindow extends JFrame {
progressDialog.updateProgress(current, total, currentFile); progressDialog.updateProgress(current, total, currentFile);
} }
@Override
public void onFileProgress(long current, long total) {
progressDialog.updateFileProgress(current, total);
}
@Override @Override
public boolean isCancelled() { public boolean isCancelled() {
return progressDialog.isCancelled(); return progressDialog.isCancelled();

View File

@ -4,9 +4,11 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.util.LinkedList;
public class ProgressDialog extends JDialog { public class ProgressDialog extends JDialog {
private final JProgressBar progressBar; private final JProgressBar progressBar;
private final JProgressBar fileProgressBar;
private final JLabel statusLabel; private final JLabel statusLabel;
private final JLabel speedLabel; private final JLabel speedLabel;
private final JButton pauseButton; private final JButton pauseButton;
@ -15,6 +17,12 @@ public class ProgressDialog extends JDialog {
private volatile boolean cancelled = false; private volatile boolean cancelled = false;
private volatile boolean paused = false; private volatile boolean paused = false;
private long startTime = -1; private long startTime = -1;
private long totalBytesProcessed = 0;
private long lastFileBytes = 0;
private final LinkedList<long[]> 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) { public ProgressDialog(Frame owner, String title) {
this(owner, title, true); this(owner, title, true);
@ -28,15 +36,23 @@ public class ProgressDialog extends JDialog {
JPanel infoPanel = new JPanel(new GridLayout(2, 1, 5, 5)); JPanel infoPanel = new JPanel(new GridLayout(2, 1, 5, 5));
statusLabel = new JLabel("Starting..."); statusLabel = new JLabel("Starting...");
speedLabel = new JLabel("Speed: 0 B/s"); speedLabel = new JLabel(" ");
infoPanel.add(statusLabel); infoPanel.add(statusLabel);
infoPanel.add(speedLabel); infoPanel.add(speedLabel);
add(infoPanel, BorderLayout.NORTH); add(infoPanel, BorderLayout.NORTH);
JPanel progressPanel = new JPanel(new GridLayout(2, 1, 5, 5));
progressBar = new JProgressBar(0, 100); progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true); progressBar.setStringPainted(true);
progressBar.setPreferredSize(new Dimension(400, 25)); 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)); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
pauseButton = new JButton("Pause"); pauseButton = new JButton("Pause");
@ -83,37 +99,20 @@ public class ProgressDialog extends JDialog {
cancelled = true; cancelled = true;
} }
private boolean displayAsBytes = false;
public void setDisplayAsBytes(boolean displayAsBytes) {
this.displayAsBytes = displayAsBytes;
}
public void updateProgress(long current, long total, String fileName) { public void updateProgress(long current, long total, String fileName) {
if (startTime == -1) { if (startTime == -1) {
startTime = System.currentTimeMillis(); startTime = System.currentTimeMillis();
} }
lastFileBytes = 0;
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
if (total > 0) { if (total > 0) {
progressBar.setIndeterminate(false); progressBar.setIndeterminate(false);
int percent = (int) Math.min(100, ((double) current / total * 100)); int percent = (int) Math.min(100, ((double) current / total * 100));
progressBar.setValue(percent); progressBar.setValue(percent);
if (displayAsBytes) { progressBar.setString("Total: " + current + " / " + total + " (" + percent + "%)");
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("");
}
} else { } else {
progressBar.setIndeterminate(true); progressBar.setIndeterminate(true);
speedLabel.setText("");
} }
statusLabel.setText(fileName); statusLabel.setText(fileName);
}); });
@ -121,6 +120,55 @@ public class ProgressDialog extends JDialog {
checkState(); 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) { private String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B"; if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024)); int exp = (int) (Math.log(bytes) / Math.log(1024));