/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2016 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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.syncany.chunk.MultiChunk;
import org.syncany.database.ChunkEntry.ChunkChecksum;
import org.syncany.database.DatabaseVersion.DatabaseVersionStatus;
import org.syncany.database.DatabaseVersionHeader;
import org.syncany.database.FileContent.FileChecksum;
import org.syncany.database.MultiChunkEntry;
import org.syncany.database.MultiChunkEntry.MultiChunkId;
import org.syncany.database.VectorClock;
/**
* The multi-chunk data access object (DAO) queries and modifies the <i>multichunk</i> and
* <i>multichunk_chunk</t> table in the SQL database. These tables correspond to the Java
* object {@link MultiChunk}.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class MultiChunkSqlDao extends AbstractSqlDao {
public MultiChunkSqlDao(Connection connection) {
super(connection);
}
public void writeMultiChunks(Connection connection, long databaseVersionId, Collection<MultiChunkEntry> multiChunks) throws SQLException {
for (MultiChunkEntry multiChunk : multiChunks) {
PreparedStatement preparedStatement = getStatement(connection, "multichunk.insert.all.writeMultiChunks.sql");
preparedStatement.setString(1, multiChunk.getId().toString());
preparedStatement.setLong(2, databaseVersionId);
preparedStatement.setLong(3, multiChunk.getSize());
preparedStatement.executeUpdate();
preparedStatement.close();
writeMultiChunkRefs(connection, multiChunk);
}
}
private void writeMultiChunkRefs(Connection connection, MultiChunkEntry multiChunk) throws SQLException {
PreparedStatement preparedStatement = getStatement("multichunk.insert.all.writeMultiChunkRefs.sql");
for (ChunkChecksum chunkChecksum : multiChunk.getChunks()) {
preparedStatement.setString(1, multiChunk.getId().toString());
preparedStatement.setString(2, chunkChecksum.toString());
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
preparedStatement.close();
}
public void writeMuddyMultiChunks(Map<DatabaseVersionHeader, Collection<MultiChunkEntry>> muddyMultiChunksPerDatabaseVersion) throws SQLException {
PreparedStatement preparedStatement = getStatement("multichunk_muddy.insert.muddy.writeMuddyMultiChunks.sql");
for (DatabaseVersionHeader muddyDatabaseVersionHeader : muddyMultiChunksPerDatabaseVersion.keySet()) {
Collection<MultiChunkEntry> muddyMultiChunks = muddyMultiChunksPerDatabaseVersion.get(muddyDatabaseVersionHeader);
for (MultiChunkEntry muddyMultiChunk : muddyMultiChunks) {
String multiChunkIdStr = muddyMultiChunk.getId().toString();
String clientName = muddyDatabaseVersionHeader.getClient();
Long clientVersion = muddyDatabaseVersionHeader.getVectorClock().getClock(clientName);
preparedStatement.setString(1, multiChunkIdStr);
preparedStatement.setString(2, clientName);
preparedStatement.setLong(3, clientVersion);
preparedStatement.addBatch();
}
}
preparedStatement.executeBatch();
preparedStatement.close();
}
public void removeUnreferencedMultiChunks() throws SQLException {
// Note: Chunk references (multichunk_chunk) must be removed first, because
// of the foreign key constraints.
removeUnreferencedMultiChunkChunkRefs();
removeUnreferencedMultiChunksInt();
}
private void removeUnreferencedMultiChunksInt() throws SQLException {
PreparedStatement preparedStatement = getStatement("multichunk.delete.all.removeUnreferencedMultiChunks.sql");
preparedStatement.executeUpdate();
preparedStatement.close();
}
private void removeUnreferencedMultiChunkChunkRefs() throws SQLException {
PreparedStatement preparedStatement = getStatement("multichunk.delete.all.removeUnreferencedMultiChunkChunkRefs.sql");
preparedStatement.executeUpdate();
preparedStatement.close();
}
public void removeNonMuddyMultiChunks() throws SQLException {
PreparedStatement preparedStatement = getStatement("multichunk_muddy.delete.muddy.removeNonMuddyMultiChunks.sql");
preparedStatement.executeUpdate();
preparedStatement.close();
}
/**
* Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}.
*/
public List<MultiChunkId> getMultiChunkIds(FileChecksum fileChecksum) {
List<MultiChunkId> multiChunkIds = new ArrayList<MultiChunkId>();
if (fileChecksum == null) {
return multiChunkIds;
}
else {
try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunkIdsForFileChecksum.sql")) {
preparedStatement.setString(1, fileChecksum.toString());
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
multiChunkIds.add(MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id")));
}
return multiChunkIds;
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}.
*/
public Map<MultiChunkId, MultiChunkEntry> getMultiChunks(VectorClock vectorClock) {
try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunksWithChunksForDatabaseVersion.sql")) {
preparedStatement.setString(1, vectorClock.toString());
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createMultiChunkEntriesWithChunks(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* no commit
*/
public void updateDirtyMultiChunksNewDatabaseId(long newDatabaseVersionId) {
try (PreparedStatement preparedStatement = getStatement("multichunk.update.dirty.updateDirtyMultiChunksNewDatabaseId.sql")) {
preparedStatement.setLong(1, newDatabaseVersionId);
preparedStatement.executeUpdate();
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}.
*/
public MultiChunkId getMultiChunkId(ChunkChecksum chunkChecksum) {
try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunkIdForChunk.sql")) {
preparedStatement.setString(1, chunkChecksum.toString());
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
return MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id"));
}
}
return null;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* Note: This method selects also {@link DatabaseVersionStatus#DIRTY DIRTY}.
*/
public Map<ChunkChecksum,MultiChunkId> getMultiChunkIdsByChecksums(List<ChunkChecksum> chunkChecksums) {
// Gather a unique array of checksum strings (required for query!)
Set<ChunkChecksum> chunkChecksumSet = new HashSet<ChunkChecksum>(chunkChecksums);
String[] checksums = new String[chunkChecksumSet.size()];
int i = 0;
for (ChunkChecksum checksum : chunkChecksumSet) {
checksums[i] = checksum.toString();
i++;
}
// Execute query
Map<ChunkChecksum, MultiChunkId> result = new HashMap<ChunkChecksum, MultiChunkId>();
try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunkIdForChunks.sql")) {
preparedStatement.setArray(1, connection.createArrayOf("varchar", checksums));
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
result.put(ChunkChecksum.parseChunkChecksum(resultSet.getString("chunk_checksum")),
MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id")));
}
}
return result;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<MultiChunkId> getDirtyMultiChunkIds() {
List<MultiChunkId> dirtyMultiChunkIds = new ArrayList<MultiChunkId>();
try (PreparedStatement preparedStatement = getStatement("multichunk.select.dirty.getDirtyMultiChunkIds.sql")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
dirtyMultiChunkIds.add(MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id")));
}
return dirtyMultiChunkIds;
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Map<MultiChunkId, MultiChunkEntry> getUnusedMultiChunks() {
try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getUnusedMultiChunks.sql")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createMultiChunkEntriesWithoutChunks(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Map<MultiChunkId, MultiChunkEntry> getMultiChunks() {
try (PreparedStatement preparedStatement = getStatement("multichunk.select.all.getMultiChunks.sql")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createMultiChunkEntriesWithoutChunks(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Map<MultiChunkId, MultiChunkEntry> getMuddyMultiChunks() {
try (PreparedStatement preparedStatement = getStatement("multichunk.select.muddy.getMuddyMultiChunks.sql")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createMultiChunkEntriesWithoutChunks(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
private Map<MultiChunkId, MultiChunkEntry> createMultiChunkEntriesWithoutChunks(ResultSet resultSet) throws SQLException {
Map<MultiChunkId, MultiChunkEntry> unusedMultiChunkIds = new HashMap<MultiChunkId, MultiChunkEntry>();
while (resultSet.next()) {
MultiChunkId multiChunkId = MultiChunkId.parseMultiChunkId(resultSet.getString("id"));
long multiChunkSize = resultSet.getLong("size");
unusedMultiChunkIds.put(multiChunkId, new MultiChunkEntry(multiChunkId, multiChunkSize));
}
return unusedMultiChunkIds;
}
private Map<MultiChunkId, MultiChunkEntry> createMultiChunkEntriesWithChunks(ResultSet resultSet) throws SQLException {
Map<MultiChunkId, MultiChunkEntry> multiChunkEntries = new HashMap<MultiChunkId, MultiChunkEntry>();
MultiChunkId currentMultiChunkId = null;
while (resultSet.next()) {
MultiChunkId multiChunkId = MultiChunkId.parseMultiChunkId(resultSet.getString("multichunk_id"));
long multiChunkSize = resultSet.getLong("size");
MultiChunkEntry multiChunkEntry = null;
if (currentMultiChunkId != null && currentMultiChunkId.equals(multiChunkId)) {
multiChunkEntry = multiChunkEntries.get(multiChunkId);
}
else {
multiChunkEntry = new MultiChunkEntry(multiChunkId, multiChunkSize);
}
multiChunkEntry.addChunk(ChunkChecksum.parseChunkChecksum(resultSet.getString("chunk_checksum")));
multiChunkEntries.put(multiChunkId, multiChunkEntry);
currentMultiChunkId = multiChunkId;
}
return multiChunkEntries;
}
}