Magpie 🐦

Hex.pm Hexdocs CI Coverage Status License: MIT

Elixir client for the Dropbox API v2, built on Req.

Like the bird, Magpie collects and stashes your things — in your Dropbox.

Installation

def deps do
[
{:magpie, "~> 0.8.0"}
]
end

No configuration is required. Endpoint URLs, retry controls and extra Req options can be configured per client and per Storage operation, with config :magpie, ... as a fallback — see the configuration guide. See the testing guide for isolated offline tests and optional real Dropbox contract checks.

Quick start

# A refresh token keeps the client working indefinitely — Magpie mints
# access tokens as needed (see the OAuth guide)
client =
Magpie.Client.new(
refresh_token: System.fetch_env!("DROPBOX_REFRESH_TOKEN"),
app_key: System.fetch_env!("DROPBOX_APP_KEY"),
app_secret: System.fetch_env!("DROPBOX_APP_SECRET")
)
# For a quick script, a static access token works too (Dropbox expires it in ~4h)
client = Magpie.Client.new("DROPBOX_ACCESS_TOKEN")
alias Magpie.Storage
{:ok, %Magpie.FileMetadata{size: size}} =
Storage.put(client, "/Backup/report.pdf", {:file, "priv/report.pdf"}, verify: true)
{:ok, contents} = Storage.get(client, "/Backup/report.pdf")
{:ok, url} = Storage.url(client, "/Backup/report.pdf")
# Large downloads stream directly to disk instead of living in BEAM memory
{:ok, "tmp/report.pdf"} =
Storage.download(client, "/Backup/report.pdf", "tmp/report.pdf", mkdir_p: true)

Every call returns {:ok, result} on success or an error tuple. Dropbox API errors are %Magpie.Error{} values with the HTTP status, Dropbox's error_summary, full body and request_id; expected transport failures are also returned instead of raising from normal Storage calls. Files, folders and deleted entries come back as Magpie.FileMetadata, Magpie.FolderMetadata and Magpie.DeletedMetadata structs:

for %Magpie.FileMetadata{name: name, size: size, server_modified: at} <- entries do
"#{name}: #{size} bytes, modified #{DateTime.to_date(at)}"
end

Incremental listings and webhooks

{:ok, page} = Magpie.Storage.list_page(client, "", recursive: true, include_deleted: true)
# Apply page.entries durably before saving page.cursor in your own storage.
{:ok, next} = Magpie.Storage.continue_list(client, page.cursor)
# Drain while next.has_more, then reuse the final cursor for future changes.

Magpie.ListPage keeps entries, cursor and continuity together without fetching more pages. Invalidated cursors return Magpie.CursorError requiring an explicit state rebuild. Existing Storage.list and Storage.stream contracts are unchanged. Magpie.Webhook verifies challenges, raw-body signatures and notified accounts; an optional Magpie.Webhook.Plug delegates enqueueing to your application. See the incremental and webhook guide for checkpointing, recovery, Phoenix, background jobs and offline consumer tests.

Features

Runnable examples

These small applications show how Magpie fits into a longer workflow. Each has an offline demo, tests, and commands for running against your own Dropbox app.

Clone this repository and follow each example's README. The examples use the local Magpie checkout and need no credentials for their offline demos.

Documentation

The API reference lives on HexDocs, along with the guides:

Development

The default suite uses Req.Test stubs and a loopback HTTP server; it needs no Dropbox credentials. The optional real-account test is excluded by default.

mix test # run the suite
mix coveralls # run with coverage report

Origin

Magpie started as a fork of sger/elixir_dropbox, which is no longer maintained. It has since been rewritten on top of Req/Jason with a new offline test suite. Credit and thanks to the original Elixir Dropbox contributors.

License

MIT — see LICENSE.