All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.mvc.backup.view.BackupView Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show newest version
package org.bidib.wizard.mvc.backup.view;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;

import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.text.Document;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.client.common.table.AbstractEmptyTable;
import org.bidib.wizard.client.common.table.ProgressCellRenderer;
import org.bidib.wizard.client.common.text.WizardComponentFactory;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.client.common.view.statusbar.StatusBarPublisher;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.service.SettingsService;
import org.bidib.wizard.core.dialog.FileDialog;
import org.bidib.wizard.mvc.backup.controller.listener.BackupControllerListener;
import org.bidib.wizard.mvc.backup.model.BackupTableModel;
import org.bidib.wizard.mvc.backup.model.NodeBackupModel;
import org.bidib.wizard.mvc.common.view.text.ChangeDocumentListener;
import org.bidib.wizard.mvc.debug.view.FileStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jgoodies.binding.adapter.SingleListSelectionAdapter;
import com.jgoodies.binding.beans.PropertyAdapter;
import com.jgoodies.binding.beans.PropertyConnector;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.value.ConverterValueModel;
import com.jgoodies.binding.value.ValueModel;
import com.jgoodies.forms.builder.FormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.Paddings;
import com.jidesoft.grid.AutoFilterTableHeader;
import com.jidesoft.grid.CellRendererManager;
import com.jidesoft.grid.CellStyle;
import com.jidesoft.grid.CellStyleTableHeader;
import com.jidesoft.grid.FilterableTableModel;
import com.jidesoft.grid.HeaderStyleModel;
import com.jidesoft.grid.IFilterableTableModel;
import com.jidesoft.grid.JideTable;
import com.jidesoft.grid.NestedTableHeader;
import com.jidesoft.grid.SortableTableModel;
import com.jidesoft.grid.TableColumnChooserPopupMenuCustomizer;
import com.jidesoft.grid.TableHeaderPopupMenuInstaller;
import com.jidesoft.grid.TableModelWrapperUtils;
import com.jidesoft.icons.CheckBoxIcon;
import com.jidesoft.swing.DefaultOverlayable;
import com.jidesoft.swing.JideSwingUtilities;
import com.jidesoft.swing.StyledLabelBuilder;
import com.jidesoft.swing.TristateCheckBox;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;

public class BackupView implements Dockable {
    private static final Logger LOGGER = LoggerFactory.getLogger(BackupView.class);

    private static final String ENCODED_DIALOG_COLUMN_SPECS = "pref, 3dlu, fill:pref:grow, 3dlu, pref";

    private static final String ENCODED_DIALOG_ROW_SPECS = "p, 3dlu, fill:50dlu:grow, 3dlu, p, 3dlu, p";

    private final JPanel contentPanel;

    private SelectionInList nodeBackupSelection;

    private TableModelListener tableModelListener;

    private ValueModel backupDirectoryValueModel;

    private JTextField backupDirectoryText;

    private final JButton selectDirectoryButton = new JButton(Resources.getString(getClass(), "select"));

    private final JButton startBackupButton = new JButton(Resources.getString(getClass(), "startBackup"));

    private final BackupTableModel backupTableModel;

    private final SettingsService settingsService;

    private final BackupControllerListener backupController;

    private final StatusBarPublisher statusBarPublisher;

    public BackupView(final BackupControllerListener backupController, final BackupTableModel backupTableModel,
        final SettingsService settingsService, final StatusBarPublisher statusBarPublisher) {

        this.backupController = backupController;
        this.backupTableModel = backupTableModel;
        this.settingsService = settingsService;
        this.statusBarPublisher = statusBarPublisher;

        DockKeys.DOCKKEY_BACKUP_VIEW.setName(Resources.getString(getClass(), "title"));
        DockKeys.DOCKKEY_BACKUP_VIEW.setFloatEnabled(true);
        DockKeys.DOCKKEY_BACKUP_VIEW.setAutoHideEnabled(false);

        LOGGER.info("Create new BackupView");

        final WizardSettingsInterface wizardSettings = this.settingsService.getWizardSettings();
        String storedBackupDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_BACKUP_KEY);
        if (StringUtils.isNotBlank(storedBackupDirectory)) {
            try {
                this.backupTableModel.setBackupDirectory(new File(storedBackupDirectory));
            }
            catch (Exception ex) {
                LOGGER.warn("Set the backup directory in the model failed.", ex);
            }
        }

