12345678module events910import strings11import time12import src.machine13import src.storage1415pub const kind_flag = 'flag'16pub const kind_clear = 'clear'17pub const kind_strip = 'strip'181920pub struct Event {21pub mut:22 id string23 kind string24 save_id string25 path string26 reason string27 created_at i6428 machine string29}303132pub struct Flag {33pub:34 save_id string35 flagged bool36 reason string37 at i6438}3940const select_event = 'SELECT id, kind, save_id, path, reason, created_at, machine FROM events'4142fn read_event(stmt &storage.Stmt) Event {43 return Event{44 id: stmt.text(0)45 kind: stmt.text(1)46 save_id: stmt.text_or_none(2) or { '' }47 path: stmt.text_or_none(3) or { '' }48 reason: stmt.text_or_none(4) or { '' }49 created_at: stmt.int(5)50 machine: stmt.text(6)51 }52}53545556pub fn write(store &storage.Store, event Event) !Event {57 mut record := event58 if record.created_at == 0 {59 60 61 62 record.created_at = next_timestamp(store)!63 }64 if record.machine == '' {65 record.machine = machine.name()66 }67 record.id = event_id(record)68 insert(store, record)!69 return record70}7172737475pub fn receive(store &storage.Store, event Event) !bool {76 insert(store, event)!77 return store.db.changes() > 078}7980fn next_timestamp(store &storage.Store) !i64 {81 now := time.now().unix()82 mut stmt := store.db.prepare('SELECT MAX(created_at) FROM events')!83 defer {84 stmt.finalize()85 }86 if !stmt.step()! {87 return now88 }89 newest := stmt.int_or_none(0) or { return now }90 if newest >= now {91 return newest + 192 }93 return now94}9596fn insert(store &storage.Store, record Event) ! {97 mut stmt := store.db.prepare('INSERT OR IGNORE INTO events98 (id, kind, save_id, path, reason, created_at, machine) VALUES (?, ?, ?, ?, ?, ?, ?)')!99 defer {100 stmt.finalize()101 }102 stmt.bind_text(1, record.id)!103 stmt.bind_text(2, record.kind)!104 bind_optional(stmt, 3, record.save_id)!105 bind_optional(stmt, 4, record.path)!106 bind_optional(stmt, 5, record.reason)!107 stmt.bind_int(6, record.created_at)!108 stmt.bind_text(7, record.machine)!109 stmt.step()!110}111112pub fn flag(store &storage.Store, save_id string, reason string) !Event {113 return write(store, Event{114 kind: kind_flag115 save_id: save_id116 reason: reason117 })!118}119120pub fn clear_flag(store &storage.Store, save_id string) !Event {121 return write(store, Event{122 kind: kind_clear123 save_id: save_id124 })!125}126127128129130pub fn strip(store &storage.Store, save_id string, path string) !Event {131 return write(store, Event{132 kind: kind_strip133 save_id: save_id134 path: path135 })!136}137138pub fn event_id(event Event) string {139 mut sb := strings.Builder{}140 put(mut sb, 'kind', event.kind)141 put(mut sb, 'save', event.save_id)142 put(mut sb, 'path', event.path)143 put(mut sb, 'reason', event.reason)144 put(mut sb, 'created', event.created_at.str())145 put(mut sb, 'machine', event.machine)146 return storage.hash_hex(sb.str().bytes())[..12]147}148149fn put(mut sb strings.Builder, name string, value string) {150 sb.write_string('${name}:${value.len}:')151 sb.write_string(value)152 sb.write_string('\n')153}154155fn bind_optional(stmt &storage.Stmt, index int, value string) ! {156 if value == '' {157 stmt.bind_null(index)!158 } else {159 stmt.bind_text(index, value)!160 }161}162163164pub fn all(store &storage.Store) ![]Event {165 mut stmt := store.db.prepare('${select_event} ORDER BY created_at, id')!166 defer {167 stmt.finalize()168 }169 mut out := []Event{}170 for stmt.step()! {171 out << read_event(stmt)172 }173 return out174}175176177178179pub fn latest_flags(store &storage.Store) !map[string]Flag {180 mut stmt := store.db.prepare('${select_event} WHERE kind IN (?, ?) AND save_id IS NOT NULL181 ORDER BY created_at, id')!182 defer {183 stmt.finalize()184 }185 stmt.bind_text(1, kind_flag)!186 stmt.bind_text(2, kind_clear)!187 mut flags := map[string]Flag{}188 for stmt.step()! {189 event := read_event(stmt)190 flags[event.save_id] = Flag{191 save_id: event.save_id192 flagged: event.kind == kind_flag193 reason: event.reason194 at: event.created_at195 }196 }197 return flags198}199200pub fn flag_state(store &storage.Store, save_id string) !Flag {201 flags := latest_flags(store)!202 return flags[save_id] or {203 Flag{204 save_id: save_id205 }206 }207}208209210pub fn flagged_ids(store &storage.Store) ![]string {211 flags := latest_flags(store)!212 mut ids := []string{}213 for save_id, state in flags {214 if state.flagged {215 ids << save_id216 }217 }218 ids.sort()219 return ids220}221222pub fn strip_events(store &storage.Store) ![]Event {223 mut stmt := store.db.prepare('${select_event} WHERE kind = ? ORDER BY created_at, id')!224 defer {225 stmt.finalize()226 }227 stmt.bind_text(1, kind_strip)!228 mut out := []Event{}229 for stmt.step()! {230 out << read_event(stmt)231 }232 return out233}