| | |
| | | package de.micromata.borgbutler.cache; |
| | | |
| | | import de.micromata.borgbutler.cache.memory.MemoryCache; |
| | | import de.micromata.borgbutler.cache.memory.MemoryCacheObject; |
| | | import de.micromata.borgbutler.config.BorgRepoConfig; |
| | | import de.micromata.borgbutler.data.Archive; |
| | | import de.micromata.borgbutler.data.FileSystemFilter; |
| | |
| | | import org.apache.commons.collections4.CollectionUtils; |
| | | 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.output.ByteArrayOutputStream; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.nustaq.serialization.FSTConfiguration; |
| | | import org.nustaq.serialization.FSTObjectInput; |
| | |
| | | private static Logger log = LoggerFactory.getLogger(ArchiveFilelistCache.class); |
| | | private static final String CACHE_ARCHIVE_LISTS_BASENAME = "archive-content-"; |
| | | private static final String CACHE_FILE_GZIP_EXTENSION = ".gz"; |
| | | private static final int MAX_NUMBER_OF_RECENT_ENTRIES = 2; |
| | | private static final int MAX_SIZE_MEMORY_CACHE = 30 * 1024 * 1024; // 50 MB |
| | | private File cacheDir; |
| | | private int cacheArchiveContentMaxDiscSizeMB; |
| | | private long FILES_EXPIRE_TIME = 7 * 24 * 3660 * 1000; // Expires after 7 days. |
| | | // For avoiding concurrent writing of same files (e. g. after the user has pressed a button twice). |
| | | private Set<File> savingFiles = new HashSet<>(); |
| | | private Recents recents = new Recents(MAX_NUMBER_OF_RECENT_ENTRIES); |
| | | private MemoryCache<Archive, RecentEntry> recents = new MemoryCache<>(MAX_SIZE_MEMORY_CACHE); |
| | | final FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration(); |
| | | |
| | | ArchiveFilelistCache(File cacheDir, int cacheArchiveContentMaxDiscSizeMB) { |
| | | this.cacheDir = cacheDir; |
| | | this.cacheArchiveContentMaxDiscSizeMB = cacheArchiveContentMaxDiscSizeMB; |
| | | conf.registerClass(BorgFilesystemItem.class); |
| | | conf.registerClass(Integer.class, BorgFilesystemItem.class); |
| | | conf.setShareReferences(false); |
| | | } |
| | | |
| | |
| | | Collections.sort(filesystemItems); // Sort by path. |
| | | } |
| | | log.info("Saving archive content as file list: " + file.getAbsolutePath()); |
| | | boolean ok = false; |
| | | |
| | | int fileNumber = -1; |
| | | try (FSTObjectOutput outputStream |
| | | = new FSTObjectOutput(new BufferedOutputStream(new GzipCompressorOutputStream(new FileOutputStream(file))), conf)) { |
| | | outputStream.writeObject(filesystemItems.size(), Integer.class); |
| | | Iterator<BorgFilesystemItem> it = filesystemItems.iterator(); |
| | | while (it.hasNext()) { |
| | | BorgFilesystemItem item = it.next(); |
| | | item.setFileNumber(++fileNumber); |
| | | outputStream.writeObject(item, BorgFilesystemItem.class); |
| | | } |
| | | outputStream.writeObject("EOF"); |
| | | ok = true; |
| | | } catch (IOException ex) { |
| | | log.error("Error while writing file list '" + file.getAbsolutePath() + "': " + ex.getMessage(), ex); |
| | | } |
| | | if (ok) { |
| | | // Storing current read gz archive in memory cache (recents): |
| | | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| | | try { |
| | | FileUtils.copyFile(file, baos); |
| | | recents.add(new RecentEntry(archive, baos.toByteArray())); |
| | | } catch (IOException ex) { |
| | | log.error("Error while writing gz archive to memory cache: " + ex.getMessage(), ex); |
| | | } |
| | | } |
| | | } finally { |
| | | synchronized (savingFiles) { |
| | | savingFiles.remove(file); |
| | |
| | | } catch (IOException ex) { |
| | | log.error("Can't set lastModifiedTime on file '" + file.getAbsolutePath() + "'. Pruning old cache files may not work."); |
| | | } |
| | | RecentEntry recentEntry = recents.getRecent(archive); |
| | | byte[] bytes = null; |
| | | if (recentEntry != null) { |
| | | bytes = recentEntry.serializedGz; |
| | | } else { |
| | | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| | | try { |
| | | FileUtils.copyFile(file, baos); |
| | | bytes = baos.toByteArray(); |
| | | recents.add(new RecentEntry(archive, bytes)); |
| | | } catch (IOException ex) { |
| | | log.error("Error while restoring file: " + file.getAbsolutePath() + ": " + ex.getMessage(), ex); |
| | | return null; |
| | | } |
| | | } |
| | | List<BorgFilesystemItem> list = new ArrayList<>(); |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | | /* try { |
| | | IOUtils.copy(new BufferedInputStream(new GzipCompressorInputStream(new FileInputStream(file))), out); |
| | | } catch (IOException ex) { |
| | | |
| | | }*/ |
| | | byte[] bytes = out.toByteArray(); |
| | | out = null; |
| | | try (FSTObjectInput inputStream = new FSTObjectInput(new BufferedInputStream(new GzipCompressorInputStream(new FileInputStream(file))), conf)) { |
| | | try (FSTObjectInput inputStream = new FSTObjectInput(new BufferedInputStream(new GzipCompressorInputStream(new ByteArrayInputStream(bytes))), conf)) { |
| | | int size = (Integer) inputStream.readObject(Integer.class); |
| | | int fileNumber = -1; |
| | | for (int i = 0; i < size; i++) { |
| | | ++fileNumber; |
| | | BorgFilesystemItem item = (BorgFilesystemItem) inputStream.readObject(BorgFilesystemItem.class); |
| | | item.setFileNumber(fileNumber); |
| | | if (filter == null || filter.matches(item)) { |
| | | list.add(item); |
| | | if (filter != null && filter.isFinished()) break; |
| | |
| | | } catch (Exception ex) { |
| | | log.error("Error while reading file list '" + file.getAbsolutePath() + "': " + ex.getMessage(), ex); |
| | | } |
| | | Collections.sort(list); // Sort by path (if archive list order wasn't correct). |
| | | |
| | | log.info("Loading done."); |
| | | return filter(list, filter); |
| | | } |
| | |
| | | return file.getName().startsWith(CACHE_ARCHIVE_LISTS_BASENAME); |
| | | } |
| | | |
| | | private class Recents { |
| | | private RecentEntry[] recents; |
| | | private int pos = 0; |
| | | private int size; |
| | | private class RecentEntry extends MemoryCacheObject<Archive> { |
| | | private byte[] serializedGz; |
| | | |
| | | private Recents(int size) { |
| | | this.size = size; |
| | | recents = new RecentEntry[size]; |
| | | @Override |
| | | protected boolean matches(Archive identifier) { |
| | | return StringUtils.equals(this.getIdentifier().getId(), identifier.getId()); |
| | | } |
| | | |
| | | private void add(RecentEntry entry) { |
| | | synchronized (this) { |
| | | log.info("Add recent at position #" + pos + ": " + entry.archive.getName()); |
| | | recents[pos++] = entry; |
| | | if (pos >= size) pos = 0; |
| | | } |
| | | @Override |
| | | protected int getSize() { |
| | | return serializedGz != null ? serializedGz.length : 0; |
| | | } |
| | | |
| | | private RecentEntry getRecent(Archive archive) { |
| | | synchronized (this) { |
| | | for (RecentEntry entry : recents) { |
| | | if (entry != null && entry.matches(archive)) { |
| | | return entry; |
| | | } |
| | | } |
| | | } |
| | | log.info("No recent entry found for archive: " + archive.getName()); |
| | | return null; |
| | | } |
| | | |
| | | private void removeOldestEntry() { |
| | | int oldestEntry = pos + 1 >= size ? 0 : pos + 1; |
| | | recents[oldestEntry] = null; |
| | | log.info("Remove oldest entry #" + oldestEntry + ": " + recents[oldestEntry]); |
| | | } |
| | | } |
| | | |
| | | private class RecentEntry { |
| | | private Archive archive; |
| | | private List<BorgFilesystemItem> filesystemItems; |
| | | |
| | | private boolean matches(Archive archive) { |
| | | if (this.archive == null || archive == null) { |
| | | return false; |
| | | } |
| | | return StringUtils.equals(this.archive.getId(), archive.getId()); |
| | | } |
| | | |
| | | private RecentEntry(Archive archive, List<BorgFilesystemItem> filesystemItems) { |
| | | this.archive = archive; |
| | | this.filesystemItems = filesystemItems; |
| | | private RecentEntry(Archive archive, byte[] serializedGz) { |
| | | super(archive); |
| | | this.serializedGz = serializedGz; |
| | | } |
| | | } |
| | | } |