AbstractFileBrowser.st
changeset 17868 fa8c5a7bef99
parent 17865 30a8ebe0a1a5
child 17869 4f02f7359cd2
--- a/AbstractFileBrowser.st	Mon Jan 01 10:58:48 2018 +0100
+++ b/AbstractFileBrowser.st	Mon Jan 01 11:00:34 2018 +0100
@@ -2895,6 +2895,26 @@
     ^ self == AbstractFileBrowser
 ! !
 
+!AbstractFileBrowser class methodsFor:'queries-file'!
+
+getBestDirectoryFrom:directories
+    "from a set of directories, return the common parent directory"
+
+    |stringCol firstPre|
+
+    directories isEmpty ifTrue:[^ nil].
+    directories size == 1 ifTrue:[^ directories first].
+    stringCol := (directories collect:[:file| file asString]) asOrderedCollection.
+    firstPre := stringCol at:1.
+    stringCol from:2 do:[:el|
+         firstPre :=  firstPre commonPrefixWith:el.
+    ].
+    (firstPre endsWith:(OperatingSystem fileSeparator)) ifTrue:[
+        firstPre removeLast.
+    ].
+    ^ firstPre asFilename
+! !
+
 !AbstractFileBrowser class methodsFor:'resources'!
 
 classResources
@@ -3136,6 +3156,126 @@
     "Created: / 12-11-2017 / 12:02:05 / cg"
 ! !
 
