Files
herolib/lib/hero/herofs/fs_blob.v
2025-09-27 06:57:12 +04:00

285 lines
7.0 KiB
V

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'
)
}
}
}