#!/bin/bash
# Copyright (C) 2010,2011,2012 Zakhar for ubuntu-fr

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# The text of the GNU General Public License is available at
# http://www.gnu.org/licenses/gpl-2.0.html; if you cannot access it,
# write to the Free Software Foundation, Inc., 51 Franklin Street,
# Fifth Floor, Boston, MA 02110-1301 USA.
#===========================================================

# Fonction :
# ----------
#  - Rassemble des fichiers découpés par XtremSplit
#  - Vérifie le MD5 s'il est inclus dans les fichiers source
#
# Usage :
# -------
#  tuXtremMerge Nom_Du_Fichier_NNN.xtm
#   (N'importe lequel des fichiers fonctionne : 001 ou autre)
#
# Tested : Ubuntu Lucid,Karmic, Busybox (Synology DS211j, DS1010+, DS411+, DS209, DS211+)
# ------
#
# Version : 1.7.1
# -------
#
# Date : 2012-01-21
# -----
#
# Author : Zakhar (ubuntu-fr)
# ------
#
# Contributor :  // Ajouter votre nom si vous contribuez et redistribuez //
# -----------
#  Hypnose @ ubuntu.fr : amélioration des spécifications
#  Totor @ ubuntu.fr : amélioration et optimisation du code
#  Moonface @ ubuntu.fr : témoignage, test et debug de fonctionnement sur Synology DS211j
#  stadros83 @ ubuntu.fr : debug sur Synology DS1010+ (merci pour la patience !)
#                          A partir de là, certaines spécificités Synology (Busybox) sont
#                          dans le script "compagnon" busyXtremMerge
#  zootroopa @ ubuntu.fr : debug et test sur Synology DS411+
#  Gajo22 @ ubuntu.fr : témoignage sur Synology DS209 = OK (après installation coreutils)
#  NiKo88 @ ubuntu.fr : témoignage sur Synology DS211+
#  Respawner @ ubuntu.fr : aide pour le format des .exe
#  Hizoka @ ubuntu.fr : signalement de bug pour noms de fichiers bizarres !
#  Josh63 @ ubuntu.fr : témoignage sur Synology DS211
#  McMyst @ ubuntu.fr : témoignage sur Dlink DNS 325
#
# History :
# ---------
#  1.7.1
#  - Correction du calcul du nom de la destination lorsque le nom du fichier source
#    contient des caractères tels que {}
#  1.7.0
#  - Prise en compte des fichiers au format .exe
#    Le différence réside seulement dans le premier fichier dont il faut retirer
#    305664 octets au début et 24 octets à la fin, pour avoir l'équivalent du xtm.
#  1.6.2
#  - Retrait du "process substitution" a profit d'une fonction avec un simple "pipe"
#    davantage compatible avec d'autres Linux (comme busybox)
#  1.6.1
#  - Simplification de la substitution du md5sum pour busybox (nécessite 1.0.1 busyXtremMerge)
#  1.6.0
#  - Modification de l'algorithme pour le premier fichier afin de le rendre compatible Busybox
#    (pas de ibs/obs sur le dd). Ainsi modifié, c'est également plus rapide sur Linux standard !
#  - Simplification algo sur le dernier fichier (idem ci-dessus).
#  - Rajout de l'option pipefail pour ne pas "masquer" les éventuelles erreurs lors des "pipes"
#    du processus de recollage.
#  1.5.6
#  - Remplacement de od par dd lorsqu'on doit lire des chaines.
#  - Remplacement de basemane par interne ${var##*/}
#  1.5.5
#  - Retour au code 1.5.3. (plus amélioration: suppression de 'du + cut' remplacé par 'stat -c%s')
#    et séparation du code non fini pour Synology 1010+
#  1.5.4
#  - Tentative du mise au point pour Synology 1010+ qui utilise une Busybox 1.16.1
#  1.5.2
#  - Suppression useless cat ! (Totor @ ubuntu.fr)
#  - Remplacement du fichier temporaire par un process substitution (Totor @ ubuntu.fr)
#  - Isolation des libellés et codes erreur.
#  - Nouveaux paramètre : V (Version), t (temps) [et donc désormais n'affiche plus par défaut les temps]
#  1.5.1
#  - Sécurité : création du fichier temporaire avec mktemp
#  - Amélioration de libellés
#  - Ordonnancement plus logique des tests de cohérence initiaux.
#  - Optimisations mineures.
#  1.5.0
#  - Suppression et fusion des options -o et -a !..
#    Le script utilise désormais toujours l'option -o (ce qui est sa plus-value)
#    Si un traitement partiel est détecté, il reprend là où ça en était (deuxième plus-value)
#    Ces deux options sont donc désormais inutiles car implicites... et cela simplifie le code !
#  - Le script fonctionne désormais "à la mode Firefox", c'est à dire que le fichier résultat
#    est d'abord écrit dans resultat.part, et celui-ci est renommé à la fin du traitement.
#    La fonction de reprise tient compte de ce nouveau fonctionnement, de même que l'option -f
#  - Le premier fichier bénéficie aussi de l'optimisation (parallelisme copie/md5)
#  1.2.0
#  - Suppression de l'option -s devenue inutile. Le fichier est maintenant mis par défaut (suggestion Hypnose @ ubuntu-fr)
#    dans le même répertoire que les fichiers source (xtm). Le mode par défaut précédent s'obtient en spécifiant "."
#    (c'est à dire le répertoire courant) comme destination.
#  - Rajout des options d'optimisation -a et -o
#    -a permet de lancer le script en cours de téléchargement et d'obtenir un résultat partiel
#    -o optimise les MD5/concaténation via un tee (tout est fait en une seule commande, une seule lecture).
#  - Diverses corrections de bugs
#  1.1.0
#  - Rajout de l'exploration des arguments
#  - Rajout des options : voir description dans la fonction usage
#  - Un peu de couleurs pour égayer l'affichage !
#  1.0.1
#  - Correction du chemin si le script est lancé depuis via PATH (exemple script placé dans /usr/local/bin)
#  - Affichage des fichiers tels que passés en paramètres avec leurs éventuels chemins relatifs/absolus
#  1.0.0
#  - Supporte d'être lancé avec des noms de fichiers contenant des chemins
#  - Deuxième paramètre optionnel pour spécifier le fichier destination
#  - Support des noms de fichiers contenant des espaces (source et destination)
#  - Vérification au préalable de l'existence du fichier destination pour éviter les écrasement intempestifs
#  - Vérification avant de calculer les MD5 qu'on va bien pouvoir écrire sur le fichier destination


