mirror of https://github.com/micromata/borgbackup-butler.git

Kai Reinhard
09.28.2018 5c94bff143dd5876046e528855497a96cd2bb55a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package de.micromata.borgbutler.cache;
 
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.config.Definitions;
import de.micromata.borgbutler.json.JsonUtils;
import lombok.Getter;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.io.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
public abstract class AbstractCache<T> {
    private static Logger log = LoggerFactory.getLogger(AbstractCache.class);
    private static final String CACHE_FILE_PREFIX = "cache-";
    private static final String CACHE_FILE_EXTENSION = "json";
    private static final String CACHE_FILE_ZIP_EXTENSION = ".zip";
 
    /**
     * INITIAL - on startup (not yet read), DIRTY - modifications not written, SAVED - content written to file.
     */
    private enum STATE {INITIAL, DIRTY, SAVED}
 
    @JsonIgnore
    protected File cacheFile;
    @JsonIgnore
    @Getter
    private boolean zip;
    @JsonIgnore
    private STATE state = STATE.INITIAL;
    @Getter
    private Date lastModified;
    @Getter
    private Date created;
    @Getter
    protected Map<String, T> elements = new HashMap<>();
 
    public T get(BorgRepoConfig repoConfig, String identifier) {
        if (identifier == null) {
            return null;
        }
        if (this.state == STATE.INITIAL) {
            read();
        }
        for (T element : elements.values()) {
            if (matches(element, identifier)) {
                return element;
            }
        }
        return load(repoConfig, identifier);
    }
 
    protected abstract T load(BorgRepoConfig repoConfig, String identifier);
 
    public abstract boolean matches(T element, String identifier);
 
    public abstract String getIdentifier(T element);
 
    public abstract void updateFrom(T dest, T source);
 
    /**
     * Removes all entries (doesn't effect the cache files!).
     */
    public void clear() {
        elements.clear();
        state = STATE.DIRTY;
    }
 
    public void upsert(BorgRepoConfig repoConfig, T element) {
        T existingElement = get(repoConfig, getIdentifier(element));
        if (existingElement == null) {
            elements.put(getIdentifier(element), element);
        } else {
            updateFrom(existingElement, element);
        }
        state = STATE.DIRTY; // Needed to save cache to file.
    }
 
    public void read() {
        try {
            if (!cacheFile.exists()) {
                // Cache file doesn't exist. Nothing to read.
                state = STATE.DIRTY; // Needed to save cache to file.
                return;
            }
            log.info("Parsing cache file '" + cacheFile.getAbsolutePath() + "'.");
            String json;
            if (zip) {
                try (GzipCompressorInputStream in = new GzipCompressorInputStream(new FileInputStream(cacheFile))) {
                    StringWriter writer = new StringWriter();
                    IOUtils.copy(in, writer, Definitions.STD_CHARSET);
                    json = writer.toString();
                }
            } else {
                json = FileUtils.readFileToString(cacheFile, Definitions.STD_CHARSET);
            }
            AbstractCache readCache = JsonUtils.fromJson(this.getClass(), json);
            if (readCache != null) {
                this.elements = readCache.elements;
                this.lastModified = readCache.lastModified;
                this.created = readCache.created;
                this.state = STATE.SAVED; // State of cache is updated from cache file.
            } else {
                log.error("Error while parsing cache: " + cacheFile.getAbsolutePath());
                this.state = STATE.DIRTY; // Needed to save cache to file.
            }
        } catch (IOException ex) {
            log.error("Error while trying to read cache file '" + cacheFile.getAbsolutePath() + "': "
                    + ex.getMessage(), ex);
            this.state = STATE.DIRTY; // Needed to save cache to file.
        }
    }
 
    public void save() {
        if (this.state == STATE.SAVED || this.state == STATE.INITIAL) {
            log.info("Cache file is up to date (nothing to save): " + cacheFile);
            return;
        }
        log.info("Saving to cache file: " + cacheFile);
        if (created == null) {
            created = lastModified = new Date();
        } else {
            lastModified = new Date();
        }
        String json = JsonUtils.toJson(this);
        try {
            if (this.zip) {
                try (GzipCompressorOutputStream out = new GzipCompressorOutputStream(new FileOutputStream(cacheFile))) {
                    IOUtils.copy(new StringReader(json), out, Definitions.STD_CHARSET);
                }
            } else {
                FileUtils.write(cacheFile, json, Definitions.STD_CHARSET);
            }
            this.state = STATE.SAVED;
        } catch (IOException ex) {
            log.error("Error while trying to write cache file '" + cacheFile.getAbsolutePath() + "': "
                    + ex.getMessage(), ex);
            this.state = STATE.DIRTY;
        }
    }
 
    /**
     * Needed by jackson for deserialization.
     */
    AbstractCache() {
    }
 
    AbstractCache(File cacheDir, String cacheFilename) {
        this(cacheDir, cacheFilename, false);
    }
 
    AbstractCache(File cacheDir, String cacheFilename, boolean zip) {
        this.zip = zip;
        String filename = CACHE_FILE_PREFIX + cacheFilename + "." + CACHE_FILE_EXTENSION;
        if (this.zip)
            filename = filename + CACHE_FILE_EXTENSION;
        cacheFile = new File(cacheDir, CACHE_FILE_PREFIX + cacheFilename + "." + CACHE_FILE_EXTENSION);
        this.state = STATE.INITIAL;
    }
 
    public static boolean isCacheFile(File file) {
        String filename = file.getName();
        String extension = FilenameUtils.getExtension(filename);
        return filename.startsWith(CACHE_FILE_PREFIX) &&
                (extension.equals(CACHE_FILE_EXTENSION)
                        || extension.equals(CACHE_FILE_EXTENSION + CACHE_FILE_ZIP_EXTENSION));
    }
}