/* 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.settings; import java.io.File; import java.text.DateFormat; import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ListViewer; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.FileTransfer; import org.eclipse.swt.dnd.Transfer; 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.DateTime; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Spinner; import org.eclipse.swt.widgets.Text; import de.blizzy.backup.BackupApplication; import de.blizzy.backup.BackupPlugin; import de.blizzy.backup.Messages; import de.blizzy.backup.Utils; import de.blizzy.backup.vfs.ILocation; import de.blizzy.backup.vfs.ILocationProvider; import de.blizzy.backup.vfs.LocationProviderDescriptor; import de.blizzy.backup.vfs.filesystem.FileSystemFileOrFolder; import de.blizzy.backup.vfs.filesystem.FileSystemLocationProvider; public class SettingsDialog extends Dialog { private ListViewer foldersViewer; private Text outputFolderText; private Button runHourlyRadio; private DateTime dailyTime; private Button fileCompareMetadataRadio; private Button fileCompareChecksumRadio; private Label scheduleExplanationLabel; private Button maxAgeDaysRadio; private Spinner maxAgeDaysSpinner; private Spinner maxDiskFillRateSpinner; public SettingsDialog(Shell parentShell) { super(parentShell); } @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); newShell.setImages(BackupApplication.getWindowImages()); newShell.setText(Messages.Title_Settings); } @Override protected boolean isResizable() { return true; } @Override protected Control createDialogArea(Composite parent) { Settings settings = BackupApplication.getSettingsManager().getSettings(); Composite composite = (Composite) super.createDialogArea(parent); ((GridLayout) composite.getLayout()).numColumns = 1; ((GridLayout) composite.getLayout()).verticalSpacing = 10; Group foldersComposite = new Group(composite, SWT.NONE); foldersComposite.setText(Messages.Title_FoldersToBackup); foldersComposite.setLayout(new GridLayout(2, false)); foldersComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); foldersViewer = new ListViewer(foldersComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); foldersViewer.setContentProvider(new ArrayContentProvider()); foldersViewer.setLabelProvider(new FoldersLabelProvider()); foldersViewer.setSorter(new ViewerSorter() { @Override public int compare(Viewer viewer, Object e1, Object e2) { return ((ILocation) e1).getDisplayName().compareToIgnoreCase(((ILocation) e2).getDisplayName()); } }); GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); gd.widthHint = convertWidthInCharsToPixels(60); gd.heightHint = convertHeightInCharsToPixels(10); foldersViewer.getControl().setLayoutData(gd); foldersViewer.setInput(new HashSet<>(settings.getLocations())); Composite folderButtonsComposite = new Composite(foldersComposite, SWT.NONE); GridLayout layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; folderButtonsComposite.setLayout(layout); folderButtonsComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); for (final LocationProviderDescriptor descriptor : BackupPlugin.getDefault().getLocationProviders()) { Button button = new Button(folderButtonsComposite, SWT.PUSH); button.setText(NLS.bind(Messages.Button_AddX, descriptor.getName())); button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { addFolder(descriptor.getLocationProvider()); } }); } final Button removeFolderButton = new Button(folderButtonsComposite, SWT.PUSH); removeFolderButton.setText(Messages.Button_Remove); removeFolderButton.setEnabled(false); removeFolderButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Label label = new Label(foldersComposite, SWT.NONE); label.setText(Messages.DropFoldersHelp); gd = new GridData(SWT.LEFT, SWT.CENTER, false, false); gd.horizontalSpan = 2; label.setLayoutData(gd); Group outputFolderComposite = new Group(composite, SWT.NONE); outputFolderComposite.setText(Messages.Title_OutputFolder); outputFolderComposite.setLayout(new GridLayout(3, false)); outputFolderComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); label = new Label(outputFolderComposite, SWT.NONE); label.setText(Messages.Label_BackupOutputFolder + ":"); //$NON-NLS-1$ label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); outputFolderText = new Text(outputFolderComposite, SWT.BORDER | SWT.READ_ONLY); outputFolderText.setText(StringUtils.defaultString(settings.getOutputFolder())); outputFolderText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Button browseOutputFolderButton = new Button(outputFolderComposite, SWT.PUSH); browseOutputFolderButton.setText(Messages.Button_Browse + "..."); //$NON-NLS-1$ browseOutputFolderButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); Group scheduleComposite = new Group(composite, SWT.NONE); scheduleComposite.setText(Messages.Title_WhenToBackup); scheduleComposite.setLayout(new GridLayout(2, false)); scheduleComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); runHourlyRadio = new Button(scheduleComposite, SWT.RADIO); runHourlyRadio.setText(Messages.Label_RunHourly); gd = new GridData(SWT.LEFT, SWT.CENTER, false, false); gd.horizontalSpan = 2; runHourlyRadio.setLayoutData(gd); final Button runDailyRadio = new Button(scheduleComposite, SWT.RADIO); runDailyRadio.setText(Messages.Label_RunDaily + ":"); //$NON-NLS-1$ runDailyRadio.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); dailyTime = new DateTime(scheduleComposite, SWT.TIME | SWT.SHORT); runHourlyRadio.setSelection(settings.isRunHourly()); runDailyRadio.setSelection(!settings.isRunHourly()); dailyTime.setHours(settings.getDailyHours()); dailyTime.setMinutes(settings.getDailyMinutes()); dailyTime.setEnabled(!settings.isRunHourly()); Group fileComparisonComposite = new Group(composite, SWT.NONE); fileComparisonComposite.setText(Messages.Title_FileComparison); fileComparisonComposite.setLayout(new GridLayout(1, false)); fileComparisonComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); fileCompareMetadataRadio = new Button(fileComparisonComposite, SWT.RADIO); fileCompareMetadataRadio.setText(Messages.CompareFilesMetadata); fileCompareMetadataRadio.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); fileCompareChecksumRadio = new Button(fileComparisonComposite, SWT.RADIO); fileCompareChecksumRadio.setText(Messages.CompareFilesChecksum); fileCompareChecksumRadio.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); fileCompareMetadataRadio.setSelection(!settings.isUseChecksums()); fileCompareChecksumRadio.setSelection(settings.isUseChecksums()); Group maxAgeComposite = new Group(composite, SWT.NONE); maxAgeComposite.setText(Messages.Title_MaximumBackupAge); maxAgeComposite.setLayout(new GridLayout(2, false)); maxAgeComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); Button maxAgeUnlimitedRadio = new Button(maxAgeComposite, SWT.RADIO); maxAgeUnlimitedRadio.setText(Messages.Label_KeepAll); gd = new GridData(SWT.LEFT, SWT.CENTER, false, false); gd.horizontalSpan = 2; maxAgeUnlimitedRadio.setLayoutData(gd); maxAgeDaysRadio = new Button(maxAgeComposite, SWT.RADIO); maxAgeDaysRadio.setText(Messages.Label_DeleteAfterDays + ":"); //$NON-NLS-1$ maxAgeDaysRadio.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); maxAgeDaysSpinner = new Spinner(maxAgeComposite, SWT.BORDER); maxAgeDaysSpinner.setMinimum(14); maxAgeDaysSpinner.setMaximum(365); maxAgeUnlimitedRadio.setSelection(settings.getMaxAgeDays() < 0); maxAgeDaysRadio.setSelection(settings.getMaxAgeDays() > 0); maxAgeDaysSpinner.setEnabled(settings.getMaxAgeDays() > 0); maxAgeDaysSpinner.setSelection(settings.getMaxAgeDays() > 0 ? settings.getMaxAgeDays() : 90); Group maxDiskFillRateComposite = new Group(composite, SWT.NONE); maxDiskFillRateComposite.setText(Messages.Title_MaximumDiskFillRate); maxDiskFillRateComposite.setLayout(new GridLayout(3, false)); maxDiskFillRateComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); label = new Label(maxDiskFillRateComposite, SWT.NONE); label.setText(Messages.Label_DiskFillRate + ":"); //$NON-NLS-1$ label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); maxDiskFillRateSpinner = new Spinner(maxDiskFillRateComposite, SWT.BORDER); maxDiskFillRateSpinner.setMinimum(5); maxDiskFillRateSpinner.setMaximum(95); maxDiskFillRateSpinner.setSelection(settings.getMaxDiskFillRate()); label = new Label(maxDiskFillRateComposite, SWT.NONE); label.setText("%"); //$NON-NLS-1$ label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); Group scheduleExplanationComposite = new Group(composite, SWT.NONE); scheduleExplanationComposite.setText(Messages.Title_ScheduleExplanation); scheduleExplanationComposite.setLayout(new GridLayout(1, false)); scheduleExplanationComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); scheduleExplanationLabel = new Label(scheduleExplanationComposite, SWT.NONE); scheduleExplanationLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); updateExplanationLabel(); foldersViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent e) { removeFolderButton.setEnabled(!e.getSelection().isEmpty()); } }); removeFolderButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { removeFolder(); } }); browseOutputFolderButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { browseOutputFolder(); } }); runDailyRadio.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { dailyTime.setEnabled(runDailyRadio.getSelection()); updateExplanationLabel(); } }); dailyTime.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateExplanationLabel(); } }); fileCompareChecksumRadio.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { showWarnings(fileCompareChecksumRadio); } }); maxAgeDaysRadio.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { maxAgeDaysSpinner.setEnabled(maxAgeDaysRadio.getSelection()); updateExplanationLabel(); } }); maxAgeDaysSpinner.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { maxAgeDaysSpinner.setEnabled(maxAgeDaysRadio.getSelection()); updateExplanationLabel(); } }); maxDiskFillRateSpinner.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateExplanationLabel(); } }); DropTarget dropTarget = new DropTarget(foldersViewer.getControl(), DND.DROP_LINK); dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() }); dropTarget.addDropListener(new DropTargetListener() { @Override public void dragEnter(DropTargetEvent event) { if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) { event.detail = DND.DROP_LINK; event.feedback = DND.FEEDBACK_SCROLL; } else { event.detail = DND.DROP_NONE; } } @Override public void dragLeave(DropTargetEvent event) { } @Override public void dragOver(DropTargetEvent event) { if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) { event.detail = DND.DROP_LINK; event.feedback = DND.FEEDBACK_SCROLL; } else { event.detail = DND.DROP_NONE; } } @Override public void dropAccept(DropTargetEvent event) { if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) { event.detail = DND.DROP_LINK; event.feedback = DND.FEEDBACK_SCROLL; } else { event.detail = DND.DROP_NONE; } } @Override public void drop(DropTargetEvent event) { if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) { if (event.data != null) { for (String file : (String[]) event.data) { if (new File(file).isDirectory()) { addFolder(FileSystemLocationProvider.location(Utils.toCanonicalFile(new File(file)))); } } } else { event.detail = DND.DROP_NONE; } } else { event.detail = DND.DROP_NONE; } } @Override public void dragOperationChanged(DropTargetEvent event) { if (FileTransfer.getInstance().isSupportedType(event.currentDataType)) { event.detail = DND.DROP_LINK; event.feedback = DND.FEEDBACK_SCROLL; } else { event.detail = DND.DROP_NONE; } } }); return composite; } private void showWarnings(Control selectedControl) { if ((selectedControl == fileCompareChecksumRadio) && fileCompareChecksumRadio.getSelection()) { MessageDialog.openInformation(getShell(), Messages.Title_Information, Messages.Warning_CompareChecksum); } } private void addFolder(ILocationProvider provider) { ILocation newLocation = provider.promptLocation(getShell()); if (newLocation != null) { addFolder(newLocation); } } private void addFolder(ILocation location) { @SuppressWarnings("unchecked") Set<ILocation> locations = (Set<ILocation>) foldersViewer.getInput(); // is the new folder a child of any folder in the backup? if so, display error message for (ILocation oldLocation : locations) { if (Utils.isParent(oldLocation.getRootFolder(), location.getRootFolder())) { MessageDialog.openError(getShell(), Messages.Title_FolderCannotBeAdded, NLS.bind(Messages.ParentFolderInBackup, Utils.getSimpleName(location.getRootFolder()))); return; } } // is the new folder the parent of the output folder? if so, display error message String outputFolder = StringUtils.defaultString(outputFolderText.getText()); if (StringUtils.isNotBlank(outputFolder) && Utils.isParent(location.getRootFolder(), new FileSystemFileOrFolder(new File(outputFolder)))) { MessageDialog.openError(getShell(), Messages.Title_FolderCannotBeAdded, NLS.bind(Messages.FolderIsParentOfBackupFolder, Utils.getSimpleName(location.getRootFolder()))); return; } // is the new folder the same as the output folder? if so, display error message if (StringUtils.isNotBlank(outputFolder) && location.getRootFolder().equals(new FileSystemFileOrFolder(new File(outputFolder)))) { MessageDialog.openError(getShell(), Messages.Title_FolderCannotBeAdded, NLS.bind(Messages.FolderIsOutputFolder, Utils.getSimpleName(location.getRootFolder()))); return; } // is the new folder a child of the output folder? if so, display error message if (StringUtils.isNotBlank(outputFolder) && Utils.isParent(new FileSystemFileOrFolder(new File(outputFolder)), location.getRootFolder())) { MessageDialog.openError(getShell(), Messages.Title_FolderCannotBeAdded, NLS.bind(Messages.FolderIsChildOfOutputFolder, Utils.getSimpleName(location.getRootFolder()))); return; } // is the new folder the parent of any folder in the backup? if so, remove those folders for (ILocation oldLocation : new HashSet<>(locations)) { if (Utils.isParent(location.getRootFolder(), oldLocation.getRootFolder())) { locations.remove(oldLocation); foldersViewer.remove(oldLocation); } } if (locations.add(location)) { foldersViewer.add(location); } } private void removeFolder() { @SuppressWarnings("unchecked") List<String> selectedFolders = ((IStructuredSelection) foldersViewer.getSelection()).toList(); @SuppressWarnings("unchecked") Set<ILocation> folders = (Set<ILocation>) foldersViewer.getInput(); folders.removeAll(selectedFolders); foldersViewer.remove(selectedFolders.toArray(new ILocation[0])); } private void browseOutputFolder() { @SuppressWarnings("unchecked") Set<ILocation> locations = (Set<ILocation>) foldersViewer.getInput(); String folder = outputFolderText.getText(); if (StringUtils.isEmpty(folder)) { folder = null; } dialogLoop: for (;;) { DirectoryDialog dlg = new DirectoryDialog(getShell(), SWT.SAVE); dlg.setText(Messages.Title_SelectOutputFolder); dlg.setFilterPath(folder); folder = dlg.open(); if (folder == null) { break; } if (Utils.isBackupFolder(folder)) { if (MessageDialog.openConfirm(getShell(), Messages.Title_ExistingBackup, NLS.bind(Messages.FolderContainsExistingBackup, Utils.getSimpleName(new FileSystemFileOrFolder(new File(folder)))))) { break; } else { continue; } } // does folder contain files? if so, display error message if (new File(folder).list().length > 0) { MessageDialog.openError(getShell(), Messages.Title_InvalidFolder, NLS.bind(Messages.FolderNotEmpty, Utils.getSimpleName(new FileSystemFileOrFolder(new File(folder))))); continue; } // display error message if: // - folder is the same as any folder in the backup // - folder is a child of any folder in the backup for (ILocation oldLocation : locations) { if (new FileSystemFileOrFolder(new File(folder)).equals(oldLocation.getRootFolder()) || Utils.isParent(oldLocation.getRootFolder(), new FileSystemFileOrFolder(new File(folder)))) { MessageDialog.openError(getShell(), Messages.Title_InvalidFolder, NLS.bind(Messages.OutputFolderIsInBackup, Utils.getSimpleName(new FileSystemFileOrFolder(new File(folder))))); continue dialogLoop; } } break; } if (folder != null) { outputFolderText.setText(folder); } } private void updateExplanationLabel() { DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT); Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, dailyTime.getHours()); c.set(Calendar.MINUTE, dailyTime.getMinutes()); scheduleExplanationLabel.setText( "- " + (runHourlyRadio.getSelection() ? //$NON-NLS-1$ Messages.ScheduleExplanation_HourlyBackups : NLS.bind(Messages.ScheduleExplanation_DailyBackups, timeFormat.format(c.getTime()))) + "\n" + //$NON-NLS-1$ (runHourlyRadio.getSelection() ? "- " + //$NON-NLS-1$ NLS.bind(Messages.ScheduleExplanation_HourlyBackupsKeepTime, Integer.valueOf(BackupPlugin.KEEP_HOURLIES_DAYS)) + "\n" : //$NON-NLS-1$ "") + //$NON-NLS-1$ "- " + //$NON-NLS-1$ NLS.bind(Messages.ScheduleExplanation_DailyBackupsKeepTime, Integer.valueOf(BackupPlugin.KEEP_DAILIES_DAYS)) + "\n" + //$NON-NLS-1$ "- " + (maxAgeDaysRadio.getSelection() ? //$NON-NLS-1$ NLS.bind(Messages.ScheduleExplanation_WeeklyBackupsKeepDays, Integer.valueOf(maxAgeDaysSpinner.getSelection())) : Messages.ScheduleExplanation_WeeklyBackupsNoAge) + "\n" + //$NON-NLS-1$ "- " + //$NON-NLS-1$ NLS.bind(Messages.ScheduleExplanation_WeeklyBackupsKeepDisk, Integer.valueOf(maxDiskFillRateSpinner.getSelection())) + // compensate for missing line (!runHourlyRadio.getSelection() ? "\n " : "")); //$NON-NLS-1$ //$NON-NLS-2$ } @Override protected void buttonPressed(int buttonId) { if (buttonId == IDialogConstants.OK_ID) { @SuppressWarnings("unchecked") Set<ILocation> folders = (Set<ILocation>) foldersViewer.getInput(); String outputFolder = outputFolderText.getText(); if (StringUtils.isBlank(outputFolder)) { outputFolder = null; } Settings settings = new Settings(folders, outputFolder, runHourlyRadio.getSelection(), dailyTime.getHours(), dailyTime.getMinutes(), fileCompareChecksumRadio.getSelection(), maxAgeDaysRadio.getSelection() ? maxAgeDaysSpinner.getSelection() : -1, maxDiskFillRateSpinner.getSelection()); BackupApplication.getSettingsManager().setSettings(settings); } super.buttonPressed(buttonId); } }