#==========================================================
#  Messages and Error codes.
#+ This way it is easyier, if somebody would like to localise
readonly MSG_ERROR="\E[1;31mErreur\E[0m\n"
readonly MSG_OK="\E[1;32mOK\E[0m"
readonly MSG_ATTENTION="\E[1;33mAttention !\E[0m"

readonly MSG_BAD_OPTION='Option - incorrecte'
readonly MSG_TOO_MANY_PARAMS='Trop de paramètre :'
readonly MSG_UNKNOWN_OPTION='Option inconnue :'
readonly MSG_UNSPECIFIED_SOURCE_FILE='Fichier source non spécifié'
readonly MSG_VERSION='tuXtremMerge (turbo XTM), version 1.7.1'
readonly MSG_OPTION_M_AND_N="Vous ne pouvez spécifier les options \E[1;32m-m\E[0m et \E[1;32m-n\E[0m simultanément."
readonly MSG_IGNORING_OPTION_F="L'option \E[1;32m-f\E[0m sera ignorée puisque l'option -\E[1;32m-m\E[0m est spécifiée."
readonly MSG_IGNORING_DEST="Le nom du fichier résultat sera ignoré puisque l'option \E[1;32m-m\E[0m est spécifiée."
readonly MSG_CHECKING='Vérifications ...  '
readonly MSG_CHECKING_FIRST_SOURCE_FILE="Vérification d'existence du premier fichier source..."
readonly MSG_TIP="\E[1;34mAstuce :\E[0;34m il faut le premier et le dernier fichier, corrects et complets, pour que le script puisse fonctionner.\nVous pouvez optimiser le résultat en récupérant ces deux fichiers en priorité.\E[0m"
readonly MSG_SUCCESS="==================================================\n\E[1;32mToutes les opérations sont terminées avec succès !\E[0m"
readonly MSG_FIRST_FILE_ERROR=' non trouvé, vide ou erreur'
readonly MSG_FIRST_FILE_FOUND='Premier fichier source trouvé :'
readonly MSG_OPTION_M_AND_NO_MD5="\E[1;33mRien à faire !\E[0m\nL'option \E[1;32m-m\E[0m est spécifiée, or il n'y a pas MD5 à vérifier dans ces fichiers xtm."
readonly MSG_CHECKING_LAST_SOURCE_FILE="Vérification d'existence du dernier fichier source..."
readonly MSG_LAST_FILE_ERROR="Fichier %s%3.3u.xtm non trouvé ou vide\n"
readonly MSG_LAST_FILE_FOUND="Dernier fichier source trouvé: %s%3.3u.xtm\n"
readonly MSG_FILE_SIZES_OK='Tailles premier et dernier fichier cohérentes.'
readonly MSG_FILE_SIZES_ERROR='Premier ou dernier fichier de taille incohérente.'
readonly MSG_NO_FILE_WRITTEN="Aucun fichier résultat ne sera écrit car l'option \E[1;32m-m\E[0m est spécifiée."
readonly MSG_COMPUTING_DEST="Détermination de l'emplacement du résultat..."
readonly MSG_DISPLAY_DEST='Emplacement du résultat :'
readonly MSG_CHECK_DEST_WRITABLE="Vérification de la possibilité d'écrire le résultat : existence, autorisation d'écriture, espace disponible, etc..."
readonly MSG_WARN_FORCED_OVERWRITE="\nEcrasement forcé par l'option \E[1;32m-f\E[0m, le fichier résultat existe déjà."
readonly MSG_WARN_OVERWRITE="\nLe fichier : %s existe déjà.\n"
readonly MSG_FILE_SIZE_MATCHES='La taille du fichier correspond au résultat prévu dans le xtm.'
readonly MSG_FILE_SIZE_DOES_NOT_MATCH='La taille du fichier ne correspond pas au résultat prévu dans le xtm.'
readonly MSG_OVERWRITE_HINT="Si vous désirez ré-écrire ce fichier effacez/renommez-le au préalable ou spécifiez l'option \E[1;32m-f\E[0m pour forcer l'écrasement"
readonly MSG_WRITE_ERROR="Ecriture de : %s impossible.\nVeuillez vérifier que vous avez l'autorisation d'écrire ce fichier et que son nom est correct.\n"
readonly MSG_INSUFFICIENT_SPACE="Espace insuffisant sur %s pour créer le fichier %s.\nEspace nécessaire : %'u\nEspace disponible : %'u\n" 
readonly MSG_CHUNKS_AVAIL='fichiers déjà traités.'
readonly MSG_INFO_DELETED_OLD_FILE="Fichier partiel incohérent supprimé par option \E[1;32m-f\E[0m"
readonly MSG_INCOHERENT_PARTIAL_FILE="La taille du fichier partiel n'est pas un multiple de la taille de découpage des fichiers xtm.\nSupprimez ce fichier (%s.part) ou utilisez l'option \E[1;32m-f\E[0m\n"
readonly MSG_ALL_CHECKED_OK='Vérifications pour le fichier résultat terminées.'
readonly MSG_PROCESSING_START="\E[1mTraitement optimisé des %u fichiers\E[0m\n"
readonly MSG_PROCESSING_RESTART="\E[1mReprise du traitement à partir du fichier %u\E[0m\nReste à traiter %u fichier(s)\n"
readonly MSG_SEPARATOR='=================================='
readonly MSG_PROCESSING_FILE='Traitement de %s%3.3u.%s ...  '
readonly MSG_FILE_MISSING='*** Le fichier est manquant ou de taille incorrecte.'
readonly MSG_FILE_MISSING_TIP='*** Relancez le programme lorsque le fichier sera complet.'