+!AbstractFileBrowser class methodsFor:'utilities - files'!
+
+allFilesInDirectories:directories forWhich:aBlock
+    |allFiles|
+
+    directories isEmpty ifTrue:[^ #()].
+
+    allFiles := OrderedCollection new.
+    directories do:[:dir|
+        [
+            |fileNames|
+
+            fileNames := dir directoryContents.
+            fileNames notNil ifTrue:[
+                fileNames := fileNames 
+                                collect:[:fn | dir construct:fn]
+                                thenSelect:[:fn | fn isDirectory not and:[aBlock value:fn]].
+                allFiles addAll:fileNames.
+            ]
+        ] on:FileStream openErrorSignal do:[:ex|
+            self warn:(self classResources stringWithCRs:'Cannot access: %1\(%2)' 
+                            with:ex pathName
+                            with:ex description).
+            ex proceedWith:nil.
+        ].
+    ].
+
+    ^ allFiles
+!
+
+fileFindDuplicatesIn:directories
+    "scan directories for duplicate files.
+     return a dictionary mapping files to their duplicate(s);
+     the oldest file found will be the key, the younger files in the value (a collection).
+     Files without duplicate(s) will not have an entry in the dictionary."
+
+    |infoDir filesBySize anySameSized 
+     result allFiles|
+
+    directories isEmpty ifTrue:[^ #()].
+
+    allFiles := self allFilesInDirectories:directories forWhich:[:f | true].
+    allFiles isEmpty ifTrue:[^ #()].
+
+    "/ for each, get the file's info (size, modificationTime etc.).
+    infoDir := Dictionary new.
+    allFiles do:[:fn |
+        infoDir at:fn put:(fn info)
+    ].
+
+    "/ in a first pass, collect groups by the same size
+    "/ remember those fileSizes for which multiple files exist
+    filesBySize := Dictionary new.
+    anySameSized := false.
+    infoDir keysAndValuesDo:[:fn :info |
+        |sz entry|
+
+        sz := info fileSize.
+        entry := filesBySize at:sz ifAbsentPut:[Set new].
+        entry add:fn.
+        entry size > 1 ifTrue:[ anySameSized := true ].
+    ].
+
+    "/ any of same size ?
+    anySameSized ifFalse:[^ #() ].
+
+    result := Dictionary new.
+
+    "/ now walk over the same-sized files and compare the contents.
+    filesBySize do:[:entry |
+        |files|
+
+        entry size > 1 ifTrue:[
+            files := entry asArray.
+            files 
+                sort:[:a :b |
+                    |tA tB|
+                    tA := (infoDir at:a) modificationTime.
+                    tB := (infoDir at:b) modificationTime.
+                    tA < tB
+                ].
+
+            1 to:files size-1 do:[:idx1 |
+                |fn1|
+
+                fn1 := files at:idx1.
+                idx1+1 to:files size do:[:idx2 |
+                    |fn2|
+
+                    fn2 := files at:idx2.
+
+                    (result at:fn2 ifAbsent:nil) ~= fn1 ifTrue:[
+                        "/ compare the files
+                        (fn1 sameContentsAs:fn2) ifTrue:[
+                            "/ Transcript show:'Same: '; show:fn1 baseName; show:' and '; showCR:fn2 baseName.
+                            result at:fn1 put:fn2.
+                        ]
+                    ]
+                ]
+            ]
+        ]
+    ].
+
+    result := result associations.
+    result := result collect:[:assoc |
+                                    |f1 f2|
+
+                                    f1 := assoc key asString.
+                                    f2 := assoc value asString.
+                                    f1 < f2 ifTrue:[
+                                        f2 -> f1
+                                    ] ifFalse:[
+                                        f1 -> f2
+                                    ]
+                            ].
+    "/ result sort:[:f1 :f2 | f1 key > f2 key "f2 value < f1 key value"].
+    result sort:[:f1 :f2 | " f1 key > f2 key" f2 value < f1 key value].
+    ^ result
+! !
+
 !AbstractFileBrowser methodsFor:'actions'!
 
 askForCommandFor:fileName thenDo:aBlock
@@ -6433,32 +6573,7 @@
 !AbstractFileBrowser methodsFor:'menu actions-tools'!
 
 allFilesInSelectedDirectoriesForWhich:aBlock
-    |directories allFiles|
-
-    directories := self currentSelectedDirectories.
-    directories isEmpty ifTrue:[^ self].
-
-    allFiles := OrderedCollection new.
-    directories do:[:dir|
-        [
-            |fileNames|
-
-            fileNames := dir directoryContents.
-            fileNames notNil ifTrue:[
-                fileNames := fileNames 
-                                collect:[:fn | dir construct:fn]
-                                thenSelect:[:fn | fn isDirectory not and:[aBlock value:fn]].
-                allFiles addAll:fileNames.
-            ]
-        ] on:FileStream openErrorSignal do:[:ex|
-            self warn:(resources stringWithCRs:'Cannot access: %1\(%2)' 
-                            with:ex pathName
-                            with:ex description).
-            ex proceedWith:nil.
-        ].
-    ].
-
-    ^ allFiles
+    ^ self class allFilesInDirectories:(self currentSelectedDirectories) forWhich:aBlock
 !
 
 conversionChainFrom:inSuffix to:outSuffix
@@ -7282,101 +7397,35 @@
 fileFindDuplicates
     "scan directory for duplicate files"
 
-    |infoDir filesBySize result info stream textBox maxLength directories allFiles titleStream size|
+    |directories duplicatesDictionary info prefixSize stream titleStream textBox maxLength|
 
     directories := self currentSelectedDirectories.
-
-    self withActivityIndicationDo:[
-        result := Dictionary new.
-
-        directories := self currentSelectedDirectories.
-        directories isEmpty ifTrue:[^ self].
-
-        allFiles := self allFilesInSelectedDirectoriesForWhich:[:f | true].
-
-        infoDir := Dictionary new.
-        allFiles do:[:fn |
-            infoDir at:fn put:(fn info)
-        ].
-
-        "/ for each, get the file's size.
-        "/ in a first pass, look for files of the same size and
-        "/ compare them ...
-
-        filesBySize := Dictionary new.
-        infoDir keysAndValuesDo:[:fn :info |
-            |sz entry|
-
-            sz := info size.
-            entry := filesBySize at:sz ifAbsentPut:[Set new].
-            entry add:fn.
-        ].
-
-        "/ any of same size ?
-
-        filesBySize do:[:entry |
-            |files|
-
-            entry size > 1 ifTrue:[
-                files := entry asArray.
-                1 to:files size-1 do:[:idx1 |
-                    idx1+1 to:files size do:[:idx2 |
-                        |fn1 fn2|
-
-                        fn1 := files at:idx1.
-                        fn2 := files at:idx2.
-
-                        (result at:fn2 ifAbsent:nil) ~= fn1 ifTrue:[
-                            "/ compare the files
-                            (fn1 sameContentsAs:fn2) ifTrue:[
-"/                                Transcript show:'Same: '; show:fn1 baseName; show:' and '; showCR:fn2 baseName.
-                                result at:fn1 put:fn2.
-                            ]
-                        ]
-                    ]
-                ]
-            ]
-        ].
-
-        result := result associations.
-        result := result collect:[:assoc |
-                                        |f1 f2|
-
-                                        f1 := assoc key asString.
-                                        f2 := assoc value asString.
-                                        f1 < f2 ifTrue:[
-                                            f2 -> f1
-                                        ] ifFalse:[
-                                            f1 -> f2
-                                        ]
-                                ].
-        "/ result sort:[:f1 :f2 | f1 key > f2 key "f2 value < f1 key value"].
-        result sort:[:f1 :f2 | " f1 key > f2 key" f2 value < f1 key value].
-
-        info := OrderedCollection new.
-        size := self getBestDirectory asString size.
-        result do:[:assoc |
-            |fn1 fn2|
-
-            fn1 := assoc key.
-            fn2 := assoc value.
-            size > 1 ifTrue:[
-                fn1 := ('..', (fn1 copyFrom:(size + 1))).
-                fn2 := ('..', (fn2 copyFrom:(size + 1))).
-            ].
-            (fn1 includes:Character space) ifTrue:[
-                fn1 := '"' , fn1 , '"'
-            ].
-            (fn2 includes:Character space) ifTrue:[
-                fn2 := '"' , fn2 , '"'
-            ].
-            info add:(fn1 , ' same as ' , fn2)
-        ].
-        info isEmpty ifTrue:[
-            Dialog information:'No duplicate files found.'.
-            ^ self.
-        ].
-    ].
+    duplicatesDictionary := self class fileFindDuplicatesIn:directories.
+    duplicatesDictionary isEmpty ifTrue:[
+        Dialog information:'No duplicate files found.'.
+        ^ self.
+    ].
+
+    info := OrderedCollection new.
+    prefixSize := (self getBestDirectory) asString size.
+    duplicatesDictionary do:[:assoc |
+        |fn1 fn2|
+
+        fn1 := assoc key.
+        fn2 := assoc value.
+        prefixSize > 1 ifTrue:[
+            fn1 := ('..', (fn1 copyFrom:(prefixSize + 1))).
+            fn2 := ('..', (fn2 copyFrom:(prefixSize + 1))).
+        ].
+        (fn1 includes:Character space) ifTrue:[
+            fn1 := '"' , fn1 , '"'
+        ].
+        (fn2 includes:Character space) ifTrue:[
+            fn2 := '"' , fn2 , '"'
+        ].
+        info add:(fn1 , ' same as ' , fn2)
+    ].
+
     stream := CharacterWriteStream new.
     stream nextPutAllLines:info.
 
@@ -7386,9 +7435,9 @@
     ] ifFalse:[
         titleStream nextPutLine:'ies: '.
         directories do:[:dir|
-            size > 1 ifTrue:[
+            prefixSize > 1 ifTrue:[
                 titleStream nextPutAll:'..'.
-                titleStream nextPutLine:((dir asString) copyFrom:(size + 1)).
+                titleStream nextPutLine:((dir asString) copyFrom:(prefixSize + 1)).
             ] ifFalse:[
                 titleStream nextPutLine:(dir asString).
             ].
@@ -9342,21 +9391,9 @@
 !
 
 getBestDirectory
-
-    |selFiles stringCol firstPre|
-
-    selFiles := self currentSelectedDirectories.
-    selFiles isEmpty ifTrue:[^ nil].
-    selFiles size == 1 ifTrue:[^ selFiles first].
-    stringCol := (selFiles collect:[:file| file asString]) asOrderedCollection.
-    firstPre := stringCol at:1.
-    stringCol from:2 do:[:el|
-         firstPre :=  firstPre commonPrefixWith:el.
-    ].
-    (firstPre endsWith:(OperatingSystem fileSeparator)) ifTrue:[
-        firstPre removeLast.
-    ].
-    ^ firstPre asFilename
+    "from a set of directories, return the common parent directory"
+
+    ^ self class getBestDirectoryFrom:(self currentSelectedDirectories)
 !
 
 getDirWithoutFileName:aFileName