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}