/*
* 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.cli;
import static java.util.Arrays.asList;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.syncany.cli.util.CommandLineUtil;
import org.syncany.database.FileVersion;
import org.syncany.database.FileVersion.FileType;
import org.syncany.database.ObjectId;
import org.syncany.database.PartialFileHistory;
import org.syncany.operations.OperationResult;
import org.syncany.operations.ls.LsOperationOptions;
import org.syncany.operations.ls.LsOperationResult;
import com.google.common.base.Function;
public class LsCommand extends Command {
protected static final Logger logger = Logger.getLogger(LsCommand.class.getSimpleName());
private static final int CHECKSUM_LENGTH_LONG = 40;
private static final int CHECKSUM_LENGTH_SHORT = 10;
private static final String DATE_FORMAT_PATTERN = "yy-MM-dd HH:mm:ss";
private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_PATTERN);
private int checksumLength;
private boolean groupedVersions;
private boolean fetchHistories;
@Override
public CommandScope getRequiredCommandScope() {
return CommandScope.INITIALIZED_LOCALDIR;
}
@Override
public boolean canExecuteInDaemonScope() {
return true;
}
@Override
public int execute(String[] operationArgs) throws Exception {
LsOperationOptions operationOptions = parseOptions(operationArgs);
LsOperationResult operationResult = client.ls(operationOptions);
printResults(operationResult);
return 0;
}
@Override
public LsOperationOptions parseOptions(String[] operationArgs) throws Exception {
LsOperationOptions operationOptions = new LsOperationOptions();
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
OptionSpec<String> optionDateStr = parser.acceptsAll(asList("D", "date")).withRequiredArg();
OptionSpec<Void> optionRecursive = parser.acceptsAll(asList("r", "recursive"));
OptionSpec<String> optionFileTypes = parser.acceptsAll(asList("t", "types")).withRequiredArg();
OptionSpec<Void> optionLongChecksums = parser.acceptsAll(asList("f", "full-checksums"));
OptionSpec<Void> optionWithVersions = parser.acceptsAll(asList("V", "versions"));
OptionSpec<Void> optionGroupedVersions = parser.acceptsAll(asList("g", "group"));
OptionSet options = parser.parse(operationArgs);
// --date=..
if (options.has(optionDateStr)) {
Date logViewDate = parseDateOption(options.valueOf(optionDateStr));
operationOptions.setDate(logViewDate);
}
// --recursive
operationOptions.setRecursive(options.has(optionRecursive));
// --types=[tds]
if (options.has(optionFileTypes)) {
String fileTypesStr = options.valueOf(optionFileTypes).toLowerCase();
HashSet<FileType> fileTypes = new HashSet<>();
if (fileTypesStr.contains("f")) {
fileTypes.add(FileType.FILE);
}
if (fileTypesStr.contains("d")) {
fileTypes.add(FileType.FOLDER);
}
if (fileTypesStr.contains("s")) {
fileTypes.add(FileType.SYMLINK);
}
operationOptions.setFileTypes(fileTypes);
}
// --versions
fetchHistories = options.has(optionWithVersions);
operationOptions.setFetchHistories(fetchHistories);
// --long-checksums (display option)
checksumLength = (options.has(optionLongChecksums)) ? CHECKSUM_LENGTH_LONG : CHECKSUM_LENGTH_SHORT;
// --group (display option)
groupedVersions = options.has(optionGroupedVersions);
// <path-expr>
List<?> nonOptionArgs = options.nonOptionArguments();
if (nonOptionArgs.size() > 0) {
operationOptions.setPathExpression(nonOptionArgs.get(0).toString());
}
return operationOptions;
}
@Override
public void printResults(OperationResult operationResult) {
LsOperationResult concreteOperationResult = (LsOperationResult) operationResult;
int longestSize = calculateLongestSize(concreteOperationResult.getFileTree());
int longestVersion = calculateLongestVersion(concreteOperationResult.getFileTree());
if (fetchHistories) {
printHistories(concreteOperationResult, longestSize, longestVersion);
}
else {
printTree(concreteOperationResult, longestSize, longestVersion);
}
}
private void printTree(LsOperationResult operationResult, int longestSize, int longestVersion) {
for (FileVersion fileVersion : operationResult.getFileTree().values()) {
printOneVersion(fileVersion, longestVersion, longestSize);
}
}
private void printHistories(LsOperationResult operationResult, int longestSize, int longestVersion) {
if (groupedVersions) {
printGroupedHistories(operationResult, longestSize, longestVersion);
}
else {
printNonGroupedHistories(operationResult, longestSize, longestVersion);
}
}
private void printNonGroupedHistories(LsOperationResult operationResult, int longestSize, int longestVersion) {
for (FileVersion fileVersion : operationResult.getFileTree().values()) {
PartialFileHistory fileHistory = operationResult.getFileVersions().get(fileVersion.getFileHistoryId());
for (FileVersion fileVersionInHistory : fileHistory.getFileVersions().values()) {
printOneVersion(fileVersionInHistory, longestVersion, longestSize);
}
}
}
private void printGroupedHistories(LsOperationResult operationResult, int longestSize, int longestVersion) {
Iterator<FileVersion> fileVersionIterator = operationResult.getFileTree().values().iterator();
while (fileVersionIterator.hasNext()) {
FileVersion fileVersion = fileVersionIterator.next();
PartialFileHistory fileHistory = operationResult.getFileVersions().get(fileVersion.getFileHistoryId());
out.printf("File %s, %s\n", formatObjectId(fileHistory.getFileHistoryId()), fileVersion.getPath());
for (FileVersion fileVersionInHistory : fileHistory.getFileVersions().values()) {
if (fileVersionInHistory.equals(fileVersion)) {
out.print(" * ");
}
else {
out.print(" ");
}
printOneVersion(fileVersionInHistory, longestVersion, longestSize);
}
if (fileVersionIterator.hasNext()) {
out.println();
}
}
}
private void printOneVersion(FileVersion fileVersion, int longestVersion, int longestSize) {
String posixPermissions = (fileVersion.getPosixPermissions() != null) ? fileVersion.getPosixPermissions() : "";
String dosAttributes = (fileVersion.getDosAttributes() != null) ? fileVersion.getDosAttributes() : "";
String fileChecksum = formatObjectId(fileVersion.getChecksum());
String fileHistoryId = formatObjectId(fileVersion.getFileHistoryId());
String path = (fileVersion.getType() == FileType.SYMLINK) ? fileVersion.getPath() + " -> " + fileVersion.getLinkTarget() : fileVersion.getPath();
out.printf("%-20s %9s %4s %" + longestSize + "d %8s %" + checksumLength + "s %" + checksumLength + "s %"+longestVersion+"d %s\n",
DATE_FORMAT.format(fileVersion.getUpdated()), posixPermissions, dosAttributes, fileVersion.getSize(), fileVersion.getType(),
fileChecksum, fileHistoryId, fileVersion.getVersion(), path);
}
private String formatObjectId(ObjectId checksum) {
if (checksum == null || "".equals(checksum)) {
return "";
}
else {
return checksum.toString().substring(0, checksumLength);
}
}
private int calculateLongestVersion(Map<String, FileVersion> fileVersions) {
return calculateLongestValue(fileVersions, new Function<FileVersion, Integer>() {
public Integer apply(FileVersion fileVersion) {
return (""+fileVersion.getVersion()).length();
}
});
}
private int calculateLongestSize(Map<String, FileVersion> fileVersions) {
return calculateLongestValue(fileVersions, new Function<FileVersion, Integer>() {
public Integer apply(FileVersion fileVersion) {
return (""+fileVersion.getSize()).length();
}
});
}
private int calculateLongestValue(Map<String, FileVersion> fileVersions, Function<FileVersion, Integer> callbackFunction) {
int result = 0;
for (FileVersion fileVersion : fileVersions.values()) {
result = Math.max(result, callbackFunction.apply(fileVersion));
}
return result;
}
protected Date parseDateOption(String dateStr) throws Exception {
Pattern relativeDatePattern = Pattern.compile("(\\d+(?:[.,]\\d+)?)(mo|[smhdwy])");
Matcher relativeDateMatcher = relativeDatePattern.matcher(dateStr);
if (relativeDateMatcher.find()) {
long restoreDateMillies = CommandLineUtil.parseTimePeriod(dateStr)*1000;
Date restoreDate = new Date(System.currentTimeMillis()-restoreDateMillies);
logger.log(Level.FINE, "Restore date: "+restoreDate);
return restoreDate;
}
else {
try {
Date restoreDate = DATE_FORMAT.parse(dateStr);
logger.log(Level.FINE, "Restore date: "+restoreDate);
return restoreDate;
}
catch (Exception e) {
throw new Exception("Invalid '--date' argument: " + dateStr + ", use relative date or absolute format: " + DATE_FORMAT_PATTERN);
}
}
}
}