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("dummy text to be replaced");
207 mainPanel.add(_tileSetLabel, c);
208 c.gridx = 0; c.gridy = 1;
210 _zoomLabel = new JLabel("dummy text to be replaced");
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(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;
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 // TODO: Maybe this can be speeded up so that it just finds the tilesets first and then gets the details later
277 // Check if directory has anything in it
278 _model = new TileCacheModel(_cacheDir);
279 _model.buildTileSets();
280 if (_model.isAborted()) return;
282 if (_model.getNumTileSets() <= 0)
284 _app.showErrorMessage(getNameKey(), "error.cache.empty");
289 // Set controls according to current config
290 _setsTable.setModel(new TileSetTableModel(_model));
291 _deleteSetButton.setEnabled(false);
292 // Set column widths after model has been set
293 _setsTable.getColumnModel().getColumn(0).setPreferredWidth(220);
294 _setsTable.getColumnModel().getColumn(1).setPreferredWidth(200);
296 _cards.next(_cardPanel);
300 * Prepare and show the delete panel
302 private void showDeleteCard()
307 RowInfo totals = new RowInfo();
308 for (int i=0; i<_setsTable.getRowCount(); i++) {
309 if (_setsTable.isRowSelected(i))
311 if (desc == null) desc = _model.getTileSet(i).getPath();
312 totals.addRow(_model.getTileSet(i).getRowInfo());
316 if (numSelected == 0) return;
317 String tileSetDesc = (numSelected == 1?desc:I18nManager.getText("dialog.diskcache.tileset.multiple"));
318 _tileSetLabel.setText(I18nManager.getText("dialog.diskcache.tileset") + " : "
320 _zoomLabel.setText(I18nManager.getText("dialog.diskcache.table.zoom") + " : "
321 + totals.getZoomRange());
323 // enable/disable edit fields
325 // show the next card
326 _cards.next(_cardPanel);
330 * Enable or disable the fields for entering tile age
332 private void enableAgeFields()
334 boolean showAgeBoxes = !_deleteAllRadio.isSelected();
335 _ageLabel.setEnabled(showAgeBoxes);
336 _daysField.setEnabled(showAgeBoxes);
340 * Try to delete all the files in the currently selected tilesets
341 * (Maybe more than one tileset is selected in the table)
343 private void deleteCurrentSets()
345 // Determine age limit if given
347 if (!_deleteAllRadio.isSelected()) {
348 ageLimit = _daysField.getValue();
350 // Loop over selected tilesets and delete them
351 int totalDeleted = 0;
352 for (int i=0; i<_setsTable.getRowCount(); i++)
354 if (_setsTable.isRowSelected(i))
356 File dir = new File(_model.getCacheDir(), _model.getTileSet(i).getPath());
359 int numFilesDeleted = deleteFilesFrom(dir, ageLimit);
360 if (numFilesDeleted > 0) {
361 totalDeleted += numFilesDeleted;
366 if (totalDeleted > 0)
368 // Show confirmation message
369 JOptionPane.showMessageDialog(_dialog, I18nManager.getTextWithNumber("dialog.diskcache.deleted", totalDeleted),
370 I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
372 _cards.first(_cardPanel);
373 new Thread(this).start();
376 _app.showErrorMessage(getNameKey(), "error.cache.cannotdelete");
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
387 private static int deleteFilesFrom(File inDir, int inMaxDays)
390 long now = System.currentTimeMillis();
391 if (inDir.exists() && inDir.isDirectory())
393 for (File subdir : inDir.listFiles())
395 if (subdir.isDirectory()) {
396 numDeleted += deleteFilesFrom(subdir, inMaxDays);
398 else if (subdir.isFile() && subdir.exists() && _TILEFILTER.accept(subdir))
400 long fileAge = (now - subdir.lastModified()) / 1000 / 60 / 60 / 24;
401 if (inMaxDays < 0 || fileAge > inMaxDays)
403 if (subdir.delete()) {
409 // Try to delete the directory (doesn't work if not empty)