an atproto pds written in F# (.NET 9) 馃
pds fsharp giraffe dotnet atproto
at main 3.8 kB view raw
1namespace PDSharp.Core 2 3open System.IO 4open Amazon.S3 5open Amazon.S3.Model 6open PDSharp.Core.Config 7 8/// Interface for binary large object (blob) storage 9type IBlobStore = 10 /// Store a blob by CID 11 abstract member Put : Cid * byte[] -> Async<unit> 12 /// Retrieve a blob by CID 13 abstract member Get : Cid -> Async<byte[] option> 14 /// Check if a blob exists (optional optimization) 15 abstract member Has : Cid -> Async<bool> 16 /// Delete a blob by CID 17 abstract member Delete : Cid -> Async<unit> 18 19module BlobStore = 20 21 /// File-system based blob store 22 type DiskBlobStore(encodedRootPath : string) = 23 let rootPath = 24 if Path.IsPathRooted encodedRootPath then 25 encodedRootPath 26 else 27 Path.Combine(Directory.GetCurrentDirectory(), encodedRootPath) 28 29 do 30 if not (Directory.Exists rootPath) then 31 Directory.CreateDirectory(rootPath) |> ignore 32 33 let getPath (cid : Cid) = Path.Combine(rootPath, cid.ToString()) 34 35 interface IBlobStore with 36 member _.Put(cid, data) = async { 37 let path = getPath cid 38 39 if not (File.Exists path) then 40 do! File.WriteAllBytesAsync(path, data) |> Async.AwaitTask 41 } 42 43 member _.Get(cid) = async { 44 let path = getPath cid 45 46 if File.Exists path then 47 let! data = File.ReadAllBytesAsync(path) |> Async.AwaitTask 48 return Some data 49 else 50 return None 51 } 52 53 member _.Has(cid) = async { return File.Exists(getPath cid) } 54 55 member _.Delete(cid) = async { 56 let path = getPath cid 57 58 if File.Exists path then 59 File.Delete path 60 } 61 62 /// S3-based blob store 63 type S3BlobStore(config : S3Config) = 64 let client = 65 let clientConfig = 66 AmazonS3Config(RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName config.Region) 67 68 match config.ServiceUrl with 69 | Some url -> clientConfig.ServiceURL <- url 70 | None -> () 71 72 clientConfig.ForcePathStyle <- config.ForcePathStyle 73 74 match config.AccessKey, config.SecretKey with 75 | Some access, Some secret -> new AmazonS3Client(access, secret, clientConfig) 76 | _ -> new AmazonS3Client(clientConfig) 77 78 let bucket = config.Bucket 79 80 interface IBlobStore with 81 member _.Put(cid, data) = async { 82 let request = PutObjectRequest() 83 request.BucketName <- bucket 84 request.Key <- cid.ToString() 85 use ms = new MemoryStream(data) 86 request.InputStream <- ms 87 let! _ = client.PutObjectAsync(request) |> Async.AwaitTask 88 () 89 } 90 91 member _.Get(cid) = async { 92 try 93 let request = GetObjectRequest() 94 request.BucketName <- bucket 95 request.Key <- cid.ToString() 96 97 use! response = client.GetObjectAsync(request) |> Async.AwaitTask 98 use ms = new MemoryStream() 99 do! response.ResponseStream.CopyToAsync(ms) |> Async.AwaitTask 100 return Some(ms.ToArray()) 101 with 102 | :? AmazonS3Exception as ex when ex.StatusCode = System.Net.HttpStatusCode.NotFound -> return None 103 | _ -> return None 104 } 105 106 member _.Has(cid) = async { 107 try 108 let request = GetObjectMetadataRequest() 109 request.BucketName <- bucket 110 request.Key <- cid.ToString() 111 let! _ = client.GetObjectMetadataAsync(request) |> Async.AwaitTask 112 return true 113 with 114 | :? AmazonS3Exception as ex when ex.StatusCode = System.Net.HttpStatusCode.NotFound -> return false 115 | _ -> return false 116 } 117 118 member _.Delete(cid) = async { 119 let request = DeleteObjectRequest() 120 request.BucketName <- bucket 121 request.Key <- cid.ToString() 122 let! _ = client.DeleteObjectAsync(request) |> Async.AwaitTask 123 () 124 }