Files
zw-go/client.go

398 lines
7.8 KiB
Go

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) MoveFile(
ctx context.Context,
sourcePath string,
destinationPath string,
) error {
_, err := c.rpc.MoveFile(ctx, &zwdaemonv1.MoveFileRequest{
SourcePath: sourcePath,
DestinationPath: destinationPath,
})
if err != nil {
return convertRPCError(err)
}
return nil
}
func (c *Client) CopyFile(
ctx context.Context,
sourcePath string,
destinationPath string,
) error {
_, err := c.rpc.CopyFile(ctx, &zwdaemonv1.CopyFileRequest{
SourcePath: sourcePath,
DestinationPath: destinationPath,
})
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}
}
}
}
func (c *Client) ReplaceFile(
ctx context.Context,
logicalPath string,
source io.Reader,
sourceLen uint64,
) error {
uploadCtx, cancel := context.WithCancel(ctx)
defer cancel()
stream, err := c.rpc.ReplaceFile(uploadCtx)
if err != nil {
return convertRPCError(err)
}
send := func(request *zwdaemonv1.ReplaceFileRequest) 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.ReplaceFileRequest{
Payload: &zwdaemonv1.ReplaceFileRequest_Header{
Header: &zwdaemonv1.ReplaceFileHeader{
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.ReplaceFileRequest{
Payload: &zwdaemonv1.ReplaceFileRequest_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}
}
}
}