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; 021 022import java.text.DateFormat; 023import java.text.SimpleDateFormat; 024import java.util.Date; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.List; 028import java.util.logging.Level; 029import java.util.logging.Logger; 030import java.util.regex.Matcher; 031import java.util.regex.Pattern; 032 033import joptsimple.OptionParser; 034import joptsimple.OptionSet; 035import joptsimple.OptionSpec; 036 037import org.syncany.cli.util.CommandLineUtil; 038import org.syncany.database.FileVersion; 039import org.syncany.database.FileVersion.FileStatus; 040import org.syncany.database.FileVersion.FileType; 041import org.syncany.database.ObjectId; 042import org.syncany.database.PartialFileHistory; 043import org.syncany.operations.OperationResult; 044import org.syncany.operations.ls.LsOperation; 045import org.syncany.operations.ls.LsOperationOptions; 046import org.syncany.operations.ls.LsOperationResult; 047 048import com.google.common.base.Function; 049 050public class LsCommand extends Command { 051 protected static final Logger logger = Logger.getLogger(LsCommand.class.getSimpleName()); 052 053 private static final int CHECKSUM_LENGTH_LONG = 40; 054 private static final int CHECKSUM_LENGTH_SHORT = 10; 055 private static final String DATE_FORMAT_PATTERN = "yy-MM-dd HH:mm:ss"; 056 private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_PATTERN); 057 058 private int checksumLength; 059 private boolean groupedVersions; 060 private boolean fetchHistories; 061 062 @Override 063 public CommandScope getRequiredCommandScope() { 064 return CommandScope.INITIALIZED_LOCALDIR; 065 } 066 067 @Override 068 public boolean canExecuteInDaemonScope() { 069 return true; 070 } 071 072 @Override 073 public int execute(String[] operationArgs) throws Exception { 074 LsOperationOptions operationOptions = parseOptions(operationArgs); 075 LsOperationResult operationResult = new LsOperation(config, operationOptions).execute(); 076 077 printResults(operationResult); 078 079 return 0; 080 } 081 082 @Override 083 public LsOperationOptions parseOptions(String[] operationArgs) throws Exception { 084 LsOperationOptions operationOptions = new LsOperationOptions(); 085 086 OptionParser parser = new OptionParser(); 087 parser.allowsUnrecognizedOptions(); 088 089 OptionSpec<String> optionDateStr = parser.acceptsAll(asList("D", "date")).withRequiredArg(); 090 OptionSpec<Void> optionRecursive = parser.acceptsAll(asList("r", "recursive")); 091 OptionSpec<String> optionFileTypes = parser.acceptsAll(asList("t", "types")).withRequiredArg(); 092 OptionSpec<Void> optionLongChecksums = parser.acceptsAll(asList("f", "full-checksums")); 093 OptionSpec<Void> optionWithVersions = parser.acceptsAll(asList("V", "versions")); 094 OptionSpec<Void> optionGroupedVersions = parser.acceptsAll(asList("g", "group")); 095 OptionSpec<Void> optionFileHistoryId = parser.acceptsAll(asList("H", "file-history")); 096 OptionSpec<Void> optionDeleted = parser.acceptsAll(asList("q", "deleted")); 097 098 OptionSet options = parser.parse(operationArgs); 099 100 // --date=.. 101 if (options.has(optionDateStr)) { 102 Date logViewDate = parseDateOption(options.valueOf(optionDateStr)); 103 operationOptions.setDate(logViewDate); 104 } 105 106 // --recursive 107 operationOptions.setRecursive(options.has(optionRecursive)); 108 109 // --types=[tds] 110 if (options.has(optionFileTypes)) { 111 String fileTypesStr = options.valueOf(optionFileTypes).toLowerCase(); 112 HashSet<FileType> fileTypes = new HashSet<>(); 113 114 if (fileTypesStr.contains("f")) { 115 fileTypes.add(FileType.FILE); 116 } 117 118 if (fileTypesStr.contains("d")) { 119 fileTypes.add(FileType.FOLDER); 120 } 121 122 if (fileTypesStr.contains("s")) { 123 fileTypes.add(FileType.SYMLINK); 124 } 125 126 operationOptions.setFileTypes(fileTypes); 127 } 128 129 // --versions 130 fetchHistories = options.has(optionWithVersions) || options.has(optionFileHistoryId); 131 operationOptions.setFetchHistories(fetchHistories); 132 133 // --file-history 134 operationOptions.setFileHistoryId(options.has(optionFileHistoryId)); 135 136 // --long-checksums (display option) 137 checksumLength = (options.has(optionLongChecksums)) ? CHECKSUM_LENGTH_LONG : CHECKSUM_LENGTH_SHORT; 138 139 // --group (display option) 140 groupedVersions = options.has(optionGroupedVersions); 141 142 // --deleted 143 operationOptions.setDeleted(options.has(optionDeleted)); 144 145 // <path-expr> 146 List<?> nonOptionArgs = options.nonOptionArguments(); 147 148 if (nonOptionArgs.size() > 0) { 149 operationOptions.setPathExpression(nonOptionArgs.get(0).toString()); 150 } 151 152 return operationOptions; 153 } 154 155 @Override 156 public void printResults(OperationResult operationResult) { 157 LsOperationResult concreteOperationResult = (LsOperationResult) operationResult; 158 159 int longestSize = calculateLongestSize(concreteOperationResult.getFileList()); 160 int longestVersion = calculateLongestVersion(concreteOperationResult.getFileList()); 161 162 if (fetchHistories) { 163 printHistories(concreteOperationResult, longestSize, longestVersion); 164 } 165 else { 166 printTree(concreteOperationResult, longestSize, longestVersion); 167 } 168 } 169 170 private void printTree(LsOperationResult operationResult, int longestSize, int longestVersion) { 171 for (FileVersion fileVersion : operationResult.getFileList()) { 172 printOneVersion(fileVersion, longestVersion, longestSize); 173 } 174 } 175 176 private void printHistories(LsOperationResult operationResult, int longestSize, int longestVersion) { 177 if (groupedVersions) { 178 printGroupedHistories(operationResult, longestSize, longestVersion); 179 } 180 else { 181 printNonGroupedHistories(operationResult, longestSize, longestVersion); 182 } 183 } 184 185 private void printNonGroupedHistories(LsOperationResult operationResult, int longestSize, int longestVersion) { 186 for (FileVersion fileVersion : operationResult.getFileList()) { 187 PartialFileHistory fileHistory = operationResult.getFileVersions().get(fileVersion.getFileHistoryId()); 188 189 for (FileVersion fileVersionInHistory : fileHistory.getFileVersions().values()) { 190 printOneVersion(fileVersionInHistory, longestVersion, longestSize); 191 } 192 } 193 } 194 195 private void printGroupedHistories(LsOperationResult operationResult, int longestSize, int longestVersion) { 196 Iterator<FileVersion> fileVersionIterator = operationResult.getFileList().iterator(); 197 198 while (fileVersionIterator.hasNext()) { 199 FileVersion fileVersion = fileVersionIterator.next(); 200 PartialFileHistory fileHistory = operationResult.getFileVersions().get(fileVersion.getFileHistoryId()); 201 202 out.printf("File %s, %s\n", formatObjectId(fileHistory.getFileHistoryId()), fileVersion.getPath()); 203 204 for (FileVersion fileVersionInHistory : fileHistory.getFileVersions().values()) { 205 if (fileVersionInHistory.equals(fileVersion)) { 206 out.print(" * "); 207 } 208 else { 209 out.print(" "); 210 } 211 212 printOneVersion(fileVersionInHistory, longestVersion, longestSize); 213 } 214 215 if (fileVersionIterator.hasNext()) { 216 out.println(); 217 } 218 } 219 } 220 221 private void printOneVersion(FileVersion fileVersion, int longestVersion, int longestSize) { 222 String fileStatus = formatFileStatusShortStr(fileVersion.getStatus()); 223 String fileType = formatFileTypeShortStr(fileVersion.getType()); 224 String posixPermissions = (fileVersion.getPosixPermissions() != null) ? fileVersion.getPosixPermissions() : ""; 225 String dosAttributes = (fileVersion.getDosAttributes() != null) ? fileVersion.getDosAttributes() : ""; 226 String fileChecksum = formatObjectId(fileVersion.getChecksum()); 227 String fileHistoryId = formatObjectId(fileVersion.getFileHistoryId()); 228 String path = (fileVersion.getType() == FileType.SYMLINK) ? fileVersion.getPath() + " -> " + fileVersion.getLinkTarget() : fileVersion.getPath(); 229 230 out.printf("%s %s %s %9s %4s %" + longestSize + "d %" + checksumLength + "s %" + checksumLength + "s %"+longestVersion+"d %s\n", 231 DATE_FORMAT.format(fileVersion.getUpdated()), fileStatus, fileType, posixPermissions, dosAttributes, fileVersion.getSize(), 232 fileChecksum, fileHistoryId, fileVersion.getVersion(), path); 233 } 234 235 private String formatFileStatusShortStr(FileStatus status) { 236 switch (status) { 237 case NEW: 238 return "A"; 239 240 case CHANGED: 241 case RENAMED: 242 return "M"; 243 244 case DELETED: 245 return "D"; 246 247 default: 248 return "?"; 249 } 250 } 251 252 private String formatFileTypeShortStr(FileType type) { 253 switch (type) { 254 case FILE: 255 return "-"; 256 257 case FOLDER: 258 return "d"; 259 260 case SYMLINK: 261 return "s"; 262 263 default: 264 return "?"; 265 } 266 } 267 268 private String formatObjectId(ObjectId checksum) { 269 if (checksum == null || "".equals(checksum)) { 270 return ""; 271 } 272 else { 273 return checksum.toString().substring(0, checksumLength); 274 } 275 } 276 277 private int calculateLongestVersion(List<FileVersion> fileVersions) { 278 return calculateLongestValue(fileVersions, new Function<FileVersion, Integer>() { 279 public Integer apply(FileVersion fileVersion) { 280 return (""+fileVersion.getVersion()).length(); 281 } 282 }); 283 } 284 285 private int calculateLongestSize(List<FileVersion> fileVersions) { 286 return calculateLongestValue(fileVersions, new Function<FileVersion, Integer>() { 287 public Integer apply(FileVersion fileVersion) { 288 return (""+fileVersion.getSize()).length(); 289 } 290 }); 291 } 292 293 private int calculateLongestValue(List<FileVersion> fileVersions, Function<FileVersion, Integer> callbackFunction) { 294 int result = 0; 295 296 for (FileVersion fileVersion : fileVersions) { 297 result = Math.max(result, callbackFunction.apply(fileVersion)); 298 } 299 300 return result; 301 } 302 303 protected Date parseDateOption(String dateStr) throws Exception { 304 Pattern relativeDatePattern = Pattern.compile("(\\d+(?:[.,]\\d+)?)(mo|[smhdwy])"); 305 Matcher relativeDateMatcher = relativeDatePattern.matcher(dateStr); 306 307 if (relativeDateMatcher.find()) { 308 long restoreDateMillies = CommandLineUtil.parseTimePeriod(dateStr)*1000; 309 310 Date restoreDate = new Date(System.currentTimeMillis()-restoreDateMillies); 311 312 logger.log(Level.FINE, "Restore date: "+restoreDate); 313 return restoreDate; 314 } 315 else { 316 try { 317 Date restoreDate = DATE_FORMAT.parse(dateStr); 318 319 logger.log(Level.FINE, "Restore date: "+restoreDate); 320 return restoreDate; 321 } 322 catch (Exception e) { 323 throw new Exception("Invalid '--date' argument: " + dateStr + ", use relative date or absolute format: " + DATE_FORMAT_PATTERN); 324 } 325 } 326 } 327}