de.blizzy.backup.restore.RestoreDialog.java Source code

Java tutorial

Introduction

Here is the source code for de.blizzy.backup.restore.RestoreDialog.java

Source

/*
blizzy's Backup - Easy to use personal file backup application
Copyright (C) 2011-2012 Maik Schreiber
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package de.blizzy.backup.restore;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.jooq.Condition;
import org.jooq.Cursor;
import org.jooq.Record;
import org.jooq.exception.DataAccessException;

import de.blizzy.backup.BackupApplication;
import de.blizzy.backup.BackupPlugin;
import de.blizzy.backup.Compression;
import de.blizzy.backup.FileAttributes;
import de.blizzy.backup.IStorageInterceptor;
import de.blizzy.backup.Messages;
import de.blizzy.backup.StorageInterceptorDescriptor;
import de.blizzy.backup.Utils;
import de.blizzy.backup.Utils.IFileOrFolderEntry;
import de.blizzy.backup.database.Database;
import de.blizzy.backup.database.EntryType;
import de.blizzy.backup.database.schema.Tables;
import de.blizzy.backup.database.schema.tables.records.BackupsRecord;
import de.blizzy.backup.settings.Settings;
import de.blizzy.backup.vfs.filesystem.FileSystemFileOrFolder;

public class RestoreDialog extends Dialog {
    private Settings settings;
    private List<Backup> backups = new ArrayList<>();
    private Database database;
    private ComboViewer backupsViewer;
    private Text searchText;
    private boolean listenForSearchText = true;
    private TableViewer entriesViewer;
    private Button moveUpButton;
    private Link currentFolderLink;
    private Timer timer = new Timer();
    private TimerTask searchTimerTask;
    private List<IStorageInterceptor> storageInterceptors = new ArrayList<>();
    private Boolean alwaysRestoreFromOlderBackups;

    public RestoreDialog(Shell parentShell) {
        super(parentShell);

        settings = BackupApplication.getSettingsManager().getSettings();
        database = new Database(settings, false);
    }

    @Override
    protected void configureShell(Shell newShell) {
        super.configureShell(newShell);
        newShell.setImages(BackupApplication.getWindowImages());
        newShell.setText(Messages.Title_Restore);
    }

    @Override
    protected boolean isResizable() {
        return true;
    }

    @Override
    public int open() {
        final ProgressMonitorDialog dlg = new ProgressMonitorDialog(getParentShell());
        IRunnableWithProgress runnable = new IRunnableWithProgress() {
            @Override
            public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                monitor.beginTask(Messages.Title_OpenBackupDatabase, IProgressMonitor.UNKNOWN);

                final boolean[] ok = { true };
                List<StorageInterceptorDescriptor> descs = BackupPlugin.getDefault().getStorageInterceptors();
                for (final StorageInterceptorDescriptor desc : descs) {
                    final IStorageInterceptor interceptor = desc.getStorageInterceptor();
                    SafeRunner.run(new ISafeRunnable() {
                        @Override
                        public void run() {
                            IDialogSettings settings = Utils
                                    .getChildSection(Utils.getSection("storageInterceptors"), desc.getId()); //$NON-NLS-1$
                            if (!interceptor.initialize(dlg.getShell(), settings)) {
                                ok[0] = false;
                            }
                        }

                        @Override
                        public void handleException(Throwable t) {
                            ok[0] = false;
                            interceptor.showErrorMessage(t, dlg.getShell());
                            BackupPlugin.getDefault().logError(
                                    "error while initializing storage interceptor '" + desc.getName() + "'", t); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    });
                    storageInterceptors.add(interceptor);
                }

                if (!ok[0]) {
                    monitor.done();
                    throw new InterruptedException();
                }

                Cursor<BackupsRecord> cursor = null;
                try {
                    database.open(storageInterceptors);
                    database.initialize();

                    cursor = database.factory().selectFrom(Tables.BACKUPS)
                            .where(Tables.BACKUPS.NUM_ENTRIES.isNotNull()).orderBy(Tables.BACKUPS.RUN_TIME.desc())
                            .fetchLazy();
                    while (cursor.hasNext()) {
                        BackupsRecord record = cursor.fetchOne();
                        Backup backup = new Backup(record.getId().intValue(),
                                new Date(record.getRunTime().getTime()), record.getNumEntries().intValue());
                        backups.add(backup);
                    }
                } catch (SQLException | IOException e) {
                    boolean handled = false;
                    for (IStorageInterceptor interceptor : storageInterceptors) {
                        if (interceptor.showErrorMessage(e, dlg.getShell())) {
                            handled = true;
                        }
                    }
                    if (handled) {
                        throw new InterruptedException();
                    }
                    throw new InvocationTargetException(e);
                } finally {
                    database.closeQuietly(cursor);
                    monitor.done();
                }
            }
        };
        try {
            dlg.run(true, false, runnable);
        } catch (InvocationTargetException e) {
            // TODO
            BackupPlugin.getDefault().logError("Error while opening backup database", e); //$NON-NLS-1$
        } catch (InterruptedException e) {
            return Window.CANCEL;
        }

        return super.open();
    }

    @Override
    public boolean close() {
        IRunnableWithProgress runnable = new IRunnableWithProgress() {
            @Override
            public void run(IProgressMonitor monitor) {
                monitor.beginTask(Messages.Title_CloseBackupDatabase, IProgressMonitor.UNKNOWN);
                try {
                    database.close();
                    for (final IStorageInterceptor interceptor : storageInterceptors) {
                        SafeRunner.run(new ISafeRunnable() {
                            @Override
                            public void run() {
                                interceptor.destroy();
                            }

                            @Override
                            public void handleException(Throwable t) {
                                BackupPlugin.getDefault().logError("error while destroying storage interceptor", t); //$NON-NLS-1$
                            }
                        });
                    }
                } finally {
                    monitor.done();
                }
            }
        };
        ProgressMonitorDialog dlg = new ProgressMonitorDialog(getShell());
        try {
            dlg.run(true, false, runnable);
        } catch (InvocationTargetException e) {
            // TODO
            BackupPlugin.getDefault().logError("Error while closing backup database", e); //$NON-NLS-1$
        } catch (InterruptedException e) {
            // not cancelable
        }

        System.gc();

        return super.close();
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CLOSE_LABEL, false);
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite composite = (Composite) super.createDialogArea(parent);
        ((GridLayout) composite.getLayout()).numColumns = 2;
        ((GridLayout) composite.getLayout()).makeColumnsEqualWidth = false;

        Composite backupsAndSearchComposite = new Composite(composite, SWT.NONE);
        GridLayout layout = new GridLayout(2, false);
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        backupsAndSearchComposite.setLayout(layout);
        backupsAndSearchComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        Label label = new Label(backupsAndSearchComposite, SWT.NONE);
        label.setText(Messages.Label_ShowBackupContentsAt + ":"); //$NON-NLS-1$
        label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));

        backupsViewer = new ComboViewer(backupsAndSearchComposite);
        backupsViewer.getCombo().setVisibleItemCount(10);
        backupsViewer.getControl().setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));

        label = new Label(backupsAndSearchComposite, SWT.NONE);
        label.setText(Messages.Label_SearchFileFolder + ":"); //$NON-NLS-1$
        label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));

        searchText = new Text(backupsAndSearchComposite, SWT.BORDER);
        searchText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        new Label(composite, SWT.NONE);

        backupsViewer.setContentProvider(new ArrayContentProvider());
        backupsViewer.setLabelProvider(new BackupLabelProvider());
        backupsViewer.setSorter(new ViewerSorter() {
            @Override
            public int compare(Viewer viewer, Object e1, Object e2) {
                return ((Backup) e2).runTime.compareTo(((Backup) e1).runTime);
            }
        });
        backupsViewer.setInput(backups);

        entriesViewer = new TableViewer(composite,
                SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL);
        GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
        gd.widthHint = convertWidthInCharsToPixels(100);
        gd.heightHint = convertHeightInCharsToPixels(20);
        entriesViewer.getControl().setLayoutData(gd);
        entriesViewer.setContentProvider(new ArrayContentProvider());
        entriesViewer.setLabelProvider(new EntryLabelProvider(parent.getDisplay()));
        entriesViewer.setSorter(new EntrySorter());

        Table table = entriesViewer.getTable();
        TableLayout tableLayout = new TableLayout();
        table.setLayout(tableLayout);
        table.setHeaderVisible(true);
        table.setLinesVisible(false);

        TableColumn col = new TableColumn(table, SWT.LEFT);
        col.setText(Messages.Label_Name);
        tableLayout.addColumnData(new ColumnWeightData(50, true));
        col = new TableColumn(table, SWT.LEFT);
        col.setText(Messages.Label_Size);
        tableLayout.addColumnData(new ColumnWeightData(20, true));
        col = new TableColumn(table, SWT.LEFT);
        col.setText(Messages.Label_ModificationDate);
        tableLayout.addColumnData(new ColumnWeightData(30, true));

        Composite entriesButtonsComposite = new Composite(composite, SWT.NONE);
        layout = new GridLayout(1, false);
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        entriesButtonsComposite.setLayout(layout);
        entriesButtonsComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, true));

        moveUpButton = new Button(entriesButtonsComposite, SWT.PUSH);
        moveUpButton.setText(Messages.Button_MoveUp);
        moveUpButton.setEnabled(false);
        moveUpButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        final Button restoreButton = new Button(entriesButtonsComposite, SWT.PUSH);
        restoreButton.setText(Messages.Button_Restore + "..."); //$NON-NLS-1$
        restoreButton.setEnabled(false);
        restoreButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        currentFolderLink = new Link(composite, SWT.NONE);
        gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
        gd.horizontalSpan = 2;
        currentFolderLink.setLayoutData(gd);

        backupsViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent e) {
                try {
                    Backup backup = (Backup) ((IStructuredSelection) e.getSelection()).getFirstElement();
                    showBackup(backup);
                } catch (DataAccessException ex) {
                    BackupPlugin.getDefault().logError("error while showing backup", ex); //$NON-NLS-1$
                }
            }
        });

        searchText.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                if (listenForSearchText) {
                    startSearchTimer(searchText.getText());
                }
            }
        });

        entriesViewer.addOpenListener(new IOpenListener() {
            @Override
            public void open(OpenEvent e) {
                IStructuredSelection selection = (IStructuredSelection) e.getSelection();
                if (selection.size() == 1) {
                    Entry entry = (Entry) selection.getFirstElement();
                    if (entry.type == EntryType.FOLDER) {
                        listenForSearchText = false;
                        try {
                            searchText.setText(StringUtils.EMPTY);
                        } finally {
                            listenForSearchText = true;
                        }
                        try {
                            Backup backup = (Backup) ((IStructuredSelection) backupsViewer.getSelection())
                                    .getFirstElement();
                            showFolder(backup, entry);
                        } catch (DataAccessException ex) {
                            BackupPlugin.getDefault().logError("error while showing folder", ex); //$NON-NLS-1$
                        }
                    }
                }
            }
        });

        entriesViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent e) {
                restoreButton.setEnabled(!e.getSelection().isEmpty());
            }
        });

        moveUpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    moveUp();
                } catch (DataAccessException ex) {
                    BackupPlugin.getDefault().logError("error while moving up", ex); //$NON-NLS-1$
                }
            }
        });

        restoreButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                @SuppressWarnings("unchecked")
                List<Entry> entries = ((IStructuredSelection) entriesViewer.getSelection()).toList();
                restore(entries);
            }
        });

        currentFolderLink.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    Backup backup = (Backup) ((IStructuredSelection) backupsViewer.getSelection())
                            .getFirstElement();
                    int pos = e.text.indexOf('_');
                    int folderId = Integer.parseInt(e.text.substring(0, pos));
                    int parentFolderId = Integer.parseInt(e.text.substring(pos + 1));
                    showFolder(backup, folderId, parentFolderId);
                } catch (DataAccessException ex) {
                    BackupPlugin.getDefault().logError("error while showing folder", ex); //$NON-NLS-1$
                }
            }
        });

        if (!backups.isEmpty()) {
            backupsViewer.setSelection(new StructuredSelection(backups.get(0)), true);
        }

        return composite;
    }

    private void showBackup(Backup backup) {
        showEntries(backup, -1);
        moveUpButton.setEnabled(false);
    }

    private void showFolder(Backup backup, Entry entry) {
        showFolder(backup, entry.id, entry.parentId);
    }

    private void showFolder(Backup backup, int entryId, int parentFolderId) {
        showEntries(backup, entryId);
        moveUpButton.setEnabled(true);
        moveUpButton.setData((parentFolderId > 0) ? Integer.valueOf(parentFolderId) : backup);
    }

    private void moveUp() {
        Object data = moveUpButton.getData();
        if (data instanceof Backup) {
            showBackup((Backup) data);
        } else {
            int folderId = ((Integer) moveUpButton.getData()).intValue();
            Backup backup = (Backup) ((IStructuredSelection) backupsViewer.getSelection()).getFirstElement();
            try {
                Integer parentIdInt = database.factory().select(Tables.ENTRIES.PARENT_ID).from(Tables.ENTRIES)
                        .where(Tables.ENTRIES.ID.equal(Integer.valueOf(folderId)))
                        .fetchOne(Tables.ENTRIES.PARENT_ID);
                showEntries(backup, folderId);
                moveUpButton.setEnabled(true);
                moveUpButton.setData((parentIdInt != null) ? parentIdInt : backup);
            } catch (DataAccessException e) {
                BackupPlugin.getDefault().logError("error while getting parent ID", e); //$NON-NLS-1$
            }
        }
    }

    private void showEntries(Backup backup, int parentFolderId) {
        List<Entry> entries = Collections.emptyList();
        Cursor<Record> cursor = null;
        try {
            cursor = getEntriesCursor(backup.id, parentFolderId, null, -1);
            entries = getEntries(cursor, false);
        } catch (DataAccessException e) {
            BackupPlugin.getDefault().logError("error while loading entries", e); //$NON-NLS-1$
        } finally {
            database.closeQuietly(cursor);
        }

        ((EntryLabelProvider) entriesViewer.getLabelProvider()).setShowFullPath(false);
        ((EntrySorter) entriesViewer.getSorter()).setSortFullPath(false);
        entriesViewer.setInput(entries);
        entriesViewer.getControl().setData(Integer.valueOf(parentFolderId));
        listenForSearchText = false;
        try {
            searchText.setText(StringUtils.EMPTY);
        } finally {
            listenForSearchText = true;
        }

        String folder = getFolderLink(parentFolderId);
        currentFolderLink.setText(
                StringUtils.isNotBlank(folder) ? Messages.Label_CurrentFolder + ": " + getFolderLink(parentFolderId) //$NON-NLS-1$
                        : StringUtils.EMPTY);
    }

    private String getFolderLink(int folderId) {
        if (folderId <= 0) {
            return null;
        }

        Record record = database.factory().select(Tables.ENTRIES.NAME, Tables.ENTRIES.PARENT_ID)
                .from(Tables.ENTRIES).where(Tables.ENTRIES.ID.equal(Integer.valueOf(folderId))).fetchOne();
        String name = record.getValue(Tables.ENTRIES.NAME);
        Integer parentFolderId = record.getValue(Tables.ENTRIES.PARENT_ID);
        String parentFolder = getFolderLink((parentFolderId != null) ? parentFolderId.intValue() : -1);
        String part = "<a href=\"" + folderId + "_" + ((parentFolderId != null) ? parentFolderId.intValue() : -1) + //$NON-NLS-1$ //$NON-NLS-2$
                "\">" + name + "</a>"; //$NON-NLS-1$ //$NON-NLS-2$
        return (parentFolder != null) ? parentFolder + File.separator + part : part;
    }

    private String getFolderPath(int folderId) {
        if (folderId <= 0) {
            return null;
        }

        Record record = database.factory().select(Tables.ENTRIES.NAME, Tables.ENTRIES.PARENT_ID)
                .from(Tables.ENTRIES).where(Tables.ENTRIES.ID.equal(Integer.valueOf(folderId))).fetchOne();
        String name = record.getValue(Tables.ENTRIES.NAME);
        Integer parentFolderId = record.getValue(Tables.ENTRIES.PARENT_ID);
        String parentPath = getFolderPath((parentFolderId != null) ? parentFolderId.intValue() : -1);
        return (parentPath != null) ? parentPath + File.separator + name : name;
    }

    private Cursor<Record> getEntriesCursor(int backupId, int parentFolderId, String searchText, int entryId) {
        Condition searchCondition;
        if (entryId > 0) {
            searchCondition = Tables.ENTRIES.ID.equal(Integer.valueOf(entryId));
        } else if (StringUtils.isNotBlank(searchText)) {
            searchCondition = Tables.ENTRIES.NAME_LOWER.like("%" + searchText.toLowerCase() + "%"); //$NON-NLS-1$ //$NON-NLS-2$
        } else if (parentFolderId > 0) {
            searchCondition = Tables.ENTRIES.PARENT_ID.equal(Integer.valueOf(parentFolderId));
        } else {
            searchCondition = Tables.ENTRIES.PARENT_ID.isNull();
        }
        return database.factory()
                .select(Tables.ENTRIES.ID, Tables.ENTRIES.PARENT_ID, Tables.ENTRIES.NAME, Tables.ENTRIES.TYPE,
                        Tables.ENTRIES.CREATION_TIME, Tables.ENTRIES.MODIFICATION_TIME, Tables.ENTRIES.HIDDEN,
                        Tables.FILES.LENGTH, Tables.FILES.BACKUP_PATH, Tables.FILES.COMPRESSION)
                .from(Tables.ENTRIES).leftOuterJoin(Tables.FILES).on(Tables.FILES.ID.equal(Tables.ENTRIES.FILE_ID))
                .where(Tables.ENTRIES.BACKUP_ID.equal(Integer.valueOf(backupId)), searchCondition)
                .orderBy(Tables.ENTRIES.NAME.asc()).fetchLazy();
    }

    private List<Entry> getEntries(Cursor<Record> cursor, boolean fullPaths) {
        List<Entry> entries = new ArrayList<>();
        while (cursor.hasNext()) {
            Record record = cursor.fetchOne();
            Entry entry = toEntry(record, fullPaths);
            entries.add(entry);
        }
        return entries;
    }

    private Entry toEntry(Record record, boolean fullPaths) {
        int id = record.getValue(Tables.ENTRIES.ID).intValue();
        Integer parentIdInt = record.getValue(Tables.ENTRIES.PARENT_ID);
        int parentId = (parentIdInt != null) ? parentIdInt.intValue() : -1;
        String name = record.getValue(Tables.ENTRIES.NAME);
        EntryType type = EntryType.fromValue(record.getValue(Tables.ENTRIES.TYPE).intValue());
        Timestamp createTime = record.getValue(Tables.ENTRIES.CREATION_TIME);
        Date creationTime = (createTime != null) ? new Date(createTime.getTime()) : null;
        Timestamp modTime = record.getValue(Tables.ENTRIES.MODIFICATION_TIME);
        Date modificationTime = (modTime != null) ? new Date(modTime.getTime()) : null;
        boolean hidden = record.getValue(Tables.ENTRIES.HIDDEN).booleanValue();
        Long lengthLong = record.getValue(Tables.FILES.LENGTH);
        long length = (lengthLong != null) ? lengthLong.longValue() : -1;
        String backupPath = record.getValue(Tables.FILES.BACKUP_PATH);
        Byte compressionByte = record.getValue(Tables.FILES.COMPRESSION);
        Compression compression = (compressionByte != null) ? Compression.fromValue(compressionByte.intValue())
                : null;
        Entry entry = new Entry(id, parentId, name, type, creationTime, modificationTime, hidden, length,
                backupPath, compression);
        if (fullPaths) {
            entry.fullPath = getFolderPath(parentId);
        }
        return entry;
    }

    private Entry findInOlderBackups(Entry entry) throws IOException {
        Date runTime = new Date(database.factory().select(Tables.BACKUPS.RUN_TIME).from(Tables.ENTRIES)
                .join(Tables.BACKUPS).on(Tables.BACKUPS.ID.equal(Tables.ENTRIES.BACKUP_ID))
                .where(Tables.ENTRIES.ID.equal(Integer.valueOf(entry.id))).fetchOne()
                .getValue(Tables.BACKUPS.RUN_TIME).getTime());
        Cursor<Record> cursor = database.factory().select(Tables.BACKUPS.ID).from(Tables.BACKUPS)
                .where(Tables.BACKUPS.RUN_TIME.lessThan(new Timestamp(runTime.getTime())))
                .orderBy(Tables.BACKUPS.RUN_TIME.desc()).fetchLazy();
        try {
            while (cursor.hasNext()) {
                Record record = cursor.fetchOne();
                int backupId = record.getValue(Tables.BACKUPS.ID).intValue();
                int oldEntryId = findInBackup(entry.id, backupId);
                if (oldEntryId > 0) {
                    Cursor<Record> entriesCursor = getEntriesCursor(backupId, -1, null, oldEntryId);
                    try {
                        List<Entry> entries = getEntries(entriesCursor, false);
                        if (!entries.isEmpty()) {
                            Entry oldEntry = entries.get(0);
                            if (oldEntry.type == EntryType.FILE) {
                                return oldEntry;
                            }
                        }
                    } finally {
                        database.closeQuietly(entriesCursor);
                    }
                }
            }
        } finally {
            database.closeQuietly(cursor);
        }

        return null;
    }

    private int findInBackup(int entryId, int backupId) throws IOException {
        IFileOrFolderEntry entry = toFileOrFolderEntry(entryId);
        return Utils.findFileOrFolderEntryInBackup(entry, backupId, database);
    }

    private Utils.IFileOrFolderEntry toFileOrFolderEntry(final int entryId) {
        Record record = database.factory()
                .select(Tables.ENTRIES.NAME, Tables.ENTRIES.PARENT_ID, Tables.ENTRIES.TYPE).from(Tables.ENTRIES)
                .where(Tables.ENTRIES.ID.equal(Integer.valueOf(entryId))).fetchOne();
        final String name = record.getValue(Tables.ENTRIES.NAME);
        final Integer parentId = record.getValue(Tables.ENTRIES.PARENT_ID);
        final EntryType type = EntryType.fromValue(record.getValue(Tables.ENTRIES.TYPE).intValue());
        return new Utils.IFileOrFolderEntry() {
            @Override
            public boolean isFolder() {
                return type == EntryType.FOLDER;
            }

            @Override
            public IFileOrFolderEntry getParentFolder() {
                return (parentId != null) ? toFileOrFolderEntry(parentId.intValue()) : null;
            }

            @Override
            public String getName() {
                return name;
            }

            @Override
            public String getAbsolutePath() {
                return RestoreDialog.this.getAbsolutePath(entryId);
            }
        };
    }

    private String getAbsolutePath(int entryId) {
        Record record = database.factory().select(Tables.ENTRIES.NAME, Tables.ENTRIES.PARENT_ID)
                .from(Tables.ENTRIES).where(Tables.ENTRIES.ID.equal(Integer.valueOf(entryId))).fetchOne();
        String name = record.getValue(Tables.ENTRIES.NAME);
        Integer parentId = record.getValue(Tables.ENTRIES.PARENT_ID);
        return (parentId != null) ? getAbsolutePath(parentId.intValue()) + File.separator + name : name;
    }

    private void restore(final Collection<Entry> entries) {
        String folder = null;
        for (;;) {
            DirectoryDialog dlg = new DirectoryDialog(getShell(), SWT.SAVE);
            dlg.setText(Messages.Title_SelectOutputFolder);
            dlg.setFilterPath(folder);
            folder = dlg.open();
            if (folder == null) {
                break;
            }

            if (new File(folder).list().length > 0) {
                MessageDialog.openError(getShell(), Messages.Title_FolderNotEmpty, NLS.bind(Messages.FolderNotEmpty,
                        Utils.getSimpleName(new FileSystemFileOrFolder(new File(folder)))));
                continue;
            }

            break;
        }

        if (folder != null) {
            alwaysRestoreFromOlderBackups = null;

            final String myFolder = folder;
            Backup backup = (Backup) ((IStructuredSelection) backupsViewer.getSelection()).getFirstElement();
            final int backupId = backup.id;
            final int numEntries = backup.numEntries;
            final ProgressMonitorDialog dlg = new ProgressMonitorDialog(getShell());
            IRunnableWithProgress runnable = new IRunnableWithProgress() {
                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {

                    try {
                        monitor.beginTask(Messages.Title_RestoreFromBackup, numEntries);
                        for (Entry entry : entries) {
                            restoreEntry(entry, new File(myFolder), settings.getOutputFolder(), backupId, monitor,
                                    dlg.getShell());
                        }
                    } catch (IOException e) {
                        throw new InvocationTargetException(e);
                    } finally {
                        monitor.done();
                    }
                }
            };
            try {
                dlg.run(true, true, runnable);
            } catch (InvocationTargetException e) {
                // TODO
                BackupPlugin.getDefault().logError("error while restoring from backup", e.getCause()); //$NON-NLS-1$
            } catch (InterruptedException e) {
                // okay
            }
        }
    }

    private void restoreEntry(Entry entry, File parentFolder, String outputFolder, int backupId,
            IProgressMonitor monitor, Shell shell) throws IOException, InterruptedException {

        if (monitor.isCanceled()) {
            throw new InterruptedException();
        }

        boolean isFolder = entry.type == EntryType.FOLDER;

        if (entry.type == EntryType.FAILED_FILE) {
            if (alwaysRestoreFromOlderBackups == null) {
                alwaysRestoreFromOlderBackups = Boolean.valueOf(promptRestoreFromOlderBackups(shell));
            }
            if (alwaysRestoreFromOlderBackups.booleanValue()) {
                entry = findInOlderBackups(entry);
            }
        }

        if ((entry != null) && (entry.type != EntryType.FAILED_FILE)) {
            Path outputPath;
            if (entry.type == EntryType.FOLDER) {
                File newFolder = new File(parentFolder, escapeFileName(entry.name));
                FileUtils.forceMkdir(newFolder);

                Cursor<Record> cursor = getEntriesCursor(backupId, entry.id, null, -1);
                try {
                    for (Entry e : getEntries(cursor, false)) {
                        restoreEntry(e, newFolder, outputFolder, backupId, monitor, shell);
                    }
                } finally {
                    database.closeQuietly(cursor);
                }

                outputPath = newFolder.toPath();
            } else {
                File inputFile = Utils.toBackupFile(entry.backupPath, outputFolder);
                File outputFile = new File(parentFolder, escapeFileName(entry.name));
                outputPath = outputFile.toPath();
                InputStream in = null;
                try {
                    InputStream fileIn = new BufferedInputStream(new FileInputStream(inputFile));
                    InputStream interceptIn = fileIn;
                    for (IStorageInterceptor interceptor : storageInterceptors) {
                        interceptIn = interceptor.interceptInputStream(interceptIn, entry.length);
                    }
                    InputStream compressIn = entry.compression.getInputStream(interceptIn);
                    in = compressIn;
                    Files.copy(in, outputPath);
                } finally {
                    IOUtils.closeQuietly(in);
                }
            }

            FileAttributes fileAttributes = FileAttributes.get(outputPath);
            if (entry.hidden) {
                fileAttributes.setHidden(entry.hidden);
            }
            FileTime createTime = (entry.creationTime != null) ? FileTime.fromMillis(entry.creationTime.getTime())
                    : null;
            FileTime modTime = (entry.modificationTime != null)
                    ? FileTime.fromMillis(entry.modificationTime.getTime())
                    : null;
            fileAttributes.setTimes(createTime, modTime);
        }

        if (!isFolder) {
            monitor.worked(1);
        }
    }

    private boolean promptRestoreFromOlderBackups(final Shell shell) {
        Display display = shell.getDisplay();
        final boolean[] result = new boolean[1];
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                if (!shell.isDisposed()) {
                    result[0] = MessageDialog.openQuestion(shell, Messages.Title_FailedFiles,
                            Messages.RestoreFailedFilesFromOlderBackups);
                }
            }
        });
        return result[0];
    }

    private String escapeFileName(String name) {
        return name.replace('/', '_').replace('\\', '_').replace(':', '_').replace(';', '_').replaceAll("__", "_"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    private void startSearchTimer(final String text) {
        synchronized (this) {
            if (searchTimerTask != null) {
                searchTimerTask.cancel();
            }

            searchTimerTask = new TimerTask() {
                @Override
                public void run() {
                    if (StringUtils.isNotBlank(text)) {
                        runSearchAsync(text);
                    } else {
                        final Shell shell = getShell();
                        final Display display = shell.getDisplay();
                        if (!display.isDisposed()) {
                            display.asyncExec(new Runnable() {
                                @Override
                                public void run() {
                                    if (!display.isDisposed() && !shell.isDisposed()) {
                                        Backup backup = (Backup) ((IStructuredSelection) backupsViewer
                                                .getSelection()).getFirstElement();
                                        try {
                                            showBackup(backup);
                                        } catch (DataAccessException e) {
                                            BackupPlugin.getDefault().logError("error while showing backup", e); //$NON-NLS-1$
                                        }
                                    }
                                }
                            });
                        }
                    }
                }
            };
            timer.schedule(searchTimerTask, 1000L);
        }
    }

    private void runSearchAsync(String text) {
        final Shell shell = getShell();
        final Display display = shell.getDisplay();
        final org.eclipse.swt.graphics.Cursor[] oldCursor = new org.eclipse.swt.graphics.Cursor[1];
        final int[] backupId = { -1 };
        if (!display.isDisposed()) {
            display.syncExec(new Runnable() {
                @Override
                public void run() {
                    if (!display.isDisposed() && !shell.isDisposed()) {
                        oldCursor[0] = shell.getCursor();
                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
                        shell.setEnabled(false);
                        Backup backup = (Backup) ((IStructuredSelection) backupsViewer.getSelection())
                                .getFirstElement();
                        if (backup != null) {
                            backupId[0] = backup.id;
                        }
                    }
                }
            });
        }
        Cursor<Record> cursor = null;
        try {
            if (backupId[0] > 0) {
                cursor = getEntriesCursor(backupId[0], -1, text, -1);
                final List<Entry> entries = getEntries(cursor, true);
                if (!display.isDisposed()) {
                    display.syncExec(new Runnable() {
                        @Override
                        public void run() {
                            if (!display.isDisposed() && !shell.isDisposed()) {
                                ((EntryLabelProvider) entriesViewer.getLabelProvider()).setShowFullPath(true);
                                ((EntrySorter) entriesViewer.getSorter()).setSortFullPath(true);
                                entriesViewer.setInput(entries);
                                currentFolderLink.setText(StringUtils.EMPTY);
                                moveUpButton.setEnabled(false);
                            }
                        }
                    });
                }
            }
        } catch (DataAccessException e) {
            BackupPlugin.getDefault().logError("error while searching for entries", e); //$NON-NLS-1$
        } finally {
            database.closeQuietly(cursor);

            if (!display.isDisposed()) {
                display.syncExec(new Runnable() {
                    @Override
                    public void run() {
                        if (!display.isDisposed() && !shell.isDisposed()) {
                            shell.setEnabled(true);
                            shell.setCursor(oldCursor[0]);
                        }
                    }
                });
            }
        }
    }
}