DEV Community

Cover image for gdup — a C# CLI for uploading huge files to Google Drive (and surviving interruptions)
Shawn K. Lewis
Shawn K. Lewis

Posted on AI-assisted

gdup — a C# CLI for uploading huge files to Google Drive (and surviving interruptions)

Uploading a 50 GB file to Google Drive through the browser works fine, right up until the Wi‑Fi blips at 87%. Then you start from zero.

So I wrote gdup: a small Windows command-line tool in C# that uploads one large file to Google Drive as fast as a single stream allows, and picks up where it left off when something breaks.

Repo: https://github.com/shawnklewis/GoogleDriveResumableUploader (.NET 10, Windows)

What it does

  • Resumable uploads. Ctrl+C, a crash, or a dropped connection doesn't lose progress. Re-run the same command (or gdup resume <file>) and it continues.
  • Network interface pinning. --via "Ethernet 2" forces the upload onto a specific NIC. Handy when one machine has a fast wired link and a slow default route.
  • Checksum verification, with its own exit code when the hashes don't match.
  • Encrypted credentials. Config is stored DPAPI-encrypted under %APPDATA%\gdup\gdup.conf, in an rclone-style format. You can import an existing Drive remote straight from your rclone.conf.
  • Verifiable releases. GitHub Actions builds, tests and publishes gdup.exe with a SHA-256 checksum and a signed build-provenance attestation.

Usage

gdup config                       # create a remote (bring your own Desktop-app OAuth client)
gdup config import-rclone         # or reuse a Drive remote from rclone.conf
gdup nics                         # list interfaces for --via

gdup upload D:\video\big.mkv gdrive:Backups/2026
gdup upload D:\video\big.mkv gdrive:Backups --via "Ethernet 2" --chunk-size 512M --stall-timeout 90

gdup resume --list
gdup resume D:\video\big.mkv
Enter fullscreen mode Exit fullscreen mode

Exit codes are scriptable: 0 success, 1 usage/config error, 2 upload failed, 3 checksum mismatch.

How Drive's resumable upload protocol works

This is the part that makes the whole thing possible, and it's simpler than it sounds:

  1. Start a session. POST the file's metadata to the upload endpoint with uploadType=resumable. Drive replies with a session URI in the Location header. That URI is the upload. Keep it.
  2. Send chunks. PUT byte ranges to the session URI with a Content-Range: bytes start-end/total header. Every chunk except the last must be a multiple of 256 KiB.
  3. Drive acknowledges progress. For each intermediate chunk it answers 308 Resume Incomplete with a Range header telling you what it has actually stored.
  4. Recover after a failure. Send an empty PUT with Content-Range: bytes */total. Drive tells you how many bytes it has, and you continue from there.
  5. Finish. The final chunk returns 200/201 with the file's metadata.

The key insight: the server, not the client, is the source of truth for progress. As long as you keep the session URI, you can come back later and ask "where were we?"

How gdup builds on that

Sessions survive the process. As soon as Drive hands back a session URI, gdup writes it to a small JSON file under %LOCALAPPDATA%\gdup\sessions. The file name is a SHA-256 of the file's path, size and last-write time plus the destination remote, folder and name:

public static string KeyFor(string fullPath, long size, long mtimeTicks,
                            string remote, string folderId, string name)
{
    var raw = string.Join("|", fullPath.ToUpperInvariant(), size, mtimeTicks, remote, folderId, name);
    return Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(raw)));
}
Enter fullscreen mode Exit fullscreen mode

If the file changes on disk, the key changes, so gdup never resumes a stale session against different bytes. Leftover state for older versions of the same file is cleaned up automatically. Writes go to a .tmp file first and are then moved into place, so a crash mid-save can't leave a half-written state file.

One loop, three outcomes. Every response is interpreted the same way: 200/201 means done, 308 means "continue from this offset", and 404/410 means Drive has thrown the session away. An expired session is dropped and a fresh one is started from byte zero. If that happens three times in a row with no progress, gdup gives up instead of looping forever.

Retries back off with jitter. Transient failures (network errors, timeouts, retryable Drive errors) retry with exponential backoff capped at 64 seconds, half fixed and half random. Local disk read errors get only a few attempts: a USB hiccup can clear on retry, but a bad block won't.

A watchdog for stalls. A connection can hang without ever erroring. A watchdog tracks bytes leaving the socket; if nothing moves for --stall-timeout seconds (default 60), it cancels the chunk and resumes from Drive's committed offset. Once a chunk's body is fully sent, it allows longer (at least five minutes) for Drive's reply, since Drive can take a while to acknowledge a large chunk.

Chunk sizes are validated up front. --chunk-size defaults to 256 MiB and must be a multiple of 256 KiB, up to 4 GiB, so a bad value fails at the command line instead of halfway through an upload.

--via binds the socket. gdup uses a single HttpClient whose SocketsHttpHandler.ConnectCallback binds each socket to the chosen interface's local IP before connecting, and Windows then routes the traffic out of that NIC. Auto-redirect is off, because Drive's 308 Resume Incomplete is not a redirect and must not be followed.

The checksum runs in parallel. The local MD5 is computed alongside the upload, then compared with the md5Checksum Drive reports for the finished file.

Things that surprised me

You can't combine network links for one file. I hoped --via could bond two connections. It can't: Drive accepts only one ordered byte stream per file, so --via chooses a link rather than aggregating them.

The drive.file scope is narrower than you'd think. With the default drive.file scope, gdup can only see folders it created or that were shared with the app. An existing folder it didn't create looks empty, so gdup may create a duplicate instead of reusing it. That's the trade-off for not asking for full Drive access.

Bring your own OAuth client. gdup uses your own Desktop-app OAuth client, so your uploads count against your quota and nobody else's app sits between you and your files.

Try it

Grab gdup.exe from the releases page and verify it:

gh attestation verify gdup.exe --repo shawnklewis/GoogleDriveResumableUploader
Enter fullscreen mode Exit fullscreen mode

The exe isn't code-signed yet, so SmartScreen may warn on first run (More info → Run anyway).


I'm a C#/.NET engineer in Trinidad & Tobago. Most of my work is Blazor, EF Core and SQL Server, plus embedded C# for motion control on a custom 5-axis 3D printer. If you're looking for someone who builds this kind of thing, I'm available for contract work.

Top comments (0)