Skip to content

Commit

Permalink
[feature/workers] handling directory recursively and computing hash w…
Browse files Browse the repository at this point in the history
…ith workers (#6)
  • Loading branch information
Bilou4 authored Apr 5, 2024
1 parent 1908809 commit 646a856
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 78 deletions.
53 changes: 34 additions & 19 deletions cmd/hash.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,55 @@
package cmd

import (
"fmt"
"hash"
"io"
"os"

"github.com/spf13/cobra"
)

type HashResult struct {
res []byte
path string
err error
}

type JobsParam struct {
path string
h hash.Hash
}

// hashCmd represents the hash command
var hashCmd = &cobra.Command{
Use: "hash",
Short: "",
Long: ``,
Use: "hash",
Short: "The hash command computes the hash of a given FILE.",
Long: `The hash command computes the hash of a given FILE.
Without FILE or when FILE is '-', read the standard input.
If the list of FILE contains a directory, it will be proceed recursively.
If the list of FILE contains './...' it will proceed directories recursively from the current directory.`,
SilenceErrors: true,
}

func init() {
rootCmd.AddCommand(hashCmd)
}

func getFilesToCompute(args []string) []string {
if len(args) == 0 || (len(args) == 1 && args[0] == "-") {
return []string{"-"}
}
return args
}

func computeFiles(filesToCheck []string, h hash.Hash) (string, error) {
var res string
for _, filePath := range filesToCheck {
hashedValue, err := computeHash(filePath, h)
func computeHash(path string, h hash.Hash) ([]byte, error) {
var r io.Reader
if path == "-" {
r = os.Stdin
} else {
file, err := os.Open(path)
if err != nil {
return "", err
return nil, err
}
res += fmt.Sprintf("%x %s\n", hashedValue, filePath)
h.Reset()
defer file.Close()
r = file
}
_, err := io.Copy(h, r)
if err != nil {
return nil, err
}
return res, nil
return h.Sum(nil), nil
}
25 changes: 18 additions & 7 deletions cmd/md5Hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"crypto/md5"
"fmt"

"github.com/spf13/cobra"
)
Expand All @@ -13,16 +12,28 @@ var md5HashCmd = &cobra.Command{
Short: "Display MD5 checksums (128 bits).",
Long: `Display MD5 checksums (128 bits).
Without FILE or when FILE is '-', read the standard input.`,
Without FILE or when FILE is '-', read the standard input.
If the list of FILE contains a directory, it will be proceed recursively.
If the list of FILE contains './...' it will proceed directories recursively from the current directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
filesToCheck := getFilesToCompute(args)
h := md5.New()
res, err := computeFiles(filesToCheck, h)
filesToCheck, err := getFilesToCompute(args)
if err != nil {
return err
}
fmt.Print(res)
return nil

numJobs := len(filesToCheck)
jobs := make(chan JobsParam, numJobs)
results := make(chan HashResult, numJobs)

initWorkers(jobs, results)

for _, filePath := range filesToCheck {
h := md5.New()
jobs <- JobsParam{filePath, h}
}
close(jobs)

return waitForResult(numJobs, results)
},
}

Expand Down
96 changes: 79 additions & 17 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package cmd

import (
"bufio"
"encoding/hex"
"fmt"
"hash"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"slices"

"github.com/spf13/cobra"
)
Expand All @@ -29,25 +31,85 @@ func Execute() {
}
}

