View Javadoc
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                 // Wait for all tasks to complete
192                 for (Future<?> future : futures) {
193                     try {
194                         future.get();
195                     } catch (InterruptedException e) {
196                         logger.error("Remove process interrupted", e);
197                         currentThread().interrupt(); // Restore interrupted status
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 }