From 1cf426caffa0bd99d6263bde7e27a945f2f214b8 Mon Sep 17 00:00:00 2001 From: Akianonymus Date: Wed, 13 May 2020 11:40:25 +0530 Subject: [PATCH 1/3] More code cleanup | Handle more errors * given the function of this script, it's not unusual to have such large script, so lets cleanup while we can while maintaining the script readability * Fix a typo in install.sh * Fix handling of wrong code provided during credentials setup. * Properly send exit code to the terminal --- google-oauth2.sh | 49 +++-- install.sh | 33 ++-- upload.sh | 475 ++++++++++++++++++----------------------------- 3 files changed, 239 insertions(+), 318 deletions(-) diff --git a/google-oauth2.sh b/google-oauth2.sh index 6673c36..47c58bd 100755 --- a/google-oauth2.sh +++ b/google-oauth2.sh @@ -34,21 +34,31 @@ jsonValue() { grep -o "\"""${1}""\"\:.*" | sed -e "s/.*\"""${1}""\": //" -e 's/[",]*$//' -e 's/["]*$//' -e 's/[,]*$//' -e "s/\"//" -n -e "${num}"p } +# Remove array duplicates, maintain the order as original. +# Usage: removeArrayDuplicates "${somearray[@]}" +# https://stackoverflow.com/a/37962595 +removeArrayDuplicates() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 + declare -A Aseen + Aunique=() + for i in "$@"; do + { [[ -z ${i} || ${Aseen[${i}]} ]]; } && continue + Aunique+=("${i}") && Aseen[${i}]=x + done + printf '%s\n' "${Aunique[@]}" +} + # Update Config. Incase of old value, update, for new value add. # Usage: updateConfig valuename value configpath updateConfig() { [[ $# -lt 3 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 declare VALUE_NAME="${1}" VALUE="${2}" CONFIG_PATH="${3}" FINAL=() - declare -A Aseen printf "" >> "${CONFIG_PATH}" # If config file doesn't exist. - mapfile -t VALUES < "${CONFIG_PATH}" && VALUES+=("${VALUE_NAME}=${VALUE}") + mapfile -t VALUES < "${CONFIG_PATH}" && VALUES+=("${VALUE_NAME}=\"${VALUE}\"") for i in "${VALUES[@]}"; do [[ ${i} =~ ${VALUE_NAME}\= ]] && FINAL+=("${VALUE_NAME}=\"${VALUE}\"") || FINAL+=("${i}") done - for i in "${FINAL[@]}"; do - [[ ${Aseen[${i}]} ]] && continue - printf "%s\n" "${i}" && Aseen[${i}]=x - done >| "${CONFIG_PATH}" + removeArrayDuplicates "${FINAL[@]}" >| "${CONFIG_PATH}" } printf "Starting script..\n" @@ -75,9 +85,9 @@ if [[ -z ${CLIENT_SECRET} ]]; then fi for _ in {1..2}; do clearLine 1; done -printf "Required credentials set.\n" if [[ ${1} = create ]]; then + printf "Required credentials set.\n" printf "Visit the below URL, tap on allow and then enter the code obtained:\n" URL="https://accounts.google.com/o/oauth2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&response_type=code&prompt=consent" printf "%s\n\n" "${URL}" @@ -90,16 +100,33 @@ if [[ ${1} = create ]]; then ACCESS_TOKEN="$(jsonValue access_token <<< "${RESPONSE}")" REFRESH_TOKEN="$(jsonValue refresh_token <<< "${RESPONSE}")" - printf "Access Token: %s\n" "${ACCESS_TOKEN}" - printf "Refresh Token: %s\n" "${REFRESH_TOKEN}" + if [[ -n ${ACCESS_TOKEN} && -n ${REFRESH_TOKEN} ]]; then + updateConfig REFRESH_TOKEN "${REFRESH_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" + updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" + + printf "Access Token: %s\n" "${ACCESS_TOKEN}" + printf "Refresh Token: %s\n" "${REFRESH_TOKEN}" + else + printf "Error: Wrong code given, make sure you copy the exact code\n" + exit 1 + fi else printf "\nNo code provided, run the script and try again.\n" exit 1 fi elif [[ ${1} = refresh ]]; then + # Method to regenerate access_token ( also updates in config ). + # Make a request on https://www.googleapis.com/oauth2/""${API_VERSION}""/tokeninfo?access_token=${ACCESS_TOKEN} url and check if the given token is valid, if not generate one. + # Requirements: Refresh Token + getTokenandUpdate() { + RESPONSE="$(curl --compressed -s -X POST --data "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token" "${TOKEN_URL}")" + ACCESS_TOKEN="$(jsonValue access_token <<< "${RESPONSE}")" + updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" + } if [[ -n ${REFRESH_TOKEN} ]]; then - RESPONSE="$(curl --compressed -s -X POST --data "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token" ${TOKEN_URL})" - ACCESS_TOKEN="$(echo "${RESPONSE}" | jsonValue access_token)" + printf "Required credentials set.\n" + getTokenandUpdate + clearLine 1 printf "Access Token: %s\n" "${ACCESS_TOKEN}" else printf "Refresh Token not set, use %s create to generate one.\n" "${0}" diff --git a/install.sh b/install.sh index 7ec0140..969951e 100755 --- a/install.sh +++ b/install.sh @@ -13,6 +13,7 @@ Options:\n -p | --path - Custom path where you want to install script.\nDefault Path: %s/.google-drive-upload \n -c | --cmd - Custom command name, after installation script will be available as the input argument.\nDefault Name: upload \n -r | --repo - Upload script from your custom repo,e.g --repo labbots/google-drive-upload, make sure your repo file structure is same as official repo.\n + -R | --release - Specify tag name for the github repo, applies to custom and default repo both.\n -B | --branch - Specify branch name for the github repo, applies to custom and default repo both.\n -s | --shell-rc - Specify custom rc file, where PATH is appended, by default script detects .zshrc and .bashrc.\n -D | --debug - Display script command trace.\n @@ -35,21 +36,31 @@ isTerminal() { [[ -t 1 || -z ${TERM} ]] && return 0 || return 1 } +# Remove array duplicates, maintain the order as original. +# Usage: removeArrayDuplicates "${somearray[@]}" +# https://stackoverflow.com/a/37962595 +removeArrayDuplicates() { + [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 + declare -A Aseen + Aunique=() + for i in "$@"; do + { [[ -z ${i} || ${Aseen[${i}]} ]]; } && continue + Aunique+=("${i}") && Aseen[${i}]=x + done + printf '%s\n' "${Aunique[@]}" +} + # Update Config. Incase of old value, update, for new value add. # Usage: updateConfig valuename value configpath updateConfig() { [[ $# -lt 3 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 declare VALUE_NAME="${1}" VALUE="${2}" CONFIG_PATH="${3}" FINAL=() - declare -A Aseen printf "" >> "${CONFIG_PATH}" # If config file doesn't exist. - mapfile -t VALUES < "${CONFIG_PATH}" && VALUES+=("${VALUE_NAME}=${VALUE}") + mapfile -t VALUES < "${CONFIG_PATH}" && VALUES+=("${VALUE_NAME}=\"${VALUE}\"") for i in "${VALUES[@]}"; do [[ ${i} =~ ${VALUE_NAME}\= ]] && FINAL+=("${VALUE_NAME}=\"${VALUE}\"") || FINAL+=("${i}") done - for i in "${FINAL[@]}"; do - [[ ${Aseen[${i}]} ]] && continue - printf "%s\n" "${i}" && Aseen[${i}]=x - done >| "${CONFIG_PATH}" + removeArrayDuplicates "${FINAL[@]}" >| "${CONFIG_PATH}" } # Detect profile file @@ -99,7 +110,7 @@ variables() { TYPE_VALUE="latest" SHELL_RC="$(detectProfile)" # shellcheck source=/dev/null - if [[ -f ${INFO_PATH}/google-drive-upload.info ]]; then + if [[ -r ${INFO_PATH}/google-drive-upload.info ]]; then source "${INFO_PATH}"/google-drive-upload.info fi } @@ -140,7 +151,7 @@ install() { done updateConfig LATEST_INSTALLED_SHA "${LATEST_CURRENT_SHA}" "${INFO_PATH}"/google-drive-upload.info updateConfig PATH "${INSTALL_PATH}:${PATH}" "${INFO_PATH}"/google-drive-upload.binpath - if ! grep "source ${INFO_PATH}/google-drive-upload.binpath" "${SHELL_RC}" 1> /dev/null 2>&1; then + if ! grep "source ${INFO_PATH}/google-drive-upload.binpath" "${SHELL_RC}" &> /dev/null; then printf "\nsource %s/google-drive-upload.binpath" "${INFO_PATH}" >> "${SHELL_RC}" fi clearLine 1 @@ -158,7 +169,7 @@ install() { # Update the script update() { printf "Fetching latest version info..\n" - LATEST_CURRENT_SHA="$(getLatestSHA "${REPO}" "${TYPE}" "${TYPE_VALUE}")" + LATEST_CURRENT_SHA="$(getLatestSHA "${TYPE}" "${TYPE_VALUE}" "${REPO}")" if [[ -z "${LATEST_CURRENT_SHA}" ]]; then printf "Cannot fetch remote latest version.\n" exit 1 @@ -171,7 +182,7 @@ update() { curl --compressed -Ls "https://raw.githubusercontent.com/${REPO}/${LATEST_CURRENT_SHA}/upload.sh" -o "${INSTALL_PATH}/${COMMAND_NAME}" updateConfig LATEST_INSTALLED_SHA "${LATEST_CURRENT_SHA}" "${INFO_PATH}"/google-drive-upload.info updateConfig PATH "${INSTALL_PATH}:${PATH}" "${INFO_PATH}"/google-drive-upload.binpath - if ! grep "source ${INFO_PATH}/google-drive-upload.binpath" "${SHELL_RC}" 1> /dev/null 2>&1; then + if ! grep "source ${INFO_PATH}/google-drive-upload.binpath" "${SHELL_RC}" &> /dev/null; then printf "\nsource %s/google-drive-upload.binpath" "${INFO_PATH}" >> "${SHELL_RC}" fi clearLine 1 @@ -336,7 +347,7 @@ main() { startInteractive fi - if type -a "${COMMAND_NAME}" > /dev/null 2>&1; then + if type -a "${COMMAND_NAME}" &> /dev/null; then update else install diff --git a/upload.sh b/upload.sh index 7f096c4..38f79d6 100755 --- a/upload.sh +++ b/upload.sh @@ -43,11 +43,6 @@ isTerminal() { [[ -t 1 || -z ${TERM} ]] && return 0 || return 1 } -# Check for -q/--quiet -isNotQuiet() { - [[ -z ${QUIET} ]] && return 0 || return 1 -} - # Usage: bashSleep 1 ( where is time in seconds ) # https://github.com/dylanaraps/pure-bash-bible#use-read-as-an-alternative-to-the-sleep-command bashSleep() { @@ -55,36 +50,43 @@ bashSleep() { } # Move cursor to nth no. of line and clear it to the begining. +# Usage: clearLine x ( where x is the no of line you wanna clear ) clearLine() { printf "\033[%sA\033[2K" "${1}" } # Convert bytes to human readable form, pure bash. +# Usage: bytesToHuman bytes # https://unix.stackexchange.com/a/259254 bytesToHuman() { declare b=${1:-0} d='' s=0 S=(Bytes {K,M,G,T,P,E,Y,Z}B) while ((b > 1024)); do d="$(printf ".%02d" $((b % 1024 * 100 / 1024)))" - b=$((b / 1024)) - ((s++)) + b=$((b / 1024)) && ((s++)) done printf "%s\n" "${b}${d} ${S[${s}]}" } -# Usage: dirname "path" +# Default curl command for every curl request in this script, just to decrease script line :p +curlCmd() { + curl --compressed "${@}" +} + +# Usage: dirname "path" ( alternative to dirname command ) +# https://github.com/dylanaraps/pure-bash-bible#get-the-directory-name-of-a-file-path dirname() { declare tmp=${1:-.} - [[ $tmp != *[!/]* ]] && { printf '/\n' && return; } + [[ ${tmp} != *[!/]* ]] && { printf '/\n' && return; } tmp="${tmp%%"${tmp##*[!/]}"}" - [[ $tmp != */* ]] && { printf '.\n' && return; } + [[ ${tmp} != */* ]] && { printf '.\n' && return; } tmp=${tmp%/*} && tmp="${tmp%%"${tmp##*[!/]}"}" printf '%s\n' "${tmp:-/}" } -# Update the script +# Update ( install ) the script update() { printf 'Fetching update script..\n' # shellcheck source=/dev/null @@ -93,16 +95,16 @@ update() { fi declare REPO="${REPO:-labbots/google-drive-upload}" TYPE_VALUE="${TYPE_VALUE:-latest}" if [[ ${TYPE} = branch ]]; then - if __SCRIPT="$(curl --compressed -Ls "https://raw.githubusercontent.com/${REPO}/${TYPE_VALUE}/install.sh")"; then + if __SCRIPT="$(curlCmd -Ls "https://raw.githubusercontent.com/${REPO}/${TYPE_VALUE}/install.sh")"; then bash <<< "${__SCRIPT}" else printf "Error: Cannot download update script..\n" fi else declare LATEST_SHA - LATEST_SHA="$(hash="$(curl -L --compressed -s "https://github.com/${REPO}/releases/${TYPE_VALUE}" | grep "=\"/""${REPO}""/commit")" && + LATEST_SHA="$(hash="$(curlCmd -L -s "https://github.com/${REPO}/releases/${TYPE_VALUE}" | grep "=\"/""${REPO}""/commit")" && read -r firstline <<< "${hash}" && : "${hash/*commit\//}" && printf "%s\n" "${_/\"*/}")" - if __SCRIPT="$(curl --compressed -Ls "https://raw.githubusercontent.com/${REPO}/${LATEST_SHA}/install.sh")"; then + if __SCRIPT="$(curlCmd -Ls "https://raw.githubusercontent.com/${REPO}/${LATEST_SHA}/install.sh")"; then bash <<< "${__SCRIPT}" else printf "Error: Cannot download update script..\n" @@ -114,7 +116,7 @@ update() { versionInfo() { # shellcheck source=/dev/null if [[ -f "${HOME}/.google-drive-upload/google-drive-upload.info" ]]; then - cat "${HOME}/.google-drive-upload/google-drive-upload.info" + printf "%s\n" "$(< "${HOME}/.google-drive-upload/google-drive-upload.info")" else printf "google-drive-upload is not installed system wide.\n" fi @@ -122,6 +124,7 @@ versionInfo() { # Print a text to center interactively and fill the rest of the line with text specified. # This function is fine-tuned to this script functionality, so may appear unusual. +# Usage: printCenter normal/justify sometext filler_symbol or sometext sometext2 filler_symbol # https://gist.github.com/TrinityCoder/911059c83e5f7a351b785921cf7ecda printCenter() { [[ $# -lt 3 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 @@ -166,7 +169,7 @@ printCenter() { return 0 } -# Usage: count < "file" or count <<< "$variable" or pipe some output. +# Usage: count < "file" or count <<< "$variable" or pipe some output. ( alt to wc -l ) # https://github.com/dylanaraps/pure-bash-bible#get-the-number-of-lines-in-a-file count() { mapfile -tn 0 lines @@ -182,13 +185,14 @@ jsonValue() { } # Remove array duplicates, maintain the order as original. +# Usage: removeArrayDuplicates "${somearray[@]}" # https://stackoverflow.com/a/37962595 removeArrayDuplicates() { [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 declare -A Aseen Aunique=() for i in "$@"; do - [[ ${Aseen[${i}]} ]] && continue + { [[ -z ${i} || ${Aseen[${i}]} ]]; } && continue Aunique+=("${i}") && Aseen[${i}]=x done printf '%s\n' "${Aunique[@]}" @@ -199,16 +203,12 @@ removeArrayDuplicates() { updateConfig() { [[ $# -lt 3 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 declare VALUE_NAME="${1}" VALUE="${2}" CONFIG_PATH="${3}" FINAL=() - declare -A Aseen printf "" >> "${CONFIG_PATH}" # If config file doesn't exist. - mapfile -t VALUES < "${CONFIG_PATH}" && VALUES+=("${VALUE_NAME}=${VALUE}") + mapfile -t VALUES < "${CONFIG_PATH}" && VALUES+=("${VALUE_NAME}=\"${VALUE}\"") for i in "${VALUES[@]}"; do [[ ${i} =~ ${VALUE_NAME}\= ]] && FINAL+=("${VALUE_NAME}=\"${VALUE}\"") || FINAL+=("${i}") done - for i in "${FINAL[@]}"; do - [[ ${Aseen[${i}]} ]] && continue - printf "%s\n" "${i}" && Aseen[${i}]=x - done >| "${CONFIG_PATH}" + removeArrayDuplicates "${FINAL[@]}" >| "${CONFIG_PATH}" } # Extract file/folder ID from the given INPUT in case of gdrive URL. @@ -249,10 +249,7 @@ driveInfo() { declare FOLDER_ID="${1}" FETCH="${2}" TOKEN="${3}" declare SEARCH_RESPONSE FETCHED_DATA - SEARCH_RESPONSE="$(curl \ - --compressed \ - --silent \ - -XGET \ + SEARCH_RESPONSE="$(curlCmd -s \ -H "Authorization: Bearer ${TOKEN}" \ "${API_URL}/drive/${API_VERSION}/files/${FOLDER_ID}?fields=${FETCH}&supportsAllDrives=true")" @@ -271,10 +268,7 @@ checkExistingFile() { QUERY="$(urlEncode "name='${NAME}' and '${ROOTDIR}' in parents and trashed=false and 'me' in writers")" - SEARCH_RESPONSE="$(curl \ - --compressed \ - --silent \ - -XGET \ + SEARCH_RESPONSE="$(curlCmd -s \ -H "Authorization: Bearer ${TOKEN}" \ "${API_URL}/drive/${API_VERSION}/files?q=${QUERY}&fields=files(id)")" @@ -291,10 +285,7 @@ createDirectory() { QUERY="$(urlEncode "mimeType='application/vnd.google-apps.folder' and name='${DIRNAME}' and trashed=false and '${ROOTDIR}' in parents")" - SEARCH_RESPONSE="$(curl \ - --compressed \ - --silent \ - -XGET \ + SEARCH_RESPONSE="$(curlCmd -s \ -H "Authorization: Bearer ${TOKEN}" \ "${API_URL}/drive/${API_VERSION}/files?q=${QUERY}&fields=files(id)&supportsAllDrives=true")" @@ -303,9 +294,7 @@ createDirectory() { if [[ -z ${FOLDER_ID} ]]; then declare CREATE_FOLDER_POST_DATA CREATE_FOLDER_RESPONSE CREATE_FOLDER_POST_DATA="{\"mimeType\": \"application/vnd.google-apps.folder\",\"name\": \"${DIRNAME}\",\"parents\": [\"${ROOTDIR}\"]}" - CREATE_FOLDER_RESPONSE="$(curl \ - --compressed \ - --silent \ + CREATE_FOLDER_RESPONSE="$(curlCmd -s \ -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json; charset=UTF-8" \ @@ -330,11 +319,12 @@ uploadFile() { INPUTSIZE="$(wc -c < "${INPUT}")" READABLE_SIZE="$(bytesToHuman "${INPUTSIZE}")" + # Handle extension-less files if [[ ${INPUTNAME} = "${EXTENSION}" ]]; then declare MIME_TYPE - if type -p mimetype > /dev/null 2>&1; then + if type -p mimetype &> /dev/null; then MIME_TYPE="$(mimetype --output-format %m "${INPUT}")" - elif type -p file > /dev/null 2>&1; then + elif type -p file &> /dev/null; then MIME_TYPE="$(file --brief --mime-type "${INPUT}")" else printCenter "justify" "Error: file or mimetype command not found." && printf "\n" @@ -353,41 +343,35 @@ uploadFile() { FILE_LINK="${SKIP_DUPLICATES_FILE_ID/${SKIP_DUPLICATES_FILE_ID}/https://drive.google.com/open?id=${SKIP_DUPLICATES_FILE_ID}}" else # https://developers.google.com/drive/api/""${API_VERSION}""/reference/files/update - REQUEST_METHOD=PATCH + REQUEST_METHOD="PATCH" URL="${API_URL}/upload/drive/${API_VERSION}/files/${EXISTING_FILE_ID}?uploadType=resumable&supportsAllDrives=true&supportsTeamDrives=true" # JSON post data to specify the file name and folder under while the file to be updated POSTDATA="{\"mimeType\": \"${MIME_TYPE}\",\"name\": \"${SLUG}\",\"addParents\": [\"${FOLDER_ID}\"]}" - STRING=Updated + STRING="Updated" fi else - JOB=create + JOB="create" fi fi if [[ -n ${SKIP_DUPLICATES_FILE_ID} ]]; then - if isNotQuiet; then - printCenter "justify" "${SLUG}" " already exists." "=" - else - printCenterQuiet "[ ${SLUG} already exists. ]" - fi + # Stop upload if already exists ( -d/--skip-duplicates ) + "${QUIET:-printCenter}" "justify" "${SLUG}" " already exists." "=" else # Set proper variables for creating files if [[ ${JOB} = create ]]; then URL="${API_URL}/upload/drive/${API_VERSION}/files?uploadType=resumable&supportsAllDrives=true&supportsTeamDrives=true" - REQUEST_METHOD=POST + REQUEST_METHOD="POST" # JSON post data to specify the file name and folder under while the file to be created POSTDATA="{\"mimeType\": \"${MIME_TYPE}\",\"name\": \"${SLUG}\",\"parents\": [\"${FOLDER_ID}\"]}" - STRING=Uploaded + STRING="Uploaded" fi [[ -z ${PARALLEL} ]] && printCenter "justify" "${INPUT##*/}" " | ${READABLE_SIZE}" "=" generateUploadLink() { - UPLOADLINK="$(curl \ - --compressed \ - --silent \ + UPLOADLINK="$(curlCmd -s \ -X "${REQUEST_METHOD}" \ - -H "Host: www.googleapis.com" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json; charset=UTF-8" \ -H "X-Upload-Content-Type: ${MIME_TYPE}" \ @@ -400,12 +384,9 @@ uploadFile() { uploadFilefromURI() { # Curl command to push the file to google drive. - # If the file size is large then the content can be split to chunks and uploaded. - # In that case content range needs to be specified. # Not implemented yet. [[ -z ${PARALLEL} ]] && clearLine 1 && printCenter "justify" "Uploading.." "-" # shellcheck disable=SC2086 # Because unnecessary to another check because ${CURL_ARGS} won't be anything problematic. - UPLOAD_BODY="$(curl \ - --compressed \ + UPLOAD_BODY="$(curlCmd \ -X PUT \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: ${MIME_TYPE}" \ @@ -436,39 +417,24 @@ uploadFile() { } normalLogging() { - if [[ -n ${VERBOSE_PROGRESS} ]]; then - if isNotQuiet; then - printCenter "justify" "${SLUG} " "| ${READABLE_SIZE} | ${STRING}" "=" - else - printCenterQuiet "[ ${SLUG} | ${READABLE_SIZE} | ${STRING} ]" - fi - else - if isNotQuiet; then - [[ -z ${PARALLEL} ]] && for _ in {1..3}; do clearLine 1; done - printCenter "justify" "${SLUG} " "| ${READABLE_SIZE} | ${STRING}" "=" - else - printCenterQuiet "[ ${SLUG} | ${READABLE_SIZE} | ${STRING} ]" - fi + if [[ -z ${VERBOSE_PROGRESS:-${PARALLEL}} ]]; then + for _ in {1..3}; do clearLine 1; done fi + "${QUIET:-printCenter}" "justify" "${SLUG} " "| ${READABLE_SIZE} | ${STRING}" "=" } errorLogging() { - if isNotQuiet; then - printCenter "justify" "Upload link generation ERROR" ", ${SLUG} not ${STRING}." "=" 1>&2 && [[ -z ${PARALLEL} ]] && printf "\n\n\n" - else - printCenterQuiet "Upload link generation ERROR, ${SLUG} not ${STRING}." 1>&2 - fi - UPLOAD_STATUS=ERROR && export UPLOAD_STATUS # Send a error status, used in folder uploads. + "${QUIET:-printCenter}" "justify" "Upload link generation ERROR" ", ${SLUG} not ${STRING}." "=" 1>&2 && [[ -z ${PARALLEL} ]] && printf "\n\n\n" 1>&2 + UPLOAD_STATUS="ERROR" && export UPLOAD_STATUS # Send a error status, used in folder uploads. } + # Used for resuming interrupted uploads logUploadSession() { - __file="${HOME}/.google-drive-upload/${SLUG}__::__${FOLDER_ID}__::__${INPUTSIZE}" { [[ ${INPUTSIZE} -gt 1000000 ]] && printf "%s\n" "${UPLOADLINK}" >| "${__file}"; } || : } removeUploadSession() { - __file="${HOME}/.google-drive-upload/${SLUG}__::__${FOLDER_ID}__::__${INPUTSIZE}" - { [[ -f "${__file}" ]] && rm "${__file}"; } || : + rm -f "${__file}" } fullUpload() { @@ -487,15 +453,14 @@ uploadFile() { errorLogging fi } - # https://developers.google.com/drive/api/v3/manage-uploads + __file="${HOME}/.google-drive-upload/${SLUG}__::__${FOLDER_ID}__::__${INPUTSIZE}" - if [[ -f "${__file}" ]]; then - RESUMABLE="$(< "${__file}")" - UPLOADLINK="${RESUMABLE}" - HTTP_CODE="$(curl -X PUT "${UPLOADLINK}" -s --write-out %"{http_code}")" - if [[ ${HTTP_CODE} = "308" ]]; then - UPLOADED_RANGE="$(: "$(curl \ - --compressed -s \ + # https://developers.google.com/drive/api/v3/manage-uploads + if [[ -r "${__file}" ]]; then + UPLOADLINK="$(< "${__file}")" + HTTP_CODE="$(curlCmd -s -X PUT "${UPLOADLINK}" --write-out %"{http_code}")" + if [[ ${HTTP_CODE} = "308" ]]; then # Active Resumable URI give 308 status + UPLOADED_RANGE="$(: "$(curlCmd -s \ -X PUT \ -H "Content-Range: bytes */${INPUTSIZE}" \ --url "${UPLOADLINK}" \ @@ -504,16 +469,14 @@ uploadFile() { if [[ ${UPLOADED_RANGE} =~ (^[0-9]) ]]; then CONTENT_RANGE="$(printf "bytes %s-%s/%s\n" "$((UPLOADED_RANGE + 1))" "$((INPUTSIZE - 1))" "${INPUTSIZE}")" CONTENT_LENGTH="$((INPUTSIZE - $((UPLOADED_RANGE + 1))))" - [[ -z ${PARALLEL} ]] && printCenter "justify" "Resuming interrupted upload.." "-" - # Curl command to push the file to google drive. - # If the file size is large then the content can be split to chunks and uploaded. - # In that case content range needs to be specified. # Not implemented yet. - [[ -z ${PARALLEL} ]] && printCenter "justify" "Uploading.." "-" + [[ -z ${PARALLEL} ]] && { + printCenter "justify" "Resuming interrupted upload.." "-" + printCenter "justify" "Uploading.." "-" + } # shellcheck disable=SC2086 # Because unnecessary to another check because ${CURL_ARGS} won't be anything problematic. # Resuming interrupted uploads needs http1.1 - UPLOAD_BODY="$(curl \ + UPLOAD_BODY="$(curlCmd -s \ --http1.1 \ - --compressed \ -X PUT \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: ${MIME_TYPE}" \ @@ -523,8 +486,7 @@ uploadFile() { -T "${INPUT}" \ -o- \ --url "${UPLOADLINK}" \ - --globoff \ - -s)" || : + --globoff)" || : if [[ -n ${UPLOAD_BODY} ]]; then collectFileInfo normalLogging resume @@ -536,17 +498,16 @@ uploadFile() { [[ -z ${PARALLEL} ]] && printCenter "justify" "Generating upload link.." "-" fullUpload fi - elif [[ ${HTTP_CODE} =~ 40* ]]; then + elif [[ ${HTTP_CODE} =~ 40* ]]; then # Dead Resumable URI give 400,404.. status [[ -z ${PARALLEL} ]] && printCenter "justify" "Generating upload link.." "-" fullUpload - elif [[ ${HTTP_CODE} =~ [200,201] ]]; then + elif [[ ${HTTP_CODE} =~ [200,201] ]]; then # Completed Resumable URI give 200 or 201 status UPLOAD_BODY="${HTTP_CODE}" collectFileInfo normalLogging removeUploadSession fi else - # Curl command to initiate resumable upload session and grab the location URL [[ -z ${PARALLEL} ]] && printCenter "justify" "Generating upload link.." "-" fullUpload fi @@ -557,20 +518,16 @@ uploadFile() { # Requirements: Given file/folder ID, type, role and access_token. shareID() { [[ $# -lt 2 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 - declare LC_ALL=C ID="${1}" TOKEN="${2}" SHARE_EMAIL="${3}" ROLE="reader" + declare LC_ALL=C ID="${1}" TOKEN="${2}" SHARE_EMAIL="${3}" ROLE="reader" TYPE="anyone" declare TYPE SHARE_POST_DATA SHARE_POST_DATA SHARE_RESPONSE SHARE_ID if [[ -n ${SHARE_EMAIL} ]]; then TYPE="user" SHARE_POST_DATA="{\"role\":\"${ROLE}\",\"type\":\"${TYPE}\",\"emailAddress\":\"${SHARE_EMAIL}\"}" else - TYPE="anyone" SHARE_POST_DATA="{\"role\":\"${ROLE}\",\"type\":\"${TYPE}\"}" fi - SHARE_RESPONSE="$(curl \ - --compressed \ - --silent \ - --output /dev/null \ + SHARE_RESPONSE="$(curlCmd -s \ -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json; charset=UTF-8" \ @@ -614,12 +571,10 @@ setupArguments() { usage ;; update) - update - exit $? + update && exit $? ;; info) - versionInfo - exit $? + versionInfo && exit $? ;; create-dir) checkLongoptions @@ -633,7 +588,7 @@ setupArguments() { checkLongoptions CONFIG="${!OPTIND}" && OPTIND=$((OPTIND + 1)) # shellcheck source=/dev/null - [[ -n ${CONFIG} && -f ${CONFIG} ]] && + [[ -r ${CONFIG} ]] && source "${CONFIG}" || printf "Warning: Given config file (%s) doesn't exist, will use existing config or prompt for credentials..\n" "${OPTARG}" ;; save-info) @@ -641,7 +596,7 @@ setupArguments() { LOG_FILE_ID="${!OPTIND}" && OPTIND=$((OPTIND + 1)) ;; skip-subdirs) - SKIP_SUBDIRS=true + SKIP_SUBDIRS="true" ;; parallel) checkLongoptions @@ -655,38 +610,39 @@ setupArguments() { [[ ${NO_OF_PARALLEL_JOBS} -gt 10 ]] && { NO_OF_PARALLEL_JOBS=10 || NO_OF_PARALLEL_JOBS="${!OPTIND}"; } ;; esac - PARALLEL_UPLOAD=true && OPTIND=$((OPTIND + 1)) + PARALLEL_UPLOAD="true" && OPTIND=$((OPTIND + 1)) ;; overwrite) - OVERWRITE=Overwrite + OVERWRITE="Overwrite" && UPLOAD_METHOD="update" ;; skip-duplicates) - SKIP_DUPLICATES=true + SKIP_DUPLICATES="true" && UPLOAD_METHOD="update" ;; file | folder) checkLongoptions INPUT_ARRAY+=("${!OPTIND}") && OPTIND=$((OPTIND + 1)) ;; share) - SHARE=true + SHARE="true" + # https://stackoverflow.com/a/57295993 + # Optional arguments # https://stackoverflow.com/questions/402377/using-getopts-to-process-long-and-short-command-line-options/28466267#28466267 + EMAIL_REGEX="^([A-Za-z]+[A-Za-z0-9]*\+?((\.|\-|\_)?[A-Za-z]+[A-Za-z0-9]*)*)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" if [[ -n ${!OPTIND} && ! ${!OPTIND} =~ ^(\-|\-\-) ]]; then SHARE_EMAIL="${!OPTIND}" && ! [[ ${SHARE_EMAIL} =~ ${EMAIL_REGEX} ]] && printf "\nError: Provided email address for share option is invalid.\n" && exit 1 OPTIND=$((OPTIND + 1)) fi ;; quiet) - QUIET=true - CURL_ARGS="-s" + QUIET="printCenterQuiet" && CURL_ARGS="-s" ;; verbose) - VERBOSE=true + VERBOSE="true" ;; verbose-progress) - VERBOSE_PROGRESS=true - CURL_ARGS="" + VERBOSE_PROGRESS="true" && CURL_ARGS="" ;; debug) - DEBUG=true + DEBUG="true" ;; '') shorthelp @@ -700,8 +656,7 @@ setupArguments() { usage ;; u) - update - exit $? + update && exit $? ;; C) FOLDERNAME="${OPTARG}" @@ -712,14 +667,14 @@ setupArguments() { z) CONFIG="${OPTARG}" # shellcheck source=/dev/null - [[ -n ${CONFIG} && -f ${CONFIG} ]] && + [[ -r ${CONFIG} ]] && source "${CONFIG}" || printf "Warning: Given config file (%s) doesn't exist, will use existing config or prompt for credentials..\n" "${OPTARG}" ;; i) LOG_FILE_ID="${OPTARG}" ;; s) - SKIP_SUBDIRS=true + SKIP_SUBDIRS="true" ;; p) NO_OF_PARALLEL_JOBS="${OPTARG}" @@ -732,13 +687,13 @@ setupArguments() { [[ ${NO_OF_PARALLEL_JOBS} -gt 10 ]] && { NO_OF_PARALLEL_JOBS=10 || NO_OF_PARALLEL_JOBS="${OPTARG}"; } ;; esac - PARALLEL_UPLOAD=true + PARALLEL_UPLOAD="true" ;; o) - OVERWRITE=Overwrite + OVERWRITE="Overwrite" && UPLOAD_METHOD="update" ;; d) - SKIP_DUPLICATES="Skip Existing" + SKIP_DUPLICATES="Skip Existing" && UPLOAD_METHOD="update" ;; f) INPUT_ARRAY+=("${OPTARG}") @@ -751,21 +706,19 @@ setupArguments() { SHARE_EMAIL="${!OPTIND}" && ! [[ ${SHARE_EMAIL} =~ ${EMAIL_REGEX} ]] && printf "\nError: Provided email address for share option is invalid.\n" && exit 1 OPTIND=$((OPTIND + 1)) fi - SHARE=true + SHARE=" (SHARED)" ;; q) - QUIET=true - CURL_ARGS="-s" + QUIET="printCenterQuiet" && CURL_ARGS="-s" ;; v) - VERBOSE=true + VERBOSE="true" ;; V) - VERBOSE_PROGRESS=true - CURL_ARGS="" + VERBOSE_PROGRESS="true" && CURL_ARGS="" ;; D) - DEBUG=true + DEBUG="true" ;; :) printf '%s: -%s: option requires an argument\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1 @@ -779,7 +732,7 @@ setupArguments() { # Incase ${1} argument was not taken as input, check if any arguments after all the valid flags have been passed, for INPUT and FOLDERNAME. # Also check, if folder or dir, else exit. - if [[ -z ${INPUT_ARRAY[*]} ]]; then + if [[ -z ${INPUT_ARRAY[0]} ]]; then if [[ -n ${1} && -f ${1} || -d ${1} ]]; then FINAL_INPUT_ARRAY+=("${1}") { [[ -n ${2} && ${2} != -* ]] && FOLDER_INPUT="${2}"; } || : @@ -789,7 +742,7 @@ setupArguments() { else for array in "${INPUT_ARRAY[@]}"; do { [[ -f ${array} || -d ${array} ]] && FINAL_INPUT_ARRAY+=("${array[@]}"); } || { - printf "\nError: Invalid Input ( %s ), no such file or directory.\n\n" "${array}" + printf "\nError: Invalid Input ( %s ), no such file or directory.\n" "${array}" exit 1 } done @@ -804,14 +757,14 @@ setupArguments() { # To avoid spamming in debug mode. checkDebug() { - printCenterQuiet() { printf "%s\n" "${1}"; } + printCenterQuiet() { { [[ $# = 3 ]] && printf "%s\n" "${2}"; } || { printf "%s%s\n" "${2}" "${3}"; }; } if [[ -n ${DEBUG} ]]; then set -x printCenter() { { [[ $# = 3 ]] && printf "%s\n" "${2}"; } || { printf "%s%s\n" "${2}" "${3}"; }; } clearLine() { :; } && newLine() { :; } else set +x - if isNotQuiet; then + if [[ -z ${QUIET} ]]; then if isTerminal; then # This refreshes the interactive shell so we can use the ${COLUMNS} variable in the printCenter function. shopt -s checkwinsize && (: && :) @@ -840,34 +793,33 @@ checkInternet() { if isTerminal; then CHECK_INTERNET="$(sh -ic 'exec 3>&1 2>/dev/null; { curl --compressed -Is google.com 1>&3; kill 0; } | { sleep 10; kill 0; }' || :)" else - CHECK_INTERNET="$(curl --compressed -Is google.com -m 10)" + CHECK_INTERNET="$(curlCmd -s -I google.com -m 10)" fi - if [[ -z $CHECK_INTERNET ]]; then - clearLine 1 && newLine "\n" && printCenter "justify" "Error: Internet connection not available" "=" && newLine "\n" + clearLine 1 + if [[ -z ${CHECK_INTERNET} ]]; then + newLine "\n" && printCenter "justify" "Error: Internet connection not available" "=" exit 1 - else - clearLine 1 fi } # Set the path and random name for temp file ( used for showing parallel uploads progress ). setupTempfile() { - type -p mktemp > /dev/null && { TMPFILE="$(mktemp -u)" || TMPFILE="${PWD}/$((RANDOM * 2)).LOG"; } - trap '[[ -f "${TMPFILE}"SUCCESS ]] && rm "${TMPFILE}"SUCCESS ; [[ -f "${TMPFILE}"ERROR ]] && rm "${TMPFILE}"ERROR' EXIT + type -p mktemp &> /dev/null && { TMPFILE="$(mktemp -u)" || TMPFILE="${PWD}/$((RANDOM * 2)).LOG"; } + trap 'rm -f "${TMPFILE}"SUCCESS ; rm -f "${TMPFILE}"ERROR' EXIT } # Credentials checkCredentials() { # shellcheck source=/dev/null # Config file is created automatically after first run - [[ -f ${HOME}/.googledrive.conf ]] && source "${HOME}"/.googledrive.conf + [[ -r ${CONFIG:-${HOME}/.googledrive.conf} ]] && source "${CONFIG:-${HOME}/.googledrive.conf}" [[ -z ${CLIENT_ID} ]] && read -r -p "Client ID: " CLIENT_ID && { - updateConfig CLIENT_ID "${CLIENT_ID}" "${HOME}"/.googledrive.conf + updateConfig CLIENT_ID "${CLIENT_ID}" "${CONFIG:-${HOME}/.googledrive.conf}" } [[ -z ${CLIENT_SECRET} ]] && read -r -p "Client Secret: " CLIENT_SECRET && { - updateConfig CLIENT_SECRET "${CLIENT_SECRET}" "${HOME}"/.googledrive.conf + updateConfig CLIENT_SECRET "${CLIENT_SECRET}" "${CONFIG:-${HOME}/.googledrive.conf}" } # Method to obtain refresh_token. @@ -876,22 +828,26 @@ checkCredentials() { read -r -p "If you have a refresh token generated, then type the token, else leave blank and press return key.. Refresh Token: " REFRESH_TOKEN && REFRESH_TOKEN="${REFRESH_TOKEN//[[:space:]]/}" if [[ -n ${REFRESH_TOKEN} ]]; then - updateConfig REFRESH_TOKEN "${REFRESH_TOKEN}" "${HOME}"/.googledrive.conf + updateConfig REFRESH_TOKEN "${REFRESH_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" else printf "\nVisit the below URL, tap on allow and then enter the code obtained:\n" URL="https://accounts.google.com/o/oauth2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${SCOPE}&response_type=code&prompt=consent" - printf "%s\n\n" "${URL}" - read -r -p "Enter the authorization code: " CODE + printf "%s\n" "${URL}" && read -r -p "Enter the authorization code: " CODE CODE="${CODE//[[:space:]]/}" if [[ -n ${CODE} ]]; then - RESPONSE="$(curl --compressed -s -X POST \ + RESPONSE="$(curlCmd -s -X POST \ --data "code=${CODE}&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&redirect_uri=${REDIRECT_URI}&grant_type=authorization_code" "${TOKEN_URL}")" ACCESS_TOKEN="$(jsonValue access_token <<< "${RESPONSE}")" REFRESH_TOKEN="$(jsonValue refresh_token <<< "${RESPONSE}")" - updateConfig REFRESH_TOKEN "${REFRESH_TOKEN}" "${HOME}"/.googledrive.conf - updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${HOME}"/.googledrive.conf + if [[ -n ${ACCESS_TOKEN} && -n ${REFRESH_TOKEN} ]]; then + updateConfig REFRESH_TOKEN "${REFRESH_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" + updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" + else + printf "Error: Wrong code given, make sure you copy the exact code.\n" + exit 1 + fi else printf "\n" printCenter "normal" "No code provided, run the script and try again" " " @@ -900,53 +856,46 @@ checkCredentials() { fi fi - # Method to regenerate access_token. + # Method to regenerate access_token ( also updates in config ). # Make a request on https://www.googleapis.com/oauth2/""${API_VERSION}""/tokeninfo?access_token=${ACCESS_TOKEN} url and check if the given token is valid, if not generate one. # Requirements: Refresh Token - if [[ -z ${ACCESS_TOKEN} ]]; then - RESPONSE="$(curl --compressed -s -X POST --data "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token" "${TOKEN_URL}")" - ACCESS_TOKEN="$(jsonValue access_token <<< "${RESPONSE}")" - updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${HOME}"/.googledrive.conf - elif curl --compressed -s "${API_URL}/oauth2/""${API_VERSION}""/tokeninfo?access_token=${ACCESS_TOKEN}" | jsonValue error_description > /dev/null 2>&1; then - RESPONSE="$(curl --compressed -s -X POST --data "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token" "${TOKEN_URL}")" + getTokenandUpdate() { + RESPONSE="$(curlCmd -s -X POST --data "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token" "${TOKEN_URL}")" ACCESS_TOKEN="$(jsonValue access_token <<< "${RESPONSE}")" - updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${HOME}"/.googledrive.conf + updateConfig ACCESS_TOKEN "${ACCESS_TOKEN}" "${CONFIG:-${HOME}/.googledrive.conf}" + } + if [[ -z ${ACCESS_TOKEN} ]]; then + getTokenandUpdate + elif curlCmd -s "${API_URL}/oauth2/""${API_VERSION}""/tokeninfo?access_token=${ACCESS_TOKEN}" | jsonValue error_description &> /dev/null; then + getTokenandUpdate fi } # Setup root directory where all file/folders will be uploaded. setupRootdir() { - if [[ -n ${ROOTDIR} ]]; then - ROOT_FOLDER="${ROOTDIR//[[:space:]]/}" + checkROOTID() { + ROOT_FOLDER="$(driveInfo "$(extractID "${ROOT_FOLDER}")" "id" "${ACCESS_TOKEN}")" || { + { [[ ${ROOT_FOLDER} =~ "File not found" ]] && "${QUIET:-printCenter}" "justify" "Given root folder " " ID/URL invalid." "="; } || { printf "%s\n" "${ROOT_FOLDER}"; } + exit 1 + } if [[ -n ${ROOT_FOLDER} ]]; then - ROOT_FOLDER="$(driveInfo "$(extractID "${ROOT_FOLDER}")" "id" "${ACCESS_TOKEN}")" || { - { [[ ${ROOT_FOLDER} =~ "File not found" ]] && printCenter "justify" "Given root folder " " ID/URL invalid." "="; } || { printf "%s\n" "${ROOT_FOLDER}"; } - exit 1 - } - if [[ -n ${ROOT_FOLDER} ]]; then - updateConfig ROOT_FOLDER "${ROOT_FOLDER}" "${HOME}"/.googledrive.conf - else - printCenter "justify" "Given root folder " " ID/URL invalid." "=" - exit 1 - fi + updateConfig ROOT_FOLDER "${ROOT_FOLDER}" "${CONFIG:-${HOME}/.googledrive.conf}" + else + "${QUIET:-printCenter}" "justify" "Given root folder " " ID/URL invalid." "=" + exit 1 fi + } + if [[ -n ${ROOTDIR} ]]; then + ROOT_FOLDER="${ROOTDIR//[[:space:]]/}" + { [[ -n ${ROOT_FOLDER} ]] && checkROOTID; } || : elif [[ -z ${ROOT_FOLDER} ]]; then read -r -p "Root Folder ID or URL (Default: root): " ROOT_FOLDER ROOT_FOLDER="${ROOT_FOLDER//[[:space:]]/}" if [[ -n ${ROOT_FOLDER} ]]; then - ROOT_FOLDER="$(driveInfo "$(extractID "${ROOT_FOLDER}")" "id" "${ACCESS_TOKEN}")" || { - { [[ ${ROOT_FOLDER} =~ "File not found" ]] && printCenter "justify" "Given root folder " " ID/URL invalid." "="; } || { printf "%s\n" "${ROOT_FOLDER}"; } - exit 1 - } - if [[ -n ${ROOT_FOLDER} ]]; then - updateConfig ROOT_FOLDER "${ROOT_FOLDER}" "${HOME}"/.googledrive.conf - else - printCenter "justify" "Given root folder " " ID/URL invalid." "=" - exit 1 - fi + checkROOTID else - ROOT_FOLDER=root - updateConfig ROOT_FOLDER "${ROOT_FOLDER}" "${HOME}"/.googledrive.conf + ROOT_FOLDER="root" + updateConfig ROOT_FOLDER "${ROOT_FOLDER}" "${CONFIG:-${HOME}/.googledrive.conf}" fi fi } @@ -967,15 +916,10 @@ processArguments() { # Check if the argument is a file or a directory. if [[ -f ${INPUT} ]]; then printCenter "justify" "Given Input" ": FILE" "=" - if [[ -n ${OVERWRITE} || -n ${SKIP_DUPLICATES} ]]; then - printCenter "justify" "Upload Method" ": ${SKIP_DUPLICATES:-$OVERWRITE}" "-" && newLine "\n" - uploadFile update "${INPUT}" "${WORKSPACE_FOLDER_ID}" "${ACCESS_TOKEN}" - else - printCenter "justify" "Upload Method" ": Create" "-" && newLine "\n" - uploadFile create "${INPUT}" "${WORKSPACE_FOLDER_ID}" "${ACCESS_TOKEN}" - fi + printCenter "justify" "Upload Method" ": ${SKIP_DUPLICATES:-${OVERWRITE:-Create}}" "=" && newLine "\n" + uploadFile "${UPLOAD_METHOD:-create}" "${INPUT}" "${WORKSPACE_FOLDER_ID}" "${ACCESS_TOKEN}" FILE_ID="${SKIP_DUPLICATES_FILE_ID:-${FILE_ID}}" - [[ ${UPLOAD_STATUS} = ERROR ]] && for _ in {1..2}; do clearLine 1; done && return 1 + [[ ${UPLOAD_STATUS} = ERROR ]] && for _ in {1..2}; do clearLine 1; done && continue if [[ -n "${SHARE}" ]]; then printCenter "justify" "Sharing the file.." "-" if SHARE_MSG="$(shareID "${FILE_ID}" "${ACCESS_TOKEN}" "${SHARE_EMAIL}")"; then @@ -983,24 +927,19 @@ processArguments() { else clearLine 1 fi - printCenter "justify" "DriveLink" " (SHARED)" "-" - else - printCenter "justify" "DriveLink" "-" fi + printCenter "justify" "DriveLink" "${SHARE:-}" "-" isTerminal && printCenter "normal" "$(printf "\xe2\x86\x93 \xe2\x86\x93 \xe2\x86\x93\n")" " " printCenter "normal" "${FILE_LINK}" " " printf "\n" elif [[ -d ${INPUT} ]]; then - unset EMPTY - # Unset PARALLEL value if input is file, for preserving the logging output. - [[ -n ${PARALLEL_UPLOAD} ]] && { parallel=true || unset parallel; } - - FOLDER_NAME="${INPUT##*/}" + INPUT="$(cd "${INPUT}" && pwd)" # to handle dirname when current directory (.) is given as input. + unset EMPTY # Used when input folder is empty + parallel="${PARALLEL_UPLOAD:-}" # Unset PARALLEL value if input is file, for preserving the logging output. printCenter "justify" "Upload Method" ": ${SKIP_DUPLICATES:-${OVERWRITE:-Create}}" "=" - printCenter "justify" "Given Input" ": FOLDER" "-" && newLine "\n" - printCenter "justify" "Folder: ${FOLDER_NAME}" "=" + FOLDER_NAME="${INPUT##*/}" && printCenter "justify" "Folder: ${FOLDER_NAME}" "=" NEXTROOTDIRID="${WORKSPACE_FOLDER_ID}" @@ -1011,46 +950,34 @@ processArguments() { if [[ -n ${FILENAMES[0]} ]]; then NO_OF_FILES="${#FILENAMES[@]}" for _ in {1..2}; do clearLine 1; done - if isNotQuiet; then - printCenter "justify" "Folder: ${FOLDER_NAME} " "| ${NO_OF_FILES} File(s)" "=" && printf "\n" - else - printCenterQuiet "[ Folder: ${FOLDER_NAME} | ${NO_OF_FILES} File(s) ]" - fi - ID="$(createDirectory "${INPUT}" "${NEXTROOTDIRID}" "${ACCESS_TOKEN}")" + "${QUIET:-printCenter}" "justify" "Folder: ${FOLDER_NAME} " "| ${NO_OF_FILES} File(s)" "=" && printf "\n" + printCenter "justify" "Creating folder.." "-" + ID="$(createDirectory "${INPUT}" "${NEXTROOTDIRID}" "${ACCESS_TOKEN}")" && clearLine 1 DIRIDS[1]="${ID}" if [[ -n ${parallel} ]]; then { [[ ${NO_OF_PARALLEL_JOBS} -gt ${NO_OF_FILES} ]] && NO_OF_PARALLEL_JOBS_FINAL="${NO_OF_FILES}"; } || { NO_OF_PARALLEL_JOBS_FINAL="${NO_OF_PARALLEL_JOBS}"; } # Export because xargs cannot access if it is just an internal variable. - export ID CURL_ARGS="-s" PARALLEL ACCESS_TOKEN STRING OVERWRITE COLUMNS API_URL API_VERSION LOG_FILE_ID SKIP_DUPLICATES - export -f uploadFile printCenter clearLine jsonValue urlEncode checkExistingFile isNotQuiet + export ID CURL_ARGS="-s" ACCESS_TOKEN STRING OVERWRITE COLUMNS API_URL API_VERSION LOG_FILE_ID SKIP_DUPLICATES QUIET UPLOAD_METHOD + export -f uploadFile printCenter clearLine jsonValue urlEncode checkExistingFile printCenterQuiet newLine bytesToHuman curlCmd [[ -f ${TMPFILE}SUCCESS ]] && rm "${TMPFILE}"SUCCESS [[ -f ${TMPFILE}ERROR ]] && rm "${TMPFILE}"ERROR # shellcheck disable=SC2016 printf "%s\n" "${FILENAMES[@]}" | xargs -n1 -P"${NO_OF_PARALLEL_JOBS_FINAL}" -i bash -c ' - if [[ -n ${OVERWRITE} || -n ${SKIP_DUPLICATES} ]]; then - uploadFile update "{}" "${ID}" "${ACCESS_TOKEN}" parallel - else - uploadFile create "{}" "${ID}" "${ACCESS_TOKEN}" parallel - fi - ' 1>| "${TMPFILE}"SUCCESS 2>| "${TMPFILE}"ERROR & + uploadFile "${UPLOAD_METHOD:-create}" "{}" "${ID}" "${ACCESS_TOKEN}" parallel + ' 1>| "${TMPFILE}"SUCCESS 2>| "${TMPFILE}"ERROR & while true; do [[ -f "${TMPFILE}"SUCCESS || -f "${TMPFILE}"ERROR ]] && { break || bashSleep 0.5; }; done + newLine "\n" ERROR_STATUS=0 SUCCESS_STATUS=0 while true; do SUCCESS_STATUS="$(count < "${TMPFILE}"SUCCESS)" ERROR_STATUS="$(count < "${TMPFILE}"ERROR)" bashSleep 1 - if isNotQuiet; then - if [[ $(((SUCCESS_STATUS + ERROR_STATUS))) != "${TOTAL}" ]]; then - clearLine 1 && printCenter "justify" "Status" ": ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" "=" - fi - else - if [[ $(((SUCCESS_STATUS + ERROR_STATUS))) != "${TOTAL}" ]]; then - clearLine 1 && printCenterQuiet "Status: ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" - fi + if [[ $(((SUCCESS_STATUS + ERROR_STATUS))) != "${TOTAL}" ]]; then + clearLine 1 && "${QUIET:-printCenter}" "justify" "Status" ": ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" "=" fi TOTAL="$(((SUCCESS_STATUS + ERROR_STATUS)))" [[ ${TOTAL} = "${NO_OF_FILES}" ]] && break @@ -1063,18 +990,13 @@ processArguments() { ERROR_STATUS=0 SUCCESS_STATUS=0 for file in "${FILENAMES[@]}"; do DIRTOUPLOAD="${ID}" - if [[ -n ${OVERWRITE} || -n ${SKIP_DUPLICATES} ]]; then - uploadFile update "${file}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" - else - uploadFile create "${file}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" - fi + uploadFile "${UPLOAD_METHOD:-create}" "${file}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" [[ ${UPLOAD_STATUS} = ERROR ]] && ERROR_STATUS="$((ERROR_STATUS + 1))" || SUCCESS_STATUS="$((SUCCESS_STATUS + 1))" || : - if [[ ${VERBOSE} = true || ${VERBOSE_PROGRESS} = true ]]; then + if [[ -n ${VERBOSE:-${VERBOSE_PROGRESS}} ]]; then printCenter "justify" "Status: ${SUCCESS_STATUS} Uploaded" " | ${ERROR_STATUS} Failed" "=" && newLine "\n" else for _ in {1..2}; do clearLine 1; done printCenter "justify" "Status: ${SUCCESS_STATUS} Uploaded" " | ${ERROR_STATUS} Failed" "=" - fi done fi @@ -1096,18 +1018,10 @@ processArguments() { if [[ -n ${FILENAMES[0]} ]]; then NO_OF_FILES="${#FILENAMES[@]}" for _ in {1..3}; do clearLine 1; done - if isNotQuiet; then - if [[ ${NO_OF_SUB_FOLDERS} != 0 ]]; then - printCenter "justify" "${FOLDER_NAME} " "| ${NO_OF_FILES} File(s) | ${NO_OF_SUB_FOLDERS} Sub-folders" "=" - else - printCenter "justify" "${FOLDER_NAME} " "| ${NO_OF_FILES} File(s)" "=" - fi + if [[ ${NO_OF_SUB_FOLDERS} != 0 ]]; then + "${QUIET:-printCenter}" "justify" "${FOLDER_NAME} " "| ${NO_OF_FILES} File(s) | ${NO_OF_SUB_FOLDERS} Sub-folders" "=" else - if [[ ${NO_OF_SUB_FOLDERS} != 0 ]]; then - printCenterQuiet "[ ${FOLDER_NAME} | ${NO_OF_FILES} File(s) | ${NO_OF_SUB_FOLDERS} Sub-folders ]" - else - printCenterQuiet "[ ${FOLDER_NAME} | ${NO_OF_FILES} File(s) ]" - fi + "${QUIET:-printCenter}" "justify" "${FOLDER_NAME} " "| ${NO_OF_FILES} File(s)" "=" fi newLine "\n" printCenter "justify" "Creating Folder(s).." "-" @@ -1150,23 +1064,19 @@ processArguments() { if [[ -n ${parallel} ]]; then { [[ ${NO_OF_PARALLEL_JOBS} -gt ${NO_OF_FILES} ]] && NO_OF_PARALLEL_JOBS_FINAL="${NO_OF_FILES}"; } || { NO_OF_PARALLEL_JOBS_FINAL="${NO_OF_PARALLEL_JOBS}"; } # Export because xargs cannot access if it is just an internal variable. - export CURL_ARGS="-s" ACCESS_TOKEN STRING OVERWRITE COLUMNS API_URL API_VERSION LOG_FILE_ID QUIET - export -f uploadFile printCenter clearLine jsonValue urlEncode checkExistingFile isNotQuiet printCenterQuiet newLine + export CURL_ARGS="-s" ACCESS_TOKEN STRING OVERWRITE COLUMNS API_URL API_VERSION LOG_FILE_ID SKIP_DUPLICATES QUIET UPLOAD_METHOD + export -f uploadFile printCenter clearLine jsonValue urlEncode checkExistingFile printCenterQuiet newLine bytesToHuman curlCmd [[ -f "${TMPFILE}"SUCCESS ]] && rm "${TMPFILE}"SUCCESS [[ -f "${TMPFILE}"ERROR ]] && rm "${TMPFILE}"ERROR # shellcheck disable=SC2016 printf "%s\n" "${FINAL_LIST[@]}" | xargs -n1 -P"${NO_OF_PARALLEL_JOBS_FINAL}" -i bash -c ' - LIST="{}" - FILETOUPLOAD="${LIST//*"|:_//_:|"}" - DIRTOUPLOAD="$(: "|:_//_:|""${FILETOUPLOAD}" && : "${LIST::-${#_}}" && printf "%s\n" "${_//*"|:_//_:|"}")" - if [[ -n ${OVERWRITE} || -n ${SKIP_DUPLICATES} ]]; then - uploadFile update "${FILETOUPLOAD}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" parallel - else - uploadFile create "${FILETOUPLOAD}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" parallel - fi - ' 1>| "${TMPFILE}"SUCCESS 2>| "${TMPFILE}"ERROR & + LIST="{}" + FILETOUPLOAD="${LIST//*"|:_//_:|"}" + DIRTOUPLOAD="$(: "|:_//_:|""${FILETOUPLOAD}" && : "${LIST::-${#_}}" && printf "%s\n" "${_//*"|:_//_:|"}")" + uploadFile "${UPLOAD_METHOD:-create}" "${FILETOUPLOAD}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" parallel + ' 1>| "${TMPFILE}"SUCCESS 2>| "${TMPFILE}"ERROR & while true; do [[ -f "${TMPFILE}"SUCCESS || -f "${TMPFILE}"ERROR ]] && { break || bashSleep 0.5; }; done @@ -1175,14 +1085,8 @@ processArguments() { SUCCESS_STATUS="$(count < "${TMPFILE}"SUCCESS)" ERROR_STATUS="$(count < "${TMPFILE}"ERROR)" bashSleep 1 - if isNotQuiet; then - if [[ $(((SUCCESS_STATUS + ERROR_STATUS))) != "${TOTAL}" ]]; then - clearLine 1 && printCenter "justify" "Status" ": ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" "=" - fi - else - if [[ $(((SUCCESS_STATUS + ERROR_STATUS))) != "${TOTAL}" ]]; then - clearLine 1 && printCenterQuiet "Status: ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" - fi + if [[ $(((SUCCESS_STATUS + ERROR_STATUS))) != "${TOTAL}" ]]; then + clearLine 1 && "${QUIET:-printCenter}" "justify" "Status" ": ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" "=" fi TOTAL="$(((SUCCESS_STATUS + ERROR_STATUS)))" [[ ${TOTAL} = "${NO_OF_FILES}" ]] && break @@ -1196,13 +1100,9 @@ processArguments() { for LIST in "${FINAL_LIST[@]}"; do FILETOUPLOAD="${LIST//*"|:_//_:|"/}" DIRTOUPLOAD="$(: "|:_//_:|""${FILETOUPLOAD}" && : "${LIST::-${#_}}" && printf "%s\n" "${_//*"|:_//_:|"/}")" - if [[ -n ${OVERWRITE} || -n ${SKIP_DUPLICATES} ]]; then - uploadFile update "${FILETOUPLOAD}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" - else - uploadFile create "${FILETOUPLOAD}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" - fi + uploadFile "${UPLOAD_METHOD:-create}" "${FILETOUPLOAD}" "${DIRTOUPLOAD}" "${ACCESS_TOKEN}" [[ ${UPLOAD_STATUS} = ERROR ]] && ERROR_STATUS="$((ERROR_STATUS + 1))" || SUCCESS_STATUS="$((SUCCESS_STATUS + 1))" || : - if [[ -n ${VERBOSE} || -n ${VERBOSE_PROGRESS} ]]; then + if [[ -n ${VERBOSE:-${VERBOSE_PROGRESS}} ]]; then printCenter "justify" "Status" ": ${SUCCESS_STATUS} Uploaded | ${ERROR_STATUS} Failed" "=" && newLine "\n" else for _ in {1..2}; do clearLine 1; done @@ -1225,30 +1125,19 @@ processArguments() { else clearLine 1 fi - printCenter "justify" "FolderLink " " (SHARED)" "=" - else - printCenter "justify" "FolderLink" "=" fi + printCenter "justify" "FolderLink" "${SHARE:-}" "-" isTerminal && printCenter "normal" "$(printf "\xe2\x86\x93 \xe2\x86\x93 \xe2\x86\x93\n")" " " printCenter "normal" "$(: "$(read -r firstline <<< "${DIRIDS[1]}" && printf "%s\n" "${firstline/"|:_//_:|"*/}")" && printf "%s\n" "${_/$_/https://drive.google.com/open?id=$_}")" " " fi newLine "\n" - if isNotQuiet; then - [[ ${SUCCESS_STATUS} -gt 0 ]] && printCenter "justify" "Total Files " "Uploaded: ${SUCCESS_STATUS}" "=" - [[ ${ERROR_STATUS} -gt 0 ]] && printCenter "justify" "Total Files " "Failed: ${ERROR_STATUS}" "=" - else - [[ ${SUCCESS_STATUS} -gt 0 ]] && printCenterQuiet "[ Total Files Uploaded: ${SUCCESS_STATUS} ]" - [[ ${ERROR_STATUS} -gt 0 ]] && printCenterAuiet "[ Total Files Failed: ${ERROR_STATUS} ]" - fi + [[ ${SUCCESS_STATUS} -gt 0 ]] && "${QUIET:-printCenter}" "justify" "Total Files " "Uploaded: ${SUCCESS_STATUS}" "=" + [[ ${ERROR_STATUS} -gt 0 ]] && "${QUIET:-printCenter}" "justify" "Total Files " "Failed: ${ERROR_STATUS}" "=" printf "\n" else - if isNotQuiet; then - for _ in {1..2}; do clearLine 1; done - printCenter 'justify' "Empty Folder." "-" - else - printCenterQuiet "Empty Folder.\n" - fi + for _ in {1..2}; do clearLine 1; done + "${QUIET:-printCenter}" 'justify' "Empty Folder." "-" printf "\n" fi fi @@ -1258,11 +1147,9 @@ processArguments() { main() { [[ $# = 0 ]] && shortHelp - # To cleanup subprocesses. - trap 'exit' INT TERM && trap 'kill -- $$' EXIT + trap 'exit "$?"' INT TERM && trap 'exit "$?"' EXIT - set -o errexit -o noclobber -o pipefail - checkBashVersion + checkBashVersion && set -o errexit -o noclobber -o pipefail setupArguments "${@}" checkDebug && checkInternet @@ -1288,11 +1175,7 @@ main() { END="$(printf "%(%s)T\\n" "-1")" DIFF="$((END - START))" - if isNotQuiet; then - printCenter "normal" " Time Elapsed: ""$((DIFF / 60))"" minute(s) and ""$((DIFF % 60))"" seconds. " "=" - else - printCenterQuiet "Time Elapsed: ""$((DIFF / 60))"" minute(s) and ""$((DIFF % 60))"" seconds." - fi + "${QUIET:-printCenter}" "normal" " Time Elapsed: ""$((DIFF / 60))"" minute(s) and ""$((DIFF % 60))"" seconds. " "=" } main "${@}" From 04059f9b7d6f48bba8d0ab194fff3b2ed9b1eb46 Mon Sep 17 00:00:00 2001 From: Akianonymus Date: Wed, 13 May 2020 11:41:29 +0530 Subject: [PATCH 2/3] Rewrite README * Add table of contents for easy navigation * Add star, latest release and license badge to README --- README.md | 394 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 258 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index d6d50ed..bd60569 100755 --- a/README.md +++ b/README.md @@ -1,37 +1,97 @@ -# Google drive upload +

