-- Minstrel's own backup: its settings and its runs. -- name: GetBackupSettings :one SELECT * FROM backup_settings WHERE id = true; -- name: SaveBackupSettings :exec UPDATE backup_settings SET enabled = $1, destination = $2, folder = $3, endpoint = $4, bucket = $5, access_key = $6, secret_key = $7, run_hour = $8, upload_limit_kib = $9, include_music = $10, updated_at = now() WHERE id = true; -- name: StartBackupRun :one INSERT INTO backup_runs (trigger) VALUES ($1) RETURNING id, started_at; -- name: FinishBackupRun :exec UPDATE backup_runs SET status = $2, finished_at = now(), location = $3, bytes = $4, error = $5, music_files = $6, music_bytes = $7, music_note = $8 WHERE id = $1; -- name: ListBackupRuns :many SELECT id, trigger, status, started_at, finished_at, location, bytes, error, music_files, music_bytes, music_note FROM backup_runs ORDER BY started_at DESC LIMIT $1; -- name: BackupRunTimes :one -- The times the verdict turns on. A run that never happened is the epoch, -- so the row always exists. failing_since is the first failure after the last -- success, and the epoch when the latest finished run succeeded. SELECT COALESCE((SELECT max(started_at) FROM backup_runs WHERE status = 'succeeded'), 'epoch'::timestamptz)::timestamptz AS last_success, COALESCE((SELECT max(started_at) FROM backup_runs), 'epoch'::timestamptz)::timestamptz AS last_attempt, COALESCE((SELECT min(started_at) FROM backup_runs WHERE status = 'failed' AND started_at > COALESCE((SELECT max(started_at) FROM backup_runs WHERE status = 'succeeded'), 'epoch'::timestamptz)), 'epoch'::timestamptz)::timestamptz AS failing_since; -- name: FailUnfinishedBackupRuns :execrows UPDATE backup_runs SET status = 'failed', finished_at = now(), error = 'The server stopped while the backup was running.' WHERE status = 'running'; -- name: PruneBackupRuns :execrows DELETE FROM backup_runs WHERE status <> 'running' AND started_at < $1; -- name: PendingBackupMusic :many -- Library files the backup may not hold as they are now, in path order from -- after_path, so a night that runs out of time carries on where it stopped: -- never sent, a different size, touched by a scan since they were last -- checked, or refused before the settings were last saved (a fixed key gets -- another go). The caller compares the file's own size and mtime before -- sending. SELECT t.id, t.file_path, t.file_size, m.size AS sent_size, m.mtime AS sent_mtime, m.error AS sent_error FROM tracks t LEFT JOIN backup_music_files m ON m.track_id = t.id WHERE t.missing_since IS NULL AND t.file_path > sqlc.arg(after_path)::text AND (m.track_id IS NULL OR m.size <> t.file_size OR t.updated_at > m.checked_at OR (m.error <> '' AND m.checked_at < (SELECT updated_at FROM backup_settings))) ORDER BY t.file_path LIMIT sqlc.arg(max_rows); -- name: RecordBackupMusic :exec INSERT INTO backup_music_files (track_id, size, mtime, location, error) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (track_id) DO UPDATE SET size = EXCLUDED.size, mtime = EXCLUDED.mtime, location = EXCLUDED.location, error = EXCLUDED.error, checked_at = now(); -- name: BackupMusicProgress :one -- How much of the library the backup holds as it is now. SELECT count(*)::bigint AS total_files, COALESCE(sum(t.file_size), 0)::bigint AS total_bytes, count(*) FILTER (WHERE m.error = '' AND m.size = t.file_size AND t.updated_at <= m.checked_at)::bigint AS sent_files, COALESCE(sum(t.file_size) FILTER (WHERE m.error = '' AND m.size = t.file_size AND t.updated_at <= m.checked_at), 0)::bigint AS sent_bytes, count(*) FILTER (WHERE m.error <> '' AND m.size = t.file_size AND t.updated_at <= m.checked_at)::bigint AS refused_files FROM tracks t LEFT JOIN backup_music_files m ON m.track_id = t.id WHERE t.missing_since IS NULL; -- name: RefusedBackupMusic :many SELECT t.file_path, m.error, m.checked_at FROM backup_music_files m JOIN tracks t ON t.id = m.track_id WHERE m.error <> '' AND m.size = t.file_size AND t.updated_at <= m.checked_at ORDER BY m.checked_at DESC LIMIT $1;