All Downloads are FREE. Search and download functionalities are using the official Maven repository.

studio.utils.FilesBackup Maven / Gradle / Ivy

package studio.utils;

import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class FilesBackup {

    protected static long BACKUP_PERIOD_MILLIS = 24*60*60*1000L; // ond day
    private final static int RETAIN_BACKUP_HISTORY_DAYS = 14; // keep the last 2 weeks

    private final Map lastBackupTime = new HashMap<>();
    private final Map backupIndex = new HashMap<>();
    private final Path backupDirPath;
    private static final Logger log = LogManager.getLogger();

    //Useful in tests
    private static boolean enabled = true;

    public static void setEnabled(boolean enabled) {
        log.info("setEnabled {}", enabled);
        FilesBackup.enabled = enabled;
    }

    public FilesBackup(String backupFolder) {
        backupDirPath = Paths.get(backupFolder);
        if (!enabled) return;

        if (Files.notExists(backupDirPath)) {
            try {
                log.info("Creating backup folder {}", backupDirPath);
                Files.createDirectories(backupDirPath);
            } catch (IOException e) {
                log.error("Error on backup folder creation", e);
            }
        }
    }

    public OutputStream newFileOutputStream(String filename) throws IOException {
        try {
            backup(filename);
        } catch (IOException e) {
            log.error("Error on backup {}", filename, e);
        }

        Path tmpFile = Paths.get(filename).resolveSibling(FilenameUtils.getName(filename) + ".tmp");
        Files.deleteIfExists(tmpFile);

        FileOutputStream outputStream = new FileOutputStream(tmpFile.toFile());
        return new OutputStream() {
            @Override
            public void write(byte[] b) throws IOException {
                outputStream.write(b);
            }

            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                outputStream.write(b, off, len);
            }

            @Override
            public void write(int b) throws IOException {
                outputStream.write(b);
            }

            @Override
            public void flush() throws IOException {
                outputStream.flush();
            }

            @Override
            public void close() throws IOException {
                outputStream.close();
                Files.move(tmpFile, Paths.get(filename), REPLACE_EXISTING);
            }
        };
    }

    public void backup(String filename) throws IOException {
        if (!enabled) return;

        if (Files.notExists(Paths.get(filename))) return;

        long current = System.currentTimeMillis();
        Long lastBackup = lastBackupTime.get(filename);

        if (lastBackup == null) {
            initBackupIndex(filename);
            lastBackupTime.put(filename, current);
            lastBackup = 0L;
        }

        if (current - lastBackup < BACKUP_PERIOD_MILLIS) return;

        doBackup(filename);
        lastBackupTime.put(filename, current);
    }

    private void initBackupIndex(String filename) throws IOException {
        BackupFile reference = new BackupFile(filename);

        cleanupBackupHistory(reference);

        int maxIndex = Files.list(backupDirPath)
                                .filter(Files::isRegularFile)
                                .map(p -> new BackupFile(p, reference) )
                                .filter(BackupFile::isValid)
                                .mapToInt(BackupFile::getIndex)
                                .max()
                                .orElse(-1);

        backupIndex.put(filename, maxIndex);
    }

    private void doBackup(String filename) throws IOException {
        BackupFile reference = new BackupFile(filename);
        cleanupBackupHistory(reference);

        int index = backupIndex.get(filename) + 1;
        backupIndex.put(filename, index);
        Path dest = backupDirPath.resolve(reference.name + "-" + index + "." + reference.ext);
        log.info("Backing up {} to {}", filename, dest);
        Files.copy(reference.path, dest);
    }

    private void cleanupBackupHistory(BackupFile reference) throws IOException {
        Instant keepInstant = Instant.now().minus(RETAIN_BACKUP_HISTORY_DAYS, ChronoUnit.DAYS);

        Files.list(backupDirPath)
                .filter(Files::isRegularFile)
                .map(p -> new BackupFile(p, reference))
                .filter(BackupFile::isValid)
                .forEach(b -> {
                    try {
                        BasicFileAttributes attr = Files.readAttributes(b.path, BasicFileAttributes.class);
                        if (attr.lastModifiedTime().toInstant().isBefore(keepInstant)) {
                            log.info("Remove old backup {}", b.path);
                            Files.delete(b.path);
                        }
                    } catch (IOException e) {
                        log.error("Error during history clean up", e);
                    }
                });
    }

    private static class BackupFile {
        public boolean valid = false;
        public int index = -1;
        public String name;
        public String ext;
        public Path path;

        BackupFile(Path path) {
            this.path = path;
            ext = FilenameUtils.getExtension(path.toString());
            name = FilenameUtils.removeExtension(FilenameUtils.getName(path.toString()));
        }

        BackupFile(String filename) {
            this(Paths.get(filename));
        }

        BackupFile(Path backup, BackupFile reference) {
            this(backup);
            this.path = backup;

            if (! ext.equals(reference.ext)) return;
            if (! name.startsWith(reference.name + "-")) return;
            try {
                index = Integer.parseInt(name.substring(reference.name.length() + 1));
                valid = true;
            } catch (NumberFormatException e ) {}
        }

        boolean isValid() {
            return valid;
        }

        int getIndex() {
            return  index;
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy