Add bolt store
This commit is contained in:
		| @@ -18,7 +18,6 @@ import ( | |||||||
| 	"gitea.benny.dog/torjus/ezshare/config" | 	"gitea.benny.dog/torjus/ezshare/config" | ||||||
| 	"gitea.benny.dog/torjus/ezshare/pb" | 	"gitea.benny.dog/torjus/ezshare/pb" | ||||||
| 	"gitea.benny.dog/torjus/ezshare/server" | 	"gitea.benny.dog/torjus/ezshare/server" | ||||||
| 	"gitea.benny.dog/torjus/ezshare/store" |  | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| 	"google.golang.org/grpc/credentials" | 	"google.golang.org/grpc/credentials" | ||||||
| @@ -137,7 +136,12 @@ func ActionServe(c *cli.Context) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fileStore := store.NewMemoryFileStore() | 	// Setup store | ||||||
|  | 	s, closeFunc, err := cfg.Server.StoreConfig.GetStore() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("unable to initialize store: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer closeFunc() | ||||||
| 	// Setup shutdown-handling | 	// Setup shutdown-handling | ||||||
| 	rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) | 	rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) | ||||||
| 	defer rootCancel() | 	defer rootCancel() | ||||||
| @@ -157,7 +161,7 @@ func ActionServe(c *cli.Context) error { | |||||||
| 			grpcAddr = c.String("grpc-addr") | 			grpcAddr = c.String("grpc-addr") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		grpcFileServer := server.NewGRPCFileServiceServer(fileStore) | 		grpcFileServer := server.NewGRPCFileServiceServer(s) | ||||||
| 		if c.IsSet("hostname") { | 		if c.IsSet("hostname") { | ||||||
| 			grpcFileServer.Hostname = c.String("hostname") | 			grpcFileServer.Hostname = c.String("hostname") | ||||||
| 		} | 		} | ||||||
| @@ -215,7 +219,7 @@ func ActionServe(c *cli.Context) error { | |||||||
| 		if c.IsSet("http-addr") { | 		if c.IsSet("http-addr") { | ||||||
| 			httpAddr = c.String("http-addr") | 			httpAddr = c.String("http-addr") | ||||||
| 		} | 		} | ||||||
| 		httpServer := server.NewHTTPSever(fileStore) | 		httpServer := server.NewHTTPSever(s) | ||||||
| 		httpServer.Addr = httpAddr | 		httpServer.Addr = httpAddr | ||||||
|  |  | ||||||
| 		// wait for cancel | 		// wait for cancel | ||||||
|   | |||||||
| @@ -10,7 +10,9 @@ import ( | |||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"gitea.benny.dog/torjus/ezshare/store" | ||||||
| 	"github.com/pelletier/go-toml" | 	"github.com/pelletier/go-toml" | ||||||
| 	"google.golang.org/grpc/credentials" | 	"google.golang.org/grpc/credentials" | ||||||
| ) | ) | ||||||
| @@ -26,23 +28,33 @@ type CertificatePaths struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type ServerConfig struct { | type ServerConfig struct { | ||||||
| 	LogLevel string            `toml:"LogLevel"` | 	LogLevel    string             `toml:"LogLevel"` | ||||||
| 	Hostname string            `toml:"Hostname"` | 	Hostname    string             `toml:"Hostname"` | ||||||
| 	GRPC     *ServerGRPCConfig `toml:"GRPC"` | 	StoreConfig *ServerStoreConfig `toml:"Store"` | ||||||
| 	HTTP     *ServerHTTPConfig `toml:"HTTP"` | 	GRPC        *ServerGRPCConfig  `toml:"GRPC"` | ||||||
|  | 	HTTP        *ServerHTTPConfig  `toml:"HTTP"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ServerStoreConfig struct { | type ServerStoreConfig struct { | ||||||
| 	Type          string         `toml:"Type"` | 	Type            string           `toml:"Type"` | ||||||
| 	FSStoreConfig *FSStoreConfig `toml:"Filesystem"` | 	FSStoreConfig   *FSStoreConfig   `toml:"Filesystem"` | ||||||
|  | 	BoltStoreConfig *BoltStoreConfig `toml:"Bolt"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type BoltStoreConfig struct { | ||||||
|  | 	Path string `toml:"Path"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type FSStoreConfig struct { | type FSStoreConfig struct { | ||||||
| 	Dir string `toml:"Dir"` | 	Dir string `toml:"Dir"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ServerGRPCConfig struct { | type ServerGRPCConfig struct { | ||||||
| 	ListenAddr string            `toml:"ListenAddr"` | 	ListenAddr string            `toml:"ListenAddr"` | ||||||
| 	CACerts    *CertificatePaths `toml:"CACerts"` | 	CACerts    *CertificatePaths `toml:"CACerts"` | ||||||
| 	Certs      *CertificatePaths `toml:"Certs"` | 	Certs      *CertificatePaths `toml:"Certs"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ServerHTTPConfig struct { | type ServerHTTPConfig struct { | ||||||
| 	ListenAddr string `toml:"ListenAddr"` | 	ListenAddr string `toml:"ListenAddr"` | ||||||
| } | } | ||||||
| @@ -213,3 +225,24 @@ func (c *Config) ToDefaultFile() error { | |||||||
| 	} | 	} | ||||||
| 	return fmt.Errorf("config-file already exists") | 	return fmt.Errorf("config-file already exists") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (sc *ServerStoreConfig) GetStore() (store.FileStore, func() error, error) { | ||||||
|  | 	nopCloseFunc := func() error { return nil } | ||||||
|  | 	if strings.EqualFold(sc.Type, "bolt") { | ||||||
|  | 		s, err := store.NewBoltStore(sc.BoltStoreConfig.Path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, nil, err | ||||||
|  | 		} | ||||||
|  | 		return s, s.Close, err | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	if strings.EqualFold(sc.Type, "filesystem") { | ||||||
|  | 		s := store.NewFileSystemStore(sc.FSStoreConfig.Dir) | ||||||
|  | 		return s, nopCloseFunc, nil | ||||||
|  | 	} | ||||||
|  | 	if strings.EqualFold(sc.Type, "memory") { | ||||||
|  | 		return store.NewMemoryFileStore(), nopCloseFunc, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, nil, fmt.Errorf("invalid store config") | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,10 +15,15 @@ Hostname = "localhost" | |||||||
| # Storage configuration | # Storage configuration | ||||||
| [Server.Store] | [Server.Store] | ||||||
| # How server stores file | # How server stores file | ||||||
| # Must be one of: filesystem, memory | # Must be one of: filesystem, memory, bolt | ||||||
| # Required | # Required | ||||||
| Type = "filesystem" | Type = "filesystem" | ||||||
|  |  | ||||||
|  | [Server.Store.Bolt] | ||||||
|  | # Where the bolt-db is stored | ||||||
|  | # Required if store-type is bolt | ||||||
|  | Path = "" | ||||||
|  |  | ||||||
| [Server.Store.Filesystem] | [Server.Store.Filesystem] | ||||||
| # Where files are stored | # Where files are stored | ||||||
| # Required if store-type is filesystem | # Required if store-type is filesystem | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ require ( | |||||||
| 	github.com/google/uuid v1.3.0 | 	github.com/google/uuid v1.3.0 | ||||||
| 	github.com/pelletier/go-toml v1.9.4 | 	github.com/pelletier/go-toml v1.9.4 | ||||||
| 	github.com/urfave/cli/v2 v2.3.0 | 	github.com/urfave/cli/v2 v2.3.0 | ||||||
|  | 	go.etcd.io/bbolt v1.3.6 | ||||||
| 	google.golang.org/grpc v1.42.0 | 	google.golang.org/grpc v1.42.0 | ||||||
| 	google.golang.org/protobuf v1.27.1 | 	google.golang.org/protobuf v1.27.1 | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.sum
									
									
									
									
									
								
							| @@ -70,6 +70,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 | |||||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= | ||||||
| github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= | ||||||
|  | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= | ||||||
|  | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= | ||||||
| go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||||
| @@ -97,6 +99,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h | |||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								store/bolt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								store/bolt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | package store | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"gitea.benny.dog/torjus/ezshare/pb" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | 	bolt "go.etcd.io/bbolt" | ||||||
|  | 	"google.golang.org/protobuf/proto" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type BoltStore struct { | ||||||
|  | 	db *bolt.DB | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var bktKey = []byte("files") | ||||||
|  |  | ||||||
|  | func NewBoltStore(path string) (*BoltStore, error) { | ||||||
|  | 	s := &BoltStore{} | ||||||
|  | 	db, err := bolt.Open(path, 0660, &bolt.Options{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("unable to open store: %w", err) | ||||||
|  | 	} | ||||||
|  | 	s.db = db | ||||||
|  | 	err = db.Update(func(t *bolt.Tx) error { | ||||||
|  | 		if _, err := t.CreateBucketIfNotExists(bktKey); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("unable to create bucket: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return s, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *BoltStore) Close() error { | ||||||
|  | 	return s.db.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *BoltStore) GetFile(id string) (*pb.File, error) { | ||||||
|  | 	var file pb.File | ||||||
|  | 	var data []byte | ||||||
|  | 	if err := s.db.View(func(t *bolt.Tx) error { | ||||||
|  | 		bkt := t.Bucket(bktKey) | ||||||
|  | 		data = bkt.Get([]byte(id)) | ||||||
|  | 		return nil | ||||||
|  | 	}); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error getting file: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if data == nil { | ||||||
|  | 		return nil, ErrNoSuchFile | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := proto.Unmarshal(data, &file) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("unable to unmarshal file: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &file, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *BoltStore) StoreFile(file *pb.File) (string, error) { | ||||||
|  | 	file.FileId = uuid.Must(uuid.NewRandom()).String() | ||||||
|  | 	data, err := proto.Marshal(file) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("unable to marshal file: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = s.db.Update(func(t *bolt.Tx) error { | ||||||
|  | 		bkt := t.Bucket(bktKey) | ||||||
|  | 		return bkt.Put([]byte(file.FileId), data) | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("unable to store file: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return file.FileId, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *BoltStore) DeleteFile(id string) error { | ||||||
|  | 	return s.db.Update(func(t *bolt.Tx) error { | ||||||
|  | 		bkt := t.Bucket(bktKey) | ||||||
|  | 		data := bkt.Get([]byte(id)) | ||||||
|  | 		if data == nil { | ||||||
|  | 			return ErrNoSuchFile | ||||||
|  | 		} | ||||||
|  | 		return bkt.Delete([]byte(id)) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								store/bolt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								store/bolt_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | package store_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"gitea.benny.dog/torjus/ezshare/store" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestBoltStore(t *testing.T) { | ||||||
|  | 	path := filepath.Join(t.TempDir(), "boltstore.db") | ||||||
|  | 	s, err := store.NewBoltStore(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Error opening store: %s", err) | ||||||
|  | 	} | ||||||
|  | 	doFileStoreTest(s, t) | ||||||
|  | 	s.Close() | ||||||
|  | } | ||||||
| @@ -1,21 +1,13 @@ | |||||||
| package store_test | package store_test | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"gitea.benny.dog/torjus/ezshare/store" | 	"gitea.benny.dog/torjus/ezshare/store" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestFileStore(t *testing.T) { | func TestFileStore(t *testing.T) { | ||||||
| 	dir, err := ioutil.TempDir(os.TempDir(), "ezshare-test") | 	dir := t.TempDir() | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("unable to create temp-dir") |  | ||||||
| 	} |  | ||||||
| 	defer func() { |  | ||||||
| 		_ = os.RemoveAll(dir) |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	s := store.NewFileSystemStore(dir) | 	s := store.NewFileSystemStore(dir) | ||||||
| 	doFileStoreTest(s, t) | 	doFileStoreTest(s, t) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user