====== S3-Guide — Objekt-Speicher mit std.cloud.s3 ======
Dieser Guide erklärt den praktischen Einsatz von ''std.cloud.s3'' für AWS S3, Cloudflare R2 und MinIO. 54 Funktionen decken alles ab: einfache Up-/Downloads, Multipart für große Dateien, Presigned URLs und vollständig paginiertes Listing.
→ [[lyx_-_programmiersprache:units:cloud:aws|std.cloud.s3 (Unit-Referenz)]] · [[lyx_-_programmiersprache:guides:cloud|Cloud-Guide]] · [[lyx_-_programmiersprache:guides:welche-unit|Welche Unit?]]
**''std.cloud.s3'' und ''std.datetime'' lassen sich nicht gemeinsam importieren.** Beide bringen ''DaysInMonth'' mit:
sema error: mehrdeutiges Symbol 'DaysInMonth' — exportiert von 'std.time' und 'std.datetime'
Die Kollision entsteht **transitiv**: ''std.cloud.s3'' → ''std.cloud.aws.transport'' → ''std.time''. Sie ist am Import nicht abzulesen, und einen Ausweg über Alias oder qualifizierten Zugriff gibt es nicht (→ [[lyx_-_programmiersprache:sprache:module-und-importe|Module & Importe]]).
**Für Zeitangaben in S3-Programmen deshalb ''std.time'' nehmen** — ''unix_time()'', ''DatetimeFromUnixSeconds'', ''DatetimeToUnixSeconds''. Wer ''std.datetime'' braucht, trennt den Code in zwei Units. Nachgemessen mit 1.1.0A.
----
===== Konzept: Buckets und Objekte =====
S3 ist ein **Objekt-Speicher** — kein Dateisystem. Objekte haben einen **Key** (beliebiger String, oft pfadähnlich wie ''backups/2026/report.pdf''), werden in **Buckets** gespeichert und haben einen zugehörigen **Content-Type**. Es gibt keine echten Verzeichnisse, aber Keys mit Slash lassen sich durch den Delimiter-Parameter wie Verzeichnisse behandeln.
Region: eu-central-1
└── Bucket: mein-bucket
├── bericht.pdf (Key, Content-Type: application/pdf)
├── fotos/strand.jpg (Key, Content-Type: image/jpeg)
└── backups/2026/db.tar (Key, Content-Type: application/octet-stream)
**Maximale Objekt-Größe:** 5 TB (Einzelupload: 5 GB; darüber hinaus Multipart-Upload nötig).
----
===== Verbindung herstellen =====
==== AWS S3 ====
import std.cloud.aws.core;
import std.cloud.s3;
// Empfohlen: Credential Chain (Env → ~/.aws/credentials)
var creds: AWSCreds := AWSCredentialsDefault();
var s3: S3Client := S3Connect(creds, "eu-central-1"c);
// Nach der Arbeit immer schließen
S3Close(s3);
AWSCredentialsFree(creds);
**Regionen wählen:** Bucket-Region und Connect-Region müssen übereinstimmen. S3 gibt bei falscher Region HTTP 301 zurück (Redirect zur richtigen Region) — der Client folgt dem nicht automatisch.
==== Cloudflare R2 (kein Egress-Preis) ====
import std.cloud.s3;
var s3: S3Client := S3ConnectR2(
"mein-cf-account-id"c, // Cloudflare Account ID (Dashboard → Rechte Seite)
"r2-access-key-id"c, // R2 API Token (nicht der CF Global API Key!)
"r2-secret-access-key"c
);
// Danach identische API wie AWS S3
R2-Credentials anlegen: Cloudflare Dashboard → R2 → Manage R2 API Tokens → Create API Token.
==== MinIO (lokal / self-hosted) ====
import std.cloud.s3;
var s3: S3Client := S3ConnectMinio(
"localhost"c, // Host
9000, // Port (MinIO-Default)
"minioadmin"c, // Access Key
"minioadmin"c // Secret Key
);
MinIO starten (Docker):
docker run -d -p 9000:9000 -p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data --console-address ":9001"
==== LocalStack (AWS-Emulation für Tests) ====
import std.cloud.aws.core;
import std.cloud.s3;
var creds: AWSCreds := AWSCredentialsStatic("test"c, "test"c);
var s3: S3Client := S3ConnectEndpoint(
creds,
"us-east-1"c,
"localhost:4566"c, // LocalStack-Endpoint
0, // kein TLS
1 // path-style (LocalStack-Anforderung)
);
LocalStack starten:
docker run -d -p 4566:4566 localstack/localstack
==== Custom Endpoint (andere S3-kompatible Dienste) ====
// Beliebiger S3-kompatibler Dienst:
var s3: S3Client := S3ConnectEndpoint(
creds,
"us-east-1"c, // region (oft irrelevant bei non-AWS)
"storage.example.com"c, // eigener Endpoint (ohne https://)
1, // TLS: 1=ja, 0=nein
0 // path-style: 0=virtual-hosted, 1=path-style
);
----
===== Basis-Operationen =====
==== Hochladen ====
import std.cloud.aws.core;
import std.cloud.s3;
import std.alloc;
fn Upload(s3: S3Client): void {
// Aus Datei (einfachster Weg für Dateien)
S3PutObjectFile(s3, "mein-bucket"c, "bericht.pdf"c,
"/tmp/bericht.pdf"c, "application/pdf"c);
// Aus Speicherpuffer
var text: pchar := "Hallo S3!"c;
var len: int64 := 9;
S3PutObject(s3, "mein-bucket"c, "hello.txt"c,
text as int64, len, "text/plain; charset=utf-8"c);
// Fehler prüfen
if (S3LastStatus(s3) != 200) {
Print("Upload-Fehler: "c);
PrintLn(S3LastError(s3));
}
}
**Content-Types (Auswahl):**
^ Content-Type ^ Typische Erweiterung ^
| ''application/pdf'' | .pdf |
| ''application/json'' | .json |
| ''application/octet-stream'' | .bin, .tar, .gz (Fallback für alles) |
| ''text/plain; charset=utf-8'' | .txt |
| ''text/html; charset=utf-8'' | .html |
| ''image/jpeg'' | .jpg, .jpeg |
| ''image/png'' | .png |
| ''image/webp'' | .webp |
| ''video/mp4'' | .mp4 |
| ''application/zip'' | .zip |
==== Herunterladen ====
fn Download(s3: S3Client): void {
// In Datei speichern (empfohlen für große Objekte)
var ok: int64 := S3GetObjectFile(s3, "mein-bucket"c, "bericht.pdf"c,
"/tmp/download.pdf"c);
if (ok == 0) {
PrintLn("Download fehlgeschlagen"c);
return;
}
// In Speicherpuffer (für kleine Objekte / direkte Verarbeitung)
var buf: int64 := S3GetObject(s3, "mein-bucket"c, "config.json"c);
if (buf != 0) {
// buf zeigt auf allokierten Puffer mit Dateiinhalt
Print(buf as pchar);
free(buf, S3GetObjectSize(s3, "mein-bucket"c, "config.json"c) + 1);
}
}
==== Teilweise herunterladen (Range-Download) ====
Nützlich für Logs, CSV-Header oder wenn nur die ersten N Bytes gebraucht werden:
// Nur die ersten 4096 Bytes (z.B. CSV-Header lesen)
var gelesen: int64 := alloc(8);
var header: int64 := S3GetObjectRange(s3, "mein-bucket"c, "data.csv"c, 0, 4095, gelesen);
if (header != 0) {
// header: allokierter Puffer mit genau 4096 Bytes
Print(header as pchar);
free(header, 4096);
}
// Byte-Range innerhalb einer großen Datei (z.B. Video-Seekpoint)
var chunkLen: int64 := alloc(8);
var chunk: int64 := S3GetObjectRange(s3, "mein-bucket"c, "video.mp4"c,
1048576, // Start: 1 MB
1048576, // Laenge: 1 MB
chunkLen); // tatsaechlich gelesen
==== Löschen ====
// Einzelnes Objekt
var ok: int64 := S3DeleteObject(s3, "mein-bucket"c, "alt/datei.pdf"c);
// Mehrere Objekte in einem Request (effizienter als Einzelaufrufe)
var keys: array := ["alt/a.pdf"c, "alt/b.pdf"c, "alt/c.pdf"c];
var count: int64 := 3;
S3DeleteObjects(s3, "mein-bucket"c, keys as int64, count);
==== Metadaten ohne Download (HEAD) ====
// Existenzprüfung ohne Datei herunterladen
if (S3ObjectExists(s3, "mein-bucket"c, "bericht.pdf"c) == 0) {
PrintLn("Datei nicht vorhanden"c);
}
// Vollständige Metadaten
var meta: S3ObjectMeta := S3HeadObject(s3, "mein-bucket"c, "bericht.pdf"c);
Print("Größe: "c); PrintLn(IntToStr(meta.size));
Print("Content-Type: "c); PrintLn(meta.contentType as pchar);
Print("ETag: "c); PrintLn(meta.etag as pchar);
Print("Größe in Bytes: "c); PrintLn(IntToStr(S3GetObjectSize(s3, "mein-bucket"c, "bericht.pdf"c)));
// Nur Größe
var groesse: int64 := S3GetObjectSize(s3, "mein-bucket"c, "video.mp4"c);
PrintLn(IntToStr(groesse));
==== Kopieren (server-seitig) ====
// Kopiert ohne lokalen Download — S3 macht alles serverseitig
S3CopyObject(s3, "quell-bucket"c, "original.pdf"c,
"ziel-bucket"c, "archiv/2026/original.pdf"c);
----
===== Listing und Pagination =====
S3 liefert maximal 1000 Objekte pro Request. Bei größeren Buckets muss paginiert werden.
==== Einfaches Listing (bis 1000 Objekte) ====
fn ListeAnzeigen(s3: S3Client): void {
var list: int64 := S3ListObjects(s3, "mein-bucket"c, ""c, 100);
if (list == 0) { PrintLn("Listing fehlgeschlagen"c); return; }
var i: int64 := 0;
while (i < S3ListCount(list)) {
Print(S3ListGetKey(list, i));
Print(" "c);
PrintLn(IntToStr(S3ListGetSize(list, i)));
i := i + 1;
}
if (S3ListIsTruncated(list) == 1) {
PrintLn("(weitere Seiten vorhanden — S3ListAll verwenden)"c);
}
S3ListFree(list);
}
==== Vollständiges Listing (automatisch paginiert) ====
Für Buckets mit mehr als 1000 Objekten — ''S3ListAll'' paginiert automatisch durch alle Seiten:
fn AllesListen(s3: S3Client): void {
// Alle Objekte mit Prefix "backups/"
var list: int64 := S3ListAll(s3, "mein-bucket"c, "backups/"c);
if (list == 0) { return; }
var total: int64 := S3ListCount(list);
Print("Gesamt: "c); PrintLn(IntToStr(total));
var i: int64 := 0;
while (i < total) {
Print(S3ListGetKey(list, i));
Print(" "c);
PrintLn(IntToStr(S3ListGetSize(list, i)));
i := i + 1;
}
S3ListFree(list);
}
==== Manuelle Pagination (für Streaming-Verarbeitung) ====
Sinnvoll wenn der gesamte Bucket sehr groß ist und die Objekte direkt verarbeitet werden sollen, ohne alles in den Speicher zu laden:
fn PaginierteVerarbeitung(s3: S3Client): void {
var token: int64 := 0; // null = erste Seite
while (1 == 1) {
var list: int64 := S3ListObjectsNext(s3, "mein-bucket"c, "logs/"c, token, 1000);
if (list == 0) { break; }
var i: int64 := 0;
while (i < S3ListCount(list)) {
// Objekt direkt verarbeiten — nicht alles sammeln
var key: pchar := S3ListGetKey(list, i) as pchar;
// ... z.B. herunterladen, analysieren, löschen ...
i := i + 1;
}
if (S3ListIsTruncated(list) == 0) {
S3ListFree(list);
break;
}
// Token für nächste Seite merken (Puffer hält Zeiger — List vorher freigeben!)
var nextToken: pchar := S3ListGetToken(list) as pchar;
// nextToken kopieren bevor ListFree — S3ListGetToken gibt internen Zeiger zurück
var kopie: int64 := alloc(256);
StrCopy(kopie as pchar, nextToken);
token := kopie as pchar;
S3ListFree(list);
}
}
==== Verzeichnis-Simulation mit Delimiter ====
S3 hat keine echten Verzeichnisse, aber mit ''delimiter=''/''' kann man Prefixes wie Ordner behandeln:
// Listet nur die "Verzeichnisse" auf der obersten Ebene, keine tiefer liegenden Objekte
var list: int64 := S3ListWithDelimiter(s3, "mein-bucket"c, ""c, "/"c);
// S3ListGetPrefix gibt die CommonPrefixes zurück (= "Verzeichnisse")
var i: int64 := 0;
while (i < S3ListCount(list)) {
// Reguläre Objekte auf der obersten Ebene
PrintLn(S3ListGetKey(list, i) as pchar);
i := i + 1;
}
// CommonPrefixes = virtuelle Unterordner:
// fotos/ backups/ logs/ usw.
S3ListFree(list);
----
===== Multipart-Upload =====
Für Dateien über **5 GB** ist Multipart-Upload Pflicht. Unter 100 MB ist ''S3PutObject'' / ''S3PutObjectFile'' einfacher. Im Bereich 100 MB – 5 GB je nach Anforderung:
^ Datei-Größe ^ Empfehlung ^
| < 5 MB | ''S3PutObject'' / ''S3PutObjectFile'' |
| 5 MB – 5 GB | ''S3PutObjectMultipart'' (High-Level, einfach) |
| > 5 GB | Manueller Multipart oder ''S3PutObjectMultipart'' |
| > 5 TB | Nicht möglich — S3-Limit |
==== High-Level (empfohlen) ====
import std.cloud.s3;
import std.alloc;
import std.fs;
fn GrosseDateiHochladen(s3: S3Client, localPath: pchar, key: pchar): int64 {
var sz: int64 := FileSize(localPath);
var data: int64 := alloc(sz);
if (data == 0) { return 0; }
ReadFile(localPath, data as pchar, sz);
// Automatisch aufgeteilt in 50-MB-Parts
var ok: int64 := S3PutObjectMultipart(s3, "mein-bucket"c, key,
data, sz,
"application/octet-stream"c,
50000000); // 50 MB pro Part
free(data, sz);
return ok;
}
==== Manueller Multipart (für Streaming / eigene Steuerung) ====
fn ManuellerMultipart(s3: S3Client): void {
var bucket: pchar := "mein-bucket"c;
var key: pchar := "archiv/gross.tar"c;
var PART_SIZE: int64 := 50000000; // 50 MB (Minimum: 5 MB außer letztem Part)
// 1. Upload starten — AWS gibt eine Upload-ID zurück
var uploadId: int64 := S3MultipartBegin(s3, bucket, key, "application/x-tar"c);
if (uploadId == 0) {
PrintLn("Multipart-Start fehlgeschlagen"c);
return;
}
// 2. Parts hochladen (hier simuliert mit fixem Puffer)
var partETags: int64 := alloc(100 * 8); // 100 Zeiger auf ETag-Strings
var partNum: int64 := 1;
// In echtem Code: Datei in PART_SIZE-Stücken lesen
var partData: int64 := alloc(PART_SIZE);
// ... Daten befüllen ...
var etag: int64 := S3MultipartUploadPart(s3, bucket, key,
uploadId, partNum,
partData, PART_SIZE);
poke64(partETags, etag);
free(partData, PART_SIZE);
partNum := partNum + 1;
// 3. Upload abschließen
var ok: int64 := S3MultipartComplete(s3, bucket, key,
uploadId, partETags, partNum - 1);
if (ok == 0) {
// Bei Fehler: Upload abbrechen (sonst entstehen Kosten für die Parts!)
S3MultipartAbort(s3, bucket, key, uploadId);
PrintLn("Multipart-Abschluss fehlgeschlagen — abgebrochen"c);
}
free(partETags, 100 * 8);
free(uploadId, StrLen(uploadId as pchar) + 1);
}
**Wichtig:** Nicht abgeschlossene Multipart-Uploads verursachen Speicherkosten! Immer ''S3MultipartAbort'' aufrufen wenn der Upload fehlschlägt. In AWS kann man Lifecycle-Rules konfigurieren, die unvollständige Uploads nach N Tagen automatisch löschen.
----
===== Presigned URLs =====
Presigned URLs ermöglichen zeitlich befristeten Zugriff auf S3-Objekte **ohne Credentials** — ideal für Browser-Uploads, temporäre Download-Links und öffentliche Freigaben.
import std.cloud.aws.core;
import std.cloud.s3;
fn PresignedBeispiele(s3: S3Client): void {
var bucket: pchar := "mein-bucket"c;
var key: pchar := "report.pdf"c;
// GET-URL: Benutzer kann Datei herunterladen (1 Stunde gültig)
var dlUrl: int64 := S3PresignGet(s3, bucket, key, 3600);
if (dlUrl != 0) {
Print("Download: "c); PrintLn(dlUrl as pchar);
free(dlUrl, StrLen(dlUrl as pchar) + 1);
}
// PUT-URL: Browser-Upload direkt nach S3 (ohne Server als Proxy, 15 Minuten)
var ulUrl: int64 := S3PresignPut(s3, bucket, "uploads/foto.jpg"c, 900);
if (ulUrl != 0) {
Print("Upload-URL: "c); PrintLn(ulUrl as pchar);
free(ulUrl, StrLen(ulUrl as pchar) + 1);
}
// HEAD-URL: Metadaten abrufen (z.B. für Content-Length-Check)
var headUrl: int64 := S3PresignHead(s3, bucket, key, 300);
if (headUrl != 0) {
free(headUrl, StrLen(headUrl as pchar) + 1);
}
// DELETE-URL: Delegiertes Löschen (mit Vorsicht verwenden!)
var delUrl: int64 := S3PresignDelete(s3, bucket, "temp/draft.pdf"c, 60);
if (delUrl != 0) {
free(delUrl, StrLen(delUrl as pchar) + 1);
}
}
**Ablauf "Browser-Upload direkt zu S3":**
Browser Eigener Server S3
│ │ │
│── POST /upload-token ──►│ │
│ │── S3PresignPut ─►│
│ │◄── Presigned URL ─┘
│◄── Upload-URL ──────────┤ │
│ │
│────── PUT {presigned-url} (Datei) ────────►│
│◄───── 200 OK ───────────────────────────────┘
Der Server bekommt die Datei **nie zu sehen** — sie landet direkt in S3. Spart Bandbreite und Serverkosten.
**Ablauf "temporärer Download-Link":**
Server S3
│── S3PresignGet ───►│
│◄── Presigned URL ──┘
│
Server sendet URL an Nutzer/App:
"https://mein-bucket.s3.eu-central-1.amazonaws.com/report.pdf?X-Amz-Signature=..."
URL ist 1 Stunde gültig, danach 403.
----
===== Bucket-Verwaltung =====
// Bucket anlegen (in der mit S3Connect angegebenen Region)
S3BucketCreate(s3, "neuer-bucket"c);
// Existenz prüfen
if (S3BucketExists(s3, "mein-bucket"c) == 0) {
PrintLn("Bucket existiert nicht"c);
}
// Alle Buckets auflisten
var buckets: S3BucketList := S3ListBuckets(s3);
var i: int64 := 0;
while (i < S3BucketListCount(buckets)) {
PrintLn(S3BucketListGetName(buckets, i) as pchar);
i := i + 1;
}
// Bucket löschen (muss leer sein!)
S3BucketDelete(s3, "alter-bucket"c);
----
===== Fehlerbehandlung =====
S3-Funktionen geben bei Fehler ''0'' zurück (bei ''int64''-Ergebnissen). Den HTTP-Statuscode und Fehlertext danach abfragen:
fn MitFehlerbehandlung(s3: S3Client): void {
var ok: int64 := S3PutObjectFile(s3, "mein-bucket"c, "datei.pdf"c,
"/tmp/datei.pdf"c, "application/pdf"c);
if (ok == 0) {
var status: int64 := S3LastStatus(s3);
var errText: pchar := S3LastError(s3) as pchar;
var errCode: pchar := S3LastErrorCode(s3) as pchar;
Print("HTTP "c); Print(IntToStr(status));
Print(": "c); PrintLn(errCode);
PrintLn(errText);
}
}
**Häufige Fehlercodes:**
^ HTTP ^ S3-Fehlercode ^ Bedeutung ^ Lösung ^
| 403 | ''AccessDenied'' | Fehlende Berechtigung | IAM-Policy / Bucket-Policy prüfen |
| 403 | ''InvalidAccessKeyId'' | Access Key ungültig | Credentials prüfen |
| 404 | ''NoSuchBucket'' | Bucket existiert nicht | Bucket-Name und Region prüfen |
| 404 | ''NoSuchKey'' | Objekt nicht vorhanden | Key-Pfad prüfen |
| 409 | ''BucketAlreadyExists'' | Bucket-Name vergeben | Bucket-Namen sind global eindeutig |
| 413 | ''EntityTooLarge'' | Datei > 5 GB bei Einzelupload | Multipart verwenden |
| 503 | ''ServiceUnavailable'' | S3 temporär nicht erreichbar | Retry mit Backoff |
----
===== Provider-Vergleich: AWS S3 / R2 / MinIO =====
^ Kriterium ^ AWS S3 ^ Cloudflare R2 ^ MinIO ^
| Egress-Kosten | ja (pro GB) | **nein** (0,00 USD) | nein (self-hosted) |
| Speicherkosten | $0,023/GB/Monat | $0,015/GB/Monat | eigene Hardware |
| Latenz zu CF Workers | mittel | **sehr niedrig** | — |
| Presigned URLs | ja | ja | ja |
| Multipart-Upload | ja | ja | ja |
| Lyx-Connect | ''S3Connect'' | ''S3ConnectR2'' | ''S3ConnectMinio'' |
| Geeignet für | Standard, AWS-Ökosystem | Statische Assets, CDN-Ursprung | Entwicklung, on-premise |
| API-Unterschied | keine | keine | keine (identische API) |
**Faustregel:**
* **AWS S3** → wenn EC2, Lambda und andere AWS-Dienste ebenfalls genutzt werden
* **Cloudflare R2** → wenn Egress-Kosten relevant sind oder Daten nahe bei CF Workers liegen sollen
* **MinIO** → für lokale Entwicklung oder self-hosted Infrastruktur
----
===== Typische Workflows =====
==== Workflow: Backup-Job ====
import std.cloud.aws.core;
import std.cloud.s3;
import std.alloc;
import std.fs;
import std.os;
fn DatenbankBackup(s3: S3Client, dbDumpPfad: pchar): int64 {
// Key mit Datum (YYYY-MM-DD)
// Datum als Unix-Sekunden — std.datetime ist hier nicht importierbar,
// siehe Kasten unten.
var jetzt: int64 := unix_time();
var key: int64 := alloc(128);
StrConcat(key as pchar, "backups/"c, IntToStr(jetzt));
StrConcat(key as pchar, key as pchar, "/db.dump.gz"c);
var sz: int64 := FileSize(dbDumpPfad);
if (sz == 0) { return 0; }
// > 100 MB → Multipart; kleinere Dumps direkt
var ok: int64;
if (sz > 104857600) {
var data: int64 := alloc(sz);
ReadFile(dbDumpPfad, data as pchar, sz);
ok := S3PutObjectMultipart(s3, "backups"c, key, data, sz,
"application/gzip"c, 50000000);
free(data, sz);
} else {
ok := S3PutObjectFile(s3, "backups"c, key, dbDumpPfad,
"application/gzip"c);
}
free(key, 128);
return ok;
}
==== Workflow: Temporärer Download-Link ====
fn DownloadLinkErzeugen(s3: S3Client, key: pchar, ablaufSek: int64): int64 {
var url: int64 := S3PresignGet(s3, "mein-bucket"c, key, ablaufSek);
if (url == 0) {
Print("Presign fehlgeschlagen: "c);
PrintLn(S3LastError(s3));
}
// Caller übernimmt Besitz — mit free() freigeben
return url;
}
==== Workflow: Alte Objekte aufräumen ====
fn AlteLogsLoeschen(s3: S3Client, prefix: pchar, maxAlterTage: int64): int64 {
var list: int64 := S3ListAll(s3, "log-bucket"c, prefix);
if (list == 0) { return 0; }
var grenze: int64 := unix_time() - (maxAlterTage * 86400);
var grenzDatum: pchar := "2026-01-01"c; // im echten Code aus `grenze` gebildet
var geloescht: int64 := 0;
var i: int64 := 0;
while (i < S3ListCount(list)) {
var mtime: pchar := S3ListGetModified(list, i) as pchar;
// S3 liefert ISO-8601; ohne std.datetime hier nur die ersten
// zehn Zeichen als Datum vergleichen (YYYY-MM-DD sortiert lexikografisch).
if (StrCmp(mtime, grenzDatum) < 0) {
var key: pchar := S3ListGetKey(list, i) as pchar;
S3DeleteObject(s3, "log-bucket"c, key);
geloescht := geloescht + 1;
}
i := i + 1;
}
S3ListFree(list);
Print("Gelöscht: "c); PrintLn(IntToStr(geloescht));
return geloescht;
}
==== Workflow: Datei-Synchronisation (Existenzprüfung vor Upload) ====
fn SyncDatei(s3: S3Client, localPath: pchar, bucket: pchar, key: pchar): int64 {
// Nur hochladen wenn Größe unterschiedlich (einfacher ETag-Vergleich fehlt hier)
if (S3ObjectExists(s3, bucket, key) == 1) {
var remoteSize: int64 := S3GetObjectSize(s3, bucket, key);
var localSize: int64 := FileSize(localPath);
if (remoteSize == localSize) {
PrintLn("Datei bereits aktuell"c);
return 1;
}
}
return S3PutObjectFile(s3, bucket, key, localPath, "application/octet-stream"c);
}
----
===== Speicherverwaltung =====
Funktionen, die Daten aus S3 zurückgeben, allokieren Speicher. Der Aufrufer ist für die Freigabe verantwortlich:
^ Funktion ^ Rückgabe ^ Freigabe ^
| ''S3GetObject'' | allokierter Puffer | ''free(buf, size + 1)'' |
| ''S3GetObjectRange'' | allokierter Puffer | ''free(buf, rangeLen + 1)'' |
| ''S3PresignGet'' / ''Put'' / … | allokierter URL-String | ''free(url, StrLen+1)'' |
| ''S3LastError'' / ''S3LastErrorCode'' | interner Puffer (nicht freigeben!) | — |
| ''S3ListAll'' / ''S3ListObjects'' | Listing-Handle | ''S3ListFree(list)'' |
| ''S3GetETag'' | allokierter String | ''free(etag, len+1)'' |
| ''S3GetObjectSize'' | ''int64'' (kein Speicher) | — |
**Faustregel:** Wenn die Funktion einen Puffer ''> 0'' zurückgibt, gehört er dem Aufrufer.
----
===== Weiterführend =====
* [[lyx_-_programmiersprache:units:cloud:aws|std.cloud.s3 — vollständige Unit-Referenz (alle 54 Funktionen)]]
* [[lyx_-_programmiersprache:guides:cloud|Cloud-Guide — AWS, DigitalOcean, Cloudflare im Überblick]]
* [[lyx_-_programmiersprache:units:cloud:cf|std.cloud.cf.r2 — R2 via native CF-API]] (Alternative zu S3ConnectR2)
* [[lyx_-_programmiersprache:guides:welche-unit|Welche Unit? — Entscheidungshilfe]]
Letzte Aktualisierung: 2026-06-12