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;
}
}