readonly E_BAD_OPTION=65
readonly E_UNKNOWN_OPTION=66
readonly E_TOO_MANY_PARAMS=67
readonly E_UNSPECIFIED_SOURCE_FILE=68
readonly E_MSG_OPTION_M_AND_N=69
readonly E_FIRST_FILE_ERROR=80
readonly E_LAST_FILE_ERROR=81
readonly E_FILE_SIZES_ERROR=82
readonly E_WRITE_ERROR=83
readonly E_INSUFFICIENT_SPACE=83
readonly E_INCOHERENT_PARTIAL_FILE=84
readonly E_WARN_OVERWRITE=96
readonly E_CRITICAL_ERROR=127

#==========================================================
# Utility functions
# usage : affiche l'usage/aide du script
# v_echo : affichage pour l'option verbeux

usage()
{
  if [ -n "$1" ]; then echo "$1"; fi
  cat <<EOF
Usage : tuXtremMerge [Options] Source [Destination]

Source : le fichier source, généralement de la forme [chemin/]fichier.001.xtm
         On peut cependant indiquer n'importe lequel des xtm de la série, par
         exemple fichier.007.xtm

Destination : nom du fichier destination souhaité.
         Si la destination est un répertoire existant, le fichier résultat sera
         stocké dans ce répertoire, avec le nom indiqué au découpage en xtm.
         Par défaut, le fichier résultat est stocké dans le répertoire du
         fichier source et porte le nom indiqué lors du découpage en xtm.

Options :
 -h  Affiche le présent message d'aide.
 -n  Ne vérifie pas les md5 (concaténation sans vérifier).
 -m  Vérifie seulement les md5 (pas de concaténation)
 -f  Force l'écriture du fichier résultat.
     Si un fichier existe déjà il est écrasé.
 -v  Verbeux. Permet aussi de diagnostiquer les incohérences entre options.
 -t  Temps. Affiche les heures de début et fin de traitement.
     Doublé (tt) affiche davantage de temps intermédiaires.
 -V  Affiche la version du script.

ASTUCE :
 Dans tous les cas, le premier et le dernier fichier sont nécessaires pour
 que le script puisse au moins commencer à assembler. S'il s'agit de fichiers
 téléchargés, veillez donc à récupérer ces deux fichiers en priorité.
EOF
}

