Services

Dropbox

Dropbox over its HTTP API: uploads in one request or by upload sessions that recover a lost answer, downloads, folders listed with cursors, shared links made or found again, a refresh token traded for short-lived tokens, and 429s waited out as Retry-After says.

Version 1.0.0 · 12 functions · 3 crates

#files#storage#oauth#uploads

Functions

What use dropbox.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use dropbox_upload, dropbox_upload_file from dropbox (Quillo 0.6).

dropbox_upload(path: str, text: str, mode: str) → anyor an error

Uploads text to path in one request (at most 150 MB). mode is "add" (the default; a name in use is an error), "overwrite", or "update:<rev>", which fails if the file has changed. Gives its metadata: name, path_display, rev, size, content_hash.

dropbox_upload_file(local: str, path: str, mode: str, chunk_kb: int) → anyor an error

Uploads a local file: in one request when it is small, by an upload session in pieces of chunk_kb (0 for 4 MiB; Dropbox wants multiples of 4 MiB, save the last) when it is not. If Dropbox reports a different offset after a lost answer, it carries on from there. Gives its metadata, with pieces.

dropbox_download(path: str) → anyor an error

A file's content as text, with the metadata Dropbox sends in a header: { text, metadata }.

dropbox_download_file(path: str, local: str) → anyor an error

Saves a file to a local path, and gives its metadata.

dropbox_list(path: str, options: any) → anyor an error

One page of a folder, "" being the root. options: recursive, limit, include_deleted. Gives { entries, cursor, more }, each entry with .tag file, folder or deleted. Keep cursor to ask later what has changed.

dropbox_list_continue(cursor: str) → anyor an error

The next page after a cursor, or what has changed since it.

dropbox_list_all(path: str, recursive: bool, max: int) → anyor an error

Every entry of a folder, page after page, up to max: { entries, cursor, complete }.

dropbox_metadata(path: str) → anyor an error

A file or folder's metadata, or null when there is none at that path.

dropbox_create_folder(path: str) → boolor an error

Makes a folder; false when it was there.

dropbox_move(from: str, to: str) → anyor an error

Moves or renames; gives the new metadata.

dropbox_delete(path: str) → boolor an error

Deletes a file or folder; false when there was none.

Get it

$ quillo install dropbox               # dropbox.qwer into this folder$ quillo install dropbox --example     # and its example, dropbox.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/dropbox.qwer$ curl -O https://quillolang.org/plugins/files/dropbox.qwe

Then, in your program, all of it or only what you call:

main.qwe
use dropbox.qweruse dropbox_upload, dropbox_upload_file from dropbox      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
ureq = "2"base64 = "0.22"serde_json = { version = "1", features = ["preserve_order"] }

Example

dropbox.qwe
// Files in Dropbox: small uploads, a big one by an upload session, folders listed by cursor, shared// links, and a name with accents that must travel in a header.////   DROPBOX_REFRESH_TOKEN=... DROPBOX_APP_KEY=... DROPBOX_APP_SECRET=... quillo run dropbox.qwe//   then open /upload, /big, /list, /links, /files and /broken//// Or DROPBOX_ACCESS_TOKEN=... for a token from the app console.use "dropbox.qwer"route GET /upload {    let one = dropbox_upload("/reports/año 2026 – résumé.txt", "first text", "add")?    let two = dropbox_upload("/reports/note.txt", "version one", "add")?    let three = dropbox_upload("/reports/note.txt", "version two", "update:" + two.rev)?    {        name: one.name,        size: two.size,        again: dropbox_upload("/reports/note.txt", "x", "add").error,        stale: dropbox_upload("/reports/note.txt", "x", "update:" + two.rev).error,        text: dropbox_download("/reports/note.txt")?.text,        accents: dropbox_download("/reports/año 2026 – résumé.txt")?.metadata.name,        rev_changed: three.rev != two.rev,    }}// Bigger than a piece, so it goes through start, append and finish: this plugin's own source, in// pieces of 8 KB (a real upload takes multiples of 4 MiB, the default).route GET /big {    let r = dropbox_upload_file("dropbox.qwer", "/backups/big.bin", "overwrite", 8)?    { in_pieces: r.pieces > 1, same: dropbox_download("/backups/big.bin")?.text == read_file("dropbox.qwer")? }}route GET /list {    let names = (d) => d.entries | map((e) => e.name) | sort    let first = dropbox_list("/reports", { limit: 1 })?    let next = dropbox_list_continue(first.cursor)?    let all = dropbox_list_all("", true, 100)?    let cut = dropbox_list_all("", true, 2)?    { first: len(first.entries), more: first.more, next: len(next.entries), all: names(all), complete: all.complete, cut: cut.complete }}route GET /links {    let made = dropbox_shared_link("/reports/note.txt", { requested_visibility: "public" })?    let again = dropbox_shared_link("/reports/note.txt", null)?    { url: made.url, new: made.new, same: again.url == made.url, second_new: again.new }}route GET /files {    let folder = dropbox_create_folder("/archive")?    let moved = dropbox_move("/reports/note.txt", "/archive/note.txt")?    {        folder: folder,        folder_again: dropbox_create_folder("/archive")?,        moved: moved.path_display,        old: dropbox_metadata("/reports/note.txt")?,        new: dropbox_metadata("/archive/note.txt")?.name,        deleted: dropbox_delete("/archive/note.txt")?,        deleted_again: dropbox_delete("/archive/note.txt")?,    }}route GET /broken = {    missing: dropbox_download("/nothing.txt").error,    bad_path: dropbox_upload("no-slash.txt", "x", "add").error,    bad_mode: dropbox_upload("/a.txt", "x", "replace").error,    outside: dropbox_list("/does/not/exist", null).error,}
$ quillo run dropbox.qwe$ curl 'localhost:8080/upload'$ curl 'localhost:8080/big'$ curl 'localhost:8080/list'$ curl 'localhost:8080/links'$ curl 'localhost:8080/files'$ curl 'localhost:8080/broken'

