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.dao; 019 020import java.sql.Connection; 021import java.sql.PreparedStatement; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.util.Collection; 025import java.util.HashMap; 026import java.util.Map; 027 028import org.syncany.database.ChunkEntry.ChunkChecksum; 029import org.syncany.database.FileContent; 030import org.syncany.database.FileContent.FileChecksum; 031import org.syncany.database.VectorClock; 032 033/** 034 * The file content data access object (DAO) writes and queries the SQL database for information 035 * on {@link FileContent}s. It translates the relational data in the <i>filecontent</i> table to 036 * Java objects. 037 * 038 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 039 */ 040public class FileContentSqlDao extends AbstractSqlDao { 041 public FileContentSqlDao(Connection connection) { 042 super(connection); 043 } 044 045 /** 046 * Writes a list of {@link FileContent}s to the database using <code>INSERT</code>s and the given connection. 047 * It fills two tables, the <i>filecontent</i> table ({@link FileContent}) and the <i>filecontent_chunk</i> 048 * table ({@link ChunkChecksum}). 049 * 050 * <p>To do the latter (write chunk references), this method calls 051 * {@link #writeFileContentChunkRefs(Connection, FileContent) writeFileContentChunkRefs()} for every 052 * {@link FileContent}. 053 * 054 * <p><b>Note:</b> This method executes, but does not commit the queries. 055 * 056 * @param connection The connection used to execute the statements 057 * @param databaseVersionId 058 * @param fileContents List of {@link FileContent}s to be inserted in the database 059 * @throws SQLException If the SQL statement fails 060 */ 061 public void writeFileContents(Connection connection, long databaseVersionId, Collection<FileContent> fileContents) throws SQLException { 062 for (FileContent fileContent : fileContents) { 063 PreparedStatement preparedStatement = getStatement(connection, "filecontent.insert.all.writeFileContents.sql"); 064 065 preparedStatement.setString(1, fileContent.getChecksum().toString()); 066 preparedStatement.setLong(2, databaseVersionId); 067 preparedStatement.setLong(3, fileContent.getSize()); 068 069 preparedStatement.executeUpdate(); 070 preparedStatement.close(); 071 072 // Write chunk references 073 writeFileContentChunkRefs(connection, fileContent); 074 } 075 } 076 077 private void writeFileContentChunkRefs(Connection connection, FileContent fileContent) throws SQLException { 078 PreparedStatement preparedStatement = getStatement(connection, "filecontent.insert.all.writeFileContentChunkRefs.sql"); 079 int order = 0; 080 081 for (ChunkChecksum chunkChecksum : fileContent.getChunks()) { 082 preparedStatement.setString(1, fileContent.getChecksum().toString()); 083 preparedStatement.setString(2, chunkChecksum.toString()); 084 preparedStatement.setInt(3, order); 085 086 preparedStatement.addBatch(); 087 088 order++; 089 } 090 091 preparedStatement.executeBatch(); 092 preparedStatement.close(); 093 } 094 095 /** 096 * Removes unreferenced {@link FileContent}s from the database table <i>filecontent</i>, 097 * as well as the corresponding chunk references (list of {@link ChunkChecksum}s) from the 098 * table <i>filecontent_chunk</i>. 099 * 100 * <p><b>Note:</b> This method executes, but <b>does not commit</b> the query. 101 * 102 * @throws SQLException If the SQL statement fails 103 */ 104 public void removeUnreferencedFileContents() throws SQLException { 105 // Note: Chunk references (filcontent_chunk) must be removed first, because 106 // of the foreign key constraints. 107 108 removeUnreferencedFileContentChunkRefs(); 109 removeUnreferencedFileContentsInt(); 110 } 111 112 private void removeUnreferencedFileContentsInt() throws SQLException { 113 PreparedStatement preparedStatement = getStatement("filecontent.delete.all.removeUnreferencedFileContents.sql"); 114 preparedStatement.executeUpdate(); 115 preparedStatement.close(); 116 } 117 118 private void removeUnreferencedFileContentChunkRefs() throws SQLException { 119 PreparedStatement preparedStatement = getStatement("filecontent.delete.all.removeUnreferencedFileContentRefs.sql"); 120 preparedStatement.executeUpdate(); 121 preparedStatement.close(); 122 } 123 124 /** 125 * Queries the database for a particular {@link FileContent}, either with or without the 126 * corresponding chunk references (list of {@link ChunkChecksum}). 127 * 128 * @param fileChecksum {@link FileContent}-identifying file checksum 129 * @param includeChunkChecksums If <code>true</code>, the resulting {@link FileContent} will contain its chunk references 130 * @return Returns a {@link FileContent} either with or without chunk references, or <code>null</code> if it does not exist. 131 */ 132 public FileContent getFileContent(FileChecksum fileChecksum, boolean includeChunkChecksums) { 133 if (fileChecksum == null) { 134 return null; 135 } 136 else if (includeChunkChecksums) { 137 return getFileContentWithChunkChecksums(fileChecksum); 138 } 139 else { 140 return getFileContentWithoutChunkChecksums(fileChecksum); 141 } 142 } 143 144 /** 145 * Queries the SQL database for all {@link FileContent}s that <b>originally appeared</b> in the 146 * database version identified by the given vector clock. 147 * 148 * <p><b>Note:</b> This method does <b>not</b> select all the file contents that are referenced 149 * in the database version. In particular, it <b>does not return</b> file contents that appeared 150 * in previous other database versions. 151 * 152 * @param vectorClock Vector clock that identifies the database version 153 * @return Returns all {@link FileContent}s that originally belong to a database version 154 */ 155 public Map<FileChecksum, FileContent> getFileContents(VectorClock vectorClock) { 156 try (PreparedStatement preparedStatement = getStatement("filecontent.select.master.getFileContentsWithChunkChecksumsForDatabaseVersion.sql")) { 157 preparedStatement.setString(1, vectorClock.toString()); 158 159 try (ResultSet resultSet = preparedStatement.executeQuery()) { 160 return createFileContents(resultSet); 161 } 162 } 163 catch (SQLException e) { 164 throw new RuntimeException(e); 165 } 166 } 167 168 private FileContent getFileContentWithoutChunkChecksums(FileChecksum fileChecksum) { 169 try (PreparedStatement preparedStatement = getStatement("filecontent.select.all.getFileContentByChecksumWithoutChunkChecksums.sql")) { 170 preparedStatement.setString(1, fileChecksum.toString()); 171 172 try (ResultSet resultSet = preparedStatement.executeQuery()) { 173 if (resultSet.next()) { 174 FileContent fileContent = new FileContent(); 175 176 fileContent.setChecksum(FileChecksum.parseFileChecksum(resultSet.getString("checksum"))); 177 fileContent.setSize(resultSet.getLong("size")); 178 179 return fileContent; 180 } 181 } 182 183 return null; 184 } 185 catch (SQLException e) { 186 throw new RuntimeException(e); 187 } 188 } 189 190 private FileContent getFileContentWithChunkChecksums(FileChecksum fileChecksum) { 191 try (PreparedStatement preparedStatement = getStatement("filecontent.select.all.getFileContentByChecksumWithChunkChecksums.sql")) { 192 preparedStatement.setString(1, fileChecksum.toString()); 193 194 try (ResultSet resultSet = preparedStatement.executeQuery()) { 195 FileContent fileContent = null; 196 197 while (resultSet.next()) { 198 if (fileContent == null) { 199 fileContent = new FileContent(); 200 201 fileContent.setChecksum(FileChecksum.parseFileChecksum(resultSet.getString("checksum"))); 202 fileContent.setSize(resultSet.getLong("size")); 203 } 204 205 // Add chunk references 206 ChunkChecksum chunkChecksum = ChunkChecksum.parseChunkChecksum(resultSet.getString("chunk_checksum")); 207 fileContent.addChunk(chunkChecksum); 208 } 209 210 return fileContent; 211 } 212 } 213 catch (SQLException e) { 214 throw new RuntimeException(e); 215 } 216 } 217 218 private Map<FileChecksum, FileContent> createFileContents(ResultSet resultSet) throws SQLException { 219 Map<FileChecksum, FileContent> fileContents = new HashMap<FileChecksum, FileContent>(); 220 FileChecksum currentFileChecksum = null; 221 222 while (resultSet.next()) { 223 FileChecksum fileChecksum = FileChecksum.parseFileChecksum(resultSet.getString("checksum")); 224 FileContent fileContent = null; 225 226 if (currentFileChecksum != null && currentFileChecksum.equals(fileChecksum)) { 227 fileContent = fileContents.get(fileChecksum); 228 } 229 else { 230 fileContent = new FileContent(); 231 232 fileContent.setChecksum(fileChecksum); 233 fileContent.setSize(resultSet.getLong("size")); 234 } 235 236 ChunkChecksum chunkChecksum = ChunkChecksum.parseChunkChecksum(resultSet.getString("chunk_checksum")); 237 fileContent.addChunk(chunkChecksum); 238 239 fileContents.put(fileChecksum, fileContent); 240 currentFileChecksum = fileChecksum; 241 } 242 243 return fileContents; 244 } 245 246 /** 247 * no commit 248 */ 249 public void updateDirtyFileContentsNewDatabaseId(long newDatabaseVersionId) { 250 try (PreparedStatement preparedStatement = getStatement("filecontent.update.dirty.updateDirtyFileContentsNewDatabaseId.sql")) { 251 preparedStatement.setLong(1, newDatabaseVersionId); 252 preparedStatement.executeUpdate(); 253 } 254 catch (SQLException e) { 255 throw new RuntimeException(e); 256 } 257 } 258}