Compresser ses sauvegardes smsbackuprestore (android) en c avec smscompress

Soyez le premier à donner votre avis sur cette source.

Vue 4 363 fois - Téléchargée 484 fois

Description

Bonjour à tous,
nombreuses sont les personnes qui utilisent un logiciel de sauvegarde de SMS avec android et l'un des plus populaire est SMSBackupRestore (source voir tout en bas).
Le souci de celui-ci c'est qu'il créé de multiples sauvegardes et qu'il ne propose pas des les compresser en une seule.
smscompress est un logiciel en cours de développement qui permettra de charger un ou plusieurs fichier de sauvegarde xml, de les ranger et de le faire un seul fichier.
Pour le moment il ne prend pas en compte les multiples erreurs pouvant venir des fichiers&utilisateurs, il est donc à utiliser avec prudence.
Hésitez pas à faire des commentaires ou corriger des choses qui vous choquent.

Source / Exemple :


/*************************

    • SMSCOMPRESS **
    • Version 1.0.0(b) **
    • By Thal-Lab **
    • Where is my mind ? **
                                                  • /
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "loadsms.h" /*
    • Ce programme est un complément à l'application SMSBACKUP.APK, logiciel pour Gphone sous android.
    • Principe :
    • Les sauvegardes se font sur des fichiers XML, qui se cumulent si elles sont faites régulièrement.
    • Entrée : un/des fichier(s) XML,
    • Sorti : Un fichier XML sans doublon rangé par date et d'ordre croissant,
    • Variable : Seront précisés au cours du code source,
    • Le programme se devra d'être le plus simple possible et faire le travail sans trop de complexité,
    • Il pourra accepter de nombreux arguments correspondant aux nombreux fichiers d'entrées.
    • Aucun commentaire sur mon anglais S.V.P. (lol)
  • /
int main(int argc, char *argv[]) { /*
    • Vérification des erreurs d'arguments
    • Le logiciel se fermera si les conditions d'utilisations ne sont pas respéctées, c'est-à-dire :
    • Deux arguments minimum dont :
    • -Le premier la destination de sauvergarde,
    • -Le second la source de sauvegarde
    • Il affichera éventuellement un mini manuelle.
  • /
if(argv[1]== NULL || argv[2] == NULL) { printf("Missing argument error, the program will close.\n"); printf("The application is used as follows:\n" "smscompress destination source1 source2 ... SourceN.\n" "example : smscompress /home/name/jhon-doe.xml /media/gphone/smsbackup/*\n"); return 0; } /*
    • Création de deux variables à allocation dynamique :
    • oldbackup : enregistrement de toutes les sauvegardes de tous les fichiers, sans en-tête,
    • Sans la balise smses.
    • newbackup : Sauvegarde prête à être utilisé et enregistré sur le fichier elle contient :
    • de nouvelles en-têtes et une balise smses contenant le nouveau nombre de sms
    • le tout rangé par ordre chronologique croissant.
    • Les deux variables pèsent au début SIZE_ALLOC octets chacune.
  • /
char *oldbackup = NULL; memmalloc(oldbackup, SIZE_ALLOC);//alloué if (oldbackup == NULL)//Si l'allocation a raté, fin. { printf("Memory allocation problem, the program needs to close.\n"); exit(0); } char *newbackup = NULL; memmalloc(newbackup, SIZE_ALLOC);//alloué if (newbackup == NULL)//Si l'allocation a raté, fin. { printf("Memory allocation problem, the program needs to close.\n"); exit(0); } char select = 0;//Cette variable à pour but de proposer à l'utilisateur des choix /*
    • Vérification de l'existence du fichier de destination
    • Si le fichier existe déjà, le programme demandera alors s'il peut l'écraser
    • Le cas contraire le programme se fermera pour laisser entrer de nouveaux arguments
  • /
FILE *destination = NULL; destination = fopen(argv[1], "rb"); if (destination != NULL) { while(select != 'o') { printf("Do you want to overwrite the file \"%s\"? ", argv[1]); scanf("%c", &select); if(select == 'n') { printf("You do not want to overwrite the file, the program will close.\n"); return 0; } else if(select == 'o') { destination = fopen(argv[1], "wb+"); if(destination == NULL) { printf("Error writing file, the program will close."); return 0; } else { printf("Writing the file was successful.\n"); } } else { select = 0; } } } else { destination = fopen(argv[1], "wb+"); if(destination == NULL) { printf("Error writing file, the program will close."); return 0; } } /*
    • Boucle de vérification d'existence des arguments
    • Ici le programme ne s'arrêtera pas si les sources sont mauvaises, il passera simplement à une autre
    • Si toutes les sources sont mauvaises, que la sauvegarde n'a pas lieu, le programme fera simplement une sauvegarde vide.
    • Dans la boucle i est initialisé à 2, car :
    • -Argument 0 : nom du programme,
    • -Argument 1 : nom de destination,
    • -Argument 2 : nom de la première source.
  • /
int i,//Variable de boucle for i, non initialisée j,//Variable de boucle non définie k,//Variable de boucle non définie gchar,//lecteur de caractère pour la fonction fgetc sizebackup = 0;//Evite des débordements mémoire /*
    • Ce buffer contient une balise sms entière, estimé par ce calcul à :
    • size_buffer = ( 160 * 50 ) + ( 160 * 10 ), large
  • /
char buffer[SIZE_BUFFER * sizeof(char)]; /*
    • Déclaration de la structure IntElementSMS
    • Elle servira à de multiple tache de manipulation
    • Déclaration de la structure d'allocation de mémoire
    • Initialisation de la variable memtest à 1 car 1 fois SIZE_ALLOC ont été alloué
  • /
ElementSMS sms; int memtest = 1; sms.countsms = 0; for(i = 2; i<= argc-1; i++) { /*
    • Prépraration du fichier source suivi de la vérification de son existence
    • En cas d'erreur le programme continue la boucle en attendant qu'il n'y ai plus d'argument(s)
    • Si aucune erreur se fait, lancement de la compression des fichiers.
  • /
FILE *source = NULL; printf("Opening of \"%s\", ",argv[i]);//Affichage à l'utilisateur de l'avancement source = fopen(argv[i], "rb+"); if(source == NULL || extension(argv[i]) != 1) { /*
    • Le programme donnera seulement l'argument erroné et la ligne lui correspondant
    • En fin de boucle le programme relancera soit un autre argument,
    • Soit il finira l'enregistrement.
  • /
printf("The file does not exist and/or extend is bad, argument \"%s\" number %d is not valide \n", argv[i], i); } else { printf("Current reading, ");//Affichage à l'utilisateur de l'avancement j = 0,//Boucle non défini, on met j à 0 k = 0,//Boucle non défini, on met k à 0 initElementSMS(&sms); initab(buffer, SIZE_BUFFER); /*
    • Cette boucle va permettre de supprimer les balise d'en-tête
    • Et ranger de manière compressé la sauvegarde
  • /
do { gchar = fgetc(source);//Lecture du caractère if(gchar == '<' || sms.openbalise == TRUE)//Si la balise s'ouvre ou si la balise est déjà ouverte { sms.openbalise =TRUE; buffer[j++] = gchar;//mise dans le buffer if(cmp_b(buffer,"<?xml",5) == 0 && sms.headxml == FALSE && sms.balisesmses == FALSE)//Détection de la balise <? ?> { /*
    • Suppression de cette balise qui peut-être handicapant pour la suite
    • Elle n'est plus utile le temps de la compression
  • /
while(gchar != '>') { gchar = fgetc(source); } sms.headxml = TRUE; j -= 5; } if(cmp_b(buffer,"<smses",6) == 0 && sms.balisesmses == FALSE && sms.headxml == TRUE) { /*
    • Suppression de la balise <smses>
    • Elle n'est plus utile le temps de la compression
  • /
while(gchar != '>') { gchar = fgetc(source); } sms.balisesmses = TRUE; j -= 6; } if(cmp_b(buffer,"<sms ",5) == 0 && sms.headxml == TRUE && sms.balisesmses == TRUE) { while(gchar != '>') { gchar = fgetc(source); buffer[j++] = gchar; } } if(cmp_b(buffer,"</smses>",8) == 0 && sms.balisesmses == TRUE && sms.headxml == TRUE) { /*
    • Suppression de la balise </smses>
    • Elle n'est plus utile le temps de la compression
  • /
j -= 8; } } if(gchar == '>' && sms.openbalise == TRUE) { /*
    • Le programme ferme le buffer par \0
    • Il augmente la taille de oldbackup
    • Il réallou autant que nécessaire la mémoire
  • /
buffer[j*sizeof(char*)]='\0';//ça parrait bête mais parfois ça sauve un bug ! sms.bufferint = readargdate(buffer); if(sms.bufferint != ERROR_F && sms.headxml == TRUE && sms.balisesmses == TRUE) { if((single(sms.dateorder, sms.countsms, sms.bufferint)) == TRUE) { sms.dateorder[sms.countsms++] = sms.bufferint; sizebackup += j; while(((memtest)*(SIZE_ALLOC))>=(sizebackup)) { memrealloc(oldbackup, memtest++);//Réalouer (SIZE_ALLOC * 4) Octet } sprintf(oldbackup, "%s%s",oldbackup, buffer); } } j = 0, sms.openbalise = FALSE; } }while(gchar != EOF); printf("0k.\n"); } close(source); } printf("Storing date in ascending order during...\n"); ascendingdate(sms.dateorder, sms.countsms); /*
    • Le tri des sms ne peut se faire en une fois, ça rendrait le programme trop lourd à écrire x)
    • La boucle rangera les sms gràce dateorder dans newbackup
    • Un allocation dynamique sera faite sur newbackup en début de programme
    • il ne sera plus question ensuite de la remodifier, realloc étant trop gourmand
    • La taille de realloc est de la taille de oldbackup + le nombre de sms x 3 c'est attire, deux espace et un \n
    • Et de MAX_BALISE une estimation de la taille des balises restantent
    • La fonction est un peu désorganisée mais elle fonctionne, c'est important !
  • /
printf("loading ...\n"); memnewrealloc(newbackup, ((strlen(oldbackup) + MAX_BALISE + (sms.countsms * 3)))); initElementSMS(&sms); sms.bufferint = 0, newbackup[0] = '\0'; printf("Text Analysis in progress ...\n"); for(k = 0; k <= sms.countsms; k++) { i = 0, j = 0; while(sms.dateorder[k] != sms.bufferint) { if(oldbackup[i] == '<' || sms.openbalise == TRUE) { sms.openbalise = TRUE; buffer[j++] = oldbackup[i]; } if(oldbackup[i] == '>' && sms.openbalise == TRUE) { sms.openbalise = FALSE; buffer[j] = '\0'; sms.bufferint = readargdate(buffer); if(sms.dateorder[k] == sms.bufferint) { sprintf(newbackup, "%s %s\n", newbackup, buffer);//on note bien la préparation au nouveau fichier XML } j = 0; } i++; } } free(oldbackup);//Libèration de la mémoire oldbackup = NULL;//Forcer /*
    • Fermeture du programme
    • Libération de la mémoire
  • /
printf("Writing the new xml file ...\n"); fprintf(destination,"%s%s%d%s%s%s", HEAD, BODY_SMSES, sms.countsms, BODY_SMSES_CLOSE, newbackup, BODY_END_SMSES);//on remarque l'ajout des balises close(destination); free(newbackup); newbackup = NULL; printf("Finish, you have analyzed %d SMS. The program will close, bye.\n", sms.countsms); return 0; } /***************************************************************************************************************************************/ //loadsms.c /***************************************************************************************************************************************/ #include <string.h> #include "loadsms.h" //Initialiser un tableau char à 0 sur n caractère. void initab(char tab[], int n) { int i; for(i = 0 ;i <= n; i++) { tab[i] = 0; } } /*
    • La fonction va changer un char en int
    • Cette fonction à pour but de lire un attribut "date" écrit dans le buffer
    • Elle revoit un int et avec la formule : n = (n * 10) + chartoint
    • On convertira un string en int facilement.
  • /
