001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.database; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import org.syncany.database.ChunkEntry.ChunkChecksum; 028import org.syncany.database.FileContent.FileChecksum; 029import org.syncany.database.FileVersion.FileStatus; 030import org.syncany.database.MultiChunkEntry.MultiChunkId; 031import org.syncany.database.PartialFileHistory.FileHistoryId; 032 033/** 034 * The database represents the internal file and chunk index of the application. It 035 * can be used to reference or load a full local database (local client) or a 036 * remote database (from a delta database file of another clients). 037 * 038 * <p>A database consists of a sorted list of {@link DatabaseVersion}s, i.e. it is a 039 * collection of changes to the local file system. 040 * 041 * <p>For convenience, the class also offers a set of functionality to select objects 042 * from the current accumulated database. Examples include {@link #getChunk(ChunkChecksum) getChunk()}, 043 * {@link #getContent(FileChecksum) getContent()} and {@link #getMultiChunk(MultiChunkId) getMultiChunk()}. 044 * 045 * <p>To allow this convenience, a few caches are kept in memory, and updated whenever a 046 * database version is added or removed. 047 * 048 * @see DatabaseVersion 049 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 050 */ 051public class MemoryDatabase { 052 private List<DatabaseVersion> databaseVersions; 053 054 // Caches 055 private DatabaseVersion fullDatabaseVersionCache; 056 private Map<String, PartialFileHistory> filenameHistoryCache; 057 private Map<VectorClock, DatabaseVersion> databaseVersionIdCache; 058 private Map<FileChecksum, List<PartialFileHistory>> contentChecksumFileHistoriesCache; 059 060 public MemoryDatabase() { 061 databaseVersions = new ArrayList<DatabaseVersion>(); 062 063 // Caches 064 fullDatabaseVersionCache = new DatabaseVersion(); 065 filenameHistoryCache = new HashMap<String, PartialFileHistory>(); 066 databaseVersionIdCache = new HashMap<VectorClock, DatabaseVersion>(); 067 contentChecksumFileHistoriesCache = new HashMap<FileChecksum, List<PartialFileHistory>>(); 068 } 069 070 public DatabaseVersion getLastDatabaseVersion() { 071 if (databaseVersions.size() == 0) { 072 return null; 073 } 074 075 return databaseVersions.get(databaseVersions.size() - 1); 076 } 077 078 public List<DatabaseVersion> getDatabaseVersions() { 079 return Collections.unmodifiableList(databaseVersions); 080 } 081 082 public DatabaseVersion getDatabaseVersion(VectorClock vectorClock) { 083 return databaseVersionIdCache.get(vectorClock); 084 } 085 086 public FileContent getContent(FileChecksum checksum) { 087 return (checksum != null) ? fullDatabaseVersionCache.getFileContent(checksum) : null; 088 } 089 090 public Object getChunk(ChunkChecksum checksum) { 091 return (checksum != null) ? fullDatabaseVersionCache.getChunk(checksum) : null; 092 } 093 094 public MultiChunkEntry getMultiChunk(MultiChunkId id) { 095 return fullDatabaseVersionCache.getMultiChunk(id); 096 } 097 098 /** 099 * Get a multichunk that this chunk is contained in. 100 */ 101 public MultiChunkId getMultiChunkIdForChunk(ChunkChecksum chunk) { 102 return fullDatabaseVersionCache.getMultiChunkId(chunk); 103 } 104 105 public PartialFileHistory getFileHistory(String relativeFilePath) { 106 return filenameHistoryCache.get(relativeFilePath); 107 } 108 109 public List<PartialFileHistory> getFileHistories(FileChecksum fileContentChecksum) { 110 return contentChecksumFileHistoriesCache.get(fileContentChecksum); 111 } 112 113 public PartialFileHistory getFileHistory(FileHistoryId fileId) { 114 return fullDatabaseVersionCache.getFileHistory(fileId); 115 } 116 117 public Collection<PartialFileHistory> getFileHistories() { 118 return fullDatabaseVersionCache.getFileHistories(); 119 } 120 121 public Collection<MultiChunkEntry> getMultiChunks() { 122 return fullDatabaseVersionCache.getMultiChunks(); 123 } 124 125 public void addDatabaseVersion(DatabaseVersion databaseVersion) { 126 databaseVersions.add(databaseVersion); 127 128 // Populate caches 129 // WARNING: Do NOT reorder, order important!! 130 updateDatabaseVersionIdCache(databaseVersion); 131 updateFullDatabaseVersionCache(databaseVersion); 132 updateFilenameHistoryCache(); 133 updateContentChecksumCache(); 134 } 135 136 public void removeDatabaseVersion(DatabaseVersion databaseVersion) { 137 databaseVersions.remove(databaseVersion); 138 139 // Populate caches 140 // WARNING: Do NOT reorder, order important!! 141 updateFullDatabaseVersionCache(); 142 updateDatabaseVersionIdCache(); 143 updateFilenameHistoryCache(); 144 updateContentChecksumCache(); 145 } 146 147 // TODO [medium] Very inefficient. Always updates whole cache 148 private void updateContentChecksumCache() { 149 contentChecksumFileHistoriesCache.clear(); 150 151 for (PartialFileHistory fullFileHistory : fullDatabaseVersionCache.getFileHistories()) { 152 FileChecksum lastVersionChecksum = fullFileHistory.getLastVersion().getChecksum(); 153 154 if (lastVersionChecksum != null) { 155 List<PartialFileHistory> historiesWithVersionsWithSameChecksum = contentChecksumFileHistoriesCache.get(lastVersionChecksum); 156 157 // Create if it does not exist 158 if (historiesWithVersionsWithSameChecksum == null) { 159 historiesWithVersionsWithSameChecksum = new ArrayList<PartialFileHistory>(); 160 } 161 162 // Add to cache 163 historiesWithVersionsWithSameChecksum.add(fullFileHistory); 164 contentChecksumFileHistoriesCache.put(lastVersionChecksum, historiesWithVersionsWithSameChecksum); 165 } 166 } 167 168 } 169 170 private void updateFilenameHistoryCache() { 171 // TODO [medium] Performance: This throws away the unchanged entries. It should only update new database version 172 filenameHistoryCache.clear(); 173 174 for (PartialFileHistory cacheFileHistory : fullDatabaseVersionCache.getFileHistories()) { 175 FileVersion lastVersion = cacheFileHistory.getLastVersion(); 176 String fileName = lastVersion.getPath(); 177 178 if (lastVersion.getStatus() != FileStatus.DELETED) { 179 filenameHistoryCache.put(fileName, cacheFileHistory); 180 } 181 } 182 } 183 184 private void updateDatabaseVersionIdCache(DatabaseVersion newDatabaseVersion) { 185 databaseVersionIdCache.put(newDatabaseVersion.getVectorClock(), newDatabaseVersion); 186 } 187 188 private void updateDatabaseVersionIdCache() { 189 databaseVersionIdCache.clear(); 190 191 for (DatabaseVersion databaseVersion : databaseVersions) { 192 updateDatabaseVersionIdCache(databaseVersion); 193 } 194 } 195 196 private void updateFullDatabaseVersionCache() { 197 fullDatabaseVersionCache = new DatabaseVersion(); 198 199 for (DatabaseVersion databaseVersion : databaseVersions) { 200 updateFullDatabaseVersionCache(databaseVersion); 201 } 202 } 203 204 private void updateFullDatabaseVersionCache(DatabaseVersion newDatabaseVersion) { 205 // Chunks 206 for (ChunkEntry sourceChunk : newDatabaseVersion.getChunks()) { 207 if (fullDatabaseVersionCache.getChunk(sourceChunk.getChecksum()) == null) { 208 fullDatabaseVersionCache.addChunk(sourceChunk); 209 } 210 } 211 212 // Multichunks 213 for (MultiChunkEntry sourceMultiChunk : newDatabaseVersion.getMultiChunks()) { 214 if (fullDatabaseVersionCache.getMultiChunk(sourceMultiChunk.getId()) == null) { 215 fullDatabaseVersionCache.addMultiChunk(sourceMultiChunk); 216 } 217 } 218 219 // Contents 220 for (FileContent sourceFileContent : newDatabaseVersion.getFileContents()) { 221 if (fullDatabaseVersionCache.getFileContent(sourceFileContent.getChecksum()) == null) { 222 fullDatabaseVersionCache.addFileContent(sourceFileContent); 223 } 224 } 225 226 // Histories 227 for (PartialFileHistory sourceFileHistory : newDatabaseVersion.getFileHistories()) { 228 PartialFileHistory targetFileHistory = fullDatabaseVersionCache.getFileHistory(sourceFileHistory.getFileHistoryId()); 229 230 if (targetFileHistory == null) { 231 fullDatabaseVersionCache.addFileHistory(sourceFileHistory.clone()); 232 } 233 else { 234 for (FileVersion sourceFileVersion : sourceFileHistory.getFileVersions().values()) { 235 if (targetFileHistory.getFileVersion(sourceFileVersion.getVersion()) == null) { 236 targetFileHistory.addFileVersion(sourceFileVersion); 237 } 238 } 239 } 240 } 241 } 242 243}