| 1 | // Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module s3 |
| 5 | |
| 6 | // FileOptions configures a File handle. |
| 7 | @[params] |
| 8 | pub struct FileOptions { |
| 9 | pub: |
| 10 | bucket string |
| 11 | } |
| 12 | |
| 13 | // File is a reference to one S3 object. It holds no buffer; every method |
| 14 | // round-trips to S3 (or generates a presigned URL for `presign`). |
| 15 | // |
| 16 | // Construct via `Client.file(...)` or directly with `File{ client: &c, key: '...' }`. |
| 17 | pub struct File { |
| 18 | pub: |
| 19 | client &Client |
| 20 | bucket string |
| 21 | key string |
| 22 | } |
| 23 | |
| 24 | // read returns the full object body. For range reads, see `read_range`. |
| 25 | pub fn (f &File) read() ![]u8 { |
| 26 | return f.client.get(f.key, bucket: f.bucket) |
| 27 | } |
| 28 | |
| 29 | // text is a UTF-8 convenience over `read`. |
| 30 | pub fn (f &File) text() !string { |
| 31 | return f.client.get_string(f.key, bucket: f.bucket) |
| 32 | } |
| 33 | |
| 34 | // read_range fetches `bytes=<begin>-<end_inclusive>` (HTTP Range semantics). |
| 35 | // Use `end < 0` to read to end-of-file. |
| 36 | pub fn (f &File) read_range(begin i64, end i64) ![]u8 { |
| 37 | range := if end < 0 { 'bytes=${begin}-' } else { 'bytes=${begin}-${end}' } |
| 38 | return f.client.get(f.key, bucket: f.bucket, range: range) |
| 39 | } |
| 40 | |
| 41 | // write uploads `data` as the entire object body. |
| 42 | pub fn (f &File) write(data []u8, opts PutOptions) ! { |
| 43 | mut o := opts |
| 44 | if o.bucket == '' { |
| 45 | o = PutOptions{ |
| 46 | ...opts |
| 47 | bucket: f.bucket |
| 48 | } |
| 49 | } |
| 50 | f.client.put(f.key, data, o)! |
| 51 | } |
| 52 | |
| 53 | // write_string is a UTF-8 convenience over `write`. |
| 54 | pub fn (f &File) write_string(s string, opts PutOptions) ! { |
| 55 | f.write(s.bytes(), opts)! |
| 56 | } |
| 57 | |
| 58 | // stat returns object metadata (size / etag / last-modified / content-type). |
| 59 | pub fn (f &File) stat() !Stat { |
| 60 | return f.client.stat(f.key, bucket: f.bucket) |
| 61 | } |
| 62 | |
| 63 | // exists is a HEAD that converts 404 into `false`. |
| 64 | pub fn (f &File) exists() !bool { |
| 65 | return f.client.exists(f.key, bucket: f.bucket) |
| 66 | } |
| 67 | |
| 68 | // size returns the Content-Length, in bytes. |
| 69 | pub fn (f &File) size() !i64 { |
| 70 | return f.client.size(f.key, bucket: f.bucket) |
| 71 | } |
| 72 | |
| 73 | // delete removes the object. Idempotent: no error when the object is absent. |
| 74 | pub fn (f &File) delete() ! { |
| 75 | f.client.delete(f.key, bucket: f.bucket)! |
| 76 | } |
| 77 | |
| 78 | // presign returns a presigned URL for this object. See `PresignOptions`. |
| 79 | pub fn (f &File) presign(opts PresignOptions) !string { |
| 80 | mut o := opts |
| 81 | if o.bucket == '' && f.bucket != '' { |
| 82 | o = PresignOptions{ |
| 83 | ...opts |
| 84 | bucket: f.bucket |
| 85 | } |
| 86 | } |
| 87 | return f.client.presign(f.key, o) |
| 88 | } |
| 89 | |