This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
doggo-fetcher/internal/hash.go

157 lines
3.1 KiB
Go

package internal
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
type HashActions interface {
GetFolderHash(path string) (string, error)
CompareReleaseHash(path string, hash string) error
AddHash(path, hash string) error
ReplaceHash(path, hash string) error
RemoveHash(path string) error
writeHashTable() error
}
type Hash struct {
hashTable map[string]string
hashFile string
}
// NewHash returns a new Hash object. It reads the hashes from the ~/.local/doggofetcher/hashes.txt file then loads
// them into the hash table.
func NewHash(hashFile string) (HashActions, error) {
f, err := os.Open(hashFile)
if err != nil {
return &Hash{}, err
}
defer f.Close()
hashTable := make(map[string]string)
sc := bufio.NewScanner(f)
for sc.Scan() {
l := strings.Split(sc.Text(), " ")
hashTable[l[0]] = l[1]
}
return &Hash{
hashTable: hashTable,
hashFile: hashFile,
}, nil
}
// GetFolderHash returns the hash of the folder by using the Merkle tree.
func (h *Hash) GetFolderHash(path string) (string, error) {
hashes := [][]byte{}
err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.Type().IsRegular() {
return nil
}
if d.IsDir() {
hash, err := h.GetFolderHash(path)
if err != nil {
return err
}
hashes = append(hashes, []byte(hash))
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
sha := sha256.New()
buf := make([]byte, 10*1024)
for {
n, err := f.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sha.Write(buf[:n])
}
hashes = append(hashes, sha.Sum(nil))
return nil
})
if err != nil {
return "", err
}
sha := sha256.New()
for _, h := range hashes {
if _, err := sha.Write(h); err != nil {
return "", err
}
}
return hex.EncodeToString(sha.Sum(nil)), nil
}
// CompareReleaseHash compares the hash of the release with the hash in the hash table.
func (h *Hash) CompareReleaseHash(path, hash string) error {
if h, ok := h.hashTable[path]; !ok {
return ErrHashNotFound
} else if h != hash {
return ErrHashInvalid
} else {
return nil
}
}
// AddHash adds a hash to the hash table by writing the "hashTable" property and the file.
func (h *Hash) AddHash(path, hash string) error {
h.hashTable[path] = hash
return h.writeHashTable()
}
// ReplaceHash replaces the hash in the hash table with the new hash.
func (h *Hash) ReplaceHash(path, hash string) error {
h.hashTable[path] = hash
return h.writeHashTable()
}
// RemoveHash removes a hash from the hash table.
func (h *Hash) RemoveHash(path string) error {
delete(h.hashTable, path)
return h.writeHashTable()
}
// writeHashTable writes the hash table to the file with the given data.
func (h *Hash) writeHashTable() error {
start := true
var data []byte
for path, hash := range h.hashTable {
if start {
start = false
} else {
data = append(data, []byte("\n")...)
}
data = append(data, []byte(fmt.Sprintf("%s %s", path, hash))...)
}
return os.WriteFile(h.hashFile, data, 0644)
}