summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buf.gen.yaml17
-rw-r--r--client.go293
-rw-r--r--client_test.go1024
-rw-r--r--download.go71
-rw-r--r--errors.go112
-rw-r--r--gen/zwdaemon/v1/daemon.pb.go1352
-rw-r--r--gen/zwdaemon/v1/daemon_grpc.pb.go463
-rw-r--r--go.mod14
-rw-r--r--go.sum38
-rw-r--r--mise.toml6
-rw-r--r--types.go17
11 files changed, 3406 insertions, 1 deletions
diff --git a/buf.gen.yaml b/buf.gen.yaml
new file mode 100644
index 0000000..e3a3e1d
--- /dev/null
+++ b/buf.gen.yaml
@@ -0,0 +1,17 @@
+version: v2
+clean: true
+
+plugins:
+ - remote: buf.build/protocolbuffers/go:v1.36.11
+ revision: 1
+ out: gen
+ opt:
+ - module=git.pablu.de/pablu/zw-go/gen
+ - remote: buf.build/grpc/go:v1.6.2
+ revision: 1
+ out: gen
+ opt:
+ - module=git.pablu.de/pablu/zw-go/gen
+
+inputs:
+ - directory: ../zw
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..f799f07
--- /dev/null
+++ b/client.go
@@ -0,0 +1,293 @@
+package zw
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "net/url"
+
+ zwdaemonv1 "git.pablu.de/pablu/zw-go/gen/zwdaemon/v1"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+)
+
+const supportedAPIMajor uint32 = 1
+
+func optionalString(value string) *string {
+ if value == "" {
+ return nil
+ }
+ return &value
+}
+
+type Client struct {
+ conn *grpc.ClientConn
+ rpc zwdaemonv1.ZwDaemonServiceClient
+}
+
+func Connect(ctx context.Context, socketPath string) (*Client, error) {
+ target := (&url.URL{
+ Scheme: "unix",
+ Path: socketPath,
+ }).String()
+
+ conn, err := grpc.NewClient(
+ target,
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("create daemon client: %w", err)
+ }
+
+ client := &Client{
+ conn: conn,
+ rpc: zwdaemonv1.NewZwDaemonServiceClient(conn),
+ }
+
+ info, err := client.ServerInfo(ctx)
+ if err != nil {
+ _ = conn.Close()
+ return nil, err
+ }
+
+ if info.APIMajor != supportedAPIMajor {
+ _ = conn.Close()
+ return nil, &IncompatibleAPIVersionError{
+ ServerMajor: info.APIMajor,
+ ServerMinor: info.APIMinor,
+ SupportedMajor: supportedAPIMajor,
+ }
+ }
+
+ return client, nil
+}
+
+func (c *Client) Close() error {
+ return c.conn.Close()
+}
+
+func (c *Client) ServerInfo(ctx context.Context) (ServerInfo, error) {
+ response, err := c.rpc.GetServerInfo(ctx, &zwdaemonv1.GetServerInfoRequest{})
+ if err != nil {
+ return ServerInfo{}, convertRPCError(err)
+ }
+
+ return ServerInfo{
+ APIMajor: response.ApiMajor,
+ APIMinor: response.ApiMinor,
+ DaemonVersion: response.DaemonVersion,
+ }, nil
+}
+
+func (c *Client) Create(ctx context.Context, vaultPath string, password string) error {
+ _, err := c.rpc.Create(ctx, &zwdaemonv1.CreateRequest{
+ VaultPath: vaultPath,
+ Password: password,
+ })
+ if err != nil {
+ return convertRPCError(err)
+ }
+ return nil
+}
+
+func (c *Client) Unseal(ctx context.Context, vaultPath string, password string) error {
+ _, err := c.rpc.Unseal(ctx, &zwdaemonv1.UnsealRequest{
+ VaultPath: vaultPath,
+ Password: password,
+ })
+ if err != nil {
+ return convertRPCError(err)
+ }
+ return nil
+}
+
+func (c *Client) Seal(ctx context.Context) error {
+ _, err := c.rpc.Seal(ctx, &zwdaemonv1.SealRequest{})
+ if err != nil {
+ return convertRPCError(err)
+ }
+ return nil
+}
+
+func (c *Client) ListFiles(ctx context.Context, filter ListFilter) ([]ListedFile, error) {
+ response, err := c.rpc.ListFiles(ctx, &zwdaemonv1.ListFilesRequest{
+ Prefix: optionalString(filter.Prefix),
+ Contains: optionalString(filter.Contains),
+ })
+ if err != nil {
+ return nil, convertRPCError(err)
+ }
+
+ files := make([]ListedFile, len(response.GetFiles()))
+ for index, file := range response.GetFiles() {
+ files[index] = ListedFile{
+ LogicalPath: file.GetLogicalPath(),
+ Size: file.GetSize(),
+ }
+ }
+
+ return files, nil
+}
+
+func (c *Client) ListPaths(ctx context.Context) ([]string, error) {
+ files, err := c.ListFiles(ctx, ListFilter{})
+ if err != nil {
+ return nil, err
+ }
+
+ paths := make([]string, len(files))
+ for index, file := range files {
+ paths[index] = file.LogicalPath
+ }
+
+ return paths, nil
+}
+
+func (c *Client) DeleteFile(ctx context.Context, logicalPath string) error {
+ _, err := c.rpc.DeleteFile(ctx, &zwdaemonv1.DeleteFileRequest{
+ LogicalPath: logicalPath,
+ })
+ if err != nil {
+ return convertRPCError(err)
+ }
+ return nil
+}
+
+func (c *Client) Compact(ctx context.Context) error {
+ _, err := c.rpc.Compact(ctx, &zwdaemonv1.CompactRequest{})
+ if err != nil {
+ return convertRPCError(err)
+ }
+ return nil
+}
+
+func (c *Client) GetFile(
+ ctx context.Context,
+ logicalPath string,
+) (io.ReadCloser, error) {
+ streamCtx, cancel := context.WithCancel(ctx)
+
+ stream, err := c.rpc.GetFile(streamCtx, &zwdaemonv1.GetFileRequest{
+ LogicalPath: logicalPath,
+ })
+ if err != nil {
+ cancel()
+ return nil, convertRPCError(err)
+ }
+
+ return &downloadReader{
+ cancel: cancel,
+ recv: func() ([]byte, error) {
+ response, err := stream.Recv()
+ if err != nil {
+ return nil, err
+ }
+ return response.GetData(), nil
+ },
+ }, nil
+}
+
+func (c *Client) GetFileRange(
+ ctx context.Context,
+ logicalPath string,
+ start uint64,
+ exclusiveEnd uint64,
+) (io.ReadCloser, error) {
+ streamCtx, cancel := context.WithCancel(ctx)
+
+ stream, err := c.rpc.GetFileRange(streamCtx, &zwdaemonv1.GetFileRangeRequest{
+ LogicalPath: logicalPath,
+ Start: start,
+ ExclusiveEnd: exclusiveEnd,
+ })
+ if err != nil {
+ cancel()
+ return nil, convertRPCError(err)
+ }
+
+ return &downloadReader{
+ cancel: cancel,
+ recv: func() ([]byte, error) {
+ response, err := stream.Recv()
+ if err != nil {
+ return nil, err
+ }
+ return response.GetData(), nil
+ },
+ }, nil
+}
+
+func (c *Client) AddFile(
+ ctx context.Context,
+ logicalPath string,
+ source io.Reader,
+ sourceLen uint64,
+) error {
+ uploadCtx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ stream, err := c.rpc.AddFile(uploadCtx)
+ if err != nil {
+ return convertRPCError(err)
+ }
+
+ send := func(request *zwdaemonv1.AddFileRequest) error {
+ err := stream.Send(request)
+ if err == nil {
+ return nil
+ }
+
+ if errors.Is(err, io.EOF) {
+ _, receiveErr := stream.CloseAndRecv()
+ if receiveErr != nil {
+ return convertRPCError(receiveErr)
+ }
+ }
+
+ return convertRPCError(err)
+ }
+
+ err = send(&zwdaemonv1.AddFileRequest{
+ Payload: &zwdaemonv1.AddFileRequest_Header{
+ Header: &zwdaemonv1.AddFileHeader{
+ LogicalPath: logicalPath,
+ SourceLen: sourceLen,
+ },
+ },
+ })
+ if err != nil {
+ return err
+ }
+
+ const chunkSize = 512 * 1024
+ buffer := make([]byte, chunkSize)
+
+ for {
+ n, readErr := source.Read(buffer)
+
+ if n > 0 {
+ err := send(&zwdaemonv1.AddFileRequest{
+ Payload: &zwdaemonv1.AddFileRequest_Chunk{
+ Chunk: buffer[:n],
+ },
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ switch {
+ case readErr == nil:
+ continue
+ case errors.Is(readErr, io.EOF):
+ _, err := stream.CloseAndRecv()
+ if err != nil {
+ return convertRPCError(err)
+ }
+ return nil
+ default:
+ return &UploadReadError{Err: readErr}
+ }
+ }
+}
diff --git a/client_test.go b/client_test.go
new file mode 100644
index 0000000..af890f0
--- /dev/null
+++ b/client_test.go
@@ -0,0 +1,1024 @@
+package zw
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "net"
+ "path/filepath"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ zwdaemonv1 "git.pablu.de/pablu/zw-go/gen/zwdaemon/v1"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ "google.golang.org/protobuf/proto"
+ "google.golang.org/protobuf/types/known/anypb"
+)
+
+const testZwErrorDetailTypeURL = "type.googleapis.com/zw.daemon.v1.ZwErrorDetail"
+
+type testService struct {
+ zwdaemonv1.UnimplementedZwDaemonServiceServer
+
+ response *zwdaemonv1.GetServerInfoResponse
+ err error
+
+ createRequests chan *zwdaemonv1.CreateRequest
+ createErr error
+ unsealRequests chan *zwdaemonv1.UnsealRequest
+ unsealErr error
+ sealCalls chan struct{}
+ sealErr error
+
+ listFilesRequests chan *zwdaemonv1.ListFilesRequest
+ listFilesResponse *zwdaemonv1.ListFilesResponse
+ listFilesErr error
+
+ deleteFileRequests chan *zwdaemonv1.DeleteFileRequest
+ deleteFileErr error
+ compactCalls chan struct{}
+ compactErr error
+
+ getFile func(*zwdaemonv1.GetFileRequest, grpc.ServerStreamingServer[zwdaemonv1.GetFileResponse]) error
+ getFileRange func(*zwdaemonv1.GetFileRangeRequest, grpc.ServerStreamingServer[zwdaemonv1.GetFileRangeResponse]) error
+ addFile func(grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse]) error
+}
+
+func (s *testService) GetServerInfo(
+ context.Context,
+ *zwdaemonv1.GetServerInfoRequest,
+) (*zwdaemonv1.GetServerInfoResponse, error) {
+ return s.response, s.err
+}
+
+func (s *testService) Create(
+ _ context.Context,
+ request *zwdaemonv1.CreateRequest,
+) (*zwdaemonv1.CreateResponse, error) {
+ if s.createRequests != nil {
+ s.createRequests <- request
+ }
+ return &zwdaemonv1.CreateResponse{}, s.createErr
+}
+
+func (s *testService) Unseal(
+ _ context.Context,
+ request *zwdaemonv1.UnsealRequest,
+) (*zwdaemonv1.UnsealResponse, error) {
+ if s.unsealRequests != nil {
+ s.unsealRequests <- request
+ }
+ return &zwdaemonv1.UnsealResponse{}, s.unsealErr
+}
+
+func (s *testService) Seal(
+ context.Context,
+ *zwdaemonv1.SealRequest,
+) (*zwdaemonv1.SealResponse, error) {
+ if s.sealCalls != nil {
+ s.sealCalls <- struct{}{}
+ }
+ return &zwdaemonv1.SealResponse{}, s.sealErr
+}
+
+func (s *testService) ListFiles(
+ _ context.Context,
+ request *zwdaemonv1.ListFilesRequest,
+) (*zwdaemonv1.ListFilesResponse, error) {
+ if s.listFilesRequests != nil {
+ s.listFilesRequests <- request
+ }
+ if s.listFilesResponse == nil {
+ return &zwdaemonv1.ListFilesResponse{}, s.listFilesErr
+ }
+ return s.listFilesResponse, s.listFilesErr
+}
+
+func (s *testService) DeleteFile(
+ _ context.Context,
+ request *zwdaemonv1.DeleteFileRequest,
+) (*zwdaemonv1.DeleteFileResponse, error) {
+ if s.deleteFileRequests != nil {
+ s.deleteFileRequests <- request
+ }
+ return &zwdaemonv1.DeleteFileResponse{}, s.deleteFileErr
+}
+
+func (s *testService) Compact(
+ context.Context,
+ *zwdaemonv1.CompactRequest,
+) (*zwdaemonv1.CompactResponse, error) {
+ if s.compactCalls != nil {
+ s.compactCalls <- struct{}{}
+ }
+ return &zwdaemonv1.CompactResponse{}, s.compactErr
+}
+
+func (s *testService) GetFile(
+ request *zwdaemonv1.GetFileRequest,
+ stream grpc.ServerStreamingServer[zwdaemonv1.GetFileResponse],
+) error {
+ if s.getFile == nil {
+ return nil
+ }
+ return s.getFile(request, stream)
+}
+
+func (s *testService) GetFileRange(
+ request *zwdaemonv1.GetFileRangeRequest,
+ stream grpc.ServerStreamingServer[zwdaemonv1.GetFileRangeResponse],
+) error {
+ if s.getFileRange == nil {
+ return nil
+ }
+ return s.getFileRange(request, stream)
+}
+
+func (s *testService) AddFile(
+ stream grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse],
+) error {
+ if s.addFile == nil {
+ return stream.SendAndClose(&zwdaemonv1.AddFileResponse{})
+ }
+ return s.addFile(stream)
+}
+
+func startTestServer(t *testing.T, service *testService) string {
+ t.Helper()
+
+ socketPath := filepath.Join(t.TempDir(), "daemon.sock")
+ listener, err := net.Listen("unix", socketPath)
+ if err != nil {
+ t.Fatalf("listen on Unix socket: %v", err)
+ }
+
+ server := grpc.NewServer()
+ zwdaemonv1.RegisterZwDaemonServiceServer(server, service)
+
+ serveErr := make(chan error, 1)
+ go func() {
+ serveErr <- server.Serve(listener)
+ }()
+
+ t.Cleanup(func() {
+ server.Stop()
+ if err := <-serveErr; err != nil {
+ t.Errorf("serve gRPC test server: %v", err)
+ }
+ })
+
+ return socketPath
+}
+
+func connectTestClient(t *testing.T, socketPath string) (*Client, error) {
+ t.Helper()
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ return Connect(ctx, socketPath)
+}
+
+func startConnectedTestClient(t *testing.T, service *testService) *Client {
+ t.Helper()
+
+ service.response = &zwdaemonv1.GetServerInfoResponse{ApiMajor: supportedAPIMajor}
+ client, err := connectTestClient(t, startTestServer(t, service))
+ if err != nil {
+ t.Fatalf("Connect() error = %v", err)
+ }
+ t.Cleanup(func() { _ = client.Close() })
+ return client
+}
+
+func TestConnectAcceptsSupportedMajorAndMinorDifferences(t *testing.T) {
+ for _, minor := range []uint32{0, 99} {
+ t.Run(fmt.Sprintf("minor_%d", minor), func(t *testing.T) {
+ socketPath := startTestServer(t, &testService{
+ response: &zwdaemonv1.GetServerInfoResponse{
+ ApiMajor: supportedAPIMajor,
+ ApiMinor: minor,
+ DaemonVersion: "test-version",
+ },
+ })
+
+ client, err := connectTestClient(t, socketPath)
+ if err != nil {
+ t.Fatalf("Connect() error = %v", err)
+ }
+ t.Cleanup(func() { _ = client.Close() })
+ })
+ }
+}
+
+func TestConnectRejectsUnsupportedMajor(t *testing.T) {
+ socketPath := startTestServer(t, &testService{
+ response: &zwdaemonv1.GetServerInfoResponse{
+ ApiMajor: 2,
+ ApiMinor: 3,
+ },
+ })
+
+ client, err := connectTestClient(t, socketPath)
+ if client != nil {
+ _ = client.Close()
+ t.Fatal("Connect() returned a client for an unsupported API major")
+ }
+
+ var versionErr *IncompatibleAPIVersionError
+ if !errors.As(err, &versionErr) {
+ t.Fatalf("Connect() error = %T %v, want *IncompatibleAPIVersionError", err, err)
+ }
+ if versionErr.ServerMajor != 2 || versionErr.ServerMinor != 3 || versionErr.SupportedMajor != 1 {
+ t.Fatalf("version error = %#v", versionErr)
+ }
+}
+
+func TestServerInfoMapsResponse(t *testing.T) {
+ socketPath := startTestServer(t, &testService{
+ response: &zwdaemonv1.GetServerInfoResponse{
+ ApiMajor: 1,
+ ApiMinor: 7,
+ DaemonVersion: "1.2.3-test",
+ },
+ })
+
+ client, err := connectTestClient(t, socketPath)
+ if err != nil {
+ t.Fatalf("Connect() error = %v", err)
+ }
+ t.Cleanup(func() { _ = client.Close() })
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ info, err := client.ServerInfo(ctx)
+ if err != nil {
+ t.Fatalf("ServerInfo() error = %v", err)
+ }
+
+ want := ServerInfo{APIMajor: 1, APIMinor: 7, DaemonVersion: "1.2.3-test"}
+ if info != want {
+ t.Fatalf("ServerInfo() = %#v, want %#v", info, want)
+ }
+}
+
+func TestConnectPreservesStructuredRPCError(t *testing.T) {
+ serverStatus, err := status.New(codes.FailedPrecondition, "vault is sealed").WithDetails(
+ &zwdaemonv1.ZwErrorDetail{
+ Code: zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_VAULT_SEALED,
+ Retryable: true,
+ },
+ )
+ if err != nil {
+ t.Fatalf("create structured status: %v", err)
+ }
+ socketPath := startTestServer(t, &testService{err: serverStatus.Err()})
+
+ _, err = connectTestClient(t, socketPath)
+ rpcErr := requireRPCError(t, err)
+
+ if rpcErr.GRPCCode != codes.FailedPrecondition {
+ t.Errorf("GRPCCode = %v, want %v", rpcErr.GRPCCode, codes.FailedPrecondition)
+ }
+ if rpcErr.ZwCode == nil || *rpcErr.ZwCode != ErrorCodeVaultSealed {
+ t.Errorf("ZwCode = %v, want %v", rpcErr.ZwCode, ErrorCodeVaultSealed)
+ }
+ if rpcErr.Message != "vault is sealed" {
+ t.Errorf("Message = %q, want %q", rpcErr.Message, "vault is sealed")
+ }
+ if !rpcErr.Retryable {
+ t.Error("Retryable = false, want true")
+ }
+ if got := status.Code(err); got != codes.FailedPrecondition {
+ t.Errorf("status.Code(error) = %v, want %v", got, codes.FailedPrecondition)
+ }
+}
+
+func TestConnectPreservesUnknownZwErrorCode(t *testing.T) {
+ const unknownCode zwdaemonv1.ZwErrorCode = 999
+ serverStatus, err := status.New(codes.Internal, "future daemon error").WithDetails(
+ &zwdaemonv1.ZwErrorDetail{Code: unknownCode},
+ )
+ if err != nil {
+ t.Fatalf("create structured status: %v", err)
+ }
+ socketPath := startTestServer(t, &testService{err: serverStatus.Err()})
+
+ _, err = connectTestClient(t, socketPath)
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.ZwCode == nil || *rpcErr.ZwCode != unknownCode {
+ t.Fatalf("ZwCode = %v, want numeric value %d", rpcErr.ZwCode, unknownCode)
+ }
+}
+
+func TestConnectHandlesStatusWithoutZwDetail(t *testing.T) {
+ socketPath := startTestServer(t, &testService{
+ err: status.Error(codes.Unavailable, "daemon unavailable"),
+ })
+
+ _, err := connectTestClient(t, socketPath)
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.ZwCode != nil {
+ t.Errorf("ZwCode = %v, want nil", rpcErr.ZwCode)
+ }
+ if rpcErr.Retryable {
+ t.Error("Retryable = true without ZwErrorDetail")
+ }
+}
+
+func TestConnectIgnoresMalformedZwErrorDetail(t *testing.T) {
+ serverErr := statusWithRawDetail(
+ codes.Internal,
+ "malformed detail",
+ testZwErrorDetailTypeURL,
+ []byte{0xff},
+ )
+ socketPath := startTestServer(t, &testService{err: serverErr})
+
+ _, err := connectTestClient(t, socketPath)
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.ZwCode != nil {
+ t.Errorf("ZwCode = %v, want nil", rpcErr.ZwCode)
+ }
+ if rpcErr.GRPCCode != codes.Internal || rpcErr.Message != "malformed detail" {
+ t.Errorf("base status was not preserved: %#v", rpcErr)
+ }
+}
+
+func TestConnectRequiresExactZwErrorDetailTypeURL(t *testing.T) {
+ encoded, err := proto.Marshal(&zwdaemonv1.ZwErrorDetail{
+ Code: zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_VAULT_SEALED,
+ Retryable: true,
+ })
+ if err != nil {
+ t.Fatalf("marshal ZwErrorDetail: %v", err)
+ }
+ serverErr := statusWithRawDetail(
+ codes.FailedPrecondition,
+ "wrong type URL prefix",
+ "https://example.invalid/zw.daemon.v1.ZwErrorDetail",
+ encoded,
+ )
+ socketPath := startTestServer(t, &testService{err: serverErr})
+
+ _, err = connectTestClient(t, socketPath)
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.ZwCode != nil {
+ t.Errorf("ZwCode = %v for non-canonical type URL, want nil", rpcErr.ZwCode)
+ }
+ if rpcErr.Retryable {
+ t.Error("Retryable = true for non-canonical type URL")
+ }
+}
+
+func TestCreateAndUnsealMapRequests(t *testing.T) {
+ service := &testService{
+ createRequests: make(chan *zwdaemonv1.CreateRequest, 1),
+ unsealRequests: make(chan *zwdaemonv1.UnsealRequest, 1),
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ if err := client.Create(ctx, "/vaults/new.zw", "create-password"); err != nil {
+ t.Fatalf("Create() error = %v", err)
+ }
+ createRequest := <-service.createRequests
+ if createRequest.GetVaultPath() != "/vaults/new.zw" {
+ t.Errorf("Create vault path = %q", createRequest.GetVaultPath())
+ }
+ if createRequest.GetPassword() != "create-password" {
+ t.Errorf("Create password = %q", createRequest.GetPassword())
+ }
+
+ if err := client.Unseal(ctx, "/vaults/existing.zw", "unseal-password"); err != nil {
+ t.Fatalf("Unseal() error = %v", err)
+ }
+ unsealRequest := <-service.unsealRequests
+ if unsealRequest.GetVaultPath() != "/vaults/existing.zw" {
+ t.Errorf("Unseal vault path = %q", unsealRequest.GetVaultPath())
+ }
+ if unsealRequest.GetPassword() != "unseal-password" {
+ t.Errorf("Unseal password = %q", unsealRequest.GetPassword())
+ }
+}
+
+func TestSealAndCompactCallDaemon(t *testing.T) {
+ service := &testService{
+ sealCalls: make(chan struct{}, 1),
+ compactCalls: make(chan struct{}, 1),
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ if err := client.Seal(ctx); err != nil {
+ t.Fatalf("Seal() error = %v", err)
+ }
+ <-service.sealCalls
+
+ if err := client.Compact(ctx); err != nil {
+ t.Fatalf("Compact() error = %v", err)
+ }
+ <-service.compactCalls
+}
+
+func TestListFilesMapsFilterAndPreservesResponseOrder(t *testing.T) {
+ service := &testService{
+ listFilesRequests: make(chan *zwdaemonv1.ListFilesRequest, 1),
+ listFilesResponse: &zwdaemonv1.ListFilesResponse{
+ Files: []*zwdaemonv1.ListedFile{
+ {LogicalPath: "logs/second.txt", Size: 22},
+ {LogicalPath: "logs/first.txt", Size: 11},
+ },
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ files, err := client.ListFiles(ctx, ListFilter{Prefix: "logs/", Contains: "txt"})
+ if err != nil {
+ t.Fatalf("ListFiles() error = %v", err)
+ }
+
+ request := <-service.listFilesRequests
+ if request.Prefix == nil || request.GetPrefix() != "logs/" {
+ t.Errorf("ListFiles prefix = %v", request.Prefix)
+ }
+ if request.Contains == nil || request.GetContains() != "txt" {
+ t.Errorf("ListFiles contains = %v", request.Contains)
+ }
+
+ want := []ListedFile{
+ {LogicalPath: "logs/second.txt", Size: 22},
+ {LogicalPath: "logs/first.txt", Size: 11},
+ }
+ if len(files) != len(want) {
+ t.Fatalf("ListFiles() returned %d files, want %d", len(files), len(want))
+ }
+ for index := range want {
+ if files[index] != want[index] {
+ t.Errorf("ListFiles()[%d] = %#v, want %#v", index, files[index], want[index])
+ }
+ }
+}
+
+func TestListPathsOmitsEmptyFiltersAndMapsPaths(t *testing.T) {
+ service := &testService{
+ listFilesRequests: make(chan *zwdaemonv1.ListFilesRequest, 1),
+ listFilesResponse: &zwdaemonv1.ListFilesResponse{
+ Files: []*zwdaemonv1.ListedFile{
+ {LogicalPath: "first", Size: 10},
+ {LogicalPath: "second", Size: 20},
+ },
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ paths, err := client.ListPaths(ctx)
+ if err != nil {
+ t.Fatalf("ListPaths() error = %v", err)
+ }
+
+ request := <-service.listFilesRequests
+ if request.Prefix != nil || request.Contains != nil {
+ t.Errorf("ListPaths request filters = prefix %v, contains %v; want both absent", request.Prefix, request.Contains)
+ }
+ want := []string{"first", "second"}
+ if len(paths) != len(want) {
+ t.Fatalf("ListPaths() returned %d paths, want %d", len(paths), len(want))
+ }
+ for index := range want {
+ if paths[index] != want[index] {
+ t.Errorf("ListPaths()[%d] = %q, want %q", index, paths[index], want[index])
+ }
+ }
+}
+
+func TestDeleteFileMapsRequestAndStructuredError(t *testing.T) {
+ serverStatus, err := status.New(codes.NotFound, "entry not found").WithDetails(
+ &zwdaemonv1.ZwErrorDetail{
+ Code: zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_ENTRY_NOT_FOUND,
+ },
+ )
+ if err != nil {
+ t.Fatalf("create structured status: %v", err)
+ }
+ service := &testService{
+ deleteFileRequests: make(chan *zwdaemonv1.DeleteFileRequest, 1),
+ deleteFileErr: serverStatus.Err(),
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ err = client.DeleteFile(ctx, "missing.txt")
+ request := <-service.deleteFileRequests
+ if request.GetLogicalPath() != "missing.txt" {
+ t.Errorf("DeleteFile logical path = %q", request.GetLogicalPath())
+ }
+
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.GRPCCode != codes.NotFound {
+ t.Errorf("GRPCCode = %v, want %v", rpcErr.GRPCCode, codes.NotFound)
+ }
+ if rpcErr.ZwCode == nil || *rpcErr.ZwCode != ErrorCodeEntryNotFound {
+ t.Errorf("ZwCode = %v, want %v", rpcErr.ZwCode, ErrorCodeEntryNotFound)
+ }
+}
+
+func TestConvertRPCErrorAcceptsNil(t *testing.T) {
+ if err := convertRPCError(nil); err != nil {
+ t.Fatalf("convertRPCError(nil) = %v", err)
+ }
+}
+
+func TestGetFileReadsChunksAcrossBuffersAndSkipsEmptyChunks(t *testing.T) {
+ requests := make(chan *zwdaemonv1.GetFileRequest, 1)
+ service := &testService{
+ getFile: func(
+ request *zwdaemonv1.GetFileRequest,
+ stream grpc.ServerStreamingServer[zwdaemonv1.GetFileResponse],
+ ) error {
+ requests <- request
+ for _, chunk := range [][]byte{[]byte("abc"), nil, []byte("defgh")} {
+ if err := stream.Send(&zwdaemonv1.GetFileResponse{Data: chunk}); err != nil {
+ return err
+ }
+ }
+ return nil
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ reader, err := client.GetFile(ctx, "folder/file.bin")
+ if err != nil {
+ t.Fatalf("GetFile() error = %v", err)
+ }
+ t.Cleanup(func() { _ = reader.Close() })
+
+ request := <-requests
+ if request.GetLogicalPath() != "folder/file.bin" {
+ t.Errorf("GetFile logical path = %q", request.GetLogicalPath())
+ }
+
+ buffer := make([]byte, 2)
+ var contents []byte
+ for {
+ n, err := reader.Read(buffer)
+ contents = append(contents, buffer[:n]...)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("Read() error = %v", err)
+ }
+ if n == 0 {
+ t.Fatal("Read() returned no bytes and no error")
+ }
+ }
+ if got, want := string(contents), "abcdefgh"; got != want {
+ t.Fatalf("download contents = %q, want %q", got, want)
+ }
+}
+
+func TestGetFileRangeMapsRequestAndReadsResponse(t *testing.T) {
+ requests := make(chan *zwdaemonv1.GetFileRangeRequest, 1)
+ service := &testService{
+ getFileRange: func(
+ request *zwdaemonv1.GetFileRangeRequest,
+ stream grpc.ServerStreamingServer[zwdaemonv1.GetFileRangeResponse],
+ ) error {
+ requests <- request
+ return stream.Send(&zwdaemonv1.GetFileRangeResponse{Data: []byte("range")})
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ reader, err := client.GetFileRange(ctx, "large.bin", 10, 15)
+ if err != nil {
+ t.Fatalf("GetFileRange() error = %v", err)
+ }
+ t.Cleanup(func() { _ = reader.Close() })
+
+ contents, err := io.ReadAll(reader)
+ if err != nil {
+ t.Fatalf("read range: %v", err)
+ }
+ if got, want := string(contents), "range"; got != want {
+ t.Fatalf("range contents = %q, want %q", got, want)
+ }
+
+ request := <-requests
+ if request.GetLogicalPath() != "large.bin" || request.GetStart() != 10 || request.GetExclusiveEnd() != 15 {
+ t.Fatalf("GetFileRange request = %#v", request)
+ }
+}
+
+func TestGetFileReturnsTerminalStructuredErrorAfterReceivedData(t *testing.T) {
+ serverStatus, err := status.New(codes.DataLoss, "download failed").WithDetails(
+ &zwdaemonv1.ZwErrorDetail{
+ Code: zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_INTERNAL,
+ Retryable: true,
+ },
+ )
+ if err != nil {
+ t.Fatalf("create structured status: %v", err)
+ }
+ service := &testService{
+ getFile: func(
+ _ *zwdaemonv1.GetFileRequest,
+ stream grpc.ServerStreamingServer[zwdaemonv1.GetFileResponse],
+ ) error {
+ if err := stream.Send(&zwdaemonv1.GetFileResponse{Data: []byte("partial")}); err != nil {
+ return err
+ }
+ return serverStatus.Err()
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ reader, err := client.GetFile(ctx, "damaged.bin")
+ if err != nil {
+ t.Fatalf("GetFile() error = %v", err)
+ }
+ t.Cleanup(func() { _ = reader.Close() })
+
+ buffer := make([]byte, 32)
+ n, err := reader.Read(buffer)
+ if err != nil {
+ t.Fatalf("first Read() error = %v", err)
+ }
+ if got, want := string(buffer[:n]), "partial"; got != want {
+ t.Fatalf("first Read() = %q, want %q", got, want)
+ }
+
+ n, err = reader.Read(buffer)
+ if n != 0 {
+ t.Errorf("terminal Read() returned %d bytes", n)
+ }
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.GRPCCode != codes.DataLoss {
+ t.Errorf("GRPCCode = %v, want %v", rpcErr.GRPCCode, codes.DataLoss)
+ }
+ if rpcErr.ZwCode == nil || *rpcErr.ZwCode != ErrorCodeInternal {
+ t.Errorf("ZwCode = %v, want %v", rpcErr.ZwCode, ErrorCodeInternal)
+ }
+ if rpcErr.Message != "download failed" || !rpcErr.Retryable {
+ t.Errorf("terminal RPC error = %#v", rpcErr)
+ }
+
+ infoCtx, infoCancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer infoCancel()
+ if _, err := client.ServerInfo(infoCtx); err != nil {
+ t.Fatalf("ServerInfo() after failed download = %v", err)
+ }
+}
+
+func TestClosingDownloadCancelsStreamAndKeepsClientReusable(t *testing.T) {
+ started := make(chan struct{}, 1)
+ canceled := make(chan struct{}, 1)
+ service := &testService{
+ getFile: func(
+ _ *zwdaemonv1.GetFileRequest,
+ stream grpc.ServerStreamingServer[zwdaemonv1.GetFileResponse],
+ ) error {
+ started <- struct{}{}
+ <-stream.Context().Done()
+ canceled <- struct{}{}
+ return stream.Context().Err()
+ },
+ }
+ client := startConnectedTestClient(t, service)
+
+ reader, err := client.GetFile(context.Background(), "abandoned.bin")
+ if err != nil {
+ t.Fatalf("GetFile() error = %v", err)
+ }
+ select {
+ case <-started:
+ case <-time.After(5 * time.Second):
+ t.Fatal("server did not start download")
+ }
+
+ if err := reader.Close(); err != nil {
+ t.Fatalf("Close() error = %v", err)
+ }
+ if err := reader.Close(); err != nil {
+ t.Fatalf("second Close() error = %v", err)
+ }
+ if _, err := reader.Read(make([]byte, 1)); !errors.Is(err, fs.ErrClosed) {
+ t.Fatalf("Read() after Close() error = %v, want fs.ErrClosed", err)
+ }
+
+ select {
+ case <-canceled:
+ case <-time.After(5 * time.Second):
+ t.Fatal("closing reader did not cancel server stream")
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if _, err := client.ServerInfo(ctx); err != nil {
+ t.Fatalf("ServerInfo() after closing download = %v", err)
+ }
+}
+
+func TestAddFileSendsHeaderBeforeEmptyAndMultiChunkBodies(t *testing.T) {
+ tests := []struct {
+ name string
+ contents []byte
+ chunkSizes []int
+ }{
+ {name: "empty"},
+ {
+ name: "multiple_chunks",
+ contents: bytes.Repeat([]byte("x"), 512*1024+137),
+ chunkSizes: []int{512 * 1024, 137},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ received := make(chan receivedUpload, 1)
+ service := &testService{
+ addFile: func(
+ stream grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse],
+ ) error {
+ upload, err := receiveUpload(stream)
+ if err != nil {
+ return err
+ }
+ received <- upload
+ return stream.SendAndClose(&zwdaemonv1.AddFileResponse{})
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ err := client.AddFile(ctx, "upload.bin", bytes.NewReader(test.contents), uint64(len(test.contents)))
+ if err != nil {
+ t.Fatalf("AddFile() error = %v", err)
+ }
+
+ upload := <-received
+ if upload.header.GetLogicalPath() != "upload.bin" {
+ t.Errorf("logical path = %q", upload.header.GetLogicalPath())
+ }
+ if upload.header.GetSourceLen() != uint64(len(test.contents)) {
+ t.Errorf("source length = %d", upload.header.GetSourceLen())
+ }
+ if !bytes.Equal(upload.contents, test.contents) {
+ t.Errorf("uploaded contents differ")
+ }
+ if len(upload.chunkSizes) != len(test.chunkSizes) {
+ t.Fatalf("chunk sizes = %v, want %v", upload.chunkSizes, test.chunkSizes)
+ }
+ for index := range test.chunkSizes {
+ if upload.chunkSizes[index] != test.chunkSizes[index] {
+ t.Errorf("chunk sizes = %v, want %v", upload.chunkSizes, test.chunkSizes)
+ }
+ }
+ })
+ }
+}
+
+func TestAddFileProcessesDataReturnedWithEOF(t *testing.T) {
+ received := make(chan receivedUpload, 1)
+ service := &testService{
+ addFile: func(
+ stream grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse],
+ ) error {
+ upload, err := receiveUpload(stream)
+ if err != nil {
+ return err
+ }
+ received <- upload
+ return stream.SendAndClose(&zwdaemonv1.AddFileResponse{})
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ err := client.AddFile(ctx, "combined.bin", &dataAndEOFReader{data: []byte("contents")}, 8)
+ if err != nil {
+ t.Fatalf("AddFile() error = %v", err)
+ }
+ upload := <-received
+ if got, want := string(upload.contents), "contents"; got != want {
+ t.Fatalf("uploaded contents = %q, want %q", got, want)
+ }
+}
+
+func TestAddFileReturnsLengthRejectionFromCloseAndRecv(t *testing.T) {
+ serverStatus, err := status.New(codes.InvalidArgument, "declared length does not match source").WithDetails(
+ &zwdaemonv1.ZwErrorDetail{
+ Code: zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_INVALID_REQUEST,
+ },
+ )
+ if err != nil {
+ t.Fatalf("create structured status: %v", err)
+ }
+ service := &testService{
+ addFile: func(
+ stream grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse],
+ ) error {
+ if _, err := receiveUpload(stream); err != nil {
+ return err
+ }
+ return serverStatus.Err()
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ err = client.AddFile(ctx, "short.bin", bytes.NewReader([]byte("abc")), 4)
+ rpcErr := requireRPCError(t, err)
+ if rpcErr.GRPCCode != codes.InvalidArgument {
+ t.Errorf("GRPCCode = %v, want %v", rpcErr.GRPCCode, codes.InvalidArgument)
+ }
+ if rpcErr.ZwCode == nil || *rpcErr.ZwCode != ErrorCodeInvalidRequest {
+ t.Errorf("ZwCode = %v, want %v", rpcErr.ZwCode, ErrorCodeInvalidRequest)
+ }
+}
+
+func TestUploadReadFailureCancelsRPCAndConnectionAndPathRemainReusable(t *testing.T) {
+ injectedErr := errors.New("injected source error")
+ firstStreamErr := make(chan error, 1)
+ headers := make(chan *zwdaemonv1.AddFileHeader, 2)
+ var calls atomic.Int32
+
+ service := &testService{
+ addFile: func(
+ stream grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse],
+ ) error {
+ call := calls.Add(1)
+ if call == 1 {
+ first, err := stream.Recv()
+ if err != nil {
+ firstStreamErr <- err
+ return err
+ }
+ headers <- first.GetHeader()
+
+ for {
+ _, err := stream.Recv()
+ if err != nil {
+ firstStreamErr <- err
+ return err
+ }
+ }
+ }
+
+ upload, err := receiveUpload(stream)
+ if err != nil {
+ return err
+ }
+ headers <- upload.header
+ return stream.SendAndClose(&zwdaemonv1.AddFileResponse{})
+ },
+ }
+ client := startConnectedTestClient(t, service)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ err := client.AddFile(ctx, "reusable.bin", &dataAndErrorReader{
+ data: []byte("partial"),
+ err: injectedErr,
+ }, 7)
+ var readErr *UploadReadError
+ if !errors.As(err, &readErr) {
+ t.Fatalf("AddFile() error = %T %v, want *UploadReadError", err, err)
+ }
+ if !errors.Is(err, injectedErr) {
+ t.Fatalf("AddFile() error does not wrap source error: %v", err)
+ }
+
+ select {
+ case err := <-firstStreamErr:
+ if errors.Is(err, io.EOF) {
+ t.Fatal("reader failure cleanly closed the upload stream")
+ }
+ if !errors.Is(err, context.Canceled) && status.Code(err) != codes.Canceled {
+ t.Fatalf("server stream error = %v, want cancellation", err)
+ }
+ case <-time.After(5 * time.Second):
+ t.Fatal("server did not observe upload cancellation")
+ }
+
+ infoCtx, infoCancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer infoCancel()
+ if _, err := client.ServerInfo(infoCtx); err != nil {
+ t.Fatalf("ServerInfo() after reader failure = %v", err)
+ }
+
+ if err := client.AddFile(ctx, "reusable.bin", bytes.NewReader([]byte("replacement")), 11); err != nil {
+ t.Fatalf("second AddFile() on same path = %v", err)
+ }
+ for index := 0; index < 2; index++ {
+ header := <-headers
+ if header == nil || header.GetLogicalPath() != "reusable.bin" {
+ t.Fatalf("upload header = %#v", header)
+ }
+ }
+}
+
+type receivedUpload struct {
+ header *zwdaemonv1.AddFileHeader
+ contents []byte
+ chunkSizes []int
+}
+
+func receiveUpload(
+ stream grpc.ClientStreamingServer[zwdaemonv1.AddFileRequest, zwdaemonv1.AddFileResponse],
+) (receivedUpload, error) {
+ first, err := stream.Recv()
+ if err != nil {
+ return receivedUpload{}, err
+ }
+ header := first.GetHeader()
+ if header == nil {
+ return receivedUpload{}, status.Error(codes.InvalidArgument, "first upload message is not a header")
+ }
+
+ upload := receivedUpload{header: header}
+ for {
+ request, err := stream.Recv()
+ if err == io.EOF {
+ return upload, nil
+ }
+ if err != nil {
+ return upload, err
+ }
+ payload, ok := request.GetPayload().(*zwdaemonv1.AddFileRequest_Chunk)
+ if !ok {
+ return upload, status.Error(codes.InvalidArgument, "upload message after header is not a chunk")
+ }
+ upload.contents = append(upload.contents, payload.Chunk...)
+ upload.chunkSizes = append(upload.chunkSizes, len(payload.Chunk))
+ }
+}
+
+type dataAndEOFReader struct {
+ data []byte
+ done bool
+}
+
+func (r *dataAndEOFReader) Read(destination []byte) (int, error) {
+ if r.done {
+ return 0, io.EOF
+ }
+ r.done = true
+ return copy(destination, r.data), io.EOF
+}
+
+type dataAndErrorReader struct {
+ data []byte
+ err error
+ done bool
+}
+
+func (r *dataAndErrorReader) Read(destination []byte) (int, error) {
+ if r.done {
+ return 0, r.err
+ }
+ r.done = true
+ return copy(destination, r.data), r.err
+}
+
+func requireRPCError(t *testing.T, err error) *RPCError {
+ t.Helper()
+
+ var rpcErr *RPCError
+ if !errors.As(err, &rpcErr) {
+ t.Fatalf("error = %T %v, want *RPCError", err, err)
+ }
+ return rpcErr
+}
+
+func statusWithRawDetail(code codes.Code, message, typeURL string, value []byte) error {
+ statusProto := status.New(code, message).Proto()
+ statusProto.Details = append(statusProto.Details, &anypb.Any{
+ TypeUrl: typeURL,
+ Value: value,
+ })
+ return status.FromProto(statusProto).Err()
+}
diff --git a/download.go b/download.go
new file mode 100644
index 0000000..19dd1b2
--- /dev/null
+++ b/download.go
@@ -0,0 +1,71 @@
+package zw
+
+import (
+ "context"
+ "io"
+ "io/fs"
+ "sync"
+ "sync/atomic"
+)
+
+type downloadReader struct {
+ recv func() ([]byte, error)
+ cancel context.CancelFunc
+
+ pending []byte
+ terminal error
+
+ cancelOnce sync.Once
+ closed atomic.Bool
+}
+
+func (r *downloadReader) Read(destination []byte) (int, error) {
+ if len(destination) == 0 {
+ return 0, nil
+ }
+
+ if r.closed.Load() {
+ return 0, fs.ErrClosed
+ }
+
+ if len(r.pending) > 0 {
+ n := copy(destination, r.pending)
+ r.pending = r.pending[n:]
+ return n, nil
+ }
+
+ if r.terminal != nil {
+ return 0, r.terminal
+ }
+
+ for {
+ chunk, err := r.recv()
+ if err != nil {
+ if r.closed.Load() {
+ return 0, fs.ErrClosed
+ }
+
+ if err == io.EOF {
+ r.terminal = io.EOF
+ } else {
+ r.terminal = convertRPCError(err)
+ }
+ r.cancelOnce.Do(r.cancel)
+ return 0, r.terminal
+ }
+
+ if len(chunk) == 0 {
+ continue
+ }
+
+ n := copy(destination, chunk)
+ r.pending = chunk[n:]
+ return n, nil
+ }
+}
+
+func (r *downloadReader) Close() error {
+ r.closed.Store(true)
+ r.cancelOnce.Do(r.cancel)
+ return nil
+}
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..d308853
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,112 @@
+package zw
+
+import (
+ "fmt"
+
+ zwdaemonv1 "git.pablu.de/pablu/zw-go/gen/zwdaemon/v1"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+const zwErrorDetailTypeURL = "type.googleapis.com/zw.daemon.v1.ZwErrorDetail"
+
+type ErrorCode = zwdaemonv1.ZwErrorCode
+
+const (
+ ErrorCodeUnspecified = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_UNSPECIFIED
+ ErrorCodeVaultSealed = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_VAULT_SEALED
+ ErrorCodeVaultAlreadyUnsealed = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_VAULT_ALREADY_UNSEALED
+ ErrorCodeEntryNotFound = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_ENTRY_NOT_FOUND
+ ErrorCodeEntryAlreadyExists = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_ENTRY_ALREADY_EXISTS
+ ErrorCodeAuthenticationOrCorruption = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_AUTHENTICATION_OR_CORRUPTION
+ ErrorCodeInvalidVaultFormat = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_INVALID_VAULT_FORMAT
+ ErrorCodeUnsupportedVaultVersion = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_UNSUPPORTED_VAULT_VERSION
+ ErrorCodeUnsupportedProtocolVersion = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_UNSUPPORTED_PROTOCOL_VERSION
+ ErrorCodePermissionDenied = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_PERMISSION_DENIED
+ ErrorCodeStorageFull = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_STORAGE_FULL
+ ErrorCodeCommitFailed = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_COMMIT_FAILED
+ ErrorCodeInvalidRequest = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_INVALID_REQUEST
+ ErrorCodeInternal = zwdaemonv1.ZwErrorCode_ZW_ERROR_CODE_INTERNAL
+)
+
+type RPCError struct {
+ GRPCCode codes.Code
+ ZwCode *ErrorCode
+ Message string
+ Retryable bool
+
+ status *status.Status
+}
+
+func (e *RPCError) Error() string {
+ return e.Message
+}
+
+func (e *RPCError) GRPCStatus() *status.Status {
+ if e.status != nil {
+ return e.status
+ }
+ return status.New(e.GRPCCode, e.Message)
+}
+
+type IncompatibleAPIVersionError struct {
+ ServerMajor uint32
+ ServerMinor uint32
+ SupportedMajor uint32
+}
+
+func (e *IncompatibleAPIVersionError) Error() string {
+ return fmt.Sprintf(
+ "daemon API version %d.%d is incompatible; this client supports major version %d",
+ e.ServerMajor,
+ e.ServerMinor,
+ e.SupportedMajor,
+ )
+}
+
+type UploadReadError struct {
+ Err error
+}
+
+func (e *UploadReadError) Error() string {
+ return "read upload source: " + e.Err.Error()
+}
+
+func (e *UploadReadError) Unwrap() error {
+ return e.Err
+}
+
+func convertRPCError(err error) error {
+ if err == nil {
+ return nil
+ }
+
+ grpcStatus, ok := status.FromError(err)
+ if !ok {
+ return err
+ }
+
+ rpcErr := &RPCError{
+ GRPCCode: grpcStatus.Code(),
+ Message: grpcStatus.Message(),
+ status: grpcStatus,
+ }
+
+ for _, detail := range grpcStatus.Proto().GetDetails() {
+ if detail.GetTypeUrl() != zwErrorDetailTypeURL {
+ continue
+ }
+
+ var zwDetail zwdaemonv1.ZwErrorDetail
+ if err := detail.UnmarshalTo(&zwDetail); err != nil {
+ continue
+ }
+
+ code := zwDetail.GetCode()
+ rpcErr.ZwCode = &code
+ rpcErr.Retryable = zwDetail.GetRetryable()
+ break
+ }
+
+ return rpcErr
+}
diff --git a/gen/zwdaemon/v1/daemon.pb.go b/gen/zwdaemon/v1/daemon.pb.go
new file mode 100644
index 0000000..68fb3c3
--- /dev/null
+++ b/gen/zwdaemon/v1/daemon.pb.go
@@ -0,0 +1,1352 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.11
+// protoc (unknown)
+// source: zw/daemon/v1/daemon.proto
+
+package zwdaemonv1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ZwErrorCode int32
+
+const (
+ ZwErrorCode_ZW_ERROR_CODE_UNSPECIFIED ZwErrorCode = 0
+ ZwErrorCode_ZW_ERROR_CODE_VAULT_SEALED ZwErrorCode = 1
+ ZwErrorCode_ZW_ERROR_CODE_VAULT_ALREADY_UNSEALED ZwErrorCode = 2
+ ZwErrorCode_ZW_ERROR_CODE_ENTRY_NOT_FOUND ZwErrorCode = 3
+ ZwErrorCode_ZW_ERROR_CODE_ENTRY_ALREADY_EXISTS ZwErrorCode = 4
+ ZwErrorCode_ZW_ERROR_CODE_AUTHENTICATION_OR_CORRUPTION ZwErrorCode = 5
+ ZwErrorCode_ZW_ERROR_CODE_INVALID_VAULT_FORMAT ZwErrorCode = 6
+ ZwErrorCode_ZW_ERROR_CODE_UNSUPPORTED_VAULT_VERSION ZwErrorCode = 7
+ ZwErrorCode_ZW_ERROR_CODE_UNSUPPORTED_PROTOCOL_VERSION ZwErrorCode = 8
+ ZwErrorCode_ZW_ERROR_CODE_PERMISSION_DENIED ZwErrorCode = 9
+ ZwErrorCode_ZW_ERROR_CODE_STORAGE_FULL ZwErrorCode = 10
+ ZwErrorCode_ZW_ERROR_CODE_COMMIT_FAILED ZwErrorCode = 11
+ ZwErrorCode_ZW_ERROR_CODE_INVALID_REQUEST ZwErrorCode = 12
+ ZwErrorCode_ZW_ERROR_CODE_INTERNAL ZwErrorCode = 13
+)
+
+// Enum value maps for ZwErrorCode.
+var (
+ ZwErrorCode_name = map[int32]string{
+ 0: "ZW_ERROR_CODE_UNSPECIFIED",
+ 1: "ZW_ERROR_CODE_VAULT_SEALED",
+ 2: "ZW_ERROR_CODE_VAULT_ALREADY_UNSEALED",
+ 3: "ZW_ERROR_CODE_ENTRY_NOT_FOUND",
+ 4: "ZW_ERROR_CODE_ENTRY_ALREADY_EXISTS",
+ 5: "ZW_ERROR_CODE_AUTHENTICATION_OR_CORRUPTION",
+ 6: "ZW_ERROR_CODE_INVALID_VAULT_FORMAT",
+ 7: "ZW_ERROR_CODE_UNSUPPORTED_VAULT_VERSION",
+ 8: "ZW_ERROR_CODE_UNSUPPORTED_PROTOCOL_VERSION",
+ 9: "ZW_ERROR_CODE_PERMISSION_DENIED",
+ 10: "ZW_ERROR_CODE_STORAGE_FULL",
+ 11: "ZW_ERROR_CODE_COMMIT_FAILED",
+ 12: "ZW_ERROR_CODE_INVALID_REQUEST",
+ 13: "ZW_ERROR_CODE_INTERNAL",
+ }
+ ZwErrorCode_value = map[string]int32{
+ "ZW_ERROR_CODE_UNSPECIFIED": 0,
+ "ZW_ERROR_CODE_VAULT_SEALED": 1,
+ "ZW_ERROR_CODE_VAULT_ALREADY_UNSEALED": 2,
+ "ZW_ERROR_CODE_ENTRY_NOT_FOUND": 3,
+ "ZW_ERROR_CODE_ENTRY_ALREADY_EXISTS": 4,
+ "ZW_ERROR_CODE_AUTHENTICATION_OR_CORRUPTION": 5,
+ "ZW_ERROR_CODE_INVALID_VAULT_FORMAT": 6,
+ "ZW_ERROR_CODE_UNSUPPORTED_VAULT_VERSION": 7,
+ "ZW_ERROR_CODE_UNSUPPORTED_PROTOCOL_VERSION": 8,
+ "ZW_ERROR_CODE_PERMISSION_DENIED": 9,
+ "ZW_ERROR_CODE_STORAGE_FULL": 10,
+ "ZW_ERROR_CODE_COMMIT_FAILED": 11,
+ "ZW_ERROR_CODE_INVALID_REQUEST": 12,
+ "ZW_ERROR_CODE_INTERNAL": 13,
+ }
+)
+
+func (x ZwErrorCode) Enum() *ZwErrorCode {
+ p := new(ZwErrorCode)
+ *p = x
+ return p
+}
+
+func (x ZwErrorCode) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ZwErrorCode) Descriptor() protoreflect.EnumDescriptor {
+ return file_zw_daemon_v1_daemon_proto_enumTypes[0].Descriptor()
+}
+
+func (ZwErrorCode) Type() protoreflect.EnumType {
+ return &file_zw_daemon_v1_daemon_proto_enumTypes[0]
+}
+
+func (x ZwErrorCode) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ZwErrorCode.Descriptor instead.
+func (ZwErrorCode) EnumDescriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{0}
+}
+
+type GetServerInfoRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetServerInfoRequest) Reset() {
+ *x = GetServerInfoRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetServerInfoRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetServerInfoRequest) ProtoMessage() {}
+
+func (x *GetServerInfoRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetServerInfoRequest.ProtoReflect.Descriptor instead.
+func (*GetServerInfoRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{0}
+}
+
+type SealRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SealRequest) Reset() {
+ *x = SealRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SealRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SealRequest) ProtoMessage() {}
+
+func (x *SealRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SealRequest.ProtoReflect.Descriptor instead.
+func (*SealRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{1}
+}
+
+type SealResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SealResponse) Reset() {
+ *x = SealResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SealResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SealResponse) ProtoMessage() {}
+
+func (x *SealResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SealResponse.ProtoReflect.Descriptor instead.
+func (*SealResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{2}
+}
+
+type CreateResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateResponse) Reset() {
+ *x = CreateResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateResponse) ProtoMessage() {}
+
+func (x *CreateResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateResponse.ProtoReflect.Descriptor instead.
+func (*CreateResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{3}
+}
+
+type UnsealResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UnsealResponse) Reset() {
+ *x = UnsealResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UnsealResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnsealResponse) ProtoMessage() {}
+
+func (x *UnsealResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UnsealResponse.ProtoReflect.Descriptor instead.
+func (*UnsealResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{4}
+}
+
+type CompactResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CompactResponse) Reset() {
+ *x = CompactResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CompactResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CompactResponse) ProtoMessage() {}
+
+func (x *CompactResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CompactResponse.ProtoReflect.Descriptor instead.
+func (*CompactResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{5}
+}
+
+type CompactRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CompactRequest) Reset() {
+ *x = CompactRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CompactRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CompactRequest) ProtoMessage() {}
+
+func (x *CompactRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CompactRequest.ProtoReflect.Descriptor instead.
+func (*CompactRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{6}
+}
+
+type AddFileResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AddFileResponse) Reset() {
+ *x = AddFileResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AddFileResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddFileResponse) ProtoMessage() {}
+
+func (x *AddFileResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddFileResponse.ProtoReflect.Descriptor instead.
+func (*AddFileResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{7}
+}
+
+type DeleteFileResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeleteFileResponse) Reset() {
+ *x = DeleteFileResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteFileResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteFileResponse) ProtoMessage() {}
+
+func (x *DeleteFileResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteFileResponse.ProtoReflect.Descriptor instead.
+func (*DeleteFileResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{8}
+}
+
+type GetServerInfoResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ApiMajor uint32 `protobuf:"varint,1,opt,name=api_major,json=apiMajor,proto3" json:"api_major,omitempty"`
+ ApiMinor uint32 `protobuf:"varint,2,opt,name=api_minor,json=apiMinor,proto3" json:"api_minor,omitempty"`
+ DaemonVersion string `protobuf:"bytes,3,opt,name=daemon_version,json=daemonVersion,proto3" json:"daemon_version,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetServerInfoResponse) Reset() {
+ *x = GetServerInfoResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetServerInfoResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetServerInfoResponse) ProtoMessage() {}
+
+func (x *GetServerInfoResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetServerInfoResponse.ProtoReflect.Descriptor instead.
+func (*GetServerInfoResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *GetServerInfoResponse) GetApiMajor() uint32 {
+ if x != nil {
+ return x.ApiMajor
+ }
+ return 0
+}
+
+func (x *GetServerInfoResponse) GetApiMinor() uint32 {
+ if x != nil {
+ return x.ApiMinor
+ }
+ return 0
+}
+
+func (x *GetServerInfoResponse) GetDaemonVersion() string {
+ if x != nil {
+ return x.DaemonVersion
+ }
+ return ""
+}
+
+type CreateRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ VaultPath string `protobuf:"bytes,1,opt,name=vault_path,json=vaultPath,proto3" json:"vault_path,omitempty"`
+ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateRequest) Reset() {
+ *x = CreateRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateRequest) ProtoMessage() {}
+
+func (x *CreateRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead.
+func (*CreateRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *CreateRequest) GetVaultPath() string {
+ if x != nil {
+ return x.VaultPath
+ }
+ return ""
+}
+
+func (x *CreateRequest) GetPassword() string {
+ if x != nil {
+ return x.Password
+ }
+ return ""
+}
+
+type UnsealRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ VaultPath string `protobuf:"bytes,1,opt,name=vault_path,json=vaultPath,proto3" json:"vault_path,omitempty"`
+ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UnsealRequest) Reset() {
+ *x = UnsealRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UnsealRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnsealRequest) ProtoMessage() {}
+
+func (x *UnsealRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[11]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UnsealRequest.ProtoReflect.Descriptor instead.
+func (*UnsealRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *UnsealRequest) GetVaultPath() string {
+ if x != nil {
+ return x.VaultPath
+ }
+ return ""
+}
+
+func (x *UnsealRequest) GetPassword() string {
+ if x != nil {
+ return x.Password
+ }
+ return ""
+}
+
+type ListFilesRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Prefix *string `protobuf:"bytes,1,opt,name=prefix,proto3,oneof" json:"prefix,omitempty"`
+ Contains *string `protobuf:"bytes,2,opt,name=contains,proto3,oneof" json:"contains,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListFilesRequest) Reset() {
+ *x = ListFilesRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListFilesRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListFilesRequest) ProtoMessage() {}
+
+func (x *ListFilesRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[12]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListFilesRequest.ProtoReflect.Descriptor instead.
+func (*ListFilesRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *ListFilesRequest) GetPrefix() string {
+ if x != nil && x.Prefix != nil {
+ return *x.Prefix
+ }
+ return ""
+}
+
+func (x *ListFilesRequest) GetContains() string {
+ if x != nil && x.Contains != nil {
+ return *x.Contains
+ }
+ return ""
+}
+
+type ListFilesResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Files []*ListedFile `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListFilesResponse) Reset() {
+ *x = ListFilesResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListFilesResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListFilesResponse) ProtoMessage() {}
+
+func (x *ListFilesResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[13]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListFilesResponse.ProtoReflect.Descriptor instead.
+func (*ListFilesResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *ListFilesResponse) GetFiles() []*ListedFile {
+ if x != nil {
+ return x.Files
+ }
+ return nil
+}
+
+type ListedFile struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ LogicalPath string `protobuf:"bytes,1,opt,name=logical_path,json=logicalPath,proto3" json:"logical_path,omitempty"`
+ Size uint64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListedFile) Reset() {
+ *x = ListedFile{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListedFile) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListedFile) ProtoMessage() {}
+
+func (x *ListedFile) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[14]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListedFile.ProtoReflect.Descriptor instead.
+func (*ListedFile) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *ListedFile) GetLogicalPath() string {
+ if x != nil {
+ return x.LogicalPath
+ }
+ return ""
+}
+
+func (x *ListedFile) GetSize() uint64 {
+ if x != nil {
+ return x.Size
+ }
+ return 0
+}
+
+type GetFileRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ LogicalPath string `protobuf:"bytes,1,opt,name=logical_path,json=logicalPath,proto3" json:"logical_path,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetFileRequest) Reset() {
+ *x = GetFileRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetFileRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetFileRequest) ProtoMessage() {}
+
+func (x *GetFileRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[15]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetFileRequest.ProtoReflect.Descriptor instead.
+func (*GetFileRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{15}
+}
+
+func (x *GetFileRequest) GetLogicalPath() string {
+ if x != nil {
+ return x.LogicalPath
+ }
+ return ""
+}
+
+type GetFileRangeRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ LogicalPath string `protobuf:"bytes,1,opt,name=logical_path,json=logicalPath,proto3" json:"logical_path,omitempty"`
+ Start uint64 `protobuf:"varint,2,opt,name=start,proto3" json:"start,omitempty"`
+ ExclusiveEnd uint64 `protobuf:"varint,3,opt,name=exclusive_end,json=exclusiveEnd,proto3" json:"exclusive_end,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetFileRangeRequest) Reset() {
+ *x = GetFileRangeRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[16]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetFileRangeRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetFileRangeRequest) ProtoMessage() {}
+
+func (x *GetFileRangeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[16]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetFileRangeRequest.ProtoReflect.Descriptor instead.
+func (*GetFileRangeRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{16}
+}
+
+func (x *GetFileRangeRequest) GetLogicalPath() string {
+ if x != nil {
+ return x.LogicalPath
+ }
+ return ""
+}
+
+func (x *GetFileRangeRequest) GetStart() uint64 {
+ if x != nil {
+ return x.Start
+ }
+ return 0
+}
+
+func (x *GetFileRangeRequest) GetExclusiveEnd() uint64 {
+ if x != nil {
+ return x.ExclusiveEnd
+ }
+ return 0
+}
+
+type GetFileResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetFileResponse) Reset() {
+ *x = GetFileResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[17]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetFileResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetFileResponse) ProtoMessage() {}
+
+func (x *GetFileResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[17]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetFileResponse.ProtoReflect.Descriptor instead.
+func (*GetFileResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{17}
+}
+
+func (x *GetFileResponse) GetData() []byte {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+type GetFileRangeResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetFileRangeResponse) Reset() {
+ *x = GetFileRangeResponse{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetFileRangeResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetFileRangeResponse) ProtoMessage() {}
+
+func (x *GetFileRangeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[18]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetFileRangeResponse.ProtoReflect.Descriptor instead.
+func (*GetFileRangeResponse) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{18}
+}
+
+func (x *GetFileRangeResponse) GetData() []byte {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+type AddFileRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // Types that are valid to be assigned to Payload:
+ //
+ // *AddFileRequest_Header
+ // *AddFileRequest_Chunk
+ Payload isAddFileRequest_Payload `protobuf_oneof:"payload"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AddFileRequest) Reset() {
+ *x = AddFileRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AddFileRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddFileRequest) ProtoMessage() {}
+
+func (x *AddFileRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[19]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddFileRequest.ProtoReflect.Descriptor instead.
+func (*AddFileRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *AddFileRequest) GetPayload() isAddFileRequest_Payload {
+ if x != nil {
+ return x.Payload
+ }
+ return nil
+}
+
+func (x *AddFileRequest) GetHeader() *AddFileHeader {
+ if x != nil {
+ if x, ok := x.Payload.(*AddFileRequest_Header); ok {
+ return x.Header
+ }
+ }
+ return nil
+}
+
+func (x *AddFileRequest) GetChunk() []byte {
+ if x != nil {
+ if x, ok := x.Payload.(*AddFileRequest_Chunk); ok {
+ return x.Chunk
+ }
+ }
+ return nil
+}
+
+type isAddFileRequest_Payload interface {
+ isAddFileRequest_Payload()
+}
+
+type AddFileRequest_Header struct {
+ Header *AddFileHeader `protobuf:"bytes,1,opt,name=header,proto3,oneof"`
+}
+
+type AddFileRequest_Chunk struct {
+ Chunk []byte `protobuf:"bytes,2,opt,name=chunk,proto3,oneof"`
+}
+
+func (*AddFileRequest_Header) isAddFileRequest_Payload() {}
+
+func (*AddFileRequest_Chunk) isAddFileRequest_Payload() {}
+
+type AddFileHeader struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ LogicalPath string `protobuf:"bytes,1,opt,name=logical_path,json=logicalPath,proto3" json:"logical_path,omitempty"`
+ SourceLen uint64 `protobuf:"varint,2,opt,name=source_len,json=sourceLen,proto3" json:"source_len,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AddFileHeader) Reset() {
+ *x = AddFileHeader{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[20]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AddFileHeader) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddFileHeader) ProtoMessage() {}
+
+func (x *AddFileHeader) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[20]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddFileHeader.ProtoReflect.Descriptor instead.
+func (*AddFileHeader) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{20}
+}
+
+func (x *AddFileHeader) GetLogicalPath() string {
+ if x != nil {
+ return x.LogicalPath
+ }
+ return ""
+}
+
+func (x *AddFileHeader) GetSourceLen() uint64 {
+ if x != nil {
+ return x.SourceLen
+ }
+ return 0
+}
+
+type DeleteFileRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ LogicalPath string `protobuf:"bytes,1,opt,name=logical_path,json=logicalPath,proto3" json:"logical_path,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeleteFileRequest) Reset() {
+ *x = DeleteFileRequest{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[21]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteFileRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteFileRequest) ProtoMessage() {}
+
+func (x *DeleteFileRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[21]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteFileRequest.ProtoReflect.Descriptor instead.
+func (*DeleteFileRequest) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{21}
+}
+
+func (x *DeleteFileRequest) GetLogicalPath() string {
+ if x != nil {
+ return x.LogicalPath
+ }
+ return ""
+}
+
+type ZwErrorDetail struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Code ZwErrorCode `protobuf:"varint,1,opt,name=code,proto3,enum=zw.daemon.v1.ZwErrorCode" json:"code,omitempty"`
+ Retryable bool `protobuf:"varint,2,opt,name=retryable,proto3" json:"retryable,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ZwErrorDetail) Reset() {
+ *x = ZwErrorDetail{}
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[22]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ZwErrorDetail) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ZwErrorDetail) ProtoMessage() {}
+
+func (x *ZwErrorDetail) ProtoReflect() protoreflect.Message {
+ mi := &file_zw_daemon_v1_daemon_proto_msgTypes[22]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ZwErrorDetail.ProtoReflect.Descriptor instead.
+func (*ZwErrorDetail) Descriptor() ([]byte, []int) {
+ return file_zw_daemon_v1_daemon_proto_rawDescGZIP(), []int{22}
+}
+
+func (x *ZwErrorDetail) GetCode() ZwErrorCode {
+ if x != nil {
+ return x.Code
+ }
+ return ZwErrorCode_ZW_ERROR_CODE_UNSPECIFIED
+}
+
+func (x *ZwErrorDetail) GetRetryable() bool {
+ if x != nil {
+ return x.Retryable
+ }
+ return false
+}
+
+var File_zw_daemon_v1_daemon_proto protoreflect.FileDescriptor
+
+const file_zw_daemon_v1_daemon_proto_rawDesc = "" +
+ "\n" +
+ "\x19zw/daemon/v1/daemon.proto\x12\fzw.daemon.v1\"\x16\n" +
+ "\x14GetServerInfoRequest\"\r\n" +
+ "\vSealRequest\"\x0e\n" +
+ "\fSealResponse\"\x10\n" +
+ "\x0eCreateResponse\"\x10\n" +
+ "\x0eUnsealResponse\"\x11\n" +
+ "\x0fCompactResponse\"\x10\n" +
+ "\x0eCompactRequest\"\x11\n" +
+ "\x0fAddFileResponse\"\x14\n" +
+ "\x12DeleteFileResponse\"x\n" +
+ "\x15GetServerInfoResponse\x12\x1b\n" +
+ "\tapi_major\x18\x01 \x01(\rR\bapiMajor\x12\x1b\n" +
+ "\tapi_minor\x18\x02 \x01(\rR\bapiMinor\x12%\n" +
+ "\x0edaemon_version\x18\x03 \x01(\tR\rdaemonVersion\"J\n" +
+ "\rCreateRequest\x12\x1d\n" +
+ "\n" +
+ "vault_path\x18\x01 \x01(\tR\tvaultPath\x12\x1a\n" +
+ "\bpassword\x18\x02 \x01(\tR\bpassword\"J\n" +
+ "\rUnsealRequest\x12\x1d\n" +
+ "\n" +
+ "vault_path\x18\x01 \x01(\tR\tvaultPath\x12\x1a\n" +
+ "\bpassword\x18\x02 \x01(\tR\bpassword\"h\n" +
+ "\x10ListFilesRequest\x12\x1b\n" +
+ "\x06prefix\x18\x01 \x01(\tH\x00R\x06prefix\x88\x01\x01\x12\x1f\n" +
+ "\bcontains\x18\x02 \x01(\tH\x01R\bcontains\x88\x01\x01B\t\n" +
+ "\a_prefixB\v\n" +
+ "\t_contains\"C\n" +
+ "\x11ListFilesResponse\x12.\n" +
+ "\x05files\x18\x01 \x03(\v2\x18.zw.daemon.v1.ListedFileR\x05files\"C\n" +
+ "\n" +
+ "ListedFile\x12!\n" +
+ "\flogical_path\x18\x01 \x01(\tR\vlogicalPath\x12\x12\n" +
+ "\x04size\x18\x02 \x01(\x04R\x04size\"3\n" +
+ "\x0eGetFileRequest\x12!\n" +
+ "\flogical_path\x18\x01 \x01(\tR\vlogicalPath\"s\n" +
+ "\x13GetFileRangeRequest\x12!\n" +
+ "\flogical_path\x18\x01 \x01(\tR\vlogicalPath\x12\x14\n" +
+ "\x05start\x18\x02 \x01(\x04R\x05start\x12#\n" +
+ "\rexclusive_end\x18\x03 \x01(\x04R\fexclusiveEnd\"%\n" +
+ "\x0fGetFileResponse\x12\x12\n" +
+ "\x04data\x18\x01 \x01(\fR\x04data\"*\n" +
+ "\x14GetFileRangeResponse\x12\x12\n" +
+ "\x04data\x18\x01 \x01(\fR\x04data\"j\n" +
+ "\x0eAddFileRequest\x125\n" +
+ "\x06header\x18\x01 \x01(\v2\x1b.zw.daemon.v1.AddFileHeaderH\x00R\x06header\x12\x16\n" +
+ "\x05chunk\x18\x02 \x01(\fH\x00R\x05chunkB\t\n" +
+ "\apayload\"Q\n" +
+ "\rAddFileHeader\x12!\n" +
+ "\flogical_path\x18\x01 \x01(\tR\vlogicalPath\x12\x1d\n" +
+ "\n" +
+ "source_len\x18\x02 \x01(\x04R\tsourceLen\"6\n" +
+ "\x11DeleteFileRequest\x12!\n" +
+ "\flogical_path\x18\x01 \x01(\tR\vlogicalPath\"\\\n" +
+ "\rZwErrorDetail\x12-\n" +
+ "\x04code\x18\x01 \x01(\x0e2\x19.zw.daemon.v1.ZwErrorCodeR\x04code\x12\x1c\n" +
+ "\tretryable\x18\x02 \x01(\bR\tretryable*\x9b\x04\n" +
+ "\vZwErrorCode\x12\x1d\n" +
+ "\x19ZW_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x1e\n" +
+ "\x1aZW_ERROR_CODE_VAULT_SEALED\x10\x01\x12(\n" +
+ "$ZW_ERROR_CODE_VAULT_ALREADY_UNSEALED\x10\x02\x12!\n" +
+ "\x1dZW_ERROR_CODE_ENTRY_NOT_FOUND\x10\x03\x12&\n" +
+ "\"ZW_ERROR_CODE_ENTRY_ALREADY_EXISTS\x10\x04\x12.\n" +
+ "*ZW_ERROR_CODE_AUTHENTICATION_OR_CORRUPTION\x10\x05\x12&\n" +
+ "\"ZW_ERROR_CODE_INVALID_VAULT_FORMAT\x10\x06\x12+\n" +
+ "'ZW_ERROR_CODE_UNSUPPORTED_VAULT_VERSION\x10\a\x12.\n" +
+ "*ZW_ERROR_CODE_UNSUPPORTED_PROTOCOL_VERSION\x10\b\x12#\n" +
+ "\x1fZW_ERROR_CODE_PERMISSION_DENIED\x10\t\x12\x1e\n" +
+ "\x1aZW_ERROR_CODE_STORAGE_FULL\x10\n" +
+ "\x12\x1f\n" +
+ "\x1bZW_ERROR_CODE_COMMIT_FAILED\x10\v\x12!\n" +
+ "\x1dZW_ERROR_CODE_INVALID_REQUEST\x10\f\x12\x1a\n" +
+ "\x16ZW_ERROR_CODE_INTERNAL\x10\r2\x88\x06\n" +
+ "\x0fZwDaemonService\x12X\n" +
+ "\rGetServerInfo\x12\".zw.daemon.v1.GetServerInfoRequest\x1a#.zw.daemon.v1.GetServerInfoResponse\x12C\n" +
+ "\x06Create\x12\x1b.zw.daemon.v1.CreateRequest\x1a\x1c.zw.daemon.v1.CreateResponse\x12C\n" +
+ "\x06Unseal\x12\x1b.zw.daemon.v1.UnsealRequest\x1a\x1c.zw.daemon.v1.UnsealResponse\x12=\n" +
+ "\x04Seal\x12\x19.zw.daemon.v1.SealRequest\x1a\x1a.zw.daemon.v1.SealResponse\x12L\n" +
+ "\tListFiles\x12\x1e.zw.daemon.v1.ListFilesRequest\x1a\x1f.zw.daemon.v1.ListFilesResponse\x12H\n" +
+ "\aGetFile\x12\x1c.zw.daemon.v1.GetFileRequest\x1a\x1d.zw.daemon.v1.GetFileResponse0\x01\x12W\n" +
+ "\fGetFileRange\x12!.zw.daemon.v1.GetFileRangeRequest\x1a\".zw.daemon.v1.GetFileRangeResponse0\x01\x12H\n" +
+ "\aAddFile\x12\x1c.zw.daemon.v1.AddFileRequest\x1a\x1d.zw.daemon.v1.AddFileResponse(\x01\x12O\n" +
+ "\n" +
+ "DeleteFile\x12\x1f.zw.daemon.v1.DeleteFileRequest\x1a .zw.daemon.v1.DeleteFileResponse\x12F\n" +
+ "\aCompact\x12\x1c.zw.daemon.v1.CompactRequest\x1a\x1d.zw.daemon.v1.CompactResponseB5Z3git.pablu.de/pablu/zw-go/gen/zwdaemon/v1;zwdaemonv1b\x06proto3"
+
+var (
+ file_zw_daemon_v1_daemon_proto_rawDescOnce sync.Once
+ file_zw_daemon_v1_daemon_proto_rawDescData []byte
+)
+
+func file_zw_daemon_v1_daemon_proto_rawDescGZIP() []byte {
+ file_zw_daemon_v1_daemon_proto_rawDescOnce.Do(func() {
+ file_zw_daemon_v1_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_zw_daemon_v1_daemon_proto_rawDesc), len(file_zw_daemon_v1_daemon_proto_rawDesc)))
+ })
+ return file_zw_daemon_v1_daemon_proto_rawDescData
+}
+
+var file_zw_daemon_v1_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_zw_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
+var file_zw_daemon_v1_daemon_proto_goTypes = []any{
+ (ZwErrorCode)(0), // 0: zw.daemon.v1.ZwErrorCode
+ (*GetServerInfoRequest)(nil), // 1: zw.daemon.v1.GetServerInfoRequest
+ (*SealRequest)(nil), // 2: zw.daemon.v1.SealRequest
+ (*SealResponse)(nil), // 3: zw.daemon.v1.SealResponse
+ (*CreateResponse)(nil), // 4: zw.daemon.v1.CreateResponse
+ (*UnsealResponse)(nil), // 5: zw.daemon.v1.UnsealResponse
+ (*CompactResponse)(nil), // 6: zw.daemon.v1.CompactResponse
+ (*CompactRequest)(nil), // 7: zw.daemon.v1.CompactRequest
+ (*AddFileResponse)(nil), // 8: zw.daemon.v1.AddFileResponse
+ (*DeleteFileResponse)(nil), // 9: zw.daemon.v1.DeleteFileResponse
+ (*GetServerInfoResponse)(nil), // 10: zw.daemon.v1.GetServerInfoResponse
+ (*CreateRequest)(nil), // 11: zw.daemon.v1.CreateRequest
+ (*UnsealRequest)(nil), // 12: zw.daemon.v1.UnsealRequest
+ (*ListFilesRequest)(nil), // 13: zw.daemon.v1.ListFilesRequest
+ (*ListFilesResponse)(nil), // 14: zw.daemon.v1.ListFilesResponse
+ (*ListedFile)(nil), // 15: zw.daemon.v1.ListedFile
+ (*GetFileRequest)(nil), // 16: zw.daemon.v1.GetFileRequest
+ (*GetFileRangeRequest)(nil), // 17: zw.daemon.v1.GetFileRangeRequest
+ (*GetFileResponse)(nil), // 18: zw.daemon.v1.GetFileResponse
+ (*GetFileRangeResponse)(nil), // 19: zw.daemon.v1.GetFileRangeResponse
+ (*AddFileRequest)(nil), // 20: zw.daemon.v1.AddFileRequest
+ (*AddFileHeader)(nil), // 21: zw.daemon.v1.AddFileHeader
+ (*DeleteFileRequest)(nil), // 22: zw.daemon.v1.DeleteFileRequest
+ (*ZwErrorDetail)(nil), // 23: zw.daemon.v1.ZwErrorDetail
+}
+var file_zw_daemon_v1_daemon_proto_depIdxs = []int32{
+ 15, // 0: zw.daemon.v1.ListFilesResponse.files:type_name -> zw.daemon.v1.ListedFile
+ 21, // 1: zw.daemon.v1.AddFileRequest.header:type_name -> zw.daemon.v1.AddFileHeader
+ 0, // 2: zw.daemon.v1.ZwErrorDetail.code:type_name -> zw.daemon.v1.ZwErrorCode
+ 1, // 3: zw.daemon.v1.ZwDaemonService.GetServerInfo:input_type -> zw.daemon.v1.GetServerInfoRequest
+ 11, // 4: zw.daemon.v1.ZwDaemonService.Create:input_type -> zw.daemon.v1.CreateRequest
+ 12, // 5: zw.daemon.v1.ZwDaemonService.Unseal:input_type -> zw.daemon.v1.UnsealRequest
+ 2, // 6: zw.daemon.v1.ZwDaemonService.Seal:input_type -> zw.daemon.v1.SealRequest
+ 13, // 7: zw.daemon.v1.ZwDaemonService.ListFiles:input_type -> zw.daemon.v1.ListFilesRequest
+ 16, // 8: zw.daemon.v1.ZwDaemonService.GetFile:input_type -> zw.daemon.v1.GetFileRequest
+ 17, // 9: zw.daemon.v1.ZwDaemonService.GetFileRange:input_type -> zw.daemon.v1.GetFileRangeRequest
+ 20, // 10: zw.daemon.v1.ZwDaemonService.AddFile:input_type -> zw.daemon.v1.AddFileRequest
+ 22, // 11: zw.daemon.v1.ZwDaemonService.DeleteFile:input_type -> zw.daemon.v1.DeleteFileRequest
+ 7, // 12: zw.daemon.v1.ZwDaemonService.Compact:input_type -> zw.daemon.v1.CompactRequest
+ 10, // 13: zw.daemon.v1.ZwDaemonService.GetServerInfo:output_type -> zw.daemon.v1.GetServerInfoResponse
+ 4, // 14: zw.daemon.v1.ZwDaemonService.Create:output_type -> zw.daemon.v1.CreateResponse
+ 5, // 15: zw.daemon.v1.ZwDaemonService.Unseal:output_type -> zw.daemon.v1.UnsealResponse
+ 3, // 16: zw.daemon.v1.ZwDaemonService.Seal:output_type -> zw.daemon.v1.SealResponse
+ 14, // 17: zw.daemon.v1.ZwDaemonService.ListFiles:output_type -> zw.daemon.v1.ListFilesResponse
+ 18, // 18: zw.daemon.v1.ZwDaemonService.GetFile:output_type -> zw.daemon.v1.GetFileResponse
+ 19, // 19: zw.daemon.v1.ZwDaemonService.GetFileRange:output_type -> zw.daemon.v1.GetFileRangeResponse
+ 8, // 20: zw.daemon.v1.ZwDaemonService.AddFile:output_type -> zw.daemon.v1.AddFileResponse
+ 9, // 21: zw.daemon.v1.ZwDaemonService.DeleteFile:output_type -> zw.daemon.v1.DeleteFileResponse
+ 6, // 22: zw.daemon.v1.ZwDaemonService.Compact:output_type -> zw.daemon.v1.CompactResponse
+ 13, // [13:23] is the sub-list for method output_type
+ 3, // [3:13] is the sub-list for method input_type
+ 3, // [3:3] is the sub-list for extension type_name
+ 3, // [3:3] is the sub-list for extension extendee
+ 0, // [0:3] is the sub-list for field type_name
+}
+
+func init() { file_zw_daemon_v1_daemon_proto_init() }
+func file_zw_daemon_v1_daemon_proto_init() {
+ if File_zw_daemon_v1_daemon_proto != nil {
+ return
+ }
+ file_zw_daemon_v1_daemon_proto_msgTypes[12].OneofWrappers = []any{}
+ file_zw_daemon_v1_daemon_proto_msgTypes[19].OneofWrappers = []any{
+ (*AddFileRequest_Header)(nil),
+ (*AddFileRequest_Chunk)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_zw_daemon_v1_daemon_proto_rawDesc), len(file_zw_daemon_v1_daemon_proto_rawDesc)),
+ NumEnums: 1,
+ NumMessages: 23,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_zw_daemon_v1_daemon_proto_goTypes,
+ DependencyIndexes: file_zw_daemon_v1_daemon_proto_depIdxs,
+ EnumInfos: file_zw_daemon_v1_daemon_proto_enumTypes,
+ MessageInfos: file_zw_daemon_v1_daemon_proto_msgTypes,
+ }.Build()
+ File_zw_daemon_v1_daemon_proto = out.File
+ file_zw_daemon_v1_daemon_proto_goTypes = nil
+ file_zw_daemon_v1_daemon_proto_depIdxs = nil
+}
diff --git a/gen/zwdaemon/v1/daemon_grpc.pb.go b/gen/zwdaemon/v1/daemon_grpc.pb.go
new file mode 100644
index 0000000..73b99bf
--- /dev/null
+++ b/gen/zwdaemon/v1/daemon_grpc.pb.go
@@ -0,0 +1,463 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.6.2
+// - protoc (unknown)
+// source: zw/daemon/v1/daemon.proto
+
+package zwdaemonv1
+
+import (
+ context "context"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.64.0 or later.
+const _ = grpc.SupportPackageIsVersion9
+
+const (
+ ZwDaemonService_GetServerInfo_FullMethodName = "/zw.daemon.v1.ZwDaemonService/GetServerInfo"
+ ZwDaemonService_Create_FullMethodName = "/zw.daemon.v1.ZwDaemonService/Create"
+ ZwDaemonService_Unseal_FullMethodName = "/zw.daemon.v1.ZwDaemonService/Unseal"
+ ZwDaemonService_Seal_FullMethodName = "/zw.daemon.v1.ZwDaemonService/Seal"
+ ZwDaemonService_ListFiles_FullMethodName = "/zw.daemon.v1.ZwDaemonService/ListFiles"
+ ZwDaemonService_GetFile_FullMethodName = "/zw.daemon.v1.ZwDaemonService/GetFile"
+ ZwDaemonService_GetFileRange_FullMethodName = "/zw.daemon.v1.ZwDaemonService/GetFileRange"
+ ZwDaemonService_AddFile_FullMethodName = "/zw.daemon.v1.ZwDaemonService/AddFile"
+ ZwDaemonService_DeleteFile_FullMethodName = "/zw.daemon.v1.ZwDaemonService/DeleteFile"
+ ZwDaemonService_Compact_FullMethodName = "/zw.daemon.v1.ZwDaemonService/Compact"
+)
+
+// ZwDaemonServiceClient is the client API for ZwDaemonService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type ZwDaemonServiceClient interface {
+ GetServerInfo(ctx context.Context, in *GetServerInfoRequest, opts ...grpc.CallOption) (*GetServerInfoResponse, error)
+ Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error)
+ Unseal(ctx context.Context, in *UnsealRequest, opts ...grpc.CallOption) (*UnsealResponse, error)
+ Seal(ctx context.Context, in *SealRequest, opts ...grpc.CallOption) (*SealResponse, error)
+ ListFiles(ctx context.Context, in *ListFilesRequest, opts ...grpc.CallOption) (*ListFilesResponse, error)
+ GetFile(ctx context.Context, in *GetFileRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetFileResponse], error)
+ GetFileRange(ctx context.Context, in *GetFileRangeRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetFileRangeResponse], error)
+ AddFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[AddFileRequest, AddFileResponse], error)
+ DeleteFile(ctx context.Context, in *DeleteFileRequest, opts ...grpc.CallOption) (*DeleteFileResponse, error)
+ Compact(ctx context.Context, in *CompactRequest, opts ...grpc.CallOption) (*CompactResponse, error)
+}
+
+type zwDaemonServiceClient struct {
+ cc grpc.ClientConnInterface
+}
+
+func NewZwDaemonServiceClient(cc grpc.ClientConnInterface) ZwDaemonServiceClient {
+ return &zwDaemonServiceClient{cc}
+}
+
+func (c *zwDaemonServiceClient) GetServerInfo(ctx context.Context, in *GetServerInfoRequest, opts ...grpc.CallOption) (*GetServerInfoResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(GetServerInfoResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_GetServerInfo_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *zwDaemonServiceClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(CreateResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_Create_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *zwDaemonServiceClient) Unseal(ctx context.Context, in *UnsealRequest, opts ...grpc.CallOption) (*UnsealResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(UnsealResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_Unseal_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *zwDaemonServiceClient) Seal(ctx context.Context, in *SealRequest, opts ...grpc.CallOption) (*SealResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(SealResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_Seal_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *zwDaemonServiceClient) ListFiles(ctx context.Context, in *ListFilesRequest, opts ...grpc.CallOption) (*ListFilesResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(ListFilesResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_ListFiles_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *zwDaemonServiceClient) GetFile(ctx context.Context, in *GetFileRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetFileResponse], error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ stream, err := c.cc.NewStream(ctx, &ZwDaemonService_ServiceDesc.Streams[0], ZwDaemonService_GetFile_FullMethodName, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &grpc.GenericClientStream[GetFileRequest, GetFileResponse]{ClientStream: stream}
+ if err := x.ClientStream.SendMsg(in); err != nil {
+ return nil, err
+ }
+ if err := x.ClientStream.CloseSend(); err != nil {
+ return nil, err
+ }
+ return x, nil
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type ZwDaemonService_GetFileClient = grpc.ServerStreamingClient[GetFileResponse]
+
+func (c *zwDaemonServiceClient) GetFileRange(ctx context.Context, in *GetFileRangeRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetFileRangeResponse], error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ stream, err := c.cc.NewStream(ctx, &ZwDaemonService_ServiceDesc.Streams[1], ZwDaemonService_GetFileRange_FullMethodName, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &grpc.GenericClientStream[GetFileRangeRequest, GetFileRangeResponse]{ClientStream: stream}
+ if err := x.ClientStream.SendMsg(in); err != nil {
+ return nil, err
+ }
+ if err := x.ClientStream.CloseSend(); err != nil {
+ return nil, err
+ }
+ return x, nil
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type ZwDaemonService_GetFileRangeClient = grpc.ServerStreamingClient[GetFileRangeResponse]
+
+func (c *zwDaemonServiceClient) AddFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[AddFileRequest, AddFileResponse], error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ stream, err := c.cc.NewStream(ctx, &ZwDaemonService_ServiceDesc.Streams[2], ZwDaemonService_AddFile_FullMethodName, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &grpc.GenericClientStream[AddFileRequest, AddFileResponse]{ClientStream: stream}
+ return x, nil
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type ZwDaemonService_AddFileClient = grpc.ClientStreamingClient[AddFileRequest, AddFileResponse]
+
+func (c *zwDaemonServiceClient) DeleteFile(ctx context.Context, in *DeleteFileRequest, opts ...grpc.CallOption) (*DeleteFileResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(DeleteFileResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_DeleteFile_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *zwDaemonServiceClient) Compact(ctx context.Context, in *CompactRequest, opts ...grpc.CallOption) (*CompactResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(CompactResponse)
+ err := c.cc.Invoke(ctx, ZwDaemonService_Compact_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// ZwDaemonServiceServer is the server API for ZwDaemonService service.
+// All implementations must embed UnimplementedZwDaemonServiceServer
+// for forward compatibility.
+type ZwDaemonServiceServer interface {
+ GetServerInfo(context.Context, *GetServerInfoRequest) (*GetServerInfoResponse, error)
+ Create(context.Context, *CreateRequest) (*CreateResponse, error)
+ Unseal(context.Context, *UnsealRequest) (*UnsealResponse, error)
+ Seal(context.Context, *SealRequest) (*SealResponse, error)
+ ListFiles(context.Context, *ListFilesRequest) (*ListFilesResponse, error)
+ GetFile(*GetFileRequest, grpc.ServerStreamingServer[GetFileResponse]) error
+ GetFileRange(*GetFileRangeRequest, grpc.ServerStreamingServer[GetFileRangeResponse]) error
+ AddFile(grpc.ClientStreamingServer[AddFileRequest, AddFileResponse]) error
+ DeleteFile(context.Context, *DeleteFileRequest) (*DeleteFileResponse, error)
+ Compact(context.Context, *CompactRequest) (*CompactResponse, error)
+ mustEmbedUnimplementedZwDaemonServiceServer()
+}
+
+// UnimplementedZwDaemonServiceServer must be embedded to have
+// forward compatible implementations.
+//
+// NOTE: this should be embedded by value instead of pointer to avoid a nil
+// pointer dereference when methods are called.
+type UnimplementedZwDaemonServiceServer struct{}
+
+func (UnimplementedZwDaemonServiceServer) GetServerInfo(context.Context, *GetServerInfoRequest) (*GetServerInfoResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method GetServerInfo not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) Create(context.Context, *CreateRequest) (*CreateResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method Create not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) Unseal(context.Context, *UnsealRequest) (*UnsealResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method Unseal not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) Seal(context.Context, *SealRequest) (*SealResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method Seal not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) ListFiles(context.Context, *ListFilesRequest) (*ListFilesResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method ListFiles not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) GetFile(*GetFileRequest, grpc.ServerStreamingServer[GetFileResponse]) error {
+ return status.Error(codes.Unimplemented, "method GetFile not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) GetFileRange(*GetFileRangeRequest, grpc.ServerStreamingServer[GetFileRangeResponse]) error {
+ return status.Error(codes.Unimplemented, "method GetFileRange not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) AddFile(grpc.ClientStreamingServer[AddFileRequest, AddFileResponse]) error {
+ return status.Error(codes.Unimplemented, "method AddFile not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) DeleteFile(context.Context, *DeleteFileRequest) (*DeleteFileResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method DeleteFile not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) Compact(context.Context, *CompactRequest) (*CompactResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method Compact not implemented")
+}
+func (UnimplementedZwDaemonServiceServer) mustEmbedUnimplementedZwDaemonServiceServer() {}
+func (UnimplementedZwDaemonServiceServer) testEmbeddedByValue() {}
+
+// UnsafeZwDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to ZwDaemonServiceServer will
+// result in compilation errors.
+type UnsafeZwDaemonServiceServer interface {
+ mustEmbedUnimplementedZwDaemonServiceServer()
+}
+
+func RegisterZwDaemonServiceServer(s grpc.ServiceRegistrar, srv ZwDaemonServiceServer) {
+ // If the following call panics, it indicates UnimplementedZwDaemonServiceServer was
+ // embedded by pointer and is nil. This will cause panics if an
+ // unimplemented method is ever invoked, so we test this at initialization
+ // time to prevent it from happening at runtime later due to I/O.
+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
+ t.testEmbeddedByValue()
+ }
+ s.RegisterService(&ZwDaemonService_ServiceDesc, srv)
+}
+
+func _ZwDaemonService_GetServerInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(GetServerInfoRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).GetServerInfo(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_GetServerInfo_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).GetServerInfo(ctx, req.(*GetServerInfoRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ZwDaemonService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(CreateRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).Create(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_Create_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).Create(ctx, req.(*CreateRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ZwDaemonService_Unseal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(UnsealRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).Unseal(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_Unseal_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).Unseal(ctx, req.(*UnsealRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ZwDaemonService_Seal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(SealRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).Seal(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_Seal_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).Seal(ctx, req.(*SealRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ZwDaemonService_ListFiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ListFilesRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).ListFiles(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_ListFiles_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).ListFiles(ctx, req.(*ListFilesRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ZwDaemonService_GetFile_Handler(srv interface{}, stream grpc.ServerStream) error {
+ m := new(GetFileRequest)
+ if err := stream.RecvMsg(m); err != nil {
+ return err
+ }
+ return srv.(ZwDaemonServiceServer).GetFile(m, &grpc.GenericServerStream[GetFileRequest, GetFileResponse]{ServerStream: stream})
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type ZwDaemonService_GetFileServer = grpc.ServerStreamingServer[GetFileResponse]
+
+func _ZwDaemonService_GetFileRange_Handler(srv interface{}, stream grpc.ServerStream) error {
+ m := new(GetFileRangeRequest)
+ if err := stream.RecvMsg(m); err != nil {
+ return err
+ }
+ return srv.(ZwDaemonServiceServer).GetFileRange(m, &grpc.GenericServerStream[GetFileRangeRequest, GetFileRangeResponse]{ServerStream: stream})
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type ZwDaemonService_GetFileRangeServer = grpc.ServerStreamingServer[GetFileRangeResponse]
+
+func _ZwDaemonService_AddFile_Handler(srv interface{}, stream grpc.ServerStream) error {
+ return srv.(ZwDaemonServiceServer).AddFile(&grpc.GenericServerStream[AddFileRequest, AddFileResponse]{ServerStream: stream})
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type ZwDaemonService_AddFileServer = grpc.ClientStreamingServer[AddFileRequest, AddFileResponse]
+
+func _ZwDaemonService_DeleteFile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(DeleteFileRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).DeleteFile(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_DeleteFile_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).DeleteFile(ctx, req.(*DeleteFileRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ZwDaemonService_Compact_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(CompactRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ZwDaemonServiceServer).Compact(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ZwDaemonService_Compact_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ZwDaemonServiceServer).Compact(ctx, req.(*CompactRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+// ZwDaemonService_ServiceDesc is the grpc.ServiceDesc for ZwDaemonService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var ZwDaemonService_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "zw.daemon.v1.ZwDaemonService",
+ HandlerType: (*ZwDaemonServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "GetServerInfo",
+ Handler: _ZwDaemonService_GetServerInfo_Handler,
+ },
+ {
+ MethodName: "Create",
+ Handler: _ZwDaemonService_Create_Handler,
+ },
+ {
+ MethodName: "Unseal",
+ Handler: _ZwDaemonService_Unseal_Handler,
+ },
+ {
+ MethodName: "Seal",
+ Handler: _ZwDaemonService_Seal_Handler,
+ },
+ {
+ MethodName: "ListFiles",
+ Handler: _ZwDaemonService_ListFiles_Handler,
+ },
+ {
+ MethodName: "DeleteFile",
+ Handler: _ZwDaemonService_DeleteFile_Handler,
+ },
+ {
+ MethodName: "Compact",
+ Handler: _ZwDaemonService_Compact_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{
+ {
+ StreamName: "GetFile",
+ Handler: _ZwDaemonService_GetFile_Handler,
+ ServerStreams: true,
+ },
+ {
+ StreamName: "GetFileRange",
+ Handler: _ZwDaemonService_GetFileRange_Handler,
+ ServerStreams: true,
+ },
+ {
+ StreamName: "AddFile",
+ Handler: _ZwDaemonService_AddFile_Handler,
+ ClientStreams: true,
+ },
+ },
+ Metadata: "zw/daemon/v1/daemon.proto",
+}
diff --git a/go.mod b/go.mod
index a6af57a..75f976d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,15 @@
module git.pablu.de/pablu/zw-go
-go 1.25.0
+go 1.26.0
+
+require (
+ google.golang.org/grpc v1.83.0
+ google.golang.org/protobuf v1.36.11
+)
+
+require (
+ golang.org/x/net v0.55.0 // indirect
+ golang.org/x/sys v0.45.0 // indirect
+ golang.org/x/text v0.37.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..2010d3d
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,38 @@
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
+go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
+go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
+go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
+go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
+go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
+go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
+go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
+go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
+go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
+golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
+golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
+golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
+gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
+gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
+google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
diff --git a/mise.toml b/mise.toml
index 93c2963..7fc8548 100644
--- a/mise.toml
+++ b/mise.toml
@@ -1,2 +1,8 @@
[tools]
go = "1.26.0"
+"go:golang.org/x/tools/gopls" = "latest"
+buf = "1.72.0"
+
+[tasks.generate]
+description = "Generate Go protobuf and gRPC bindings"
+run = "buf generate"
diff --git a/types.go b/types.go
new file mode 100644
index 0000000..af04b54
--- /dev/null
+++ b/types.go
@@ -0,0 +1,17 @@
+package zw
+
+type ServerInfo struct {
+ APIMajor uint32
+ APIMinor uint32
+ DaemonVersion string
+}
+
+type ListFilter struct {
+ Prefix string
+ Contains string
+}
+
+type ListedFile struct {
+ LogicalPath string
+ Size uint64
+}