Lecture et écriture de fichiers scl (supercopier list)

Description

Ce code permet de lire et d'écrire des fichiers SCL qui sont les fichiers utilisés avec le programme SuperCopier.

Source / Exemple :


package scl;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

/**

  • Permet de lire et de créer des fichiers .SCL (SuperCopier List) en version 1.
*
  • Voici les spécifications de l'encodage du fichier :
  • - Un fichier SCL est un fichier binaire.
  • - Les valeurs numériques int et int64 sont encodées en little-endian.
  • - Les chaînes de caractères sont encodés sous la forme : CHAR1 0x00 CHAR2 0x00 CHAR3 0x00 ...,
  • Il est donc primordial de connaître la longueur de la chaîne avant de la lire. Cette longueur précède donc à chaque fois une chaîne.
  • Cependant, cette longueur donne le nombre de caractère d'une chaîne. EX: test = longueur 4. Etant donné que chaque caractère utilise 2 octets (le caractère et 0x00), on doit doubler la longueur pour obtenir le nombre d'octets à lire.
  • - Le fichier SCL peut-être décomposé en trois parties logiques : l'en-tête, la partie des dossiers et la partie des fichiers.
*
  • La partie en-tête :
  • --------------------
  • Les 16 premiers octets sont la signature (magic paquet) permettant de confirmer que l'on traite bien un fichier SCL.
  • Les 4 octets suivants donnent le numéro de version du fichier SCL.
*
  • La partie dossiers :
  • --------------------
  • Chaque bloc d'informations donne 3 valeurs : un id, un dossier source et un dossier destination.
*
  • Cette partie commence par la version de la partie dossiers sur 4 octets. S'en suit directement le nombre de blocs présents, toujours sur 4 octets.
*
  • A la suite de cela sont directement les blocs. Chaque bloc est donc constitué de trois informations :
  • - l'id est codé sur les 4 premiers octets. Cette ID peut tout simplement être ignoré car, à la suite d'un bug dans le logiciel SuperCopier, la valeur sera toujours 0xff 0xff 0xff 0xff
  • - les 4 octets suivants donne la taille de la chaîne représentant le répertoire source
  • - les [longueur * 2] octets suivants représentent la chaîne de caractère du répretoire source
  • - les 4 octets suivants donne la taille de la chaîne représentant le répertoire de destination
  • - les [longueur * 2] octets suivants représentent la chaîne de caractère du répertoire de destination
*
  • Ces blocs d'informations sont importants les fichiers sont liés à ceux-ci. Chaque fichier possède un ID qui donne le bloc d'informations auquel il est lié.
  • Les blocs d'informations des dossiers sont donnés dans l'ordre inverse de leur ID. Par ex: le dernier bloc doit avoir l'ID 0, le bloc précédent l'ID 1, ... le dernier bloc l'ID [NB_BLOCS-1].
*
  • La partie fichiers :
  • ---------------------
  • Chaque bloc d'informations donne 3 valeurs : l'id du bloc d'informations de la partie "dossiers" auquel le fichier est lié, le nom du fichier source, le nom du fichier destination et la taille du fichier.
*
  • Cette partie commence par la version de la partie dossiers sur 4 octets. S'en suit directement le nombre de blocs présents, toujours sur 4 octets.
*
  • A la suite de cela sont directement les blocs. Chaque bloc est donc constitué de trois informations :
  • - l'id du bloc d'informations des dossiers est codé sur 4 octets.
  • - les 4 octets suivants donne la taille de la chaîne représentant le nom du fichier source
  • - les [longueur * 2] octets suivants représentent la chaîne de caractère de nom du fichier source
  • - les 4 octets suivants donne la taille de la chaîne représentant le nom du fichier de destination
  • - les [longueur * 2] octets suivants représentent la chaîne de caractère du nom du fichier de destination
  • - les 8 octets suivants donne la taille du fichier
  • /
public class SuperCopierList { /**
  • La signature d'en-ête d'un fichier SCL
  • /
private static final byte[] SCL_SIGNATURE = new byte[] { 0x53, 0x43, 0x32, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x20, 0x20, 0x20 }; /**
  • La version du fichier SCL
  • /
private static final int COPIER_DATA_VERSION = 1; /**
  • La version de la partie dossiers du fichier SCL
  • /
private static final int DIRLIST_DATA_VERSION = 1; /**
  • La version de la partie fichiers du fichier SCL
  • /
private static final int FILELIST_DATA_VERSION = 1; /**
  • Lit le contenu d'un fichier SCL et extrait une liste d'éléments.
  • Si le fichier est mal formaté, rien n'est p
*
  • @param fileName le chemin complet vers le fichier SCL à ouvrir
  • @return la lite des fichiers
  • @throws IllegalArgumentException s'il le fichier n'est pas un SCL, s'il s'agit d'une version non supporté ou que le fichier est corrompu
  • /
public static Vector<SuperCopierListItem> read(String fileName) { try { //on récupère les données du fichier byte[] data = Tools.readStream(fileName); //on contrôle la signature d'en-tête if(!checkSignature(data)) throw new IllegalArgumentException("Incorrect file's signature"); int offset = SCL_SIGNATURE.length; //on contrôle la version du fichier SCL et la version de la partie dossiers if(readInt(data, offset) != COPIER_DATA_VERSION || readInt(data, offset+4) != DIRLIST_DATA_VERSION) throw new IllegalArgumentException("Version not supported"); //on extrait le nombre de blocs d'informations des dossiers offset += 8; int countDirs = readInt(data, offset); offset += 4; //on parcourt les blocs d'informations des dossiers String[][] dirs = new String[countDirs][2]; for(int i=0; i < countDirs; i++) { //les valeurs sont lus par la fin (dernier element lu doit avoir l'index 0 et le premier countDirs-1) int index = countDirs-1-i; //l'id du dossier, doit être ignoré int idDir = readInt(data, offset); offset += 4; //la longueur de la chaîne du dossier source int lenghtDir1 = readInt(data, offset); offset += 4; //la chaîne du dossier source dirs[index][0] = readString(data, offset, lenghtDir1); offset += (lenghtDir1 * 2); //la longueur de la chaîne du dossier de destination int lenghtDir2 = readInt(data, offset); offset += 4; //la chaîne du dossier de destination dirs[index][1] = readString(data, offset, lenghtDir2); offset += (lenghtDir2 * 2); } //on contrôle la version de la partie fichiers if(readInt(data, offset) != FILELIST_DATA_VERSION) throw new IllegalArgumentException("Version not supported"); //on extrait le nombre de blocs d'informations des fichiers offset += 4; int countFiles = readInt(data, offset); offset += 4; Vector<SuperCopierListItem> items = new Vector<SuperCopierListItem>(); //on parcout les blocs d'informations des fichiers for(int i=0; i < countFiles; i++) { //l'id du bloc d'informations des dossiers à lier int idDir = readInt(data, offset); offset += 4; //la longueur de la chaîne du nom de fichier source int lenghtDir1 = readInt(data, offset); offset += 4; //la chaîne du nom de fichier source String fileSrc = readString(data, offset, lenghtDir1); offset += (lenghtDir1 * 2); //la longueur de la chaîne du nom de fichier de destination int lenghtDir2 = readInt(data, offset); offset += 4; //la chaîne du nom de fichier de destination String fileDst = readString(data, offset, lenghtDir2); offset += (lenghtDir2 * 2); //la longueur du fichier long fileSize = readInt64(data, offset); offset += 8; //on ajoute l'élément items.add(new SuperCopierListItem(dirs[idDir][0], dirs[idDir][1], fileSrc, fileDst, fileSize)); } return items; } catch(ArrayIndexOutOfBoundsException e) { //s'il on lit trop loin c'est que le fichier est corrompu ou incomplet throw new IllegalArgumentException("File's corrupted"); } } public static void write(Vector<SuperCopierListItem> items, String fileName) { //--- en-tête --- byte[] data = writeSignature(); data = writeInt(data, COPIER_DATA_VERSION); //--- la partie dossier --- Map<String, Object[]> dirs = new HashMap<String, Object[]>(); //on utilise un HashMap pour accélerer la recherche Object[][] dirsRef = new Object[items.size()][3]; //on utilise ce tableau pour référencer les blocs d'informations par rapport à l'ID donné au bloc int index = 0; for(SuperCopierListItem item : items) { String key = item.getDirSrc() + "|" + item.getDirDest(); if(!dirs.containsKey(key)) //si on ne trouve pas de correspondance, ce tuple d'informations de dossiers n'existe pas encore { Object[] blocDatas = new Object[] { item.getDirSrc(), item.getDirDest(), index}; dirsRef[index] = blocDatas; //on place la référence dans le tableau dirs.put(key, blocDatas); //on créé le bloc d'informations en lui associant un ID pour faire le lien avec les fichiers index++; } } //on écrit la version de la partie dossier data = writeInt(data, DIRLIST_DATA_VERSION); //on écrit le nombre de blocs d'informations data = writeInt(data, dirs.size()); //on écrit les blocs d'informations dans l'ordre inverse for(int i=index-1; i >= 0; i--) { //on écrit l'ID du bloc à 0xff 0xff 0xff 0xff (à la suite d'un bug dans SuperCopier) data = writeInt(data, -1); //on écrit la chaîne du dossier source (avec sa longueur juste avant) data = writeString(data, (String)dirsRef[i][0]); //on écrit la chaîne du dossier de destination (avec sa longueur juste avant) data = writeString(data, (String)dirsRef[i][1]); } //--- la partie fichiers --- //on écrit la version de la partie fichiers data = writeInt(data, FILELIST_DATA_VERSION); //on écrit le nombre de blocs d'informations data = writeInt(data, items.size()); for(SuperCopierListItem item : items) { //on récupère et on écrit l'ID du blocs d'informations des dossiers liés String key = item.getDirSrc() + "|" + item.getDirDest(); int idDir = (Integer)dirs.get(key)[2]; data = writeInt(data, idDir); //on écrit la chaîne du nom de fichier source (avec sa longueur juste avant) data = writeString(data, item.getFileNameSrc()); //on écrit la chaîne du nom de fichier de destination (avec sa longueur juste avant) data = writeString(data, item.getFileNameDest()); //on écrit la taille du fichier data = writeInt64(data, item.getFileSize()); } //on écrit le fichier SCL Tools.writeStream(fileName, data); } /**
  • Lit l'entier (int) présent dans les 4 prochains octets à partir du décalage courant.
*
  • La lecture se fait en little-endian.
*
  • @param data les données
  • @param offset le décalage
  • @return l'entier lu
  • /
private static int readInt(byte[] data, int offset) { return data[offset+3] << 24 | data[offset+2] << 16 | data[offset+1] << 8 | data[offset]; } /**
  • Lit l'entier 64 (int64) présent dans les 8 prochains octets à partir du décalage courant.
*
  • La lecture se fait en little-endian.
*
  • @param data les données
  • @param offset le décalage
  • @return l'entier lu
  • /
private static long readInt64(byte[] data, int offset) { return ((long)data[offset+7] & 0xff) << 56 | ((long)data[offset+6] & 0xff) << 48 | ((long)data[offset+5] & 0xff) << 40 | ((long)data[offset+4] & 0xff) << 32 | ((long)data[offset+3] & 0xff) << 24 | ((long)data[offset+2] & 0xff) << 16 | ((long)data[offset+1] & 0xff) << 8 | ((long)data[offset] & 0xff); } /**
  • Lit la chaîne de longueur spécifiée à partir du décalage courant.
*
  • Voir le description de la classe pour les détails d'encodage d'une chaîne.
*
  • @param data les données
  • @param offset le décalage
  • @param lenght la longueur de la chaîne (et non pas la longueur en octets)
  • @return la chaîne lue
  • /
private static String readString(byte[] data, int offset, int lenght) { String str = ""; lenght *= 2; //on passe un octet sur deux for(int i=0; i < lenght; i+=2) str += (char)data[offset + i]; return str; } /*
  • Permet de vérifier la signature du fichier SCL.
*
  • @param vrai si la signature a pu être vérifiée, faux sinon
  • /
private static boolean checkSignature(byte[] data) { if(data.length > SCL_SIGNATURE.length) //s'il n'y a déjà pas assez de données pour comparer, on abandonne directement { for(int i=0; i < SCL_SIGNATURE.length; i++) { if(data[i] != SCL_SIGNATURE[i]) return false; } } else return false; return true; } /**
  • Ecris l'entier (int) à la fin des données fournies (en allouant systématiquement la place nécessaire).
*
  • L'écriture se fait en little-endian.
*
  • @param data les données
  • @param value la valeur a ajouter
  • @return le résultat
  • /
private static byte[] writeInt(byte[] data, int value) { //on alloue l'espace supplémentaire nécessaire int oldSize = data.length; data = Arrays.copyOf(data, oldSize + 4); data[oldSize] = (byte)(value); data[oldSize+1] = (byte)(value >> 8); data[oldSize+2] = (byte)(value >> 16); data[oldSize+3] = (byte)(value >> 24); return data; } /**
  • Ecris l'entier 64 (int64) à la fin des données fournies (en allouant systématiquement la place nécessaire).
*
  • L'écriture se fait en little-endian.
*
  • @param data les données
  • @param value la valeur a ajouter
  • @return le résultat
  • /
private static byte[] writeInt64(byte[] data, long value) { //on alloue l'espace supplémentaire nécessaire int oldSize = data.length; data = Arrays.copyOf(data, oldSize + 8); data[oldSize] = (byte)(value); data[oldSize+1] = (byte)(value >> 8); data[oldSize+2] = (byte)(value >> 16); data[oldSize+3] = (byte)(value >> 24); data[oldSize+4] = (byte)(value >> 32); data[oldSize+5] = (byte)(value >> 40); data[oldSize+6] = (byte)(value >> 48); data[oldSize+7] = (byte)(value >> 56); return data; } /**
  • Ecrit la chaîne à la suite des données fournies (en allouant systématiquement la place nécessaire).
*
  • La taille de la chaîne est automatiquement ajoutée avant celle-ci.
  • Voir le description de la classe pour les détails d'encodage d'une chaîne.
*
  • @param data les données
  • @param str la chaîne à écrire
  • @return le résultat
  • /
static private byte[] writeString(byte[] data, String str) { //on écrit la taille de la chaîne qui va suivre data = writeInt(data, str.length()); //on alloue l'espace supplémentaire nécessaire int oldSize = data.length; data = Arrays.copyOf(data, oldSize + (str.length() * 2)); //on écrit deux octets à chaque fois for(int i=0; i < str.length(); i++) { data[oldSize+(i*2)] = (byte)str.charAt(i); data[oldSize+(i*2)+1] = 0x00; } return data; } /**
  • Créé un tableau de données contenant la signature d'un fichier SCL en son debut.
  • @return le tableau de données
  • /
private static byte[] writeSignature() { return Arrays.copyOf(SCL_SIGNATURE, SCL_SIGNATURE.length); } }

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.