Files
core/warehouse/Warehouse.go

148 lines
3.5 KiB
Go

/*
File Username: Warehouse.go
Copyright: 2021 Peernet Foundation s.r.o.
Author: Peter Kleissner
*/
package warehouse
import (
"encoding/hex"
"io/ioutil"
"os"
"path/filepath"
)
// Blake3 hash size = 32 bytes.
const (
hashSize = 256 / 8
)
// Warehouse represents a folder on disk.
type Warehouse struct {
Directory string // The main directory for the files
Temp string // Temporary folder
}
// Init initializes the warehouse
func Init(Directory string) (wh *Warehouse, err error) {
// The temp folder will always be a sub-folder named "_Temp"
wh = &Warehouse{Directory: Directory, Temp: filepath.Join(Directory, "_Temp")}
if err = createDirectory(wh.Directory); err != nil {
return nil, err
}
if err = createDirectory(wh.Temp); err != nil {
return nil, err
}
return
}
// ---- hash functions ----
func ValidateHash(hash []byte) (hashA string, err error) {
if len(hash) != hashSize {
return "", os.ErrInvalid
}
return hex.EncodeToString(hash), nil
}
// ---- path ----
func createDirectory(path string) (err error) {
if _, err = os.Stat(path); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(path, os.ModePerm)
}
return err
}
// buildPath returns the full directory and the filename for the hash
func buildPath(storagePath, hash string) (directory string, filename string) {
part1 := hash[:4]
part2 := hash[4:8]
filename = hash[8:]
newPath := filepath.Join(storagePath, part1, part2)
return newPath, filename
}
// tempFile creates a temporary file in the Warehouse. Do not forget to delete.
func (wh *Warehouse) tempFile() (file *os.File, err error) {
file, err = ioutil.TempFile(wh.Temp, "wh")
return
}
// createFilePath creates the file path for the specified hash and returns the full file path
func (wh *Warehouse) createFilePath(hash []byte) (pathFull string, err error) {
path, filename := buildPath(wh.Directory, hex.EncodeToString(hash))
return filepath.Join(path, filename), createDirectory(path)
}
// IterateFiles iterates through all the files and calls the callback
func (wh *Warehouse) IterateFiles(Callback func(Hash []byte, Size int64) (Continue bool)) (err error) {
// list all directories in the local Storage folder. We have to walk 2 levels down to see the actual files.
files, err := ioutil.ReadDir(wh.Directory)
if err != nil {
return err
}
for _, file := range files {
name1 := file.Name()
_, err = hex.DecodeString(name1)
// we are only looking for directories. Username has to be "XXXX" hex chars only.
if !file.IsDir() || len(name1) != 4 || err != nil {
continue
}
// iterate through the next level
files1, err := ioutil.ReadDir(filepath.Join(wh.Directory, name1))
if err != nil {
return err
}
for _, file2 := range files1 {
name2 := file2.Name()
_, err = hex.DecodeString(name2)
// we are only looking for directories. Username has to be "XXXX" hex chars only.
if !file2.IsDir() || len(name2) != 4 || err != nil {
continue
}
// iterate through the next level
files2, err := ioutil.ReadDir(filepath.Join(wh.Directory, name1, name2))
if err != nil {
return err
}
for _, file3 := range files2 {
name3 := file3.Name()
_, err = hex.DecodeString(name3)
// finally we are only looking for files
if file3.IsDir() || len(name3) != hashSize*2-8 || err != nil {
continue
}
size := file3.Size()
hash, err := hex.DecodeString(name1 + name2 + name3)
if err != nil {
return err
}
// found the hash! forward
if !Callback(hash, size) {
return nil
}
}
}
}
return nil
}