io.github.lucaseasedup.logit.backup.BackupManager.java Source code

Java tutorial

Introduction

Here is the source code for io.github.lucaseasedup.logit.backup.BackupManager.java

Source

/*
 * BackupManager.java
 *
 * Copyright (C) 2012-2014 LucasEasedUp
 *
 * 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 io.github.lucaseasedup.logit.backup;

import static io.github.lucaseasedup.logit.message.MessageHelper.t;
import io.github.lucaseasedup.logit.LogItCoreObject;
import io.github.lucaseasedup.logit.account.AccountManager;
import io.github.lucaseasedup.logit.common.ReportedException;
import io.github.lucaseasedup.logit.common.Timer;
import io.github.lucaseasedup.logit.config.TimeUnit;
import io.github.lucaseasedup.logit.storage.SqliteStorage;
import io.github.lucaseasedup.logit.storage.Storage;
import io.github.lucaseasedup.logit.storage.Storage.DataType;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import org.bukkit.scheduler.BukkitRunnable;

public final class BackupManager extends LogItCoreObject implements Runnable {
    /**
     * Creates a new {@code BackupManager}.
     * 
     * @param accountManager the {@code AccountManager} that this
     *                       {@code BackupManager} will operate on.
     * 
     * @throws IllegalArgumentException if {@code accountManager} is {@code null}.
     */
    public BackupManager(AccountManager accountManager) {
        if (accountManager == null)
            throw new IllegalArgumentException();

        timer = new Timer(TASK_PERIOD);
        timer.start();

        this.accountManager = accountManager;
    }

    @Override
    public void dispose() {
        timer = null;
        accountManager = null;
    }

    /**
     * Internal method. Do not call directly.
     */
    @Override
    public void run() {
        if (!getConfig("config.yml").getBoolean("backup.schedule.enabled"))
            return;

        timer.run();

        long interval = getConfig("config.yml").getTime("backup.schedule.interval", TimeUnit.TICKS);

        if (timer.getElapsed() >= interval) {
            createBackupAsynchronously();

            timer.reset();
        }
    }

    /**
     * Creates a new backup of all the accounts stored
     * in the underlying {@code AccountManager}.
     * 
     * <p> If this method was called with the {@code asynchronously}
     * parameter set to {@code true}, a {@code ReportedException}
     * will not be thrown even if an I/O error occurred.
     * 
     * @return the created backup file.
     * 
     * @throws ReportedException if an I/O error occurred,
     *                           and it was reported to the logger.
     */
    public File createBackup() {
        final File backupFile = allocateBackupFileForDate(new Date());

        log(Level.INFO, t("createBackup.creating"));

        try {
            exportAccounts(backupFile);

            log(Level.INFO, t("createBackup.success.log").replace("{0}", backupFile.getName()));
        } catch (IOException ex) {
            log(Level.WARNING, t("createBackup.fail.log"), ex);

            ReportedException.throwNew(ex);
        }

        return backupFile;
    }

    /**
     * Asynchronously creates a new backup of all the accounts stored
     * in the underlying {@code AccountManager}.
     * 
     * <p> If this method was called with the {@code asynchronously}
     * parameter set to {@code true}, a {@code ReportedException}
     * will not be thrown even if an I/O error occurred.
     * 
     * @return the created backup file.
     * 
     * @throws ReportedException if an I/O error occurred,
     *                           and it was reported to the logger.
     */
    public File createBackupAsynchronously() {
        final File backupFile = allocateBackupFileForDate(new Date());

        log(Level.INFO, t("createBackup.creating"));

        new BukkitRunnable() {
            @Override
            public void run() {
                try {
                    exportAccounts(backupFile);

                    log(Level.INFO, t("createBackup.success.log").replace("{0}", backupFile.getName()));
                } catch (IOException ex) {
                    log(Level.WARNING, t("createBackup.fail.log"), ex);
                }
            }
        }.runTaskAsynchronously(getPlugin());

        return backupFile;
    }

    private void exportAccounts(File backupFile) throws IOException {
        if (backupFile == null)
            throw new IllegalArgumentException();

        Hashtable<String, DataType> keys = accountManager.getStorage().getKeys(accountManager.getUnit());
        String primaryKey = accountManager.getStorage().getPrimaryKey(accountManager.getUnit());
        List<Storage.Entry> entries = accountManager.getStorage().selectEntries(accountManager.getUnit());

        try (Storage backupStorage = new SqliteStorage("jdbc:sqlite:" + backupFile)) {
            backupStorage.connect();
            backupStorage.createUnit("accounts", keys, primaryKey);

            try {
                backupStorage.setAutobatchEnabled(true);

                for (Storage.Entry entry : entries) {
                    backupStorage.addEntry("accounts", entry);
                }

                backupStorage.executeBatch();
                backupStorage.clearBatch();
            } finally {
                backupStorage.setAutobatchEnabled(false);
            }
        }
    }

    /**
     * Restores a backup, loading all the accounts into the underlying {@code AccountManager}.
     * 
     * @param filename the backup filename.
     * 
     * @throws FileNotFoundException    if no such backup exists.
     * @throws IllegalArgumentException if {@code filename} is {@code null} or blank.
     * @throws ReportedException        if an I/O error occurred,
     *                                  and it was reported to the logger.
     */
    public void restoreBackup(String filename) throws FileNotFoundException {
        if (StringUtils.isBlank(filename))
            throw new IllegalArgumentException();

        File backupFile = getBackupFile(filename);

        if (backupFile == null)
            throw new FileNotFoundException();

        try (Storage backupStorage = new SqliteStorage("jdbc:sqlite:" + backupFile)) {
            backupStorage.connect();

            List<Storage.Entry> entries = backupStorage.selectEntries("accounts");

            accountManager.getStorage().eraseUnit(accountManager.getUnit());

            try {
                accountManager.getStorage().setAutobatchEnabled(true);

                for (Storage.Entry entry : entries) {
                    accountManager.getStorage().addEntry(accountManager.getUnit(), entry);
                }

                accountManager.getStorage().executeBatch();
                accountManager.getStorage().clearBatch();
            } finally {
                accountManager.getStorage().setAutobatchEnabled(false);
            }

            log(Level.INFO, t("restoreBackup.success.log").replace("{0}", filename));
        } catch (IOException ex) {
            log(Level.WARNING, ex);

            ReportedException.throwNew(ex);
        }
    }

    /**
     * Removes a certain amount of backups starting from the oldest.
     * 
     * @param amount the amount of backups to remove.
     * 
     * @return number of backups actually removed.
     */
    public int removeBackups(int amount) {
        if (amount < 0)
            throw new IllegalArgumentException();

        File[] backupFiles = getBackups();
        int effectiveAmount = 0;

        for (int i = 0; i < amount; i++) {
            if (i >= backupFiles.length)
                break;

            if (backupFiles[i].delete()) {
                effectiveAmount++;
            }
        }

        log(Level.INFO, t("removeBackups.success").replace("{0}", String.valueOf(effectiveAmount)));

        return effectiveAmount;
    }

    /**
     * Creates an array of all the backup files from the backup directory.
     * 
     * @return an array of {@code File} objects, each representing a backup.
     */
    public File[] getBackups() {
        File backupDir = getDataFile(getConfig("config.yml").getString("backup.path"));
        File[] backupFiles = backupDir.listFiles();

        if (backupFiles == null)
            return new File[0];

        Arrays.sort(backupFiles);

        return backupFiles;
    }

    public File[] getBackups(Comparator<File> comparator) {
        if (comparator == null)
            throw new IllegalArgumentException();

        File[] backupFiles = getBackups();

        Arrays.sort(backupFiles, comparator);

        return backupFiles;
    }

    /**
     * Creates a backup {@code File} object with the given filename
     * relative to the backup directory.
     * 
     * @param filename the backup filename.
     * 
     * @return the backup file, or {@code null} if a backup
     *         with the given filename does not exist.
     * 
     * @throws IllegalArgumentException if {@code filename} is {@code null} or blank.
     */
    public File getBackupFile(String filename) {
        if (StringUtils.isBlank(filename))
            throw new IllegalArgumentException();

        File backupDir = getDataFile(getConfig("config.yml").getString("backup.path"));
        File backupFile = new File(backupDir, filename);

        if (!backupFile.exists())
            return null;

        return backupFile;
    }

    public Date parseBackupFilename(String filename) throws ParseException {
        if (StringUtils.isBlank(filename))
            throw new IllegalArgumentException();

        return buildDateFormat().parse(filename);
    }

    private String formatBackupFilename(Date date) {
        if (date == null)
            throw new IllegalArgumentException();

        return buildDateFormat().format(date);
    }

    private DateFormat buildDateFormat() {
        String backupFilenameFormat = getConfig("config.yml").getString("backup.filename");
        DateFormat dateFormat = new SimpleDateFormat(backupFilenameFormat);

        return dateFormat;
    }

    private synchronized File allocateBackupFileForDate(Date date) {
        if (date == null)
            throw new IllegalArgumentException();

        File backupDir = getDataFile(getConfig("config.yml").getString("backup.path"));
        File backupFile;
        int suffixIdx = 0;

        do {
            suffixIdx++;

            String filename = formatBackupFilename(date);

            if (suffixIdx > 1) {
                filename += "__" + suffixIdx;
            }

            backupFile = new File(backupDir, filename + ".db");
        } while (backupFile.exists());

        backupDir.getParentFile().mkdirs();
        backupDir.mkdir();

        try {
            backupFile.createNewFile();
        } catch (IOException ex) {
            log(Level.WARNING, ex);
        }

        return backupFile;
    }

    /**
     * Recommended task period of {@code BackupManager} running as a Bukkit task.
     */
    public static final long TASK_PERIOD = TimeUnit.SECONDS.convert(2, TimeUnit.TICKS);

    private Timer timer;
    private AccountManager accountManager;
}