module herofs import crypto.blake3 import freeflowuniverse.herolib.data.encoder import freeflowuniverse.herolib.data.ourtime import freeflowuniverse.herolib.hero.db import freeflowuniverse.herolib.schemas.jsonrpc { Response, new_error, new_response, new_response_false, new_response_int, new_response_ok, new_response_true } import freeflowuniverse.herolib.hero.user { UserRef } import json // FsBlob represents binary data up to 1MB @[heap] pub struct FsBlob { db.Base pub mut: hash string // blake192 hash of content data []u8 // Binary data (max 1MB) size_bytes int // Size in bytes } // Update DBFsBlob struct: pub struct DBFsBlob { pub mut: db &db.DB @[skip; str: skip] factory &FSFactory = unsafe { nil } @[skip; str: skip] } pub fn (self FsBlob) type_name() string { return 'fs_blob' } // return example rpc call and result for each methodname pub fn (self FsBlob) description(methodname string) string { match methodname { 'set' { return 'Create or update a blob. Returns the ID of the blob.' } 'get' { return 'Retrieve a blob by ID. Returns the blob object.' } 'delete' { return 'Delete a blob by ID. Returns true if successful.' } 'exist' { return 'Check if a blob exists by ID. Returns true or false.' } 'list' { return 'List all blobs. Returns an array of blob objects.' } 'get_by_hash' { return 'Retrieve a blob by its hash. Returns the blob object.' } 'exists_by_hash' { return 'Check if a blob exists by its hash. Returns true or false.' } 'verify' { return 'Verify the integrity of a blob by its hash. Returns true or false.' } else { return 'This is generic method for the root object, TODO fill in, ...' } } } // return example rpc call and result for each methodname pub fn (self FsBlob) example(methodname string) (string, string) { match methodname { 'set' { return '{"data": "SGVsbG8gV29ybGQh"}', '1' } 'get' { return '{"id": 1}', '{"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b27796d9ad9587", "data": "SGVsbG8gV29ybGQh", "size_bytes": 12}' } 'delete' { return '{"id": 1}', 'true' } 'exist' { return '{"id": 1}', 'true' } 'list' { return '{}', '[{"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b27796d9ad9587", "data": "SGVsbG8gV29ybGQh", "size_bytes": 12}]' } 'get_by_hash' { return '{"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b27796d9ad9587"}', '{"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b27796d9ad9587", "data": "SGVsbG8gV29ybGQh", "size_bytes": 12}' } 'exists_by_hash' { return '{"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b27796d9ad9587"}', 'true' } 'verify' { return '{"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b27796d9ad9587"}', 'true' } else { return '{}', '{}' } } } pub fn (self FsBlob) dump(mut e encoder.Encoder) ! { e.add_string(self.hash) e.add_list_u8(self.data) e.add_int(self.size_bytes) } fn (mut self DBFsBlob) load(mut o FsBlob, mut e encoder.Decoder) ! { o.hash = e.get_string()! o.data = e.get_list_u8()! o.size_bytes = e.get_int()! } @[params] pub struct FsBlobArg { pub mut: data []u8 @[required] } pub fn (mut blob FsBlob) calculate_hash() { hash := blake3.sum256(blob.data) blob.hash = hash.hex()[..48] // blake192 = first 192 bits = 48 hex chars } // get new blob, not from the DB pub fn (mut self DBFsBlob) new(args FsBlobArg) !FsBlob { if args.data.len > 1024 * 1024 { // 1MB limit return error('Blob size exceeds 1MB limit') } mut o := FsBlob{ data: args.data size_bytes: args.data.len } // Calculate hash o.calculate_hash() // Set base fields o.updated_at = ourtime.now().unix() return o } pub fn (mut self DBFsBlob) set(o_ FsBlob) !FsBlob { // Use db set function which now modifies the object in-place o := self.db.set[FsBlob](o_)! // Store the hash -> id mapping for lookup self.db.redis.hset('fsblob:hashes', o.hash, o.id.str())! return o } pub fn (mut self DBFsBlob) delete(id u32) ! { // Get the blob to retrieve its hash mut blob := self.get(id)! // Remove hash -> id mapping self.db.redis.hdel('fsblob:hashes', blob.hash)! // Delete the blob self.db.delete[FsBlob](id)! } pub fn (mut self DBFsBlob) delete_multi(ids []u32) ! { for id in ids { self.delete(id)! } } pub fn (mut self DBFsBlob) exist(id u32) !bool { return self.db.exists[FsBlob](id)! } pub fn (mut self DBFsBlob) exist_multi(ids []u32) !bool { for id in ids { if !self.exist(id)! { return false } } return true } pub fn (mut self DBFsBlob) get(id u32) !FsBlob { mut o, data := self.db.get_data[FsBlob](id)! mut e_decoder := encoder.decoder_new(data) self.load(mut o, mut e_decoder)! return o } pub fn (mut self DBFsBlob) get_multi(id []u32) ![]FsBlob { mut blobs := []FsBlob{} for i in id { blobs << self.get(i)! } return blobs } pub fn (mut self DBFsBlob) list() ![]FsBlob { return self.db.list[FsBlob]()!.map(self.get(it)!) } pub fn (mut self DBFsBlob) get_by_hash(hash string) !FsBlob { // Get blob ID from Redis hash mapping id_str := self.db.redis.hget('fsblob:hashes', hash)! if id_str == '' { return error('Blob with hash ${hash} not found') } id := id_str.u32() return self.get(id)! } pub fn (mut self DBFsBlob) exists_by_hash(hash string) !bool { // Check if hash exists in Redis mapping id_str := self.db.redis.hget('fsblob:hashes', hash)! return id_str != '' } pub fn (blob FsBlob) verify_integrity() bool { hash := blake3.sum256(blob.data) return hash.hex()[..48] == blob.hash } pub fn (mut self DBFsBlob) verify(hash string) !bool { blob := self.get_by_hash(hash)! return blob.verify_integrity() } pub fn fs_blob_handle(mut f FSFactory, rpcid int, servercontext map[string]string, userref UserRef, method string, params string) !Response { match method { 'get' { id := db.decode_u32(params)! res := f.fs_blob.get(id)! return new_response(rpcid, json.encode(res)) } 'set' { mut o := db.decode_generic[FsBlob](params)! o = f.fs_blob.set(o)! return new_response_int(rpcid, int(o.id)) } 'delete' { id := db.decode_u32(params)! f.fs_blob.delete(id)! return new_response_ok(rpcid) } 'exist' { id := db.decode_u32(params)! if f.fs_blob.exist(id)! { return new_response_true(rpcid) } else { return new_response_false(rpcid) } } 'list' { res := f.fs_blob.list()! return new_response(rpcid, json.encode(res)) } 'get_by_hash' { hash := db.decode_string(params)! res := f.fs_blob.get_by_hash(hash)! return new_response(rpcid, json.encode(res)) } 'exists_by_hash' { hash := db.decode_string(params)! if f.fs_blob.exists_by_hash(hash)! { return new_response_true(rpcid) } else { return new_response_false(rpcid) } } 'verify' { hash := db.decode_string(params)! if f.fs_blob.verify(hash)! { return new_response_true(rpcid) } else { return new_response_false(rpcid) } } else { return new_error(rpcid, code: 32601 message: 'Method ${method} not found on fs_blob' ) } } }