mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-06 09:12:02 +00:00

# Description of Changes Introduced a new `common` module for shared libs and commonly used classes. See the screenshot below for the file structure and classes that have been moved. --- <img width="452" alt="Screenshot 2025-05-22 at 11 46 56" src="https://github.com/user-attachments/assets/c9badabc-48f9-4079-b83e-7cfde0fb840f" /> <img width="470" alt="Screenshot 2025-05-22 at 11 47 30" src="https://github.com/user-attachments/assets/e8315b09-2e78-4c50-b9de-4dd9b9b0ecb1" /> ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [x] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
352 lines
19 KiB
Java
352 lines
19 KiB
Java
package stirling.software.SPDF.UI.impl;
|
|
|
|
import java.awt.*;
|
|
import java.io.BufferedReader;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.swing.*;
|
|
|
|
import io.github.pixee.security.BoundedLineReader;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
import stirling.software.common.util.UIScaling;
|
|
|
|
@Slf4j
|
|
public class LoadingWindow extends JDialog {
|
|
private final JProgressBar progressBar;
|
|
private final JLabel statusLabel;
|
|
private final JPanel mainPanel;
|
|
private final JLabel brandLabel;
|
|
private long startTime;
|
|
|
|
private Timer stuckTimer;
|
|
private long stuckThreshold = 4000;
|
|
private long timeAt90Percent = -1;
|
|
private volatile Process explorerProcess;
|
|
private static final boolean IS_WINDOWS =
|
|
System.getProperty("os.name").toLowerCase().contains("win");
|
|
|
|
public LoadingWindow(Frame parent, String initialUrl) {
|
|
super(parent, "Initializing Stirling-PDF", true);
|
|
startTime = System.currentTimeMillis();
|
|
log.info("Creating LoadingWindow - initialization started at: {}", startTime);
|
|
|
|
// Initialize components
|
|
mainPanel = new JPanel();
|
|
mainPanel.setBackground(Color.WHITE);
|
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));
|
|
mainPanel.setLayout(new GridBagLayout());
|
|
GridBagConstraints gbc = new GridBagConstraints();
|
|
|
|
// Configure GridBagConstraints
|
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
|
gbc.insets = new Insets(5, 5, 5, 5);
|
|
gbc.weightx = 1.0;
|
|
gbc.weighty = 0.0;
|
|
|
|
// Add icon
|
|
try {
|
|
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
|
|
if (is != null) {
|
|
Image img = ImageIO.read(is);
|
|
if (img != null) {
|
|
Image scaledImg = UIScaling.scaleIcon(img, 48, 48);
|
|
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
|
gbc.gridy = 0;
|
|
mainPanel.add(iconLabel, gbc);
|
|
log.info("Icon loaded and scaled successfully");
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("Failed to load icon", e);
|
|
}
|
|
|
|
// URL Label with explicit size
|
|
brandLabel = new JLabel(initialUrl);
|
|
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
|
brandLabel.setPreferredSize(new Dimension(300, 25));
|
|
brandLabel.setText("Stirling-PDF");
|
|
gbc.gridy = 1;
|
|
mainPanel.add(brandLabel, gbc);
|
|
|
|
// Status label with explicit size
|
|
statusLabel = new JLabel("Initializing...");
|
|
statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
|
statusLabel.setPreferredSize(new Dimension(300, 25));
|
|
gbc.gridy = 2;
|
|
mainPanel.add(statusLabel, gbc);
|
|
|
|
// Progress bar with explicit size
|
|
progressBar = new JProgressBar(0, 100);
|
|
progressBar.setStringPainted(true);
|
|
progressBar.setPreferredSize(new Dimension(300, 25));
|
|
gbc.gridy = 3;
|
|
mainPanel.add(progressBar, gbc);
|
|
|
|
// Set dialog properties
|
|
setContentPane(mainPanel);
|
|
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
|
setResizable(false);
|
|
setUndecorated(false);
|
|
|
|
// Set size and position
|
|
setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200));
|
|
|
|
setLocationRelativeTo(parent);
|
|
setAlwaysOnTop(true);
|
|
setProgress(0);
|
|
setStatus("Starting...");
|
|
|
|
log.info(
|
|
"LoadingWindow initialization completed in {}ms",
|
|
System.currentTimeMillis() - startTime);
|
|
}
|
|
|
|
private void checkAndRefreshExplorer() {
|
|
if (!IS_WINDOWS) {
|
|
return;
|
|
}
|
|
if (timeAt90Percent == -1) {
|
|
timeAt90Percent = System.currentTimeMillis();
|
|
stuckTimer =
|
|
new Timer(
|
|
1000,
|
|
e -> {
|
|
long currentTime = System.currentTimeMillis();
|
|
if (currentTime - timeAt90Percent > stuckThreshold) {
|
|
try {
|
|
log.debug(
|
|
"Attempting Windows explorer refresh due to 90% stuck state");
|
|
String currentDir = System.getProperty("user.dir");
|
|
|
|
// Store current explorer PIDs before we start new one
|
|
Set<String> existingPids = new HashSet<>();
|
|
ProcessBuilder listExplorer =
|
|
new ProcessBuilder(
|
|
"cmd",
|
|
"/c",
|
|
"wmic",
|
|
"process",
|
|
"where",
|
|
"name='explorer.exe'",
|
|
"get",
|
|
"ProcessId",
|
|
"/format:csv");
|
|
Process process = listExplorer.start();
|
|
BufferedReader reader =
|
|
new BufferedReader(
|
|
new InputStreamReader(
|
|
process.getInputStream()));
|
|
String line;
|
|
while ((line =
|
|
BoundedLineReader.readLine(
|
|
reader, 5_000_000))
|
|
!= null) {
|
|
if (line.matches(".*\\d+.*")) { // Contains numbers
|
|
String[] parts = line.trim().split(",");
|
|
if (parts.length >= 2) {
|
|
existingPids.add(
|
|
parts[parts.length - 1].trim());
|
|
}
|
|
}
|
|
}
|
|
process.waitFor(2, TimeUnit.SECONDS);
|
|
|
|
// Start new explorer
|
|
ProcessBuilder pb =
|
|
new ProcessBuilder(
|
|
"cmd",
|
|
"/c",
|
|
"start",
|
|
"/min",
|
|
"/b",
|
|
"explorer.exe",
|
|
currentDir);
|
|
pb.redirectErrorStream(true);
|
|
explorerProcess = pb.start();
|
|
|
|
// Schedule cleanup
|
|
Timer cleanupTimer =
|
|
new Timer(
|
|
2000,
|
|
cleanup -> {
|
|
try {
|
|
// Find new explorer processes
|
|
ProcessBuilder findNewExplorer =
|
|
new ProcessBuilder(
|
|
"cmd",
|
|
"/c",
|
|
"wmic",
|
|
"process",
|
|
"where",
|
|
"name='explorer.exe'",
|
|
"get",
|
|
"ProcessId",
|
|
"/format:csv");
|
|
Process newProcess =
|
|
findNewExplorer.start();
|
|
BufferedReader newReader =
|
|
new BufferedReader(
|
|
new InputStreamReader(
|
|
newProcess
|
|
.getInputStream()));
|
|
String newLine;
|
|
while ((newLine =
|
|
BoundedLineReader
|
|
.readLine(
|
|
newReader,
|
|
5_000_000))
|
|
!= null) {
|
|
if (newLine.matches(
|
|
".*\\d+.*")) {
|
|
String[] parts =
|
|
newLine.trim()
|
|
.split(",");
|
|
if (parts.length >= 2) {
|
|
String pid =
|
|
parts[
|
|
parts.length
|
|
- 1]
|
|
.trim();
|
|
if (!existingPids
|
|
.contains(
|
|
pid)) {
|
|
log.debug(
|
|
"Found new explorer.exe with PID: "
|
|
+ pid);
|
|
ProcessBuilder
|
|
killProcess =
|
|
new ProcessBuilder(
|
|
"taskkill",
|
|
"/PID",
|
|
pid,
|
|
"/F");
|
|
killProcess
|
|
.redirectErrorStream(
|
|
true);
|
|
Process killResult =
|
|
killProcess
|
|
.start();
|
|
killResult.waitFor(
|
|
2,
|
|
TimeUnit
|
|
.SECONDS);
|
|
log.debug(
|
|
"Explorer process terminated: "
|
|
+ pid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
newProcess.waitFor(
|
|
2, TimeUnit.SECONDS);
|
|
} catch (Exception ex) {
|
|
log.error(
|
|
"Error cleaning up Windows explorer process",
|
|
ex);
|
|
}
|
|
});
|
|
cleanupTimer.setRepeats(false);
|
|
cleanupTimer.start();
|
|
stuckTimer.stop();
|
|
} catch (Exception ex) {
|
|
log.error("Error refreshing Windows explorer", ex);
|
|
}
|
|
}
|
|
});
|
|
stuckTimer.setRepeats(true);
|
|
stuckTimer.start();
|
|
}
|
|
}
|
|
|
|
public void setProgress(final int progress) {
|
|
SwingUtilities.invokeLater(
|
|
() -> {
|
|
try {
|
|
int validProgress = Math.min(Math.max(progress, 0), 100);
|
|
log.info(
|
|
"Setting progress to {}% at {}ms since start",
|
|
validProgress, System.currentTimeMillis() - startTime);
|
|
|
|
// Log additional details when near 90%
|
|
if (validProgress >= 85 && validProgress <= 95) {
|
|
log.info(
|
|
"Near 90% progress - Current status: {}, Window visible: {}, "
|
|
+ "Progress bar responding: {}, Memory usage: {}MB",
|
|
statusLabel.getText(),
|
|
isVisible(),
|
|
progressBar.isEnabled(),
|
|
Runtime.getRuntime().totalMemory() / (1024 * 1024));
|
|
|
|
// Add thread state logging
|
|
Thread currentThread = Thread.currentThread();
|
|
log.info(
|
|
"Current thread state - Name: {}, State: {}, Priority: {}",
|
|
currentThread.getName(),
|
|
currentThread.getState(),
|
|
currentThread.getPriority());
|
|
|
|
if (validProgress >= 90 && validProgress < 95) {
|
|
checkAndRefreshExplorer();
|
|
} else {
|
|
// Reset the timer if we move past 95%
|
|
if (validProgress >= 95) {
|
|
if (stuckTimer != null) {
|
|
stuckTimer.stop();
|
|
}
|
|
timeAt90Percent = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
progressBar.setValue(validProgress);
|
|
progressBar.setString(validProgress + "%");
|
|
mainPanel.revalidate();
|
|
mainPanel.repaint();
|
|
} catch (Exception e) {
|
|
log.error("Error updating progress to " + progress, e);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void setStatus(final String status) {
|
|
log.info(
|
|
"Status update at {}ms - Setting status to: {}",
|
|
System.currentTimeMillis() - startTime,
|
|
status);
|
|
|
|
SwingUtilities.invokeLater(
|
|
() -> {
|
|
try {
|
|
String validStatus = status != null ? status : "";
|
|
statusLabel.setText(validStatus);
|
|
|
|
// Log UI state when status changes
|
|
log.info(
|
|
"UI State - Window visible: {}, Progress: {}%, Status: {}",
|
|
isVisible(), progressBar.getValue(), validStatus);
|
|
|
|
mainPanel.revalidate();
|
|
mainPanel.repaint();
|
|
} catch (Exception e) {
|
|
log.error("Error updating status to: " + status, e);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void dispose() {
|
|
log.info("LoadingWindow disposing after {}ms", System.currentTimeMillis() - startTime);
|
|
super.dispose();
|
|
}
|
|
}
|