        // create form builder
        FormBuilder dialogBuilder = null;
        boolean debugDialog = false;
        if (debugDialog) {
            JPanel panel = new FormDebugPanel();
            dialogBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        else {
            JPanel panel = new JPanel(new BorderLayout());
            dialogBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        dialogBuilder.border(Paddings.DIALOG);

        dialogBuilder.add(Resources.getString(getClass(), "select-nodes-to-backup")).xyw(1, 1, 3);

        nodeBackupSelection =
            new SelectionInList(
                (ListModel) this.backupTableModel.getNodeBackupListModel());

        final SortableTableModel tableModel = new SortableTableModel(new BackupTableTableModel(nodeBackupSelection));

        // create a booster table
        final AbstractEmptyTable backupTable =
            new AbstractEmptyTable(tableModel, Resources.getString(getClass(), "empty_table")) {
                private static final long serialVersionUID = 1L;
            };
        backupTable.adjustRowHeight();
        backupTable.setSelectionModel(new SingleListSelectionAdapter(nodeBackupSelection.getSelectionIndexHolder()));
        backupTable.setAutoResizeMode(JideTable.AUTO_RESIZE_FILL | JideTable.AUTO_RESIZE_LAST_COLUMN);
        // backupTable.setFillsGrids(true);

        CellRendererManager
            .registerRenderer(Integer.class, new ProgressCellRenderer(true), ProgressCellRenderer.CONTEXT);

        TableColumn tc = backupTable.getColumnModel().getColumn(BackupTableTableModel.COLUMN_SELECTED);
        tc.setPreferredWidth(30);
        tc.setMaxWidth(50);
        tc = backupTable.getColumnModel().getColumn(BackupTableTableModel.COLUMN_UNIQUE_ID);
        tc.setMinWidth(120);
        tc.setMaxWidth(150);
        tc.setPreferredWidth(150);
        tc = backupTable.getColumnModel().getColumn(BackupTableTableModel.COLUMN_DESCRIPTION);
        tc.setPreferredWidth(300);
        tc = backupTable.getColumnModel().getColumn(BackupTableTableModel.COLUMN_PROGRESS);

        this.tableModelListener = new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                int viewColumn = backupTable.convertColumnIndexToView(0);
                if (viewColumn < 0) {
                    return;
                }
                boolean hasUnselected = false;
                boolean hasSelected = false;
                for (int row = 0; row < backupTable.getRowCount(); row++) {
                    Object value = backupTable.getValueAt(row, viewColumn);
                    if (value instanceof Boolean) {
                        if ((Boolean) value) {
                            hasSelected = true;
                        }
                        else {
                            hasUnselected = true;
                        }
                        if (hasSelected && hasUnselected) {
                            break;
                        }
                    }
                }
                int state;
                if (hasSelected && hasUnselected) {
                    state = TristateCheckBox.STATE_MIXED;
                }
                else if (hasSelected) {
                    state = TristateCheckBox.STATE_SELECTED;
                }
                else {
                    state = TristateCheckBox.STATE_UNSELECTED;
                }

                final HeaderStyleModel headerStyleModel =
                    (HeaderStyleModel) TableModelWrapperUtils
                        .getActualTableModel(backupTable.getModel(), HeaderStyleModel.class);

                CellStyle style = headerStyleModel.getHeaderStyleAt(0, 0);
                if (style.getIcon() instanceof CheckBoxIcon) {
                    ((CheckBoxIcon) style.getIcon()).setState(state);
                    backupTable.getTableHeader().repaint();
                }
            }
        };
        tableModel.addTableModelListener(tableModelListener);

