138 lines
4.2 KiB
Go
138 lines
4.2 KiB
Go
package remote
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
stdgrpc "google.golang.org/grpc"
|
||
"google.golang.org/grpc/credentials/insecure"
|
||
|
||
agateGrpc "gitea.unprism.ru/KRBL/Agate/grpc"
|
||
"gitea.unprism.ru/KRBL/Agate/interfaces"
|
||
"gitea.unprism.ru/KRBL/Agate/store"
|
||
)
|
||
|
||
// Client представляет клиент для подключения к удаленному серверу снапшотов.
|
||
type Client struct {
|
||
conn *stdgrpc.ClientConn
|
||
client agateGrpc.SnapshotServiceClient
|
||
}
|
||
|
||
// Убедимся, что Client реализует интерфейс interfaces.SnapshotClient
|
||
var _ interfaces.SnapshotClient = (*Client)(nil)
|
||
|
||
// NewClient создает нового клиента, подключенного к указанному адресу.
|
||
func NewClient(address string) (*Client, error) {
|
||
conn, err := stdgrpc.Dial(address, stdgrpc.WithTransportCredentials(insecure.NewCredentials()))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to connect to server at %s: %w", address, err)
|
||
}
|
||
client := agateGrpc.NewSnapshotServiceClient(conn)
|
||
return &Client{conn: conn, client: client}, nil
|
||
}
|
||
|
||
// Close закрывает соединение с сервером.
|
||
func (c *Client) Close() error {
|
||
if c.conn != nil {
|
||
return c.conn.Close()
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ListSnapshots получает список снапшотов с удаленного сервера.
|
||
func (c *Client) ListSnapshots(ctx context.Context) ([]store.SnapshotInfo, error) {
|
||
response, err := c.client.ListSnapshots(ctx, &agateGrpc.ListSnapshotsRequest{})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to list snapshots: %w", err)
|
||
}
|
||
|
||
snapshots := make([]store.SnapshotInfo, 0, len(response.Snapshots))
|
||
for _, snapshot := range response.Snapshots {
|
||
snapshots = append(snapshots, store.SnapshotInfo{
|
||
ID: snapshot.Id,
|
||
Name: snapshot.Name,
|
||
ParentID: snapshot.ParentId,
|
||
CreationTime: snapshot.CreationTime.AsTime(),
|
||
})
|
||
}
|
||
return snapshots, nil
|
||
}
|
||
|
||
// FetchSnapshotDetails получает детальную информацию о конкретном снапшоте.
|
||
func (c *Client) FetchSnapshotDetails(ctx context.Context, snapshotID string) (*store.Snapshot, error) {
|
||
response, err := c.client.GetSnapshotDetails(ctx, &agateGrpc.GetSnapshotDetailsRequest{
|
||
SnapshotId: snapshotID,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to get snapshot details: %w", err)
|
||
}
|
||
|
||
snapshot := &store.Snapshot{
|
||
ID: response.Info.Id,
|
||
Name: response.Info.Name,
|
||
ParentID: response.Info.ParentId,
|
||
CreationTime: response.Info.CreationTime.AsTime(),
|
||
Files: make([]store.FileInfo, 0, len(response.Files)),
|
||
}
|
||
|
||
for _, file := range response.Files {
|
||
snapshot.Files = append(snapshot.Files, store.FileInfo{
|
||
Path: file.Path,
|
||
Size: file.SizeBytes,
|
||
IsDir: file.IsDir,
|
||
SHA256: file.Sha256Hash,
|
||
})
|
||
}
|
||
return snapshot, nil
|
||
}
|
||
|
||
// DownloadSnapshotDiff скачивает архив с разницей между снапшотами.
|
||
func (c *Client) DownloadSnapshotDiff(ctx context.Context, snapshotID, localParentID, targetPath string) error {
|
||
var offset int64
|
||
fileInfo, err := os.Stat(targetPath)
|
||
if err == nil {
|
||
offset = fileInfo.Size()
|
||
} else if !os.IsNotExist(err) {
|
||
return fmt.Errorf("failed to stat temporary file: %w", err)
|
||
}
|
||
|
||
req := &agateGrpc.DownloadSnapshotDiffRequest{
|
||
SnapshotId: snapshotID,
|
||
LocalParentId: localParentID,
|
||
Offset: offset,
|
||
}
|
||
|
||
stream, err := c.client.DownloadSnapshotDiff(ctx, req)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to start snapshot diff download: %w", err)
|
||
}
|
||
|
||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||
return fmt.Errorf("failed to create directory for %s: %w", targetPath, err)
|
||
}
|
||
|
||
file, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to open file %s: %w", targetPath, err)
|
||
}
|
||
defer file.Close()
|
||
|
||
for {
|
||
resp, err := stream.Recv()
|
||
if err == io.EOF {
|
||
break
|
||
}
|
||
if err != nil {
|
||
return fmt.Errorf("error receiving diff chunk: %w", err)
|
||
}
|
||
if _, err := file.Write(resp.ChunkData); err != nil {
|
||
return fmt.Errorf("error writing to file: %w", err)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|