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 write_once(store &storage.Store, event Event) !Event {76 mut record := event77 if record.created_at == 0 {78 record.created_at = time.now().unix()79 }80 if record.machine == '' {81 record.machine = machine.name()82 }83 record.id = storage.hash_hex('${record.kind}:${record.save_id}:${record.path}'.bytes())[..12]84 insert(store, record)!85 return record86}8788899091pub fn receive(store &storage.Store, event Event) !bool {92 insert(store, event)!93 return store.db.changes() > 094}9596fn next_timestamp(store &storage.Store) !i64 {97 now := time.now().unix()98 mut stmt := store.db.prepare('SELECT MAX(created_at) FROM events')!99 defer {100 stmt.finalize()101 }102 if !stmt.step()! {103 return now104 }105 newest := stmt.int_or_none(0) or { return now }106 if newest >= now {107 return newest + 1108 }109 return now110}111112fn insert(store &storage.Store, record Event) ! {113 mut stmt := store.db.prepare('INSERT OR IGNORE INTO events114 (id, kind, save_id, path, reason, created_at, machine) VALUES (?, ?, ?, ?, ?, ?, ?)')!115 defer {116 stmt.finalize()117 }118 stmt.bind_text(1, record.id)!119 stmt.bind_text(2, record.kind)!120 bind_optional(stmt, 3, record.save_id)!121 bind_optional(stmt, 4, record.path)!122 bind_optional(stmt, 5, record.reason)!123 stmt.bind_int(6, record.created_at)!124 stmt.bind_text(7, record.machine)!125 stmt.step()!126}127128pub fn flag(store &storage.Store, save_id string, reason string) !Event {129 return write(store, Event{130 kind: kind_flag131 save_id: save_id132 reason: reason133 })!134}135136pub fn clear_flag(store &storage.Store, save_id string) !Event {137 return write(store, Event{138 kind: kind_clear139 save_id: save_id140 })!141}142143144145pub fn strip(store &storage.Store, save_id string, path string) !Event {146 return write_once(store, Event{147 kind: kind_strip148 save_id: save_id149 path: path150 })!151}152153pub fn event_id(event Event) string {154 mut sb := strings.Builder{}155 put(mut sb, 'kind', event.kind)156 put(mut sb, 'save', event.save_id)157 put(mut sb, 'path', event.path)158 put(mut sb, 'reason', event.reason)159 put(mut sb, 'created', event.created_at.str())160 put(mut sb, 'machine', event.machine)161 return storage.hash_hex(sb.str().bytes())[..12]162}163164fn put(mut sb strings.Builder, name string, value string) {165 sb.write_string('${name}:${value.len}:')166 sb.write_string(value)167 sb.write_string('\n')168}169170fn bind_optional(stmt &storage.Stmt, index int, value string) ! {171 if value == '' {172 stmt.bind_null(index)!173 } else {174 stmt.bind_text(index, value)!175 }176}177178179pub fn all(store &storage.Store) ![]Event {180 mut stmt := store.db.prepare('${select_event} ORDER BY created_at, id')!181 defer {182 stmt.finalize()183 }184 mut out := []Event{}185 for stmt.step()! {186 out << read_event(stmt)187 }188 return out189}190191192193194pub fn latest_flags(store &storage.Store) !map[string]Flag {195 mut stmt := store.db.prepare('${select_event} WHERE kind IN (?, ?) AND save_id IS NOT NULL196 ORDER BY created_at, id')!197 defer {198 stmt.finalize()199 }200 stmt.bind_text(1, kind_flag)!201 stmt.bind_text(2, kind_clear)!202 mut flags := map[string]Flag{}203 for stmt.step()! {204 event := read_event(stmt)205 flags[event.save_id] = Flag{206 save_id: event.save_id207 flagged: event.kind == kind_flag208 reason: event.reason209 at: event.created_at210 }211 }212 return flags213}214215pub fn flag_state(store &storage.Store, save_id string) !Flag {216 flags := latest_flags(store)!217 return flags[save_id] or {218 Flag{219 save_id: save_id220 }221 }222}223224225pub fn flagged_ids(store &storage.Store) ![]string {226 flags := latest_flags(store)!227 mut ids := []string{}228 for save_id, state in flags {229 if state.flagged {230 ids << save_id231 }232 }233 ids.sort()234 return ids235}236237pub fn strip_events(store &storage.Store) ![]Event {238 mut stmt := store.db.prepare('${select_event} WHERE kind = ? ORDER BY created_at, id')!239 defer {240 stmt.finalize()241 }242 stmt.bind_text(1, kind_strip)!243 mut out := []Event{}244 for stmt.step()! {245 out << read_event(stmt)246 }247 return out248}