Google drive upload

+

+Stars +Latest Release +License +

-Google drive upload is a Bash script based on v3 google APIs to upload files/directories into google drive. This is a minimalistic shell script which utilizes google OAuth2.0 device flow to generate access tokens to authorize application to upload files to your google drive. +> Google drive upload is a bash compliant script based on v3 google APIs. -Further usage documentation can be found at my blog page [Labbots.com](https://labbots.com/google-drive-upload-bash-script/ "Labbots.com"). +> It utilizes google OAuth2.0 to generate access tokens and to authorize application for uploading files/folders to your google drive. -## Dependencies +- Minimal +- Upload or Update files/folders +- Recursive folder uploading +- Sync your folders + - Overwrite or skip existing files. +- Resume Interrupted Uploads +- Share files/folders + - To anyone or a specific email. +- Config file support + - Easy to use on multiple machines. +- Latest gdrive api used i.e v3 +- Pretty logging +- Easy to install and update -This script does not have very many dependencies. Most of the dependencies are available by default in most linux platforms. This script explicitly requires the following packages: +## Table of Contents -- Bash ( 4.x + ) -- Curl -- grep -- sed ( Stream editor ) -- file/mimetype ( generating mimetype for extensionless files ) -- find command ( for recursive uploading ) -- xargs ( for parallel uploading ) +- [Compatibility](#compatibility) + - [Linux or MacOS](#linux-or-macos) + - [Android](#android) + - [iOS](#ios) + - [Windows](#windows) +- [Installing and Updating](#installing-and-updating) + - [Native Dependencies](#native-dependencies) + - [Installation](#installation) + - [Basic Method](#basic-method) + - [Advanced Method](#advanced-method) + - [Updation](#updation) +- [Usage](#usage) + - [Generating Oauth Credentials](#generating-oauth-credentials) + - [First Run](#first-run) + - [Upload](#upload) + - [Custom Flags](#custom-flags) + - [Resuming Interrupted Uploads](#resuming-interrupted-uploads) +- [Inspired By](#inspired-by) +- [License](#license) -## Features +## Compatibility -- Upload files and folders normally/parallelly. -- Upload sub-folders and content inside it hierarchically. -- Upload multiple files / folders with single command. -- Update (overwrite) your files/folder, like synchronisation. -- Config file support ( easy to use script on multiple machines ). -- Uses latest gdrive v3 api. -- Share files after uploading ( to an email or just anyone ). -- Pretty Logging. -- Easy to install and update. -- Resume interrupted uploads. +As this is a bash script, there aren't many dependencies. See [Native Dependencies](#native-dependencies) after this section for explicitly required program list. -## Installation +### Linux or MacOS -### Default values set by installation script +For Linux or MacOS, you hopefully don't need to configure anything extra, it should work by default. + +### Android + +Install [Termux](https://wiki.termux.com/wiki/Main_Page) and done. + +It's fully tested for all usecases of this script. + +### iOS + +Install [iSH](https://ish.app/) + +While it has not been officially tested, but should work given the description of the app. Report if you got it working by creating an issue. + +### Windows + +Use [Windows Subsystem](https://docs.microsoft.com/en-us/windows/wsl/install-win10) + +Again, it has not been officially tested on windows, their shouldn't be anything preventing it from working. Report if you got it working by creating an issue. + +## Installing and Updating + +### Native Dependencies + +The script explicitly requires the following programs: + +| Program | Role In Script | +| --------| -------------- | +| Bash | Execution of script | +| Curl | All network requests | +| file/mimetype | Mimetype generation for extension less files | +| find | To find files and folders for recursive folder uploads | +| xargs | For parallel uploading | +| grep | Miscellaneous | +| sed | Miscellaneous | + +### Installation + +You can install the script by automatic installation script provided in the repository. + +Default values set by automatic installation script: Repo: `labbots/google-drive-upload` @@ -47,51 +107,73 @@ Shell file: `.bashrc` or `.zshrc` or `.profile` For custom command names, repo, shell file, etc, see advanced installation method. -### Basic Installation +**Now, for automatic install script, there are two ways:** + +#### Basic Method To install google-drive-upload in your system, you can run the below command: -`curl --compressed -# -o- https://raw.githubusercontent.com/labbots/google-drive-upload/master/install.sh | bash` +```shell +bash <(curl --compressed -s https://raw.githubusercontent.com/labbots/google-drive-upload/master/install.sh) +``` and done. -### Advanced Installation +#### Advanced Method + +This section provides information on how to utilise the install.sh script for custom usescases. -This section provides on how to utilise the install.sh script fully for custom usescases. +These are the flags that are available in the install.sh script: -First, we need to download the script, run the following command: +- **-i | --interactive** -`curl --compressed -# https://raw.githubusercontent.com/labbots/google-drive-upload/master/install.sh -o install.sh` + Install script interactively, will ask for all the variables one by one. -These are the flags that are available in the install.sh script: + Note: This will disregard all arguments given with below flags. + +- **-p | --path ** + + Custom path where you want to install the script. + +- **-c | --cmd ** - -i | --interactive - Install script interactively, will ask for all the varibles one by one. + Custom command name, after installation, script will be available as the input argument. - Note: This will disregard all arguments given with below flags. +- **-r | --repo ** - -p | --path - Custom path where you want to install script. + Install script from your custom repo, e.g --repo labbots/google-drive-upload, make sure your repo file structure is same as official repo. - -c | --cmd - Custom command name, after installation script will be available as the input argument. +- **-B | --branch ** - -r | --repo - Upload script from your custom repo,e.g --repo labbots/google-drive-upload, make sure your repo file structure is same as official repo. + Specify branch name for the github repo, applies to custom and default repo both. - -B | --branch - Specify branch name for the github repo, applies to custom and default repo both. +- **-R | --release ** - -R | --release - Specify tag name for the github repo, applies to custom and default repo both. + Specify tag name for the github repo, applies to custom and default repo both. - -s | --shell-rc - Specify custom rc file, where PATH is appended, by default script detects .zshrc and .bashrc. +- **-s | --shell-rc ** - -D | --debug - Display script command trace. + Specify custom rc file, where PATH is appended, by default script detects .zshrc, .bashrc. and .profile. - -h | --help - Display usage instructions. +- **-D | --debug** + + Display script command trace. + +- **-h | --help** + + Display usage instructions. Now, run the script and use flags according to your usecase. -E.g: `bash install.sh -r username/reponame -p somepath -s shell_file -c command_name -B branch_name` +E.g: + +```shell +bash <(curl --compressed -s https://raw.githubusercontent.com/labbots/google-drive-upload/master/install.sh) -r username/reponame -p somepath -s shell_file -c command_name -B branch_name +``` -## Updation +### Updation -If you have followed the above method to install the script, then you can automatically update the script. +If you have followed the automatic method to install the script, then you can automatically update the script. There are two methods: @@ -101,131 +183,171 @@ There are two methods: This will update the script where it is installed. - **If you use the this flag without actually installing the script, e.g just by `bash upload.sh -u` then it will install the script or update if already installed.** + **If you use the this flag without actually installing the script,** + + **e.g just by `bash upload.sh -u` then it will install the script or update if already installed.** 2. Run the installation script again. Yes, just run the installation script again as we did in install section, and voila, it's done. -### Note: Both above methods obeys the values set by user in advanced installation, e.g if you have installed the script with different repo, say `myrepo/gdrive-upload`, then the update will be also fetched from the same repo +**Note: Both above methods obeys the values set by user in advanced installation,** +**e.g if you have installed the script with different repo, say `myrepo/gdrive-upload`, then the update will be also fetched from the same repo.** ## Usage -When the script is executed for the first time. It asks for few configuration variables interactively to connect with google APIs. The script requires Client ID and Client secret to access the APIs which can be generated at [google console]. +First, we need to obtain our Oauth credentials, here's how to do it: + +### Generating Oauth Credentials + +- Log into google developer console at [google console](https://console.developers.google.com/). +- Create new Project or use existing project. +- Creating new OAuth 2.0 Credentials: + - Select Application type "other". + - Provide name for the new credentials. ( anything ) + - This would provide a new Client ID and Client Secret. + - Download your credentials.json by clicking on the download button. +- Enable Google Drive API for the project under "Library". + +Now, we have obtained our credentials, move to next section to use those credentials to setup: + +### First Run + +On first run, the script asks for all the required credentials, which we have obtained in the previous section. + +Execute the script: `gupload filename` + +Now, it will ask for following credentials: + +**Client ID:** Copy and paste from credentials.json -### Generating Google OAuth2 Credentials - -- Log into google developer console at [google console]. -- Create new Project or use existing project if you don't have a project already created. -- Create new OAuth 2.0 Credentials - - Select Application type "other" - - Provide name for the new credentials - - This would provide a new client ID and Client Secret -- Enable Google Drive API for the project under "Library" +**Client Secret:** Copy and paste from credentials.json -*Note:* When the script is run for the first time. The script must be authorized in browser. This may show a certificate warning if the application authorized domain is not configured on google console project. +**Refresh Token:** If you have previously generated a refresh token authenticated to your account, then enter it, otherwise leave blank. +If you don't have refresh token, script outputs a URL on the terminal script, open that url in a web browser and tap on allow. Copy the code and paste in the terminal. -### Running the script +**Root Folder:** Gdrive folder url/id from your account which you want to set as root folder. You can leave it blank and it takes `root` folder as default. -Script also asks for root folder to be set to which the script uploads documents by default. -The default folder will be the root of your google drive. If you want to upload documents to any specific directory by default then provide the google folder ID or URL of that folder when root folder ID/URL is requested. +If everything went fine, all the required credentials have been set, read the next section on how to upload a file/folder. -For example -`https://drive.google.com/drive/folders/8ZzWiO_pMAtBrLpARBZJNV09TVmM` +### Upload -In the above URL. The folder id is ***8ZzWiO_pMAtBrLpARBZJNV09TVmM*** +For uploading files, the syntax is simple; -Note: You can input either URL or ID. +`gupload filename/foldername gdrive_folder_name` -The default configurations of the script are store under **$HOME/.googledrive.conf** +where `filename/foldername` is input file/folder and `gdrive_folder_name` is the name of the folder on gdrive, where the input file/folder will be uploaded. -The script can be used in the following way +If gdrive_folder_name is present on gdrive, then script will upload there, else will make a folder with that name. - ./upload.sh -Above command will create a folder under the pre-configured root directory and upload the specified file under the folder name. If the folder already exists then the file is uploaded under the folder. +Apart from basic usage, this script provides many flags for custom usecases, like parallel uploading, skipping upload of existing files, overwriting, etc. -Other Options available are +### Custom Flags - -C | --create-dir - option to create directory. Will provide folder id. Can be used to specify workspace folder for uploading files/folders. +These are the custom flags that are currently implemented: - -r | --root-dir - google folder id or url to which the file/directory to upload. - - -s | --skip-subdirs - Skip creation of sub folders and upload all files inside the INPUT folder/sub-folders in the INPUT folder, use this along with -p/--parallel option to speed up the uploads. - - -p | --parallel - Upload multiple files in parallel, Max value = 10, use with folders. - - Note : - This command is only helpful if you are uploading many files which aren't big enough to utilise your full bandwidth, using it otherwise will not speed up your upload and even error sometimes, - 1 - 6 value is recommended, but can use upto 10. If errors with a high value, use smaller number. - Beaware, this isn't magic, obviously it comes at a cost of increased cpu/ram utilisation as it forks multiple bash processes to upload ( google how xargs works with -P option ). - - -o | --overwrite - Overwrite the files with the same name, if present in the root folder/input folder, also works with recursive folders and single/multiple files. - - Note : - If you use this flag along with -d/--skip-duplicates, the skip duplicates flag is preferred. - - -d | --skip-duplicates - Do not upload the files with the same name, if already present in the root folder/input folder, also works with recursive folders. - - -f | --[file/folder] - Specify files and folders explicitly in one command, use multiple times for multiple folder/files. +- **-z | --config** - For uploading multiple input into the same folder: - 1. Use -C / --create-dir ( e.g `./upload.sh -f file1 -f folder1 -f file2 -C ` ) option. - 2. Give two initial arguments which will use the second argument as the folder you wanna upload ( e.g: `./upload.sh filename -f filename -f foldername` ). - - This flag can also be used for uploading files/folders which have `-` character in their name, normally it won't work, because of the flags, but using `-f -[file|folder]namewithhyphen` works. Applies for -C/--create-dir too. - Also, as specified by longflags ( `--[file|folder]` ), you can simultaneously upload a folder and a file. - Incase of multiple -f flag having duplicate arguments, it takes the last duplicate of the argument to upload, in the same order provided. - - -S | --share - Share the uploaded input file/folder, grant reader permission to provided email address or to everyone with the shareable link. - - -q | --quiet - Supress the normal output, only show success/error upload messages for files, and one extra line at the beginning for folder showing no. of files and sub folders. - - -v | --verbose - Display detailed message (only for non-parallel uploads). - - -V | --verbose-progress - Display detailed message and detailed upload progress(only for non-parallel uploads). - - -i | --save-info - Save uploaded files info to the given filename." - - -u | --update - Update the installed script in your system, if not installed, then install. - - --info - Show detailed info, only if script is installed system wide. - - -h | --help - Display usage instructions. - - -z | --config - Override default config file with custom config file. - - -D | --debug - Display script command trace." + Override default config file with custom config file. -To create a folder: + Default Config: `"${HOME}/.googledrive.conf` - ./upload.sh -C -r +- **-C | --create-dir ** -This will give the folder id of the newly created folder which can be used to upload files to specific directory in google drive. + Option to create directory. Will provide folder id. Can be used to specify workspace folder for uploading files/folders. -To Upload file to specific google folder +- **-r | --root-dir ** - ./upload.sh -r + Google folder id or url to which the file/directory to upload. -The script also allows to upload directories. If directory path is provided as argument to the script then the script recursively uploads all the sub-folder and files present in the heirarchial way as it is present on the local machine. +- **-s | --skip-subdirs** -### Resume interrupted uploads. + Skip creation of sub folders and upload all files inside the INPUT folder/sub-folders in the INPUT folder, use this along with -p/--parallel option to speed up the uploads. -Either interrupted due to bad internet connection or manual interruption. - - - It checks 3 things, filesize, name and workspace folder. If a upload was interrupted, then resumable upload link is saved in "$HOME/.google-drive-upload/", later on when running the same command as before, if applicable, resumes the upload from the same position as before. - - Small files cannot be resumed, less that 1 MB, and the amount of size uploaded should be more than 1 MB to resume. - - No progress bars for resumable uploads it messes up with output. - - You can interrupt many times you want, it will resume( hopefully ). +- **-p | --parallel ** + + Upload multiple files in parallel, Max value = 10, use with folders. + + Note: + - This command is only helpful if you are uploading many files which aren't big enough to utilise your full bandwidth, using it otherwise will not speed up your upload and even error sometimes, + - 1 - 6 value is recommended, but can use upto 10. If errors with a high value, use smaller number. + - Beaware, this isn't magic, obviously it comes at a cost of increased cpu/ram utilisation as it forks multiple bash processes to upload ( google how xargs works with -P option ). + +- **-o | --overwrite** + + Overwrite the files with the same name, if present in the root folder/input folder, also works with recursive folders and single/multiple files. + + Note: If you use this flag along with -d/--skip-duplicates, the skip duplicates flag is preferred. + +- **-d | --skip-duplicates** + + Do not upload the files with the same name, if already present in the root folder/input folder, also works with recursive folders. + +- **-f | --[file/folder]** + + Specify files and folders explicitly in one command, use multiple times for multiple folder/files. + + For uploading multiple input into the same folder: + + - Use -C / --create-dir ( e.g `./upload.sh -f file1 -f folder1 -f file2 -C ` ) option. + - Give two initial arguments which will use the second argument as the folder you wanna upload ( e.g: `./upload.sh filename -f filename -f foldername` ). + + This flag can also be used for uploading files/folders which have `-` character in their name, normally it won't work, because of the flags, but using `-f -[file|folder]namewithhyphen` works. Applies for -C/--create-dir too. + + Also, as specified by longflags ( `--[file|folder]` ), you can simultaneously upload a folder and a file. + + Incase of multiple -f flag having duplicate arguments, it takes the last duplicate of the argument to upload, in the same order provided. + +- **-S | --share ** + + Share the uploaded input file/folder, grant reader permission to provided email address or to everyone with the shareable link. + +- **-q | --quiet** + + Supress the normal output, only show success/error upload messages for files, and one extra line at the beginning for folder showing no. of files and sub folders. + +- **-v | --verbose** + + Dislay detailed message (only for non-parallel uploads). + +- **-V | --verbose-progress** + + Display detailed message and detailed upload progress(only for non-parallel uploads). + +- **-i | --save-info ** + + Save uploaded files info to the given filename." + +- **-u | --update** + + Update the installed script in your system, if not installed, then install. + +- **--info** + + Show detailed info, only if script is installed system wide. + +- **-h | --help** + + Display usage instructions. + +- **-D | --debug** + + Display script command trace. + +### Resuming Interrupted Uploads + +Uploads interrupted either due to bad internet connection or manual interruption, can be resumed from the same position. + +- Script checks 3 things, filesize, name and workspace folder. If an upload was interrupted, then resumable upload link is saved in `"$HOME/.google-drive-upload/"`, which later on when running the same command as before, if applicable, resumes the upload from the same position as before. +- Small files cannot be resumed, less that 1 MB, and the amount of size uploaded should be more than 1 MB to resume. +- No progress bars for resumable uploads as it messes up with output. +- You can interrupt many times you want, it will resume ( hopefully ). ## Inspired By -- [github-bashutils] - soulseekah/bash-utils -- [deanet-gist] - Uploading File into Google Drive +- [github-bashutils](https://github.com/soulseekah/bash-utils) - soulseekah/bash-utils +- [deanet-gist](https://gist.github.com/deanet/3427090) - Uploading File into Google Drive ## License MIT - -[github-bashutils]: -[deanet-gist]: -[google console]: From 72c426be007b10927a0f5188097080ad0adb1c2f Mon Sep 17 00:00:00 2001 From: Akianonymus Date: Wed, 13 May 2020 13:05:59 +0530 Subject: [PATCH 3/3] Add -U/--uninstall flag --- README.md | 23 +++++++++++++++++++++++ install.sh | 41 +++++++++++++++++++++++++++++++++++------ upload.sh | 22 +++++++++++++++------- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index bd60569..059712d 100755 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ - [Upload](#upload) - [Custom Flags](#custom-flags) - [Resuming Interrupted Uploads](#resuming-interrupted-uploads) +- [Uninstall](#Uninstall) - [Inspired By](#inspired-by) - [License](#license) @@ -343,6 +344,28 @@ Uploads interrupted either due to bad internet connection or manual interruption - No progress bars for resumable uploads as it messes up with output. - You can interrupt many times you want, it will resume ( hopefully ). +## Uninstall + +If you have followed the automatic method to install the script, then you can automatically uninstall the script. + +There are two methods: + +1. Use the script itself to uninstall the script. + + `gupload -U or gupload --uninstall` + + This will remove the script related files and remove path change from shell file. + +2. Run the installation script again with -U/--uninstall flag + + ```shell + bash <(curl --compressed -s https://raw.githubusercontent.com/labbots/google-drive-upload/master/install.sh) --uninstall + ``` + + Yes, just run the installation script again with the flag and voila, it's done. + +**Note: Both above methods obeys the values set by user in advanced installation,** + ## Inspired By - [github-bashutils](https://github.com/soulseekah/bash-utils) - soulseekah/bash-utils diff --git a/install.sh b/install.sh index 969951e..d792959 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Install or Update google-drive-upload +# Install, Update or Uninstall google-drive-upload usage() { printf " @@ -16,6 +16,7 @@ Options:\n -R | --release - Specify tag name for the github repo, applies to custom and default repo both.\n -B | --branch - Specify branch name for the github repo, applies to custom and default repo both.\n -s | --shell-rc - Specify custom rc file, where PATH is appended, by default script detects .zshrc and .bashrc.\n + -U | --uninstall - Uninstall the script and remove related files.\n -D | --debug - Display script command trace.\n -h | --help - Display usage instructions.\n\n" "${0##*/}" "${HOME}" exit 0 @@ -194,6 +195,21 @@ update() { fi } +# Uninstall the script +uninstall() { + printf "Uninstalling..\n" + __bak="source ${INFO_PATH}/google-drive-upload.binpath" + if sed -i "s|${__bak}||g" "${SHELL_RC}"; then + rm -f "${INSTALL_PATH}/${COMMAND_NAME}" + rm -f "${INFO_PATH}/google-drive-upload.info" + rm -f "${INFO_PATH}/google-drive-upload.binpath" + clearLine 1 + printf "Uninstall complete\n" + else + printf 'Error: Uninstall failed\n' + fi +} + # Setup the varibles and process getopts flags. setupArguments() { [[ $# = 0 ]] && printf "%s: Missing arguments\n" "${FUNCNAME[0]}" && return 1 @@ -242,6 +258,9 @@ setupArguments() { checkLongoptions SHELL_RC="${!OPTIND}" && OPTIND=$((OPTIND + 1)) ;; + uninstall) + UNINSTALL="true" + ;; debug) DEBUG=true ;; @@ -278,7 +297,6 @@ setupArguments() { TYPE=branch TYPE_VALUE="${OPTARG}" ;; - R) TYPE=release TYPE_VALUE="${OPTARG}" @@ -286,6 +304,9 @@ setupArguments() { s) SHELL_RC="${OPTARG}" ;; + U) + UNINSTALL="true" + ;; D) DEBUG=true ;; @@ -346,11 +367,19 @@ main() { if [[ -n ${INTERACTIVE} ]]; then startInteractive fi - - if type -a "${COMMAND_NAME}" &> /dev/null; then - update + if [[ -n ${UNINSTALL} ]]; then + if type -a "${COMMAND_NAME}" &> /dev/null; then + uninstall + else + printf "google-drive-upload is not installed\n" + exit 1 + fi else - install + if type -a "${COMMAND_NAME}" &> /dev/null; then + update + else + install + fi fi } diff --git a/upload.sh b/upload.sh index 38f79d6..8e1a233 100755 --- a/upload.sh +++ b/upload.sh @@ -23,6 +23,7 @@ Options:\n -V | --verbose-progress - Display detailed message and detailed upload progress(only for non-parallel uploads).\n -u | --update - Update the installed script in your system.\n --info - Show detailed info, only if script is installed system wide.\n + -U | --uninstall - Uninstall script, remove related files.\n -D | --debug - Display script command trace.\n -h | --help - Display usage instructions.\n" "${0##*/}" exit 0 @@ -86,9 +87,10 @@ dirname() { printf '%s\n' "${tmp:-/}" } -# Update ( install ) the script +# Update ( install, uninstall ) the script update() { - printf 'Fetching update script..\n' + declare job="${1}" + printf 'Fetching %s script..\n' "${job:-update}" # shellcheck source=/dev/null if [[ -f "${HOME}/.google-drive-upload/google-drive-upload.info" ]]; then source "${HOME}/.google-drive-upload/google-drive-upload.info" @@ -96,18 +98,18 @@ update() { declare REPO="${REPO:-labbots/google-drive-upload}" TYPE_VALUE="${TYPE_VALUE:-latest}" if [[ ${TYPE} = branch ]]; then if __SCRIPT="$(curlCmd -Ls "https://raw.githubusercontent.com/${REPO}/${TYPE_VALUE}/install.sh")"; then - bash <<< "${__SCRIPT}" + bash <(printf "%s\n" "${__SCRIPT}") --"${job:-}" else - printf "Error: Cannot download update script..\n" + printf "Error: Cannot download %s script..\n" "${job:-update}" fi else declare LATEST_SHA LATEST_SHA="$(hash="$(curlCmd -L -s "https://github.com/${REPO}/releases/${TYPE_VALUE}" | grep "=\"/""${REPO}""/commit")" && read -r firstline <<< "${hash}" && : "${hash/*commit\//}" && printf "%s\n" "${_/\"*/}")" if __SCRIPT="$(curlCmd -Ls "https://raw.githubusercontent.com/${REPO}/${LATEST_SHA}/install.sh")"; then - bash <<< "${__SCRIPT}" + bash <(printf "%s\n" "${__SCRIPT}") --"${job:-}" else - printf "Error: Cannot download update script..\n" + printf "Error: Cannot download %s script..\n" "${job:-update}" fi fi } @@ -559,7 +561,7 @@ setupArguments() { REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob" TOKEN_URL="https://accounts.google.com/o/oauth2/token" - SHORTOPTS=":qvVi:sp:odf:Shur:C:Dz:-:" + SHORTOPTS=":qvVi:sp:odf:ShuUr:C:Dz:-:" while getopts "${SHORTOPTS}" OPTION; do case "${OPTION}" in # Parse longoptions # https://stackoverflow.com/questions/402377/using-getopts-to-process-long-and-short-command-line-options/28466267#28466267 @@ -573,6 +575,9 @@ setupArguments() { update) update && exit $? ;; + uninstall) + update uninstall && exit $? + ;; info) versionInfo && exit $? ;; @@ -658,6 +663,9 @@ setupArguments() { u) update && exit $? ;; + U) + update uninstall && exit $? + ;; C) FOLDERNAME="${OPTARG}" ;;