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.cli; 019 020import static java.util.Arrays.asList; 021import joptsimple.OptionParser; 022import joptsimple.OptionSet; 023import joptsimple.OptionSpec; 024 025import org.syncany.cli.util.CommandLineUtil; 026import org.syncany.operations.OperationResult; 027import org.syncany.operations.cleanup.CleanupOperation; 028import org.syncany.operations.cleanup.CleanupOperationOptions; 029import org.syncany.operations.cleanup.CleanupOperationResult; 030import org.syncany.operations.daemon.messages.CleanupStartCleaningSyncExternalEvent; 031import org.syncany.operations.daemon.messages.CleanupStartSyncExternalEvent; 032import org.syncany.operations.status.StatusOperationOptions; 033 034import com.google.common.eventbus.Subscribe; 035 036public class CleanupCommand extends Command { 037 @Override 038 public CommandScope getRequiredCommandScope() { 039 return CommandScope.INITIALIZED_LOCALDIR; 040 } 041 042 @Override 043 public boolean canExecuteInDaemonScope() { 044 return false; 045 } 046 047 @Override 048 public int execute(String[] operationArgs) throws Exception { 049 CleanupOperationOptions operationOptions = parseOptions(operationArgs); 050 CleanupOperationResult operationResult = new CleanupOperation(config, operationOptions).execute(); 051 052 printResults(operationResult); 053 054 return 0; 055 } 056 057 @Override 058 public CleanupOperationOptions parseOptions(String[] operationArgs) throws Exception { 059 CleanupOperationOptions operationOptions = new CleanupOperationOptions(); 060 061 OptionParser parser = new OptionParser(); 062 parser.allowsUnrecognizedOptions(); 063 064 OptionSpec<Void> optionForce = parser.acceptsAll(asList("f", "force")); 065 OptionSpec<Void> optionNoOlderVersionRemoval = parser.acceptsAll(asList("O", "no-delete-older-than")); 066 OptionSpec<Void> optionNoVersionRemovalByInterval = parser.acceptsAll(asList("I", "no-delete-interval")); 067 OptionSpec<Void> optionNoRemoveTempFiles = parser.acceptsAll(asList("T", "no-temp-removal")); 068 OptionSpec<String> optionKeepMinTime = parser.acceptsAll(asList("o", "delete-older-than")) 069 .withRequiredArg().ofType(String.class); 070 071 OptionSet options = parser.parse(operationArgs); 072 073 // -F, --force 074 operationOptions.setForce(options.has(optionForce)); 075 076 // -V, --no-version-removal 077 operationOptions.setRemoveOldVersions(!options.has(optionNoOlderVersionRemoval)); 078 079 // -T, --no-temp-removal 080 operationOptions.setRemoveUnreferencedTemporaryFiles(!options.has(optionNoRemoveTempFiles)); 081 082 // -I, --no-delete-interval 083 operationOptions.setRemoveVersionsByInterval(!options.has(optionNoVersionRemovalByInterval)); 084 085 // -o=<time>, --delete-older-than=<time> 086 if (options.has(optionKeepMinTime)) { 087 long keepDeletedFilesForSeconds = CommandLineUtil.parseTimePeriod(options.valueOf(optionKeepMinTime)); 088 089 if (keepDeletedFilesForSeconds < 0) { 090 throw new Exception("Invalid value for --delete-older-than==" + keepDeletedFilesForSeconds + "; must be >= 0"); 091 } 092 093 operationOptions.setMinKeepSeconds(keepDeletedFilesForSeconds); 094 } 095 096 // Parse 'status' options 097 operationOptions.setStatusOptions(parseStatusOptions(operationArgs)); 098 099 return operationOptions; 100 } 101 102 private StatusOperationOptions parseStatusOptions(String[] operationArgs) throws Exception { 103 StatusCommand statusCommand = new StatusCommand(); 104 statusCommand.setOut(out); 105 106 return statusCommand.parseOptions(operationArgs); 107 } 108 109 @Override 110 public void printResults(OperationResult operationResult) { 111 CleanupOperationResult concreteOperationResult = (CleanupOperationResult) operationResult; 112 113 switch (concreteOperationResult.getResultCode()) { 114 case NOK_DIRTY_LOCAL: 115 out.println("Cannot cleanup database if local repository is in a dirty state; Call 'up' first."); 116 break; 117 118 case NOK_RECENTLY_CLEANED: 119 out.println("Cleanup has been done recently, so it is not necessary."); 120 out.println("If you are sure it is necessary, override with --force."); 121 break; 122 123 case NOK_LOCAL_CHANGES: 124 out.println("Local changes detected. Please call 'up' first'."); 125 break; 126 127 case NOK_REMOTE_CHANGES: 128 out.println("Remote changes detected or repository is locked by another user. Please call 'down' first."); 129 break; 130 131 case NOK_OTHER_OPERATIONS_RUNNING: 132 out.println("Cannot run cleanup while other clients are performing up/down/cleanup. Try again later."); 133 break; 134 135 case OK: 136 if (concreteOperationResult.getMergedDatabaseFilesCount() > 0) { 137 out.println(concreteOperationResult.getMergedDatabaseFilesCount() + " database files merged."); 138 } 139 140 if (concreteOperationResult.getRemovedMultiChunksCount() > 0) { 141 out.printf("%d multichunk(s) deleted on remote storage (freed %.2f MB)\n", 142 concreteOperationResult.getRemovedMultiChunksCount(), 143 (double) concreteOperationResult.getRemovedMultiChunksSize() / 1024 / 1024); 144 } 145 146 if (concreteOperationResult.getRemovedOldVersionsCount() > 0) { 147 out.println(concreteOperationResult.getRemovedOldVersionsCount() + " file histories shortened."); 148 // TODO [low] This counts only the file histories, not file versions; not very helpful! 149 } 150 151 out.println("Cleanup successful."); 152 break; 153 154 case OK_NOTHING_DONE: 155 out.println("Cleanup not necessary. Nothing done."); 156 break; 157 158 default: 159 throw new RuntimeException("Invalid result code: " + concreteOperationResult.getResultCode().toString()); 160 } 161 } 162 163 @Subscribe 164 public void onCleanupStartEventReceived(CleanupStartSyncExternalEvent syncEvent) { 165 out.printr("Checking if cleanup is needed ..."); 166 } 167 168 @Subscribe 169 public void onCleanupStartCleaningEventReceived(CleanupStartCleaningSyncExternalEvent syncEvent) { 170 out.printr("Cleanup is needed, starting to clean ..."); 171 } 172}