Skip to content

Commit

Permalink
Dev (wip) (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenkoehler authored May 28, 2021
1 parent 9ed75bb commit f2b764d
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 91 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '27 05 * * 6'

jobs:
analyze:
Expand Down
14 changes: 2 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
External Docs
-------------

### Kong ###

* [Kong](https://github.com/alecthomas/kong)

### Docopt ###

* [Main Project](http://docopt.org/)
* [Docopt-go](https://godoc.org/github.com/docopt/docopt-go)
=> Bug: Leerzeilen in Options-Doc Fork? Oder Kong?
Notes & External Docs
---------------------

### Cross Compiling ###

Expand Down
24 changes: 12 additions & 12 deletions chdiff/chdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package chdiff

import (
_ "embed"
"fmt"
"log"
"os"
"path"

"github.com/alecthomas/kong"
"github.com/soerenkoehler/chdiff-go/digest"
Expand All @@ -20,8 +20,8 @@ var cli struct {
}

type cmdDigest struct {
Path string `arg:"" name:"PATH" type:"path" default:"." help:"Path for which to calculate the digest"`
Mode string `name:"mode" short:"m" help:"The checksum algorithm to use [SHA256,SHA512]." enum:"SHA256,SHA512" default:"SHA256"`
Path string `arg:"" name:"PATH" type:"path" default:"." help:"Path for which to calculate the digest"`
Algorithm string `name:"alg" help:"The checksum algorithm to use [SHA256,SHA512]." enum:"SHA256,SHA512" default:"SHA256"`
}

func DoMain(
Expand All @@ -30,8 +30,6 @@ func DoMain(
digestService digest.Service,
stdioService util.StdIOService) {

var err error

os.Args = args
log.SetOutput(stdioService.Stdout())

Expand All @@ -47,16 +45,18 @@ func DoMain(
switch ctx.Command() {

case "create", "create <PATH>":
err = digestService.Create(cli.Create.Path, "out.txt", cli.Create.Mode)
digestService.Create(
cli.Create.Path,
path.Join(cli.Create.Path, "out.txt"),
cli.Create.Algorithm)

case "verify", "verify <PATH>":
err = digestService.Verify(cli.Verify.Path, "out.txt", cli.Verify.Mode)
digestService.Verify(
cli.Verify.Path,
path.Join(cli.Verify.Path, "out.txt"),
cli.Verify.Algorithm)

default:
err = fmt.Errorf("unknown command: %s", ctx.Command())
}

if err != nil {
log.Fatalf("Error: %s", err)
log.Fatalf("unknown command: %s", ctx.Command())
}
}
27 changes: 14 additions & 13 deletions chdiff/chdiff_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package chdiff_test
package chdiff

import (
"path"
"path/filepath"
"testing"

"github.com/soerenkoehler/chdiff-go/chdiff"
"github.com/soerenkoehler/chdiff-go/util"
"github.com/soerenkoehler/go-testutils/mockutil"
)
Expand All @@ -13,34 +13,35 @@ type digestServiceMock struct {
mockutil.Registry
}

func (mock digestServiceMock) Create(dataPath, digestPath, mode string) error {
func (mock digestServiceMock) Create(dataPath, digestPath, algorithm string) {
mockutil.Register(
&mock.Registry,
mockutil.Call{"create", dataPath, digestPath, mode})
return nil
mockutil.Call{"create", dataPath, digestPath, algorithm})
}

func (mock *digestServiceMock) Verify(dataPath, digestPath, mode string) error {
func (mock *digestServiceMock) Verify(dataPath, digestPath, algorithm string) {
mockutil.Register(
&mock.Registry,
mockutil.Call{"verify", dataPath, digestPath, mode})
return nil
mockutil.Call{"verify", dataPath, digestPath, algorithm})
}

func expectDigestServiceCall(
t *testing.T,
args []string,
call, dataPath, digestPath, mode string) {
call, dataPath, digestPath, algorithm string) {

absDataPath, _ := filepath.Abs(dataPath)
absDigestPath := path.Join(absDataPath, digestPath)

digestService := &digestServiceMock{}
digestService := &digestServiceMock{
Registry: mockutil.Registry{T: t},
}

chdiff.DoMain("TEST", args, digestService, util.DefaultStdIOService{})
DoMain("TEST", args, digestService, util.DefaultStdIOService{})

mockutil.Verify(t,
mockutil.Verify(
&digestService.Registry,
mockutil.Call{call, absDataPath, digestPath, mode})
mockutil.Call{call, absDataPath, absDigestPath, algorithm})
}

func TestCmdVerifyIsDefault(t *testing.T) {
Expand Down
166 changes: 123 additions & 43 deletions digest/digest.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,156 @@
package digest

import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
"io"
"log"
"os"
"path"
"path/filepath"
"sort"
"sync"
"time"

"github.com/soerenkoehler/chdiff-go/util"
)

type DigestEntry struct {
file string
hash string
size int64
modTime time.Time
}

type Digest map[string]DigestEntry

type DigestContext struct {
rootpath string
algorithm string
waitgroup *sync.WaitGroup
digest chan DigestEntry
}

// Service is the mockable API for the digest service.
type Service interface {
Create(dataPath, digestPath, mode string) error
Verify(dataPath, digestPath, mode string) error
Create(dataPath, digestPath, algorithm string)
Verify(dataPath, digestPath, algorithm string)
}

// DefaultService ist the production implementation of the digest service.
type DefaultService struct{}

// Digest is a map file path => checksum
type Digest map[string]string

// Create ... TODO
func (DefaultService) Create(dataPath, digestPath, mode string) error {
digest, err := calculate(dataPath, mode)
func (DefaultService) Create(dataPath, digestPath, algorithm string) {
digest := calculateDigest(dataPath, algorithm)
fmt.Printf("Saving %s\n", digestPath)
for _, k := range digest.sortedKeys() {
fmt.Printf("%s => %s\n", k, digest[k])
fmt.Print(digest[k].entryToString())
}
return err
}

// Verify ... TODO
func (DefaultService) Verify(dataPath, digestPath, mode string) error {
digest, err := calculate(dataPath, mode)
func (DefaultService) Verify(dataPath, digestPath, algorithm string) {
digest := calculateDigest(dataPath, algorithm)
fmt.Printf("Verify %s\n", digestPath)
for _, k := range digest.sortedKeys() {
fmt.Printf("%s => %s\n", k, digest[k])
fmt.Print(digest[k].entryToString())
}
return err
}

func calculate(rootPath, mode string) (Digest, error) {
wait := sync.WaitGroup{}

var processPath, processDir, processFile func(string)

processPath = func(path string) {
wait.Add(1)
go func() {
switch info := util.Stat(path); {
case info.IsSymlink:
log.Printf("skipping symlink: %s => %s", path, info.Target)
case info.IsDir:
processDir(path)
default:
processFile(path)
}
wait.Done()
}()
func calculateDigest(rootpath, algorithm string) Digest {
context := DigestContext{
rootpath: rootpath,
algorithm: algorithm,
waitgroup: &sync.WaitGroup{},
digest: make(chan DigestEntry),
}

processDir = func(dirPath string) {
entries, err := os.ReadDir(dirPath)
if err != nil {
log.Println(err)
go func() {
context.processPath(context.rootpath)
context.waitgroup.Wait()
close(context.digest)
}()

result := Digest{}
for entry := range context.digest {
result[entry.file] = entry
}

return result
}

func (context DigestContext) processPath(path string) {
context.waitgroup.Add(1)
go func() {
switch info := util.Stat(path); {
case info.IsSymlink:
log.Printf("[W] skipping symlink: %s => %s", path, info.Target)
case info.IsDir:
context.processDir(path)
default:
context.processFile(path)
}
context.waitgroup.Done()
}()
}

func (context DigestContext) processDir(dir string) {
entries, err := os.ReadDir(dir)
if err != nil {
log.Printf("[E]: %s\n", err)
} else {
for _, entry := range entries {
processPath(path.Join(dirPath, entry.Name()))
context.processPath(path.Join(dir, entry.Name()))
}
}
}

func (context DigestContext) processFile(file string) {
info, err := os.Lstat(file)
if err != nil {
log.Printf("[E]: %s\n", err)
return
}

relativePath, err := filepath.Rel(context.rootpath, file)
if err != nil {
log.Printf("[E]: %s\n", err)
return
}

checksum, err := context.getNewHash()
if err != nil {
log.Printf("[E]: %s\n", err)
return
}

processFile = func(path string) {
// fmt.Printf("calculate(%s, %s)\n", mode, path)
input, err := os.Open(file)
if err != nil {
log.Printf("[E]: %s\n", err)
return
}

processPath(rootPath)
wait.Wait()
defer input.Close()
io.Copy(checksum, input)

return Digest{}, nil
context.digest <- DigestEntry{
file: relativePath,
hash: hex.EncodeToString(checksum.Sum(nil)),
size: info.Size(),
modTime: info.ModTime(),
}
}

func (context DigestContext) getNewHash() (hash.Hash, error) {
switch context.algorithm {
case "SHA256":
return sha256.New(), nil
case "SHA512":
return sha512.New(), nil
}
return nil, fmt.Errorf("invalid hash algorithm %v", context.algorithm)
}

func (digest Digest) sortedKeys() []string {
Expand All @@ -91,3 +161,13 @@ func (digest Digest) sortedKeys() []string {
sort.Strings(keys)
return keys
}

func (entry DigestEntry) entryToString() string {
return fmt.Sprintf(
"# %d %s %s\n%s *%s\n",
entry.size,
entry.modTime.Local().Format("20060102-150405"),
entry.file,
entry.hash,
entry.file)
}
Loading

0 comments on commit f2b764d

Please sign in to comment.