Skip to content

Instantly share code, notes, and snippets.

@vnykmshr
Created May 9, 2014 18:35
Show Gist options
  • Save vnykmshr/89b4663f0df95743f268 to your computer and use it in GitHub Desktop.
Save vnykmshr/89b4663f0df95743f268 to your computer and use it in GitHub Desktop.
Dropbox Uploader Script
#!/usr/bin/env bash
#
# Dropbox Uploader
#
# Copyright (C) 2010-2014 Andrea Fabrizi <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#Default configuration file
CONFIG_FILE=~/.dropbox_uploader
#Default chunk size in Mb for the upload process
#It is recommended to increase this value only if you have enough free space on your /tmp partition
#Lower values may increase the number of http requests
CHUNK_SIZE=4
#Curl location
#If not set, curl will be searched into the $PATH
#CURL_BIN="/usr/bin/curl"
#Default values
TMP_DIR="/tmp"
DEBUG=0
QUIET=0
SHOW_PROGRESSBAR=0
SKIP_EXISTING_FILES=0
ERROR_STATUS=0
#Don't edit these...
API_REQUEST_TOKEN_URL="https://api.dropbox.com/1/oauth/request_token"
API_USER_AUTH_URL="https://www2.dropbox.com/1/oauth/authorize"
API_ACCESS_TOKEN_URL="https://api.dropbox.com/1/oauth/access_token"
API_CHUNKED_UPLOAD_URL="https://api-content.dropbox.com/1/chunked_upload"
API_CHUNKED_UPLOAD_COMMIT_URL="https://api-content.dropbox.com/1/commit_chunked_upload"
API_UPLOAD_URL="https://api-content.dropbox.com/1/files_put"
API_DOWNLOAD_URL="https://api-content.dropbox.com/1/files"
API_DELETE_URL="https://api.dropbox.com/1/fileops/delete"
API_MOVE_URL="https://api.dropbox.com/1/fileops/move"
API_COPY_URL="https://api.dropbox.com/1/fileops/copy"
API_METADATA_URL="https://api.dropbox.com/1/metadata"
API_INFO_URL="https://api.dropbox.com/1/account/info"
API_MKDIR_URL="https://api.dropbox.com/1/fileops/create_folder"
API_SHARES_URL="https://api.dropbox.com/1/shares"
APP_CREATE_URL="https://www2.dropbox.com/developers/apps"
RESPONSE_FILE="$TMP_DIR/du_resp_$RANDOM"
CHUNK_FILE="$TMP_DIR/du_chunk_$RANDOM"
BIN_DEPS="sed basename date grep stat dd mkdir"
VERSION="0.13"
umask 077
#Check the shell
if [ -z "$BASH_VERSION" ]; then
echo -e "Error: this script requires the BASH shell!"
exit 1
fi
shopt -s nullglob #Bash allows filename patterns which match no files to expand to a null string, rather than themselves
shopt -s dotglob #Bash includes filenames beginning with a "." in the results of filename expansion
db_upload_dir "$SRC" "$DST"
#It's a file
elif [[ -e $SRC ]]; then
db_upload_file "$SRC" "$DST"
#Unsupported object...
else
print " > Skipping not regular file \"$SRC\"\n"
fi
}
#Generic upload wrapper around db_chunked_upload_file and db_simple_upload_file
#The final upload function will be choosen based on the file size
#$1 = Local source file
#$2 = Remote destination file
function db_upload_file
{
local FILE_SRC=$(normalize_path "$1")
local FILE_DST=$(normalize_path "$2")
shopt -s nocasematch
#Checking not allowed file names
basefile_dst=$(basename "$FILE_DST")
if [[ $basefile_dst == "thumbs.db" || \
$basefile_dst == "desktop.ini" || \
$basefile_dst == ".ds_store" || \
$basefile_dst == "icon\r" || \
$basefile_dst == ".dropbox" || \
$basefile_dst == ".dropbox.attr" \
]]; then
print " > Skipping not allowed file name \"$FILE_DST\"\n"
return
fi
shopt -u nocasematch
#Checking file size
FILE_SIZE=$(file_size "$FILE_SRC")
#Checking if the file already exists
TYPE=$(db_stat "$FILE_DST")
if [[ $TYPE != "ERR" && $SKIP_EXISTING_FILES == 1 ]]; then
print " > Skipping already existing file \"$FILE_DST\"\n"
return
fi
if (( $FILE_SIZE > 157286000 )); then
#If the file is greater than 150Mb, the chunked_upload API will be used
db_chunked_upload_file "$FILE_SRC" "$FILE_DST"
else
db_simple_upload_file "$FILE_SRC" "$FILE_DST"
fi
}
#Simple file upload
#$1 = Local source file
#$2 = Remote destination file
function db_simple_upload_file
{
local FILE_SRC=$(normalize_path "$1")
local FILE_DST=$(normalize_path "$2")
if [[ $SHOW_PROGRESSBAR == 1 && $QUIET == 0 ]]; then
CURL_PARAMETERS="--progress-bar"
LINE_CR="\n"
else
CURL_PARAMETERS="-s"
LINE_CR=""
fi
print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\"... $LINE_CR"
$CURL_BIN $CURL_ACCEPT_CERTIFICATES $CURL_PARAMETERS -i --globoff -o "$RESPONSE_FILE" --upload-file "$FILE_SRC" "$API_UPLOAD_URL/$ACCESS_LEVEL/$(urlencode "$FILE_DST")?oauth_consumer_key=$APPKEY&oauth_token=$OAUTH_ACCESS_TOKEN&oauth_signature_method=PLAINTEXT&oauth_signature=$APPSECRET%26$OAUTH_ACCESS_TOKEN_SECRET&oauth_timestamp=$(utime)&oauth_nonce=$RANDOM"
check_http_response
#Check
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then
print "DONE\n"
else
print "FAILED\n"
print "An error occurred requesting /upload\n"
ERROR_STATUS=1
fi
}
#Chunked file upload
#$1 = Local source file
#$2 = Remote destination file
function db_chunked_upload_file
{
local FILE_SRC=$(normalize_path "$1")
local FILE_DST=$(normalize_path "$2")
print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\""
local FILE_SIZE=$(file_size "$FILE_SRC")
local OFFSET=0
local UPLOAD_ID=""
local UPLOAD_ERROR=0
local CHUNK_PARAMS=""
#Uploading chunks...
while ([[ $OFFSET != $FILE_SIZE ]]); do
let OFFSET_MB=$OFFSET/1024/1024
#Create the chunk
dd if="$FILE_SRC" of="$CHUNK_FILE" bs=1048576 skip=$OFFSET_MB count=$CHUNK_SIZE 2> /dev/null
#Only for the first request these parameters are not included
if [[ $OFFSET != 0 ]]; then
CHUNK_PARAMS="upload_id=$UPLOAD_ID&offset=$OFFSET"
fi
#Uploading the chunk...
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -s --show-error --globoff -i -o "$RESPONSE_FILE" --upload-file "$CHUNK_FILE" "$API_CHUNKED_UPLOAD_URL?$CHUNK_PARAMS&oauth_consumer_key=$APPKEY&oauth_token=$OAUTH_ACCESS_TOKEN&oauth_signature_method=PLAINTEXT&oauth_signature=$APPSECRET%26$OAUTH_ACCESS_TOKEN_SECRET&oauth_timestamp=$(utime)&oauth_nonce=$RANDOM" 2> /dev/null
check_http_response
#Check
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then
print "."
UPLOAD_ERROR=0
UPLOAD_ID=$(sed -n 's/.*"upload_id": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE")
OFFSET=$(sed -n 's/.*"offset": *\([^}]*\).*/\1/p' "$RESPONSE_FILE")
else
check_http_response
#Check
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then
print "DONE\n"
else
print "FAILED\n"
ERROR_STATUS=1
fi
}
#Create a new directory
#$1 = Remote directory to create
function db_mkdir
{
local DIR_DST=$(normalize_path "$1")
print " > Creating Directory \"$DIR_DST\"... "
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -s --show-error --globoff -i -o "$RESPONSE_FILE" --data "oauth_consumer_key=$APPKEY&oauth_token=$OAUTH_ACCESS_TOKEN&oauth_signature_method=PLAINTEXT&oauth_signature=$APPSECRET%26$OAUTH_ACCESS_TOKEN_SECRET&oauth_timestamp=$(utime)&oauth_nonce=$RANDOM&root=$ACCESS_LEVEL&path=$(urlencode "$DIR_DST")" "$API_MKDIR_URL" 2> /dev/null
check_http_response
#Check
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then
print "DONE\n"
elif grep -q "^HTTP/1.1 403 Forbidden" "$RESPONSE_FILE"; then
print "ALREADY EXISTS\n"
else
print "FAILED\n"
ERROR_STATUS=1
fi
}
#List remote directory
#$1 = Remote directory
function db_list
{
local DIR_DST=$(normalize_path "$1")
print " > Listing \"$DIR_DST\"... "
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -s --show-error --globoff -i -o "$RESPONSE_FILE" "$API_METADATA_URL/$ACCESS_LEVEL/$(urlencode "$DIR_DST")?oauth_consumer_key=$APPKEY&oauth_token=$OAUTH_ACCESS_TOKEN&oauth_signature_method=PLAINTEXT&oauth_signature=$APPSECRET%26$OAUTH_ACCESS_TOKEN_SECRET&oauth_timestamp=$(utime)&oauth_nonce=$RANDOM" 2> /dev/null
check_http_response
#Check
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then
local IS_DIR=$(sed -n 's/^\(.*\)\"contents":.\[.*/\1/p' "$RESPONSE_FILE")
#It's a directory
if [[ $IS_DIR != "" ]]; then
print "DONE\n"
#Extracting directory content [...]
#and replacing "}, {" with "}\n{"
#I don't like this piece of code... but seems to be the only way to do this with SED, writing a portable code...
local DIR_CONTENT=$(sed -n 's/.*: \[{\(.*\)/\1/p' "$RESPONSE_FILE" | sed 's/}, *{/}\
{/g')
#Converting escaped quotes to unicode format and extracting files and subfolders
echo "$DIR_CONTENT" | sed 's/\\"/\\u0022/' | sed -n 's/.*"bytes": *\([0-9]*\),.*"path": *"\([^"]*\)",.*"is_dir": *\([^"]*\),.*/\2:\3;\1/p' > $RESPONSE_FILE
#Looking for the biggest file size
#to calculate the padding to use
local padding=0
while read -r line; do
local FILE=${line%:*}
local META=${line##*:}
local SIZE=${META#*;}
if (( ${#SIZE} > $padding )); then
padding=${#SIZE}
fi
done < $RESPONSE_FILE
#For each entry...
while read -r line; do
local FILE=${line%:*}
local META=${line##*:}
local TYPE=${META%;*}
local SIZE=${META#*;}
#Removing unneeded /
FILE=${FILE##*/}
if [[ $TYPE == "false" ]]; then
TYPE="F"
else
TYPE="D"
fi
FILE=$(echo -e "$FILE")
printf " [$TYPE] %-${padding}s %s\n" "$SIZE" "$FILE"
done < $RESPONSE_FILE
#It's a file
else
print "FAILED: $DIR_DST is not a directory!\n"
ERROR_STATUS=1
fi
else
print "FAILED\n"
ERROR_STATUS=1
fi
}
#Share remote file
#$1 = Remote file
function db_share
{
local FILE_DST=$(normalize_path "$1")
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -s --show-error --globoff -i -o "$RESPONSE_FILE" "$API_SHARES_URL/$ACCESS_LEVEL/$(urlencode "$FILE_DST")?oauth_consumer_key=$APPKEY&oauth_token=$OAUTH_ACCESS_TOKEN&oauth_signature_method=PLAINTEXT&oauth_signature=$APPSECRET%26$OAUTH_ACCESS_TOKEN_SECRET&oauth_timestamp=$(utime)&oauth_nonce=$RANDOM&short_url=false" 2> /dev/null
check_http_response
#Check
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then
print " > Share link: "
echo $(sed -n 's/.*"url": "\([^"]*\).*/\1/p' "$RESPONSE_FILE")
db_copy "/$FILE_SRC" "/$FILE_DST"
;;
mkdir)
if [[ $argnum < 1 ]]; then
usage
fi
DIR_DST=$ARG1
db_mkdir "/$DIR_DST"
;;
list)
DIR_DST=$ARG1
#Checking DIR_DST
if [[ $DIR_DST == "" ]]; then
DIR_DST="/"
fi
db_list "/$DIR_DST"
;;
unlink)
db_unlink
;;
*)
if [[ $COMMAND != "" ]]; then
print "Error: Unknown command: $COMMAND\n\n"
ERROR_STATUS=1
fi
usage
;;
esac
remove_temp_files
exit $ERROR_STATUS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment