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.operations.down; 019 020import java.util.Collection; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import org.syncany.config.Config; 029import org.syncany.database.ChunkEntry.ChunkChecksum; 030import org.syncany.database.FileContent; 031import org.syncany.database.FileVersion; 032import org.syncany.database.MemoryDatabase; 033import org.syncany.database.MultiChunkEntry.MultiChunkId; 034import org.syncany.database.PartialFileHistory; 035import org.syncany.database.SqlDatabase; 036import org.syncany.operations.Downloader; 037import org.syncany.operations.Operation; 038import org.syncany.operations.OperationResult; 039import org.syncany.operations.down.actions.FileCreatingFileSystemAction; 040import org.syncany.operations.down.actions.FileSystemAction; 041import org.syncany.plugins.transfer.TransferManager; 042 043/** 044 * Applies a given winners database to the local directory. 045 * 046 * <p>Steps: 047 * <ul> 048 * <li>Determine whether the local branch needs to be updated (new database versions); if so, determine 049 * local {@link FileSystemAction}s</li> 050 * <li>Determine, download and decrypt required multi chunks from remote storage from file actions 051 * (implemented in {@link #determineMultiChunksToDownload(FileVersion, MemoryDatabase) determineMultiChunksToDownload()}, 052 * and {@link Downloader#downloadAndDecryptMultiChunks(Set) downloadAndDecryptMultiChunks()})</li> 053 * <li>Apply file system actions locally, creating conflict files where necessary if local file does 054 * not match the expected file (implemented in {@link #applyFileSystemActions(List) applyFileSystemActions()} </li> 055 * </ul> 056 * 057 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 058 */ 059public class ApplyChangesOperation extends Operation { 060 private static final Logger logger = Logger.getLogger(DownOperation.class.getSimpleName()); 061 062 private SqlDatabase localDatabase; 063 private Downloader downloader; 064 065 private MemoryDatabase winnersDatabase; 066 private DownOperationResult result; 067 068 private boolean cleanupOccurred; 069 private List<PartialFileHistory> preDeleteFileHistoriesWithLastVersion; 070 071 public ApplyChangesOperation(Config config, SqlDatabase localDatabase, TransferManager transferManager, MemoryDatabase winnersDatabase, 072 DownOperationResult result, boolean cleanupOccurred, List<PartialFileHistory> preDeleteFileHistoriesWithLastVersion) { 073 074 super(config); 075 076 this.localDatabase = localDatabase; 077 this.downloader = new Downloader(config, transferManager); 078 this.winnersDatabase = winnersDatabase; 079 this.result = result; 080 this.cleanupOccurred = cleanupOccurred; 081 this.preDeleteFileHistoriesWithLastVersion = preDeleteFileHistoriesWithLastVersion; 082 } 083 084 @Override 085 public OperationResult execute() throws Exception { 086 logger.log(Level.INFO, "Determine file system actions ..."); 087 088 FileSystemActionReconciliator actionReconciliator = new FileSystemActionReconciliator(config, result.getChangeSet()); 089 List<FileSystemAction> actions; 090 091 if (cleanupOccurred) { 092 actions = actionReconciliator.determineFileSystemActions(winnersDatabase, true, preDeleteFileHistoriesWithLastVersion); 093 } 094 else { 095 actions = actionReconciliator.determineFileSystemActions(winnersDatabase); 096 } 097 098 Set<MultiChunkId> unknownMultiChunks = determineRequiredMultiChunks(actions, winnersDatabase); 099 100 downloader.downloadAndDecryptMultiChunks(unknownMultiChunks); 101 result.getDownloadedMultiChunks().addAll(unknownMultiChunks); 102 103 applyFileSystemActions(actions); 104 105 return null; 106 } 107 108 /** 109 * Finds the multichunks that need to be downloaded to apply the given file system actions. 110 * The method looks at all {@link FileCreatingFileSystemAction}s and returns their multichunks. 111 */ 112 private Set<MultiChunkId> determineRequiredMultiChunks(List<FileSystemAction> actions, MemoryDatabase winnersDatabase) { 113 Set<MultiChunkId> multiChunksToDownload = new HashSet<MultiChunkId>(); 114 115 for (FileSystemAction action : actions) { 116 if (action instanceof FileCreatingFileSystemAction) { // TODO [low] This adds ALL multichunks even though some might be available locally 117 multiChunksToDownload.addAll(determineMultiChunksToDownload(action.getFile2(), winnersDatabase)); 118 } 119 } 120 121 return multiChunksToDownload; 122 } 123 124 /** 125 * Finds the multichunks that need to be downloaded for the given file version -- using the local 126 * database and given winners database. Returns a set of multichunk identifiers. 127 */ 128 private Collection<MultiChunkId> determineMultiChunksToDownload(FileVersion fileVersion, MemoryDatabase winnersDatabase) { 129 Set<MultiChunkId> multiChunksToDownload = new HashSet<MultiChunkId>(); 130 131 // First: Check if we know this file locally! 132 List<MultiChunkId> multiChunkIds = localDatabase.getMultiChunkIds(fileVersion.getChecksum()); 133 134 if (multiChunkIds.size() > 0) { 135 multiChunksToDownload.addAll(multiChunkIds); 136 } 137 else { 138 // Second: We don't know it locally; must be from the winners database 139 FileContent winningFileContent = winnersDatabase.getContent(fileVersion.getChecksum()); 140 boolean winningFileHasContent = winningFileContent != null; 141 142 if (winningFileHasContent) { // File can be empty! 143 List<ChunkChecksum> fileChunks = winningFileContent.getChunks(); 144 145 // TODO [medium] Instead of just looking for multichunks to download here, we should look for chunks in local files as well 146 // and return the chunk positions in the local files ChunkPosition (chunk123 at file12, offset 200, size 250) 147 148 Map<ChunkChecksum, MultiChunkId> checksumsWithMultiChunkIds = localDatabase.getMultiChunkIdsByChecksums(fileChunks); 149 150 for (ChunkChecksum chunkChecksum : fileChunks) { 151 MultiChunkId multiChunkIdForChunk = checksumsWithMultiChunkIds.get(chunkChecksum); 152 if (multiChunkIdForChunk == null) { 153 multiChunkIdForChunk = winnersDatabase.getMultiChunkIdForChunk(chunkChecksum); 154 155 if (multiChunkIdForChunk == null) { 156 throw new RuntimeException("Cannot find multichunk for chunk "+chunkChecksum); 157 } 158 } 159 160 if (!multiChunksToDownload.contains(multiChunkIdForChunk)) { 161 logger.log(Level.INFO, " + Adding multichunk " + multiChunkIdForChunk + " to download list ..."); 162 multiChunksToDownload.add(multiChunkIdForChunk); 163 } 164 } 165 } 166 } 167 168 return multiChunksToDownload; 169 } 170 171 /** 172 * Applies the given file system actions in a sensible order. To do that, 173 * the given actions are first sorted using the {@link FileSystemActionComparator} and 174 * then executed individually using {@link FileSystemAction#execute()}. 175 */ 176 private void applyFileSystemActions(List<FileSystemAction> actions) throws Exception { 177 // Sort 178 FileSystemActionComparator actionComparator = new FileSystemActionComparator(); 179 actionComparator.sort(actions); 180 181 logger.log(Level.FINER, "- Applying file system actions (sorted!) ..."); 182 183 // Apply 184 for (FileSystemAction action : actions) { 185 if (logger.isLoggable(Level.FINER)) { 186 logger.log(Level.FINER, " + {0}", action); 187 } 188 189 // Execute the file system action 190 191 // Note that exceptions are not caught here, to prevent 192 // apply-failed-delete-on-up situations. 193 194 action.execute(); 195 } 196 } 197}