int chartoint(int charint) { if(charint=='0') return 0; if(charint=='1') return 1; if(charint=='2') return 2; if(charint=='3') return 3; if(charint=='4') return 4; if(charint=='5') return 5; if(charint=='6') return 6; if(charint=='7') return 7; if(charint=='8') return 8; if(charint=='9') return 9; return -1; } //Cette fonction range par ordre croissant les dates void ascendingdate(double tab[], int max) { int i,j; for(i = 0; i <= (max - 1); i++) { for(j = 0 + 1; j <= (max - 1); j++) if(tab[i] < tab[j]) { exchange(tab, i, j); } if(tab[i] > tab[j]) { exchange(tab, j, i); } } } /*
    • Cette fonction va vérifier si dans un table une valeur est unique
    • La fonction renverra TRUE si c'est vérifié ou FALSE si cela n'est pas
  • /
int single(double tab[], int max, double singlevaleur) { int i; for(i = 0; i <= max; i++) { if(singlevaleur == tab[i]) { return FALSE; } } return TRUE; } /*
    • Ce tableau permet de lire et analyser l'attribut "date" dans le buffer
    • La fonction peut prendre énormement de temps
    • Elle renvoie -1 si il y a une erreur ou la valeur de la date si ça se passe bien
  • /
double readargdate(char tab[]) { int i, j; double n = 0; for(i = 0; (strncmp((tab + i), "date=\"", 6)) != 0; i++) { if(tab[i] == '\0') { return ERROR_F; } } for(j = i + 6; tab[j] != '"'; j++) { if(chartoint(tab[j]) == ERROR_F) { return ERROR_F; } n = (n * 10) + chartoint(tab[j]); } return n; } //Echanger la valeur b/a et a/b dans le tableau void exchange(double tab[], int a, int b) { double n; n = tab[a]; tab[a] = tab[b]; tab[b] = n; } /*
    • Fonction pour initialiser la structure ElementSMS, pointeur utile ici.
    • openbalise : Ouverture(TRUE) de balise (<) et fermeture(FALSE) de balise (>).
    • openvar : Ouverture(TRUE) de valeur d'attrivut (<) et fermeture(FALSE) de valeur d'attribut (>).
    • headxml : Balise d'en-tête <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> croisé ou non.
  • balisesmses : Balise <smses/> croisé ou non.
  • /
void initElementSMS(ElementSMS *tmp) { tmp->openbalise = FALSE, tmp->openvar = FALSE, tmp->headxml = FALSE, tmp->balisesmses = FALSE; } //Fonction pour la lecture des extensions et éliminer les fichiers non XML, peut-on faire plus simple ? int extension(char *ext) { int i = strlen(ext); if(strcmp((ext + i - 4), ".xml") == 0 || strcmp((ext + i - 4), ".XML") == 0) { return TRUE; } return FALSE; } /*
    • Fonction pour la lecture des balises moins performante que strncmp mais évite un bug de la fonction
    • c'est moche de faire strncmp à sa "sauce".
  • /
int cmp_b(const char *s1, const char *s2, int sizechar) { int i; for(i = 0;((*s1++) == (*s2++)); ++i) { if(i == sizechar) { for(;((*s1--) == (*s2--));--i) { if(i <= 0) { return TRUE; } } } } return FALSE; } /***************************************************************************************************************************************/ //loadsms.h /***************************************************************************************************************************************/ #ifndef EXPORTSMS_H #define EXPORTSMS_H #include <stdlib.h> //Booléens très utile #define FALSE 0 #define TRUE 1 /*
    • Ces variables vont définir la taille du buffer
    • On prend 160 la taille maximum d'un SMS
    • On suppose que les messages de 50 sont le maximum qu'un utilisateur va envoyé
    • On suppose aussi que les balises et leur contenu seront de la taille de :
    • 10 SMS au maximum. Le tout est large.
  • /
