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