v_echo()
{
  if [ $OPTION_v ]; then echo -e "*** $1"; fi
}

#______________________________
# Function that scan parameters
# It works with style: -a -v
# but also BSD style : -av

scan_parameters()
{
  for param in "$@"
  do
    case "$param" in
      -* )
           if [ "$param" == "-" ]; then
             usage "$MSG_BAD_OPTION"
             exit $E_BAD_OPTION
           fi
           i=1
           while [ $i -lt ${#param} ]
           do
             case ${param:$i:1} in
               h )
                  usage
                  exit 0
                  ;;
               m )
                  OPTION_m='1'
                  ;;
               n )
                  OPTION_n='1'
                  ;;
               f )
                  OPTION_f='1'
                  ;;
               t )
                  if [ ${param:$i:2} = 'tt' ]; then
                    OPTION_t='2'
                    i=$(( $i + 1 ))
                  else
                    OPTION_t='1'
                  fi
                  ;;
               v )
                  OPTION_v='1'
                  ;;
               V )
                  if [ -n "$MSG_BUSY_VERSION" ]; then
                    echo "$MSG_BUSY_VERSION"
                  fi
                  echo "$MSG_VERSION"
                  exit 0
                  ;;
               * )
                  usage "$MSG_UNKNOWN_OPTION -${param:$i:1}" 
                  exit $E_UNKNOWN_OPTION
                  ;;
             esac
             i=$(( $i + 1 ))
           done
           ;;
      *)
         if [ -z "$DISPLAY_SOURCE_FILE_NAME" ]; then
           DISPLAY_SOURCE_FILE_NAME="$param"
         elif [ -z "$DISPLAY_DEST_FILE_NAME" ]; then
           DISPLAY_DEST_FILE_NAME="$param"
         else
           usage "$MSG_TOO_MANY_PARAMS $param"
           exit $E_TOO_MANY_PARAMS
         fi
         ;;
    esac
  done

  if [ -z "$DISPLAY_SOURCE_FILE_NAME" ]; then
    usage "$MSG_UNSPECIFIED_SOURCE_FILE"
    exit $E_UNSPECIFIED_SOURCE_FILE
  else
    SOURCE_FILE_NAME=$( readlink -f "$DISPLAY_SOURCE_FILE_NAME" )
  fi
}

#_________________________________
# Functions that check parameters
# and give some warnings or errors
# if things are not correct


check_parameters()
{
if [ $OPTION_m ]; then
  if [ $OPTION_n ]; then
    echo -e "$MSG_ERROR$MSG_OPTION_M_AND_N"
    exit $E_MSG_OPTION_M_AND_N
  fi
  if [ $OPTION_f ]; then
    v_echo "$MSG_IGNORING_OPTION_F"
  fi
  if [ -n "$DISPLAY_DEST_FILE_NAME" ]; then
    v_echo "$MSG_IGNORING_DEST"
  fi
fi
}

