4 Commits

Author SHA1 Message Date
65b1daa52c Bug fix 2025-05-10 14:07:12 +03:00
fb2ae39b47 Add support for tracking and persisting snapshot IDs
Introduce `currentSnapshotID` to track the active snapshot and persist its state in a file. This change ensures the current snapshot ID is restored during initialization and maintained consistently across snapshot operations.
2025-05-10 13:12:17 +03:00
3efa753394 Quick bug fix 2025-05-10 01:31:33 +03:00
f7c1e461e6 Refactor snapshot methods to use active directory context
Updated `SaveSnapshot` and `RestoreSnapshot` methods to reference the active directory via `BlobStore`. Introduced `RestoreSnapshotToDir` for granular restore operations. Additionally, updated dependencies in `go.mod` to their latest versions for compatibility and maintenance.
2025-05-10 01:23:36 +03:00
3 changed files with 126 additions and 21 deletions

111
api.go
View File

@ -40,6 +40,8 @@ type Agate struct {
options AgateOptions options AgateOptions
metadataDir string metadataDir string
blobsDir string blobsDir string
currentSnapshotID string
currentIDFile string
} }
// New initializes a new instance of the Agate library with the given options. // New initializes a new instance of the Agate library with the given options.
@ -90,16 +92,38 @@ func New(options AgateOptions) (*Agate, error) {
return nil, fmt.Errorf("failed to create snapshot manager: %w", err) return nil, fmt.Errorf("failed to create snapshot manager: %w", err)
} }
return &Agate{ // Create a file path for storing the current snapshot ID
currentIDFile := filepath.Join(options.WorkDir, "current_snapshot_id")
agate := &Agate{
manager: manager, manager: manager,
options: options, options: options,
metadataDir: metadataDir, metadataDir: metadataDir,
blobsDir: blobsDir, blobsDir: blobsDir,
}, nil currentIDFile: currentIDFile,
} }
// SaveSnapshot creates a new snapshot from the current state of the work directory. // Load the current snapshot ID if it exists
if _, err := os.Stat(currentIDFile); err == nil {
data, err := os.ReadFile(currentIDFile)
if err == nil && len(data) > 0 {
agate.currentSnapshotID = string(data)
}
}
// Call OpenFunc if provided to initialize resources in the active directory
if options.OpenFunc != nil {
if err := options.OpenFunc(options.BlobStore.GetActiveDir()); err != nil {
return nil, fmt.Errorf("failed to open resources during initialization: %w", err)
}
}
return agate, nil
}
// SaveSnapshot creates a new snapshot from the current state of the active directory.
// If parentID is provided, it will be set as the parent of the new snapshot. // If parentID is provided, it will be set as the parent of the new snapshot.
// If parentID is empty, it will use the ID of the snapshot currently loaded in the active directory.
// Returns the ID of the created snapshot. // Returns the ID of the created snapshot.
func (a *Agate) SaveSnapshot(ctx context.Context, name string, parentID string) (string, error) { func (a *Agate) SaveSnapshot(ctx context.Context, name string, parentID string) (string, error) {
// Call CloseFunc if provided // Call CloseFunc if provided
@ -109,15 +133,30 @@ func (a *Agate) SaveSnapshot(ctx context.Context, name string, parentID string)
} }
} }
// If parentID is not provided, use the current snapshot ID
if parentID == "" {
parentID = a.currentSnapshotID
}
effectiveParentID := parentID
// Create the snapshot // Create the snapshot
snapshot, err := a.manager.CreateSnapshot(ctx, a.options.WorkDir, name, parentID) snapshot, err := a.manager.CreateSnapshot(ctx, a.options.BlobStore.GetActiveDir(), name, effectiveParentID)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to create snapshot: %w", err) return "", fmt.Errorf("failed to create snapshot: %w", err)
} }
// Update the current snapshot ID to the newly created snapshot
a.currentSnapshotID = snapshot.ID
// Save the current snapshot ID to a file
if err := a.saveCurrentSnapshotID(); err != nil {
return "", fmt.Errorf("failed to save current snapshot ID: %w", err)
}
// Call OpenFunc if provided // Call OpenFunc if provided
if a.options.OpenFunc != nil { if a.options.OpenFunc != nil {
if err := a.options.OpenFunc(a.options.WorkDir); err != nil { if err := a.options.OpenFunc(a.options.BlobStore.GetActiveDir()); err != nil {
return "", fmt.Errorf("failed to open resources after snapshot: %w", err) return "", fmt.Errorf("failed to open resources after snapshot: %w", err)
} }
} }
@ -125,7 +164,7 @@ func (a *Agate) SaveSnapshot(ctx context.Context, name string, parentID string)
return snapshot.ID, nil return snapshot.ID, nil
} }
// RestoreSnapshot extracts a snapshot to the work directory. // RestoreSnapshot extracts a snapshot to the active directory.
func (a *Agate) RestoreSnapshot(ctx context.Context, snapshotID string) error { func (a *Agate) RestoreSnapshot(ctx context.Context, snapshotID string) error {
// Call CloseFunc if provided // Call CloseFunc if provided
if a.options.CloseFunc != nil { if a.options.CloseFunc != nil {
@ -135,13 +174,55 @@ func (a *Agate) RestoreSnapshot(ctx context.Context, snapshotID string) error {
} }
// Extract the snapshot // Extract the snapshot
if err := a.manager.ExtractSnapshot(ctx, snapshotID, a.options.WorkDir); err != nil { if err := a.manager.ExtractSnapshot(ctx, snapshotID, a.options.BlobStore.GetActiveDir()); err != nil {
return fmt.Errorf("failed to extract snapshot: %w", err) return fmt.Errorf("failed to extract snapshot: %w", err)
} }
// Save the ID of the snapshot that was restored
a.currentSnapshotID = snapshotID
// Save the current snapshot ID to a file
if err := a.saveCurrentSnapshotID(); err != nil {
return fmt.Errorf("failed to save current snapshot ID: %w", err)
}
// Call OpenFunc if provided // Call OpenFunc if provided
if a.options.OpenFunc != nil { if a.options.OpenFunc != nil {
if err := a.options.OpenFunc(a.options.WorkDir); err != nil { if err := a.options.OpenFunc(a.options.BlobStore.GetActiveDir()); err != nil {
return fmt.Errorf("failed to open resources after restore: %w", err)
}
}
return nil
}
// RestoreSnapshot extracts a snapshot to the directory.
func (a *Agate) RestoreSnapshotToDir(ctx context.Context, snapshotID string, dir string) error {
// Call CloseFunc if provided
if a.options.CloseFunc != nil {
if err := a.options.CloseFunc(); err != nil {
return fmt.Errorf("failed to close resources before restore: %w", err)
}
}
// Extract the snapshot
if err := a.manager.ExtractSnapshot(ctx, snapshotID, dir); err != nil {
return fmt.Errorf("failed to extract snapshot: %w", err)
}
// If restoring to the active directory, save the snapshot ID
if dir == a.options.BlobStore.GetActiveDir() {
a.currentSnapshotID = snapshotID
// Save the current snapshot ID to a file
if err := a.saveCurrentSnapshotID(); err != nil {
return fmt.Errorf("failed to save current snapshot ID: %w", err)
}
}
// Call OpenFunc if provided
if a.options.OpenFunc != nil {
if err := a.options.OpenFunc(dir); err != nil {
return fmt.Errorf("failed to open resources after restore: %w", err) return fmt.Errorf("failed to open resources after restore: %w", err)
} }
} }
@ -164,6 +245,20 @@ func (a *Agate) DeleteSnapshot(ctx context.Context, snapshotID string) error {
return a.manager.DeleteSnapshot(ctx, snapshotID) return a.manager.DeleteSnapshot(ctx, snapshotID)
} }
// saveCurrentSnapshotID saves the current snapshot ID to a file in the WorkDir
func (a *Agate) saveCurrentSnapshotID() error {
if a.currentSnapshotID == "" {
// If there's no current snapshot ID, remove the file if it exists
if _, err := os.Stat(a.currentIDFile); err == nil {
return os.Remove(a.currentIDFile)
}
return nil
}
// Write the current snapshot ID to the file
return os.WriteFile(a.currentIDFile, []byte(a.currentSnapshotID), 0644)
}
// Close releases all resources used by the Agate instance. // Close releases all resources used by the Agate instance.
func (a *Agate) Close() error { func (a *Agate) Close() error {
// Currently, we don't have a way to close the manager directly // Currently, we don't have a way to close the manager directly

10
go.mod
View File

@ -6,14 +6,14 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
github.com/mattn/go-sqlite3 v1.14.28 github.com/mattn/go-sqlite3 v1.14.28
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2
google.golang.org/grpc v1.72.0 google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
) )
require ( require (
golang.org/x/net v0.39.0 // indirect golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.24.0 // indirect golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect
) )

10
go.sum
View File

@ -26,14 +26,24 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f h1:tjZsroqekhC63+WMqzmWyW5Twj/ZfR5HAlpd5YQ1Vs0= google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f h1:tjZsroqekhC63+WMqzmWyW5Twj/ZfR5HAlpd5YQ1Vs0=
google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=