From e8045e0b1f70fd60137991f466c3c736d30a336d Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 23 Aug 2016 12:59:34 -0700 Subject: [PATCH] Add renfnv tool. This is a tool I use for renaming files when doing photographic work. This change also renames the functions in fileutil to not be P functions. --- cmd/renfnv/README | 15 +++++ cmd/renfnv/renfnv.go | 136 +++++++++++++++++++++++++++++++++++++++++++ fileutil/fileutil.go | 8 +-- 3 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 cmd/renfnv/README create mode 100644 cmd/renfnv/renfnv.go diff --git a/cmd/renfnv/README b/cmd/renfnv/README new file mode 100644 index 0000000..13b6dee --- /dev/null +++ b/cmd/renfnv/README @@ -0,0 +1,15 @@ +renfnv + +This renames a file to the base32-encoded 64-bit FNV-1a hash of the +file's contents. The extension is preserved. This was motivated by +a desire to normalise names for photographs. + +Usage: renfnv [-fhlnv] files... + + -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. + diff --git a/cmd/renfnv/renfnv.go b/cmd/renfnv/renfnv.go new file mode 100644 index 0000000..a4cf4cd --- /dev/null +++ b/cmd/renfnv/renfnv.go @@ -0,0 +1,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) + } + } + } +} diff --git a/fileutil/fileutil.go b/fileutil/fileutil.go index b7575ed..b667696 100644 --- a/fileutil/fileutil.go +++ b/fileutil/fileutil.go @@ -7,14 +7,14 @@ import ( "golang.org/x/sys/unix" ) -// FileExistsP returns true if the file exists. -func FileExistsP(path string) bool { +// FileDoesExist returns true if the file exists. +func FileDoesExist(path string) bool { _, err := os.Stat(path) return !os.IsNotExist(err) } -// DirectoryExistsP returns true if the file exists. -func DirectoryExistsP(path string) bool { +// DirectoryDoesExist returns true if the file exists. +func DirectoryDoesExist(path string) bool { fi, err := os.Stat(path) if err != nil { return false