#!/bin/sh -u # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE # Version 2, December 2004 # Copyright (C) 2010-2013 Zakhar @ ubuntu.fr # Everyone is permitted to copy and distribute verbatim or modified # copies of this license document, and changing it is allowed as long # as the name is changed. # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION # 0. You just DO WHAT THE FUCK YOU WANT TO. # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms of the Do What The Fuck You Want # To Public License, Version 2, as published by Sam Hocevar. See # http://www.wtfpl.net/ for more details. #=========================================================== # Convention: I could have done comments in English, but as this script is # ---------- linked to a service provided by the French ISP Free, I assume it # will be mainly used by a French audience. Thus I chosed to comment in # French. In any case, if you need clarifications in English, and automated # translations give un-understandable results, I'll be glad to help, just post # your questions on the relevant thread at ubuntu-fr.org # # Fonction : # ---------- # - Upload de fichiers à distance sur la Freebox # # Usage : # ------- # Voir description dans la fonction usage() # # # Tested : Ubuntu Lucid / Precise # ------ # # Version : 1.2.0 # ------- # # Date : 2013-04-27 # ----- # # Author : Zakhar # ------ # # History : # ------- # 1.2.0 # - Compatibilité avec Synology # - Mécanisme autotarget (répertoire cible basé sur le nom du fichier via patterns) # 1.1.0 # - Licence changée en WTFPL V2 # - Compatibilité avec le firmware 1.1.9 de la Freebox Server (CSRF Token) # - Fréquence de rafraichissement par défaut non opérationnelle : corrigé. # 1.0.3 # - Possibilité de régler la fréquence de rafraichissement de la progression d'upload # afin de limiter le trafic descendant. Option -d (--display) # 1.0.2 # - correction d'un bug lorsque le fichier de configuration n'existe pas. # - détection de l'absence de curl (par ex. si on fait tourner avec un live CD) et # message indiquant comment l'installer. # 1.0.1 # - Fonctionne désormais aussi avec un disque USB externe connecté. On peut # également copier vers le disque externe si on le souhaite. # - Amélioration des commentaires # - Corrections de bugs divers # # Contributor : // Ajouter votre nom si vous contribuez et redistribuez // # ----------- # Vinky41 et root pour avoir déblayé le terrain sur la façon d'utiliser le token CSRF # ====================================================================================== # Notes générales : le script est écrit pour fonctionner avec le shell par défaut # --------------- d'Ubuntu qui est dash (et non pas bash). Cela facilitera (en # principe) son fonctionnement sur d'autres plateformes commes des NAS. # Ne venez donc pas dire : 'oui mais on peut faire mieux pour découper une substring' # et proposer un "bashism" à la place du "cut". Certes oui, avec bash, mais alors on # perd pas mal en compatibilité. # Dash est aussi bien plus rapide que bash pour ses traitements, et comme on a des # boucles de traitement importantes, il est mieux de garder cette compatibilité. # De même l'affichage est fait pour être "sympathique" avec un terminal digne de ce # nom, mais devrait se dégrader de façon toujours utilisable avec un termnial basique # comme celui utilisé par PhpShell. # # Notes Free: Je ne suis pas affilié à Free. L'interface web de la Freebox qu'utilise # ---------- ce script n'est pas Open Source. Cela signifie que j'ai utilisé le # comportement "constaté". Il apparaît que dans la plupart des cas, l'interface # développé est du "Web 2.0" et que l'échange de données se fait en JSON. On n'est # cependant pas à l'abri de : # - un comportement qui pourrait apparaître dans certains cas et que je n'ai jamais # constaté dans ma configuration particulière # - un changement de comportement de l'interface HTML de la Freebox. # J'espère cependant avoir assez bien isolé les parties où on communique avec la Freebox # pour que si de telles éventualités survenaient, les modifications soit "maîtrisables". # # Légalité : le présent script ne fait rien que vous ne puissiez faire "à la main", # --------- c'est à dire en utilisant l'interface standard proposé par Free (puisque # c'est ce même interface qui est utilisé par le script). # Le rafraichissement du suivi du téléchargement est réglé à 2 sec, exactement comme # la page Web qui réalise la même fonction. Le charge réseau est donc la même ou # inférieure, puisqu'on n'affiche pas la page entière mais seulement le JSON. # Sauf réaction contraire de Free, le script est donc tout aussi légal que l'usage # de sa Freebox à distance via les pages web prévues à cet effet. # # Sécurité : la sécurité mise en place par Free pour cette fonction d'accès à distance # -------- est vraiment déplorable. Du reste cela a déjà été relevé dans de nombreux # forums. En effet, le mot de passe d'accès circule EN CLAIR dans les échanges avec # entre l'utilisateur et la Freebox cible. Cela signifie que tout dispositif entre vous # et la Freebox cible, du genre : proxy, point d'accès public, etc... verra passer ce # mot de passe EN CLAIR, et s'il est mal intentionné, pourra le réutiliser à son # bénéfice. La "protection" de Free qui consiste à exiger un mot de passe "compliqué" # pour autoriser l'accès à distance, ne protège en réalité que contre ceux qui # essaieraient de craquer votre mot de passe (force brute, dictionnaire, etc...). # Il existe pourtant des solutions simple, même sans aller jusqu'au SSL, pour parer à # cela. Par exemple envoyer "md5( aléa + md5 (mot de passe ))". Ainsi l'aléa changeant # à chaque connexion, quelqu'un qui "écoute" la conversation ne pourra pas se # connecter à nouveau une fois la session échue. # Pour le script, cette "mauvaise sécurisation" est une facilité, puisque du coup la # procédure de connexion est très simple : on envoie le mot de passe EN CLAIR !.. # Le script vous propose plusieur façon d'envoyer ce mot de passe. # La "meilleure", en terme de sécurité, est de laisser le script vous demander le mot # de passe. Ainsi il n'est nulle part inscrit sur votre système. Il est seulement # présent transitoirement en mémoire lorsque vous le tapez et que le script l'utilise. # Vous pouvez aussi le passer au script avec l'option -p/--password, mais dans ce cas # il pourrait figurer aussi par exemple dans votre fichier d'historique de commandes. # Vous pouvez enfin l'inscrire dans un fichier de configuration, et là, tous ceux qui # ont accès à ce fichier pourront voir le mot de passe. # Cependant pour ces deux dernières solutions, moins sécurisées, Ubuntu propose des # solutions d'amélioration, comme par exemple le chiffrement de votre /home qui # évitent de telles "fuites" dans le cas par exemple où on vous volerait votre PC # portable contenant ces informations. # Mais quel que soit le soin que vous apportiez à sécuriser localement ce mot de passe, # il continuera à circuler "EN CLAIR" sur les lignes ADSL... tant que Free n'aura pas # changé cela. # ====================================================================================== # -------------------------------------------------------------------------------------- # Contournement permettant par exemple d'utiliser le script via phpshell qui lance | # un terminal "basique" ne supportant pas tput. | # -------------------------------------------------------------------------------------- if tput civis 2>/dev/null; then TPUT="tput" TPUTC="printf" vStar='⬕' else TPUT="nop" TPUTC="nop" vStar='*' fi ICONV='yes' which iconv 1>/dev/null || ICONV='no' nop() { : } red() { ${TPUTC} "\033[1;31m"; } green() { ${TPUTC} "\033[1;32m"; } yellow() { ${TPUTC} "\033[1;33m"; } cyan() { ${TPUTC} "\033[1;36m"; } normal() { ${TPUTC} "\033[0;38m"; } invis() { ${TPUT} civis; } visib() { ${TPUT} cnorm; } up() { tput cuu1; } if tput el 2>/dev/null; then TPUTCL="tput" CLEARLINE="el" else TPUTCL="printf" CLEARLINE=" \r" fi clearln() { ${TPUTCL} "${CLEARLINE}"; } # -------------------------------------------------------------------------------------- # Variables globales et fonctions utilitaires (usage et trap) | # -------------------------------------------------------------------------------------- VERSION="1.2.0" # Version de ce script FBX_CONFIG_FILE="${HOME}/.config/freebox.conf" # Fichier de configuration par défaut # Pattern pour vérifier une saisie # de type IP:Port IP_PORT_PATTERN='^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(:[0-9]{1,5})?$' JSONRPC='{"jsonrpc":"2.0","method":' # Entête des headers JSON TMPLOG='' # Fichiers temporaires de curl # Note : la variable TMPLOG est globale car utilisé par trap TMPROOT='' # Racine du serveur Web temporaire SERVER_PID=0 # PID su serveur Web temporaire TMPSRV_LOCK='/tmp/upfree_tmpsrv.lock' # Fichier de lock du serveur temporaire TRACE_CURL='' # Trace de curl ('y' pour l'activer) TRACE_CURL_FILE='/tmp/curl_debug.txt' # Fichier contenant la trace STTY_SAVE="$( stty -g )" # Etat de la console sauvegardé # Variables pour arrêter un upload en cours en cas de CTRL-C fbxIPPort='' ID_UPLOAD=0 # id de l'upload courant # 0 = pas d'upload en cours # -------------------------------------------------------------------------------------- # Messages du mode verbeux (option -v/--verbose) | # -------------------------------------------------------------------------------------- readonly vMSG_PARAM_PARSE_OK="Lecture des paramètres passés au script : OK" readonly vMSG_PARAM_FILE_FOUND="Fichier de paramètres trouvé" readonly vMSG_DEF_PARAM_FILE_FOUND="Fichier de paramètres par défaut trouvé" readonly vMSG_NO_PARAM_FILE="Aucun fichier de paramètre trouvé" readonly vMSG_PARAM_FILE_OK="Fichier de paramètres exécuté avec succès" readonly vMSG_READ_CONFIG_OK="Lecture des paramètres de configuration : OK" readonly vMSG_LOCAL_CHECK_OK="Vérifications locales des fichiers réussie." readonly vMSG_SERVER_FOUND="Utilisation du serveur déjà installé sur le port local " readonly vMSG_SERVER_SUPPORTS_RANGE="Ce serveur supporte la reprise d'un téléchargement interrompu (Range)" readonly vMSG_SERVER_NO_RANGE="Ce serveur ne supporte pas la reprise d'un téléchargement interrompu (Range)" readonly vMSG_TEMP_SERVER="Un serveur temporaire sera lancé sur le port %s." readonly vMSG_FBX_INIT_OK="Initialisation communication Freebox réussie" readonly vMSG_FBX_GET_DL_DIR_OK="Récupération du répertoire de téléchargement OK." readonly vMSG_VERIF_TARGET="Cible de l'upload vérifiée avec succès." readonly vMSG_VERIF_FBX="Vérification des fichiers sur la Freebox terminée avec succès." readonly vMSG_FBX_DISK_SPACE_OK="Espace disque disponible sur la Freebox suffisant." readonly vMSG_AUTO_TARGET="Utilisation automatique du répertoire '%s' pour le fichier %s" readonly vMSG_ALL_CHECK_OK="Toutes les vérifications sont terminées avec succès." # -------------------------------------------------------------------------------------- # Messages d'erreur génériques (sans code erreur particulier) | # -------------------------------------------------------------------------------------- readonly eMSG_CURL_ERR="Erreur curl : " # Pas d'erreur code car on rend celui de curl readonly eMSG_CONFIG="Erreur ou interruption durant la lecture de la configuration" readonly eMSG_LOCAL_VERIF="Erreur lors de la vérification locale" readonly eMSG_INIT_FBX="Erreur lors de l'initialisation avec la Freebox" readonly eMSG_VERIF_FBX="Erreur lors de la vérification avec la Freebox" readonly eMSG_VERIF_SPACE="Erreur lors de la vérification de l'espace disponible" readonly eMSG_UPLOAD="Erreur lors de l'upload" readonly eMSG_FBX_MOVE="Erreur en renommant un fichier sur la Freebox" readonly eMSG_CHECK_SERVER="Erreur lors de la vérification du serveur local" readonly eMSG_LAUNCH_SERVER="Erreur lors du lancement du serveur temporaire" readonly eMSG_SYM_LINK="Erreur de création de lien symbolique" readonly eMSG_VERIF_TARGET="Erreur de création de la vérification de la cible" # -------------------------------------------------------------------------------------- # Messages d'erreur (e) et warning (w) et exit codes | # -------------------------------------------------------------------------------------- readonly eMSG_JSON_ERR="** Erreur JSON avec la Freebox." readonly eCOD_JSON_ERR=85 readonly eMSG_INCORRECT_PASSWD="** Mot de passe incorrect." readonly eCOD_INCORRECT_PASSWD=86 readonly eMSG_FILE_NOT_IN_TREE="Le fichier : «%s» n´est pas dans l´aborescence de votre serveur web (root=%s)" readonly eCOD_FILE_NOT_IN_TREE=87 readonly eMSG_FILE_NAME_ILLEGAL="Le nom du fichier : «%s» est illégal. Il contient des caractères spéciaux (ASCII 0 à 31 ou 127)." readonly eCOD_FILE_NAME_ILLEGAL=88 readonly eMSG_NONEXISTENT_FILE="Le fichier : «%s» n´existe pas." readonly eCOD_NONEXISTENT_FILE=89 readonly eMSG_SAME_NAME_FILES="Plusieurs fichiers ont un nom identique." readonly eCOD_SAME_NAME_FILES=90 readonly eMSG_EXISTS_AS_DIR="Le fichier : «%s» existe déjà sur la Freebox en tant que répertoire." readonly eCOD_EXISTS_AS_DIR=91 readonly eMSG_TARGET_IS_BIGGER="Le fichier : «%s» est plus petit que celui sur la Freebox. Le forçage est impossible, corrigez l´erreur." readonly eCOD_TARGET_IS_BIGGER=92 readonly wMSG_FILE_EXISTS="Le fichier : «%s» existe déjà sur la Freebox. Supprimez-le ou option -f pour écraser/continuer." readonly wCOD_FILE_EXISTS=93 readonly eMSG_NOT_ENOUGH_DISK_SPACE_AVAILABLE="Pas assez d´espace disque disponible sur la Freebox. Disponible : %s / Fichiers à copier %s" readonly eCOD_NOT_ENOUGH_DISK_SPACE_AVAILABLE=94 readonly eMSG_PORT_ALREADY_IN_USE="Le port %s est déjà utilisé. Choisissez un autre port ou supprimez l'option -s si vous avez déjà un serveur web sur ce port." readonly eCOD_PORT_ALREADY_IN_USE=95 readonly eMSG_TMPSRV_IS_MONOTASK="Le serveur temporaire n'est pas multi-tâches et ne peut donc pas être utilisé pour deux uploads simultannés. Attendez la fin des autres uploads." readonly eCOD_TMPSRV_IS_MONOTASK=96 readonly eMSG_NO_SERVER="Il n'y a aucun serveur sur le port %s. Installez un serveur ou spécifiez l'option -s pour que le script utilise un serveur temporaire." readonly eCOD_NO_SERVER=97 readonly eMSG_MISSING=$( red )' Erreur : '$( normal )'téléchargement annulé.' readonly eCOD_MISSING=98 readonly eMSG_ERR_TRNF=$( red )' Erreur.' readonly eCOD_ERR_TRNF=99 readonly eMSG_BAD_DIR="Le répertoire (ou le chemin) : «%s» n'existe pas" readonly eCOD_BAD_DIR=100 readonly eMSG_MULTI_CP_TO_1_FILE="Vous ne pouvez pas recopier plusieurs fichiers sur un seul fichier cible." readonly eCOD_MULTI_CP_TO_1_FILE=101 readonly eMSG_FBX_ROOT_FORBIDDEN="Vous ne pouvez pas copier des fichier à la racine de la Freebox." readonly eCOD_FBX_ROOT_FORBIDDEN=102 readonly eMSG_TARGET_WRONG_SIZE="Le fichier cible spécifié existe déjà avec une taille différente." readonly eCOD_TARGET_WRONG_SIZE=103 readonly eMSG_FILE_EXISTS_IN_TARGET="Le fichier : «%s» existe déjà avec une taille différente dans le répertoire cible spécifié." readonly eCOD_FILE_EXISTS_IN_TARGET=104 readonly eMSG_CURL_NOT_INSTALLED="curl ne semble pas installé. Installez le avec sudo apt-get install curl Puis exécutez ce script à nouveau." readonly eCOD_CURL_NOT_INSTALLED=105 # -------------------------------------------------------------------------------------- # Chaines diverses | # -------------------------------------------------------------------------------------- # Lignes de "titre" à la mode curl readonly DISPLAY_HEADER1=\ "Fichier %% Total Emis Débit Temps Temps Temps Débit\n" # 1 2 3 4 5 6 7 8 #12345678901234567890123456789012345678901234567890123456789012345678901234567890 readonly DISPLAY_HEADER2=\ " Moyen Total Passé Restant Actuel\n" readonly TOTAL_SEPARATOR='-------------------------------------------------------------------------------' readonly TOTAL_LABEL=$( green )'Total ' readonly EVAL_ESCAPE="s/'/'\\\''/g" readonly ESCAPE_QUOTE='s|\\|\\\\|g;s|"|\\"|g' readonly wMSG_PAUSE=$( cyan )' En pause'$( normal ) readonly mUSER_INTERRUPT='Interruption utilisateur' readonly gZEN_PARAM_TITLE="Saisie des paramètres d'upload" readonly gZEN_TITLE="Upload sur Freebox" readonly mINPUT_IP_FBX="IP[:Port] de la FreeBox (ex. 10.20.30.40:9090) : " readonly mINPUT_PASSWD="Mot de passe de votre FreeBox : " readonly mINPUT_IP_LOCAL="IP[:Port] du serveur Web de ce PC (ex. 40.30.20.10:8080) : " readonly mINPUT_WEB_ROOT="Racine du serveur Web de ce PC (ex. /var/www/upload_fbx) : " readonly okJson='"jsonrpc":"2.0","result":' readonly jsonBadDir='"error":{"message":"bad directory"' # -------------------------------------------------------------------------------------- # Fonction de nettoyage final et trap en cas d'arrêt forcé du script | # -------------------------------------------------------------------------------------- clean() { if [ -n "${TMPLOG}" ]; then if [ -f "${TMPLOG}.g" ]; then . "${TMPLOG}.g" rm -f "${TMPLOG}.g" fi if [ ${ID_UPLOAD} -ne 0 ]; then fbx_dlremove ${ID_UPLOAD} 'n' fi rm -f "${TMPLOG}" rm -f "${TMPLOG}.c" if [ ${SERVER_PID} -ne 0 ]; then kill ${SERVER_PID} rm -f "${TMPSRV_LOCK}" rm -rf "${TMPROOT}" rm -f "${TMPROOT}.log" fi fi normal echo echo visib stty "${STTY_SAVE}" } trap clean HUP QUIT ABRT EXIT control_c() { exiterr 130 "${mUSER_INTERRUPT}" } trap control_c INT # -------------------------------------------------------------------------------------- # Diverses fonctions utilitaires | # -------------------------------------------------------------------------------------- usage() { if [ -n "$1" ]; then ${TPUT} bold; printf "%s\n\n" "${1}"; ${TPUT} sgr0; fi cat <&2 printf '\n\n\nErreur=%u\n%s\n' ${err} "${2}" >&2 normal >&2 if [ "${opt_g}" = 'y' ]; then zenity --error\ --title="${gZEN_TITLE}"\ --text="$( printf '%s' "${2}" | sed 's|\&|\&|g;s|<|\<|g' )" fi exit ${err} fi } checkerr() # Vérifie si une erreur est reportée et sinon message "verbeux" vecho { exiterr "${1}" "${2}" vecho "${opt_v}" "${3}" } # -------------------------------------------------------------------------------------- # Présentation des nombres comme curl : maxi 5 chiffres avec k/M/G | # -------------------------------------------------------------------------------------- slicenumber() { local e=$(( $1 / $2 )) if [ $e -ge 100 ]; then printf "%4u$3" $e else local f=$(( 100 * $1 / $2 - $e * 100 )) printf "%2u.%1u$3" $e $(( $f / 10 )) fi } number5() { if [ $1 -lt 100000 ] then printf '%5u' $1 else if [ $1 -lt 10240000 ]; then slicenumber $1 1024 'k' else if [ $1 -lt 10485760000 ]; then slicenumber $1 1048576 'M' else slicenumber $1 1073741824 'G' fi fi fi } stamp2date() { date --utc --date "1970-01-01 $1 sec" +%H:%M:%S } # -------------------------------------------------------------------------------------- # Echanges avec la Freebox | # -------------------------------------------------------------------------------------- # Tout d'abord 3 fonctions génériques pour utiliser curl, tracer, tester les erreurs. | # -------------------------------------------------------------------------------------- # Wraper de curl qui permet en plus de: # - capturer les erreurs # - tracer la requête si le flage externe de traçage est positionné # Paramètres : les mêmes que curl + -s et -w %httpcode qui sont communs # Output : la même que curl + une chaine d'erreur le cas échéant ccurl() { curl "${@}" -w "%{http_code}" -s || printf "_ERR=${?}" if [ "${TRACE_CURL}" = 'y' ]; then echo 'curl : ----------------' >>"${TRACE_CURL_FILE}" for arg in "${@}" do echo "arg=${arg}" >>"${TRACE_CURL_FILE}" done echo '-----------------------' >>"${TRACE_CURL_FILE}" cat "${TMPLOG}" >>"${TRACE_CURL_FILE}" 2>/dev/null echo '=======================' >>"${TRACE_CURL_FILE}" fi } # 'Catch' d'erreur de curl # Paramètres : # $1= la chaine (retour de ccurl) à analyser # Retour : si erreur curlcheck() { local err=0 # Erreur retournée par la fonction appelée if printf '%s' "${1}" | grep -q '_ERR='; then err=$( printf '%s' "${1}" | sed 's/.*_ERR=//' ) exiterr "${err}" "${eMSG_CURL_ERR}${err}" fi return $? } # 'Catch' d'erreur json # Cumul avec la fonction précédente, plus vérifie erreurs JSON # Paramètres : # $1= la chaine (retour de ccurl) à analyser # $2= Optionnel, la chaine pour OK si pas celle par défaut # Retour : si erreur jsoncatch() { curlcheck "${1}" || return $? if [ ${#} -eq 2 ]; then testJson="${2}" else testJson="${okJson}" fi if [ "${1}" != 200 ] || grep -vq "${testJson}" "${TMPLOG}"; then exiterr ${eCOD_JSON_ERR}\ "${eMSG_JSON_ERR} [HTTP_CODE=${1}]$( echo; cat "${TMPLOG}" 2>/dev/null )" fi return $? } stripjsonheader() # Fonction utilitaire qui retire l'entête standard json { sed -e 's|.*"jsonrpc":"2.0","result":\[||;s|\]}$||' -e 's/},/}\n/g' "${TMPLOG}" } # -------------------------------------------------------------------------------------- # Echanges avec la Freebox a proprement parler | # -------------------------------------------------------------------------------------- fbx_login() { local httpCode httpCode="$( ccurl http://"${fbxIPPort}"/login.php\ -c "${TMPLOG}.c"\ --data "login=freebox&passwd=${fbxPassword}"\ -o /dev/null \ -D "${TMPLOG}" )" curlcheck "${httpCode}" || return $? # Quand le mot de passe est correct, on a 302, mais on veut juste le cookie # donc on ne met pas d'option -L pour récupérer la page après login # Sinon, un mot de passe incorrect rend une page qui l'indique + un HTTP_CODE à 200 if [ "${httpCode}" -ne 302 ]; then exiterr ${eCOD_INCORRECT_PASSWD} "${eMSG_INCORRECT_PASSWD}" fi fbxCSRF="$( sed -n -e '/X-FBX-CSRF-Token: /s/X-FBX-CSRF-Token: //p' "${TMPLOG}" | tr -d '\r' )" return $? } # Récupère le répertoire où sont téléchargés les fichiers (page de paramétrage) fbx_getdownloaddir() { jsoncatch "$( ccurl http://"${fbxIPPort}"/download.cgi\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "${JSONRPC}\"download.config_get\"}" \ -H 'Content-Type: application/json; charset=utf-8')" return $? } # Liste un répertoire passé en $1 fbx_readdir() { local httpCode local random random=$(dd if=/dev/urandom bs=1 count=6 2>/dev/null | od | sed -n '/0000000/s/0000000//;s/ //gp') httpCode="$( ccurl http://"${fbxIPPort}"/fs.cgi\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "${JSONRPC}\"fs.list\",\"id\":1${random},\"params\":[\"${1}\",{\"with_attr\":true}]}" \ -H 'Content-Type: application/json; charset=utf-8')" curlcheck "${httpCode}" || return $? if grep -q "${jsonBadDir}" "${TMPLOG}"; then exiterr ${eCOD_BAD_DIR} "$( printf "${eMSG_BAD_DIR}" "${1}" )" fi jsoncatch "${httpCode}" return $? } # Récupère la liste des fichiers téléchargés (en cours, finis, en erreur, etc..) fbx_getdownloadlist() { jsoncatch "$( ccurl http://"${fbxIPPort}"/download.cgi\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "${JSONRPC}\"download.list\"}" \ -H 'Content-Type: application/json; charset=utf-8')" return $? } # Récupère les paramètres de remplissage du NAS fbx_storage() { jsoncatch "$( ccurl http://"${fbxIPPort}"/storage.cgi\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "${JSONRPC}\"storage.list\"}" \ -H 'Content-Type: application/json; charset=utf-8')" return $? } # Upload un fichier passé en $1 fbx_upload() { jsoncatch "$( ccurl http://"${fbxIPPort}"/download.cgi\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "url=${1}&user=freebox&method=download.http_add" \ --data-urlencode "csrf_token=${fbxCSRF}" \ -H 'X-Requested-With: XMLHttpRequest')"\ '{"result":' return $? } # Upload renomme (mv) un fichier $1 en $2 fbx_move() { local from="$( printf '%s' "${1}" | sed "${ESCAPE_QUOTE}")" local to="$( printf '%s' "${2}" | sed "${ESCAPE_QUOTE}")" jsoncatch "$( ccurl http://"${fbxIPPort}"/fs.cgi\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "${JSONRPC}\"fs.move\",\"params\":[\"${from}\",\"${to}\"]}" \ -H 'Content-Type: application/json; charset=utf-8')" return $? } # Retire un téléchargement dont l'ID est passé en $1 de la liste des téléchargements # Si le téléchargement est en cours, il est arrêté. # Si le paramètre 2 est passé, c'est qu'on ne veut pas de signalement d'erreur car on # est dans la phase d'exit, et on ne veut pas provoquer erreur sur erreur fbx_dlremove() { local httpCode httpCode="$( ccurl "http://${fbxIPPort}/download.cgi"\ -o "${TMPLOG}" \ -b "${TMPLOG}.c" \ --data "${JSONRPC}\"download.remove\",\"params\":[\"http\",${1}]}" \ -H 'Content-Type: application/json; charset=utf-8')" if [ ${#} -eq 1 ]; then jsoncatch "${httpCode}" fi return $? } # ====================================================================================== # 3) Récupération adresses IP (local & Freebox), mot de passe et racine du serveur Web | # -------------------------------------------------------------------------------------- # Le principe logique utilisé est le suivant : | # - Si un fichier de configuration est passé (-c) on l'ouvre | # -- Sinon, on ouve le fichier de configuration par défaut | # A ce stade, si on a réussi à ouvrir un des deux fichiers, on le source, et ils vont | # alors définir 1 ou plusieurs (voire tous) des paramètres nécessaires. | # Il s'agit à ce stade des paramètres "par défaut". | # Ensuite, si certains des 4 paramètres sont passés en ligne de commande (-i -p -l -r) | # les paramètres passés ont priorité sur les paramètres par défaut qu'on écrase le cas | # échéant. | # A ce stade, tout paramètre qui reste non renseigné (chaine vide) sera demandé à | # l'utilisateur avec contrôle de cohérence. | # La fonction readparam ci-dessous réalise cette saisie graphique ou terminal selon | # l'option -g du script. | # -------------------------------------------------------------------------------------- readparam() { local retvar="${1}" local prompt="${2}" local hide="${3}" local var='' if [ "${opt_g}" = 'y' ]; then if [ "${hide}" = 'y' ]; then hide='--hide-text' fi var="$( zenity --entry \ "${hide}" \ --title="${gZEN_PARAM_TITLE}" \ --text="${prompt}" )" || return $? else if [ "${hide}" = 'y' ]; then stty -echo || return $? fi read -p "${prompt}" var || return $? if [ "${hide}" = 'y' ]; then echo stty "${STTY_SAVE}" || return $? fi echo fi eval "${retvar}='$( printf '%s' "${var}" | sed "${EVAL_ESCAPE}" )'" return $? } retvars() { while [ $# -gt 0 ]; do if [ "${2}" = '' ]; then printf "%s='%s';" "${1}" "$( printf '%s' "${3}" | sed "${EVAL_ESCAPE}" )" fi shift 3 done } getconfig() { local configFile='' # Fichier de configuration ------- if [ -n "${opt_c}" ] && [ -f "${opt_c}" ]; then vecho "${opt_v}" "${vMSG_PARAM_FILE_FOUND}" configFile="${opt_c}" else if [ -f "${FBX_CONFIG_FILE}" ]; then vecho "${opt_v}" "${vMSG_DEF_PARAM_FILE_FOUND}" configFile="${FBX_CONFIG_FILE}" fi fi if [ -z "${configFile}" ]; then vecho "${opt_v}" "${vMSG_NO_PARAM_FILE}" else # Note : le fichier de configuration est sourcé dans un subshell pour éviter qu'il # ne modifie des variables qu'on ne veut pas toucher ou qu'il provoque un # exit inopiné. S'il y a une erreur, elle est capturée par set -e et on sort. configFile="$( set -e local pre_fbxIPPort="${fbxIPPort}" local pre_fbxPassword="${fbxPassword}" local pre_localIPPort="${localIPPort}" local pre_localWebRoot="${localWebRoot}" local pre_opt_f="${opt_f}" local pre_opt_s="${opt_s}" local pre_opt_t="${opt_t}" local pre_opt_d="${opt_d}" local pre_opt_port="${opt_port}" local pre_autotargets="${autotargets}" . "${configFile}" retvars 'fbxIPPort' "${pre_fbxIPPort}" "${fbxIPPort}"\ 'fbxPassword' "${pre_fbxPassword}" "${fbxPassword}"\ 'localIPPort' "${pre_localIPPort}" "${localIPPort}"\ 'localWebRoot' "${pre_localWebRoot}" "${localWebRoot}"\ 'opt_f' "${pre_opt_f}" "${opt_f}" \ 'opt_s' "${pre_opt_s}" "${opt_s}" \ 'opt_t' "${pre_opt_t}" "${opt_t}" \ 'opt_d' "${pre_opt_d}" "${opt_d}" \ 'opt_port' "${pre_opt_port}" "${opt_port}" \ 'autotargets' "${pre_autotargets}" "${autotargets}" )" || return $? eval "${configFile}" || return $? vecho "${opt_v}" "${vMSG_PARAM_FILE_OK}" fi # IP et Port de la Freebox -------- if [ -n "${opt_i}" ]; then fbxIPPort="${opt_i}"; fi until printf '%s' "${fbxIPPort}" | grep -qE "${IP_PORT_PATTERN}" do readparam 'fbxIPPort' "${mINPUT_IP_FBX}" '' || return $? done # Mot de passe de la Freebox ------ if [ -n "${opt_p}" ]; then fbxPassword="${opt_p}"; fi while [ -z "${fbxPassword}" ]; do readparam 'fbxPassword' "${mINPUT_PASSWD}" 'y' || return $? done # IP et Port serveur Web Local ---- if [ -n "${opt_l}" ]; then localIPPort="${opt_l}"; fi until printf '%s' "${localIPPort}" | grep -qE "${IP_PORT_PATTERN}" do readparam 'localIPPort' "${mINPUT_IP_LOCAL}" '' || return $? done # Racine du serveur Web Local ----- if [ -n "${opt_r}" ]; then localWebRoot="${opt_r}"; fi while [ ! -d "${localWebRoot}" ] do readparam 'localWebRoot' "${mINPUT_WEB_ROOT}" '' || return $? done # LOCAL_WEB_ROOT doit toujours terminer par / # Or le seul cas où un résultat de readlink se termine pas / c'est quand on est à # la racine. if [ "${localWebRoot}" != '/' ]; then localWebRoot=$( readlink -f -- "${localWebRoot}" )'/' fi # Si opt_d ni en param ni en ---- if [ -z "${opt_d}" ]; then opt_d='2'; fi # config, défault = 2 ---- return $? } # ====================================================================================== # Escape le nom du fichier | # -------------------------------------------------------------------------------------- # NOTE: passer le post sans url encode plante pour certains caractères. Passer en | # urlencode plante systèmatiquement car la Freebox s'attend à trouver la fin de | # chaîne non urlencodée, et ne trouve alors plus "method". | # On est donc obligé de faire des remplacements "à la main". | # Il ne faut pas oublier aussi qu'il y a 2 transferts : | # 1-) du script vers la Freebox pour donner l'ordre de télécharger un fichier | # 2-) de la freebox vers ce PC pour télécharger effectivement le fichier | # ... et donc certains caractères doivent être encodés "deux fois" (ou presque) | # Par exemple, l'espace doit être encodé : %2520, c'est à dire qu'on encore le '%' en | # %25, et 20 étant 'légal', on n'encode pas. Ainsi c'est OK pour le 1) et la Freebox | # va faire un http GET avec %20 | # Du reste, lorsqu'il y a de tels caractères, le fichier sur le répertoire de la | # téléchargement de la Freebox n'a pas le bon nom puisqu'il a un nom "escapé". Il faut | # donc ensuite le renommer une fois le transfert terminé. | # Certains caractères ne passent pas dans curl, mais passent très bien pour la Freebox | # Ceux là il suffit de faire un 'escape simple' | # fichier de test : f_ !"#$%&'()*+,-.0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz~{|} # http://82.230.150.153:51413/test/f_%20!"%23$%25&'()*+,-.0123456789:;<=>%3f@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz~{|} # Escape doubles : | # (espace) => %2520, '#' => %2523, '%' => %2525, '?' => %253f | # Escape simples : '+' => '%2B', '&' => '%26' # Caractère interdits : ascii 0 à 31 et 127 et / (réservé pour séparation répertoire) | # | # Et donc ci-dessous les 2 fonctions : | # - escape (simple) donnant le nom du fichier sur le répertoire de la freebox | # - escape (double) pour l'upload | # -------------------------------------------------------------------------------------- escapeforstore() { printf '%s' "${1}" | sed 's/%/%25/g;s/ /%20/g;s/#/%23/g;s/?/%3f/g' } escapeforupload() { # On commence par remplacer le %... sinon on va le remplacer sur les remplacements ! printf '%s' "${1}" | sed 's/%/%2525/g;s/ /%2520/g;s/#/%2523/g;s/?/%253f/g;s/&/%26/g;s/+/%2B/g' } # ====================================================================================== # 4) Vérifications locales au PC sur la liste des fichiers à uploadés passés au script | # -------------------------------------------------------------------------------------- # On commence par les vérifications locales échouent, on n'a même pas besoin d'aller | # sur le réseau... pour préserver un peu de la sécurité (voir commentaire sur le mot | # de passe en clair en entête du script) | # _____________________________________________________________________________________| # | # - Le nom de fichier ne doit pas contenir de caractères 'spéciaux' (au sens regexp) | # C'est à dire ASCII 0 à 31 et 127. En réalité sur un filesystem ext2/3/4 on peut | # mettre de tels caractères dans un nom de fichier... mais ensuite c'est fort | # compliqué à gérer car les fichiers n'apparaissent pas toujours sur leur nom | # exact (remplacement des caractères ASCII 0 à 31 par des ?) | # - Le fichier doit exister | # - Le fichier doit être dans l'arborescence du serveur Web | # - Il ne doit pas y avoir deux fois le même nom de fichier | # Au passage, on calcule | # - la taille totale des fichiers à uploader en retour | # - le + petit & + gros fichier, qui sera utile pour test le srv local & espace libre | # -------------------------------------------------------------------------------------- # Paramètres : # $1 = Variable de retour pour la taille # $2 à $N = la liste des fichiers à vérifier veriflocal() { local fileToUpload local fileList='' local varRetSize="${1}" shift local size=0 local thisSize local smallestSize=0 for fileToUpload in "$@" do if printf '%s' "${fileToUpload}" | grep -q '[[:cntrl:]]'; then exiterr ${eCOD_FILE_NAME_ILLEGAL}\ "$( printf "${eMSG_FILE_NAME_ILLEGAL}"\ "$( printf '%s' "${fileToUpload}" | sed 's/[[:cntrl:]]/?/g' )"\ )" fi if [ -f "${fileToUpload}" ]; then fileToUpload="$( readlink -f -- "${fileToUpload}" )" || return $? if printf '%s' "${fileToUpload}" | grep -vq "^${localWebRoot}"; then exiterr ${eCOD_FILE_NOT_IN_TREE}\ "$( printf "${eMSG_FILE_NOT_IN_TREE}" "${fileToUpload}" "${localWebRoot}")" fi else exiterr ${eCOD_NONEXISTENT_FILE}\ "$( printf "${eMSG_NONEXISTENT_FILE}" "${fileToUpload}")" fi thisSize=$( stat -c%s -- "${fileToUpload}" ) || return $? if [ ${thisSize} -ne 0 ]; then size=$(( ${size} + ${thisSize} )) if [ ${smallestSize} -eq 0 ] || [ ${smallestSize} -gt ${thisSize} ]; then smallestSize=${thisSize} smallestFile="${fileToUpload}" fi if [ ${thisSize} -gt ${biggestSize} ]; then biggestSize=${thisSize} fi fi fileList=$( printf "%s\n%s" "$( basename -- "${fileToUpload}" )" "${fileList}" )\ || return $? done # -------------------------------------------------------------------------------------- # On vérifie qu'il n'y a pas deux fois le meme nom de fichier. En effet comme l'upload | # se fait dans un seul répertoire, on produirait un écrasement si deux fichiers issus | # de répertoires source différents avaient un nom identique | # Si le nombre de ligne de la liste de fichiers, et de la même liste avec un tri | # "unique" sont différents c'est qu'il y a au moins un doublon. | # Note 1 : on rajoute \n sur la liste non triée, car le tri rajoute \n à la fin. | # Note 2 : Et s'il n'y a qu'un paramètre, inutile de faire le test ! | # -------------------------------------------------------------------------------------- if [ ${#} -gt 1 ] && \ [ $( printf '%s' "${fileList}" | sort -u | wc -l ) -ne \ $( printf '%s\n' "${fileList}" | wc -l ) ]; then exiterr ${eCOD_SAME_NAME_FILES} "${eMSG_SAME_NAME_FILES}" fi eval "${varRetSize}=${size}" return $? } # ====================================================================================== # 5) Vérifications du port web local et détermination des capacités du serveur (range) | # -------------------------------------------------------------------------------------- # 5a) On calcule le port sur lequel on doit servir | # - Si c'est indiqué dans opt_port, pas de problème | # -- Sinon, on regarde le port dans localIPPort | # --- si c'est toujours vide, c'est qu'on a le port 80 par défaut | # 5b) On regarde si le port calculé ci-dessus est utilisé | # On a alors 4 cas selon qu'il est utilisé ou non et selon que l'option -s est | # spécifiée ou non | # Port utilisé | Option -s : erreur, le port est déjà utilisé, on ne peut pas | # lancer un autre serveur sur le même port | # Port utilisé | Pas opt -s: OK, on utilise le serveur déjà en écoute sur ce port | # Port libre | Option -s : OK, on pourra lancer le serveur comme demandé par | # l'option -s | # Port libre | Pas opt -s: Erreur car on n'a pas de serveur sur le port et on | # n'a pas demandé de serveur par l'option -s | # 5c) On teste si le serveur local supporte le "range" en faisant un HEAD sur le plus | # fichier trouvé par la fonction de vérification locale. | # -------------------------------------------------------------------------------------- checkserver() { local httpCode local tmpPort # 5a ___________________________________________________ if [ -z "${opt_port}" ]; then opt_port="$( printf '%s' "${localIPPort}" | sed -n 's/.*://p' )" if [ -z "${opt_port}" ]; then opt_port=80 fi fi TMPSRV_LOCK="${TMPSRV_LOCK}.${opt_port}" if [ -f "${TMPSRV_LOCK}" ]; then exiterr ${eCOD_TMPSRV_IS_MONOTASK} "${eMSG_TMPSRV_IS_MONOTASK}" fi # 5b___________________________________________________ if netstat -ant | grep -q "${opt_port}"; then # Ici le port EST utilisé if [ "${opt_s}" = 'y' ]; then exiterr ${eCOD_PORT_ALREADY_IN_USE}\ "$( printf "${eMSG_PORT_ALREADY_IN_USE}" ${opt_port} )" else vecho "${opt_v}" "${vMSG_SERVER_FOUND}"${opt_port} # 5c___________________________________________________ # Si tous les fichiers à télécharger sont de taille nulle... aucune importance # que le serveur supporte ou pas 'Range', et dans ce cas on ne fera pas de test. # Dans le cas contraire (cas "normal" !) on teste en faisant un HEAD sur # le plus petit fichier de taille non nulle trouvé par veriflocal # Note: le test ne se fait qu'ici, si on trouve un serveur sur le port demandé, # dans le cas contraire (ci-dessous) c'est "notre" serveur python et on # sait qu'il ne supporte pas 'Range', inutile de tester ! if [ -n "${smallestFile}" ]; then smallestFile="$( printf '%s' "$(readlink -f -- "${smallestFile}")" | cut -c ${#localWebRoot}- )" smallestFile="$( printf '%s' "$( escapeforstore "${smallestFile}")" | sed 's|\[|%5B|g;s|\\|\\\\|g;s|{|%7B|g;s|}|%7D|g' )" httpCode="$( ccurl "http://localhost:${opt_port}${smallestFile}"\ --header 'Range: bytes=0-0'\ --head \ -o /dev/null )" curlcheck "${httpCode}" || return $? if [ "${httpCode}" = '206' ]; then supportRange='y' vecho "${opt_v}" "${vMSG_SERVER_SUPPORTS_RANGE}" else vecho "${opt_v}" "${vMSG_SERVER_NO_RANGE}" fi fi fi else # Ici le port n'est PAS utilisé, et donc si -s on lance le serveur web temporaire if [ "${opt_s}" = 'y' ]; then vecho "${opt_v}"\ "$( printf "${vMSG_TEMP_SERVER}" ${opt_port} )" else exiterr ${eCOD_NO_SERVER}\ "$( printf "${eMSG_NO_SERVER}" ${opt_port} )" fi fi return 0 } launchserver() { if [ ${SERVER_PID} -eq 0 ]; then TMPROOT="$( mktemp -d )" || return $? cd "${TMPROOT}" || return $? # Sécurité : 1) on met un fichier index.htm vide pour éviter un listage du # répertoire qui pourrait permettre facilement de voir le nom du # lien à charger. # 2) le fichier log n'est pas dans les fichiers accessible du # serveur web, et il a un nom "aléatoire". # 3) On met un lien symbolique pour le fichier à télécharger. # A tout instant il n'y a donc qu'un seul fichier accessible. # Une fois le fichier terminé, on supprime le lien et on passe # au suivant. # 4) le serveur python SimpleHTTPServer est mono-tâche, donc # dès que le téléchargement avec la Freebox démarre personne ne # peut télécharger en parallèle. # 5) "Lazy loading", le serveur Web est lancé "au dernier moment" touch "index.htm" || return $? touch "${TMPSRV_LOCK}" || return $? python -m SimpleHTTPServer "${opt_port}" 1>/dev/null 2>"${TMPROOT}.log" erreur | # -II) si le mode "forçage" (reprise/écrasement upload) a été spécifié | # .1) si le fichier sur la Freebox est plus gros = incohérence = erreur bloquante| # .2) sinon selon que le serveur supporte le range ou pas, on ajoute la taille | # déjà téléchargée au total utile ou au 'useless' (si on doit recommencer | # car pas de support du 'Range') | # -III) et si pas "forçage" on s'arrête en signalant le fichier existant. | # -IV) et si la taille trouvée correspondait déjà à la source, là on ne considère | # pas l'option -f vu qu'on est dans le cas où il n'y a rien à faire puisque | # le téléchargement est déjà complet (on ajoute juste la taille au total) | # -3) On regarde ensuite si on trouve ce même fichier sur liste des téléch.(cf Note) | # | # NOTES sur le comportement de la Freebox : | # La page de suivi des téléchargements indique aussi la liste des téléchargements | # finis (en erreur ou pas). Or si fichier existe dans cette liste, même s'il le | # fichier a été effacé du répertoire, la Freebox ne tentera pas de le télécharger à | # nouveau. Cf commentaire plus bas sur l'algorithme appliqué | # | # Paramètre : noms des variables pour le retour (tailles totales utiles et 'useless') | # Sortie : aucune | # Variables positionnées : aucune (directement) mais voir gettargetname | # -------------------------------------------------------------------------------------- veriffreebox() { local varRetTrnf="${1}" local varRetUselessTrnf="${2}" shift 2 local fileToUpload local targetDirList local line local thisID local thisFileLocalSize local thisFileDistSize=-1 local trnf=0 local uselessTrnf=0 local oIFS local token local pattern local autoDirTarget='' # 6.c-1) ______________________________ fbx_getdownloadlist || return $? local dlList="$( stripjsonheader )" fbx_readdir "${dlDir}" || return $? local dlDirList="$( stripjsonheader )" if [ -n "${opt_t}" ]; then fbx_readdir "${targetDirName}" || return $? targetDirList="$( stripjsonheader )" else targetDirList="${dlDirList}" fi # 6.c-2) ______________________________ for fileToUpload in "$@" do # 6.c-2.a) ______________________________ thisFileBaseName="$( basename -- "${fileToUpload}" )" thisFileLocalSize=$( stat -c%s -- "${fileToUpload}" ) # 6.c-2.abis) ______________________________ if [ -z "${opt_t}" ] && [ -n "${autotargets}" ]; then oIFS="${IFS}" IFS='|' targetDirList="${dlDirList}" autoDirTarget='' for token in ${autotargets}; do pattern="$( printf '%s' "${token}" | cut -d'>' -f1 )" if printf '%s' "${thisFileBaseName}" | grep -qi "${pattern}"; then IFS="${oIFS}" autoDirTarget="$( printf '%s' "${token}" | cut -d'>' -f2 )" vecho "${opt_v}" "$( printf "${vMSG_AUTO_TARGET}" "${autoDirTarget}" "${thisFileBaseName}" )" fbx_readdir "${autoDirTarget}" || return $? targetDirList="$( stripjsonheader )" break; fi done IFS="${oIFS}" fi gettargetname "${autoDirTarget}" if [ -n "${renameTarget}" ]; then if ( [ -n "${opt_t}" ] && [ "${targetIsDir}" != 'y' ] ) || \ [ -n "${autoDirTarget}" ] ; then line="$( printf '%s' "${targetDirList}" | \ grep "\"name\":\"${targetBaseName}\"" )" else line="$( printf '%s' "${targetDirList}" | \ grep "\"name\":\"${thisFileBaseName}\"" )" fi if [ -n "${line}" ]; then thisFileDistSize=$( printf '%s' "${line}" | sed 's/.*,"size"://;s/}//' ) if [ ${thisFileLocalSize} -ne ${thisFileDistSize} ]; then exiterr ${eCOD_FILE_EXISTS_IN_TARGET} \ "$( printf "${eMSG_FILE_EXISTS_IN_TARGET}" "${fileToUpload}")" else trnf=$(( ${trnf} + ${thisFileDistSize} )) renameTarget='' # Car on a déjà la cible complète donc pas de rename fi fi fi # 6.c-2.b) ______________________________ if [ ${thisFileLocalSize} -ne ${thisFileDistSize} ]; then line="$( printf '%s' "${dlDirList}" | \ grep "\"name\":\"${thisFileBaseNameEscaped}\"" )" if [ -n "${line}" ]; then # 6.c-2.b-I) ______________________________ if printf '%s' "${line}" | grep -q '"type":"dir"'; then exiterr ${eCOD_EXISTS_AS_DIR}\ "$( printf "${eMSG_EXISTS_AS_DIR}" "${fileToUpload}")" fi thisFileDistSize=$( printf '%s' "${line}" | sed 's/.*,"size"://;s/}//' ) if [ ${thisFileLocalSize} -ne ${thisFileDistSize} ]; then # 6.c-2.b-II) ______________________________ if [ "${opt_f}" = 'y' ]; then # 6.c-2.b-II.1) ______________________________ if [ ${thisFileLocalSize} -lt ${thisFileDistSize} ]; then exiterr ${eCOD_TARGET_IS_BIGGER}\ "$( printf "${eMSG_TARGET_IS_BIGGER}" "${fileToUpload}")" else # 6.c-2.b-II.2) ______________________________ if [ "${supportRange}" = 'y' ] ||\ [ ${thisFileLocalSize} -eq ${thisFileDistSize} ]; then trnf=$(( ${trnf} + ${thisFileDistSize} )) else uselessTrnf=$(( ${uselessTrnf} + ${thisFileDistSize} )) fi fi # 6.c-2.b-III) ______________________________ else exiterr ${wCOD_FILE_EXISTS}\ "$( printf "${wMSG_FILE_EXISTS}" "${fileToUpload}")" fi # 6.c-2.b-IV) ______________________________ else trnf=$(( ${trnf} + ${thisFileDistSize} )) fi fi fi # 6.c-3) ______________________________ # On supprime le fichier de la liste des téléchargements s'il existe, sauf si la # taille du fichier sur le répertoire correspond à la taille locale (cas "rien à # faire puisque le téléchargement est déjà complet"). # Cette suppression n'a pas lieu si on a une liste à explorer mais seulement quand # on traite 1 seul fichier, de façon à éviter une suppression indésirable si le # script est interrompu (erreur ou par l'utilisateur). Ainsi la suppression se fera # lors de la vérification préalable au téléchargement de chaque fichier. # En effet, # - soit le fichier n'existe pas sur le répertoire, auquel cas sa présence dans la # liste ne sert plus à rien (sauf à empêcher de téléchager le fichier !) # - soit le fichier existait dans le répertoire, et si on est à ce point c'est que # les options adéquates ont été positionnées pour qu'un nouveau téléchargement ou # continuation se fasse. Et dans ce dernier cas aussi, il faut supprimer le # fichier de la liste des téléchargements pour qu'on puisse continuer. # Le cas d'exception est lorsque les tailles correspondent car dans ce cas, de # toute façon, on ne télécharge rien, et il ne faut donc pas effacer le fichier # de la liste. if [ ${#} -eq 1 ]; then line="$( printf '%s' "${dlList}" | grep "\"name\":\"${thisFileBaseNameEscaped}\"" )" if [ -n "${line}" ] && [ ${thisFileLocalSize} -ne ${thisFileDistSize} ] ; then thisID=$( printf '%s' "${line}" | sed 's/.*,"id"://;s/,"transferred":.*//' ) fbx_dlremove ${thisID} fi fi done # Retour des variables de taille des fichiers déjà téléchargé (utile # et éventuellement 'useless' aussi) eval "${varRetTrnf}=${trnf}" if [ -n "${varRetUselessTrnf}" ]; then eval "${varRetUselessTrnf}=${uselessTrnf}" fi return $? } # -------------------------------------------------------------------------------------- # 6.d) Vérification de la taille disponible sur la Freebox. | # -------------------------------------------------------------------------------------- # NOTE : il manque probablement un peu de "raffinement", notamment | # - on ne tient pas compte de la taille des secteurs, et donc en cas de myriade de | # petits fichiers ou de disque 'presque plein', on pourrait être amenés à | # accepter un téléchargement en définitive impossible. | # - Pour la copie sur un disque externe, on doit faire 2 vérifications : | # -1) Vérifier que le plus gros des fichiers à copier pourra tenir sur le disque | # interne de la Freebox. | # -2) Vérifier que le volume cible (par son label) a l'espace libre nécessaire | # /TODO voir comment se comporte la FBX avec une partition SANS label. | # - /TODO et pour être "popre" il faudrait pouvoir garder un espace pour le | # propriétaire de la Freebox, de façon par exemple qu'on ne "plante" pas un | # éventuel enregistrement de télévision qu'il voudrait faire (quotas) | # | # Paramètre : Taille nécessaire pour les téléchargements demandés | # Sortie : aucune | # Variables positionnées : aucune | # -------------------------------------------------------------------------------------- verifstorage() { local needBytes=${1} fbx_storage || return $? local freeBytes=$( sed -e 's/"label"/\n"label"/g' "${TMPLOG}" |\ sed -n '/"label":"'"${defaultLabel}"'"/p' |\ sed 's/.*"root_free_bytes":{//;s/}.*//;s/.*,"val"://' ) if [ -z "${opt_t}" ] || [ "${targetLabel}" = "${defaultLabel}" ]; then if [ ${freeBytes} -gt ${needBytes} ]; then return 0 fi else if [ ${freeBytes} -gt ${biggestSize} ]; then freeBytes=$( sed -e 's/"label"/\n"label"/g' "${TMPLOG}" |\ sed -n '/"label":"'"${targetLabel}"'"/p' |\ sed 's/.*"root_free_bytes":{//;s/}.*//;s/.*,"val"://' ) if [ ${freeBytes} -gt ${needBytes} ]; then return 0 fi fi fi exiterr ${eCOD_NOT_ENOUGH_DISK_SPACE_AVAILABLE}\ "$( printf "${eMSG_NOT_ENOUGH_DISK_SPACE_AVAILABLE}"\ "$( number5 ${freeBytes} )"\ "$( number5 ${needBytes} )" )" } # ====================================================================================== # 7) Fonctions gérant l'affichage en cours d'upload | # -------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------- # Si on est en mode texte, affiche le header à la "mode curl" | # (en mode graphique on ne fait rien, c'est inutile !) | # | # Paramètre : nombre de fichiers à traiter | # Sortie : aucune | # Variables positionnées : displayTotal et globalDisplaySize (si plus d'1 fichier) | # -------------------------------------------------------------------------------------- displayheader() { if [ "${opt_g}" != 'y' ]; then if [ "${1}" -gt 1 ] && tput cuu1 2>/dev/null; then echo displayTotal='y' globalDisplaySize=$( number5 ${globalSize} ) fi printf "${DISPLAY_HEADER1}" printf "${DISPLAY_HEADER2}" invis fi } # -------------------------------------------------------------------------------------- # Affiche le première partie de la ligne : nom fichier, taille, % et octets traités | # En mode graphique, l'affichage est adapté pour zenity --progress : texte précédé | # du # et pourcentage en début de ligne sans # le précédant. C'est le seul affichage | # en mode graphique car le reste de la ligne n'est pas rendu pour simplifier. | # | # Paramètres : 4, voir en tête de la fonction | # Sortie et Variables positionnées : aucunes | # -------------------------------------------------------------------------------------- namedisplay() { local name="${1}" local size=${2} local displaySize="${3}" local curTrnf=${4} local pourcent=' --' if [ ${size} -ne 0 ]; then if [ ${size} -eq ${curTrnf} ]; then pourcent='100' else pourcent=$( printf '%3u' $(( ${curTrnf} * 100 / ${size} )) ) fi fi if [ "${opt_g}" = 'y' ]; then if [ ${size} -eq 0 ]; then pourcent='0'; fi printf "# %s : %s sur %s\n" "${name}" $( number5 "${curTrnf}" ) "${displaySize}" printf "%u\n" ${pourcent} else printf "\r%s " "${name}"; normal printf '%s %5s %5s' "${pourcent}" "${displaySize}" $( number5 "${curTrnf}" ) fi } # -------------------------------------------------------------------------------------- # Affiche la ligne complète : début de ligne (voir fonction name display ci-dessus) | # plus les taux de transfert moyens et instantannés, et les temps : passé, estimé et | # restant. En graphique, on ne fait que le début de ligne. | # | # Paramètres : 7, voir en tête de la fonction (plus ceux de namedisplay) | # Sortie et Variables positionnées : aucunes | # -------------------------------------------------------------------------------------- linedisplay() { local size=${2} local curTrnf=${4} local initTrnf=${5} local start="${6}" local rxRate="${7}" local now=$( date -u +%s) local spent='--:--:--' local estim='--:--:--' local left='--:--:--' local meanRate='---' namedisplay "${@}" if [ "${opt_g}" != 'y' ]; then if [ ${start} -ne 0 ]; then spent=$(( ${now} - ${start} )) if [ ${spent} -ne 0 ]; then meanRate=$(( ( ${curTrnf} - ${initTrnf} ) / ${spent} )) if [ ${meanRate} -ne 0 ]; then if [ ${size} -ne ${curTrnf} ]; then left=$(( ( ${size} - ${curTrnf} ) / ${meanRate} )) estim=$( stamp2date $(( ${left} + ${spent} )) ) left=$( stamp2date ${left} ) fi meanRate="$( number5 "${meanRate}" )" else meanRate='---' fi fi spent=$( stamp2date ${spent} ) fi if [ -n "${rxRate}" ]; then rxRate="$( number5 "${rxRate}" )" else rxRate='---' fi printf '%6s %s %s %s%6s' "${meanRate}" "${estim}" "${spent}" "${left}" "${rxRate}" fi } # -------------------------------------------------------------------------------------- # Permet, en un seul appel, d'afficher à la fois la ligne courante et la ligne de | # total le cas échéant (c'est à dire si on a + d'1 fichier et qu'on est en texte) | # | # Paramètres : 14 les 7 premiers sont idem à linedisplay. Le 8ème est la flag | # displaytotal (pour savoir si on affiche ou pas le total) suivi par | # les 6 paramètres restant pour linedisplay du total. Si le total est | # à afficher, on retire les 8 premiers paramètre et on appelle linedisplay| # avec le libellé fixe du TOTAL plus les 6 autres paramètres ce qui fait | # qu'on a bien les paramètres attendus pas linedisplay. | # Les 7 premiers paramètres concernent le fichier courant en cours d'up | # Les 7 premiers derniers paramètres concernent le total. | # Sortie et Variables positionnées : aucunes | # -------------------------------------------------------------------------------------- display() { local displayTotal="${8}" linedisplay "${@}" if [ "${displayTotal}" = 'y' ]; then printf "\n\n" shift 8 linedisplay "${TOTAL_LABEL}" "${@}" up up fi } # -------------------------------------------------------------------------------------- # Affichage en cas d'erreur. | # Il ne concerne que le début de ligne, de façon à être compatible avec linedisplay | # s'afficher aussi bien en graphique qu'en texte. | # | # Paramètres : 5, ceux de namedisplay, plus le message d'erreur à afficher. | # Sortie et Variables positionnées : aucunes | # -------------------------------------------------------------------------------------- displayerr() { local errMsg="${5}" if [ "${opt_g}" = 'y' ]; then namedisplay "${1}" "${2}" "${3} >> ${errMsg}" "${4}" else printf "\r" clearln namedisplay "${@}" printf '%s' "${errMsg}" fi } # ====================================================================================== # cut est buggé avec utf-8, même en mettant l'option -c. On fait donc un truc qui | # marche, quoique un peu compliqué... | # -------------------------------------------------------------------------------------- cututf8() { local str="${1}" local start=${2} local end=${3} if [ "${ICONV}" = "yes" ]; then printf '%s' "${str}" |\ iconv -f utf-8 -t utf-16le |\ head -c $(( ${end} * 2 )) |\ tail -c $(( (${end} - ${start} + 1 ) * 2 )) |\ iconv -f utf-16le -t utf-8 else printf '%s' "${str}" |\ head -c $(( ${end} )) |\ tail -c $(( (${end} - ${start} + 1 ) )) fi } # -------------------------------------------------------------------------------------- # Affichage un message transitoire pendant qu'on refait les vérifications sur le | # fichier qu'on s'apprête à uploader. Ce traitement ne se fait pas en mode graphique | # car c'est complexifié par le fait qu'en mode graphique on tourne dans un subshell | # | # Le nom de fichier à afficher est calculé. S'il est inféieur ou égal à 23 carac. on | # affiche le nom en entier avec padding à 23. Sinon on coupe en mettant des ... au | # milieu du nom du fichier. | # On affiche la ligne pour ce fichier avec le message de vérification et on réaffiche | # le total puisqu'on vient de décaler d'une ligne. | # | # Paramètres et Sortie : aucuns | # Variables positionnées : thisFileNameDisplay | # -------------------------------------------------------------------------------------- displayverif() { if [ "${opt_g}" != 'y' ]; then # Nettoie l'affichage pour nouvelle ligne printf "\r" clearln if [ "${displayTotal}" = 'y' ]; then printf "\n%s\n" "${TOTAL_SEPARATOR}" clearln linedisplay "${TOTAL_LABEL}"\ ${globalSize}\ "${globalDisplaySize}"\ $(( ${initTrnf} + ${doneSoFar} ))\ ${initTrnf}\ ${globalStartTime}\ '' up up fi fi if [ "${opt_g}" = 'y' ]; then thisFileNameDisplay="${thisFileBaseName}" else # Idem pour la longueur de la chaine, buf utf-8 de {#var} avec dash local len=$( printf '%s' "${thisFileBaseName}" | wc -m ) if [ ${len} -gt 23 ]; then thisFileNameDisplay="$( yellow )$( cututf8 "${thisFileBaseName}" 1 10 )...$( cututf8 "${thisFileBaseName}" $(( ${len} - 9 )) ${len} )" else thisFileNameDisplay="$( yellow )$( printf '%s' "${thisFileBaseName}" )$( printf ' ' | head -c $(( 23 - ${len} )) )" fi printf "\r%s " "${thisFileNameDisplay}" normal printf "vérifications..." fi } # ====================================================================================== # 7) Upload à proprement parler | # -------------------------------------------------------------------------------------- # Boucle principale d'émission des fichiers vers la Freebox | # Toutes les vérifications de paramètres sont faites, peut passer à l'émission des | # fichiers. L'affichage de fait "à la mode curl". | # -------------------------------------------------------------------------------------- uploadthisfile() { # Variables de boucle ----------- local urlToUpload # URL soumise à la Freebox pour le fichier en cours local thisStartTime # Heure de début de l'upload en cours local thisUpload # Chaine JSON pour le suivi de cet upload local thisStatus # Status de l'upload local thisCurTrnf # Taille courante transférée local thisRxRate # Taux de réception courant local startPause # Début de la pause (le cas échéant) local tmpIDUpload # Variables d'affichage invariantes pour ce fichier : nom et taille totale displayFileSize=$( number5 ${thisFileSize} ) thisCurTrnf=${thisInitTrnf} if [ "${opt_g}" != 'y' ]; then # Nettoie l'affichage pour nouvelle ligne printf "\r" clearln fi display "${thisFileNameDisplay}"\ ${thisFileSize}\ "${displayFileSize}"\ ${thisInitTrnf}\ ${thisInitTrnf}\ 0\ ''\ "${displayTotal}"\ ${globalSize}\ "${globalDisplaySize}"\ $(( ${doneSoFar} + ${initTrnf} ))\ ${initTrnf}\ ${globalStartTime}\ '' if [ ${thisFileSize} -ne ${thisInitTrnf} ]; then if [ "${opt_s}" = 'y' ]; then urlToUpload="/${thisFileBaseName}" else urlToUpload="$( printf '%s' "$(readlink -f -- "${fileToUpload}")" | cut -c ${#localWebRoot}- )" fi urlToUpload="http://${localIPPort}$( escapeforupload "${urlToUpload}" )" fbx_upload "${urlToUpload}" exiterr $? "${eMSG_UPLOAD}" thisStartTime=$( date -u +%s) if [ ${globalStartTime} -eq 0 ]; then globalStartTime=${thisStartTime} fi ID_UPLOAD=$( sed 's/{"result"://;s/}//' "${TMPLOG}" ) if [ "${opt_g}" = 'y' ]; then echo "ID_UPLOAD=${ID_UPLOAD}" >"${TMPLOG}.g" fi startPause=0 while : do sleep "${opt_d}" fbx_getdownloadlist || return $? thisUpload=$( stripjsonheader |\ sed -n "/,\"id\":${ID_UPLOAD}/s/{//;/,\"id\":${ID_UPLOAD}/s/}//p" ) if [ -n "${thisUpload}" ]; then thisStatus=$( printf '%s' "${thisUpload}" | sed 's/.*"status":"//;s/","url":.*//' ) thisCurTrnf=$(printf '%s' "${thisUpload}" | sed 's/.*,"transferred"://;s/,"name":.*//' ) thisRxRate=$( printf '%s' "${thisUpload}" | sed 's/.*,"rx_rate"://;s/,"size":.*//' ) else thisStatus='missing' thisRxRate='' fi if [ "${thisStatus}" != 'paused' ] && [ ${startPause} -ne 0 ]; then # Attention, selon le serveur Web, la pause va repartir de là où on en # était (Apache2 par ex.) ou repartir de zéro (server python automatique) if [ "${supportRange}" = 'y' ]; then startPause=$(( $( date -u +%s) - ${startPause} )) # Décalage du temps de pause thisStartTime=$(( ${thisStartTime} + ${startPause} )) else startPause=$(( $( date -u +%s) - ${thisStartTime} )) # Décalage d'upload de ce fichier + pause thisStartTime=$( date -u +%s) # puisqu'on repart de 0... fi globalStartTime=$(( ${globalStartTime} + ${startPause} )) startPause=0 fi case "${thisStatus}" in 'done' | 'running') if [ "${thisStatus}" = 'done' ]; then tmpIDUpload=${ID_UPLOAD} ID_UPLOAD=0 if [ "${opt_g}" = 'y' ]; then printf 'ret=0' >"${TMPLOG}.g" fi fi display "${thisFileNameDisplay}"\ ${thisFileSize}\ "${displayFileSize}"\ ${thisCurTrnf}\ ${thisInitTrnf}\ "${thisStartTime}"\ "${thisRxRate}" \ "${displayTotal}"\ ${globalSize}\ "${globalDisplaySize}"\ $(( ${initTrnf} + ${doneSoFar} + ${thisCurTrnf} - ${thisInitTrnf} ))\ ${initTrnf}\ ${globalStartTime}\ "${thisRxRate}" if [ "${thisStatus}" = 'done' ]; then fbx_dlremove ${tmpIDUpload} 'n' return 0 fi ;; 'missing') ID_UPLOAD=0 displayerr "${thisFileNameDisplay}"\ ${thisFileSize}\ "${displayFileSize}"\ ${thisCurTrnf}\ "${eMSG_MISSING}" if [ "${opt_g}" = 'y' ]; then printf '%s; ' "ret=${eCOD_MISSING}" >"${TMPLOG}.g" printf '%s' "retMsg=\"${eMSG_MISSING}\"" >>"${TMPLOG}.g" fi return ${eCOD_MISSING} ;; 'paused') if [ ${startPause} -eq 0 ]; then startPause=$( date -u +%s) displayerr "${thisFileNameDisplay}"\ ${thisFileSize}\ "${displayFileSize}"\ ${thisCurTrnf}\ "${wMSG_PAUSE}" fi ;; *) ID_UPLOAD=0 displayerr "${thisFileNameDisplay}"\ ${thisFileSize}\ "${displayFileSize}"\ ${thisCurTrnf}\ "${eMSG_ERR_TRNF}" if [ "${opt_g}" = 'y' ]; then printf '%s; ' "ret=${eCOD_ERR_TRNF}" >"${TMPLOG}.g" printf '%s' "retMsg=\"${eMSG_ERR_TRNF}\"" >>"${TMPLOG}.g" fi return ${eCOD_ERR_TRNF} ;; esac done fi } # ====================================================================================== # Utilitaire pour construire la chaine d'arguments quand on est lancé par Nautilus | buildargs() { printf '%s' "${1}" |\ while read -r file; do printf " '%s'" "$(printf '%s' "${file}" | sed -e "s|'|'\\\\''|g" )" done } # ====================================================================================== # Le script comporte les phases suivantes : | # 1) Détection du mode graphique (si on est lancés par Nautilus) | # 2) Lecture et vérification des paramètres passés au script | # 3) Récupération adresses IP (local & Freebox), mot de passe et racine du serveur Web | # 4) Vérifications locales sur la liste de fichiers à uploader passés au script. | # 5) Vérifications du port web local et éventuellement lancement d'un serveur Web. | # 6) Vérifications distantes sur la liste des fichiers passés au script | # 7) Upload à proprement parler avec | # 7.a) Préalablement, si le suivi du total est activé, on calcule les valeurs | # globales (tailles totales du téléchargement, déjà téléchargé, etc....) | # 7.b) suivi de l'upload (selon la méthode spécifiée) | # 7.c) si l'upload se termine en anomalie, on relit le répertoire pour savoir | # quelle est la taille exacte de ce qui a pu être transféré. | # 8) Le cas échéant, on renomme le fichier. | # | # Note: les fonctions de vérification sont réutilisées dans le téléchargement, en | # effet, il est toujours possible qu'un autre utilisateur ait mis des fichiers | # pendant que notre téléchargement est en cours. Par ailleurs ces fonctions | # positionnent des variables propres au fichier traité dans la boucle. | main() { local args # Arguments calculés (optarg ou chaine Nautilus) # Les variables suivantes contiennent l'option correspondante passée # à ligne de commande (lorsque l'option est passée et non vide !) local opt_c='' local opt_i='' local opt_p='' local opt_l='' local opt_r='' local opt_f='' local opt_g='' local opt_q='' local opt_s='' local opt_t='' local opt_port='' local opt_v='' local opt_d='' # Auto targets, uniquement dans le fichier de configuration local autotargets='' # Variables de configuration réseau (adresses IP, ports, etc...) # local fbxIPPort='' # On a besoin de celle là en global pour le 'trap' local fbxPassword='' local localIPPort='' local localWebRoot='' local fbxCSRF='' # Jeton CSRF retourné au login (pour upload seulement) local targetIsDir='' # Si opt_t, indique si la cible est un répertoire local targetDirName='' # Répertoire de la cible (=opt_t si c'est un rép.) local targetLabel='' # Label du disque de la target (utile si disk externe) local defaultLabel='' # Label du disque où se fait le téléchargement local targetBaseName='' # 'basename' de la cible (pour éviter de le recalculer) local smallestFile='' # Fichier le plus petit pour tester le 'range' local biggestSize=0 # Taille du plus gros fichier (pour copie disk externe) local supportRange='' # Indique si le serveur local supporte 'range' ou pas local globalSize=0 # Taille totale des fichiers à uploader local initTrnf # Valeur initiale de la taille déjà transférée local initUselessTrnf # Valeur initiale taille transférée inutile (no 'range') local dlDir # Répertoire de téléchargement paramétré sur la Freebox local displayTotal='' # Affiche-t-on la ligne total ou pas. local globalDisplaySize=0 # Taille globale en chaine affichage mode curl local thisFile # Fichier courant (passé en paramètres) local thisFileBaseName # basename du fichier courant local thisFileBaseNameEscaped # basename "escapée" du fichier local fileToUpload # Fichier réel à uploader local thisFileNameDisplay # Nom affiché du fichier en cours d'upload local thisFileSize # Taille du fichier en cours d'upload local thisInitTrnf # Taille initiale déjà transférée du fichier en cours d'upload local renameTarget # Comment on doit renommer le fichier après transfert local doneSoFar=0 # Taille uploadée à ce point local globalStartTime=0 # Heure de début de l'ensemble des transferts local ret # Variables retournées (par fichier) par uploadthisfile local retmsg # dans le cas "graphic" où il est lancé en subprocess (pipe) # ====================================================================================== # 1) Détection automatique de mode graphique | # -------------------------------------------------------------------------------------- # On détecte si on est appelé par Nautilus, car dans ce cas, il ne faut pas lire les | # arguments passés au script, mais la variable NAUTILUS_SCRIPT_SELECTED_FILE_PATHS | # -------------------------------------------------------------------------------------- if [ -n "${NAUTILUS_SCRIPT_WINDOW_GEOMETRY=}" ]; then args="$( buildargs "${NAUTILUS_SCRIPT_SELECTED_FILE_PATHS}" )" eval set -- "$args" opt_g='y' else # ====================================================================================== # 2) Lecture et vérification des paramètres passés au script | # Pour ce traitement, on le fait directement ici sans appeler une fonction car on | # a besoin de changer les arguments de cette fonction (main) pour la suite de façon | # à n'avoir que les fichiers à télécharger et pas un mélange avec les options. | # -------------------------------------------------------------------------------------- local thisScript=$( basename -- ${0} ) args="$( getopt -o c:i:p:l:r:fgqst:vd:hV \ -l config:,ipfbx:,password:,local:,root:,force,graphic,quit,server,target:,port:,verbose,display:,help,version\ -n "${thisScript}" -- "$@" 2>&1 )" ||\ { usage "$( printf '%s' "${args}" | head -n1 )"; exit; } eval set -- "$args" while true ; do case "${1}" in -c|--config) opt_c="${2}" ; shift 2 ;; -i|--ipfbx) opt_i="${2}" ; shift 2 ;; -p|--password) opt_p="${2}" ; shift 2 ;; -l|--local) opt_l="${2}" ; shift 2 ;; -r|--root) opt_r="${2}" ; shift 2 ;; -f|--force) opt_f='y' ; shift ;; -g|--graphic) opt_g='y' ; shift ;; -q|--quit) opt_q='y' ; shift ;; -s|--server) opt_s='y' ; shift ;; -t|--target) opt_t="${2}" ; shift 2 ;; --port) opt_port="${2}" ; shift 2 ;; -v|--verbose) opt_v='y' ; shift ;; -d|--display) opt_d="${2}" ; shift 2 ;; -h|--help) usage '' ;; -V|--version) printf "%s\n" "${thisScript} - Version : ${VERSION}" exit 0 ;; --) shift ; break ;; *) printf "Erreur interne d'optarg !\n" exit 1 ;; esac done fi if [ ${#} -eq 0 ]; then usage 'Aucun fichier à uploader spécifié !' fi vecho "${opt_v}" "${vMSG_PARAM_PARSE_OK}" # ====================================================================================== # 2-bis) Détection de la présence de curl. | # -------------------------------------------------------------------------------------- if ! which curl >/dev/null; then exiterr ${eCOD_CURL_NOT_INSTALLED} "${eMSG_CURL_NOT_INSTALLED}" fi # ====================================================================================== # 3) Récupération adresses IP (local & Freebox), mot de passe et racine du serveur Web | # -------------------------------------------------------------------------------------- getconfig checkerr $? "${eMSG_CONFIG}" "${vMSG_READ_CONFIG_OK}" # ====================================================================================== # 4) Vérifications locales au PC sur la liste des fichiers à uploadés passés au script | # -------------------------------------------------------------------------------------- veriflocal 'globalSize' "${@}" checkerr $? "${eMSG_LOCAL_VERIF}" "${vMSG_LOCAL_CHECK_OK}" # ====================================================================================== # 5) Vérifications du port web local et détermination des capacités du serveur (range) | # -------------------------------------------------------------------------------------- checkserver exiterr $? "${eMSG_CHECK_SERVER}" # Messages vecho dans la fonction checkserver # ====================================================================================== # 6) Vérifications avec la Freebox | # -------------------------------------------------------------------------------------- # 6.a) Initialisation ______________________________ TMPLOG="$( mktemp )" && initfreebox exiterr $? "${eMSG_INIT_FBX}" # 6.b) Vérification de la cible (le cas échéant)_____ if [ -n "${opt_t}" ]; then veriftarget ${#} checkerr $? "${eMSG_VERIF_TARGET}" "${vMSG_VERIF_TARGET}" fi # 6.c) Vérifie fichiers et liste téléchargements____ veriffreebox 'initTrnf' 'initUselessTrnf' "$@" checkerr $? "${eMSG_VERIF_FBX}" "${vMSG_VERIF_FBX}" # 6.d) Vérification expace disponible________________ verifstorage $(( ${globalSize} - ${initTrnf} - ${initUselessTrnf} )) checkerr $? "${eMSG_VERIF_SPACE}" "${vMSG_FBX_DISK_SPACE_OK}" vecho "${opt_v}" "${vMSG_ALL_CHECK_OK}" opt_v='' # ====================================================================================== # 7) L'upload des fichiers à proprement parler ! | # -------------------------------------------------------------------------------------- displayheader "$#" for thisFile in "$@" do # 7.a) Vérifications pour le fichier qu'on s'apprête à uploader_______________ thisFileBaseName="$( basename -- "${thisFile}" )" displayverif veriflocal 'thisFileSize' "${thisFile}" exiterr $? "${eMSG_LOCAL_VERIF}" veriffreebox 'thisInitTrnf' '' "${thisFile}" exiterr $? "${eMSG_VERIF_FBX}" verifstorage $(( ${globalSize} - ${doneSoFar} - ${initTrnf} - ${initUselessTrnf} )) exiterr $? "${eMSG_VERIF_SPACE}" # 7.b) Au besoin lancement du serveur, et calcul du nom réel du fichier_______ # car avec le serveur temporaire on met juste un lien symbolique if [ "${opt_s}" = 'y' ]; then launchserver exiterr $? "${eMSG_LAUNCH_SERVER}" fileToUpload="${TMPROOT}/${thisFileBaseName}" ln -s -- "$( readlink -f -- "${thisFile}")" "${fileToUpload}" exiterr $? "${eMSG_SYM_LINK}" else fileToUpload="${thisFile}" fi # 7.c) Upload a proprement parler ____________________________________________ if [ "${opt_g}" = 'y' ]; then uploadthisfile | zenity --progress \ --text="${fileToUpload}" \ --auto-close \ --title="${gZEN_TITLE}" || control_c # 7.c.1) Comme uploadthisfile est en subshell il transmet les erreurs_______ # eventuelles via un fichier qu'on source ici avant de le supprimer. . "${TMPLOG}.g" : >"${TMPLOG}.g" if [ ${ret} -ne 0 ]; then exiterr ${ret} "${retMsg}" fi else if ! uploadthisfile; then { # 7.c.2) En cas d'erreur, on relit la taille du fichier dans le rép.________ # pour savoir exactement où l'upload s'est arrêté. ret=$? if [ "${opt_q}" = 'y' ]; then echo exit ${ret} fi fbx_readdir "${dlDir}" || return $? local dlDirList="$( stripjsonheader )" local line="$( printf '%s' "${dlDirList}" | \ grep "\"name\":\"${thisFileBaseNameEscaped}\"" )" if [ -n "${line}" ]; then local thisFileDistSize=$( printf '%s' "${line}" | sed 's/.*,"size"://;s/}//' ) fi if [ "${displayTotal}" = 'y' ]; then globalSize=$(( ${globalSize} - ${thisFileSize} + ${thisFileDistSize} )) globalDisplaySize=$( number5 ${globalSize} ) fi thisFileSize=${thisFileDistSize} renameTarget='' } fi echo fi # 7.d) On retire le lien symbolique créé en 7.b_______________________________ if [ "${opt_s}" = 'y' ]; then rm -f -- "${fileToUpload}" fi # 7.e) Et enfin on renomme le fichier le cas échéant__________________________ if [ -n "${renameTarget}" ]; then fbx_move "${dlDir}/${thisFileBaseNameEscaped}" "${renameTarget}" exiterr $? "${eMSG_FBX_MOVE}" fi # 7.f) Et on peut alors mettre à jour la taille uploadée______________________ doneSoFar=$(( ${doneSoFar} + ${thisFileSize} - ${thisInitTrnf} )) done } main "${@}"