====== 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