success()
{
  if [ ! $OPTION_m ]; then
    #  If we mv directly it appears that system syncs, and so the script appears to run longer
    #+ When we rm the file first, the async write continues after the script ends.
    rm "$DEST_FILE_NAME"
    mv "$DEST_FILE_NAME_PART" "$DEST_FILE_NAME"
  fi
  echo -e "$MSG_SUCCESS"
  timing 1
  exit 0
}

tip()
{
  echo -e "$MSG_TIP"
  exit $1
}

nop()
{
  cat - >/dev/null
}

md5check()
{
  md5sum | cut -b 1-32 | grep -iq "$( echo ${1} )"
}

timing()
{
  if [ $OPTION_t -ge $1 ]; then date +%T.%N; fi
}


#==========================================================
# START OF THE MAIN PROGRAM HERE !
# /TODO 
# -> Traiter les interruptions (Ctrl-C) et erreurs par un truncate (au lieu de supprimer)

#_________________________________________
# Variables declaration and initialisation

declare -i i size SOURCE_FILE_NB DEST_FILE_NAME_LENGTH SPACE_AVAIL DISK_SPACE_NEEDED DEST_FILE_SIZE CHUNK_SIZE CHUNKS_AVAIL fMD5 LAST_SOURCE_FILE_SIZE 
OPTION_m=''
OPTION_n=''
OPTION_f=''
OPTION_v=''
declare -i OPTION_t=0
DISPLAY_SOURCE_FILE_NAME=''
DISPLAY_DEST_FILE_NAME=''
declare -i CHUNKS_AVAIL=0


set -o pipefail

scan_parameters "$@"

timing 1

if [ ! $OPTION_v ]; then
  echo -n "$MSG_CHECKING"
fi

check_parameters

#==========================================================
# Checking file existence
# - On commence par vérifier le 001 qui contient le header

v_echo "$MSG_CHECKING_FIRST_SOURCE_FILE"


declare -i fEXE EXE_OFFSET

if echo "$SOURCE_FILE_NAME" | grep -q '.exe$'; then
  fEXE=1
  EXE_OFFSET=305664
  FIRST_SUFFIX='exe'
else
  fEXE=0
  EXE_OFFSET=0
  FIRST_SUFFIX='xtm'
fi

RADIX=$( echo "$SOURCE_FILE_NAME" | sed "s/...\.${FIRST_SUFFIX}\$//" )
DISPLAY_RADIX=$( echo "$DISPLAY_SOURCE_FILE_NAME" | sed "s/...\.${FIRST_SUFFIX}\$//" )

FIRST_SOURCE_FILE_NAME="${RADIX}001.${FIRST_SUFFIX}"

if [ ! -f "$FIRST_SOURCE_FILE_NAME" ] || [ ! -s "$FIRST_SOURCE_FILE_NAME" ]; then
  echo -e "${MSG_ERROR}${DISPLAY_RADIX}001.${FIRST_SUFFIX}${MSG_FIRST_FILE_ERROR}"
  tip $E_FIRST_FILE_ERROR
else
  v_echo "$MSG_FIRST_FILE_FOUND ${DISPLAY_RADIX}001.${FIRST_SUFFIX}"
fi

fMD5=$( od -An -vtu1 -j$(( ${EXE_OFFSET} + 91 )) -N1 "$FIRST_SOURCE_FILE_NAME" )

#==========================================================
# Checking if MD5 is included in that xtm
# If not and only a MD5 check is asked (option -m) exit with message

if [ $fMD5 -eq 0 ] && [ $OPTION_m ]; then
    echo -e "$MSG_OPTION_M_AND_NO_MD5"
    exit 0
fi

CHUNK_SIZE=$(( $( stat -c%s "$FIRST_SOURCE_FILE_NAME" ) - 104 - ${fEXE} * 305688 ))

SOURCE_FILE_NB=$( od -An -vtu4 -j$(( ${EXE_OFFSET} + 92 )) -N4 "$FIRST_SOURCE_FILE_NAME" )
DEST_FILE_SIZE=$( od -An -vtu8 -j$(( ${EXE_OFFSET} + 96 )) -N8 "$FIRST_SOURCE_FILE_NAME" )

