Files
sm-seo-plugin/models/crawl.go
Jakub Zych cd7182395f fix: a crawl cancel waits for its job, and a stale crawl is ended
Cancelling a running crawl records cancel_requested_at and leaves the
row running until its job stops and writes cancelled, so no second
crawl starts and the row cannot be deleted or pruned meanwhile. The job
writes heartbeat_at every minute; a running crawl without a heartbeat
for 10 minutes is ended by the next start or delete.
2026-11-04 22:51:34 +01:00

113 lines
3.9 KiB
Go

package models
import (
"time"
"gorm.io/gorm"
)
// The states of a crawl (UI-SPEC C24).
const (
CrawlQueued = "queued"
CrawlRunning = "running"
CrawlFinished = "finished"
CrawlFailed = "failed"
CrawlCancelled = "cancelled"
)
// CrawlStatuses lists the states in filter order.
var CrawlStatuses = []string{CrawlQueued, CrawlRunning, CrawlFinished, CrawlFailed, CrawlCancelled}
// What started a crawl.
const (
TriggerAdmin = "admin"
TriggerSchedule = "schedule"
TriggerMCP = "mcp"
)
// The states of the rendered comparison of a crawl (P33-55).
const (
RenderSkipped = "skipped"
RenderDone = "done"
RenderFailed = "failed"
)
// Crawl is one run of the site audit's crawler on golem15_seo_crawls
// (D-14, P33-37): its state, what started it and who, the site address,
// the page limit, the pages fetched and whether the limit stopped it, the
// error of a failed run, the state of the rendered comparison, the conga
// job and its times. HeartbeatAt is written every minute by the running
// crawl's job; CancelRequestedAt is set when a running crawl is cancelled,
// which then stays running until its job stops and writes cancelled.
type Crawl struct {
ID uint `gorm:"column:id;primaryKey"`
Status string `gorm:"column:status"`
Trigger string `gorm:"column:trigger"`
TriggerName string `gorm:"column:trigger_name"`
Site string `gorm:"column:site"`
MaxPages int `gorm:"column:max_pages"`
Pages int `gorm:"column:pages"`
LimitHit bool `gorm:"column:limit_hit"`
Error string `gorm:"column:error"`
RenderStatus string `gorm:"column:render_status"`
RenderPages int `gorm:"column:render_pages"`
RenderNote string `gorm:"column:render_note"`
CancelledBy string `gorm:"column:cancelled_by"`
JobID *uint `gorm:"column:job_id"`
StartedAt *time.Time `gorm:"column:started_at"`
FinishedAt *time.Time `gorm:"column:finished_at"`
CreatedAt *time.Time `gorm:"column:created_at"`
// HeartbeatAt and CancelRequestedAt (P33-60).
HeartbeatAt *time.Time `gorm:"column:heartbeat_at"`
CancelRequestedAt *time.Time `gorm:"column:cancel_requested_at"`
// The list's values, filled by the Crawls controller's
// ListExtendRecords and never saved.
IssuesCell string `gorm:"-:all;column:issues"`
PagesCell string `gorm:"-:all;column:pages_cell"`
DurationCell string `gorm:"-:all;column:duration"`
TriggerCell string `gorm:"-:all;column:trigger_cell"`
}
func (Crawl) TableName() string { return "golem15_seo_crawls" }
// MorphName names a crawl in the admin.
func (Crawl) MorphName() string { return `Golem15\SEO\Models\Crawl` }
// Fillable is empty: crawls are written by the crawler only.
func (Crawl) Fillable() []string { return []string{} }
// Rules is empty: nothing is edited.
func (Crawl) Rules() map[string]string { return map[string]string{} }
// Active reports whether the crawl is queued or running.
func (c Crawl) Active() bool { return c.Status == CrawlQueued || c.Status == CrawlRunning }
// Cancelling reports whether the crawl is running with a requested cancel:
// its job has not stopped yet.
func (c Crawl) Cancelling() bool { return c.Status == CrawlRunning && c.CancelRequestedAt != nil }
// The Crawls list's filter scope.
const FilterCrawlStatus = "FilterCrawlStatus"
// FilterScopes lists the Crawls list's filter scopes.
func (Crawl) FilterScopes() []string { return []string{FilterCrawlStatus} }
// FilterScope narrows the Crawls list to one state; an unknown value
// matches nothing.
func (Crawl) FilterScope(name string, db *gorm.DB, value any) *gorm.DB {
if db == nil || name != FilterCrawlStatus {
return db
}
s, _ := value.(string)
for _, st := range CrawlStatuses {
if s == st {
return db.Where("golem15_seo_crawls.status = ?", s)
}
}
return db.Where("1 = 0")
}
func init() {
Register(Crawl{})
}