#define SIZE_SMS 160 #define SEND_SMS 50 #define SIZE_BALISE (SIZE_SMS * 10) #define SIZE_BUFFER ((SIZE_SMS * SEND_SMS) + SIZE_BALISE) /*
    • Définition des mémoires utilisées
    • Diverses macros pour l'allocation et la réallocation
  • /
#define MAX_BALISE 1024 #define SIZE_ALLOC 16384 #define DATE_ORDER 200000 //ça represente quand même 200k SMS, c'est énorme #define memmalloc(var, n)((var) = malloc((n) * (sizeof(*var)))) #define memrealloc(var, n)((var) = realloc((var), ((n) * (SIZE_ALLOC)) * (sizeof(*var)))) #define memnewrealloc(var, n)((var) = realloc((var), ((n) * (sizeof(*var))))) /*
    • Définition de constante pour la création de la nouvelle sauvegarde
    • Pour le moment elles sont peu nombreuse
  • /
#define HEAD "<?xml version=\'1.0\' encoding=\'UTF-8\' standalone=\'yes' ?>\n" #define BODY_SMSES "<smses count=\"" #define BODY_END_SMSES "</smses>" #define BODY_SMSES_CLOSE "\">\n" //les erreurs possibles #define ERROR_F -1 /*
    • Structure pour la gestion des SMS :
    • openbalise, Variable permettant de savoir si une balise est ouverte ou non
    • openvar, Variable permettant de savoir si un attribut est cours de lecture ou non
    • headxml, Variable d'en-tête de balise, si elle a été vu ou non (<? ?>)
    • countsms, Variable pour compter le nombre de SMS traité et prêt à être enregistrée
    • balisesmses, Balise <smses/> lu ou non.
  • /
