Refactor snapshot management: integrate logging, enhance concurrency with mutex, add clean extraction option, and update gRPC ListSnapshots with ListOptions.
This commit is contained in:
168
manager.go
168
manager.go
@ -6,9 +6,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -16,20 +16,26 @@ import (
|
||||
|
||||
"gitea.unprism.ru/KRBL/Agate/archive"
|
||||
"gitea.unprism.ru/KRBL/Agate/hash"
|
||||
"gitea.unprism.ru/KRBL/Agate/interfaces"
|
||||
"gitea.unprism.ru/KRBL/Agate/store"
|
||||
)
|
||||
|
||||
type SnapshotManagerData struct {
|
||||
metadataStore store.MetadataStore
|
||||
blobStore store.BlobStore
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func CreateSnapshotManager(metadataStore store.MetadataStore, blobStore store.BlobStore) (SnapshotManager, error) {
|
||||
func CreateSnapshotManager(metadataStore store.MetadataStore, blobStore store.BlobStore, logger *log.Logger) (interfaces.SnapshotManager, error) {
|
||||
if metadataStore == nil || blobStore == nil {
|
||||
return nil, errors.New("parameters can't be nil")
|
||||
}
|
||||
|
||||
return &SnapshotManagerData{metadataStore, blobStore}, nil
|
||||
return &SnapshotManagerData{
|
||||
metadataStore: metadataStore,
|
||||
blobStore: blobStore,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (data *SnapshotManagerData) CreateSnapshot(ctx context.Context, sourceDir string, name string, parentID string) (*store.Snapshot, error) {
|
||||
@ -162,9 +168,9 @@ func (data *SnapshotManagerData) GetSnapshotDetails(ctx context.Context, snapsho
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func (data *SnapshotManagerData) ListSnapshots(ctx context.Context) ([]store.SnapshotInfo, error) {
|
||||
// Retrieve list of snapshots from the metadata store
|
||||
snapshots, err := data.metadataStore.ListSnapshotsMetadata(ctx)
|
||||
func (data *SnapshotManagerData) ListSnapshots(ctx context.Context, opts store.ListOptions) ([]store.SnapshotInfo, error) {
|
||||
// Retrieve list of snapshots from the metadata store with the provided options
|
||||
snapshots, err := data.metadataStore.ListSnapshotsMetadata(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list snapshots: %w", err)
|
||||
}
|
||||
@ -177,8 +183,8 @@ func (data *SnapshotManagerData) DeleteSnapshot(ctx context.Context, snapshotID
|
||||
return errors.New("snapshot ID cannot be empty")
|
||||
}
|
||||
|
||||
// First check if the snapshot exists
|
||||
_, err := data.metadataStore.GetSnapshotMetadata(ctx, snapshotID)
|
||||
// First check if the snapshot exists and get its details
|
||||
snapshot, err := data.metadataStore.GetSnapshotMetadata(ctx, snapshotID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
// If snapshot doesn't exist, return success (idempotent operation)
|
||||
@ -187,6 +193,39 @@ func (data *SnapshotManagerData) DeleteSnapshot(ctx context.Context, snapshotID
|
||||
return fmt.Errorf("failed to check if snapshot exists: %w", err)
|
||||
}
|
||||
|
||||
// Get the parent ID of the snapshot being deleted
|
||||
parentID := snapshot.ParentID
|
||||
|
||||
// Find all snapshots that have the deleted snapshot as their parent
|
||||
// We need to update their parent ID to maintain the chain
|
||||
opts := store.ListOptions{}
|
||||
allSnapshots, err := data.metadataStore.ListSnapshotsMetadata(ctx, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list snapshots: %w", err)
|
||||
}
|
||||
|
||||
// Update parent references for any snapshots that have this one as a parent
|
||||
for _, info := range allSnapshots {
|
||||
if info.ParentID == snapshotID {
|
||||
// Get the full snapshot details
|
||||
childSnapshot, err := data.metadataStore.GetSnapshotMetadata(ctx, info.ID)
|
||||
if err != nil {
|
||||
data.logger.Printf("WARNING: failed to get child snapshot %s details: %v", info.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Update the parent ID to point to the deleted snapshot's parent
|
||||
childSnapshot.ParentID = parentID
|
||||
|
||||
// Save the updated snapshot
|
||||
if err := data.metadataStore.SaveSnapshotMetadata(ctx, *childSnapshot); err != nil {
|
||||
data.logger.Printf("WARNING: failed to update parent reference for snapshot %s: %v", info.ID, err)
|
||||
} else {
|
||||
data.logger.Printf("Updated parent reference for snapshot %s from %s to %s", info.ID, snapshotID, parentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the metadata first
|
||||
if err := data.metadataStore.DeleteSnapshotMetadata(ctx, snapshotID); err != nil {
|
||||
return fmt.Errorf("failed to delete snapshot metadata: %w", err)
|
||||
@ -197,7 +236,7 @@ func (data *SnapshotManagerData) DeleteSnapshot(ctx context.Context, snapshotID
|
||||
// Note: We don't return here because we've already deleted the metadata
|
||||
// and the blob store should handle the case where the blob doesn't exist
|
||||
// Log the error instead
|
||||
fmt.Printf("Warning: failed to delete snapshot blob: %v\n", err)
|
||||
data.logger.Printf("WARNING: failed to delete snapshot blob: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -248,7 +287,7 @@ func (data *SnapshotManagerData) OpenFile(ctx context.Context, snapshotID string
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (data *SnapshotManagerData) ExtractSnapshot(ctx context.Context, snapshotID string, path string) error {
|
||||
func (data *SnapshotManagerData) ExtractSnapshot(ctx context.Context, snapshotID string, path string, cleanTarget bool) error {
|
||||
if snapshotID == "" {
|
||||
return errors.New("snapshot ID cannot be empty")
|
||||
}
|
||||
@ -258,8 +297,8 @@ func (data *SnapshotManagerData) ExtractSnapshot(ctx context.Context, snapshotID
|
||||
path = data.blobStore.GetActiveDir()
|
||||
}
|
||||
|
||||
// First check if the snapshot exists and get its metadata
|
||||
snapshot, err := data.metadataStore.GetSnapshotMetadata(ctx, snapshotID)
|
||||
// First check if the snapshot exists
|
||||
_, err := data.metadataStore.GetSnapshotMetadata(ctx, snapshotID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return ErrNotFound
|
||||
@ -273,9 +312,20 @@ func (data *SnapshotManagerData) ExtractSnapshot(ctx context.Context, snapshotID
|
||||
return fmt.Errorf("failed to get blob path: %w", err)
|
||||
}
|
||||
|
||||
// Ensure the target directory exists
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create target directory: %w", err)
|
||||
// If cleanTarget is true, clean the target directory before extraction
|
||||
if cleanTarget {
|
||||
// Remove the directory and recreate it
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("failed to clean target directory: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create target directory: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Just ensure the target directory exists
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create target directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the archive to the target directory
|
||||
@ -283,94 +333,6 @@ func (data *SnapshotManagerData) ExtractSnapshot(ctx context.Context, snapshotID
|
||||
return fmt.Errorf("failed to extract snapshot: %w", err)
|
||||
}
|
||||
|
||||
// Create maps for files and directories in the snapshot for quick lookup
|
||||
snapshotFiles := make(map[string]bool)
|
||||
snapshotDirs := make(map[string]bool)
|
||||
for _, file := range snapshot.Files {
|
||||
if file.IsDir {
|
||||
snapshotDirs[file.Path] = true
|
||||
} else {
|
||||
snapshotFiles[file.Path] = true
|
||||
}
|
||||
}
|
||||
|
||||
// First pass: Collect all files and directories in the target
|
||||
var allPaths []string
|
||||
err = filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip the root directory itself
|
||||
if filePath == path {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create relative path
|
||||
relPath, err := filepath.Rel(path, filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get relative path: %w", err)
|
||||
}
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
|
||||
allPaths = append(allPaths, filePath)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan target directory: %w", err)
|
||||
}
|
||||
|
||||
// Sort paths by length in descending order to process deepest paths first
|
||||
// This ensures we process files before their parent directories
|
||||
sort.Slice(allPaths, func(i, j int) bool {
|
||||
return len(allPaths[i]) > len(allPaths[j])
|
||||
})
|
||||
|
||||
// Second pass: Remove files and directories that aren't in the snapshot
|
||||
for _, filePath := range allPaths {
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
// Skip if file no longer exists (might have been in a directory we already removed)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to stat file %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
// Create relative path
|
||||
relPath, err := filepath.Rel(path, filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get relative path: %w", err)
|
||||
}
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
|
||||
if info.IsDir() {
|
||||
// For directories, check if it's in the snapshot or if it's empty
|
||||
if !snapshotDirs[relPath] {
|
||||
// Check if directory is empty
|
||||
entries, err := os.ReadDir(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read directory %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
// If directory is empty, remove it
|
||||
if len(entries) == 0 {
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
return fmt.Errorf("failed to remove directory %s: %w", filePath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For files, remove if not in the snapshot
|
||||
if !snapshotFiles[relPath] {
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
return fmt.Errorf("failed to remove file %s: %w", filePath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user