#---------------------------------
# - On vérifie le dernier fichier
# => Dans le cas de MD5, celui-ci contient les MD5
# => Dans le cas non-MD5, cela sert à vérifier au moins la cohérence de taille des fichiers

v_echo "$MSG_CHECKING_LAST_SOURCE_FILE"

LAST_SOURCE_FILE_NAME=$( printf "${RADIX}%3.3u.xtm" $SOURCE_FILE_NB )
if [ ! -f "$LAST_SOURCE_FILE_NAME" ] || [ ! -s "$LAST_SOURCE_FILE_NAME" ]; then
  printf "$MSG_ERROR$MSG_LAST_FILE_ERROR" "$DISPLAY_RADIX" $SOURCE_FILE_NB
  tip $E_LAST_FILE_ERROR
else
  v_echo "$( printf "$MSG_LAST_FILE_FOUND" "$DISPLAY_RADIX" $SOURCE_FILE_NB )"
fi

LAST_SOURCE_FILE_SIZE=$(( $( stat -c%s "$LAST_SOURCE_FILE_NAME" ) - $fMD5 * $SOURCE_FILE_NB * 32 ))

if [ $(( $CHUNK_SIZE * ($SOURCE_FILE_NB - 1) + $LAST_SOURCE_FILE_SIZE )) -eq $DEST_FILE_SIZE ]; then
  v_echo "$MSG_FILE_SIZES_OK"
else
  echo -e "$MSG_ERROR$MSG_FILE_SIZES_ERROR"
  tip $E_FILE_SIZES_ERROR
fi


#==========================================================
# From here we have first and last file, we can do something!
# Checking output file
# - Vérifie qu'on ne va pas écraser un fichier (si option -f, juste message verbeux)
# - Vérifie qu'on peut bien écrire le fichier (autorisation, nom/chemin correct)
# - Vérifie qu'on a la place d'écrire le fichier
#
# Mais on commence par déterminer le nom du fichier résultat
# Ca dépend de plusieurs choses :
# - si un fichier destination a été spécifié en paramètre
#   + et si c'est le cas, si c'est un répertoire existant ou pas

if [ $OPTION_m ]; then
  v_echo "$MSG_NO_FILE_WRITTEN"
else

  v_echo "$MSG_COMPUTING_DEST"

  if [ -z "$DISPLAY_DEST_FILE_NAME" ] || [ -d "$DISPLAY_DEST_FILE_NAME" ]; then
    DEST_FILE_NAME_LENGTH=$( od -An -vtu1 -j$(( ${EXE_OFFSET} + 40 )) -N1 "$FIRST_SOURCE_FILE_NAME" )
    DEFAULT_DEST_FILE_NAME=$( dd if="$FIRST_SOURCE_FILE_NAME" bs=1 skip=$(( ${EXE_OFFSET} + 41 )) count=$DEST_FILE_NAME_LENGTH 2>/dev/null)

    if [ -z "$DISPLAY_DEST_FILE_NAME" ]; then
      this_file_radix=${DISPLAY_SOURCE_FILE_NAME%"${DISPLAY_SOURCE_FILE_NAME##*/}"}
    else                        # Ici DISPLAY_DEST_FILE_NAME est forcément un répertoire
                                #     on rajoute le / final si nécessaire
      if [ ${DISPLAY_DEST_FILE_NAME:(-1)} == '/' ]; then
        this_file_radix="$DISPLAY_DEST_FILE_NAME"
      else
        this_file_radix="$DISPLAY_DEST_FILE_NAME/"
      fi
    fi
    DISPLAY_DEST_FILE_NAME="$this_file_radix$DEFAULT_DEST_FILE_NAME"
  fi

  v_echo "$MSG_DISPLAY_DEST $DISPLAY_DEST_FILE_NAME"
  DEST_FILE_NAME=$( readlink -fn "$DISPLAY_DEST_FILE_NAME" )
  DEST_FILE_NAME_PART="${DEST_FILE_NAME}.part"