func computeHash(path string, h hash.Hash) ([]byte, error) {
if path == "-" {
reader := bufio.NewReader(os.Stdin)
buffer := make([]byte, 1024)
for {
n, err := reader.Read(buffer)
h.Write(buffer[:n])
if err == io.EOF {
break
}
// https://stackoverflow.com/questions/37334119/how-to-delete-an-element-from-a-slice-in-golang
func remove(s []string, i int) []string {
s[i] = s[len(s)-1]
return s[:len(s)-1]
}

func getFilesToCompute(args []string) ([]string, error) {
var err error
if len(args) == 0 || (len(args) == 1 && args[0] == "-") {
return []string{"-"}, nil
}

// Here ./ tells to start from the current folder, ... tells to go down recursively.
if slices.Contains(args, "./...") {
idx := slices.Index(args, "./...")
args = remove(args, idx)
args, err = computeDirRecursively(args, ".")
if err != nil {
return nil, err
}
} else {
file, err := os.Open(path)
}

// check for dirs and all files exist
for idx, elem := range args {
f, err := os.Open(elem)
if err != nil {
return nil, err
}
if _, err := io.Copy(h, file); err != nil {
defer f.Close()
fInfo, err := f.Stat()
if err != nil {
return nil, err
}
if fInfo.IsDir() {
args = remove(args, idx)
args, err = computeDirRecursively(args, elem)
if err != nil {
return nil, err
}
}
}
fmt.Printf("Computing hash for: %d files.", len(args))

return args, nil
}

func computeDirRecursively(l []string, rootFolder string) ([]string, error) {
err := filepath.WalkDir(rootFolder, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
l = append(l, path)
}
return nil
})
return l, err
}

func initWorkers(jobs chan JobsParam, results chan HashResult) {
for i := 0; i <= runtime.NumCPU(); i++ {
go worker(jobs, results)
}
}

func waitForResult(nbJobs int, results chan HashResult) error {
for a := 1; a <= nbJobs; a++ {
res := <-results
if res.err != nil {
return res.err
}
fmt.Printf("%s %s\n", hex.EncodeToString(res.res), res.path)
}
return nil
}

func worker(jobs <-chan JobsParam, results chan<- HashResult) {
for j := range jobs {
hashedValue, err := computeHash(j.path, j.h)
results <- HashResult{hashedValue, j.path, err}
}
return h.Sum(nil), nil
}
25 changes: 18 additions & 7 deletions cmd/sha1Hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"crypto/sha1"
"fmt"

"github.com/spf13/cobra"
)
Expand All @@ -13,16 +12,28 @@ var sha1HashCmd = &cobra.Command{
Short: "Display SHA-1 checksums (160 bits).",
Long: `Display SHA-1 checksums (160 bits).
Without FILE or when FILE is '-', read the standard input.`,
Without FILE or when FILE is '-', read the standard input.
If the list of FILE contains a directory, it will be proceed recursively.
If the list of FILE contains './...' it will proceed directories recursively from the current directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
filesToCheck := getFilesToCompute(args)
h := sha1.New()
res, err := computeFiles(filesToCheck, h)
filesToCheck, err := getFilesToCompute(args)
if err != nil {
return err
}
fmt.Print(res)
return nil

numJobs := len(filesToCheck)
jobs := make(chan JobsParam, numJobs)
results := make(chan HashResult, numJobs)

initWorkers(jobs, results)

for _, filePath := range filesToCheck {
h := sha1.New()
jobs <- JobsParam{filePath, h}
}
close(jobs)

return waitForResult(numJobs, results)
},
}

Expand Down
25 changes: 18 additions & 7 deletions cmd/sha224Hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"crypto/sha256"
"fmt"

"github.com/spf13/cobra"
)
Expand All @@ -13,16 +12,28 @@ var sha224HashCmd = &cobra.Command{
Short: "Display SHA-224 checksums (224 bits).",
Long: `Display SHA-224 checksums (224 bits).
Without FILE or when FILE is '-', read the standard input.`,
Without FILE or when FILE is '-', read the standard input.
If the list of FILE contains a directory, it will be proceed recursively.
If the list of FILE contains './...' it will proceed directories recursively from the current directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
filesToCheck := getFilesToCompute(args)
h := sha256.New224()
res, err := computeFiles(filesToCheck, h)
filesToCheck, err := getFilesToCompute(args)
if err != nil {
return err
}
fmt.Print(res)
return nil

numJobs := len(filesToCheck)
jobs := make(chan JobsParam, numJobs)
results := make(chan HashResult, numJobs)

initWorkers(jobs, results)

for _, filePath := range filesToCheck {
h := sha256.New224()
jobs <- JobsParam{filePath, h}
}
close(jobs)

return waitForResult(numJobs, results)
},
}

Expand Down
25 changes: 18 additions & 7 deletions cmd/sha256Hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"crypto/sha256"
"fmt"

"github.com/spf13/cobra"
)
Expand All @@ -13,16 +12,28 @@ var sha256HashCmd = &cobra.Command{
Short: "Display SHA256 checksums (256 bits).",
Long: `Display SHA256 checksums (256 bits).
Without FILE or when FILE is '-', read the standard input.`,
Without FILE or when FILE is '-', read the standard input.
If the list of FILE contains a directory, it will be proceed recursively.
If the list of FILE contains './...' it will proceed directories recursively from the current directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
filesToCheck := getFilesToCompute(args)
h := sha256.New()
res, err := computeFiles(filesToCheck, h)
filesToCheck, err := getFilesToCompute(args)
if err != nil {
return err
}
fmt.Print(res)
return nil

numJobs := len(filesToCheck)
jobs := make(chan JobsParam, numJobs)
results := make(chan HashResult, numJobs)

initWorkers(jobs, results)

for _, filePath := range filesToCheck {
h := sha256.New()
jobs <- JobsParam{filePath, h}
}
close(jobs)

return waitForResult(numJobs, results)
},
}

Expand Down
Loading

0 comments on commit 646a856

Please sign in to comment.