// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2006 by R. Pito Salas // // 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: Backups.java,v 1.7 2006/05/30 10:31:15 spyromus Exp $ // package com.salas.bb.core; import com.salas.bb.domain.*; import com.salas.bb.utils.opml.Converter; import com.salas.bb.utils.i18n.Strings; import com.salas.bbutilities.opml.export.Exporter; import java.io.File; import java.io.IOException; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.util.*; import java.text.SimpleDateFormat; import org.jdom.output.XMLOutputter; import org.jdom.Document; /** * <p>Backups manager, which saves the OPML backup on demand and controls the population of * backups in the working directory.</p> * * <p>The backups directory and number of last backups to keep are input parameters.</p> * * <p>The name of backup file is controlled with <code>FILENAME_FORMAT</code> property. * At the present moment the names will look like: <code>~2005-05-31_142501.opml</code> * which corresponds to: <code>31 May 2005, 14:25:01</code>.</p> * * <p>The manager utilitizes the same functionality as Synchronization module, meaning * that the contents of synchronization and backup OPML are completely identical.</p> */ public final class Backups { private static final SimpleDateFormat FILENAME_FORMAT = new SimpleDateFormat("'~'yyyy-MM-dd_HHmmss'.opml'"); private static final String FILENAME_PATTERN = "^~[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{6}\\.opml$"; /** * The title of the main OPML outline. */ private static final String OPML_TITLE = "BlogBridge Backup"; private final int lastBackupsToKeep; private final File backupsDir; /** * Initializes backups saver with directory and limit value. * * @param aBackupsDir directory to save backups to. If it is not present yet * it will be created during saving. * @param aLastBackupsToKeep number of backup files to keep (greater than 0). The old * backup files will be removed. * * @throws NullPointerException if backups directory isn't told. * @throws IllegalArgumentException if last backups to keep is not positive value. */ public Backups(File aBackupsDir, int aLastBackupsToKeep) { if (aBackupsDir == null) throw new NullPointerException(Strings.error("backup.directory.is.unspecified")); if (aLastBackupsToKeep <= 0) throw new IllegalArgumentException(Strings.error("backup.non.positive.limit")); backupsDir = aBackupsDir; lastBackupsToKeep = aLastBackupsToKeep; } /** * Stores the guides set as OPML in backups folder. If backups folder doesn't exist, * it will be created. If this backup will overjump the limitation of backups to keep, * the last backups will be removed. Empty guides sets are also allowed. * * @param set guides set to store. * * @throws NullPointerException if the set isn't specified. */ public void saveBackup(GuidesSet set) throws IOException { initBackupsDir(); storeSet(set); rotateBackups(); } /** * Creates backup directory if the last doesn't exist yet. * * @throws IOException if creation has failed. */ private void initBackupsDir() throws IOException { if (!backupsDir.exists()) { if (!backupsDir.mkdir()) throw new IOException(Strings.error("backup.failed.to.create.backup.directory")); } } /** * Exports the set to OPML and writes to backup file, which name is created from * current data and time. * * @param set set to export. * * @throws IOException if output operation fails. */ private void storeSet(GuidesSet set) throws IOException { com.salas.bbutilities.opml.export.Exporter exporter = new Exporter(true); Document doc = exporter.export(Converter.convertToOPML(set, OPML_TITLE)); writeBackupToFile(doc, new File(backupsDir, createBackupFileName())); } /** * Writes backup OPML data to the file. * * @param aBackupOPMLDocument exported OPML document. * @param aBackupFile backup file. * * @throws IOException if output operation fails. */ private static void writeBackupToFile(Document aBackupOPMLDocument, File aBackupFile) throws IOException { FileOutputStream fos = new FileOutputStream(aBackupFile); XMLOutputter xo = new XMLOutputter(); xo.output(aBackupOPMLDocument, fos); fos.close(); } /** * Creates name of the backup file from current date and time. * * @return backup file name. */ private String createBackupFileName() { return FILENAME_FORMAT.format(new Date()); } /** * Analyzes the list of backup files and removes oldest which are overjumping the * specified number of backups to keep. */ private void rotateBackups() { File[] backupFiles = backupsDir.listFiles(new BackupFilenameFilter()); Collection filesToRemove = chooseBackupsToRemove(backupFiles); Iterator it = filesToRemove.iterator(); while (it.hasNext()) { File backupFile = (File)it.next(); backupFile.delete(); } } /** * Selects the files from the list which have jumped over the limitation. * * @param aBackupFiles backup files. * * @return collection of files to remove. */ private Collection chooseBackupsToRemove(File[] aBackupFiles) { List backupsToRemove = new ArrayList(); if (aBackupFiles.length > lastBackupsToKeep) { SortedSet sortedBackupFiles = new TreeSet(new FilesLastModTimeComparator()); sortedBackupFiles.addAll(Arrays.asList(aBackupFiles)); int toRemove = aBackupFiles.length - lastBackupsToKeep; Iterator it = sortedBackupFiles.iterator(); while (toRemove > 0 && it.hasNext()) { Object backupFile = it.next(); backupsToRemove.add(backupFile); toRemove--; } } return backupsToRemove; } /** * Compares files by their modification times. */ private static class FilesLastModTimeComparator implements Comparator { public int compare(Object o1, Object o2) { long modTime1 = ((File)o1).lastModified(); long modTime2 = ((File)o2).lastModified(); return modTime1 > modTime2 ? 1 : modTime1 < modTime2 ? -1 : 0; } } /** * Filter for backup files. */ private static class BackupFilenameFilter implements FilenameFilter { public boolean accept(File dir, String name) { return name != null && name.matches(FILENAME_PATTERN); } } }