#---------------------------------
# Vérifications de la possibilité d'écrire le fichier résultat

  v_echo "$MSG_CHECK_DEST_WRITABLE"

  if [ -f "$DEST_FILE_NAME" ] && [ -s "$DEST_FILE_NAME" ]; then
    if [ $OPTION_f ]; then
      v_echo "$MSG_ATTENTION$MSG_WARN_FORCED_OVERWRITE"
    else
      size=$( stat -c%s "$DEST_FILE_NAME" )
      if [ $size -eq $DEST_FILE_SIZE ]; then
        printf "$MSG_ATTENTION$MSG_WARN_OVERWRITE" $DISPLAY_DEST_FILE_NAME
        echo "$MSG_FILE_SIZE_MATCHES"
      else
        printf "$MSG_ERROR$MSG_WARN_OVERWRITE" $DISPLAY_DEST_FILE_NAME
        echo "$MSG_FILE_SIZE_DOES_NOT_MATCH"
      fi
      echo -e "$MSG_OVERWRITE_HINT"
      exit $E_WARN_OVERWRITE
    fi
  fi

  touch "$DEST_FILE_NAME" "$DEST_FILE_NAME_PART" 2>/dev/null

  if [ $? -ne 0 ]; then
    printf "$MSG_ERROR$MSG_WRITE_ERROR" $DISPLAY_DEST_FILE_NAME
    exit $E_WRITE_ERROR
  fi

  TMP=$( df "$DEST_FILE_NAME" | sed -n '2p' )
  SPACE_AVAIL=$(( $( echo $TMP | cut -d' ' -f 4 ) * 1024 - 1024))
  size=$( stat -c%s "$DEST_FILE_NAME_PART" )
  DISK_SPACE_NEEDED=$(( $DEST_FILE_SIZE - $size ))
  if [ $DISK_SPACE_NEEDED == 0 ]; then
    success
  fi

  if [ $SPACE_AVAIL -lt $DISK_SPACE_NEEDED ]; then
    printf "$MSG_ERROR$MSG_INSUFFICIENT_SPACE" $( echo $TMP | cut -d' ' -f 6 ) "$DISPLAY_DEST_FILE_NAME" $DISK_SPACE_NEEDED $SPACE_AVAIL
    exit $E_INSUFFICIENT_SPACE
  fi

  CHUNKS_AVAIL=$(( $size / $CHUNK_SIZE ))

  if [ $(( $size % $CHUNK_SIZE )) -eq 0 ]; then
    v_echo "$CHUNKS_AVAIL $MSG_CHUNKS_AVAIL"
  else
    if [ $OPTION_f ]; then
      v_echo "$MSG_INFO_DELETED_OLD_FILE"
      rm "$DEST_FILE_NAME_PART" 2>/dev/null
      CHUNKS_AVAIL=0
    else
      printf "$MSG_ERROR$MSG_INCOHERENT_PARTIAL_FILE" $DISPLAY_DEST_FILE_NAME
      exit $E_INCOHERENT_PARTIAL_FILE
    fi
  fi

  v_echo "$MSG_ALL_CHECKED_OK"
fi


#==========================================================
#  The Real work starts here!
# - Toutes les vérifications et préparations étant finies on commence
#   la concaténation/checksum à proprement parler

if [ $OPTION_m ]; then
 DEST_FILE_NAME_PART="/dev/null"
fi
if [ $fMD5 -eq 0 ] || [ $OPTION_n ]; then
  MD5_PROG="nop"
else
  MD5_PROG="md5check"
fi
  
if [ ! $OPTION_v ]; then
  echo -e "$MSG_OK"
fi
timing 1

if [ $CHUNKS_AVAIL -eq 0 ]; then
  printf "$MSG_PROCESSING_START" $SOURCE_FILE_NB
else
  printf "$MSG_PROCESSING_RESTART" $(( $CHUNKS_AVAIL + 1 )) $(( $SOURCE_FILE_NB - $CHUNKS_AVAIL ))
fi
echo "$MSG_SEPARATOR"