typedef struct ElementSMS{ int openbalise, openvar, headxml, countsms, balisesmses; double dateorder[DATE_ORDER], bufferint; }ElementSMS; //Header de fonction void initab(char tab[], int n); int chartoint(int charint); void ascendingdate(double tab[], int max); int single(double tab[], int max, double singlevaleur); double readargdate(char tab[]); void exchange(double tab[], int a, int b); void initElementSMS(ElementSMS *tmp); int extension(char *ext); int cmp_b(const char *s1, const char *s2, int sizechar); #endif

Conclusion :


J'ai vu beaucoup de genre chercher à savoir comment lire une balise XML en C, ici il y a une réponse.
Si des améliorations sont possibles (et il y en a !), vous gênez pas :).

Logiciel smsbackup&restore:
http://android.riteshsahu.com/apps/sms-backup-restore

Thal-Lab.

Codes Sources

Ajouter un commentaire Commentaires
Messages postés
239
Date d'inscription
vendredi 20 octobre 2006
Statut
Membre
Dernière intervention
20 avril 2009

Bonjour,

Je persiste a penser que quelque chose cloche dans ton usage de strncmp (et aussi que tu devrais utiliser atoi() plutot que chartoint() :o) ).

Pour le PS1, je crois que je n'ai vraiment pas compris a quoi te sert SEND_SMS.
Pour le PS2, je vois que nous sommes d'accord.
Pour le PS3, mefies-toi: Si tu as importe des SMSes depuis ton vieux telephone via la carte SIM, et que ton vieux telephone contenait des SMSes de plus de 160 caracteres, ils vont etre recus comme plusieurs SMSes avec exactement la meme date (ca m'est arrive. J'ai corrige ca en les exportant avec SMSBackuupRestore, editant le fichier XML et en le reimportant).
Donc si tu ignores des dates deja vues, tu va perdre des bouts de message.
Messages postés
2
Date d'inscription
vendredi 4 février 2011
Statut
Membre
Dernière intervention
14 mars 2011

