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; 029import org.syncany.database.ChunkEntry.ChunkChecksum; 030import org.syncany.database.VectorClock; 031 032/** 033 * The chunk data access object (DAO) writes and queries the SQL database for information 034 * on {@link ChunkEntry}s. It translates the relational data in the "chunk" table to 035 * Java objects. 036 * 037 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 038 */ 039public class ChunkSqlDao extends AbstractSqlDao { 040 private Map<ChunkChecksum, ChunkEntry> chunkCache; 041 042 public ChunkSqlDao(Connection connection) { 043 super(connection); 044 this.chunkCache = null; 045 } 046 047 /** 048 * Writes a list of {@link ChunkEntry}s to the database using <code>INSERT</code>s and the given connection. 049 * 050 * <p><b>Note:</b> This method executes, but <b>does not commit</b> the query. 051 * 052 * @param connection The connection used to execute the statements 053 * @param databaseVersionId 054 * @param chunks List of {@link ChunkEntry}s to be inserted in the database 055 * @throws SQLException If the SQL statement fails 056 */ 057 public void writeChunks(Connection connection, long databaseVersionId, Collection<ChunkEntry> chunks) throws SQLException { 058 if (chunks.size() > 0) { 059 PreparedStatement preparedStatement = getStatement(connection, "chunk.insert.all.writeChunks.sql"); 060 061 for (ChunkEntry chunk : chunks) { 062 preparedStatement.setString(1, chunk.getChecksum().toString()); 063 preparedStatement.setLong(2, databaseVersionId); 064 preparedStatement.setInt(3, chunk.getSize()); 065 066 preparedStatement.addBatch(); 067 } 068 069 preparedStatement.executeBatch(); 070 preparedStatement.close(); 071 } 072 } 073 074 /** 075 * Removes unreferenced chunks from the database. Unreferenced chunks are chunks 076 * that are not referenced by any file content or multichunk. 077 * 078 * <p>During the cleanup process, when file versions are deleted, unused chunks 079 * are left over. This method removes these chunks from the database. 080 * 081 * <p><b>Note:</b> This method executes, but <b>does not commit</b> the query. 082 */ 083 public void removeUnreferencedChunks() { 084 try (PreparedStatement preparedStatement = getStatement("chunk.delete.all.removeUnreferencesChunks.sql")) { 085 preparedStatement.execute(); 086 preparedStatement.close(); 087 } 088 catch (SQLException e) { 089 throw new RuntimeException(e); 090 } 091 } 092 093 /** 094 * Queries the database of a chunk with the given checksum. 095 * 096 * <p>Note: When first called, this method loads the <b>chunk cache</b> and keeps 097 * this cache until it is cleared explicitly with {@link #clearCache()}. 098 * 099 * <p>Also note that this method will return <code>null</code> if the chunk has been 100 * added after the cache has been filled. 101 * 102 * @param chunkChecksum Chunk checksum of the chunk to be selected 103 * @return Returns the chunk entry, or <code>null</code> if the chunk does not exist. 104 */ 105 public synchronized ChunkEntry getChunk(ChunkChecksum chunkChecksum) { 106 if (chunkCache == null) { 107 loadChunkCache(); 108 } 109 110 return chunkCache.get(chunkChecksum); 111 } 112 113 /** 114 * Clears the chunk cache loaded by {@link #getChunk(ChunkChecksum) getChunk()} 115 * and resets the cache. If {@link #getChunk(ChunkChecksum) getChunk()} is called 116 * after the cache is cleared, it is re-populated. 117 */ 118 public synchronized void clearCache() { 119 if (chunkCache != null) { 120 chunkCache.clear(); 121 chunkCache = null; 122 } 123 } 124 125 /** 126 * Queries the SQL database for all chunks that <b>originally appeared</b> in the 127 * database version identified by the given vector clock. 128 * 129 * <p><b>Note:</b> This method does <b>not</b> select all the chunks that are referenced 130 * in the database version. In particular, it <b>does not return</b> chunks that appeared 131 * in previous other database versions. 132 * 133 * @param vectorClock Vector clock that identifies the database version 134 * @return Returns all chunks that originally belong to a database version 135 */ 136 public Map<ChunkChecksum, ChunkEntry> getChunks(VectorClock vectorClock) { 137 try (PreparedStatement preparedStatement = getStatement("chunk.select.all.getChunksForDatabaseVersion.sql")) { 138 preparedStatement.setString(1, vectorClock.toString()); 139 140 try (ResultSet resultSet = preparedStatement.executeQuery()) { 141 return createChunkEntries(resultSet); 142 } 143 } 144 catch (SQLException e) { 145 throw new RuntimeException(e); 146 } 147 } 148 149 protected Map<ChunkChecksum, ChunkEntry> createChunkEntries(ResultSet resultSet) throws SQLException { 150 Map<ChunkChecksum, ChunkEntry> chunks = new HashMap<ChunkChecksum, ChunkEntry>(); 151 152 while (resultSet.next()) { 153 ChunkEntry chunkEntry = createChunkEntryFromRow(resultSet); 154 chunks.put(chunkEntry.getChecksum(), chunkEntry); 155 } 156 157 return chunks; 158 } 159 160 protected ChunkEntry createChunkEntryFromRow(ResultSet resultSet) throws SQLException { 161 ChunkChecksum chunkChecksum = ChunkChecksum.parseChunkChecksum(resultSet.getString("checksum")); 162 return new ChunkEntry(chunkChecksum, resultSet.getInt("size")); 163 } 164 165 protected void loadChunkCache() { 166 try (PreparedStatement preparedStatement = getStatement("chunk.select.all.loadChunkCache.sql")) { 167 try (ResultSet resultSet = preparedStatement.executeQuery()) { 168 chunkCache = createChunkEntries(resultSet); 169 } 170 } 171 catch (SQLException e) { 172 throw new RuntimeException(e); 173 } 174 } 175 176 /** 177 * no commit 178 */ 179 public void updateDirtyChunksNewDatabaseId(long newDatabaseVersionId) { 180 try (PreparedStatement preparedStatement = getStatement("chunk.update.dirty.updateDirtyChunksNewDatabaseId.sql")) { 181 preparedStatement.setLong(1, newDatabaseVersionId); 182 preparedStatement.executeUpdate(); 183 } 184 catch (SQLException e) { 185 throw new RuntimeException(e); 186 } 187 } 188}