i=$CHUNKS_AVAIL
while (( $i < $SOURCE_FILE_NB )) ; do
  if [ $fMD5 -eq 1 ]; then
    this_file_MD5=$( dd if="$LAST_SOURCE_FILE_NAME" bs=1 skip=$(( $LAST_SOURCE_FILE_SIZE + $i * 32)) count=32 2>/dev/null )
  fi
  i=$(( $i + 1 ))
  this_file_radix=$( printf "%s%3.3u" "$RADIX" $i )

  if [ ${i} -eq 1 ]; then
    printf "$MSG_PROCESSING_FILE" "$DISPLAY_RADIX" $i ${FIRST_SUFFIX}
  else
    printf "$MSG_PROCESSING_FILE" "$DISPLAY_RADIX" $i 'xtm'
  fi

  case $i in
    1 ) 
        if [ ${fEXE} -eq 0 ]; then
          # Pour le premier fichier on ne 'tee' pas les 104 premiers octets, mais ils rentrent dans le md5
          { dd if="$this_file_radix.xtm" bs=104 count=1 2>/dev/null &&
              # On fait un deuxième dd pour "aligner" à un bloc de 4096, et ensuite par 4096
              # C'est 7% plus rapide que de ne faire que des dd par 104.
              # Si le fichier est plus petit que 4096 (peu probable) ça fonctionne tout de même
              # car dans ce cas le deuxième s'arrête à la fin du fichier et le dernier dd
              # ne retourne rien.
              { dd if="$this_file_radix.xtm" bs=8 skip=13 count=499 2>/dev/null
                dd if="$this_file_radix.xtm" bs=4096 skip=1 2>/dev/null 
              } | tee "$DEST_FILE_NAME_PART";
          } | $MD5_PROG "${this_file_MD5}"
        else
          FIRST_FILE_BLOCKS=$(( ( ${CHUNK_SIZE} - 1432 ) / 4096 ))
          FIRST_FILE_PAD=$(( ${CHUNK_SIZE} - 1432 - ${FIRST_FILE_BLOCKS} * 4096 ))
          { dd if="$this_file_radix.exe" bs=8 skip=38208 count=13 2>/dev/null &&
              # Idem que ci-dessus pour les optimisations avec des PGCD recalculés
              # C'est juste un peu plus compliqué car on doit aussi sauter 24
              # octets à la fin, donc il faut savoir combien de blocs de 4096 on a.
              { dd if="$this_file_radix.exe" bs=8 skip=38221 count=179 2>/dev/null
                dd if="$this_file_radix.exe" bs=4096 skip=75 count=${FIRST_FILE_BLOCKS} 2>/dev/null 
                dd if="$this_file_radix.exe" bs=1 skip=$(( (${FIRST_FILE_BLOCKS} + 75) * 4096 )) count=${FIRST_FILE_PAD} 2>/dev/null 
              } | tee "$DEST_FILE_NAME_PART";
          } | $MD5_PROG "${this_file_MD5}"
        fi
      ;;

    $SOURCE_FILE_NB )
        # Pour le dernier fichier on enlève les octets de MD5 (ou pas... s'il n'y en a pas !)
        dd if="$this_file_radix.xtm" bs=$LAST_SOURCE_FILE_SIZE count=1 2>/dev/null | \
          tee -a "$DEST_FILE_NAME_PART" | \
            $MD5_PROG "${this_file_MD5}"
      ;;

    *)
        # On vérifie la cohérence de taille du fichier seulement si ce n'est ni le premier
        # ni le dernier, car pour c'est deux fichiers, c'est déjà vérifié avant !
       size=$( stat -c%s "$this_file_radix.xtm" 2>/dev/null ) 
       if [ $? -ne 0 ] || [ $size -ne $CHUNK_SIZE ]; then
         echo -e "$MSG_ATTENTION"
         echo "$MSG_FILE_MISSING"
         if [ $OPTION_m ]; then
           continue
         else
           echo "$MSG_FILE_MISSING_TIP"
           exit $E_WARN_FILE_MISSING
         fi
       fi

       tee -a "$DEST_FILE_NAME_PART" <"$this_file_radix.xtm" | $MD5_PROG "${this_file_MD5}"
  esac
  if [ $? -eq 0 ]; then         # No error: all is OK
    echo -e "$MSG_OK"
  else                          # Error: it means we get an incorrect MD5, a write error, or other bad things, so here we cancel and abort
    echo -e "$MSG_ERROR"
    if [ ! $OPTION_m ]; then    # But we stop only if no option -m.
                                # If option -m we continue so that we check ALL the files and not stop on the first error
      rm "$DEST_FILE_NAME_PART" "$DEST_FILE_NAME" 2>/dev/null
      exit $E_CRITICAL_ERROR
    fi
  fi
  timing 2
done 

#==========================================================
# Success!

success