12345678module storage910import compress.zlib11import crypto.blake31213pub const max_file_size = 512 * 1024 * 10241415pub fn hash_hex(data []u8) string {16 return blake3.sum256(data).hex()17}18192021pub fn (s &Store) put_object(data []u8) !string {22 hash := hash_hex(data)23 if s.has_object(hash)! {24 return hash25 }26 packed := zlib.compress(data)!27 mut stmt := s.db.prepare('INSERT OR IGNORE INTO objects (hash, size, data) VALUES (?, ?, ?)')!28 defer {29 stmt.finalize()30 }31 stmt.bind_text(1, hash)!32 stmt.bind_int(2, i64(data.len))!33 stmt.bind_blob(3, packed)!34 stmt.step()!35 return hash36}37383940pub fn (s &Store) object_blob(hash string) ![]u8 {41 mut stmt := s.db.prepare('SELECT data FROM objects WHERE hash = ?')!42 defer {43 stmt.finalize()44 }45 stmt.bind_text(1, hash)!46 if !stmt.step()! {47 return error('object ${hash} is missing')48 }49 return stmt.blob(0)50}5152535455pub fn (s &Store) put_object_blob(hash string, blob []u8) ! {56 if s.has_object(hash)! {57 return58 }59 data := zlib.decompress(blob)!60 if hash_hex(data) != hash {61 return error('object ${hash} does not match its hash')62 }63 mut stmt := s.db.prepare('INSERT OR IGNORE INTO objects (hash, size, data) VALUES (?, ?, ?)')!64 defer {65 stmt.finalize()66 }67 stmt.bind_text(1, hash)!68 stmt.bind_int(2, i64(data.len))!69 stmt.bind_blob(3, blob)!70 stmt.step()!71}7273pub fn (s &Store) has_object(hash string) !bool {74 mut stmt := s.db.prepare('SELECT 1 FROM objects WHERE hash = ?')!75 defer {76 stmt.finalize()77 }78 stmt.bind_text(1, hash)!79 return stmt.step()!80}81828384pub fn (s &Store) get_object(hash string) ![]u8 {85 mut stmt := s.db.prepare('SELECT data FROM objects WHERE hash = ?')!86 defer {87 stmt.finalize()88 }89 stmt.bind_text(1, hash)!90 if !stmt.step()! {91 return error('object ${hash} is missing')92 }93 packed := stmt.blob(0)94 data := zlib.decompress(packed)!95 if hash_hex(data) != hash {96 return error('object ${hash} does not match its hash')97 }98 return data99}100101pub fn (s &Store) object_size(hash string) !i64 {102 mut stmt := s.db.prepare('SELECT size FROM objects WHERE hash = ?')!103 defer {104 stmt.finalize()105 }106 stmt.bind_text(1, hash)!107 if !stmt.step()! {108 return error('object ${hash} is missing')109 }110 return stmt.int(0)111}112113pub fn (s &Store) object_count() !i64 {114 mut stmt := s.db.prepare('SELECT COUNT(*) FROM objects')!115 defer {116 stmt.finalize()117 }118 if !stmt.step()! {119 return 0120 }121 return stmt.int(0)122}123124125126pub fn (s &Store) drop_unreferenced_objects() !i64 {127 before := s.object_count()!128 mut stmt := s.db.prepare('DELETE FROM objects WHERE hash NOT IN (SELECT hash FROM save_files)')!129 defer {130 stmt.finalize()131 }132 stmt.step()!133 after := s.object_count()!134 return before - after135}136137138139pub fn (s &Store) check_objects() ![]string {140 mut problems := []string{}141 mut stmt := s.db.prepare('SELECT hash FROM objects ORDER BY hash')!142 defer {143 stmt.finalize()144 }145 mut hashes := []string{}146 for stmt.step()! {147 hashes << stmt.text(0)148 }149 for hash in hashes {150 s.get_object(hash) or { problems << 'object ${hash}: ${err.msg()}' }151 }152 return problems153}154155156157158pub fn (s &Store) check_save_files() ![]string {159 mut problems := []string{}160 mut stmt := s.db.prepare('SELECT DISTINCT f.save_id, f.hash FROM save_files f161 LEFT JOIN objects o ON o.hash = f.hash WHERE o.hash IS NULL')!162 defer {163 stmt.finalize()164 }165 for stmt.step()! {166 problems << 'save ${stmt.text(0)} needs missing object ${stmt.text(1)}'167 }168 return problems169}