/******************************************************************************* * Copyright (c) 2004, 2013 Tasktop Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Tasktop Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.internal.tasks.ui; import java.io.File; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.SortedMap; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.mylyn.commons.net.Policy; import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; import org.eclipse.mylyn.internal.tasks.core.TaskActivityUtil; import org.eclipse.mylyn.internal.tasks.ui.util.TaskDataExportOperation; import org.eclipse.mylyn.internal.tasks.ui.util.TaskDataSnapshotOperation; import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.IProgressService; import com.ibm.icu.text.SimpleDateFormat; /** * @author Rob Elves */ public class TaskListBackupManager implements IPropertyChangeListener { private static final String OLD_MYLYN_2_BACKUP_FILE_PREFIX = "mylyndata-"; //$NON-NLS-1$ // Mylyn 3.0 Backup file name private static final String BACKUP_FILE_PREFIX = "mylyn-v3-data-"; //$NON-NLS-1$ private static final Pattern MYLYN_BACKUP_REGEXP = Pattern.compile("^(" + BACKUP_FILE_PREFIX + ")?(" //$NON-NLS-1$ //$NON-NLS-2$ + OLD_MYLYN_2_BACKUP_FILE_PREFIX + ")?"); //$NON-NLS-1$ private static final Pattern DATE_FORMAT_OLD = Pattern.compile("\\d{4}-\\d{2}-\\d{2}"); //$NON-NLS-1$ private static final Pattern DATE_FORMAT = Pattern.compile("\\d{4}-\\d{2}-\\d{2}-\\d{6}"); //$NON-NLS-1$ private static final long SECOND = 1000; private static final long MINUTE = 60 * SECOND; private static final long STANDARD_DELAY = 30 * MINUTE; private String backupFolderPath; private Job runBackup; private static boolean errorDisplayed = false; public TaskListBackupManager(String backupFolderPath) { this.backupFolderPath = backupFolderPath; start(STANDARD_DELAY); } public void start(long delay) { if (runBackup != null) { stop(); } runBackup = new Job("Task Data Snapshot") { //$NON-NLS-1$ @Override protected IStatus run(IProgressMonitor monitor) { try { if (TasksUiPlugin.getTaskList().getAllTasks().size() > 0) { backupNow(false, monitor); } return Status.OK_STATUS; } finally { if (PlatformUI.isWorkbenchRunning()) { schedule(STANDARD_DELAY); } } } }; runBackup.setPriority(Job.BUILD); runBackup.setSystem(true); runBackup.schedule(delay); } public void stop() { if (runBackup != null) { if (!runBackup.cancel()) { try { runBackup.join(); } catch (InterruptedException e) { // ignore } } runBackup = null; } } public static String getBackupFileName() { SimpleDateFormat format = new SimpleDateFormat(ITasksCoreConstants.FILENAME_TIMESTAMP_FORMAT, Locale.ENGLISH); String date = format.format(new Date()); String backupFileName = BACKUP_FILE_PREFIX + date + ".zip"; //$NON-NLS-1$ return backupFileName; } public void backupNow(boolean synchronous) { backupNow(synchronous, null); } public synchronized void backupNow(boolean synchronous, IProgressMonitor monitor) { monitor = Policy.monitorFor(monitor); File backupFolder = new File(backupFolderPath); if (!backupFolder.exists()) { backupFolder.mkdir(); } final TaskDataExportOperation backupJob = new TaskDataSnapshotOperation(backupFolderPath, getBackupFileName()); try { if (!synchronous) { backupJob.run(monitor); removeOldBackups(); } else { IProgressService service = PlatformUI.getWorkbench().getProgressService(); service.run(false, true, backupJob); } } catch (InterruptedException e) { } catch (Throwable e) { if (!errorDisplayed) { final Status status = new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, Messages.TaskListBackupManager_Error_occured_during_scheduled_tasklist_backup, e); errorDisplayed = true; if (Display.getCurrent() != null) { TasksUiInternal.logAndDisplayStatus(Messages.TaskListBackupManager_Scheduled_task_data_backup, status); } else { TasksUiInternal.asyncLogAndDisplayStatus(Messages.TaskListBackupManager_Scheduled_task_data_backup, status); } } // clean up corrupt backup file if (backupJob.getDestinationFile() != null) { backupJob.getDestinationFile().delete(); } } } public SortedMap<Long, File> getBackupFiles() { SortedMap<Long, File> filesMap = new TreeMap<Long, File>(); String destination = backupFolderPath; File backupFolder = new File(destination); if (!backupFolder.exists()) { return filesMap; } File[] files = backupFolder.listFiles(); if (files == null) { return filesMap; } for (File file : files) { Matcher matcher = MYLYN_BACKUP_REGEXP.matcher(file.getName()); if (matcher.find()) { Date date = null; try { SimpleDateFormat format = null; String dateText = null; Matcher dateFormatMatcher = DATE_FORMAT.matcher(file.getName()); if (dateFormatMatcher.find()) { format = new SimpleDateFormat(ITasksCoreConstants.FILENAME_TIMESTAMP_FORMAT, Locale.ENGLISH); dateText = dateFormatMatcher.group(); } else { dateFormatMatcher = DATE_FORMAT_OLD.matcher(file.getName()); if (dateFormatMatcher.find()) { format = new SimpleDateFormat(ITasksCoreConstants.OLD_FILENAME_TIMESTAMP_FORMAT, Locale.ENGLISH); dateText = dateFormatMatcher.group(); } } if (format != null && dateText != null && dateText.length() > 0) { date = format.parse(dateText); } else { continue; } } catch (IndexOutOfBoundsException e) { continue; } catch (ParseException e) { continue; } if (date != null && date.getTime() > 0) { filesMap.put(new Long(date.getTime()), file); } } } return filesMap; } /** public for testing purposes */ public synchronized void removeOldBackups() { SortedMap<Long, File> filesMap = getBackupFiles(); if (filesMap.size() > 0) { Calendar rangeStart = TaskActivityUtil.getCalendar(); rangeStart.setTimeInMillis(filesMap.lastKey()); TaskActivityUtil.snapStartOfHour(rangeStart); int startHour = rangeStart.get(Calendar.HOUR_OF_DAY); Calendar rangeEnd = TaskActivityUtil.getCalendar(); rangeEnd.setTimeInMillis(rangeStart.getTimeInMillis()); rangeEnd.add(Calendar.HOUR_OF_DAY, 1); // Keep one backup for last 8 hours of today for (int x = 0; x <= startHour && x < 9; x++) { SortedMap<Long, File> subMap = filesMap.subMap(rangeStart.getTimeInMillis(), rangeEnd.getTimeInMillis()); if (subMap.size() > 1) { while (subMap.size() > 1) { File toDelete = subMap.remove(subMap.firstKey()); toDelete.delete(); } } rangeStart.add(Calendar.HOUR_OF_DAY, -1); rangeEnd.add(Calendar.HOUR_OF_DAY, -1); } // Keep one backup a day for the past 12 days TaskActivityUtil.snapStartOfDay(rangeEnd); rangeStart.add(Calendar.DAY_OF_YEAR, -1); for (int x = 1; x <= 12; x++) { SortedMap<Long, File> subMap = filesMap.subMap(rangeStart.getTimeInMillis(), rangeEnd.getTimeInMillis()); if (subMap.size() > 1) { while (subMap.size() > 1) { File toDelete = subMap.remove(subMap.firstKey()); toDelete.delete(); } } rangeStart.add(Calendar.DAY_OF_YEAR, -1); rangeEnd.add(Calendar.DAY_OF_YEAR, -1); } // Remove all older backups SortedMap<Long, File> subMap = filesMap.subMap(0l, rangeStart.getTimeInMillis()); if (subMap.size() > 0) { while (subMap.size() > 0) { File toDelete = subMap.remove(subMap.firstKey()); toDelete.delete(); } } } } public void propertyChange(PropertyChangeEvent event) { if (event.getProperty().equals(ITasksUiPreferenceConstants.PREF_DATA_DIR)) { backupFolderPath = TasksUiPlugin.getDefault().getBackupFolderPath(); } } }