Bonsoir Eric,

Pour ton PS1 :
Je ne vois pas ce que tu veux dire pour SMSes. Il indique le nombre de SMS sauvegardé dans le fichier XML, pour le programme ce n'est pas utile de s'en servir dans la mesure on attend EOF. A la fin du programme le nombre d'SMS traité, donnera le nombre d'SMS max contenu (SMSes) dans le nouveau fichier XML.

Pour ton PS2 :
Dans ce cas utiliser une hypothétique taille de SMS est problématique. Il faut une taille dynamique pour le buffer.

Pour ton PS3 :
C'est une bonne remarque, mais dans le cas du programme, ça ne change rien. Il ne fait que lire les dates et pour le reste il recopie entièrement (et bêtement) ce qui est comprit entre les balises sms. Le vrai problème pourrait être si les dates contenaient des espaces, lettres ou autres, dans ce cas la fonction chartoint retournerait -1 ce qui entraînerait bien sûr la non mémorisation du SMS.

De manière général il ne mémorisera pas le SMS si :
- Il n'a pas eu confirmation que le fichier est un XML(via l'extension),
- Il n'a pas croisé la balise smses,
- Il n'a pas croisé la balise sms,
- La date est invalide ou déjà vu dans dateorder.

Thal-Lab.
Messages postés
239
Date d'inscription
vendredi 20 octobre 2006
Statut
Membre
Dernière intervention
20 avril 2009

