/* * This file is part of Fim - File Integrity Manager * * Copyright (C) 2017 Etienne Vrignaud * * Fim 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. * * Fim 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 Fim. If not, see <http://www.gnu.org/licenses/>. */ package org.fim.model; import org.fim.util.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; import static org.atteo.evo.inflector.English.plural; import static org.fim.util.FormatUtil.formatCreationTime; import static org.fim.util.FormatUtil.formatDate; import static org.fim.util.FormatUtil.formatLastModified; public class CompareResult { public static final String NOTHING = "[nothing]"; private static final Comparator<Difference> fileNameComparator = new Difference.FileNameComparator(); private List<Difference> added; private List<Difference> copied; private List<Difference> duplicated; private List<Difference> dateModified; private List<Difference> contentModified; private List<Difference> attributesModified; private List<Difference> renamed; private List<Difference> deleted; private List<Difference> corrupted; private Context context; private State lastState; public CompareResult(Context context, State lastState) { this(context, lastState, null); } public CompareResult(Context context, State lastState, State currentState) { this.context = context; this.lastState = lastState; added = buildModifications(currentState, Modification.added); copied = buildModifications(currentState, Modification.copied); duplicated = buildModifications(currentState, Modification.duplicated); dateModified = buildModifications(currentState, Modification.dateModified); contentModified = buildModifications(currentState, Modification.contentModified); attributesModified = buildModifications(currentState, Modification.attributesModified); renamed = buildModifications(currentState, Modification.renamed); deleted = buildModifications(currentState, Modification.deleted); corrupted = buildModifications(currentState, Modification.corrupted); } private List<Difference> buildModifications(State state, Modification modification) { List<Difference> differences; if (state != null) { differences = state.getFileStates().stream() .filter(fileState -> fileState.getModification() == modification) .map(Difference::new) .collect(Collectors.toList()); } else { differences = new ArrayList<>(); } return differences; } public void sortResults() { sortDifferences(added); sortDifferences(copied); sortDifferences(duplicated); sortDifferences(dateModified); sortDifferences(contentModified); sortDifferences(attributesModified); sortDifferences(renamed); sortDifferences(deleted); sortDifferences(corrupted); } private void sortDifferences(List<Difference> differences) { Collections.sort(differences, fileNameComparator); } public CompareResult displayChanges() { return displayChanges(null); } public CompareResult displayChanges(String notModifiedMessage) { if (lastState != null) { Logger.out.printf("Comparing with the last committed state from %s%n", formatDate(lastState.getTimestamp())); if (lastState.getComment().length() > 0) { Logger.out.println("Comment: " + lastState.getComment()); } Logger.newLine(); } if (context.isVerbose() && somethingModified()) { String stateFormat = "%-17s "; final String addedStr = String.format(stateFormat, "Added:"); displayDifferences(context, addedStr, added, diff -> Logger.out.printf(addedStr + "%s%n", diff.getFileState().getFileName())); final String copiedStr = String.format(stateFormat, "Copied:"); displayDifferences(context, copiedStr, copied, diff -> Logger.out.printf(copiedStr + "%s \t(was %s)%n", diff.getFileState().getFileName(), getPreviousFileName(diff))); final String duplicatedStr = String.format(stateFormat, "Duplicated:"); displayDifferences(context, duplicatedStr, duplicated, diff -> Logger.out.printf(duplicatedStr + "%s = %s%s%n", diff.getFileState().getFileName(), getPreviousFileName(diff), formatModifiedAttributesWithoutTimeChange(diff, true))); final String dateModifiedStr = String.format(stateFormat, "Date modified:"); displayDifferences(context, dateModifiedStr, dateModified, diff -> Logger.out.printf(dateModifiedStr + "%s \t%s%n", diff.getFileState().getFileName(), formatModifiedAttributes(diff, false))); final String contentModifiedStr = String.format(stateFormat, "Content modified:"); displayDifferences(context, contentModifiedStr, contentModified, diff -> Logger.out.printf(contentModifiedStr + "%s \t%s%n", diff.getFileState().getFileName(), formatModifiedAttributes(diff, false))); final String attrsModifiedStr = String.format(stateFormat, "Attrs. modified:"); displayDifferences(context, attrsModifiedStr, attributesModified, diff -> Logger.out.printf(attrsModifiedStr + "%s \t%s%n", diff.getFileState().getFileName(), formatModifiedAttributes(diff, false))); final String renamedStr = String.format(stateFormat, "Renamed:"); displayDifferences(context, renamedStr, renamed, diff -> Logger.out.printf(renamedStr + "%s -> %s%s%n", getPreviousFileName(diff), diff.getFileState().getFileName(), formatModifiedAttributesWithoutTimeChange(diff, true))); final String deletedStr = String.format(stateFormat, "Deleted:"); displayDifferences(context, deletedStr, deleted, diff -> Logger.out.printf(deletedStr + "%s%n", diff.getFileState().getFileName())); final String corruptedStr = String.format(stateFormat, "Corrupted?:"); displayDifferences(context, corruptedStr, corrupted, diff -> Logger.out.printf(corruptedStr + "%s \t%s%n", diff.getFileState().getFileName(), formatModifiedAttributes(diff, false))); Logger.newLine(); } displayCounts(notModifiedMessage); return this; } private static String getPreviousFileName(Difference diff) { if (diff.getPreviousFileState() == null) { // This case happens when displaying log for States produced using Fim before 1.2.1 return "?"; } return diff.getPreviousFileState().getFileName(); } static void displayDifferences(Context context, String actionStr, List<Difference> differences, Consumer<Difference> displayOneDifference) { int truncateOutput = context.getTruncateOutput(); if (truncateOutput < 1) { return; } int quarter = truncateOutput / 4; int differencesSize = differences.size(); for (int index = 0; index < differencesSize; index++) { Difference difference = differences.get(index); if (index >= truncateOutput && (differencesSize - index) > quarter) { Logger.out.println(" [Too many lines. Truncating the output] ..."); int moreFiles = differencesSize - index; Logger.out.printf("%s%d %s more%n", actionStr, moreFiles, plural("file", moreFiles)); break; } if (displayOneDifference != null) { displayOneDifference.accept(difference); } } } static String formatModifiedAttributes(Difference diff, boolean nextLine) { return internalFormatModifiedAttributes(diff, nextLine, true); } private static String formatModifiedAttributesWithoutTimeChange(Difference diff, boolean nextLine) { return internalFormatModifiedAttributes(diff, nextLine, false); } private static String internalFormatModifiedAttributes(Difference diff, boolean nextLine, boolean displayTimeChange) { if (diff.getPreviousFileState() == null) { // This case happens when displaying log for States produced using Fim before 1.2.1 return nextLine ? " ?" : "?"; } int modifCount = 0; StringBuilder modification = new StringBuilder(nextLine ? " " : ""); // Put a white space to force to add a separator Map<String, String> previousFileAttributes = diff.getPreviousFileState().getFileAttributes(); Map<String, String> currentFileAttributes = diff.getFileState().getFileAttributes(); for (FileAttribute attribute : FileAttribute.values()) { String key = attribute.name(); String previousValue = getValue(previousFileAttributes, key); String currentValue = getValue(currentFileAttributes, key); if (false == Objects.equals(previousValue, currentValue)) { modifCount++; addSeparator(diff, modification); modification.append(key).append(": ").append(previousValue).append(" -> ").append(currentValue); } } if (displayTimeChange) { modifCount = +formatTimeChange(diff, modification); } if (modifCount > 1) { modification.append('\n'); } return modification.toString(); } private static int formatTimeChange(Difference diff, StringBuilder modification) { int modifCount = 0; boolean creationTimeChanged = diff.isCreationTimeChanged(); boolean lastModifiedChanged = diff.isLastModifiedChanged(); FileTime fileTime = diff.getFileState().getFileTime(); FileTime previousFileTime = diff.getPreviousFileState().getFileTime(); if (creationTimeChanged && lastModifiedChanged && fileTime.getCreationTime() == fileTime.getLastModified() && previousFileTime.getCreationTime() == previousFileTime.getLastModified()) { modifCount++; addSeparator(diff, modification); modification.append("last modified: ").append(formatLastModified(diff.getPreviousFileState())).append(" -> ").append(formatLastModified(diff.getFileState())); } else { if (creationTimeChanged) { modifCount++; addSeparator(diff, modification); modification.append("creation time: ").append(formatCreationTime(diff.getPreviousFileState())).append(" -> ").append(formatCreationTime(diff.getFileState())); } if (lastModifiedChanged) { modifCount++; addSeparator(diff, modification); modification.append("last modified: ").append(formatLastModified(diff.getPreviousFileState())).append(" -> ").append(formatLastModified(diff.getFileState())); } } return modifCount; } static void addSeparator(Difference diff, StringBuilder modification) { if (modification.length() == 0) { return; } modification.append("\n"); int len = 17 + 1 + diff.getFileState().getFileName().length() + 1; for (int index = 0; index < len; index++) { modification.append(' '); } modification.append('\t'); } static String getValue(Map<String, String> attributes, String key) { String value = attributes != null ? attributes.get(key) : null; if (value == null || value.length() == 0) { value = NOTHING; } return value; } private CompareResult displayCounts(String notModifiedMessage) { if (somethingModified()) { String message = ""; if (!added.isEmpty()) { message += String.format("%d added, ", added.size()); } if (!copied.isEmpty()) { message += String.format("%d copied, ", copied.size()); } if (!duplicated.isEmpty()) { message += String.format("%d duplicated, ", duplicated.size()); } if (!dateModified.isEmpty()) { message += String.format("%d date modified, ", dateModified.size()); } if (!attributesModified.isEmpty()) { message += String.format("%d attrs. modified, ", attributesModified.size()); } if (!contentModified.isEmpty()) { message += String.format("%d content modified, ", contentModified.size()); } if (!renamed.isEmpty()) { message += String.format("%d renamed, ", renamed.size()); } if (!deleted.isEmpty()) { message += String.format("%d deleted, ", deleted.size()); } if (!corrupted.isEmpty()) { message += String.format("%d corrupted, ", corrupted.size()); } message = message.replaceAll(", $", ""); Logger.out.println(message + addExpectIgnored()); } else if (notModifiedMessage != null) { Logger.out.println(notModifiedMessage + addExpectIgnored()); } return this; } private String addExpectIgnored() { Ignored ignored = context.getIgnored(); if (!ignored.somethingIgnored()) { return ""; } StringBuilder sb = new StringBuilder(); sb.append(" (Not taking in account "); if (ignored.isAttributesIgnored()) { sb.append("file attributes, "); } if (ignored.isDatesIgnored()) { sb.append("modification dates, "); } if (ignored.isRenamedIgnored()) { sb.append("renamed files, "); } String result = sb.toString(); result = result.substring(0, result.length() - 2); result += ")"; return result; } public boolean somethingModified() { return modifiedCount() > 0; } public int modifiedCount() { return added.size() + copied.size() + duplicated.size() + dateModified.size() + contentModified.size() + attributesModified.size() + renamed.size() + deleted.size() + corrupted.size(); } public ModificationCounts getModificationCounts() { ModificationCounts modificationCounts = new ModificationCounts(); modificationCounts.setAdded(added.size()); modificationCounts.setCopied(copied.size()); modificationCounts.setDuplicated(duplicated.size()); modificationCounts.setDateModified(dateModified.size()); modificationCounts.setContentModified(contentModified.size()); modificationCounts.setAttributesModified(attributesModified.size()); modificationCounts.setRenamed(renamed.size()); modificationCounts.setDeleted(deleted.size()); return modificationCounts; } public List<Difference> getAdded() { return added; } public List<Difference> getCopied() { return copied; } public List<Difference> getDuplicated() { return duplicated; } public List<Difference> getDateModified() { return dateModified; } public List<Difference> getContentModified() { return contentModified; } public List<Difference> getAttributesModified() { return attributesModified; } public List<Difference> getRenamed() { return renamed; } public List<Difference> getDeleted() { return deleted; } public List<Difference> getCorrupted() { return corrupted; } }