/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2014 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.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.syncany.database.DatabaseVersion.DatabaseVersionStatus;
import org.syncany.database.FileVersion.FileStatus;
import org.syncany.database.FileVersion.FileType;
import org.syncany.database.FileVersion;
import org.syncany.database.PartialFileHistory;
import org.syncany.database.PartialFileHistory.FileHistoryId;
import org.syncany.database.VectorClock;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
/**
* The file history DAO queries and modifies the <i>filehistory</i> in
* the SQL database. This table corresponds to the Java object {@link PartialFileHistory}.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class FileHistorySqlDao extends AbstractSqlDao {
protected static final Logger logger = Logger.getLogger(FileHistorySqlDao.class.getSimpleName());
private FileVersionSqlDao fileVersionDao;
public FileHistorySqlDao(Connection connection, FileVersionSqlDao fileVersionDao) {
super(connection);
this.fileVersionDao = fileVersionDao;
}
/**
* Writes a list of {@link PartialFileHistory}s to the database table <i>filehistory</i> using <tt>INSERT</tt>s
* and the given connection. In addition, this method also writes the corresponding {@link FileVersion}s of
* each file history to the database using
* {@link FileVersionSqlDao#writeFileVersions(Connection, FileHistoryId, long, Collection) FileVersionSqlDao#writeFileVersions}.
*
* <p><b>Note:</b> This method executes, but <b>does not commit</b> the queries.
*
* @param connection The connection used to execute the statements
* @param databaseVersionId References the {@link PartialFileHistory} to which the list of file versions belongs
* @param fileHistories List of {@link PartialFileHistory}s to be written to the database
* @throws SQLException If the SQL statement fails
*/
public void writeFileHistories(Connection connection, long databaseVersionId, Collection<PartialFileHistory> fileHistories) throws SQLException {
for (PartialFileHistory fileHistory : fileHistories) {
PreparedStatement preparedStatement = getStatement(connection, "filehistory.insert.all.writeFileHistories.sql");
preparedStatement.setString(1, fileHistory.getFileHistoryId().toString());
preparedStatement.setLong(2, databaseVersionId);
int affectedRows = preparedStatement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Cannot add database version header. Affected rows is zero.");
}
preparedStatement.close();
fileVersionDao.writeFileVersions(connection, fileHistory.getFileHistoryId(), databaseVersionId, fileHistory.getFileVersions().values());
}
}
public void writePurgeFileHistories(Connection connection, long purgeDatabaseVersionId, Collection<PartialFileHistory> purgeFileHistories) throws SQLException {
for (PartialFileHistory purgeFileHistory : purgeFileHistories) {
fileVersionDao.writePurgeFileVersions(connection, purgeFileHistory.getFileHistoryId(), purgeDatabaseVersionId, purgeFileHistory.getFileVersions().values());
}
}
public void removeDirtyFileHistories() throws SQLException {
try (PreparedStatement preparedStatement = getStatement("filehistory.delete.dirty.removeDirtyFileHistories.sql")) {
preparedStatement.executeUpdate();
}
}
/**
* Removes unreferenced {@link PartialFileHistory}s from the database table
* <i>filehistory</i>. This method <b>does not</b> remove the corresponding {@link FileVersion}s.
*
* <p><b>Note:</b> This method executes, but <b>does not commit</b> the query.
*
* @throws SQLException If the SQL statement fails
*/
public void removeUnreferencedFileHistories() throws SQLException {
try (PreparedStatement preparedStatement = getStatement("filehistory.delete.all.removeUnreferencedFileHistories.sql")) {
preparedStatement.executeUpdate();
}
}
/**
* Note: Also selects versions marked as {@link DatabaseVersionStatus#DIRTY DIRTY}
*/
public Map<FileHistoryId, PartialFileHistory> getFileHistoriesWithFileVersions(VectorClock databaseVersionVectorClock) {
try (PreparedStatement preparedStatement = getStatement("filehistory.select.all.getFileHistoriesWithFileVersionsByVectorClock.sql")) {
preparedStatement.setString(1, databaseVersionVectorClock.toString());
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createFileHistoriesFromResult(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public FileHistoryId expandFileHistoryId(FileHistoryId fileHistoryIdPrefix) {
String fileHistoryIdPrefixLikeQuery = fileHistoryIdPrefix.toString() + "%";
try (PreparedStatement preparedStatement = getStatement("filehistory.select.master.expandFileHistoryId.sql")) {
preparedStatement.setString(1, fileHistoryIdPrefixLikeQuery);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
FileHistoryId fullFileHistoryId = FileHistoryId.parseFileId(resultSet.getString("filehistory_id"));
boolean nonUniqueResult = resultSet.next();
if (nonUniqueResult) {
return null;
}
else {
return fullFileHistoryId;
}
}
else {
return null;
}
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Map<FileHistoryId, PartialFileHistory> getFileHistories(List<FileHistoryId> fileHistoryIds) {
String[] fileHistoryIdsStr = createFileHistoryIdsArray(fileHistoryIds);
try (PreparedStatement preparedStatement = getStatement("filehistory.select.master.getFileHistoriesByIds.sql")) {
preparedStatement.setArray(1, connection.createArrayOf("varchar", fileHistoryIdsStr));
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createFileHistoriesFromResult(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
private String[] createFileHistoryIdsArray(List<FileHistoryId> fileHistoryIds) {
return Lists.transform(fileHistoryIds, new Function<FileHistoryId, String>() {
public String apply(FileHistoryId fileHistoryId) {
return fileHistoryId.toString();
}
}).toArray(new String[0]);
}
public Map<FileHistoryId, PartialFileHistory> getFileHistoriesWithFileVersions() {
try (PreparedStatement preparedStatement = getStatement("filehistory.select.master.getFileHistoriesWithFileVersions.sql")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return createFileHistoriesFromResult(resultSet);
}
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
protected Map<FileHistoryId, PartialFileHistory> createFileHistoriesFromResult(ResultSet resultSet) throws SQLException {
Map<FileHistoryId, PartialFileHistory> fileHistories = new HashMap<FileHistoryId, PartialFileHistory>();;
PartialFileHistory fileHistory = null;
while (resultSet.next()) {
FileVersion lastFileVersion = fileVersionDao.createFileVersionFromRow(resultSet);
FileHistoryId fileHistoryId = FileHistoryId.parseFileId(resultSet.getString("filehistory_id"));
// Old history (= same filehistory identifier)
if (fileHistory != null && fileHistory.getFileHistoryId().equals(fileHistoryId)) { // Same history!
fileHistory.addFileVersion(lastFileVersion);
}
// New history!
else {
// Add the old history
if (fileHistory != null) {
fileHistories.put(fileHistory.getFileHistoryId(), fileHistory);
}
// Create a new one
fileHistory = new PartialFileHistory(fileHistoryId);
fileHistory.addFileVersion(lastFileVersion);
}
}
// Add the last history
if (fileHistory != null) {
fileHistories.put(fileHistory.getFileHistoryId(), fileHistory);
}
return fileHistories;
}
public List<PartialFileHistory> getFileHistoriesWithLastVersion() {
List<PartialFileHistory> fileHistories = new ArrayList<PartialFileHistory>();
try (PreparedStatement preparedStatement = getStatement("filehistory.select.master.getFileHistoriesWithLastVersion.sql")) {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
FileHistoryId fileHistoryId = FileHistoryId.parseFileId(resultSet.getString("filehistory_id"));
FileVersion lastFileVersion = fileVersionDao.createFileVersionFromRow(resultSet);
PartialFileHistory fileHistory = new PartialFileHistory(fileHistoryId);
fileHistory.addFileVersion(lastFileVersion);
fileHistories.add(fileHistory);
}
}
return fileHistories;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<PartialFileHistory> getPurgeFileHistoriesWithFileVersions(VectorClock purgeDatabaseVersionVectorClock) {
List<PartialFileHistory> fileHistories = new ArrayList<PartialFileHistory>();
try (PreparedStatement preparedStatement = getStatement("fileversion.select.all.getPurgeFileHistoriesWithFileVersionsByVectorClock.sql")) {
preparedStatement.setString(1, purgeDatabaseVersionVectorClock.toString());
try (ResultSet resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
FileHistoryId purgeFileHistoryId = FileHistoryId.parseFileId(resultSet.getString("filehistory_id"));
// Create max purge version with dummy values (mandatory for XML serialization)
FileVersion maxPurgeFileVersion = new FileVersion();
maxPurgeFileVersion.setFileHistoryId(purgeFileHistoryId);
maxPurgeFileVersion.setVersion(resultSet.getLong("fileversion_maxpurgeversion"));
maxPurgeFileVersion.setPath("");
maxPurgeFileVersion.setType(FileType.FILE);
maxPurgeFileVersion.setStatus(FileStatus.DELETED);
maxPurgeFileVersion.setSize(0L);
maxPurgeFileVersion.setLastModified(new Date());
PartialFileHistory fileHistory = new PartialFileHistory(purgeFileHistoryId);
fileHistory.addFileVersion(maxPurgeFileVersion);
fileHistories.add(fileHistory);
}
}
return fileHistories;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
}