1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
package main
import (
"encoding/base32"
"encoding/binary"
"flag"
"fmt"
"hash/fnv"
"io"
"os"
"path/filepath"
"strings"
"github.com/kisom/goutils/fileutil"
"github.com/kisom/goutils/lib"
)
func hashName(path, encodedHash string) string {
basename := filepath.Base(path)
location := filepath.Dir(path)
ext := filepath.Ext(basename)
return filepath.Join(location, encodedHash+ext)
}
func newName(path string) (string, error) {
h := fnv.New32a()
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
_, err = io.Copy(h, f)
if err != nil {
return "", err
}
var buf [8]byte
binary.BigEndian.PutUint32(buf[:], h.Sum32())
encodedHash := base32.StdEncoding.EncodeToString(h.Sum(nil))
encodedHash = strings.TrimRight(encodedHash, "=")
return hashName(path, encodedHash), nil
}
func move(dst, src string, force bool) (err error) {
if fileutil.FileDoesExist(dst) && !force {
return fmt.Errorf("%s exists (pass the -f flag to overwrite)", dst)
return nil
}
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer func(e error) {
dstFile.Close()
if e != nil {
os.Remove(dst)
}
}(err)
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
}
os.Remove(src)
return nil
}
func usage(w io.Writer) {
fmt.Fprintf(w, `Usage: renfnv [-fhlnv] files...
renfnv renames files to the base32-encoded 32-bit FNV-1a hash of their
contents, preserving the dirname and extension.
Options:
-f force overwriting of files when there is a collision.
-h print this help message.
-l list changed files.
-n Perform a dry run: don't actually move files.
-v Print all files as they are processed. If both -v and -l
are specified, it will behave as if only -v was specified.
`)
}
func init() {
flag.Usage = func () { usage(os.Stdout) }
}
func main() {
var dryRun, force, printChanged, verbose bool
flag.BoolVar(&force, "f", false, "force overwriting of files if there is a collision")
flag.BoolVar(&printChanged, "l", false, "list changed files")
flag.BoolVar(&dryRun, "n", false, "dry run --- don't perform moves")
flag.BoolVar(&verbose, "v", false, "list all processed files")
flag.Parse()
if verbose && printChanged {
printChanged = false
}
for _, file := range flag.Args() {
renamed, err := newName(file)
if err != nil {
lib.Warn(err, "failed to get new file name")
continue
}
if verbose && !printChanged {
fmt.Println(file)
}
if renamed != file {
if !dryRun {
err = move(renamed, file, force)
if err != nil {
lib.Warn(err, "failed to rename file from %s to %s", file, renamed)
continue
}
}
if printChanged && !verbose {
fmt.Println(file, "->", renamed)
}
}
}
}
|