/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.database.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import org.syncany.database.ChunkEntry; import org.syncany.database.ChunkEntry.ChunkChecksum; import org.syncany.database.VectorClock; /** * The chunk data access object (DAO) writes and queries the SQL database for information * on {@link ChunkEntry}s. It translates the relational data in the "chunk" table to * Java objects. * * @author Philipp C. Heckel <philipp.heckel@gmail.com> */ public class ChunkSqlDao extends AbstractSqlDao { protected static final Logger logger = Logger.getLogger(ChunkSqlDao.class.getSimpleName()); private Map<ChunkChecksum, ChunkEntry> chunkCache; public ChunkSqlDao(Connection connection) { super(connection); this.chunkCache = null; } /** * Writes a list of {@link ChunkEntry}s to the database using <tt>INSERT</tt>s and the given connection. * * <p><b>Note:</b> This method executes, but <b>does not commit</b> the query. * * @param connection The connection used to execute the statements * @param databaseVersionId * @param chunks List of {@link ChunkEntry}s to be inserted in the database * @throws SQLException If the SQL statement fails */ public void writeChunks(Connection connection, long databaseVersionId, Collection<ChunkEntry> chunks) throws SQLException { if (chunks.size() > 0) { PreparedStatement preparedStatement = getStatement(connection, "chunk.insert.all.writeChunks.sql"); for (ChunkEntry chunk : chunks) { preparedStatement.setString(1, chunk.getChecksum().toString()); preparedStatement.setLong(2, databaseVersionId); preparedStatement.setInt(3, chunk.getSize()); preparedStatement.addBatch(); } preparedStatement.executeBatch(); preparedStatement.close(); } } /** * Removes unreferenced chunks from the database. Unreferenced chunks are chunks * that are not referenced by any file content or multichunk. * * <p>During the cleanup process, when file versions are deleted, unused chunks * are left over. This method removes these chunks from the database. * * <p><b>Note:</b> This method executes, but <b>does not commit</b> the query. */ public void removeUnreferencedChunks() { try (PreparedStatement preparedStatement = getStatement("chunk.delete.all.removeUnreferencesChunks.sql")) { preparedStatement.execute(); preparedStatement.close(); } catch (SQLException e) { throw new RuntimeException(e); } } /** * Queries the database of a chunk with the given checksum. * * <p>Note: When first called, this method loads the <b>chunk cache</b> and keeps * this cache until it is cleared explicitly with {@link #clearCache()}. * * <p>Also note that this method will return <tt>null</tt> if the chunk has been * added after the cache has been filled. * * @param chunkChecksum Chunk checksum of the chunk to be selected * @return Returns the chunk entry, or <tt>null</tt> if the chunk does not exist. */ public synchronized ChunkEntry getChunk(ChunkChecksum chunkChecksum) { if (chunkCache == null) { loadChunkCache(); } return chunkCache.get(chunkChecksum); } /** * Clears the chunk cache loaded by {@link #getChunk(ChunkChecksum) getChunk()} * and resets the cache. If {@link #getChunk(ChunkChecksum) getChunk()} is called * after the cache is cleared, it is re-populated. */ public synchronized void clearCache() { if (chunkCache != null) { chunkCache.clear(); chunkCache = null; } } /** * Queries the SQL database for all chunks that <b>originally appeared</b> in the * database version identified by the given vector clock. * * <p><b>Note:</b> This method does <b>not</b> select all the chunks that are referenced * in the database version. In particular, it <b>does not return</b> chunks that appeared * in previous other database versions. * * @param vectorClock Vector clock that identifies the database version * @return Returns all chunks that originally belong to a database version */ public Map<ChunkChecksum, ChunkEntry> getChunks(VectorClock vectorClock) { try (PreparedStatement preparedStatement = getStatement("chunk.select.all.getChunksForDatabaseVersion.sql")) { preparedStatement.setString(1, vectorClock.toString()); try (ResultSet resultSet = preparedStatement.executeQuery()) { return createChunkEntries(resultSet); } } catch (SQLException e) { throw new RuntimeException(e); } } protected Map<ChunkChecksum, ChunkEntry> createChunkEntries(ResultSet resultSet) throws SQLException { Map<ChunkChecksum, ChunkEntry> chunks = new HashMap<ChunkChecksum, ChunkEntry>(); while (resultSet.next()) { ChunkEntry chunkEntry = createChunkEntryFromRow(resultSet); chunks.put(chunkEntry.getChecksum(), chunkEntry); } return chunks; } protected ChunkEntry createChunkEntryFromRow(ResultSet resultSet) throws SQLException { ChunkChecksum chunkChecksum = ChunkChecksum.parseChunkChecksum(resultSet.getString("checksum")); return new ChunkEntry(chunkChecksum, resultSet.getInt("size")); } protected void loadChunkCache() { try (PreparedStatement preparedStatement = getStatement("chunk.select.all.loadChunkCache.sql")) { try (ResultSet resultSet = preparedStatement.executeQuery()) { chunkCache = createChunkEntries(resultSet); } } catch (SQLException e) { throw new RuntimeException(e); } } /** * no commit */ public void updateDirtyChunksNewDatabaseId(long newDatabaseVersionId) { try (PreparedStatement preparedStatement = getStatement("chunk.update.dirty.updateDirtyChunksNewDatabaseId.sql")) { preparedStatement.setLong(1, newDatabaseVersionId); preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } } }