diff --git a/README.md b/README.md index 10bc317..d2e32cd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,43 @@ # hashdir -Generate hash for folder structure + +Generate hash of all files and they paths for specified directory. + +```go +package main + +import ( + "fmt" + + "github.com/gosimple/hashdir" +) + +func main() { + dirHash, err := hashdir.Make("./someDir/", "md5") + fmt.Println(dirHash) +} +``` + +Supported hashes: + +* md5 +* sha1 +* sha256 +* sha512 + +### Requests or bugs? + + + +## Installation + +```sh +go get -u github.com/gosimple/hashdir +``` + +## License + +The source files are distributed under the +[Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/), +unless otherwise noted. +Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) +if you have further questions regarding the license. diff --git a/assets/one_more/testowe3.txt b/assets/one_more/testowe3.txt new file mode 100644 index 0000000..2be9c46 --- /dev/null +++ b/assets/one_more/testowe3.txt @@ -0,0 +1 @@ +My insides are ready to check ;) diff --git a/assets/testowe.txt b/assets/testowe.txt new file mode 100644 index 0000000..0774e36 --- /dev/null +++ b/assets/testowe.txt @@ -0,0 +1 @@ +check me diff --git a/assets/testowe2.txt b/assets/testowe2.txt new file mode 100644 index 0000000..87001f3 --- /dev/null +++ b/assets/testowe2.txt @@ -0,0 +1 @@ +Don't forget about checking me :( diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..dad30c8 --- /dev/null +++ b/doc.go @@ -0,0 +1,29 @@ +// Copyright 2020 by Dobrosław Żybort. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/* +Package hashdir generate hash of all files and they paths for specified +directory. + +Example: + + package main + + import ( + "fmt" + + "github.com/gosimple/hashdir" + ) + + func main() { + dirHash, err := hashdir.Make("./someDir/", "md5") + fmt.Println(dirHash) + } + +Requests or bugs? + +https://github.com/gosimple/hashdir/issues +*/ +package hashdir diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8bb29c6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.org/gosimple/hashdir + +go 1.14 diff --git a/hashdir.go b/hashdir.go new file mode 100644 index 0000000..05b2ae2 --- /dev/null +++ b/hashdir.go @@ -0,0 +1,123 @@ +package hashdir + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "errors" + "hash" + "io" + "os" + "path/filepath" + "strings" +) + +// Make generate hash of all files and they paths for specified directory. +func Make(dir string, hashType string) (string, error) { + var endHash string + + bigErr := filepath.Walk(dir, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + endHash, err = hashData(endHash, hashType) + if err != nil { + return err + } + + if !info.IsDir() { + pathHash, err := hashData(path, hashType) + if err != nil { + return err + } + fileHash, err := hashFile(path, hashType) + if err != nil { + return err + } + // log.Println(path, fileHash, info.ModTime()) + endHash = endHash + pathHash + fileHash + } + return nil + }) + + if bigErr != nil { + return "", bigErr + } + endHash, err := hashData(endHash, hashType) + if err != nil { + return "", err + } + return endHash, err +} + +func selectHash(hashType string) (hash.Hash, error) { + switch strings.ToLower(hashType) { + case "md5": + return md5.New(), nil + case "sha1": + return sha1.New(), nil + case "sha256": + return sha256.New(), nil + case "sha512": + return sha512.New(), nil + } + return nil, errors.New("Unknown hash: " + hashType) +} + +func hashData(data string, hashType string) (string, error) { + h, err := selectHash(hashType) + if err != nil { + return "", err + } + + _, err = h.Write([]byte(data)) + if err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +func hashFile(path string, hashType string) (string, error) { + // Handle hashing big files. + // Source: https://stackoverflow.com/q/60328216/1722542 + + f, err := os.Open(path) + if err != nil { + return "", err + } + + defer func() { + _ = f.Close() + }() + + buf := make([]byte, 1024*1024) + h, err := selectHash(hashType) + if err != nil { + return "", err + } + + for { + bytesRead, err := f.Read(buf) + if err != nil { + if err != io.EOF { + return "", err + } + _, err = h.Write(buf[:bytesRead]) + if err != nil { + return "", err + } + break + } + _, err = h.Write(buf[:bytesRead]) + if err != nil { + return "", err + } + } + + fileHash := hex.EncodeToString(h.Sum(nil)) + return fileHash, nil +} diff --git a/hashdir_test.go b/hashdir_test.go new file mode 100644 index 0000000..91c092a --- /dev/null +++ b/hashdir_test.go @@ -0,0 +1,83 @@ +package hashdir + +import "testing" + +func TestMake(t *testing.T) { + type args struct { + root string + hashType string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "Test making MD5 hash", + args{ + "./assets/", + "md5", + }, + "2d03afa7a1d8e7de98ce8a56660f86ff", + false, + }, + { + "Test making SHA1 hash", + args{ + "./assets/", + "SHA1", + }, + "6049fcf8e09f83a28d769f8493b2af35a0824886", + false, + }, + { + "Test making SHA256 hash", + args{ + "./assets/", + "sha256", + }, + "a7da71b33ec7ce8179b6d47cab465e0b51e581de8086c9e3d7f4e71ee36a39fd", + false, + }, + { + "Test making SHA512 hash", + args{ + "./assets/", + "SHA512", + }, + "5427e699678230eb1a813691d1e7925e5549ad1541bf3e380e7a8267d13fad331873ccb90ebba10f54713653b0fdd82d14532ff956e17519e557790e8c477dcf", + false, + }, + { + "Test making with wrong hash name", + args{ + "./assets/", + "unknownHash", + }, + "", + true, + }, + { + "Test non existing folder", + args{ + "./someDir/", + "md5", + }, + "", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Make(tt.args.root, tt.args.hashType) + if (err != nil) != tt.wantErr { + t.Errorf("Make() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Make() = %v, want %v", got, tt.want) + } + }) + } +}