1 package edu.jiangxin.apktoolbox.pdf.stat;
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.utils.Constants;
7 import edu.jiangxin.apktoolbox.utils.DateUtils;
8 import edu.jiangxin.apktoolbox.utils.FileUtils;
9
10 import javax.swing.*;
11 import javax.swing.table.DefaultTableModel;
12 import java.awt.*;
13 import java.awt.event.ActionEvent;
14 import java.awt.event.ActionListener;
15 import java.io.File;
16 import java.io.Serial;
17 import java.util.*;
18 import java.util.List;
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 PdfStatPanel extends EasyPanel {
25
26 @Serial
27 private static final long serialVersionUID = 1L;
28
29 private JTabbedPane tabbedPane;
30
31 private JPanel mainPanel;
32
33 private FileListPanel fileListPanel;
34
35 private JRadioButton pageCountRadioButton;
36
37 private JCheckBox isRecursiveSearched;
38
39 private JPanel resultPanel;
40
41 private JTable resultTable;
42
43 private DefaultTableModel resultTableModel;
44
45 private JLabel statInfoLabel;
46
47 private JButton statButton;
48 private JButton cancelButton;
49
50 private JProgressBar progressBar;
51
52 private transient SearchThread searchThread;
53
54 private long totalFileSize = 0;
55
56 private int totalPageCount = 0;
57
58 private transient final List<Vector<Object>> resultFileList = new ArrayList<>();
59
60
61 @Override
62 public void initUI() {
63 tabbedPane = new JTabbedPane();
64 add(tabbedPane);
65
66 createMainPanel();
67 tabbedPane.addTab("Option", null, mainPanel, "Show Stat Options");
68
69 createResultPanel();
70 tabbedPane.addTab("Result", null, resultPanel, "Show Stat Result");
71 }
72
73 private void createMainPanel() {
74 mainPanel = new JPanel();
75 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
76
77 fileListPanel = new FileListPanel();
78 fileListPanel.initialize();
79
80 JPanel checkOptionPanel = new JPanel();
81 checkOptionPanel.setLayout(new BoxLayout(checkOptionPanel, BoxLayout.X_AXIS));
82 checkOptionPanel.setBorder(BorderFactory.createTitledBorder("Check Options"));
83
84 ButtonGroup buttonGroup = new ButtonGroup();
85
86 pageCountRadioButton = new JRadioButton("统计页数");
87 pageCountRadioButton.setSelected(true);
88 pageCountRadioButton.setEnabled(false);
89 buttonGroup.add(pageCountRadioButton);
90
91 JPanel typePanel = new JPanel();
92 typePanel.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
93 typePanel.add(pageCountRadioButton);
94
95 checkOptionPanel.add(typePanel);
96 checkOptionPanel.add(Box.createHorizontalGlue());
97
98 JPanel searchOptionPanel = new JPanel();
99 searchOptionPanel.setLayout(new BoxLayout(searchOptionPanel, BoxLayout.X_AXIS));
100 searchOptionPanel.setBorder(BorderFactory.createTitledBorder("Stat Options"));
101
102 isRecursiveSearched = new JCheckBox("Recursive");
103 isRecursiveSearched.setSelected(true);
104 searchOptionPanel.add(isRecursiveSearched);
105 searchOptionPanel.add(Box.createHorizontalGlue());
106
107 JPanel operationPanel = new JPanel();
108 operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
109 operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
110
111 JPanel buttonPanel = new JPanel();
112 buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
113
114 statButton = new JButton("Stat");
115 cancelButton = new JButton("Cancel");
116 cancelButton.setEnabled(false);
117 statButton.addActionListener(new OperationButtonActionListener());
118 cancelButton.addActionListener(new OperationButtonActionListener());
119 operationPanel.add(statButton);
120 operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
121 operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
122 operationPanel.add(cancelButton);
123 operationPanel.add(Box.createHorizontalGlue());
124
125 progressBar = new JProgressBar();
126 progressBar.setStringPainted(true);
127 progressBar.setString("Ready");
128
129 mainPanel.add(fileListPanel);
130 mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
131 mainPanel.add(checkOptionPanel);
132 mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
133 mainPanel.add(searchOptionPanel);
134 mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
135 mainPanel.add(operationPanel);
136 mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
137 mainPanel.add(progressBar);
138 }
139
140 private void createResultPanel() {
141 resultPanel = new JPanel();
142 resultPanel.setLayout(new BoxLayout(resultPanel, BoxLayout.Y_AXIS));
143
144 resultTableModel = new PdfFilesTableModel(new Vector<>(), PdfFilesConstants.COLUMN_NAMES);
145 resultTable = new JTable(resultTableModel);
146 resultTable.setDefaultRenderer(Vector.class, new PdfFilesTableCellRenderer());
147 for (int i = 0; i < resultTable.getColumnCount(); i++) {
148 resultTable.getColumn(resultTable.getColumnName(i)).setCellRenderer(new PdfFilesTableCellRenderer());
149 }
150 resultTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
151 JScrollPane scrollPane = new JScrollPane(resultTable);
152
153 JPanel statInfoPanel = new JPanel();
154 statInfoPanel.setLayout(new BoxLayout(statInfoPanel, BoxLayout.X_AXIS));
155 statInfoLabel = new JLabel("");
156 statInfoPanel.add(statInfoLabel);
157 statInfoPanel.add(Box.createHorizontalGlue());
158
159 resultPanel.add(scrollPane);
160 resultPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
161 resultPanel.add(statInfoPanel);
162 }
163
164 private void processFile(File file) {
165 if (pageCountRadioButton.isSelected()) {
166 Vector<Object> fileVector = getRowVector(file);
167 resultFileList.add(fileVector);
168 } else {
169 logger.error("Invalid option selected");
170 }
171 }
172
173 class OperationButtonActionListener implements ActionListener {
174 @Override
175 public void actionPerformed(ActionEvent e) {
176 Object source = e.getSource();
177 if (source.equals(statButton)) {
178 statButton.setEnabled(false);
179 cancelButton.setEnabled(true);
180 searchThread = new SearchThread(isRecursiveSearched.isSelected());
181 searchThread.start();
182 } else if (source.equals(cancelButton)) {
183 statButton.setEnabled(true);
184 cancelButton.setEnabled(false);
185 if (searchThread.isAlive()) {
186 searchThread.interrupt();
187 searchThread.executorService.shutdownNow();
188 }
189 }
190
191 }
192 }
193
194 private void showResult() {
195 SwingUtilities.invokeLater(() -> {
196 int index = 0;
197 for (Vector<Object> file : resultFileList) {
198 file.add(0, ++index);
199 resultTableModel.addRow(file);
200 }
201 tabbedPane.setSelectedIndex(1);
202 statInfoLabel.setText("Page Count: " + totalPageCount + ", Total Size: " + FileUtils.sizeOfInHumanFormat(totalFileSize));
203 });
204 }
205
206 private Vector<Object> getRowVector(File file) {
207 Vector<Object> rowData = new Vector<>();
208 rowData.add(file.getParent());
209 rowData.add(file.getName());
210 totalFileSize += file.length();
211 rowData.add(FileUtils.sizeOfInHumanFormat(file));
212 rowData.add(DateUtils.millisecondToHumanFormat(file.lastModified()));
213 int pageCount = PdfUtils.getPageCount(file);
214 totalPageCount += pageCount;
215 rowData.add(pageCount);
216 return rowData;
217 }
218
219 class SearchThread extends Thread {
220 public final ExecutorService executorService;
221 private final AtomicInteger processedFiles = new AtomicInteger(0);
222 private int totalFiles = 0;
223 private final boolean isRecursiveSearched;
224
225 public SearchThread(boolean isRecursiveSearched) {
226 super();
227 this.isRecursiveSearched = isRecursiveSearched;
228 this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
229
230 resultFileList.clear();
231 totalFileSize = 0;
232 totalPageCount = 0;
233
234 SwingUtilities.invokeLater(() -> {
235 progressBar.setValue(0);
236 progressBar.setString("Starting search...");
237 resultTableModel.setRowCount(0);
238 statInfoLabel.setText("");
239 });
240 }
241
242 @Override
243 public void run() {
244 try {
245 List<File> fileList = fileListPanel.getFileList();
246 Set<File> fileSet = new TreeSet<>();
247 String[] extensions = new String[]{"pdf", "PDF"};
248 for (File file : fileList) {
249 fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
250 }
251
252 List<Future<?>> futures = new ArrayList<>();
253 totalFiles = fileSet.size();
254 updateProgress();
255
256 for (File file : fileSet) {
257 if (currentThread().isInterrupted()) {
258 return;
259 }
260 futures.add(executorService.submit(() -> {
261 if (currentThread().isInterrupted()) {
262 return null;
263 }
264 processFile(file);
265 incrementProcessedFiles();
266 return null;
267 }));
268 }
269
270
271 for (Future<?> future : futures) {
272 try {
273 future.get();
274 } catch (InterruptedException e) {
275 logger.error("Search interrupted", e);
276 currentThread().interrupt();
277 return;
278 }
279 }
280
281 showResult();
282 } catch (Exception e) {
283 logger.error("Search failed", e);
284 SwingUtilities.invokeLater(() -> progressBar.setString("Search failed"));
285 } finally {
286 executorService.shutdown();
287 SwingUtilities.invokeLater(() -> {
288 statButton.setEnabled(true);
289 cancelButton.setEnabled(false);
290 });
291 }
292 }
293
294 private void incrementProcessedFiles() {
295 processedFiles.incrementAndGet();
296 updateProgress();
297 }
298
299 private void updateProgress() {
300 if (totalFiles > 0) {
301 SwingUtilities.invokeLater(() -> {
302 int processed = processedFiles.get();
303 int percentage = (int) (processed * 100.0 / totalFiles);
304 progressBar.setValue(percentage);
305 progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
306 });
307 }
308 }
309 }
310 }