From 660bf355406d07816e5ddb3bb295a6108bb0e17a Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Sun, 28 Jun 2015 08:51:32 -0500 Subject: [PATCH] Refactored to separate interface from functionality. Added support for progress listeners. --- build.gradle | 7 +- .../treedif/ConsoleProgressListener.groovy | 33 +++ .../file/treedif/ProgressListener.java | 9 + .../com/jdblabs/file/treedif/TreeDiff.groovy | 205 ++++++++++++------ 4 files changed, 180 insertions(+), 74 deletions(-) create mode 100644 src/main/groovy/com/jdblabs/file/treedif/ConsoleProgressListener.groovy create mode 100644 src/main/groovy/com/jdblabs/file/treedif/ProgressListener.java diff --git a/build.gradle b/build.gradle index 78e4af6..cdeb737 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: "application" apply plugin: "maven" group = "com.jdblabs" -version = "1.1" +version = "1.2" mainClassName = "com.jdblabs.file.treediff.TreeDiff" repositories { @@ -12,10 +12,7 @@ repositories { dependencies { compile 'org.codehaus.groovy:groovy-all:2.4.3' - compile 'org.slf4j:slf4j-api:1.7.10' - compile 'ch.qos.logback:logback-core:1.1.2' - compile 'ch.qos.logback:logback-classic:1.1.2' - compile 'com.jdbernard:jdb-util:3.5' + compile 'com.jdbernard:jdb-util:3.8' compile 'commons-codec:commons-codec:1.10' compile 'com.fasterxml.jackson.core:jackson-databind:2.4.4' diff --git a/src/main/groovy/com/jdblabs/file/treedif/ConsoleProgressListener.groovy b/src/main/groovy/com/jdblabs/file/treedif/ConsoleProgressListener.groovy new file mode 100644 index 0000000..b49b651 --- /dev/null +++ b/src/main/groovy/com/jdblabs/file/treedif/ConsoleProgressListener.groovy @@ -0,0 +1,33 @@ +package com.jdblabs.file.treediff + +import com.jdbernard.util.ConsoleProgressBar + +public class ConsoleProgressListener implements ProgressListener { + + private ConsoleProgressBar consoleProgressBar + private def out + private boolean verbose + + public ConsoleProgressListener(def out, boolean verbose) { + this.consoleProgressBar = new ConsoleProgressBar(out: out) + this.out = out + this.verbose = verbose } + + public void init(File root, int total) { + out.println "-- ${root.canonicalPath}" + out.println " $total files" + consoleProgressBar.max = total + consoleProgressBar.update(0, root.name) } + + public void update(File curFile, int curCount) { + if (verbose) { + consoleProgressBar.erase() + out.println " ${curFile.canonicalPath}" } + + consoleProgressBar.update(curCount, curFile.name) } + + public void finish() { + consoleProgressBar.erase() + out.println "" } + +} diff --git a/src/main/groovy/com/jdblabs/file/treedif/ProgressListener.java b/src/main/groovy/com/jdblabs/file/treedif/ProgressListener.java new file mode 100644 index 0000000..13a316a --- /dev/null +++ b/src/main/groovy/com/jdblabs/file/treedif/ProgressListener.java @@ -0,0 +1,9 @@ +package com.jdblabs.file.treediff; + +import java.io.File; + +public interface ProgressListener { + void init(File root, int total); + void update(File curFile, int curCount); + void finish(); +} diff --git a/src/main/groovy/com/jdblabs/file/treedif/TreeDiff.groovy b/src/main/groovy/com/jdblabs/file/treedif/TreeDiff.groovy index 6a10c61..22ef9dd 100644 --- a/src/main/groovy/com/jdblabs/file/treedif/TreeDiff.groovy +++ b/src/main/groovy/com/jdblabs/file/treedif/TreeDiff.groovy @@ -1,22 +1,39 @@ package com.jdblabs.file.treediff +import com.fasterxml.jackson.databind.ObjectMapper +import com.jdbernard.util.LightOptionParser import groovy.io.FileType import groovy.swing.SwingBuilder -import com.jdbernard.util.LightOptionParser import org.apache.commons.codec.digest.DigestUtils -import com.fasterxml.jackson.databind.ObjectMapper public class TreeDiff { - public static final String VERSION = "1.1"; + public static final String VERSION = "1.2" - private static ObjectMapper objectMapper = new ObjectMapper() + private ObjectMapper objectMapper = new ObjectMapper() + private PrintStream stdout + private PrintStream stderr + private File relativeRoot + private Map displayFilter + private boolean verbose + private boolean quiet public static void main(String[] args) { + TreeDiff inst = new TreeDiff( + stdout: System.out, + stderr: System.err) + + inst.doDiff(args) + + } + + public void doDiff(String[] args) { + def cliDef = [ 'h': [longName: 'help'], - 'v': [longName: 'version'], + 'v': [longName: 'verbose'], + 'V': [longName: 'version'], 'g': [longName: 'gui'], 'i': [longName: 'analysis-in', arguments: 2], 'o': [longName: 'analysis-out', arguments: 1], @@ -31,6 +48,7 @@ public class TreeDiff { 'r': [longName: 'right-only'], 'R': [longName: 'exclude-right-only'], 'q': [longName: 'quiet'], + 'Q': [longName: 'very-quiet'], 'rd': [longName: 'directory', arguments: 1] ] @@ -38,92 +56,115 @@ public class TreeDiff { if (opts.h) { /* TODO */ return } - if (opts.v) { - println "JDB Labs TreeDiff v${VERSION}" + if (opts.V) { + stdout.println "JDB Labs TreeDiff v${VERSION}" return } - if (opts.g) { gui(opts) } - else cli(opts) - } + verbose = opts.v - public static void cli(def opts) { + if (opts.rd) relativeRoot = new File(opts.rd[0] ?: '.') + else relativeRoot = new File('.') - File rootDir, leftFile, rightFile + def progressListener - if (opts.rd) rootDir = new File(opts.rd[0] ?: '.') - else rootDir = new File('.') + if (opts.g) { + // TODO + } + else progressListener = new ConsoleProgressListener(stdout, verbose) - def show = [ same: false, content: false, path: false, + if (opts.q) quiet = true + if (opts.Q) { + quiet = true + progressListener = null } + + DirAnalysis left, right + File leftFile, rightFile + + if (opts.i) { + leftFile = resolvePath(opts.i[0], relativeRoot) + rightFile = resolvePath(opts.i[1], relativeRoot) + left = objectMapper.readValue(leftFile, DirAnalysis) + right = objectMapper.readValue(rightFile, DirAnalysis) } + + else { + + if (opts.args.size() < 2) { + /* TODO: print usage */ + stderr.println "TreeDiff v${VERSION}: exactly two directory paths are required to compare." + System.exit(1) } + + leftFile = resolvePath(opts.args[0], relativeRoot) + rightFile = resolvePath(opts.args[1], relativeRoot) + + if (!leftFile.isDirectory()) { + stderr.println "TreeDiff v${VERSION}: '${opts.args[0]}' is not a directory" + System.exit(2) } + + if (!rightFile.isDirectory()) { + stderr.println "TreeDiff v${VERSION}: '${opts.args[1]}' is not a directory" + System.exit(2) } + + left = analyzeDir(leftFile, progressListener) + right = analyzeDir(rightFile, progressListener) } + + displayFilter = [ same: false, content: false, path: false, left: false, right: false] // If none of the explicit selectors are given, assume all are expeced. if (!opts.s && !opts.c && !opts.p && !opts.l && !opts.r && !opts.q) { - show = [ same: true, content: true, path: true, + displayFilter = [ same: true, content: true, path: true, left: true, right: true] } - if (opts.s) show.same = true; if (opts.S) show.same = false - if (opts.c) show.content = true; if (opts.C) show.content = false - if (opts.p) show.path = true; if (opts.P) show.path = false - if (opts.l) show.left = true; if (opts.L) show.left = false - if (opts.r) show.right = true; if (opts.R) show.right = false - DirAnalysis left, right; + if (opts.s) displayFilter.same = true + if (opts.S) displayFilter.same = false - if (opts.i) { - leftFile = resolvePath(opts.i[0], rootDir) - rightFile = resolvePath(opts.i[1], rootDir) - left = objectMapper.readValue(leftFile, DirAnalysis) - right = objectMapper.readValue(rightFile, DirAnalysis) - } else { + if (opts.c) displayFilter.content = true + if (opts.C) displayFilter.content = false - if (opts.args.size() < 2) { - /* TODO: print usage */ - println "TreeDiff v${VERSION}: exactly two directory paths are required to compare." - System.exit(1) } + if (opts.p) displayFilter.path = true + if (opts.P) displayFilter.path = false - leftFile = resolvePath(opts.args[0], rootDir) - rightFile = resolvePath(opts.args[0], rootDir) + if (opts.l) displayFilter.left = true + if (opts.L) displayFilter.left = false - if (!leftFile.isDirectory()) { - println "TreeDiff v${VERSION}: '${opts.args[1]}' is not a directory" - System.exit(2) } + if (opts.r) displayFilter.right = true + if (opts.R) displayFilter.right = false - if (!rightFile.isDirectory()) { - println "TreeDiff v${VERSION}: '${opts.args[1]}' is not a directory" - System.exit(2) } - - left = analyzeDir(leftFile) - right = analyzeDir(rightFile) } - - - if (show.same) same(left, right).each { - println "same: ${it.relativePath}" } - - if (show.content) samePathDifferentContents(left, right).each { - println "contents differ: $it" } - - if (show.path) sameContentsDifferentPaths(left, right).each { - println "paths differ: ${it.first.relativePath} ${it.second.relativePath}" } - - if (show.left) firstSideOnly(left, right).each { - println "left only: ${it.relativePath}" } - - if (show.right) firstSideOnly(right, left).each { - println "right only: ${it.relativePath}" } + if (opts.g) displayResultsGui(left, right) + else displayResultsCli(left, right) if (opts.o) { String rootName = opts.o[0] File leftOut, rightOut if (rootName.startsWith('/')) leftOut = new File(rootName + '.left') - else leftOut = new File(rootDir, rootName + '.left') + else leftOut = new File(relativeRoot, rootName + '.left') if (rootName.startsWith('/')) rightOut = new File(rootName + '.right') - else rightOut = new File(rootDir, rootName + '.right') + else rightOut = new File(relativeRoot, rootName + '.right') objectMapper.writeValue(leftOut, left) - objectMapper.writeValue(rightOut, right) - } + objectMapper.writeValue(rightOut, right) } + } + + public void displayResultsCli(DirAnalysis left, DirAnalysis right) { + + if (displayFilter.same) same(left, right).each { + stdout.println "same: ${it.relativePath}" } + + if (displayFilter.content) samePathDifferentContents(left, right).each { + stdout.println "contents differ: $it" } + + if (displayFilter.path) sameContentsDifferentPaths(left, right).each { + stdout.println "paths differ: ${it.first.relativePath} ${it.second.relativePath}" } + + if (displayFilter.left) firstSideOnly(left, right).each { + stdout.println "left only: ${it.relativePath}" } + + if (displayFilter.right) firstSideOnly(right, left).each { + stdout.println "right only: ${it.relativePath}" } + } public static gui(def opts) { @@ -160,22 +201,43 @@ public class TreeDiff { !second.byRelativePath.containsKey(it.relativePath) && !second.byChecksum.containsKey(it.checksum) } } - public static DirAnalysis analyzeDir(File root) { + public DirAnalysis analyzeDir(File root, ProgressListener progressListener) { DirAnalysis analysis = new DirAnalysis() + int totalNumFiles = 0 + int filesProcessed = 0 + + root.eachFileRecurse(FileType.FILES) { totalNumFiles++ } + + boolean showProgress = progressListener != null && + (verbose || totalNumFiles > 100); + + if (progressListener) progressListener.init(root, totalNumFiles) + root.eachFileRecurse(FileType.FILES) { file -> + if (showProgress) progressListener.update(file, ++filesProcessed) + + String checksum = "" + try { checksum = file.withInputStream { DigestUtils.md5Hex(it) } } + catch (Exception e) { + if (!quiet) { + stderr.println "Unable to process file: ${file.canonicalPath}" + stderr.println " details: ${e.getLocalizedMessage()}" } } + FileEntry entry = new FileEntry( file: file, relativePath: getRelativePath(root, file), - checksum: file.withInputStream { DigestUtils.md5Hex(it) }) + checksum: checksum ) - analysis.allEntries << entry; + analysis.allEntries << entry analysis.byRelativePath[entry.relativePath] = entry if (!analysis.byChecksum.containsKey(entry.checksum)) { analysis.byChecksum[entry.checksum] = [] } analysis.byChecksum[entry.checksum] << entry } + if (progressListener) progressListener.finish() + return analysis } @@ -194,7 +256,7 @@ public class TreeDiff { /// Compare the parent and child path up until the end of the parent /// path. int b = 0 - while (b < parentPath.length && parentPath[b] == childPath[b] ) b++; + while (b < parentPath.length && parentPath[b] == childPath[b] ) b++ /// If we stopped before reaching the end of the parent path it must be /// that the paths do not match. The parent cannot contain the child and @@ -202,15 +264,20 @@ public class TreeDiff { if (b != parentPath.length) return "" return (['.'] + childPath[b..