AbstractFileBrowser.st
changeset 17868 fa8c5a7bef99
parent 17865 30a8ebe0a1a5
child 17869 4f02f7359cd2
equal deleted inserted replaced
17867:235d0edd6ffa 17868:fa8c5a7bef99
  2893 
  2893 
  2894 isAbstract
  2894 isAbstract
  2895     ^ self == AbstractFileBrowser
  2895     ^ self == AbstractFileBrowser
  2896 ! !
  2896 ! !
  2897 
  2897 
       
  2898 !AbstractFileBrowser class methodsFor:'queries-file'!
       
  2899 
       
  2900 getBestDirectoryFrom:directories
       
  2901     "from a set of directories, return the common parent directory"
       
  2902 
       
  2903     |stringCol firstPre|
       
  2904 
       
  2905     directories isEmpty ifTrue:[^ nil].
       
  2906     directories size == 1 ifTrue:[^ directories first].
       
  2907     stringCol := (directories collect:[:file| file asString]) asOrderedCollection.
       
  2908     firstPre := stringCol at:1.
       
  2909     stringCol from:2 do:[:el|
       
  2910          firstPre :=  firstPre commonPrefixWith:el.
       
  2911     ].
       
  2912     (firstPre endsWith:(OperatingSystem fileSeparator)) ifTrue:[
       
  2913         firstPre removeLast.
       
  2914     ].
       
  2915     ^ firstPre asFilename
       
  2916 ! !
       
  2917 
  2898 !AbstractFileBrowser class methodsFor:'resources'!
  2918 !AbstractFileBrowser class methodsFor:'resources'!
  2899 
  2919 
  2900 classResources
  2920 classResources
  2901     ^ FileBrowser classResources
  2921     ^ FileBrowser classResources
  2902 ! !
  2922 ! !
  3132         withLimit:limitOrNil
  3152         withLimit:limitOrNil
  3133         lastPart:showLastPartOrNil
  3153         lastPart:showLastPartOrNil
  3134         characterEncoding:characterEncoding
  3154         characterEncoding:characterEncoding
  3135 
  3155 
  3136     "Created: / 12-11-2017 / 12:02:05 / cg"
  3156     "Created: / 12-11-2017 / 12:02:05 / cg"
       
  3157 ! !
       
  3158 
       
  3159 !AbstractFileBrowser class methodsFor:'utilities - files'!
       
  3160 
       
  3161 allFilesInDirectories:directories forWhich:aBlock
       
  3162     |allFiles|
       
  3163 
       
  3164     directories isEmpty ifTrue:[^ #()].
       
  3165 
       
  3166     allFiles := OrderedCollection new.
       
  3167     directories do:[:dir|
       
  3168         [
       
  3169             |fileNames|
       
  3170 
       
  3171             fileNames := dir directoryContents.
       
  3172             fileNames notNil ifTrue:[
       
  3173                 fileNames := fileNames 
       
  3174                                 collect:[:fn | dir construct:fn]
       
  3175                                 thenSelect:[:fn | fn isDirectory not and:[aBlock value:fn]].
       
  3176                 allFiles addAll:fileNames.
       
  3177             ]
       
  3178         ] on:FileStream openErrorSignal do:[:ex|
       
  3179             self warn:(self classResources stringWithCRs:'Cannot access: %1\(%2)' 
       
  3180                             with:ex pathName
       
  3181                             with:ex description).
       
  3182             ex proceedWith:nil.
       
  3183         ].
       
  3184     ].
       
  3185 
       
  3186     ^ allFiles
       
  3187 !
       
  3188 
       
  3189 fileFindDuplicatesIn:directories
       
  3190     "scan directories for duplicate files.
       
  3191      return a dictionary mapping files to their duplicate(s);
       
  3192      the oldest file found will be the key, the younger files in the value (a collection).
       
  3193      Files without duplicate(s) will not have an entry in the dictionary."
       
  3194 
       
  3195     |infoDir filesBySize anySameSized 
       
  3196      result allFiles|
       
  3197 
       
  3198     directories isEmpty ifTrue:[^ #()].
       
  3199 
       
  3200     allFiles := self allFilesInDirectories:directories forWhich:[:f | true].
       
  3201     allFiles isEmpty ifTrue:[^ #()].
       
  3202 
       
  3203     "/ for each, get the file's info (size, modificationTime etc.).
       
  3204     infoDir := Dictionary new.
       
  3205     allFiles do:[:fn |
       
  3206         infoDir at:fn put:(fn info)
       
  3207     ].
       
  3208 
       
  3209     "/ in a first pass, collect groups by the same size
       
  3210     "/ remember those fileSizes for which multiple files exist
       
  3211     filesBySize := Dictionary new.
       
  3212     anySameSized := false.
       
  3213     infoDir keysAndValuesDo:[:fn :info |
       
  3214         |sz entry|
       
  3215 
       
  3216         sz := info fileSize.
       
  3217         entry := filesBySize at:sz ifAbsentPut:[Set new].
       
  3218         entry add:fn.
       
  3219         entry size > 1 ifTrue:[ anySameSized := true ].
       
  3220     ].
       
  3221 
       
  3222     "/ any of same size ?
       
  3223     anySameSized ifFalse:[^ #() ].
       
  3224 
       
  3225     result := Dictionary new.
       
  3226 
       
  3227     "/ now walk over the same-sized files and compare the contents.
       
  3228     filesBySize do:[:entry |
       
  3229         |files|
       
  3230 
       
  3231         entry size > 1 ifTrue:[
       
  3232             files := entry asArray.
       
  3233             files 
       
  3234                 sort:[:a :b |
       
  3235                     |tA tB|
       
  3236                     tA := (infoDir at:a) modificationTime.
       
  3237                     tB := (infoDir at:b) modificationTime.
       
  3238                     tA < tB
       
  3239                 ].
       
  3240 
       
  3241             1 to:files size-1 do:[:idx1 |
       
  3242                 |fn1|
       
  3243 
       
  3244                 fn1 := files at:idx1.
       
  3245                 idx1+1 to:files size do:[:idx2 |
       
  3246                     |fn2|
       
  3247 
       
  3248                     fn2 := files at:idx2.
       
  3249 
       
  3250                     (result at:fn2 ifAbsent:nil) ~= fn1 ifTrue:[
       
  3251                         "/ compare the files
       
  3252                         (fn1 sameContentsAs:fn2) ifTrue:[
       
  3253                             "/ Transcript show:'Same: '; show:fn1 baseName; show:' and '; showCR:fn2 baseName.
       
  3254                             result at:fn1 put:fn2.
       
  3255                         ]
       
  3256                     ]
       
  3257                 ]
       
  3258             ]
       
  3259         ]
       
  3260     ].
       
  3261 
       
  3262     result := result associations.
       
  3263     result := result collect:[:assoc |
       
  3264                                     |f1 f2|
       
  3265 
       
  3266                                     f1 := assoc key asString.
       
  3267                                     f2 := assoc value asString.
       
  3268                                     f1 < f2 ifTrue:[
       
  3269                                         f2 -> f1
       
  3270                                     ] ifFalse:[
       
  3271                                         f1 -> f2
       
  3272                                     ]
       
  3273                             ].
       
  3274     "/ result sort:[:f1 :f2 | f1 key > f2 key "f2 value < f1 key value"].
       
  3275     result sort:[:f1 :f2 | " f1 key > f2 key" f2 value < f1 key value].
       
  3276     ^ result
  3137 ! !
  3277 ! !
  3138 
  3278 
  3139 !AbstractFileBrowser methodsFor:'actions'!
  3279 !AbstractFileBrowser methodsFor:'actions'!
  3140 
  3280 
  3141 askForCommandFor:fileName thenDo:aBlock
  3281 askForCommandFor:fileName thenDo:aBlock
  6431 ! !
  6571 ! !
  6432 
  6572 
  6433 !AbstractFileBrowser methodsFor:'menu actions-tools'!
  6573 !AbstractFileBrowser methodsFor:'menu actions-tools'!
  6434 
  6574 
  6435 allFilesInSelectedDirectoriesForWhich:aBlock
  6575 allFilesInSelectedDirectoriesForWhich:aBlock
  6436     |directories allFiles|
  6576     ^ self class allFilesInDirectories:(self currentSelectedDirectories) forWhich:aBlock
  6437 
       
  6438     directories := self currentSelectedDirectories.
       
  6439     directories isEmpty ifTrue:[^ self].
       
  6440 
       
  6441     allFiles := OrderedCollection new.
       
  6442     directories do:[:dir|
       
  6443         [
       
  6444             |fileNames|
       
  6445 
       
  6446             fileNames := dir directoryContents.
       
  6447             fileNames notNil ifTrue:[
       
  6448                 fileNames := fileNames 
       
  6449                                 collect:[:fn | dir construct:fn]
       
  6450                                 thenSelect:[:fn | fn isDirectory not and:[aBlock value:fn]].
       
  6451                 allFiles addAll:fileNames.
       
  6452             ]
       
  6453         ] on:FileStream openErrorSignal do:[:ex|
       
  6454             self warn:(resources stringWithCRs:'Cannot access: %1\(%2)' 
       
  6455                             with:ex pathName
       
  6456                             with:ex description).
       
  6457             ex proceedWith:nil.
       
  6458         ].
       
  6459     ].
       
  6460 
       
  6461     ^ allFiles
       
  6462 !
  6577 !
  6463 
  6578 
  6464 conversionChainFrom:inSuffix to:outSuffix
  6579 conversionChainFrom:inSuffix to:outSuffix
  6465     |conv|
  6580     |conv|
  6466 
  6581 
  7280 !
  7395 !
  7281 
  7396 
  7282 fileFindDuplicates
  7397 fileFindDuplicates
  7283     "scan directory for duplicate files"
  7398     "scan directory for duplicate files"
  7284 
  7399 
  7285     |infoDir filesBySize result info stream textBox maxLength directories allFiles titleStream size|
  7400     |directories duplicatesDictionary info prefixSize stream titleStream textBox maxLength|
  7286 
  7401 
  7287     directories := self currentSelectedDirectories.
  7402     directories := self currentSelectedDirectories.
  7288 
  7403     duplicatesDictionary := self class fileFindDuplicatesIn:directories.
  7289     self withActivityIndicationDo:[
  7404     duplicatesDictionary isEmpty ifTrue:[
  7290         result := Dictionary new.
  7405         Dialog information:'No duplicate files found.'.
  7291 
  7406         ^ self.
  7292         directories := self currentSelectedDirectories.
  7407     ].
  7293         directories isEmpty ifTrue:[^ self].
  7408 
  7294 
  7409     info := OrderedCollection new.
  7295         allFiles := self allFilesInSelectedDirectoriesForWhich:[:f | true].
  7410     prefixSize := (self getBestDirectory) asString size.
  7296 
  7411     duplicatesDictionary do:[:assoc |
  7297         infoDir := Dictionary new.
  7412         |fn1 fn2|
  7298         allFiles do:[:fn |
  7413 
  7299             infoDir at:fn put:(fn info)
  7414         fn1 := assoc key.
  7300         ].
  7415         fn2 := assoc value.
  7301 
  7416         prefixSize > 1 ifTrue:[
  7302         "/ for each, get the file's size.
  7417             fn1 := ('..', (fn1 copyFrom:(prefixSize + 1))).
  7303         "/ in a first pass, look for files of the same size and
  7418             fn2 := ('..', (fn2 copyFrom:(prefixSize + 1))).
  7304         "/ compare them ...
  7419         ].
  7305 
  7420         (fn1 includes:Character space) ifTrue:[
  7306         filesBySize := Dictionary new.
  7421             fn1 := '"' , fn1 , '"'
  7307         infoDir keysAndValuesDo:[:fn :info |
  7422         ].
  7308             |sz entry|
  7423         (fn2 includes:Character space) ifTrue:[
  7309 
  7424             fn2 := '"' , fn2 , '"'
  7310             sz := info size.
  7425         ].
  7311             entry := filesBySize at:sz ifAbsentPut:[Set new].
  7426         info add:(fn1 , ' same as ' , fn2)
  7312             entry add:fn.
  7427     ].
  7313         ].
  7428 
  7314 
       
  7315         "/ any of same size ?
       
  7316 
       
  7317         filesBySize do:[:entry |
       
  7318             |files|
       
  7319 
       
  7320             entry size > 1 ifTrue:[
       
  7321                 files := entry asArray.
       
  7322                 1 to:files size-1 do:[:idx1 |
       
  7323                     idx1+1 to:files size do:[:idx2 |
       
  7324                         |fn1 fn2|
       
  7325 
       
  7326                         fn1 := files at:idx1.
       
  7327                         fn2 := files at:idx2.
       
  7328 
       
  7329                         (result at:fn2 ifAbsent:nil) ~= fn1 ifTrue:[
       
  7330                             "/ compare the files
       
  7331                             (fn1 sameContentsAs:fn2) ifTrue:[
       
  7332 "/                                Transcript show:'Same: '; show:fn1 baseName; show:' and '; showCR:fn2 baseName.
       
  7333                                 result at:fn1 put:fn2.
       
  7334                             ]
       
  7335                         ]
       
  7336                     ]
       
  7337                 ]
       
  7338             ]
       
  7339         ].
       
  7340 
       
  7341         result := result associations.
       
  7342         result := result collect:[:assoc |
       
  7343                                         |f1 f2|
       
  7344 
       
  7345                                         f1 := assoc key asString.
       
  7346                                         f2 := assoc value asString.
       
  7347                                         f1 < f2 ifTrue:[
       
  7348                                             f2 -> f1
       
  7349                                         ] ifFalse:[
       
  7350                                             f1 -> f2
       
  7351                                         ]
       
  7352                                 ].
       
  7353         "/ result sort:[:f1 :f2 | f1 key > f2 key "f2 value < f1 key value"].
       
  7354         result sort:[:f1 :f2 | " f1 key > f2 key" f2 value < f1 key value].
       
  7355 
       
  7356         info := OrderedCollection new.
       
  7357         size := self getBestDirectory asString size.
       
  7358         result do:[:assoc |
       
  7359             |fn1 fn2|
       
  7360 
       
  7361             fn1 := assoc key.
       
  7362             fn2 := assoc value.
       
  7363             size > 1 ifTrue:[
       
  7364                 fn1 := ('..', (fn1 copyFrom:(size + 1))).
       
  7365                 fn2 := ('..', (fn2 copyFrom:(size + 1))).
       
  7366             ].
       
  7367             (fn1 includes:Character space) ifTrue:[
       
  7368                 fn1 := '"' , fn1 , '"'
       
  7369             ].
       
  7370             (fn2 includes:Character space) ifTrue:[
       
  7371                 fn2 := '"' , fn2 , '"'
       
  7372             ].
       
  7373             info add:(fn1 , ' same as ' , fn2)
       
  7374         ].
       
  7375         info isEmpty ifTrue:[
       
  7376             Dialog information:'No duplicate files found.'.
       
  7377             ^ self.
       
  7378         ].
       
  7379     ].
       
  7380     stream := CharacterWriteStream new.
  7429     stream := CharacterWriteStream new.
  7381     stream nextPutAllLines:info.
  7430     stream nextPutAllLines:info.
  7382 
  7431 
  7383     titleStream := CharacterWriteStream with:'File duplicates in directory'.
  7432     titleStream := CharacterWriteStream with:'File duplicates in directory'.
  7384     directories size == 1 ifTrue:[
  7433     directories size == 1 ifTrue:[
  7385         titleStream nextPutAll:'y: '; nextPutAll: directories first asString.
  7434         titleStream nextPutAll:'y: '; nextPutAll: directories first asString.
  7386     ] ifFalse:[
  7435     ] ifFalse:[
  7387         titleStream nextPutLine:'ies: '.
  7436         titleStream nextPutLine:'ies: '.
  7388         directories do:[:dir|
  7437         directories do:[:dir|
  7389             size > 1 ifTrue:[
  7438             prefixSize > 1 ifTrue:[
  7390                 titleStream nextPutAll:'..'.
  7439                 titleStream nextPutAll:'..'.
  7391                 titleStream nextPutLine:((dir asString) copyFrom:(size + 1)).
  7440                 titleStream nextPutLine:((dir asString) copyFrom:(prefixSize + 1)).
  7392             ] ifFalse:[
  7441             ] ifFalse:[
  7393                 titleStream nextPutLine:(dir asString).
  7442                 titleStream nextPutLine:(dir asString).
  7394             ].
  7443             ].
  7395         ]
  7444         ]
  7396     ].
  7445     ].
  9340 
  9389 
  9341     ^ nil
  9390     ^ nil
  9342 !
  9391 !
  9343 
  9392 
  9344 getBestDirectory
  9393 getBestDirectory
  9345 
  9394     "from a set of directories, return the common parent directory"
  9346     |selFiles stringCol firstPre|
  9395 
  9347 
  9396     ^ self class getBestDirectoryFrom:(self currentSelectedDirectories)
  9348     selFiles := self currentSelectedDirectories.
       
  9349     selFiles isEmpty ifTrue:[^ nil].
       
  9350     selFiles size == 1 ifTrue:[^ selFiles first].
       
  9351     stringCol := (selFiles collect:[:file| file asString]) asOrderedCollection.
       
  9352     firstPre := stringCol at:1.
       
  9353     stringCol from:2 do:[:el|
       
  9354          firstPre :=  firstPre commonPrefixWith:el.
       
  9355     ].
       
  9356     (firstPre endsWith:(OperatingSystem fileSeparator)) ifTrue:[
       
  9357         firstPre removeLast.
       
  9358     ].
       
  9359     ^ firstPre asFilename
       
  9360 !
  9397 !
  9361 
  9398 
  9362 getDirWithoutFileName:aFileName
  9399 getDirWithoutFileName:aFileName
  9363 
  9400 
  9364     | dir |
  9401     | dir |