///|
pub(open) trait ObjectStore {
async fn put(Self, VcsObject) -> ObjectId
async fn get(Self, ObjectId) -> VcsObject
async fn has(Self, ObjectId) -> Bool
async fn verify(Self, ObjectId) -> Bool
}
///|
pub struct MemoryObjectStore {
objects : Map[String, Bytes]
}
///|
pub fn MemoryObjectStore::new() -> MemoryObjectStore {
{ objects: Map([]), }
}
///|
pub fn MemoryObjectStore::count(self : MemoryObjectStore) -> Int32 {
self.objects.length()
}
///|
pub fn MemoryObjectStore::corrupt_for_test(
self : MemoryObjectStore,
id : ObjectId,
bytes : Bytes,
) -> Unit {
self.objects[id.hex] = bytes
}
///|
pub impl ObjectStore for MemoryObjectStore with fn put(self, object) {
let encoded = encode_object(object)
let id = object_id_for_bytes(encoded)
if self.objects.get(id.hex) is Some(existing) {
if !verify_encoded_object(id, existing) {
raise VcsError::InvalidObject("existing object is corrupt: \{id.hex}")
}
} else {
self.objects[id.hex] = encoded
}
id
}
///|
pub impl ObjectStore for MemoryObjectStore with fn get(self, id) {
guard self.objects.get(id.hex) is Some(encoded) else {
raise VcsError::ObjectNotFound(id.hex)
}
if object_id_for_bytes(encoded) != id {
raise VcsError::InvalidObject("object checksum mismatch: \{id.hex}")
}
decode_object(encoded)
}
///|
pub impl ObjectStore for MemoryObjectStore with fn has(self, id) {
self.objects.contains(id.hex)
}
///|
pub impl ObjectStore for MemoryObjectStore with fn verify(self, id) {
match self.objects.get(id.hex) {
Some(encoded) => verify_encoded_object(id, encoded)
None => false
}
}
///|
pub extend MemoryObjectStore with ObjectStore::{put, get, has, verify}
///|
pub struct FileObjectStore {
objects_dir : String
}
///|
async fn ensure_storage_directory(path : String) -> Unit {
if @fs.exists(path) {
if @fs.kind(path, follow_symlink=false) != Directory {
raise VcsError::InvalidPath("object storage directory is not a directory")
}
return
}
@fs.mkdir(path, recursive=true, permission=0o755) catch {
error => {
if @async.is_cancellation_error(error) {
raise error
}
// A concurrent writer may have created the same bucket.
if !@fs.exists(path) || @fs.kind(path, follow_symlink=false) != Directory {
raise error
}
}
}
sync_storage_directory(path)
sync_storage_directory(parent_directory(path))
}
///|
pub fn FileObjectStore::new(repository_root : String) -> FileObjectStore {
{ objects_dir: repository_root + "/.moonvcs/objects", }
}
///|
fn FileObjectStore::path(self : FileObjectStore, id : ObjectId) -> String {
self.objects_dir + "/" + id.hex[:2].to_owned() + "/" + id.hex[2:].to_owned()
}
///|
pub fn FileObjectStore::object_path(
self : FileObjectStore,
id : ObjectId,
) -> String {
self.path(id)
}
///|
pub impl ObjectStore for FileObjectStore with fn put(self, object) {
let encoded = encode_object(object)
let id = object_id_for_bytes(encoded)
let path = self.path(id)
if @fs.exists(path) {
if !self.verify(id) {
raise VcsError::InvalidObject("existing object is corrupt: \{id.hex}")
}
return id
}
ensure_storage_directory(self.objects_dir + "/" + id.hex[:2].to_owned())
let nonce = match @env.rand(16) {
Some(bytes) => object_id_for_bytes(bytes).hex
None =>
raise VcsError::InvalidObject(
"cannot create unique object temporary file",
)
}
let temporary = path + ".tmp-" + nonce
let file = @fs.open(
temporary,
mode=WriteOnly,
create_mode=CreateNew,
permission=0o444,
sync=Full,
)
@async.with_task_group() <| group => {
let mut published = false
group.add_defer() <| () => {
@async.protect_from_cancel(() => {
if !published && @fs.exists(temporary) {
@fs.remove(temporary)
}
})
}
{
defer file.close()
file.write_at(encoded, position=0L)
file.sync()
}
@fs.rename(temporary, path, replace=false) catch {
err => if !self.verify(id) { raise err }
}
// A racing writer may have already installed the same verified object.
published = !@fs.exists(temporary)
if published {
sync_published_directory(parent_directory(path))
}
}
id
}
///|
pub impl ObjectStore for FileObjectStore with fn get(self, id) {
let path = self.path(id)
if !@fs.exists(path) {
raise VcsError::ObjectNotFound(id.hex)
}
let encoded = @fs.read_file(path).binary()
if object_id_for_bytes(encoded) != id {
raise VcsError::InvalidObject("object checksum mismatch: \{id.hex}")
}
decode_object(encoded)
}
///|
pub impl ObjectStore for FileObjectStore with fn has(self, id) {
@fs.exists(self.path(id))
}
///|
pub impl ObjectStore for FileObjectStore with fn verify(self, id) {
let path = self.path(id)
if !@fs.exists(path) {
return false
}
verify_encoded_object(id, @fs.read_file(path).binary())
}
///|
pub extend FileObjectStore with ObjectStore::{put, get, has, verify}