Skip to content
quarry Artifact [5f9733c99c]
Login

Artifact [5f9733c99c]

Artifact 5f9733c99c22d763b59353574dfbbe48e17f01cdba59e863dee2d913e7dc8f7f:


// 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()
}