// mirrorattach.go — what a Fossil mirror did about each attachment its
// store does not hold (#273).
//
// zit carries no attachment content, so a mirror holds an attachment's
// bytes only when they arrived as an ordinary artifact. For the rest,
// quarry fetches the file from the upstream into the repository's
// storage and records it here: by the fossil hash the A card names
// (src), the SHA-256 the bucket object is keyed by, and what was learnt
// about the file. A row also records a src the store turned out to
// hold, and a fetch that failed, so neither is asked about again on
// every pass.
package oltp
import (
"database/sql"
"errors"
"time"
)
const mirrorAttachSchema = `
CREATE TABLE IF NOT EXISTS mirror_attach (
repo TEXT NOT NULL,
src TEXT NOT NULL,
state TEXT NOT NULL, -- fetched | store | failed
sha256 TEXT NOT NULL DEFAULT '',
size INTEGER NOT NULL DEFAULT 0,
mimetype TEXT NOT NULL DEFAULT '',
err TEXT NOT NULL DEFAULT '',
at TEXT NOT NULL,
PRIMARY KEY (repo, src)
) WITHOUT ROWID;`
// Mirror attachment states.
const (
MirrorAttachFetched = "fetched"
MirrorAttachStore = "store"
MirrorAttachFailed = "failed"
)
// MirrorAttach is one row: what became of one attachment's bytes.
type MirrorAttach struct {
Repo, Src, State, SHA256, Mimetype, Err string
Size int64
At time.Time
}
// MirrorAttachment reads the row for repo's src.
func (d *DB) MirrorAttachment(repo, src string) (MirrorAttach, bool, error) {
var m MirrorAttach
var at string
err := d.db.QueryRow(`SELECT repo, src, state, sha256, size, mimetype, err, at
FROM mirror_attach WHERE repo = ? AND src = ?`, repo, src).
Scan(&m.Repo, &m.Src, &m.State, &m.SHA256, &m.Size, &m.Mimetype, &m.Err, &at)
if errors.Is(err, sql.ErrNoRows) {
return MirrorAttach{}, false, nil
}
if err != nil {
return MirrorAttach{}, false, err
}
m.At, _ = time.Parse(time.RFC3339, at)
return m, true, nil
}
// RecordMirrorAttachment writes m, replacing any earlier row.
func (d *DB) RecordMirrorAttachment(m MirrorAttach) error {
if m.At.IsZero() {
m.At = time.Now()
}
_, err := d.w.Exec(`INSERT OR REPLACE INTO mirror_attach (repo, src, state, sha256, size, mimetype, err, at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
m.Repo, m.Src, m.State, m.SHA256, m.Size, m.Mimetype, m.Err, m.At.UTC().Format(time.RFC3339))
return err
}
// MirrorAttachSettled is every src of repo with a row that needs no
// further pass: fetched, held by the store, or failed since cutoff.
func (d *DB) MirrorAttachSettled(repo string, cutoff time.Time) (map[string]bool, error) {
rows, err := d.db.Query(`SELECT src FROM mirror_attach WHERE repo = ?
AND (state <> 'failed' OR at >= ?)`, repo, cutoff.UTC().Format(time.RFC3339))
if err != nil {
return nil, err
}
defer rows.Close()
out := map[string]bool{}
for rows.Next() {
var s string
if err := rows.Scan(&s); err != nil {
return nil, err
}
out[s] = true
}
return out, rows.Err()
}