Refactored to separate interface from functionality. Added support for progress listeners.

This commit is contained in:
Jonathan Bernard 2015-06-28 08:51:32 -05:00
parent 9cc7547525
commit 660bf35540
4 changed files with 180 additions and 74 deletions

View File

@ -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'

View File

@ -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 "" }
}

View File

@ -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();
}

View File

@ -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
if (opts.rd) relativeRoot = new File(opts.rd[0] ?: '.')
else relativeRoot = new File('.')
def progressListener
if (opts.g) {
// TODO
}
else progressListener = new ConsoleProgressListener(stdout, verbose)
public static void cli(def opts) {
if (opts.q) quiet = true
if (opts.Q) {
quiet = true
progressListener = null }
File rootDir, leftFile, rightFile
DirAnalysis left, right
File leftFile, rightFile
if (opts.rd) rootDir = new File(opts.rd[0] ?: '.')
else rootDir = new File('.')
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) }
def show = [ same: false, content: false, path: false,
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..<childPath.length]).join('/') }
public static File resolvePath(String path, File rootDir) {
public File resolvePath(String path, File rootDir) {
File f
if (path.startsWith('/')) f = new File(path)
else f = new File(rootDir, path)
if (!f.exists()) {
println "TreeDiff v${VERSION}: '${f.canonicalPath}' cannot be found"
strerr.println "TreeDiff v${VERSION}: '${f.canonicalPath}' cannot be found"
System.exit(2) }
return f
}
private void verboseOut(String msg) { if (verbose) stdout.println msg }
private void verboseErr(String msg) { if (verbose) stderr.println msg }
}