Hum, j'ai du mal a te suivre : strncmp ne modifie aucun de ses arguments (http://en.wikipedia.org/wiki/Strncmp).
Que la fonction soit appelee dans une boucle ou pas ne change rien a ceci...

Je pense que tu as du faire une autre erreur lorsque tu as teste.

Eric

PS: Pour le nombre de SMSes, tu peux le trouver dans l'entete XML du fichier de sauvegarde si tu ne veux pas de taille dynamiques.

PS2: Si par "Telephones HTC" tu veux dire "Telephones Android", je te confirme qu'ils n'ont pas cette limitation du tout. Il est possible qu'il y ai une limite de taille de quelques kilos (j'ai jamais ecrit de SMS de plus de 4 Ko) mais ce n'est pas garanti qu'elle existe toujours... Je te conseille donc dans ce cas de considerer que le SMS peut avoir n'importe quelle taille.

PS3: J'ai remarque dans les fichiers generes par SMSBackupRestore que des fois les numeros de tels etaient inconsistants (certains avec des espaces, d'autres sans). Est-ce que tu geres ca ?
Messages postés
2
Date d'inscription
vendredi 4 février 2011
Statut
Membre
Dernière intervention
14 mars 2011

Bonjour Eric et _Jonathan,

Pourquoi ne pas avoir utilisé de parseur XML me demandez-vous ? simplement pour proposer une autre solution, serte peut-être pas parfaite, mais un autre point de vue.

C'est vrai que certains mobiles peuvent envoyer des SMS plus grand que 160 caractères, mais dans mon cas, la problématique s'appliquait seulement aux téléphones HTC et je ne sais pas si eux ont cette limite. Le SEND_SMS est un nombre hypothétique de SMS maximum envoyé, pour palier à ça je pourrais aisément faire une allocation dynamique et supprimer toutes ces variables, c'est prévue prochainement ça évitera d'avoir des nombres hypothétiques.

Pour le cmp_b, je vais pas vraiment appeler ça un bug, ce mot n'était pas judicieux, je m'explique, quand tu places un strncmp dans une boucle il se produit quelque chose d'assez problématique. Exemple :
Si ton contenu est "abcdefghijkl" et que ton strncmp a tourné 5 fois dans la boucle, ta variable sera resté sur "fghijkl". Je n'ai pas vraiment trouvé de solution plus rapide que de réécrire strncmp et d'y inclure une partie de retour arrière, manque peut-être de temps et de recherche.

Merci pour vos commentaires.
Messages postés
239
Date d'inscription
vendredi 20 octobre 2006
Statut
Membre
Dernière intervention
20 avril 2009

Bonjour,

Je trouve ton code un peu long pour ce qu'il fait. En effet un parseur XML aurait pu rendre le tout plus lisible/compact, mais il y a quelques maladresses. Par exemple la fonction int chartoint(int charint). As-tu entendu parler de la fonction atoi() ?
Si vraiement tu veux prendre un char en entree, tu peux faire un truc bien plus court, genre :
int chartoint(int charint)
{
return (charint>='0' && charint<='9')?charint-'0':-1;
}

De plus les SMS ne sont pas forcement limites a 160 caracteres. Je veux dire, oui ils le sont, mais la plupart des telephones modernes sont capable si tu tapes un sms de 300 caracteres de l'envoyer en deux fois, et lors de la reception de ces deux SMSes de les recoller et le presenter a l'utilisateur comme un seul SMS (c'est le cas des telephones Android sur lesquels tournent l'excellent SMSBackupRestore dont tu parles).

J'ai du mal a comprendre ta limite a 50 SMS envoyes (SEND_SMS). Je ne vois pas exactement a quoi elle correspond, mais on peut rapidement avoir plus de 50 SMS dans un telephone (meme pour un seul contact).

Je n'ai pas tout lu en detail, mais encore un dernier probleme :
Ta fonction int cmp_b(const char *s1, const char *s2, int sizechar) me parrait bien compliquee pour un simple strncmp (et en plus, elle ne fonctionne pas si les deux chaines ont plus de sizechar caracteres identiques).
De plus tu parles de corriger un bug de la fonction strncmp... Je serais curieux de savoir quel bug tu as trouve dans une fonction codee pour la premiere fois il y a 40 ans et qui n'aurait pas ete corrige depuis.

Eric
Afficher les 6 commentaires

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.