PdfPasswordRemoverPanel.java
package edu.jiangxin.apktoolbox.pdf.passwordremover;
import edu.jiangxin.apktoolbox.pdf.PdfUtils;
import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
import edu.jiangxin.apktoolbox.swing.extend.FileListPanel;
import edu.jiangxin.apktoolbox.swing.extend.filepanel.FilePanel;
import edu.jiangxin.apktoolbox.utils.Constants;
import edu.jiangxin.apktoolbox.utils.FileUtils;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
public class PdfPasswordRemoverPanel extends EasyPanel {
private FileListPanel fileListPanel;
private JCheckBox isRecursiveSearched;
private FilePanel targetDirPanel;
private JButton startButton;
private JButton cancelButton;
private JProgressBar progressBar;
private WorkderThread searchThread;
final private List<File> scannedFileList = new ArrayList<>();
@Override
public void initUI() {
BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
setLayout(boxLayout);
createInputPanel();
add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
createOutputPanel();
add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
createOptionPanel();
add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
createOperationPanel();
add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
createProcessBarPanel();
}
private void createInputPanel() {
fileListPanel = new FileListPanel();
add(fileListPanel);
}
private void createOutputPanel() {
targetDirPanel = new FilePanel("Target Directory");
targetDirPanel.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
add(targetDirPanel);
}
private void createOptionPanel() {
JPanel optionPanel = new JPanel();
optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.X_AXIS));
optionPanel.setBorder(BorderFactory.createTitledBorder("Options"));
isRecursiveSearched = new JCheckBox("Recursive Search");
isRecursiveSearched.setSelected(true);
optionPanel.add(isRecursiveSearched);
optionPanel.add(Box.createHorizontalGlue());
add(optionPanel);
}
private void createOperationPanel() {
JPanel operationPanel = new JPanel();
operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
startButton = new JButton("Start");
cancelButton = new JButton("Cancel");
cancelButton.setEnabled(false);
startButton.addActionListener(new OperationButtonActionListener());
cancelButton.addActionListener(new OperationButtonActionListener());
operationPanel.add(startButton);
operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
operationPanel.add(cancelButton);
operationPanel.add(Box.createHorizontalGlue());
add(operationPanel);
}
private void createProcessBarPanel() {
progressBar = new JProgressBar();
progressBar.setStringPainted(true);
progressBar.setString("Ready");
add(progressBar);
}
private void processFile(File encryptedFile, File targetDir) {
try {
if (Thread.currentThread().isInterrupted()) {
return;
}
PdfUtils.removePasswordWithIText(encryptedFile, targetDir);
} catch (Throwable t) {
logger.error("Process file failed: {}", encryptedFile.getAbsolutePath(), t);
}
}
class OperationButtonActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source.equals(startButton)) {
startButton.setEnabled(false);
cancelButton.setEnabled(true);
searchThread = new WorkderThread(isRecursiveSearched.isSelected());
searchThread.start();
} else if (source.equals(cancelButton)) {
startButton.setEnabled(true);
cancelButton.setEnabled(false);
if (searchThread.isAlive()) {
searchThread.interrupt();
searchThread.executorService.shutdownNow();
}
}
}
}
class WorkderThread extends Thread {
public final ExecutorService executorService;
private final AtomicInteger processedFiles = new AtomicInteger(0);
private int totalFiles = 0;
private final boolean isRecursiveSearched;
public WorkderThread(boolean isRecursiveSearched) {
super();
this.isRecursiveSearched = isRecursiveSearched;
this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
SwingUtilities.invokeLater(() -> {
progressBar.setValue(0);
progressBar.setString("Starting remove process...");
});
}
@Override
public void run() {
try {
List<File> fileList = fileListPanel.getFileList();
Set<File> fileSet = new TreeSet<>();
String[] extensions = new String[]{"pdf", "PDF"};
for (File file : fileList) {
fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
}
List<Future<?>> futures = new ArrayList<>();
totalFiles = fileSet.size();
updateProgress();
for (File file : fileSet) {
if (Thread.currentThread().isInterrupted()) {
return;
}
futures.add(executorService.submit(() -> {
if (Thread.currentThread().isInterrupted()) {
return null;
}
processFile(file, targetDirPanel.getFile());
incrementProcessedFiles();
return null;
}));
}
// Wait for all tasks to complete
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException e) {
logger.error("Remove process interrupted", e);
Thread.currentThread().interrupt(); // Restore interrupted status
return;
}
}
} catch (Exception e) {
logger.error("Remove process failed", e);
SwingUtilities.invokeLater(() -> progressBar.setString("Remove process failed"));
} finally {
executorService.shutdown();
SwingUtilities.invokeLater(() -> {
startButton.setEnabled(true);
cancelButton.setEnabled(false);
});
}
}
private void incrementProcessedFiles() {
processedFiles.incrementAndGet();
updateProgress();
}
private void updateProgress() {
if (totalFiles > 0) {
SwingUtilities.invokeLater(() -> {
int processed = processedFiles.get();
int percentage = (int) ((processed * 100.0) / totalFiles);
progressBar.setValue(percentage);
progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
});
}
}
}
}