]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/cache/ManageCacheFunction.java
65565987529d3885d72a38d12a7b545317e22ed9
[GpsPrune.git] / tim / prune / function / cache / ManageCacheFunction.java
1 package tim.prune.function.cache;
2
3 import java.awt.BorderLayout;
4 import java.awt.CardLayout;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.GridBagConstraints;
8 import java.awt.GridBagLayout;
9 import java.awt.Insets;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.io.File;
13
14 import javax.swing.BorderFactory;
15 import javax.swing.Box;
16 import javax.swing.BoxLayout;
17 import javax.swing.ButtonGroup;
18 import javax.swing.JButton;
19 import javax.swing.JDialog;
20 import javax.swing.JLabel;
21 import javax.swing.JOptionPane;
22 import javax.swing.JPanel;
23 import javax.swing.JProgressBar;
24 import javax.swing.JRadioButton;
25 import javax.swing.JScrollPane;
26 import javax.swing.JTable;
27 import javax.swing.ListSelectionModel;
28 import javax.swing.event.ChangeEvent;
29 import javax.swing.event.ChangeListener;
30 import javax.swing.event.ListSelectionEvent;
31 import javax.swing.event.ListSelectionListener;
32
33 import tim.prune.App;
34 import tim.prune.GenericFunction;
35 import tim.prune.I18nManager;
36 import tim.prune.config.Config;
37 import tim.prune.gui.WholeNumberField;
38
39 /**
40  * Function class to manage the tile cache on local disk
41  */
42 public class ManageCacheFunction extends GenericFunction implements Runnable
43 {
44         private JDialog _dialog = null;
45         private CardLayout _cards = null;
46         private JPanel _cardPanel = null;
47         private JProgressBar _progressBar = null;
48         private File _cacheDir = null;
49         private TileCacheModel _model = null;
50         private JTable _setsTable = null;
51         private JButton _deleteSetButton = null;
52         private JLabel _tileSetLabel = null, _zoomLabel = null;
53         private JLabel _ageLabel = null;
54         private JRadioButton _deleteAllRadio = null;
55         private WholeNumberField _daysField = null;
56
57         private static TileFilter _TILEFILTER = new TileFilter();
58
59
60         /**
61          * Constructor
62          * @param inApp App object
63          */
64         public ManageCacheFunction(App inApp) {
65                 super(inApp);
66         }
67
68         /**
69          * @return name key
70          */
71         public String getNameKey() {
72                 return "function.managetilecache";
73         }
74
75         /**
76          * Show the dialog to start
77          */
78         public void begin()
79         {
80                 // First check if directory even exists
81                 _cacheDir = null;
82                 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
83                 if (path != null && !path.equals("")) {
84                         _cacheDir = new File(path);
85                 }
86                 if (_cacheDir == null || !_cacheDir.exists() || !_cacheDir.isDirectory())
87                 {
88                         _app.showErrorMessage(getNameKey(), "error.cache.notthere");
89                         return;
90                 }
91
92                 // Build the dialog if it hasn't already been built
93                 if (_dialog == null)
94                 {
95                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); // modal
96                         _dialog.setLocationRelativeTo(_parentFrame);
97                         _cardPanel = makeContents();
98                         _dialog.getContentPane().add(_cardPanel);
99                         _dialog.pack();
100                 }
101                 // Start a new thread to build the model
102                 new Thread(this).start();
103                 // Show the first panel of the dialog including progress bar
104                 _cards.first(_cardPanel);
105                 _dialog.setVisible(true);
106         }
107
108
109         /**
110          * Make the components for the dialog
111          * @return contents inside a panel
112          */
113         private JPanel makeContents()
114         {
115                 JPanel dialogPanel = new JPanel();
116                 _cards = new CardLayout();
117                 dialogPanel.setLayout(_cards);
118
119                 // Make first card including progress bar
120                 JPanel firstCard = new JPanel();
121                 firstCard.setLayout(new BorderLayout());
122                 JPanel progPanel = new JPanel();
123                 progPanel.setLayout(new BoxLayout(progPanel, BoxLayout.Y_AXIS));
124                 progPanel.add(Box.createVerticalGlue());
125                 progPanel.add(new JLabel(I18nManager.getText("confirm.running")));
126                 _progressBar = new JProgressBar(0, 10);
127                 _progressBar.setIndeterminate(true);
128                 progPanel.add(_progressBar);
129                 progPanel.add(Box.createVerticalGlue());
130                 firstCard.add(progPanel, BorderLayout.CENTER);
131                 // Cancel button at bottom
132                 JPanel buttonPanel = new JPanel();
133                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
134                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
135                 cancelButton.addActionListener(new ActionListener() {
136                         public void actionPerformed(ActionEvent e) {
137                                 // Cancel model building and close dialog
138                                 if (_model != null) _model.cancel();
139                                 _dialog.dispose();
140                         }
141                 });
142                 buttonPanel.add(cancelButton);
143                 firstCard.add(buttonPanel, BorderLayout.SOUTH);
144                 dialogPanel.add(firstCard, "card1");
145
146                 // Make second card including tileset table
147                 JPanel secondCard = new JPanel();
148                 secondCard.setLayout(new BorderLayout());
149                 // Table in the middle
150                 JPanel midPanel = new JPanel();
151                 midPanel.setLayout(new BorderLayout());
152                 _setsTable = new JTable();
153                 _setsTable.setPreferredScrollableViewportSize(new Dimension(500, 130));
154                 _setsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
155                 midPanel.add(new JScrollPane(_setsTable), BorderLayout.CENTER);
156                 midPanel.setBorder(BorderFactory.createEmptyBorder(8, 5, 8, 5));
157                 secondCard.add(midPanel, BorderLayout.CENTER);
158                 // Activate buttons if a tileset is selected
159                 _setsTable.getSelectionModel().addListSelectionListener(
160                         new ListSelectionListener() {
161                                 public void valueChanged(ListSelectionEvent e) {
162                                         ListSelectionModel lsm = (ListSelectionModel) e.getSource();
163                                         _deleteSetButton.setEnabled(!lsm.isSelectionEmpty());
164                                 }
165                         });
166
167                 // button panel at bottom
168                 buttonPanel = new JPanel();
169                 // left group
170                 JPanel leftPanel = new JPanel();
171                 leftPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
172                 _deleteSetButton = new JButton(I18nManager.getText("button.delete"));
173                 _deleteSetButton.addActionListener(new ActionListener() {
174                         public void actionPerformed(ActionEvent arg0) {
175                                 showDeleteCard();
176                         }
177                 });
178                 leftPanel.add(_deleteSetButton);
179                 // right group
180                 JPanel rightPanel = new JPanel();
181                 rightPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
182                 JButton closeButton = new JButton(I18nManager.getText("button.close"));
183                 closeButton.addActionListener(new ActionListener() {
184                         public void actionPerformed(ActionEvent e) {
185                                 if (_dialog != null) _dialog.dispose();
186                         }
187                 });
188                 rightPanel.add(closeButton);
189                 buttonPanel.add(leftPanel, BorderLayout.WEST);
190                 buttonPanel.add(rightPanel, BorderLayout.EAST);
191                 secondCard.add(buttonPanel, BorderLayout.SOUTH);
192                 dialogPanel.add(secondCard, "card2");
193
194                 // Make third card including delete options
195                 JPanel thirdCard = new JPanel();
196                 thirdCard.setLayout(new BorderLayout());
197                 // main panel
198                 JPanel mainPanel = new JPanel();
199                 GridBagLayout gridbag = new GridBagLayout();
200                 GridBagConstraints c = new GridBagConstraints();
201                 mainPanel.setLayout(gridbag);
202                 c.gridx = 0; c.gridy = 0;
203                 c.gridheight = 1; c.gridwidth = 2;
204                 c.weightx = 0.0; c.weighty = 0.0;
205                 c.anchor = GridBagConstraints.FIRST_LINE_START;
206                 _tileSetLabel = new JLabel("dummy text to be replaced");
207                 mainPanel.add(_tileSetLabel, c);
208                 c.gridx = 0; c.gridy = 1;
209                 c.ipady = 20;
210                 _zoomLabel = new JLabel("dummy text to be replaced");
211                 mainPanel.add(_zoomLabel, c);
212
213                 JRadioButton deleteOldRadio = new JRadioButton(I18nManager.getText("dialog.diskcache.deleteold"));
214                 _deleteAllRadio = new JRadioButton(I18nManager.getText("dialog.diskcache.deleteall"));
215                 ButtonGroup bGroup = new ButtonGroup();
216                 bGroup.add(_deleteAllRadio);
217                 bGroup.add(deleteOldRadio);
218                 _deleteAllRadio.addChangeListener(new ChangeListener() {
219                         public void stateChanged(ChangeEvent e) {
220                                 enableAgeFields();
221                         }
222                 });
223                 c.gridx = 0; c.gridy = 2;
224                 c.anchor = GridBagConstraints.LINE_START;
225                 c.ipady = 0;
226                 mainPanel.add(deleteOldRadio, c);
227                 c.gridwidth = 1;
228                 c.gridx = 0; c.gridy = 3;
229                 c.insets = new Insets(0, 40, 0, 0);
230                 _ageLabel = new JLabel(I18nManager.getText("dialog.diskcache.maximumage"));
231                 mainPanel.add(_ageLabel, c);
232                 _daysField = new WholeNumberField(2);
233                 _daysField.setMinimumSize(new Dimension(20, 1));
234                 _daysField.setText("30"); // default is 30 days
235                 c.gridx = 1; c.gridy = 3;
236                 c.ipadx = 20;
237                 c.insets = new Insets(0, 15, 0, 0);
238                 mainPanel.add(_daysField, c);
239                 c.gridx = 0; c.gridy = 4;
240                 c.gridwidth = 2;
241                 c.ipadx = 0;
242                 c.insets = new Insets(0, 0, 0, 0);
243                 mainPanel.add(_deleteAllRadio, c);
244                 _deleteAllRadio.setSelected(true);
245                 thirdCard.add(mainPanel, BorderLayout.CENTER);
246                 // button panel
247                 buttonPanel = new JPanel();
248                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
249                 JButton deleteButton = new JButton(I18nManager.getText("button.delete"));
250                 deleteButton.addActionListener(new ActionListener() {
251                         public void actionPerformed(ActionEvent arg0) {
252                                 deleteCurrentSets();
253                         }
254                 });
255                 buttonPanel.add(deleteButton);
256                 cancelButton = new JButton(I18nManager.getText("button.cancel"));
257                 cancelButton.addActionListener(new ActionListener() {
258                         public void actionPerformed(ActionEvent e) {
259                                 // Go back to second card
260                                 _cards.show(_cardPanel, "card2");
261                         }
262                 });
263                 buttonPanel.add(cancelButton);
264                 thirdCard.add(buttonPanel, BorderLayout.SOUTH);
265                 dialogPanel.add(thirdCard, "card3");
266                 return dialogPanel;
267         }
268
269
270         /**
271          * Construct the model in a separate thread
272          * and go on to the second panel of the dialog
273          */
274         public void run()
275         {
276                 // Check if directory has anything in it
277                 _model = new TileCacheModel(_cacheDir);
278                 _model.buildTileSets();
279                 if (_model.isAborted()) return;
280
281                 if (_model.getNumTileSets() <= 0)
282                 {
283                         _app.showErrorMessage(getNameKey(), "error.cache.empty");
284                         _dialog.dispose();
285                         return;
286                 }
287
288                 // Set controls according to current config
289                 _setsTable.setModel(new TileSetTableModel(_model));
290                 _deleteSetButton.setEnabled(false);
291                 // Set column widths after model has been set
292                 _setsTable.getColumnModel().getColumn(0).setPreferredWidth(220);
293                 _setsTable.getColumnModel().getColumn(1).setPreferredWidth(200);
294                 // Show second panel
295                 _cards.next(_cardPanel);
296         }
297
298         /**
299          * Prepare and show the delete panel
300          */
301         private void showDeleteCard()
302         {
303                 // set tileset label
304                 int numSelected = 0;
305                 String desc = null;
306                 RowInfo totals = new RowInfo();
307                 for (int i=0; i<_setsTable.getRowCount(); i++) {
308                         if (_setsTable.isRowSelected(i))
309                         {
310                                 if (desc == null) desc = _model.getTileSet(i).getPath();
311                                 totals.addRow(_model.getTileSet(i).getRowInfo());
312                                 numSelected++;
313                         }
314                 }
315                 if (numSelected == 0) return;
316                 String tileSetDesc = (numSelected == 1?desc:I18nManager.getText("dialog.diskcache.tileset.multiple"));
317                 _tileSetLabel.setText(I18nManager.getText("dialog.diskcache.tileset") + " : "
318                         + tileSetDesc);
319                 _zoomLabel.setText(I18nManager.getText("dialog.diskcache.table.zoom") + " : "
320                         + totals.getZoomRange());
321
322                 // enable/disable edit fields
323                 enableAgeFields();
324                 // show the next card
325                 _cards.next(_cardPanel);
326         }
327
328         /**
329          * Enable or disable the fields for entering tile age
330          */
331         private void enableAgeFields()
332         {
333                 boolean showAgeBoxes = !_deleteAllRadio.isSelected();
334                 _ageLabel.setEnabled(showAgeBoxes);
335                 _daysField.setEnabled(showAgeBoxes);
336         }
337
338         /**
339          * Try to delete all the files in the currently selected tilesets
340          * (Maybe more than one tileset is selected in the table)
341          */
342         private void deleteCurrentSets()
343         {
344                 // Determine age limit if given
345                 int ageLimit = -1;
346                 if (!_deleteAllRadio.isSelected()) {
347                         ageLimit = _daysField.getValue();
348                 }
349                 // Loop over selected tilesets and delete them
350                 int totalDeleted = 0;
351                 for (int i=0; i<_setsTable.getRowCount(); i++)
352                 {
353                         if (_setsTable.isRowSelected(i))
354                         {
355                                 File dir = new File(_model.getCacheDir(), _model.getTileSet(i).getPath());
356                                 if (dir.exists())
357                                 {
358                                         int numFilesDeleted = deleteFilesFrom(dir, ageLimit);
359                                         if (numFilesDeleted > 0) {
360                                                 totalDeleted += numFilesDeleted;
361                                         }
362                                 }
363                         }
364                 }
365                 if (totalDeleted > 0)
366                 {
367                         // Show confirmation message
368                         JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.diskcache.deleted1")
369                                 + " " + totalDeleted + " " + I18nManager.getText("dialog.diskcache.deleted2"),
370                                 I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
371                         // reload model
372                         _cards.first(_cardPanel);
373                         new Thread(this).start();
374                 }
375                 else {
376                         _app.showErrorMessage(getNameKey(), "error.cache.cannotdelete");
377                 }
378         }
379
380
381         /**
382          * Delete recursively all files which are older than the age limit
383          * @param inDir directory to delete from
384          * @param inMaxDays age limit in days
385          * @return number of files deleted
386          */
387         private static int deleteFilesFrom(File inDir, int inMaxDays)
388         {
389                 int numDeleted = 0;
390                 long now = System.currentTimeMillis();
391                 if (inDir.exists() && inDir.isDirectory())
392                 {
393                         for (File subdir : inDir.listFiles())
394                         {
395                                 if (subdir.isDirectory()) {
396                                         numDeleted += deleteFilesFrom(subdir, inMaxDays);
397                                 }
398                                 else if (subdir.isFile() && subdir.exists())
399                                 {
400                                         boolean isTileFile = _TILEFILTER.accept(subdir);
401                                         boolean isBadFile = !isTileFile && subdir.getName().toLowerCase().endsWith("png");
402                                         if (isTileFile || isBadFile)
403                                         {
404                                                 long fileAge = (now - subdir.lastModified()) / 1000 / 60 / 60 / 24;
405                                                 if (inMaxDays < 0 || fileAge > inMaxDays || isBadFile)
406                                                 {
407                                                         if (subdir.delete()) {
408                                                                 numDeleted++;
409                                                         }
410                                                 }
411                                         }
412                                 }
413                         }
414                         // Try to delete the directory (doesn't work if not empty)
415                         inDir.delete();
416                 }
417                 return numDeleted;
418         }
419 }