File bulk upload

Introduction

Uploading many small files is often slower than what could be achieved because we do not use the whole network bandwidth. Nextcloud has a bulk upload API where you can upload many small files together in order to optimize the use of network bandwidth.

Usage

The API is only available for registered users of your instance. And uses the path: <server>/remote.php/dav/bulk.

Starting a bulk upload

A bulk upload is simply using a request structured as HTTP multipart with related mime type.

Each file is then sent as one HTTP part.

Each file inside an HTTP Part will need the following headers:

  • Content-Length: <file size>

  • Content-Type: <mimetype>

  • X-File-MD5: <md5 checksum>

  • X-File-Mtime: <modification time of file>

  • X-File-Path: <destination file path>

The reply is a json document with the following structure:

{
    "/small file.txt": {
        "error": false,
        "etag": "adb9aa24cbfa8e372c88431d1d99629a"
    }
}

Example of code to upload some test files with bulk upload protocol

#!/bin/bash

NB=$1
SIZE=$2

USER="admin"
PASS="admin"
SERVER="nextcloud.local"
UPLOAD_PATH="/tmp/bulk_upload_request_$(openssl rand --hex 8).txt"
BOUNDARY="boundary_$(openssl rand --hex 8)"
REMOTE_FOLDER="/test"

for ((i=1; i<="$NB"; i++))
do
        file_name=$(openssl rand --hex 8)
        file_local_path="./$file_name.txt"
        file_remote_path="$REMOTE_FOLDER/$file_name.txt"
        head -c "$SIZE" /dev/urandom > "$file_local_path"
        file_mtime=$(stat -c %Y "$file_local_path")
        file_hash=$(md5sum "$file_local_path" | awk '{ print $1 }')
        file_size=$(du -sb "$file_local_path" | awk '{ print $1 }')

        {
                echo -en "--$BOUNDARY\r\n"
                echo -en "X-File-Path: $file_remote_path\r\n"
                echo -en "X-OC-Mtime: $file_mtime\r\n"
                echo -en "X-File-Md5: $file_hash\r\n"
                echo -en "Content-Length: $file_size\r\n"
                echo -en "\r\n" >> "$UPLOAD_PATH"

                cat "$file_local_path"
                echo -en "\r\n" >> "$UPLOAD_PATH"
        } >> "$UPLOAD_PATH"
done

echo -en "--$BOUNDARY--\r\n" >> "$UPLOAD_PATH"

echo "Creating folder /test"
curl \
        -X MKCOL \
        -k \
        "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/test" > /dev/null

echo "Uploading $NB files with total size: $(du -sh "$UPLOAD_PATH" | cut -d '   ' -f1)"
echo "Local file is: $UPLOAD_PATH"
curl \
        -X POST \
        -k \
        --progress-bar \
        --cookie "XDEBUG_PROFILE=true;path=/;" \
        -H "Content-Type: multipart/related; boundary=$BOUNDARY" \
        --data-binary "@$UPLOAD_PATH" \
        "https://$USER:$PASS@$SERVER/remote.php/dav/bulk"