Added piecestore (#36)
* Added piecestore * gofmt * Added requested changes * Added cli * Removed ranger because I wanted something that can stand alone * Changed piecestore code to make it more optial for error handelling * Added missing package * Forgot io import * gofmt * Make path by hash exported * updated to simplify again whoops * Forgot ampersand * Updated to match FilePiece * Change store to use io.CopyN * Clear golinter errors * Updated go.mod * Updated go.mod
This commit is contained in:
parent
6b49aed88a
commit
4b03c08cf6
125
cmd/piecestore/main.go
Normal file
125
cmd/piecestore/main.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/zeebo/errs"
|
||||
"storj.io/storj/pkg/piecestore"
|
||||
)
|
||||
|
||||
var argError = errs.Class("argError")
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = "Piece Store CLI"
|
||||
app.Usage = "Store data in hash folder structure"
|
||||
app.Version = "1.0.0"
|
||||
|
||||
app.Flags = []cli.Flag{}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "store",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Store data by hash",
|
||||
ArgsUsage: "[hash] [dataPath] [storeDir]",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Args().Get(0) == "" {
|
||||
return argError.New("Missing data Hash")
|
||||
}
|
||||
|
||||
if c.Args().Get(1) == "" {
|
||||
return argError.New("No input file specified")
|
||||
}
|
||||
|
||||
if c.Args().Get(2) == "" {
|
||||
return argError.New("No output directory specified")
|
||||
}
|
||||
|
||||
file, err := os.Open(c.Args().Get(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the file when we are done
|
||||
defer file.Close()
|
||||
|
||||
fileInfo, err := os.Stat(c.Args().Get(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
return argError.New(fmt.Sprintf("Path (%s) is a directory, not a file", c.Args().Get(1)))
|
||||
}
|
||||
|
||||
_, err = pstore.Store(c.Args().Get(0), file, int64(fileInfo.Size()), 0, c.Args().Get(2))
|
||||
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "retrieve",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Retrieve data by hash and print to Stdout",
|
||||
ArgsUsage: "[hash] [storeDir]",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Args().Get(0) == "" {
|
||||
return argError.New("Missing data Hash")
|
||||
}
|
||||
if c.Args().Get(1) == "" {
|
||||
return argError.New("Missing file path")
|
||||
}
|
||||
fileInfo, err := os.Stat(c.Args().Get(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() != true {
|
||||
return argError.New(fmt.Sprintf("Path (%s) is a file, not a directory", c.Args().Get(1)))
|
||||
}
|
||||
|
||||
_, err = pstore.Retrieve(c.Args().Get(0), os.Stdout, -1, 0, c.Args().Get(1))
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Delete data by hash",
|
||||
ArgsUsage: "[hash] [storeDir]",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Args().Get(0) == "" {
|
||||
return argError.New("Missing data Hash")
|
||||
}
|
||||
if c.Args().Get(1) == "" {
|
||||
return argError.New("No directory specified")
|
||||
}
|
||||
err := pstore.Delete(c.Args().Get(0), c.Args().Get(1))
|
||||
|
||||
return err
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
53
go.mod
53
go.mod
@ -1,29 +1,30 @@
|
||||
module storj.io/storj
|
||||
module "storj.io/storj"
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-redis/redis v0.0.0-20180417061816-9ccc23344a52
|
||||
github.com/gogo/protobuf v1.0.0
|
||||
github.com/golang/protobuf v1.0.0
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
|
||||
github.com/magiconair/properties v1.7.6
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238
|
||||
github.com/pelletier/go-toml v1.1.0
|
||||
github.com/spacemonkeygo/errors v0.0.0-20171212215202-9064522e9fd1
|
||||
github.com/spf13/afero v1.1.0
|
||||
github.com/spf13/cast v1.2.0
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec
|
||||
github.com/spf13/pflag v1.0.1
|
||||
github.com/spf13/viper v1.0.2
|
||||
github.com/tyler-smith/go-bip39 v0.0.0-20160629163856-8e7a99b3e716
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/vivint/infectious v0.0.0-20180418194855-57d6abddc3d4
|
||||
github.com/zeebo/errs v0.1.0
|
||||
golang.org/x/crypto v0.0.0-20180410182641-f70185d77e82
|
||||
golang.org/x/net v0.0.0-20180420171651-5f9ae10d9af5
|
||||
golang.org/x/sys v0.0.0-20180430173509-4adea008a5e5
|
||||
golang.org/x/text v0.3.0
|
||||
google.golang.org/genproto v0.0.0-20180427144745-86e600f69ee4
|
||||
google.golang.org/grpc v1.11.3
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
"github.com/aleitner/FilePiece" v0.0.0-20180516062859-b020bc25bf96
|
||||
"github.com/fsnotify/fsnotify" v1.4.7
|
||||
"github.com/go-redis/redis" v0.0.0-20180417061816-9ccc23344a52
|
||||
"github.com/gogo/protobuf" v1.0.0
|
||||
"github.com/golang/protobuf" v1.0.0
|
||||
"github.com/hashicorp/hcl" v0.0.0-20180404174102-ef8a98b0bbce
|
||||
"github.com/magiconair/properties" v1.7.6
|
||||
"github.com/mitchellh/mapstructure" v0.0.0-20180220230111-00c29f56e238
|
||||
"github.com/pelletier/go-toml" v1.1.0
|
||||
"github.com/spacemonkeygo/errors" v0.0.0-20171212215202-9064522e9fd1
|
||||
"github.com/spf13/afero" v1.1.0
|
||||
"github.com/spf13/cast" v1.2.0
|
||||
"github.com/spf13/jWalterWeatherman" v0.0.0-20180109140146-7c0cea34c8ec
|
||||
"github.com/spf13/pflag" v1.0.1
|
||||
"github.com/spf13/viper" v1.0.2
|
||||
"github.com/tyler-smith/go-bip39" v0.0.0-20160629163856-8e7a99b3e716
|
||||
"github.com/urfave/cli" v1.20.0
|
||||
"github.com/vivint/infectious" v0.0.0-20180418194855-57d6abddc3d4
|
||||
"github.com/zeebo/errs" v0.1.0
|
||||
"golang.org/x/crypto" v0.0.0-20180410182641-f70185d77e82
|
||||
"golang.org/x/net" v0.0.0-20180420171651-5f9ae10d9af5
|
||||
"golang.org/x/sys" v0.0.0-20180430173509-4adea008a5e5
|
||||
"golang.org/x/text" v0.3.0
|
||||
"google.golang.org/genproto" v0.0.0-20180427144745-86e600f69ee4
|
||||
"google.golang.org/grpc" v1.11.3
|
||||
"gopkg.in/yaml.v2" v2.2.1
|
||||
)
|
||||
|
152
pkg/piecestore/pstore.go
Normal file
152
pkg/piecestore/pstore.go
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package pstore
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aleitner/FilePiece"
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ArgError = errs.Class("argError")
|
||||
FSError = errs.Class("fsError")
|
||||
)
|
||||
|
||||
// PathByHash -- creates datapath from hash and dir
|
||||
func PathByHash(hash, dir string) (string, error) {
|
||||
if len(hash) < 20 {
|
||||
return "", ArgError.New("Invalid hash length")
|
||||
}
|
||||
|
||||
folder1 := string(hash[0:2])
|
||||
folder2 := string(hash[2:4])
|
||||
fileName := string(hash[4:])
|
||||
|
||||
return path.Join(dir, folder1, folder2, fileName), nil
|
||||
}
|
||||
|
||||
// Store -- Store data into piece store
|
||||
// hash (string) Hash of the data to be stored
|
||||
// r (io.Reader) File/Stream that contains the contents of the data to be stored
|
||||
// length (length) Size of the data to be stored
|
||||
// psFileOffset (offset) Offset of the data that you are writing. Useful for multiple connections to split the data transfer
|
||||
// dir (string) pstore directory containing all other data stored
|
||||
// returns (error) error if failed and nil if successful
|
||||
func Store(hash string, r io.Reader, length int64, psFileOffset int64, dir string) (int64, error) {
|
||||
if psFileOffset < 0 {
|
||||
return 0, ArgError.New("Offset is less than 0. Must be greater than or equal to 0")
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ArgError.New("Length is less than 0. Must be greater than or equal to 0")
|
||||
}
|
||||
if dir == "" {
|
||||
return 0, ArgError.New("No path provided")
|
||||
}
|
||||
|
||||
dataPath, err := PathByHash(hash, dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Create directory path on file system
|
||||
if err = os.MkdirAll(filepath.Dir(dataPath), 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Create File on file system
|
||||
dataFile, err := os.OpenFile(dataPath, os.O_RDWR|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dataFileChunk := fpiece.NewChunk(dataFile, psFileOffset, length)
|
||||
|
||||
// Close when finished
|
||||
defer dataFile.Close()
|
||||
|
||||
return io.CopyN(dataFileChunk, r, length)
|
||||
}
|
||||
|
||||
// Retrieve -- Retrieve data from pstore directory
|
||||
// hash (string) Hash of the stored data
|
||||
// w (io.Writer) Stream that recieves the stored data
|
||||
// length (length) Amount of data to read. Read all data if -1
|
||||
// readPosOffset (offset) Offset of the data that you are reading. Useful for multiple connections to split the data transfer
|
||||
// dir (string) pstore directory containing all other data stored
|
||||
// returns (int64, error) returns err if failed and the number of bytes retrieved if successful
|
||||
func Retrieve(hash string, w io.Writer, length int64, readPosOffset int64, dir string) (int64, error) {
|
||||
if dir == "" {
|
||||
return 0, ArgError.New("No path provided")
|
||||
}
|
||||
|
||||
dataPath, err := PathByHash(hash, dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(dataPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If offset is greater than file size return
|
||||
if readPosOffset >= fileInfo.Size() || readPosOffset < 0 {
|
||||
return 0, ArgError.New("Invalid offset: %v", readPosOffset)
|
||||
}
|
||||
|
||||
// If length less than 0 read the entire file
|
||||
if length <= -1 {
|
||||
length = fileInfo.Size()
|
||||
}
|
||||
|
||||
// If trying to read past the end of the file, just read to the end
|
||||
if fileInfo.Size() < readPosOffset+length {
|
||||
length = fileInfo.Size() - readPosOffset
|
||||
}
|
||||
|
||||
dataFile, err := os.OpenFile(dataPath, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Close when finished
|
||||
defer dataFile.Close()
|
||||
|
||||
// Created a section reader so that we can concurrently retrieve the same file.
|
||||
dataFileChunk := fpiece.NewChunk(dataFile, readPosOffset, length)
|
||||
|
||||
total, err := io.CopyN(w, dataFileChunk, length)
|
||||
|
||||
return total, err
|
||||
}
|
||||
|
||||
// Delete -- Delete data from farmer
|
||||
// hash (string) Hash of the data to be stored
|
||||
// dir (string) pstore directory containing all other data stored
|
||||
// returns (error) if failed and nil if successful
|
||||
func Delete(hash string, dir string) error {
|
||||
if dir == "" {
|
||||
return ArgError.New("No path provided")
|
||||
}
|
||||
|
||||
dataPath, err := PathByHash(hash, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = os.Stat(dataPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = os.Remove(dataPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
556
pkg/piecestore/pstore_test.go
Normal file
556
pkg/piecestore/pstore_test.go
Normal file
@ -0,0 +1,556 @@
|
||||
// Copyright (C) 2018 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package pstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var tmpfile string
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
t.Run("it stores data successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
folder1 := string(hash[0:2])
|
||||
folder2 := string(hash[2:4])
|
||||
fileName := string(hash[4:])
|
||||
|
||||
createdFilePath := path.Join(os.TempDir(), folder1, folder2, fileName)
|
||||
defer os.RemoveAll(path.Join(os.TempDir(), folder1))
|
||||
_, lStatErr := os.Lstat(createdFilePath)
|
||||
if lStatErr != nil {
|
||||
t.Errorf("No file was created from Store(): %s", lStatErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
createdFile, openCreatedError := os.Open(createdFilePath)
|
||||
if openCreatedError != nil {
|
||||
t.Errorf("Error: %s opening created file %s", openCreatedError.Error(), createdFilePath)
|
||||
}
|
||||
defer createdFile.Close()
|
||||
|
||||
buffer := make([]byte, 5)
|
||||
createdFile.Seek(0, 0)
|
||||
_, _ = createdFile.Read(buffer)
|
||||
|
||||
if string(buffer) != "butts" {
|
||||
t.Errorf("Expected data butts does not equal Actual data %s", string(buffer))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("it stores data by offset successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 2, os.TempDir())
|
||||
|
||||
folder1 := string(hash[0:2])
|
||||
folder2 := string(hash[2:4])
|
||||
fileName := string(hash[4:])
|
||||
|
||||
createdFilePath := path.Join(os.TempDir(), folder1, folder2, fileName)
|
||||
defer os.RemoveAll(path.Join(os.TempDir(), folder1))
|
||||
_, lStatErr := os.Lstat(createdFilePath)
|
||||
if lStatErr != nil {
|
||||
t.Errorf("No file was created from Store(): %s", lStatErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
createdFile, openCreatedError := os.Open(createdFilePath)
|
||||
if openCreatedError != nil {
|
||||
t.Errorf("Error: %s opening created file %s", openCreatedError.Error(), createdFilePath)
|
||||
}
|
||||
defer createdFile.Close()
|
||||
|
||||
buffer := make([]byte, 7)
|
||||
createdFile.Seek(0, 0)
|
||||
_, _ = createdFile.Read(buffer)
|
||||
|
||||
// \0\0butts
|
||||
expected := []byte{0, 0, 98, 117, 116, 116, 115}
|
||||
|
||||
if string(buffer) != string(expected) {
|
||||
t.Errorf("Expected data (%v) does not equal Actual data (%v)", expected, buffer)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("it stores data by chunk successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, 4, 0, os.TempDir())
|
||||
|
||||
folder1 := string(hash[0:2])
|
||||
folder2 := string(hash[2:4])
|
||||
fileName := string(hash[4:])
|
||||
|
||||
createdFilePath := path.Join(os.TempDir(), folder1, folder2, fileName)
|
||||
defer os.RemoveAll(path.Join(os.TempDir(), folder1))
|
||||
_, lStatErr := os.Lstat(createdFilePath)
|
||||
if lStatErr != nil {
|
||||
t.Errorf("No file was created from Store(): %s", lStatErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
createdFile, openCreatedError := os.Open(createdFilePath)
|
||||
if openCreatedError != nil {
|
||||
t.Errorf("Error: %s opening created file %s", openCreatedError.Error(), createdFilePath)
|
||||
}
|
||||
defer createdFile.Close()
|
||||
|
||||
buffer := make([]byte, 4)
|
||||
createdFile.Seek(0, 0)
|
||||
_, _ = createdFile.Read(buffer)
|
||||
|
||||
// butt
|
||||
expected := []byte{98, 117, 116, 116}
|
||||
|
||||
if string(buffer) != string(expected) {
|
||||
t.Errorf("Expected data %s does not equal Actual data %s", string(expected), string(buffer))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("it should return hash err if the hash is too short", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "11111111"
|
||||
|
||||
_, err = Store(hash, file, 5, 0, os.TempDir())
|
||||
assert.NotNil(err)
|
||||
if err != nil {
|
||||
assert.Equal(err.Error(), "argError: Invalid hash length", "They should have the same error message")
|
||||
}
|
||||
})
|
||||
|
||||
// Test passing in negative offset
|
||||
t.Run("it should return an error when given negative offset", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
|
||||
_, err = Store(hash, file, int64(fi.Size()), -12, os.TempDir())
|
||||
|
||||
assert.NotNil(err)
|
||||
if err != nil {
|
||||
assert.Equal(err.Error(), "argError: Offset is less than 0. Must be greater than or equal to 0", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
// Test passing in a negative length
|
||||
t.Run("it should return an error when given length less than 0", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err = file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
|
||||
_, err = Store(hash, file, -1, 0, os.TempDir())
|
||||
assert.NotNil(err)
|
||||
if err != nil {
|
||||
assert.Equal(err.Error(), "argError: Length is less than 0. Must be greater than or equal to 0", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRetrieve(t *testing.T) {
|
||||
t.Run("it retrieves data successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
// Create file for retrieving data into
|
||||
retrievalFilePath := path.Join(os.TempDir(), "retrieved.txt")
|
||||
retrievalFile, err := os.OpenFile(retrievalFilePath, os.O_RDWR|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
t.Errorf("Error creating file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(retrievalFilePath)
|
||||
defer retrievalFile.Close()
|
||||
|
||||
_, err = Retrieve(hash, retrievalFile, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
t.Errorf("Retrieve Error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
buffer := make([]byte, 5)
|
||||
|
||||
retrievalFile.Seek(0, 0)
|
||||
_, _ = retrievalFile.Read(buffer)
|
||||
|
||||
fmt.Printf("Retrieved data: %s", string(buffer))
|
||||
|
||||
if string(buffer) != "butts" {
|
||||
t.Errorf("Expected data butts does not equal Actual data %s", string(buffer))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("it retrieves data by offset successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
// Create file for retrieving data into
|
||||
retrievalFilePath := path.Join(os.TempDir(), "retrieved.txt")
|
||||
retrievalFile, err := os.OpenFile(retrievalFilePath, os.O_RDWR|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
t.Errorf("Error creating file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(retrievalFilePath)
|
||||
defer retrievalFile.Close()
|
||||
|
||||
_, err = Retrieve(hash, retrievalFile, int64(fi.Size()), 2, os.TempDir())
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
t.Errorf("Retrieve Error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
buffer := make([]byte, 3)
|
||||
|
||||
retrievalFile.Seek(0, 0)
|
||||
_, _ = retrievalFile.Read(buffer)
|
||||
|
||||
fmt.Printf("Retrieved data: %s", string(buffer))
|
||||
|
||||
if string(buffer) != "tts" {
|
||||
t.Errorf("Expected data (tts) does not equal Actual data (%s)", string(buffer))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("it retrieves data by chunk successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
// Create file for retrieving data into
|
||||
retrievalFilePath := path.Join(os.TempDir(), "retrieved.txt")
|
||||
retrievalFile, err := os.OpenFile(retrievalFilePath, os.O_RDWR|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
t.Errorf("Error creating file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(retrievalFilePath)
|
||||
defer retrievalFile.Close()
|
||||
|
||||
_, err = Retrieve(hash, retrievalFile, 3, 0, os.TempDir())
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
t.Errorf("Retrieve Error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
buffer := make([]byte, 3)
|
||||
|
||||
retrievalFile.Seek(0, 0)
|
||||
_, _ = retrievalFile.Read(buffer)
|
||||
|
||||
fmt.Printf("Retrieved data: %s", string(buffer))
|
||||
|
||||
if string(buffer) != "but" {
|
||||
t.Errorf("Expected data (but) does not equal Actual data (%s)", string(buffer))
|
||||
}
|
||||
})
|
||||
|
||||
// Test passing in negative offset
|
||||
t.Run("it should return an error when retrieving with offset less 0", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
// Create file for retrieving data into
|
||||
retrievalFilePath := path.Join(os.TempDir(), "retrieved.txt")
|
||||
retrievalFile, err := os.OpenFile(retrievalFilePath, os.O_RDWR|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
t.Errorf("Error creating file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(retrievalFilePath)
|
||||
defer retrievalFile.Close()
|
||||
|
||||
_, err = Retrieve(hash, retrievalFile, int64(fi.Size()), -1, os.TempDir())
|
||||
assert.NotNil(err)
|
||||
if err != nil {
|
||||
assert.Equal("argError: Invalid offset: -1", err.Error(), err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
// Test passing in negative length
|
||||
t.Run("it should return the entire file successfully when retrieving with negative length", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
// Create file for retrieving data into
|
||||
retrievalFilePath := path.Join(os.TempDir(), "retrieved.txt")
|
||||
retrievalFile, err := os.OpenFile(retrievalFilePath, os.O_RDWR|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
t.Errorf("Error creating file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(retrievalFilePath)
|
||||
defer retrievalFile.Close()
|
||||
|
||||
_, err = Retrieve(hash, retrievalFile, -1, 0, os.TempDir())
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
t.Errorf("Retrieve Error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
buffer := make([]byte, 5)
|
||||
|
||||
retrievalFile.Seek(0, 0)
|
||||
_, _ = retrievalFile.Read(buffer)
|
||||
|
||||
fmt.Printf("Retrieved data: %s", string(buffer))
|
||||
|
||||
if string(buffer) != "butts" {
|
||||
t.Errorf("Expected data butts does not equal Actual data %s", string(buffer))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
t.Run("it deletes data successfully", func(t *testing.T) {
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
folder1 := string(hash[0:2])
|
||||
folder2 := string(hash[2:4])
|
||||
fileName := string(hash[4:])
|
||||
|
||||
if _, err := os.Stat(path.Join(os.TempDir(), folder1, folder2, fileName)); err != nil {
|
||||
t.Errorf("Failed to Store test file")
|
||||
return
|
||||
}
|
||||
|
||||
Delete(hash, os.TempDir())
|
||||
_, err = os.Stat(path.Join(os.TempDir(), folder1, folder2, fileName))
|
||||
if err == nil {
|
||||
t.Errorf("Failed to Delete test file")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Test passing in a hash that doesn't exist
|
||||
t.Run("it returns an error if hash doesn't exist", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
file, err := os.Open(tmpfile)
|
||||
if err != nil {
|
||||
t.Errorf("Error opening tmp file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Errorf("Could not stat test file: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
hash := "0123456789ABCDEFGHIJ"
|
||||
Store(hash, file, int64(fi.Size()), 0, os.TempDir())
|
||||
|
||||
folder1 := string(hash[0:2])
|
||||
folder2 := string(hash[2:4])
|
||||
fileName := string(hash[4:])
|
||||
|
||||
if _, err := os.Stat(path.Join(os.TempDir(), folder1, folder2, fileName)); err != nil {
|
||||
t.Errorf("Failed to Store test file")
|
||||
return
|
||||
}
|
||||
|
||||
falseHash := ""
|
||||
|
||||
err = Delete(falseHash, os.TempDir())
|
||||
assert.NotNil(err)
|
||||
if err != nil {
|
||||
assert.NotEqual(err.Error(), "argError: Hash folder does not exist", "They should be equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
content := []byte("butts")
|
||||
tmpfilePtr, err := ioutil.TempFile("", "api_test")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tmpfile = tmpfilePtr.Name()
|
||||
defer os.Remove(tmpfile) // clean up
|
||||
|
||||
if _, err := tmpfilePtr.Write(content); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpfilePtr.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
m.Run()
|
||||
}
|
Loading…
Reference in New Issue
Block a user