88 lines
2.6 KiB
Go
88 lines
2.6 KiB
Go
package offsite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
)
|
|
|
|
// PGCatalog is the Catalog on the felis database.
|
|
type PGCatalog struct{ DB *sql.DB }
|
|
|
|
func (c PGCatalog) PendingWorlds(ctx context.Context) ([]WorldBackup, error) {
|
|
return c.query(ctx, `SELECT id, server_name, backup_ref, created_at FROM world_backups
|
|
WHERE status = 'present' AND offsite_at IS NULL ORDER BY created_at`)
|
|
}
|
|
|
|
func (c PGCatalog) PresentWorlds(ctx context.Context) ([]WorldBackup, error) {
|
|
return c.query(ctx, `SELECT id, server_name, backup_ref, created_at FROM world_backups
|
|
WHERE status = 'present' ORDER BY created_at`)
|
|
}
|
|
|
|
func (c PGCatalog) query(ctx context.Context, q string, args ...any) ([]WorldBackup, error) {
|
|
rows, err := c.DB.QueryContext(ctx, q, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []WorldBackup
|
|
for rows.Next() {
|
|
var w WorldBackup
|
|
if err := rows.Scan(&w.ID, &w.Server, &w.Ref, &w.Created); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, w)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (c PGCatalog) MarkOffsite(ctx context.Context, id string, at time.Time) error {
|
|
_, err := c.DB.ExecContext(ctx,
|
|
`UPDATE world_backups SET offsite_at = $2 WHERE id = $1 AND offsite_at IS NULL`, id, at)
|
|
return err
|
|
}
|
|
|
|
func (c PGCatalog) ExpiredRefs(ctx context.Context, now time.Time) ([]string, error) {
|
|
return c.refs(ctx, `SELECT backup_ref FROM world_backups
|
|
WHERE status IN ('deleted', 'expired') AND expires_at < $1 AND offsite_at IS NOT NULL`, now)
|
|
}
|
|
|
|
func (c PGCatalog) refs(ctx context.Context, q string, args ...any) ([]string, error) {
|
|
rows, err := c.DB.QueryContext(ctx, q, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []string
|
|
for rows.Next() {
|
|
var ref string
|
|
if err := rows.Scan(&ref); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, ref)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (c PGCatalog) NewestOffsite(ctx context.Context) (time.Time, error) {
|
|
var at sql.NullTime
|
|
err := c.DB.QueryRowContext(ctx, `SELECT max(offsite_at) FROM world_backups`).Scan(&at)
|
|
return at.Time, err
|
|
}
|
|
|
|
func (c PGCatalog) KeptRefs(ctx context.Context, now time.Time) ([]string, error) {
|
|
return c.refs(ctx, `SELECT backup_ref FROM world_backups WHERE status = 'present' OR expires_at >= $1`, now)
|
|
}
|
|
|
|
// PendingCount is how many present archives wait for their copy, and since
|
|
// when the oldest has waited.
|
|
func (c PGCatalog) PendingCount(ctx context.Context) (int, time.Time, error) {
|
|
var (
|
|
n int
|
|
oldest sql.NullTime
|
|
)
|
|
err := c.DB.QueryRowContext(ctx, `SELECT count(*), min(created_at) FROM world_backups
|
|
WHERE status = 'present' AND offsite_at IS NULL`).Scan(&n, &oldest)
|
|
return n, oldest.Time, err
|
|
}
|