1 package edu.jiangxin.apktoolbox.pdf.passwordremover;
2
3 import edu.jiangxin.apktoolbox.pdf.PdfUtils;
4 import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
5 import edu.jiangxin.apktoolbox.swing.extend.FileListPanel;
6 import edu.jiangxin.apktoolbox.swing.extend.filepanel.FilePanel;
7 import edu.jiangxin.apktoolbox.utils.Constants;
8 import edu.jiangxin.apktoolbox.utils.FileUtils;
9
10 import javax.swing.*;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.io.File;
14 import java.io.Serial;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Set;
18 import java.util.TreeSet;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.Future;
22 import java.util.concurrent.atomic.AtomicInteger;
23
24 public class PdfPasswordRemoverPanel extends EasyPanel {
25 @Serial
26 private static final long serialVersionUID = 1L;
27
28 private FileListPanel fileListPanel;
29
30 private JCheckBox isRecursiveSearched;
31
32 private FilePanel targetDirPanel;
33
34 private JButton startButton;
35 private JButton cancelButton;
36
37 private JProgressBar progressBar;
38
39 private transient WorkderThread searchThread;
40
41 @Override
42 public void initUI() {
43 BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
44 setLayout(boxLayout);
45
46 createInputPanel();
47 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
48
49 createOutputPanel();
50 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
51
52 createOptionPanel();
53 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
54
55 createOperationPanel();
56 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
57
58 createProcessBarPanel();
59 }
60
61 private void createInputPanel() {
62 fileListPanel = new FileListPanel();
63 fileListPanel.initialize();
64 add(fileListPanel);
65 }
66
67 private void createOutputPanel() {
68 targetDirPanel = new FilePanel("Target Directory");
69 targetDirPanel.initialize();
70 targetDirPanel.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
71 add(targetDirPanel);
72 }
73
74 private void createOptionPanel() {
75 JPanel optionPanel = new JPanel();
76 optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.X_AXIS));
77 optionPanel.setBorder(BorderFactory.createTitledBorder("Options"));
78
79 isRecursiveSearched = new JCheckBox("Recursive Search");
80 isRecursiveSearched.setSelected(true);
81 optionPanel.add(isRecursiveSearched);
82 optionPanel.add(Box.createHorizontalGlue());
83
84 add(optionPanel);
85 }
86
87 private void createOperationPanel() {
88 JPanel operationPanel = new JPanel();
89 operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
90 operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
91
92 startButton = new JButton("Start");
93 cancelButton = new JButton("Cancel");
94 cancelButton.setEnabled(false);
95 startButton.addActionListener(new OperationButtonActionListener());
96 cancelButton.addActionListener(new OperationButtonActionListener());
97 operationPanel.add(startButton);
98 operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
99 operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
100 operationPanel.add(cancelButton);
101 operationPanel.add(Box.createHorizontalGlue());
102
103 add(operationPanel);
104 }
105
106 private void createProcessBarPanel() {
107 progressBar = new JProgressBar();
108 progressBar.setStringPainted(true);
109 progressBar.setString("Ready");
110
111 add(progressBar);
112 }
113
114 private void processFile(File encryptedFile, File targetDir) {
115 try {
116 if (Thread.currentThread().isInterrupted()) {
117 return;
118 }
119 PdfUtils.removePasswordWithIText(encryptedFile, targetDir);
120 } catch (Throwable t) {
121 logger.error("Process file failed: {}", encryptedFile.getAbsolutePath(), t);
122 }
123 }
124
125 class OperationButtonActionListener implements ActionListener {
126 @Override
127 public void actionPerformed(ActionEvent e) {
128 Object source = e.getSource();
129 if (source.equals(startButton)) {
130 startButton.setEnabled(false);
131 cancelButton.setEnabled(true);
132 searchThread = new WorkderThread(isRecursiveSearched.isSelected());
133 searchThread.start();
134 } else if (source.equals(cancelButton)) {
135 startButton.setEnabled(true);
136 cancelButton.setEnabled(false);
137 if (searchThread.isAlive()) {
138 searchThread.interrupt();
139 searchThread.executorService.shutdownNow();
140 }
141 }
142
143 }
144 }
145
146 class WorkderThread extends Thread {
147 public final ExecutorService executorService;
148 private final AtomicInteger processedFiles = new AtomicInteger(0);
149 private int totalFiles = 0;
150 private final boolean isRecursiveSearched;
151
152 public WorkderThread(boolean isRecursiveSearched) {
153 super();
154 this.isRecursiveSearched = isRecursiveSearched;
155 this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
156
157 SwingUtilities.invokeLater(() -> {
158 progressBar.setValue(0);
159 progressBar.setString("Starting remove process...");
160 });
161 }
162
163 @Override
164 public void run() {
165 try {
166 List<File> fileList = fileListPanel.getFileList();
167 Set<File> fileSet = new TreeSet<>();
168 String[] extensions = new String[]{"pdf", "PDF"};
169 for (File file : fileList) {
170 fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
171 }
172
173 List<Future<?>> futures = new ArrayList<>();
174 totalFiles = fileSet.size();
175 updateProgress();
176
177 for (File file : fileSet) {
178 if (currentThread().isInterrupted()) {
179 return;
180 }
181 futures.add(executorService.submit(() -> {
182 if (currentThread().isInterrupted()) {
183 return null;
184 }
185 processFile(file, targetDirPanel.getFile());
186 incrementProcessedFiles();
187 return null;
188 }));
189 }
190
191
192 for (Future<?> future : futures) {
193 try {
194 future.get();
195 } catch (InterruptedException e) {
196 logger.error("Remove process interrupted", e);
197 currentThread().interrupt();
198 return;
199 }
200 }
201 } catch (Exception e) {
202 logger.error("Remove process failed", e);
203 SwingUtilities.invokeLater(() -> progressBar.setString("Remove process failed"));
204 } finally {
205 executorService.shutdown();
206 SwingUtilities.invokeLater(() -> {
207 startButton.setEnabled(true);
208 cancelButton.setEnabled(false);
209 });
210 }
211 }
212
213 private void incrementProcessedFiles() {
214 processedFiles.incrementAndGet();
215 updateProgress();
216 }
217
218 private void updateProgress() {
219 if (totalFiles > 0) {
220 SwingUtilities.invokeLater(() -> {
221 int processed = processedFiles.get();
222 int percentage = (int) (processed * 100.0 / totalFiles);
223 progressBar.setValue(percentage);
224 progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
225 });
226 }
227 }
228 }
229 }