It talks to the Redis that REDIS_URL names, else Redis on this machine. The site's test runs it against a stand-in.

Source

dropbox.qwer
//! Dropbox's HTTP API: files uploaded in one request or by upload sessions for big ones, downloads, folders listed with cursors, shared links made or found again, metadata, moves and deletes, a refresh token traded for short-lived access tokens, and 429s waited out as Retry-After says.//! version: 1.0.0//!//! [dependencies]//! ureq = "2"//! base64 = "0.22"//! serde_json = { version = "1", features = ["preserve_order"] }use base64::Engine;use serde_json::{json, Value};use std::io::{Read, Seek, SeekFrom};use std::sync::Mutex;use std::time::Duration;// DROPBOX_API_URL and DROPBOX_CONTENT_URL are the two hosts (https://api.dropboxapi.com and// https://content.dropboxapi.com when not set). DROPBOX_ACCESS_TOKEN is a token; or give// DROPBOX_REFRESH_TOKEN with DROPBOX_APP_KEY and DROPBOX_APP_SECRET, and short-lived tokens are// fetched as they are needed.const SINGLE_LIMIT: usize = 150 * 1024 * 1024;const CHUNK: usize = 4 * 1024 * 1024;fn env(k: &str) -> Option<String> {    std::env::var(k).ok().filter(|v| !v.is_empty())}fn api() -> String {    env("DROPBOX_API_URL").unwrap_or_else(|| "https://api.dropboxapi.com".into()).trim_end_matches('/').to_string()}fn content() -> String {    env("DROPBOX_CONTENT_URL").unwrap_or_else(|| "https://content.dropboxapi.com".into()).trim_end_matches('/').to_string()}fn now() -> i64 {    std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs() as i64)}static TOKEN: Mutex<Option<(String, i64)>> = Mutex::new(None);fn fresh_token() -> Result<String, String> {    let (r, k, s) = (env("DROPBOX_REFRESH_TOKEN"), env("DROPBOX_APP_KEY"), env("DROPBOX_APP_SECRET"));    let (Some(refresh), Some(key), Some(secret)) = (r, k, s) else {        return Err("set DROPBOX_ACCESS_TOKEN, or DROPBOX_REFRESH_TOKEN with DROPBOX_APP_KEY and DROPBOX_APP_SECRET".into());    };    let auth = base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", key, secret));    let res = ureq::post(&format!("{}/oauth2/token", api())).timeout(Duration::from_secs(20)).set("Authorization", &format!("Basic {}", auth)).send_form(&[("grant_type", "refresh_token"), ("refresh_token", &refresh)]);    let text = match res {        Ok(r) => r.into_string().map_err(|e| e.to_string())?,        Err(ureq::Error::Status(c, r)) => {            let b = r.into_string().unwrap_or_default();            let v: Value = serde_json::from_str(&b).unwrap_or(Value::Null);            return Err(format!("Dropbox refused the refresh token ({}): {} {}", c, v["error"].as_str().unwrap_or(""), v["error_description"].as_str().unwrap_or(&b)));        }        Err(e) => return Err(format!("cannot reach Dropbox at {}: {}", api(), e)),    };    let v: Value = serde_json::from_str(&text).map_err(|e| e.to_string())?;    let tok = v["access_token"].as_str().ok_or("Dropbox gave no access_token")?.to_string();    *TOKEN.lock().unwrap_or_else(|e| e.into_inner()) = Some((tok.clone(), now() + v["expires_in"].as_i64().unwrap_or(14400)));    Ok(tok)}fn token() -> Result<String, String> {    if env("DROPBOX_REFRESH_TOKEN").is_none() {        if let Some(t) = env("DROPBOX_ACCESS_TOKEN") {            return Ok(t);        }    }    if let Some((t, until)) = TOKEN.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {        if *until > now() + 60 {            return Ok(t.clone());        }    }    fresh_token()}/// JSON for the Dropbox-API-Arg header: everything past ASCII as \uXXXX, as a header carries only ASCII.fn header_json(v: &Value) -> String {    let mut o = String::new();    for c in v.to_string().chars() {        if (c as u32) < 0x7f {            o.push(c);        } else {            let mut b = [0u16; 2];            for u in c.encode_utf16(&mut b) {                o.push_str(&format!("\\u{:04x}", u));            }        }    }    o}enum Body<'a> {    None,    Json(&'a Value),    Bytes(&'a [u8]),}struct Reply {    result: Option<String>,    bytes: Vec<u8>,}/// One call: waits out 429 and 503, and when an access token has expired fetches a new one and repeats.fn call(url: &str, arg: Option<&Value>, body: Body) -> Result<Result<Reply, (u16, Value)>, String> {    let agent = ureq::AgentBuilder::new().timeout(Duration::from_secs(300)).build();    let mut refreshed = false;    for attempt in 0..6 {        let mut req = agent.post(url).set("Authorization", &format!("Bearer {}", token()?));        if let Some(a) = arg {            req = req.set("Dropbox-API-Arg", &header_json(a));        }        let res = match &body {            Body::None if arg.is_some() => req.set("Content-Type", "application/octet-stream").send_bytes(b""),            Body::None => req.call(),            Body::Json(v) => req.set("Content-Type", "application/json").send_string(&v.to_string()),            Body::Bytes(b) => req.set("Content-Type", "application/octet-stream").send_bytes(b),        };        match res {            Ok(r) => {                let result = r.header("Dropbox-API-Result").map(str::to_string);                let mut bytes = Vec::new();                r.into_reader().read_to_end(&mut bytes).map_err(|e| e.to_string())?;                return Ok(Ok(Reply { result, bytes }));            }            Err(ureq::Error::Status(c, r)) if (c == 429 || c == 503) && attempt < 5 => {                let wait = r.header("Retry-After").and_then(|s| s.parse::<u64>().ok()).unwrap_or(1 << attempt);                std::thread::sleep(Duration::from_secs(wait.min(60)));            }            Err(ureq::Error::Status(c, r)) => {                let text = r.into_string().unwrap_or_default();                let v: Value = serde_json::from_str(&text).unwrap_or(json!({ "error_summary": text }));                let expired = c == 401 && v["error_summary"].as_str().map_or(false, |s| s.starts_with("expired_access_token"));                if expired && !refreshed && env("DROPBOX_REFRESH_TOKEN").is_some() {                    refreshed = true;                    *TOKEN.lock().unwrap_or_else(|e| e.into_inner()) = None;                    continue;                }                return Ok(Err((c, v)));            }            Err(e) => return Err(format!("cannot reach Dropbox: {}", e)),        }    }    Err("Dropbox stayed busy through six tries".into())}fn summary(c: u16, v: &Value) -> String {    format!("Dropbox answered {}: {}", c, v["error_summary"].as_str().unwrap_or(&v.to_string()).trim_end_matches('/').trim_end_matches(|x: char| x == '.' || x.is_ascii_digit()).trim_end_matches('/'))}fn rpc_raw(route: &str, body: &Value) -> Result<Result<Value, (u16, Value)>, String> {    Ok(match call(&format!("{}/2/{}", api(), route), None, Body::Json(body))? {        Ok(r) => Ok(serde_json::from_slice(&r.bytes).unwrap_or(Value::Null)),        Err(e) => Err(e),    })}fn rpc(route: &str, body: &Value) -> Result<Value, String> {    rpc_raw(route, body)?.map_err(|(c, v)| summary(c, &v))}fn check_path(p: &str) -> Result<(), String> {    if !(p.is_empty() || p.starts_with('/') || p.starts_with("id:") || p.starts_with("rev:") || p.starts_with("ns:")) {        return Err(format!("{:?} is not a Dropbox path: it starts with a /, or is an id:", p));    }    if p.len() > 1 && p.ends_with('/') {        return Err(format!("{:?}: a Dropbox path does not end in /", p));    }    Ok(())}fn mode_of(mode: &str) -> Result<Value, String> {    Ok(match mode {        "" | "add" => json!("add"),        "overwrite" => json!("overwrite"),        m if m.starts_with("update:") => json!({ ".tag": "update", "update": &m[7..] }),        m => return Err(format!("{:?} is not a write mode: add, overwrite, or update:<rev>", m)),    })}fn result_of(r: Reply) -> Value {    if !r.bytes.is_empty() {        if let Ok(v) = serde_json::from_slice(&r.bytes) {            return v;        }    }    r.result.and_then(|t| serde_json::from_str(&t).ok()).unwrap_or(Value::Null)}/// Uploads text to `path` in one request (at most 150 MB). `mode` is "add" (the default; a name in use is an error), "overwrite", or "update:<rev>", which fails if the file has changed. Gives its metadata: `name`, `path_display`, `rev`, `size`, `content_hash`.pub fn dropbox_upload(path: &str, text: &str, mode: &str) -> Result<Value, String> {    check_path(path)?;    if text.len() > SINGLE_LIMIT {        return Err("over 150 MB: use dropbox_upload_file, which uses an upload session".into());    }    let arg = json!({ "path": path, "mode": mode_of(mode)?, "autorename": false, "mute": true });    match call(&format!("{}/2/files/upload", content()), Some(&arg), Body::Bytes(text.as_bytes()))? {        Ok(r) => Ok(result_of(r)),        Err((c, v)) => Err(summary(c, &v)),    }}/// Uploads a local file: in one request when it is small, by an upload session in pieces of `chunk_kb` (0 for 4 MiB; Dropbox wants multiples of 4 MiB, save the last) when it is not. If Dropbox reports a different offset after a lost answer, it carries on from there. Gives its metadata, with `pieces`.pub fn dropbox_upload_file(local: &str, path: &str, mode: &str, chunk_kb: i64) -> Result<Value, String> {    check_path(path)?;    let mut f = std::fs::File::open(local).map_err(|e| format!("{}: {}", local, e))?;    let size = f.metadata().map_err(|e| e.to_string())?.len();    let chunk = if chunk_kb > 0 { chunk_kb as usize * 1024 } else { CHUNK };    let commit = json!({ "path": path, "mode": mode_of(mode)?, "autorename": false, "mute": true });    let mut buf = vec![0u8; chunk];    if size as usize <= chunk {        let mut all = Vec::with_capacity(size as usize);        f.read_to_end(&mut all).map_err(|e| e.to_string())?;        return match call(&format!("{}/2/files/upload", content()), Some(&commit), Body::Bytes(&all))? {            Ok(r) => {                let mut v = result_of(r);                v["pieces"] = json!(1);                Ok(v)            }            Err((c, v)) => Err(summary(c, &v)),        };    }    let read = |f: &mut std::fs::File, buf: &mut Vec<u8>| -> Result<usize, String> {        let mut got = 0;        while got < buf.len() {            let n = f.read(&mut buf[got..]).map_err(|e| e.to_string())?;            if n == 0 {                break;            }            got += n;        }        Ok(got)    };    let n = read(&mut f, &mut buf)?;    let start = call(&format!("{}/2/files/upload_session/start", content()), Some(&json!({ "close": false })), Body::Bytes(&buf[..n]))?.map_err(|(c, v)| summary(c, &v))?;    let id = result_of(start)["session_id"].as_str().ok_or("Dropbox gave no session_id")?.to_string();    let mut offset = n as u64;    let mut pieces = 1;    let mut retries = 0;    while offset < size {        f.seek(SeekFrom::Start(offset)).map_err(|e| e.to_string())?;        let n = read(&mut f, &mut buf)?;        let last = offset + n as u64 >= size;        let cursor = json!({ "session_id": id, "offset": offset });        let (url, arg) = if last {            (format!("{}/2/files/upload_session/finish", content()), json!({ "cursor": cursor, "commit": commit }))        } else {            (format!("{}/2/files/upload_session/append_v2", content()), json!({ "cursor": cursor, "close": false }))        };        match call(&url, Some(&arg), Body::Bytes(&buf[..n]))? {            Ok(r) => {                pieces += 1;                if last {                    let mut v = result_of(r);                    v["pieces"] = json!(pieces);                    return Ok(v);                }                offset += n as u64;            }            // The last append arrived though its answer was lost, or the reverse: Dropbox says where it is.            Err((409, v)) if v["error"]["correct_offset"].is_u64() || v["error"]["lookup_failed"]["correct_offset"].is_u64() => {                retries += 1;                if retries > 3 {                    return Err(summary(409, &v));                }                offset = v["error"]["correct_offset"].as_u64().or(v["error"]["lookup_failed"]["correct_offset"].as_u64()).unwrap_or(offset);            }            Err((c, v)) => return Err(summary(c, &v)),        }    }    Err("the file ended before the session finished".into())}/// A file's content as text, with the metadata Dropbox sends in a header: `{ text, metadata }`.pub fn dropbox_download(path: &str) -> Result<Value, String> {    check_path(path)?;    match call(&format!("{}/2/files/download", content()), Some(&json!({ "path": path })), Body::None)? {        Ok(r) => {            let meta: Value = r.result.as_deref().and_then(|t| serde_json::from_str(t).ok()).unwrap_or(Value::Null);            Ok(json!({ "text": String::from_utf8_lossy(&r.bytes), "metadata": meta }))        }        Err((c, v)) => Err(summary(c, &v)),    }}/// Saves a file to a local path, and gives its metadata.pub fn dropbox_download_file(path: &str, local: &str) -> Result<Value, String> {    check_path(path)?;    match call(&format!("{}/2/files/download", content()), Some(&json!({ "path": path })), Body::None)? {        Ok(r) => {            std::fs::write(local, &r.bytes).map_err(|e| format!("{}: {}", local, e))?;            Ok(r.result.as_deref().and_then(|t| serde_json::from_str(t).ok()).unwrap_or(Value::Null))        }        Err((c, v)) => Err(summary(c, &v)),    }}fn entries(r: &Value) -> Value {    json!({ "entries": r["entries"], "cursor": r["cursor"], "more": r["has_more"] })}/// One page of a folder, "" being the root. `options`: `recursive`, `limit`, `include_deleted`. Gives `{ entries, cursor, more }`, each entry with `.tag` file, folder or deleted. Keep `cursor` to ask later what has changed.pub fn dropbox_list(path: &str, options: serde_json::Value) -> Result<Value, String> {    check_path(path)?;    let mut b = json!({ "path": path, "recursive": options["recursive"] == true, "include_deleted": options["include_deleted"] == true });    if let Some(l) = options["limit"].as_i64() {        b["limit"] = json!(l);    }    Ok(entries(&rpc("files/list_folder", &b)?))}/// The next page after a `cursor`, or what has changed since it.pub fn dropbox_list_continue(cursor: &str) -> Result<Value, String> {    Ok(entries(&rpc("files/list_folder/continue", &json!({ "cursor": cursor }))?))}/// Every entry of a folder, page after page, up to `max`: `{ entries, cursor, complete }`.pub fn dropbox_list_all(path: &str, recursive: bool, max: i64) -> Result<Value, String> {    let mut p = dropbox_list(path, json!({ "recursive": recursive }))?;    let mut out: Vec<Value> = vec![];    loop {        out.extend(p["entries"].as_array().cloned().unwrap_or_default());        if out.len() as i64 >= max {            out.truncate(max.max(0) as usize);            return Ok(json!({ "entries": out, "cursor": p["cursor"], "complete": p["more"] != true }));        }        if p["more"] != true {            return Ok(json!({ "entries": out, "cursor": p["cursor"], "complete": true }));        }        p = dropbox_list_continue(p["cursor"].as_str().ok_or("no cursor")?)?;    }}/// A file or folder's metadata, or null when there is none at that path.pub fn dropbox_metadata(path: &str) -> Result<Value, String> {    check_path(path)?;    match rpc_raw("files/get_metadata", &json!({ "path": path }))? {        Ok(v) => Ok(v),        Err((409, v)) if v["error_summary"].as_str().map_or(false, |s| s.starts_with("path/not_found")) => Ok(Value::Null),        Err((c, v)) => Err(summary(c, &v)),    }}/// Makes a folder; false when it was there.pub fn dropbox_create_folder(path: &str) -> Result<bool, String> {    check_path(path)?;    match rpc_raw("files/create_folder_v2", &json!({ "path": path, "autorename": false }))? {        Ok(_) => Ok(true),        Err((409, v)) if v["error_summary"].as_str().map_or(false, |s| s.starts_with("path/conflict")) => Ok(false),        Err((c, v)) => Err(summary(c, &v)),    }}/// Moves or renames; gives the new metadata.pub fn dropbox_move(from: &str, to: &str) -> Result<Value, String> {    check_path(from)?;    check_path(to)?;    Ok(rpc("files/move_v2", &json!({ "from_path": from, "to_path": to }))?["metadata"].clone())}/// Deletes a file or folder; false when there was none.pub fn dropbox_delete(path: &str) -> Result<bool, String> {    check_path(path)?;    match rpc_raw("files/delete_v2", &json!({ "path": path }))? {        Ok(_) => Ok(true),        Err((409, v)) if v["error_summary"].as_str().map_or(false, |s| s.starts_with("path_lookup/not_found")) => Ok(false),        Err((c, v)) => Err(summary(c, &v)),    }}/// A shared link for a path. Dropbox refuses to make a second one, so when one exists that one is given. `options` are its settings, like `{ requested_visibility: "public", expires: "2027-01-01T00:00:00Z" }`, for a new link. Gives `{ url, name, path, new, expires }`.pub fn dropbox_shared_link(path: &str, options: serde_json::Value) -> Result<Value, String> {    check_path(path)?;    let mut b = json!({ "path": path });    if options.is_object() {        b["settings"] = options;    }    let shape = |m: &Value, new: bool| json!({ "url": m["url"], "name": m["name"], "path": m["path_lower"], "new": new, "expires": m["expires"] });    match rpc_raw("sharing/create_shared_link_with_settings", &b)? {        Ok(m) => Ok(shape(&m, true)),        Err((409, v)) if v["error_summary"].as_str().map_or(false, |s| s.starts_with("shared_link_already_exists")) => {            if let Some(m) = v["error"]["shared_link_already_exists"]["metadata"].as_object() {                return Ok(shape(&Value::Object(m.clone()), false));            }            let l = rpc("sharing/list_shared_links", &json!({ "path": path, "direct_only": true }))?;            l["links"].get(0).map(|m| shape(m, false)).ok_or("Dropbox says a link exists but lists none".to_string())        }        Err((c, v)) => Err(summary(c, &v)),    }}
Azure Blob SAS links azure_sas.qwer · 1.0.0Azure Blob Storage SAS links signed from the account key: browsers upload and download blobs directly, with the permissions and lifetime you choose.uploadsstorageazurefilessigning8 views · 7 downloadsGoogle Cloud Storage links gcs_sign.qwer · 1.0.0Google Cloud Storage V4 signed URLs from a service-account key: browsers upload and download directly, signed without a network call.uploadsstoragegcpfilessigning6 views · 8 downloadsS3 links s3.qwer · 1.0.0Signed upload and download links for S3, R2, MinIO and the like: browsers move the bytes, not your server.uploadsstorages3r2files8 views · 8 downloadsContent-addressed files blob_store.qwer · 1.0.0Uploads kept in a folder by their SHA-256, as Git keeps objects: the same file stored once, names pointing at contents, atomic writes, every file verified against its hash, and contents nothing names swept away.filesstorageuploadsdedupeintegrity6 views · 7 downloadsGoogle Drive google_drive.qwer · 1.0.0Google Drive's v3 API: files listed with quoted queries and page tokens, uploads by multipart or a resumable session that recovers a lost piece, downloads and Google Doc exports, sharing, and service-account JWT sign-in.filesgoogleuploadsservice-account2 views · 6 downloads3D models models_3d.qwer · 1.0.0STL and OBJ models read: triangles, size, volume, surface area and whether the mesh is closed, a 3D-print quote by weight, and OBJ turned into binary STL.filesuploadsprintingcalculators6 views · 7 downloads
Thread
29Useful? Like it, or add to its thread below.

Thread 0 posts

On Dropbox: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.