objects.v

2.6 KB · raw

1//! The object table: content-addressed file bytes.2//!3//! Objects are Blake3-addressed and stored zlib-compressed, exactly as Koh 24//! stored them on disk, so the same bytes migrate without a re-hash and a5//! future reader can decompress them with any zlib. Objects are shared between6//! saves: identical content is stored once.78module 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}1819// put_object stores `data` and returns its hash. Writing an object that is20// already present is a no-op, which is what makes repeated saves cheap.21pub 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}3738pub fn (s &Store) has_object(hash string) !bool {39	mut stmt := s.db.prepare('SELECT 1 FROM objects WHERE hash = ?')!40	defer {41		stmt.finalize()42	}43	stmt.bind_text(1, hash)!44	return stmt.step()!45}4647// get_object returns the uncompressed bytes and refuses anything that does not48// hash back to the key it was asked for.49pub fn (s &Store) get_object(hash string) ![]u8 {50	mut stmt := s.db.prepare('SELECT data FROM objects WHERE hash = ?')!51	defer {52		stmt.finalize()53	}54	stmt.bind_text(1, hash)!55	if !stmt.step()! {56		return error('object ${hash} is missing')57	}58	packed := stmt.blob(0)59	data := zlib.decompress(packed)!60	if hash_hex(data) != hash {61		return error('object ${hash} does not match its hash')62	}63	return data64}6566pub fn (s &Store) object_size(hash string) !i64 {67	mut stmt := s.db.prepare('SELECT size FROM objects WHERE hash = ?')!68	defer {69		stmt.finalize()70	}71	stmt.bind_text(1, hash)!72	if !stmt.step()! {73		return error('object ${hash} is missing')74	}75	return stmt.int(0)76}7778pub fn (s &Store) object_count() !i64 {79	mut stmt := s.db.prepare('SELECT COUNT(*) FROM objects')!80	defer {81		stmt.finalize()82	}83	if !stmt.step()! {84		return 085	}86	return stmt.int(0)87}8889// drop_unreferenced_objects removes objects no save points at any more. Only90// `strip` leaves objects behind, so this is what makes a strip permanent.91pub fn (s &Store) drop_unreferenced_objects() !i64 {92	before := s.object_count()!93	mut stmt := s.db.prepare('DELETE FROM objects WHERE hash NOT IN (SELECT hash FROM save_files)')!94	defer {95		stmt.finalize()96	}97	stmt.step()!98	after := s.object_count()!99	return before - after100}