package tim.prune.function.cache; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import tim.prune.App; import tim.prune.GenericFunction; import tim.prune.I18nManager; import tim.prune.config.Config; import tim.prune.gui.WholeNumberField; /** * Function class to manage the tile cache on local disk */ public class ManageCacheFunction extends GenericFunction implements Runnable { private JDialog _dialog = null; private CardLayout _cards = null; private JPanel _cardPanel = null; private JProgressBar _progressBar = null; private File _cacheDir = null; private TileCacheModel _model = null; private JTable _setsTable = null; private JButton _deleteSetButton = null; private JLabel _tileSetLabel = null, _zoomLabel = null; private JLabel _ageLabel = null; private JRadioButton _deleteAllRadio = null; private WholeNumberField _daysField = null; private static TileFilter _TILEFILTER = new TileFilter(); /** * Constructor * @param inApp App object */ public ManageCacheFunction(App inApp) { super(inApp); } /** * @return name key */ public String getNameKey() { return "function.managetilecache"; } /** * Show the dialog to start */ public void begin() { // First check if directory even exists _cacheDir = null; String path = Config.getConfigString(Config.KEY_DISK_CACHE); if (path != null && !path.equals("")) { _cacheDir = new File(path); } if (_cacheDir == null || !_cacheDir.exists() || !_cacheDir.isDirectory()) { _app.showErrorMessage(getNameKey(), "error.cache.notthere"); return; } // Build the dialog if it hasn't already been built if (_dialog == null) { _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); // modal _dialog.setLocationRelativeTo(_parentFrame); _cardPanel = makeContents(); _dialog.getContentPane().add(_cardPanel); _dialog.pack(); } // Start a new thread to build the model new Thread(this).start(); // Show the first panel of the dialog including progress bar _cards.first(_cardPanel); _dialog.setVisible(true); } /** * Make the components for the dialog * @return contents inside a panel */ private JPanel makeContents() { JPanel dialogPanel = new JPanel(); _cards = new CardLayout(); dialogPanel.setLayout(_cards); // Make first card including progress bar JPanel firstCard = new JPanel(); firstCard.setLayout(new BorderLayout()); JPanel progPanel = new JPanel(); progPanel.setLayout(new BoxLayout(progPanel, BoxLayout.Y_AXIS)); progPanel.add(Box.createVerticalGlue()); progPanel.add(new JLabel(I18nManager.getText("confirm.running"))); _progressBar = new JProgressBar(0, 10); _progressBar.setIndeterminate(true); progPanel.add(_progressBar); progPanel.add(Box.createVerticalGlue()); firstCard.add(progPanel, BorderLayout.CENTER); // Cancel button at bottom JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Cancel model building and close dialog if (_model != null) _model.cancel(); _dialog.dispose(); } }); buttonPanel.add(cancelButton); firstCard.add(buttonPanel, BorderLayout.SOUTH); dialogPanel.add(firstCard, "card1"); // Make second card including tileset table JPanel secondCard = new JPanel(); secondCard.setLayout(new BorderLayout()); // Table in the middle JPanel midPanel = new JPanel(); midPanel.setLayout(new BorderLayout()); _setsTable = new JTable(); _setsTable.setPreferredScrollableViewportSize(new Dimension(500, 130)); _setsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); midPanel.add(new JScrollPane(_setsTable), BorderLayout.CENTER); midPanel.setBorder(BorderFactory.createEmptyBorder(8, 5, 8, 5)); secondCard.add(midPanel, BorderLayout.CENTER); // Activate buttons if a tileset is selected _setsTable.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { ListSelectionModel lsm = (ListSelectionModel) e.getSource(); _deleteSetButton.setEnabled(!lsm.isSelectionEmpty()); } }); // button panel at bottom buttonPanel = new JPanel(); // left group JPanel leftPanel = new JPanel(); leftPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); _deleteSetButton = new JButton(I18nManager.getText("button.delete")); _deleteSetButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { showDeleteCard(); } }); leftPanel.add(_deleteSetButton); // right group JPanel rightPanel = new JPanel(); rightPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); JButton closeButton = new JButton(I18nManager.getText("button.close")); closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (_dialog != null) _dialog.dispose(); } }); rightPanel.add(closeButton); buttonPanel.add(leftPanel, BorderLayout.WEST); buttonPanel.add(rightPanel, BorderLayout.EAST); secondCard.add(buttonPanel, BorderLayout.SOUTH); dialogPanel.add(secondCard, "card2"); // Make third card including delete options JPanel thirdCard = new JPanel(); thirdCard.setLayout(new BorderLayout()); // main panel JPanel mainPanel = new JPanel(); GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); mainPanel.setLayout(gridbag); c.gridx = 0; c.gridy = 0; c.gridheight = 1; c.gridwidth = 2; c.weightx = 0.0; c.weighty = 0.0; c.anchor = GridBagConstraints.FIRST_LINE_START; _tileSetLabel = new JLabel("dummy text to be replaced"); mainPanel.add(_tileSetLabel, c); c.gridx = 0; c.gridy = 1; c.ipady = 20; _zoomLabel = new JLabel("dummy text to be replaced"); mainPanel.add(_zoomLabel, c); JRadioButton deleteOldRadio = new JRadioButton(I18nManager.getText("dialog.diskcache.deleteold")); _deleteAllRadio = new JRadioButton(I18nManager.getText("dialog.diskcache.deleteall")); ButtonGroup bGroup = new ButtonGroup(); bGroup.add(_deleteAllRadio); bGroup.add(deleteOldRadio); _deleteAllRadio.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { enableAgeFields(); } }); c.gridx = 0; c.gridy = 2; c.anchor = GridBagConstraints.LINE_START; c.ipady = 0; mainPanel.add(deleteOldRadio, c); c.gridwidth = 1; c.gridx = 0; c.gridy = 3; c.insets = new Insets(0, 40, 0, 0); _ageLabel = new JLabel(I18nManager.getText("dialog.diskcache.maximumage")); mainPanel.add(_ageLabel, c); _daysField = new WholeNumberField(2); _daysField.setMinimumSize(new Dimension(20, 1)); _daysField.setText("30"); // default is 30 days c.gridx = 1; c.gridy = 3; c.ipadx = 20; c.insets = new Insets(0, 15, 0, 0); mainPanel.add(_daysField, c); c.gridx = 0; c.gridy = 4; c.gridwidth = 2; c.ipadx = 0; c.insets = new Insets(0, 0, 0, 0); mainPanel.add(_deleteAllRadio, c); _deleteAllRadio.setSelected(true); thirdCard.add(mainPanel, BorderLayout.CENTER); // button panel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); JButton deleteButton = new JButton(I18nManager.getText("button.delete")); deleteButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { deleteCurrentSets(); } }); buttonPanel.add(deleteButton); cancelButton = new JButton(I18nManager.getText("button.cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Go back to second card _cards.show(_cardPanel, "card2"); } }); buttonPanel.add(cancelButton); thirdCard.add(buttonPanel, BorderLayout.SOUTH); dialogPanel.add(thirdCard, "card3"); return dialogPanel; } /** * Construct the model in a separate thread * and go on to the second panel of the dialog */ public void run() { // TODO: Maybe this can be speeded up so that it just finds the tilesets first and then gets the details later // Check if directory has anything in it _model = new TileCacheModel(_cacheDir); _model.buildTileSets(); if (_model.isAborted()) return; if (_model.getNumTileSets() <= 0) { _app.showErrorMessage(getNameKey(), "error.cache.empty"); _dialog.dispose(); return; } // Set controls according to current config _setsTable.setModel(new TileSetTableModel(_model)); _deleteSetButton.setEnabled(false); // Set column widths after model has been set _setsTable.getColumnModel().getColumn(0).setPreferredWidth(220); _setsTable.getColumnModel().getColumn(1).setPreferredWidth(200); // Show second panel _cards.next(_cardPanel); } /** * Prepare and show the delete panel */ private void showDeleteCard() { // set tileset label int numSelected = 0; String desc = null; RowInfo totals = new RowInfo(); for (int i=0; i<_setsTable.getRowCount(); i++) { if (_setsTable.isRowSelected(i)) { if (desc == null) desc = _model.getTileSet(i).getPath(); totals.addRow(_model.getTileSet(i).getRowInfo()); numSelected++; } } if (numSelected == 0) return; String tileSetDesc = (numSelected == 1?desc:I18nManager.getText("dialog.diskcache.tileset.multiple")); _tileSetLabel.setText(I18nManager.getText("dialog.diskcache.tileset") + " : " + tileSetDesc); _zoomLabel.setText(I18nManager.getText("dialog.diskcache.table.zoom") + " : " + totals.getZoomRange()); // enable/disable edit fields enableAgeFields(); // show the next card _cards.next(_cardPanel); } /** * Enable or disable the fields for entering tile age */ private void enableAgeFields() { boolean showAgeBoxes = !_deleteAllRadio.isSelected(); _ageLabel.setEnabled(showAgeBoxes); _daysField.setEnabled(showAgeBoxes); } /** * Try to delete all the files in the currently selected tilesets * (Maybe more than one tileset is selected in the table) */ private void deleteCurrentSets() { // Determine age limit if given int ageLimit = -1; if (!_deleteAllRadio.isSelected()) { ageLimit = _daysField.getValue(); } // Loop over selected tilesets and delete them int totalDeleted = 0; for (int i=0; i<_setsTable.getRowCount(); i++) { if (_setsTable.isRowSelected(i)) { File dir = new File(_model.getCacheDir(), _model.getTileSet(i).getPath()); if (dir.exists()) { int numFilesDeleted = deleteFilesFrom(dir, ageLimit); if (numFilesDeleted > 0) { totalDeleted += numFilesDeleted; } } } } if (totalDeleted > 0) { // Show confirmation message JOptionPane.showMessageDialog(_dialog, I18nManager.getTextWithNumber("dialog.diskcache.deleted", totalDeleted), I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE); // reload model _cards.first(_cardPanel); new Thread(this).start(); } else { _app.showErrorMessage(getNameKey(), "error.cache.cannotdelete"); } } /** * Delete recursively all files which are older than the age limit * @param inDir directory to delete from * @param inMaxDays age limit in days * @return number of files deleted */ private static int deleteFilesFrom(File inDir, int inMaxDays) { int numDeleted = 0; long now = System.currentTimeMillis(); if (inDir.exists() && inDir.isDirectory()) { for (File subdir : inDir.listFiles()) { if (subdir.isDirectory()) { numDeleted += deleteFilesFrom(subdir, inMaxDays); } else if (subdir.isFile() && subdir.exists() && _TILEFILTER.accept(subdir)) { long fileAge = (now - subdir.lastModified()) / 1000 / 60 / 60 / 24; if (inMaxDays < 0 || fileAge > inMaxDays) { if (subdir.delete()) { numDeleted++; } } } } // Try to delete the directory (doesn't work if not empty) inDir.delete(); } return numDeleted; } }