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.Collection; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.Map; 024 025import org.syncany.database.ChunkEntry.ChunkChecksum; 026import org.syncany.database.FileContent.FileChecksum; 027import org.syncany.database.MultiChunkEntry.MultiChunkId; 028import org.syncany.database.PartialFileHistory.FileHistoryId; 029 030/** 031 * The database version represents an incremental addition to the local database of 032 * a client. A user's {@link MemoryDatabase} consists of many incremental database versions. 033 * 034 * <p>A <code>DatabaseVersion</code> is identified by a {@link DatabaseVersionHeader}, a 035 * combination of a {@link VectorClock}, a local timestamp and the original client name. 036 * 037 * <p>The database version holds references to the newly added/removed/changed 038 * {@link PartialFileHistory}s as well as the corresponding {@link FileContent}s, 039 * {@link ChunkEntry}s and {@link MultiChunkEntry}s. 040 * 041 * <p>The current implementation of the database version keeps all references in memory. 042 * 043 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 044 */ 045public class DatabaseVersion { 046 public enum DatabaseVersionStatus { 047 MASTER, DIRTY 048 } 049 050 private DatabaseVersionStatus status; 051 private DatabaseVersionHeader header; 052 053 // Full DB in RAM 054 private Map<ChunkChecksum, ChunkEntry> chunks; 055 private Map<MultiChunkId, MultiChunkEntry> multiChunks; 056 private Map<FileChecksum, FileContent> fileContents; 057 private Map<FileHistoryId, PartialFileHistory> fileHistories; 058 059 // Quick access cache 060 private Map<ChunkChecksum, MultiChunkId> chunkMultiChunkCache; 061 062 public DatabaseVersion() { 063 header = new DatabaseVersionHeader(); 064 065 // Full DB in RAM 066 chunks = new HashMap<ChunkChecksum, ChunkEntry>(); 067 multiChunks = new HashMap<MultiChunkId, MultiChunkEntry>(); 068 fileContents = new HashMap<FileChecksum, FileContent>(); 069 fileHistories = new HashMap<FileHistoryId, PartialFileHistory>(); 070 071 // Quick access cache 072 chunkMultiChunkCache = new HashMap<ChunkChecksum, MultiChunkId>(); 073 } 074 075 public DatabaseVersionHeader getHeader() { 076 return header; 077 } 078 079 public void setHeader(DatabaseVersionHeader header) { 080 this.header = header; 081 } 082 083 public Date getTimestamp() { 084 return header.getDate(); 085 } 086 087 public void setTimestamp(Date timestamp) { 088 header.setDate(timestamp); 089 } 090 091 public VectorClock getVectorClock() { 092 return header.getVectorClock(); 093 } 094 095 public void setVectorClock(VectorClock vectorClock) { 096 header.setVectorClock(vectorClock); 097 } 098 099 public void setClient(String client) { 100 header.setClient(client); 101 } 102 103 public String getClient() { 104 return header.getClient(); 105 } 106 107 public DatabaseVersionStatus getStatus() { 108 return status; 109 } 110 111 public void setStatus(DatabaseVersionStatus status) { 112 this.status = status; 113 } 114 115 public boolean isEmpty() { 116 return chunks.isEmpty() && multiChunks.isEmpty() && fileContents.isEmpty() && fileHistories.isEmpty(); 117 } 118 119 // Chunk 120 121 public ChunkEntry getChunk(ChunkChecksum checksum) { 122 return chunks.get(checksum); 123 } 124 125 public void addChunk(ChunkEntry chunk) { 126 chunks.put(chunk.getChecksum(), chunk); 127 } 128 129 public Collection<ChunkEntry> getChunks() { 130 return chunks.values(); 131 } 132 133 // Multichunk 134 135 public void addMultiChunk(MultiChunkEntry multiChunk) { 136 multiChunks.put(multiChunk.getId(), multiChunk); 137 138 // Populate cache 139 for (ChunkChecksum chunkChecksum : multiChunk.getChunks()) { 140 chunkMultiChunkCache.put(chunkChecksum, multiChunk.getId()); 141 } 142 } 143 144 public MultiChunkEntry getMultiChunk(MultiChunkId multiChunkId) { 145 return multiChunks.get(multiChunkId); 146 } 147 148 /** 149 * Get a multichunk that this chunk is contained in. 150 */ 151 public MultiChunkId getMultiChunkId(ChunkChecksum chunk) { 152 return chunkMultiChunkCache.get(chunk); 153 } 154 155 /** 156 * Get all multichunks in this database version. 157 */ 158 public Collection<MultiChunkEntry> getMultiChunks() { 159 return multiChunks.values(); 160 } 161 162 // Content 163 164 public FileContent getFileContent(FileChecksum checksum) { 165 return fileContents.get(checksum); 166 } 167 168 public void addFileContent(FileContent content) { 169 fileContents.put(content.getChecksum(), content); 170 } 171 172 public Collection<FileContent> getFileContents() { 173 return fileContents.values(); 174 } 175 176 // History 177 178 public void addFileHistory(PartialFileHistory history) { 179 fileHistories.put(history.getFileHistoryId(), history); 180 } 181 182 public PartialFileHistory getFileHistory(FileHistoryId fileId) { 183 return fileHistories.get(fileId); 184 } 185 186 public Collection<PartialFileHistory> getFileHistories() { 187 return fileHistories.values(); 188 } 189 190 @Override 191 public DatabaseVersion clone() { 192 DatabaseVersion clonedDatabaseVersion = new DatabaseVersion(); 193 clonedDatabaseVersion.setHeader(getHeader()); 194 195 for (ChunkEntry chunkEntry : getChunks()) { 196 clonedDatabaseVersion.addChunk(chunkEntry); 197 } 198 199 for (MultiChunkEntry multiChunkEntry : getMultiChunks()) { 200 clonedDatabaseVersion.addMultiChunk(multiChunkEntry); 201 } 202 203 for (FileContent fileContent : getFileContents()) { 204 clonedDatabaseVersion.addFileContent(fileContent); 205 } 206 207 for (PartialFileHistory fileHistory : getFileHistories()) { 208 clonedDatabaseVersion.addFileHistory(fileHistory); 209 } 210 211 return clonedDatabaseVersion; 212 } 213 214 @Override 215 public int hashCode() { 216 final int prime = 31; 217 int result = 1; 218 result = prime * result + ((header == null) ? 0 : header.hashCode()); 219 return result; 220 } 221 222 @Override 223 public boolean equals(Object obj) { 224 if (this == obj) { 225 return true; 226 } 227 if (obj == null) { 228 return false; 229 } 230 if (!(obj instanceof DatabaseVersion)) { 231 return false; 232 } 233 DatabaseVersion other = (DatabaseVersion) obj; 234 if (header == null) { 235 if (other.header != null) { 236 return false; 237 } 238 } 239 else if (!header.equals(other.header)) { 240 return false; 241 } 242 return true; 243 } 244 245 @Override 246 public String toString() { 247 return "DatabaseVersion [header=" + header + ", chunks=" + chunks.size() + ", multiChunks=" + multiChunks.size() + ", fileContents=" 248 + fileContents.size() 249 + ", fileHistories=" + fileHistories.size() + "]"; 250 } 251}