        final SortableTableModel sortableTableModel =
            (SortableTableModel) TableModelWrapperUtils
                .getActualTableModel(backupTable.getModel(), SortableTableModel.class);
        sortableTableModel.setColumnSortable(0, false);
        sortableTableModel.sortColumn(BackupTableTableModel.COLUMN_DESCRIPTION, true, true);

        AutoFilterTableHeader header = new AutoFilterTableHeader(backupTable) {
            private static final long serialVersionUID = 1L;

            @Override
            protected IFilterableTableModel createFilterableTableModel(TableModel model) {
                return new FilterableTableModel(model) {
                    private static final long serialVersionUID = 1L;

                    @Override
                    public boolean isColumnAutoFilterable(int column) {
                        return column != 0;
                    }
                };
            }
        };
        header.setAutoFilterEnabled(true);
        header.setUseNativeHeaderRenderer(true);
        backupTable.setTableHeader(header);
        JideSwingUtilities.insertMouseListener(header, new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getSource() instanceof CellStyleTableHeader) {
                    CellStyleTableHeader _header = (CellStyleTableHeader) e.getSource();
                    Point p = e.getPoint();
                    TableColumnModel columnModel = _header.getColumnModel();
                    int index = _header.originalColumnAtPoint(p);
                    if (_header.getTable() != null && index >= 0 && index < columnModel.getColumnCount()) {
                        TableModel actualTableModel =
                            TableModelWrapperUtils
                                .getActualTableModel(_header.getTable().getModel(), HeaderStyleModel.class);
                        if (actualTableModel instanceof HeaderStyleModel
                            && ((HeaderStyleModel) actualTableModel).isHeaderStyleOn()) {
                            int actualColumnIndex =
                                TableModelWrapperUtils
                                    .getActualColumnAt(_header.getTable().getModel(),
                                        _header.getTable().convertColumnIndexToModel(index), actualTableModel);
                            int rowIndex =
                                _header instanceof NestedTableHeader ? ((NestedTableHeader) _header).getRowCount() - 1
                                    : 0;
                            CellStyle style =
                                ((HeaderStyleModel) actualTableModel).getHeaderStyleAt(rowIndex, actualColumnIndex);
                            Rectangle headerRect = _header.getHeaderRect(index);
                            Point centerPoint =
                                new Point(headerRect.x + headerRect.width / 2, headerRect.y + headerRect.height / 2);
                            if (style != null && style.getIcon() instanceof CheckBoxIcon
                                && p.x >= centerPoint.x - style.getIcon().getIconWidth() / 2
                                && p.x <= centerPoint.x + style.getIcon().getIconWidth() / 2
                                && p.y >= centerPoint.y - style.getIcon().getIconHeight() / 2
                                && p.y <= centerPoint.y + style.getIcon().getIconHeight() / 2) {
                                int state = ((CheckBoxIcon) style.getIcon()).getState();
                                if (state == TristateCheckBox.STATE_MIXED
                                    || state == TristateCheckBox.STATE_UNSELECTED) {
                                    state = TristateCheckBox.STATE_SELECTED;

                                }
                                else if (state == TristateCheckBox.STATE_SELECTED) {
                                    state = TristateCheckBox.STATE_UNSELECTED;
                                }
                                ((CheckBoxIcon) style.getIcon()).setState(state);
                                tableModel.removeTableModelListener(tableModelListener);
                                for (int row = 0; row < tableModel.getRowCount(); row++) {
                                    tableModel.setValueAt(state == TristateCheckBox.STATE_SELECTED, row, 0);
                                }
                                tableModel.addTableModelListener(tableModelListener);
                                e.consume();
                            }
                        }
                    }
                }
            }
        }, 0);
        TableHeaderPopupMenuInstaller installer = new TableHeaderPopupMenuInstaller(backupTable);
        installer.addTableHeaderPopupMenuCustomizer(new TableColumnChooserPopupMenuCustomizer());

        final DefaultOverlayable overlayTable = new DefaultOverlayable(new JScrollPane(backupTable));
        tableModel.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                overlayTable.setOverlayVisible(tableModel.getRowCount() == 0);
            }
        });

        overlayTable
            .addOverlayComponent(
                StyledLabelBuilder.createStyledLabel("{" + backupTable.getEmptyTableText() + ":f:gray}"));

        dialogBuilder.add(overlayTable).xyw(1, 3, 5);

        // create the textfield for backup directory
        backupDirectoryValueModel =
            new PropertyAdapter(this.backupTableModel, BackupTableModel.PROPERTY_BACKUP_DIRECTORY,
                true);
        final ValueModel backupDirectoryConverterModel =
            new ConverterValueModel(backupDirectoryValueModel, new FileStringConverter());
        backupDirectoryText = WizardComponentFactory.createTextField(backupDirectoryConverterModel, true);
        backupDirectoryText.setEditable(false);

        final DefaultOverlayable backupDirectoryOverlayable = new DefaultOverlayable(backupDirectoryText);
        backupDirectoryOverlayable
            .addOverlayComponent(
                StyledLabelBuilder
                    .createStyledLabel("{" + Resources.getString(getClass(), "backupDirectory.prompt") + ":f:gray}"),
                SwingConstants.WEST);
        backupDirectoryOverlayable.setOverlayLocationInsets(new Insets(0, -5, 0, 5));

        final Document backupDirectoryDoc = backupDirectoryText.getDocument();
        backupDirectoryDoc
            .addDocumentListener(
                new ChangeDocumentListener(doc -> backupDirectoryOverlayable.setOverlayVisible(doc.getLength() < 1)));
        backupDirectoryOverlayable.setOverlayVisible(backupDirectoryDoc.getLength() < 1);

        dialogBuilder.add(Resources.getString(getClass(), "backupDirectory")).xy(1, 5);
        dialogBuilder.add(backupDirectoryOverlayable).xy(3, 5);

        dialogBuilder.add(selectDirectoryButton).xy(5, 5);
        selectDirectoryButton.addActionListener(evt -> fireSelectDirectory());
        selectDirectoryButton.setEnabled(true);

        dialogBuilder.add(startBackupButton).xy(1, 7);
        startBackupButton.addActionListener(evt -> fireStartBackup());
        startBackupButton.setEnabled(false);

        this.contentPanel = dialogBuilder.build();

        PropertyConnector
            .connect(this.backupTableModel, BackupTableModel.PROPERTY_BACKUP_ENABLED, startBackupButton, "enabled");

        this.startBackupButton.setEnabled(this.backupTableModel.isBackupEnabled());

    }

    @Override
    public DockKey getDockKey() {
        return DockKeys.DOCKKEY_BACKUP_VIEW;
    }

    @Override
    public Component getComponent() {
        return contentPanel;
    }

    private static final String WORKING_DIR_BACKUP_KEY = "backup";

    private void fireSelectDirectory() {
        final WizardSettingsInterface wizardSettings = settingsService.getWizardSettings();
        String storedBackupDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_BACKUP_KEY);
        final FileFilter[] ff = null;
        final FileDialog dialog = new FileDialog(contentPanel, FileDialog.SAVE, storedBackupDirectory, null, ff) {
            @Override
            public void approve(final String selectedFile) {
                File file = new File(selectedFile);
                if (file != null && file.isDirectory()) {

                    backupTableModel.setBackupDirectory(file);

                    final String backupDir = Paths.get(selectedFile).toString();
                    LOGGER.info("Save current backupDir: {}", backupDir);

                    wizardSettings.setWorkingDirectory(WORKING_DIR_BACKUP_KEY, backupDir);
                }
            }

            @Override
            protected boolean checkOverrideExisting(File file) {
                // do not ask for override
                return true;
            }
        };
        dialog.showDialog();

    }

    private void fireStartBackup() {

        // JOptionPane
        // .showMessageDialog(this.contentPanel, "Under construction! No CV values loaded from node!",
        // Resources.getString(getClass(), "title"), JOptionPane.WARNING_MESSAGE);

        final List nodeBackupModels = new LinkedList<>();

        // collect the selected nodes
        for (NodeBackupModel nodeBackupModel : this.backupTableModel.getNodes()) {

            if (nodeBackupModel.isSelected()) {

                // perform backup
                nodeBackupModels.add(nodeBackupModel);
            }

            // reset the progress on start
            nodeBackupModel.setProgress(0);
        }

        if (CollectionUtils.isEmpty(nodeBackupModels)) {
            JOptionPane
                .showMessageDialog(this.contentPanel, Resources.getString(getClass(), "backup.no-nodes-selected"),
                    Resources.getString(getClass(), "title"), JOptionPane.WARNING_MESSAGE);
            return;
        }

        final WizardSettingsInterface wizardSettings = settingsService.getWizardSettings();
        final String storedBackupDirectory = wizardSettings.getWorkingDirectory(WORKING_DIR_BACKUP_KEY);

        boolean nodeExportAppendDateEnabled = wizardSettings.isNodeExportAppendDateEnabled();

        final File backupDir;
        if (nodeExportAppendDateEnabled) {
            // append the current date and time to the backup dir
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
            String currentDateTime = now.format(formatter);

            backupDir = new File(storedBackupDirectory, currentDateTime);
            LOGGER.info("Use storedBackupDirectory with timestamp as backupDir: {}", backupDir);
        }
        else {
            backupDir = new File(storedBackupDirectory);
            LOGGER.info("Use storedBackupDirectory as backupDir: {}", backupDir);
        }

        final String storedBackupDirectoryWithTime = backupDir.getPath();

        try {
            FileUtils.forceMkdir(backupDir);
        }
        catch (IOException ex) {
            LOGGER.warn("Create backup directory with date and time failed.", ex);
            JOptionPane
                .showMessageDialog(this.contentPanel,
                    Resources.getString(getClass(), "backup.create-backup-dir-failed", storedBackupDirectoryWithTime),
                    Resources.getString(getClass(), "title"), JOptionPane.WARNING_MESSAGE);
            return;
        }

        this.statusBarPublisher
            .publish(Resources.getString(getClass(), "backup.started", storedBackupDirectoryWithTime), null);

        LOGGER.info("Perform the backup async.");
        CompletableFuture results = CompletableFuture.supplyAsync(() -> {
            return this.backupController.performBackup(storedBackupDirectoryWithTime, nodeBackupModels);
        });

        BiConsumer handle = (s, t) -> {
            LOGGER.info("The backup has finished with verdict: {}", s, t);

            if (t == null) {
                this.statusBarPublisher
                    .publish(Resources.getString(getClass(), "backup.finished", storedBackupDirectoryWithTime), null);

                SwingUtilities
                    .invokeLater(() -> JOptionPane
                        .showMessageDialog(this.contentPanel,
                            Resources.getString(getClass(), "backup.finished.dialog", storedBackupDirectoryWithTime),
                            Resources.getString(getClass(), "title"), JOptionPane.INFORMATION_MESSAGE));
            }
            else {
                this.statusBarPublisher
                    .publish(
                        Resources.getString(getClass(), "backup.finished-with-error", storedBackupDirectoryWithTime),
                        null);

                SwingUtilities
                    .invokeLater(() -> JOptionPane
                        .showMessageDialog(this.contentPanel, Resources
                            .getString(getClass(), "backup.finished-with-error.dialog", storedBackupDirectoryWithTime),
                            Resources.getString(getClass(), "title"), JOptionPane.WARNING_MESSAGE));
            }

        };

        try {
            results.whenCompleteAsync(handle);
        }
        catch (Exception ex) {
            LOGGER.warn("The backup has finished but there was an error.", ex);
        }
    }

}