Last active
August 1, 2024 09:25
-
-
Save rwinscot/6a0212e51f6794d19ea7b75724a8e7e5 to your computer and use it in GitHub Desktop.
Bashing the Dropbox API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# | |
# Dropbox Uploader | |
# https://github.com/andreafabrizi/Dropbox-Uploader | |
# | |
# Copyright (C) 2010-2017 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=50 | |
#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 | |
EXCLUDE=() | |
#Don't edit these... | |
API_LONGPOLL_FOLDER="https://notify.dropboxapi.com/2/files/list_folder/longpoll" | |
API_CHUNKED_UPLOAD_START_URL="https://content.dropboxapi.com/2/files/upload_session/start" | |
API_CHUNKED_UPLOAD_FINISH_URL="https://content.dropboxapi.com/2/files/upload_session/finish" | |
API_CHUNKED_UPLOAD_APPEND_URL="https://content.dropboxapi.com/2/files/upload_session/append_v2" | |
API_UPLOAD_URL="https://content.dropboxapi.com/2/files/upload" | |
API_DOWNLOAD_URL="https://content.dropboxapi.com/2/files/download" | |
API_DELETE_URL="https://api.dropboxapi.com/2/files/delete" | |
API_MOVE_URL="https://api.dropboxapi.com/2/files/move" | |
API_COPY_URL="https://api.dropboxapi.com/2/files/copy" | |
API_METADATA_URL="https://api.dropboxapi.com/2/files/get_metadata" | |
API_LIST_FOLDER_URL="https://api.dropboxapi.com/2/files/list_folder" | |
API_LIST_FOLDER_CONTINUE_URL="https://api.dropboxapi.com/2/files/list_folder/continue" | |
API_ACCOUNT_INFO_URL="https://api.dropboxapi.com/2/users/get_current_account" | |
API_ACCOUNT_SPACE_URL="https://api.dropboxapi.com/2/users/get_space_usage" | |
API_MKDIR_URL="https://api.dropboxapi.com/2/files/create_folder" | |
API_SHARE_URL="https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings" | |
API_SHARE_LIST="https://api.dropboxapi.com/2/sharing/list_shared_links" | |
API_SAVEURL_URL="https://api.dropboxapi.com/2/files/save_url" | |
API_SAVEURL_JOBSTATUS_URL="https://api.dropboxapi.com/2/files/save_url/check_job_status" | |
API_SEARCH_URL="https://api.dropboxapi.com/2/files/search" | |
APP_CREATE_URL="https://www.dropbox.com/developers/apps" | |
RESPONSE_FILE="$TMP_DIR/du_resp_$RANDOM" | |
CHUNK_FILE="$TMP_DIR/du_chunk_$RANDOM" | |
TEMP_FILE="$TMP_DIR/du_tmp_$RANDOM" | |
BIN_DEPS="sed basename date grep stat dd mkdir" | |
VERSION="1.0" | |
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 | |
#Check temp folder | |
if [[ ! -d "$TMP_DIR" ]]; then | |
echo -e "Error: the temporary folder $TMP_DIR doesn't exists!" | |
echo -e "Please edit this script and set the TMP_DIR variable to a valid temporary folder to use." | |
exit 1 | |
fi | |
#Look for optional config file parameter | |
while getopts ":qpskdhf:x:" opt; do | |
case $opt in | |
f) | |
CONFIG_FILE=$OPTARG | |
;; | |
d) | |
DEBUG=1 | |
;; | |
q) | |
QUIET=1 | |
;; | |
p) | |
SHOW_PROGRESSBAR=1 | |
;; | |
k) | |
CURL_ACCEPT_CERTIFICATES="-k" | |
;; | |
s) | |
SKIP_EXISTING_FILES=1 | |
;; | |
h) | |
HUMAN_READABLE_SIZE=1 | |
;; | |
x) | |
EXCLUDE+=( $OPTARG ) | |
;; | |
\?) | |
echo "Invalid option: -$OPTARG" >&2 | |
exit 1 | |
;; | |
:) | |
echo "Option -$OPTARG requires an argument." >&2 | |
exit 1 | |
;; | |
esac | |
done | |
if [[ $DEBUG != 0 ]]; then | |
echo $VERSION | |
uname -a 2> /dev/null | |
cat /etc/issue 2> /dev/null | |
set -x | |
RESPONSE_FILE="$TMP_DIR/du_resp_debug" | |
fi | |
if [[ $CURL_BIN == "" ]]; then | |
BIN_DEPS="$BIN_DEPS curl" | |
CURL_BIN="curl" | |
fi | |
#Dependencies check | |
which $BIN_DEPS > /dev/null | |
if [[ $? != 0 ]]; then | |
for i in $BIN_DEPS; do | |
which $i > /dev/null || | |
NOT_FOUND="$i $NOT_FOUND" | |
done | |
echo -e "Error: Required program could not be found: $NOT_FOUND" | |
exit 1 | |
fi | |
#Check if readlink is installed and supports the -m option | |
#It's not necessary, so no problem if it's not installed | |
which readlink > /dev/null | |
if [[ $? == 0 && $(readlink -m "//test" 2> /dev/null) == "/test" ]]; then | |
HAVE_READLINK=1 | |
else | |
HAVE_READLINK=0 | |
fi | |
#Forcing to use the builtin printf, if it's present, because it's better | |
#otherwise the external printf program will be used | |
#Note that the external printf command can cause character encoding issues! | |
builtin printf "" 2> /dev/null | |
if [[ $? == 0 ]]; then | |
PRINTF="builtin printf" | |
PRINTF_OPT="-v o" | |
else | |
PRINTF=$(which printf) | |
if [[ $? != 0 ]]; then | |
echo -e "Error: Required program could not be found: printf" | |
fi | |
PRINTF_OPT="" | |
fi | |
#Print the message based on $QUIET variable | |
function print | |
{ | |
if [[ $QUIET == 0 ]]; then | |
echo -ne "$1"; | |
fi | |
} | |
#Returns unix timestamp | |
function utime | |
{ | |
date '+%s' | |
} | |
#Remove temporary files | |
function remove_temp_files | |
{ | |
if [[ $DEBUG == 0 ]]; then | |
rm -fr "$RESPONSE_FILE" | |
rm -fr "$CHUNK_FILE" | |
rm -fr "$TEMP_FILE" | |
fi | |
} | |
#Converts bytes to human readable format | |
function convert_bytes | |
{ | |
if [[ $HUMAN_READABLE_SIZE == 1 && "$1" != "" ]]; then | |
if (($1 > 1073741824));then | |
echo $(($1/1073741824)).$(($1%1073741824/100000000))"G"; | |
elif (($1 > 1048576));then | |
echo $(($1/1048576)).$(($1%1048576/100000))"M"; | |
elif (($1 > 1024));then | |
echo $(($1/1024)).$(($1%1024/100))"K"; | |
else | |
echo $1; | |
fi | |
else | |
echo $1; | |
fi | |
} | |
#Returns the file size in bytes | |
function file_size | |
{ | |
#Generic GNU | |
SIZE=$(stat --format="%s" "$1" 2> /dev/null) | |
if [ $? -eq 0 ]; then | |
echo $SIZE | |
return | |
fi | |
#Some embedded linux devices | |
SIZE=$(stat -c "%s" "$1" 2> /dev/null) | |
if [ $? -eq 0 ]; then | |
echo $SIZE | |
return | |
fi | |
#BSD, OSX and other OSs | |
SIZE=$(stat -f "%z" "$1" 2> /dev/null) | |
if [ $? -eq 0 ]; then | |
echo $SIZE | |
return | |
fi | |
echo "0" | |
} | |
#Usage | |
function usage | |
{ | |
echo -e "Dropbox Uploader v$VERSION" | |
echo -e "Andrea Fabrizi - [email protected]\n" | |
echo -e "Usage: $0 [PARAMETERS] COMMAND..." | |
echo -e "\nCommands:" | |
echo -e "\t upload <LOCAL_FILE/DIR ...> <REMOTE_FILE/DIR>" | |
echo -e "\t download <REMOTE_FILE/DIR> [LOCAL_FILE/DIR]" | |
echo -e "\t delete <REMOTE_FILE/DIR>" | |
echo -e "\t move <REMOTE_FILE/DIR> <REMOTE_FILE/DIR>" | |
echo -e "\t copy <REMOTE_FILE/DIR> <REMOTE_FILE/DIR>" | |
echo -e "\t mkdir <REMOTE_DIR>" | |
echo -e "\t list [REMOTE_DIR]" | |
echo -e "\t monitor [REMOTE_DIR] [TIMEOUT]" | |
echo -e "\t share <REMOTE_FILE>" | |
echo -e "\t saveurl <URL> <REMOTE_DIR>" | |
echo -e "\t search <QUERY>" | |
echo -e "\t info" | |
echo -e "\t space" | |
echo -e "\t unlink" | |
echo -e "\nOptional parameters:" | |
echo -e "\t-f <FILENAME> Load the configuration file from a specific file" | |
echo -e "\t-s Skip already existing files when download/upload. Default: Overwrite" | |
echo -e "\t-d Enable DEBUG mode" | |
echo -e "\t-q Quiet mode. Don't show messages" | |
echo -e "\t-h Show file sizes in human readable format" | |
echo -e "\t-p Show cURL progress meter" | |
echo -e "\t-k Doesn't check for SSL certificates (insecure)" | |
echo -e "\t-x Ignores/excludes directories or files from syncing. -x filename -x directoryname. example: -x .git" | |
echo -en "\nFor more info and examples, please see the README file.\n\n" | |
remove_temp_files | |
exit 1 | |
} | |
#Check the curl exit code | |
function check_http_response | |
{ | |
CODE=$? | |
#Checking curl exit code | |
case $CODE in | |
#OK | |
0) | |
;; | |
#Proxy error | |
5) | |
print "\nError: Couldn't resolve proxy. The given proxy host could not be resolved.\n" | |
remove_temp_files | |
exit 1 | |
;; | |
#Missing CA certificates | |
60|58|77) | |
print "\nError: cURL is not able to performs peer SSL certificate verification.\n" | |
print "Please, install the default ca-certificates bundle.\n" | |
print "To do this in a Debian/Ubuntu based system, try:\n" | |
print " sudo apt-get install ca-certificates\n\n" | |
print "If the problem persists, try to use the -k option (insecure).\n" | |
remove_temp_files | |
exit 1 | |
;; | |
6) | |
print "\nError: Couldn't resolve host.\n" | |
remove_temp_files | |
exit 1 | |
;; | |
7) | |
print "\nError: Couldn't connect to host.\n" | |
remove_temp_files | |
exit 1 | |
;; | |
esac | |
#Checking response file for generic errors | |
if grep -q "HTTP/1.1 400" "$RESPONSE_FILE"; then | |
ERROR_MSG=$(sed -n -e 's/{"error": "\([^"]*\)"}/\1/p' "$RESPONSE_FILE") | |
case $ERROR_MSG in | |
*access?attempt?failed?because?this?app?is?not?configured?to?have*) | |
echo -e "\nError: The Permission type/Access level configured doesn't match the DropBox App settings!\nPlease run \"$0 unlink\" and try again." | |
exit 1 | |
;; | |
esac | |
fi | |
} | |
#Urlencode | |
function urlencode | |
{ | |
#The printf is necessary to correctly decode unicode sequences | |
local string=$($PRINTF "${1}") | |
local strlen=${#string} | |
local encoded="" | |
for (( pos=0 ; pos<strlen ; pos++ )); do | |
c=${string:$pos:1} | |
case "$c" in | |
[-_.~a-zA-Z0-9] ) o="${c}" ;; | |
* ) $PRINTF $PRINTF_OPT '%%%02x' "'$c" | |
esac | |
encoded="${encoded}${o}" | |
done | |
echo "$encoded" | |
} | |
function normalize_path | |
{ | |
#The printf is necessary to correctly decode unicode sequences | |
path=$($PRINTF "${1//\/\///}") | |
if [[ $HAVE_READLINK == 1 ]]; then | |
new_path=$(readlink -m "$path") | |
#Adding back the final slash, if present in the source | |
if [[ ${path: -1} == "/" && ${#path} -gt 1 ]]; then | |
new_path="$new_path/" | |
fi | |
echo "$new_path" | |
else | |
echo "$path" | |
fi | |
} | |
#Check if it's a file or directory | |
#Returns FILE/DIR/ERR | |
function db_stat | |
{ | |
local FILE=$(normalize_path "$1") | |
if [[ $FILE == "/" ]]; then | |
echo "DIR" | |
return | |
fi | |
#Checking if it's a file or a directory | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE\"}" "$API_METADATA_URL" 2> /dev/null | |
check_http_response | |
local TYPE=$(sed -n 's/{".tag": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
case $TYPE in | |
file) | |
echo "FILE" | |
;; | |
folder) | |
echo "DIR" | |
;; | |
deleted) | |
echo "ERR" | |
;; | |
*) | |
echo "ERR" | |
;; | |
esac | |
} | |
#Generic upload wrapper around db_upload_file and db_upload_dir functions | |
#$1 = Local source file/dir | |
#$2 = Remote destination file/dir | |
function db_upload | |
{ | |
local SRC=$(normalize_path "$1") | |
local DST=$(normalize_path "$2") | |
for j in "${EXCLUDE[@]}" | |
do : | |
if [[ $(echo "$SRC" | grep "$j" | wc -l) -gt 0 ]]; then | |
print "Skipping excluded file/dir: "$j | |
return | |
fi | |
done | |
#Checking if the file/dir exists | |
if [[ ! -e $SRC && ! -d $SRC ]]; then | |
print " > No such file or directory: $SRC\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
#Checking if the file/dir has read permissions | |
if [[ ! -r $SRC ]]; then | |
print " > Error reading file $SRC: permission denied\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
TYPE=$(db_stat "$DST") | |
#If DST it's a file, do nothing, it's the default behaviour | |
if [[ $TYPE == "FILE" ]]; then | |
DST="$DST" | |
#if DST doesn't exists and doesn't ends with a /, it will be the destination file name | |
elif [[ $TYPE == "ERR" && "${DST: -1}" != "/" ]]; then | |
DST="$DST" | |
#if DST doesn't exists and ends with a /, it will be the destination folder | |
elif [[ $TYPE == "ERR" && "${DST: -1}" == "/" ]]; then | |
local filename=$(basename "$SRC") | |
DST="$DST/$filename" | |
#If DST it's a directory, it will be the destination folder | |
elif [[ $TYPE == "DIR" ]]; then | |
local filename=$(basename "$SRC") | |
DST="$DST/$filename" | |
fi | |
#It's a directory | |
if [[ -d $SRC ]]; then | |
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 | |
# Checking if the file has the correct check sum | |
if [[ $TYPE != "ERR" ]]; then | |
sha_src=$(db_sha_local "$FILE_SRC") | |
sha_dst=$(db_sha "$FILE_DST") | |
if [[ $sha_src == $sha_dst && $sha_src != "ERR" ]]; then | |
print "> Skipping file \"$FILE_SRC\", file exists with the same hash\n" | |
return | |
fi | |
fi | |
if [[ $FILE_SIZE -gt 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="-L -s" | |
LINE_CR="" | |
fi | |
print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\"... $LINE_CR" | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES $CURL_PARAMETERS -X POST -i --globoff -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"path\": \"$FILE_DST\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}" --header "Content-Type: application/octet-stream" --data-binary @"$FILE_SRC" "$API_UPLOAD_URL" | |
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") | |
if [[ $SHOW_PROGRESSBAR == 1 && $QUIET == 0 ]]; then | |
VERBOSE=1 | |
CURL_PARAMETERS="--progress-bar" | |
else | |
VERBOSE=0 | |
CURL_PARAMETERS="-L -s" | |
fi | |
local FILE_SIZE=$(file_size "$FILE_SRC") | |
local OFFSET=0 | |
local UPLOAD_ID="" | |
local UPLOAD_ERROR=0 | |
local CHUNK_PARAMS="" | |
## Ceil division | |
let NUMBEROFCHUNK=($FILE_SIZE/1024/1024+$CHUNK_SIZE-1)/$CHUNK_SIZE | |
if [[ $VERBOSE == 1 ]]; then | |
print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\" by $NUMBEROFCHUNK chunks ...\n" | |
else | |
print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\" by $NUMBEROFCHUNK chunks " | |
fi | |
#Starting a new upload session | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"close\": false}" --header "Content-Type: application/octet-stream" --data-binary @/dev/null "$API_CHUNKED_UPLOAD_START_URL" 2> /dev/null | |
check_http_response | |
SESSION_ID=$(sed -n 's/{"session_id": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
chunkNumber=1 | |
#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 | |
local CHUNK_REAL_SIZE=$(file_size "$CHUNK_FILE") | |
if [[ $VERBOSE == 1 ]]; then | |
print " >> Uploading chunk $chunkNumber of $NUMBEROFCHUNK\n" | |
fi | |
#Uploading the chunk... | |
echo > "$RESPONSE_FILE" | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST $CURL_PARAMETERS --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"cursor\": {\"session_id\": \"$SESSION_ID\",\"offset\": $OFFSET},\"close\": false}" --header "Content-Type: application/octet-stream" --data-binary @"$CHUNK_FILE" "$API_CHUNKED_UPLOAD_APPEND_URL" | |
#check_http_response not needed, because we have to retry the request in case of error | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
let OFFSET=$OFFSET+$CHUNK_REAL_SIZE | |
UPLOAD_ERROR=0 | |
if [[ $VERBOSE != 1 ]]; then | |
print "." | |
fi | |
((chunkNumber=chunkNumber+1)) | |
else | |
if [[ $VERBOSE != 1 ]]; then | |
print "*" | |
fi | |
let UPLOAD_ERROR=$UPLOAD_ERROR+1 | |
#On error, the upload is retried for max 3 times | |
if [[ $UPLOAD_ERROR -gt 2 ]]; then | |
print " FAILED\n" | |
print "An error occurred requesting /chunked_upload\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
fi | |
done | |
UPLOAD_ERROR=0 | |
#Commit the upload | |
while (true); do | |
echo > "$RESPONSE_FILE" | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"cursor\": {\"session_id\": \"$SESSION_ID\",\"offset\": $OFFSET},\"commit\": {\"path\": \"$FILE_DST\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}}" --header "Content-Type: application/octet-stream" --data-binary @/dev/null "$API_CHUNKED_UPLOAD_FINISH_URL" 2> /dev/null | |
#check_http_response not needed, because we have to retry the request in case of error | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
UPLOAD_ERROR=0 | |
break | |
else | |
print "*" | |
let UPLOAD_ERROR=$UPLOAD_ERROR+1 | |
#On error, the commit is retried for max 3 times | |
if [[ $UPLOAD_ERROR -gt 2 ]]; then | |
print " FAILED\n" | |
print "An error occurred requesting /commit_chunked_upload\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
fi | |
done | |
print " DONE\n" | |
} | |
#Directory upload | |
#$1 = Local source dir | |
#$2 = Remote destination dir | |
function db_upload_dir | |
{ | |
local DIR_SRC=$(normalize_path "$1") | |
local DIR_DST=$(normalize_path "$2") | |
#Creatig remote directory | |
db_mkdir "$DIR_DST" | |
for file in "$DIR_SRC/"*; do | |
db_upload "$file" "$DIR_DST" | |
done | |
} | |
#Generic download wrapper | |
#$1 = Remote source file/dir | |
#$2 = Local destination file/dir | |
function db_download | |
{ | |
local SRC=$(normalize_path "$1") | |
local DST=$(normalize_path "$2") | |
TYPE=$(db_stat "$SRC") | |
#It's a directory | |
if [[ $TYPE == "DIR" ]]; then | |
#If the DST folder is not specified, I assume that is the current directory | |
if [[ $DST == "" ]]; then | |
DST="." | |
fi | |
#Checking if the destination directory exists | |
if [[ ! -d $DST ]]; then | |
local basedir="" | |
else | |
local basedir=$(basename "$SRC") | |
fi | |
local DEST_DIR=$(normalize_path "$DST/$basedir") | |
print " > Downloading folder \"$SRC\" to \"$DEST_DIR\"... \n" | |
if [[ ! -d "$DEST_DIR" ]]; then | |
print " > Creating local directory \"$DEST_DIR\"... " | |
mkdir -p "$DEST_DIR" | |
#Check | |
if [[ $? == 0 ]]; then | |
print "DONE\n" | |
else | |
print "FAILED\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
fi | |
if [[ $SRC == "/" ]]; then | |
SRC_REQ="" | |
else | |
SRC_REQ="$SRC" | |
fi | |
OUT_FILE=$(db_list_outfile "$SRC_REQ") | |
if [ $? -ne 0 ]; then | |
# When db_list_outfile fail, the error message is OUT_FILE | |
print "$OUT_FILE\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
#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 == "file" ]]; then | |
db_download_file "$SRC/$FILE" "$DEST_DIR/$FILE" | |
elif [[ $TYPE == "folder" ]]; then | |
db_download "$SRC/$FILE" "$DEST_DIR" | |
fi | |
done < $OUT_FILE | |
rm -fr $OUT_FILE | |
#It's a file | |
elif [[ $TYPE == "FILE" ]]; then | |
#Checking DST | |
if [[ $DST == "" ]]; then | |
DST=$(basename "$SRC") | |
fi | |
#If the destination is a directory, the file will be download into | |
if [[ -d $DST ]]; then | |
DST="$DST/$SRC" | |
fi | |
db_download_file "$SRC" "$DST" | |
#Doesn't exists | |
else | |
print " > No such file or directory: $SRC\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
} | |
#Simple file download | |
#$1 = Remote source file | |
#$2 = Local destination file | |
function db_download_file | |
{ | |
local FILE_SRC=$(normalize_path "$1") | |
local FILE_DST=$(normalize_path "$2") | |
if [[ $SHOW_PROGRESSBAR == 1 && $QUIET == 0 ]]; then | |
CURL_PARAMETERS="-L --progress-bar" | |
LINE_CR="\n" | |
else | |
CURL_PARAMETERS="-L -s" | |
LINE_CR="" | |
fi | |
#Checking if the file already exists | |
if [[ -e $FILE_DST && $SKIP_EXISTING_FILES == 1 ]]; then | |
print " > Skipping already existing file \"$FILE_DST\"\n" | |
return | |
fi | |
# Checking if the file has the correct check sum | |
if [[ $TYPE != "ERR" ]]; then | |
sha_src=$(db_sha "$FILE_SRC") | |
sha_dst=$(db_sha_local "$FILE_DST") | |
if [[ $sha_src == $sha_dst && $sha_src != "ERR" ]]; then | |
print "> Skipping file \"$FILE_SRC\", file exists with the same hash\n" | |
return | |
fi | |
fi | |
#Creating the empty file, that for two reasons: | |
#1) In this way I can check if the destination file is writable or not | |
#2) Curl doesn't automatically creates files with 0 bytes size | |
dd if=/dev/zero of="$FILE_DST" count=0 2> /dev/null | |
if [[ $? != 0 ]]; then | |
print " > Error writing file $FILE_DST: permission denied\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
print " > Downloading \"$FILE_SRC\" to \"$FILE_DST\"... $LINE_CR" | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES $CURL_PARAMETERS -X POST --globoff -D "$RESPONSE_FILE" -o "$FILE_DST" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"path\": \"$FILE_SRC\"}" "$API_DOWNLOAD_URL" | |
check_http_response | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
print "DONE\n" | |
else | |
print "FAILED\n" | |
rm -fr "$FILE_DST" | |
ERROR_STATUS=1 | |
return | |
fi | |
} | |
#Saveurl | |
#$1 = URL | |
#$2 = Remote file destination | |
function db_saveurl | |
{ | |
local URL="$1" | |
local FILE_DST=$(normalize_path "$2") | |
local FILE_NAME=$(basename "$URL") | |
print " > Downloading \"$URL\" to \"$FILE_DST\"..." | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST/$FILE_NAME\", \"url\": \"$URL\"}" "$API_SAVEURL_URL" 2> /dev/null | |
check_http_response | |
JOB_ID=$(sed -n 's/.*"async_job_id": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
if [[ $JOB_ID == "" ]]; then | |
print " > Error getting the job id\n" | |
return | |
fi | |
#Checking the status | |
while (true); do | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"async_job_id\": \"$JOB_ID\"}" "$API_SAVEURL_JOBSTATUS_URL" 2> /dev/null | |
check_http_response | |
STATUS=$(sed -n 's/{".tag": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
case $STATUS in | |
in_progress) | |
print "+" | |
;; | |
complete) | |
print " DONE\n" | |
break | |
;; | |
failed) | |
print " ERROR\n" | |
MESSAGE=$(sed -n 's/.*"error_summary": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
print " > Error: $MESSAGE\n" | |
break | |
;; | |
esac | |
sleep 2 | |
done | |
} | |
#Prints account info | |
function db_account_info | |
{ | |
print "Dropbox Uploader v$VERSION\n\n" | |
print " > Getting info... " | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" "$API_ACCOUNT_INFO_URL" 2> /dev/null | |
check_http_response | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
name=$(sed -n 's/.*"display_name": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo -e "\n\nName:\t\t$name" | |
uid=$(sed -n 's/.*"account_id": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo -e "UID:\t\t$uid" | |
email=$(sed -n 's/.*"email": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo -e "Email:\t\t$email" | |
country=$(sed -n 's/.*"country": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo -e "Country:\t$country" | |
echo "" | |
else | |
print "FAILED\n" | |
ERROR_STATUS=1 | |
fi | |
} | |
#Prints account space usage info | |
function db_account_space | |
{ | |
print "Dropbox Uploader v$VERSION\n\n" | |
print " > Getting space usage info... " | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" "$API_ACCOUNT_SPACE_URL" 2> /dev/null | |
check_http_response | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
quota=$(sed -n 's/.*"allocated": \([0-9]*\).*/\1/p' "$RESPONSE_FILE") | |
let quota_mb=$quota/1024/1024 | |
echo -e "\n\nQuota:\t$quota_mb Mb" | |
used=$(sed -n 's/.*"used": \([0-9]*\).*/\1/p' "$RESPONSE_FILE") | |
let used_mb=$used/1024/1024 | |
echo -e "Used:\t$used_mb Mb" | |
let free_mb=$((quota-used))/1024/1024 | |
echo -e "Free:\t$free_mb Mb" | |
echo "" | |
else | |
print "FAILED\n" | |
ERROR_STATUS=1 | |
fi | |
} | |
#Account unlink | |
function db_unlink | |
{ | |
echo -ne "Are you sure you want unlink this script from your Dropbox account? [y/n]" | |
read -r answer | |
if [[ $answer == "y" ]]; then | |
rm -fr "$CONFIG_FILE" | |
echo -ne "DONE\n" | |
fi | |
} | |
#Delete a remote file | |
#$1 = Remote file to delete | |
function db_delete | |
{ | |
local FILE_DST=$(normalize_path "$1") | |
print " > Deleting \"$FILE_DST\"... " | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST\"}" "$API_DELETE_URL" 2> /dev/null | |
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 | |
} | |
#Move/Rename a remote file | |
#$1 = Remote file to rename or move | |
#$2 = New file name or location | |
function db_move | |
{ | |
local FILE_SRC=$(normalize_path "$1") | |
local FILE_DST=$(normalize_path "$2") | |
TYPE=$(db_stat "$FILE_DST") | |
#If the destination it's a directory, the source will be moved into it | |
if [[ $TYPE == "DIR" ]]; then | |
local filename=$(basename "$FILE_SRC") | |
FILE_DST=$(normalize_path "$FILE_DST/$filename") | |
fi | |
print " > Moving \"$FILE_SRC\" to \"$FILE_DST\" ... " | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"from_path\": \"$FILE_SRC\", \"to_path\": \"$FILE_DST\"}" "$API_MOVE_URL" 2> /dev/null | |
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 | |
} | |
#Copy a remote file to a remote location | |
#$1 = Remote file to rename or move | |
#$2 = New file name or location | |
function db_copy | |
{ | |
local FILE_SRC=$(normalize_path "$1") | |
local FILE_DST=$(normalize_path "$2") | |
TYPE=$(db_stat "$FILE_DST") | |
#If the destination it's a directory, the source will be copied into it | |
if [[ $TYPE == "DIR" ]]; then | |
local filename=$(basename "$FILE_SRC") | |
FILE_DST=$(normalize_path "$FILE_DST/$filename") | |
fi | |
print " > Copying \"$FILE_SRC\" to \"$FILE_DST\" ... " | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"from_path\": \"$FILE_SRC\", \"to_path\": \"$FILE_DST\"}" "$API_COPY_URL" 2> /dev/null | |
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 -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$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 a remote folder and returns the path to the file containing the output | |
#$1 = Remote directory | |
#$2 = Cursor (Optional) | |
function db_list_outfile | |
{ | |
local DIR_DST="$1" | |
local HAS_MORE="false" | |
local CURSOR="" | |
if [[ -n "$2" ]]; then | |
CURSOR="$2" | |
HAS_MORE="true" | |
fi | |
OUT_FILE="$TMP_DIR/du_tmp_out_$RANDOM" | |
while (true); do | |
if [[ $HAS_MORE == "true" ]]; then | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"cursor\": \"$CURSOR\"}" "$API_LIST_FOLDER_CONTINUE_URL" 2> /dev/null | |
else | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$DIR_DST\",\"include_media_info\": false,\"include_deleted\": false,\"include_has_explicit_shared_members\": false}" "$API_LIST_FOLDER_URL" 2> /dev/null | |
fi | |
check_http_response | |
HAS_MORE=$(sed -n 's/.*"has_more": *\([a-z]*\).*/\1/p' "$RESPONSE_FILE") | |
CURSOR=$(sed -n 's/.*"cursor": *"\([^"]*\)".*/\1/p' "$RESPONSE_FILE") | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
#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 | |
echo "$DIR_CONTENT" | sed 's/\\"/\\u0022/' > "$TEMP_FILE" | |
#Extracting files and subfolders | |
while read -r line; do | |
local FILE=$(echo "$line" | sed -n 's/.*"path_display": *"\([^"]*\)".*/\1/p') | |
local TYPE=$(echo "$line" | sed -n 's/.*".tag": *"\([^"]*\).*/\1/p') | |
local SIZE=$(convert_bytes $(echo "$line" | sed -n 's/.*"size": *\([0-9]*\).*/\1/p')) | |
echo -e "$FILE:$TYPE;$SIZE" >> "$OUT_FILE" | |
done < "$TEMP_FILE" | |
if [[ $HAS_MORE == "false" ]]; then | |
break | |
fi | |
else | |
return | |
fi | |
done | |
echo $OUT_FILE | |
} | |
#List remote directory | |
#$1 = Remote directory | |
function db_list | |
{ | |
local DIR_DST=$(normalize_path "$1") | |
print " > Listing \"$DIR_DST\"... " | |
if [[ "$DIR_DST" == "/" ]]; then | |
DIR_DST="" | |
fi | |
OUT_FILE=$(db_list_outfile "$DIR_DST") | |
if [ -z "$OUT_FILE" ]; then | |
print "FAILED\n" | |
ERROR_STATUS=1 | |
return | |
else | |
print "DONE\n" | |
fi | |
#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} -gt $padding ]]; then | |
padding=${#SIZE} | |
fi | |
done < "$OUT_FILE" | |
#For each entry, printing directories... | |
while read -r line; do | |
local FILE=${line%:*} | |
local META=${line##*:} | |
local TYPE=${META%;*} | |
local SIZE=${META#*;} | |
#Removing unneeded / | |
FILE=${FILE##*/} | |
if [[ $TYPE == "folder" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [D] %-${padding}s %s\n" "$SIZE" "$FILE" | |
fi | |
done < "$OUT_FILE" | |
#For each entry, printing files... | |
while read -r line; do | |
local FILE=${line%:*} | |
local META=${line##*:} | |
local TYPE=${META%;*} | |
local SIZE=${META#*;} | |
#Removing unneeded / | |
FILE=${FILE##*/} | |
if [[ $TYPE == "file" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [F] %-${padding}s %s\n" "$SIZE" "$FILE" | |
fi | |
done < "$OUT_FILE" | |
rm -fr "$OUT_FILE" | |
} | |
#Longpoll remote directory only once | |
#$1 = Timeout | |
#$2 = Remote directory | |
function db_monitor_nonblock | |
{ | |
local TIMEOUT=$1 | |
local DIR_DST=$(normalize_path "$2") | |
if [[ "$DIR_DST" == "/" ]]; then | |
DIR_DST="" | |
fi | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$DIR_DST\",\"include_media_info\": false,\"include_deleted\": false,\"include_has_explicit_shared_members\": false}" "$API_LIST_FOLDER_URL" 2> /dev/null | |
check_http_response | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
local CURSOR=$(sed -n 's/.*"cursor": *"\([^"]*\)".*/\1/p' "$RESPONSE_FILE") | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Content-Type: application/json" --data "{\"cursor\": \"$CURSOR\",\"timeout\": ${TIMEOUT}}" "$API_LONGPOLL_FOLDER" 2> /dev/null | |
check_http_response | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
local CHANGES=$(sed -n 's/.*"changes" *: *\([a-z]*\).*/\1/p' "$RESPONSE_FILE") | |
else | |
ERROR_MSG=$(grep "Error in call" "$RESPONSE_FILE") | |
print "FAILED to longpoll (http error): $ERROR_MSG\n" | |
ERROR_STATUS=1 | |
return 1 | |
fi | |
if [[ -z "$CHANGES" ]]; then | |
print "FAILED to longpoll (unexpected response)\n" | |
ERROR_STATUS=1 | |
return 1 | |
fi | |
if [ "$CHANGES" == "true" ]; then | |
OUT_FILE=$(db_list_outfile "$DIR_DST" "$CURSOR") | |
if [ -z "$OUT_FILE" ]; then | |
print "FAILED to list changes\n" | |
ERROR_STATUS=1 | |
return | |
fi | |
#For each entry, printing directories... | |
while read -r line; do | |
local FILE=${line%:*} | |
local META=${line##*:} | |
local TYPE=${META%;*} | |
local SIZE=${META#*;} | |
#Removing unneeded / | |
FILE=${FILE##*/} | |
if [[ $TYPE == "folder" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [D] %s\n" "$FILE" | |
elif [[ $TYPE == "file" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [F] %s %s\n" "$SIZE" "$FILE" | |
elif [[ $TYPE == "deleted" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [-] %s\n" "$FILE" | |
fi | |
done < "$OUT_FILE" | |
rm -fr "$OUT_FILE" | |
fi | |
else | |
ERROR_STATUS=1 | |
return 1 | |
fi | |
} | |
#Longpoll continuously remote directory | |
#$1 = Timeout | |
#$2 = Remote directory | |
function db_monitor | |
{ | |
local TIMEOUT=$1 | |
local DIR_DST=$(normalize_path "$2") | |
while (true); do | |
db_monitor_nonblock "$TIMEOUT" "$2" | |
done | |
} | |
#Share remote file | |
#$1 = Remote file | |
function db_share | |
{ | |
local FILE_DST=$(normalize_path "$1") | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST\",\"settings\": {\"requested_visibility\": \"public\"}}" "$API_SHARE_URL" 2> /dev/null | |
check_http_response | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
print " > Share link: " | |
SHARE_LINK=$(sed -n 's/.*"url": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo "$SHARE_LINK" | |
else | |
get_Share "$FILE_DST" | |
fi | |
} | |
#Query existing shared link | |
#$1 = Remote file | |
function get_Share | |
{ | |
local FILE_DST=$(normalize_path "$1") | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST\",\"direct_only\": true}" "$API_SHARE_LIST" | |
check_http_response | |
#Check | |
if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then | |
print " > Share link: " | |
SHARE_LINK=$(sed -n 's/.*"url": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo "$SHARE_LINK" | |
else | |
print "FAILED\n" | |
MESSAGE=$(sed -n 's/.*"error_summary": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
print " > Error: $MESSAGE\n" | |
ERROR_STATUS=1 | |
fi | |
} | |
#Search on Dropbox | |
#$1 = query | |
function db_search | |
{ | |
local QUERY="$1" | |
print " > Searching for \"$QUERY\"... " | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"\",\"query\": \"$QUERY\",\"start\": 0,\"max_results\": 1000,\"mode\": \"filename\"}" "$API_SEARCH_URL" 2> /dev/null | |
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 | |
#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 's/}, *{/}\ | |
{/g' "$RESPONSE_FILE") | |
#Converting escaped quotes to unicode format | |
echo "$DIR_CONTENT" | sed 's/\\"/\\u0022/' > "$TEMP_FILE" | |
#Extracting files and subfolders | |
rm -fr "$RESPONSE_FILE" | |
while read -r line; do | |
local FILE=$(echo "$line" | sed -n 's/.*"path_display": *"\([^"]*\)".*/\1/p') | |
local TYPE=$(echo "$line" | sed -n 's/.*".tag": *"\([^"]*\).*/\1/p') | |
local SIZE=$(convert_bytes $(echo "$line" | sed -n 's/.*"size": *\([0-9]*\).*/\1/p')) | |
echo -e "$FILE:$TYPE;$SIZE" >> "$RESPONSE_FILE" | |
done < "$TEMP_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} -gt $padding ]]; then | |
padding=${#SIZE} | |
fi | |
done < "$RESPONSE_FILE" | |
#For each entry, printing directories... | |
while read -r line; do | |
local FILE=${line%:*} | |
local META=${line##*:} | |
local TYPE=${META%;*} | |
local SIZE=${META#*;} | |
if [[ $TYPE == "folder" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [D] %-${padding}s %s\n" "$SIZE" "$FILE" | |
fi | |
done < "$RESPONSE_FILE" | |
#For each entry, printing files... | |
while read -r line; do | |
local FILE=${line%:*} | |
local META=${line##*:} | |
local TYPE=${META%;*} | |
local SIZE=${META#*;} | |
if [[ $TYPE == "file" ]]; then | |
FILE=$(echo -e "$FILE") | |
$PRINTF " [F] %-${padding}s %s\n" "$SIZE" "$FILE" | |
fi | |
done < "$RESPONSE_FILE" | |
} | |
#Query the sha256-dropbox-sum of a remote file | |
#see https://www.dropbox.com/developers/reference/content-hash for more information | |
#$1 = Remote file | |
function db_sha | |
{ | |
local FILE=$(normalize_path "$1") | |
if [[ $FILE == "/" ]]; then | |
echo "ERR" | |
return | |
fi | |
#Checking if it's a file or a directory and get the sha-sum | |
$CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE\"}" "$API_METADATA_URL" 2> /dev/null | |
check_http_response | |
local TYPE=$(sed -n 's/{".tag": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") | |
if [[ $TYPE == "folder" ]]; then | |
echo "ERR" | |
return | |
fi | |
local SHA256=$(sed -n 's/.*"content_hash": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") | |
echo "$SHA256" | |
} | |
#Query the sha256-dropbox-sum of a local file | |
#see https://www.dropbox.com/developers/reference/content-hash for more information | |
#$1 = Local file | |
function db_sha_local | |
{ | |
local FILE=$(normalize_path "$1") | |
local FILE_SIZE=$(file_size "$FILE") | |
local OFFSET=0 | |
local SKIP=0 | |
local SHA_CONCAT="" | |
which shasum > /dev/null | |
if [[ $? != 0 ]]; then | |
echo "ERR" | |
return | |
fi | |
while ([[ $OFFSET -lt "$FILE_SIZE" ]]); do | |
dd if="$FILE" of="$CHUNK_FILE" bs=4194304 skip=$SKIP count=1 2> /dev/null | |
local SHA=$(shasum -a 256 "$CHUNK_FILE" | awk '{print $1}') | |
SHA_CONCAT="${SHA_CONCAT}${SHA}" | |
let OFFSET=$OFFSET+4194304 | |
let SKIP=$SKIP+1 | |
done | |
shaHex=$(echo $SHA_CONCAT | sed 's/\([0-9A-F]\{2\}\)/\\x\1/gI') | |
echo -ne $shaHex | shasum -a 256 | awk '{print $1}' | |
} | |
################ | |
#### SETUP #### | |
################ | |
#CHECKING FOR AUTH FILE | |
if [[ -e $CONFIG_FILE ]]; then | |
#Loading data... and change old format config if necesary. | |
source "$CONFIG_FILE" 2>/dev/null || { | |
sed -i'' 's/:/=/' "$CONFIG_FILE" && source "$CONFIG_FILE" 2>/dev/null | |
} | |
#Checking if it's still a v1 API configuration file | |
if [[ $APPKEY != "" || $APPSECRET != "" ]]; then | |
echo -ne "The config file contains the old deprecated v1 oauth tokens.\n" | |
echo -ne "Please run again the script and follow the configuration wizard. The old configuration file has been backed up to $CONFIG_FILE.old\n" | |
mv "$CONFIG_FILE" "$CONFIG_FILE".old | |
exit 1 | |
fi | |
#Checking loaded data | |
if [[ $OAUTH_ACCESS_TOKEN = "" ]]; then | |
echo -ne "Error loading data from $CONFIG_FILE...\n" | |
echo -ne "It is recommended to run $0 unlink\n" | |
remove_temp_files | |
exit 1 | |
fi | |
#NEW SETUP... | |
else | |
echo -ne "\n This is the first time you run this script, please follow the instructions:\n\n" | |
echo -ne " 1) Open the following URL in your Browser, and log in using your account: $APP_CREATE_URL\n" | |
echo -ne " 2) Click on \"Create App\", then select \"Dropbox API app\"\n" | |
echo -ne " 3) Now go on with the configuration, choosing the app permissions and access restrictions to your DropBox folder\n" | |
echo -ne " 4) Enter the \"App Name\" that you prefer (e.g. MyUploader$RANDOM$RANDOM$RANDOM)\n\n" | |
echo -ne " Now, click on the \"Create App\" button.\n\n" | |
echo -ne " When your new App is successfully created, please click on the Generate button\n" | |
echo -ne " under the 'Generated access token' section, then copy and paste the new access token here:\n\n" | |
echo -ne " # Access token: " | |
read -r OAUTH_ACCESS_TOKEN | |
echo -ne "\n > The access token is $OAUTH_ACCESS_TOKEN. Looks ok? [y/N]: " | |
read -r answer | |
if [[ $answer != "y" ]]; then | |
remove_temp_files | |
exit 1 | |
fi | |
echo "OAUTH_ACCESS_TOKEN=$OAUTH_ACCESS_TOKEN" > "$CONFIG_FILE" | |
echo " The configuration has been saved." | |
remove_temp_files | |
exit 0 | |
fi | |
################ | |
#### START #### | |
################ | |
COMMAND="${*:$OPTIND:1}" | |
ARG1="${*:$OPTIND+1:1}" | |
ARG2="${*:$OPTIND+2:1}" | |
let argnum=$#-$OPTIND | |
#CHECKING PARAMS VALUES | |
case $COMMAND in | |
upload) | |
if [[ $argnum -lt 2 ]]; then | |
usage | |
fi | |
FILE_DST="${*:$#:1}" | |
for (( i=OPTIND+1; i<$#; i++ )); do | |
FILE_SRC="${*:$i:1}" | |
db_upload "$FILE_SRC" "/$FILE_DST" | |
done | |
;; | |
download) | |
if [[ $argnum -lt 1 ]]; then | |
usage | |
fi | |
FILE_SRC="$ARG1" | |
FILE_DST="$ARG2" | |
db_download "/$FILE_SRC" "$FILE_DST" | |
;; | |
saveurl) | |
if [[ $argnum -lt 1 ]]; then | |
usage | |
fi | |
URL=$ARG1 | |
FILE_DST="$ARG2" | |
db_saveurl "$URL" "/$FILE_DST" | |
;; | |
share) | |
if [[ $argnum -lt 1 ]]; then | |
usage | |
fi | |
FILE_DST="$ARG1" | |
db_share "/$FILE_DST" | |
;; | |
info) | |
db_account_info | |
;; | |
space) | |
db_account_space | |
;; | |
delete|remove) | |
if [[ $argnum -lt 1 ]]; then | |
usage | |
fi | |
FILE_DST="$ARG1" | |
db_delete "/$FILE_DST" | |
;; | |
move|rename) | |
if [[ $argnum -lt 2 ]]; then | |
usage | |
fi | |
FILE_SRC="$ARG1" | |
FILE_DST="$ARG2" | |
db_move "/$FILE_SRC" "/$FILE_DST" | |
;; | |
copy) | |
if [[ $argnum -lt 2 ]]; then | |
usage | |
fi | |
FILE_SRC="$ARG1" | |
FILE_DST="$ARG2" | |
db_copy "/$FILE_SRC" "/$FILE_DST" | |
;; | |
mkdir) | |
if [[ $argnum -lt 1 ]]; then | |
usage | |
fi | |
DIR_DST="$ARG1" | |
db_mkdir "/$DIR_DST" | |
;; | |
search) | |
if [[ $argnum -lt 1 ]]; then | |
usage | |
fi | |
QUERY=$ARG1 | |
db_search "$QUERY" | |
;; | |
list) | |
DIR_DST="$ARG1" | |
#Checking DIR_DST | |
if [[ $DIR_DST == "" ]]; then | |
DIR_DST="/" | |
fi | |
db_list "/$DIR_DST" | |
;; | |
monitor) | |
DIR_DST="$ARG1" | |
TIMEOUT=$ARG2 | |
#Checking DIR_DST | |
if [[ $DIR_DST == "" ]]; then | |
DIR_DST="/" | |
fi | |
print " > Monitoring \"$DIR_DST\" for changes...\n" | |
if [[ -n $TIMEOUT ]]; then | |
db_monitor_nonblock $TIMEOUT "/$DIR_DST" | |
else | |
db_monitor 60 "/$DIR_DST" | |
fi | |
;; | |
unlink) | |
db_unlink | |
;; | |
*) | |
if [[ $COMMAND != "" ]]; then | |
print "Error: Unknown command: $COMMAND\n\n" | |
ERROR_STATUS=1 | |
fi | |
usage | |
;; | |
esac | |
remove_temp_files | |
if [[ $ERROR_STATUS -ne 0 ]]; then | |
echo "Some error occured. Please check the log." | |
fi | |
exit $ERROR_STATUS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Either
curl
or the Dropbox api now default toHTTP/2
. But I managed to get around this by invoking this script withCURL_BIN='curl --http1.1' dropbox_uploader
. This is kind of a hack tho. So you might need to change occurrences ofHTTP/1.1 200 OK
toHTTP/2 200
to make this permanent.Otherwise, this is brilliant work! Thanks! ❤️