1 package tim.prune.function.cache;
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;
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;
34 import tim.prune.GenericFunction;
35 import tim.prune.I18nManager;
36 import tim.prune.config.Config;
37 import tim.prune.gui.WholeNumberField;
40 * Function class to manage the tile cache on local disk
42 public class ManageCacheFunction extends GenericFunction implements Runnable
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;
57 private static TileFilter _TILEFILTER = new TileFilter();
62 * @param inApp App object
64 public ManageCacheFunction(App inApp) {
71 public String getNameKey() {
72 return "function.managetilecache";
76 * Show the dialog to start
80 // First check if directory even exists
82 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
83 if (path != null && !path.equals("")) {
84 _cacheDir = new File(path);
86 if (_cacheDir == null || !_cacheDir.exists() || !_cacheDir.isDirectory())
88 _app.showErrorMessage(getNameKey(), "error.cache.notthere");
92 // Build the dialog if it hasn't already been built
95 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); // modal
96 _dialog.setLocationRelativeTo(_parentFrame);
97 _cardPanel = makeContents();
98 _dialog.getContentPane().add(_cardPanel);
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);
110 * Make the components for the dialog
111 * @return contents inside a panel
113 private JPanel makeContents()
115 JPanel dialogPanel = new JPanel();
116 _cards = new CardLayout();
117 dialogPanel.setLayout(_cards);
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();
142 buttonPanel.add(cancelButton);
143 firstCard.add(buttonPanel, BorderLayout.SOUTH);
144 dialogPanel.add(firstCard, "card1");
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());
167 // button panel at bottom
168 buttonPanel = new JPanel();
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) {
178 leftPanel.add(_deleteSetButton);
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();
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");
194 // Make third card including delete options
195 JPanel thirdCard = new JPanel();
196 thirdCard.setLayout(new BorderLayout());
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("tileset label");
207 mainPanel.add(_tileSetLabel, c);
208 c.gridx = 0; c.gridy = 1;
210 _zoomLabel = new JLabel("zoom label");
211 mainPanel.add(_zoomLabel, c);
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) {
223 c.gridx = 0; c.gridy = 2;
224 c.anchor = GridBagConstraints.LINE_START;
226 mainPanel.add(deleteOldRadio, c);
228 c.gridx = 0; c.gridy = 3;
229 c.insets = new Insets(0, 40, 0, 0);
230 _ageLabel = new JLabel("Maximum age (days)");
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;
237 c.insets = new Insets(0, 15, 0, 0);
238 mainPanel.add(_daysField, c);
239 c.gridx = 0; c.gridy = 4;
242 c.insets = new Insets(0, 0, 0, 0);
243 mainPanel.add(_deleteAllRadio, c);
244 _deleteAllRadio.setSelected(true);
245 thirdCard.add(mainPanel, BorderLayout.CENTER);
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) {
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");
263 buttonPanel.add(cancelButton);
264 thirdCard.add(buttonPanel, BorderLayout.SOUTH);
265 dialogPanel.add(thirdCard, "card3");
271 * Construct the model in a separate thread
272 * and go on to the second panel of the dialog
276 // Check if directory has anything in it
277 _model = new TileCacheModel(_cacheDir);
278 _model.buildTileSets();
279 if (_model.isAborted()) return;
281 if (_model.getNumTileSets() <= 0)
283 _app.showErrorMessage(getNameKey(), "error.cache.empty");
287 // Set controls according to current config
288 _setsTable.setModel(new TileSetTableModel(_model));
289 _deleteSetButton.setEnabled(false);
290 // Set column widths after model has been set
291 _setsTable.getColumnModel().getColumn(0).setPreferredWidth(220);
292 _setsTable.getColumnModel().getColumn(1).setPreferredWidth(200);
294 _cards.next(_cardPanel);
298 * Prepare and show the delete panel
300 private void showDeleteCard()
305 RowInfo totals = new RowInfo();
306 for (int i=0; i<_setsTable.getRowCount(); i++) {
307 if (_setsTable.isRowSelected(i))
309 if (desc == null) desc = _model.getTileSet(i).getPath();
310 totals.addRow(_model.getTileSet(i).getRowInfo());
314 if (numSelected == 0) return;
315 String tileSetDesc = (numSelected == 1?desc:I18nManager.getText("dialog.diskcache.tileset.multiple"));
316 _tileSetLabel.setText(I18nManager.getText("dialog.diskcache.tileset") + " : "
318 _zoomLabel.setText(I18nManager.getText("dialog.diskcache.table.zoom") + " : "
319 + totals.getZoomRange());
321 // enable/disable edit fields
323 // show the next card
324 _cards.next(_cardPanel);
328 * Enable or disable the fields for entering tile age
330 private void enableAgeFields()
332 boolean showAgeBoxes = !_deleteAllRadio.isSelected();
333 _ageLabel.setEnabled(showAgeBoxes);
334 _daysField.setEnabled(showAgeBoxes);
338 * Try to delete all the files in the currently selected tilesets
339 * (Maybe more than one tileset is selected in the table)
341 private void deleteCurrentSets()
343 // Determine age limit if given
345 if (!_deleteAllRadio.isSelected()) {
346 ageLimit = _daysField.getValue();
348 // Loop over selected tilesets and delete them
349 int totalDeleted = 0;
350 for (int i=0; i<_setsTable.getRowCount(); i++)
352 if (_setsTable.isRowSelected(i))
354 File dir = new File(_model.getCacheDir(), _model.getTileSet(i).getPath());
357 int numFilesDeleted = deleteFilesFrom(dir, ageLimit);
358 if (numFilesDeleted > 0) {
359 totalDeleted += numFilesDeleted;
364 if (totalDeleted > 0)
366 // Show confirmation message
367 JOptionPane.showMessageDialog(_dialog, I18nManager.getText("dialog.diskcache.deleted1")
368 + " " + totalDeleted + " " + I18nManager.getText("dialog.diskcache.deleted2"),
369 I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
371 _cards.first(_cardPanel);
372 new Thread(this).start();
375 _app.showErrorMessage(getNameKey(), "error.cache.cannotdelete");
381 * Delete recursively all files which are older than the age limit
382 * @param inDir directory to delete from
383 * @param inMaxDays age limit in days
384 * @return number of files deleted
386 private static int deleteFilesFrom(File inDir, int inMaxDays)
389 long now = System.currentTimeMillis();
390 if (inDir.exists() && inDir.isDirectory())
392 for (File subdir : inDir.listFiles())
394 if (subdir.isDirectory()) {
395 numDeleted += deleteFilesFrom(subdir, inMaxDays);
397 else if (subdir.isFile() && subdir.exists() && _TILEFILTER.accept(subdir))
399 long fileAge = (now - subdir.lastModified()) / 1000 / 60 / 60 / 24;
400 if (inMaxDays < 0 || fileAge > inMaxDays)
402 if (subdir.delete()) {
408 // Try to delete the directory (doesn't work if not empty)