|
u_handle_files_in_dirs - John COLIBRI.
|
- mots clé:utilitaire - récupération de noms de fichiers - parcours
arborescence DOS - FindFirst FindNext
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal et Delphi
- uses: u_strings, u_c_display, (u_c_basic_object,
u_c_log, u_loaded, u_types_constants, u_c_basic_file,
u_c_file_name, u_dir)
- plan:
1 - Introduction
De nombreux utilitaries destinés aux programmeurs ou aux utilisateurs
nécessitent le balayage d'un arborescence DOS afin de récupérer les fichiers
satisfaisant certains critères, tels que l'extension, la date etc.
Citons en vrac:
- pour les programmeurs
- les pretty printers
- le changement de commentaire (remplacer "(*" par "//" ou par "{")
- suppression des bannières (style ////// en travers de la page ou autre)
- les cross-reférences (liste des variables avec les numéros de ligne)
- la normalisation des fins de ligne (Retour chariot et / ou interligne)
- pour les fichiers ASCII
- les changements de jeu de caractère (Dos, Windows)
- le formattage (découpage des lignes)
- la correction orthographique
- pour les utilisateurs:
- les sauvegardes de tous ordres (le fichiers plus vieux que xxx, ceux ayant
certaines extensions...)
- affichage des statistiques d'utilisation disque (les propriétés de
Windows obligent à cliquer sur chaque répertoire, sans présenter de
synoptique globale de la consommation disque)
- suppression de fichiers sauvegardés de multiples fois
Le balayage de base se fait à l'aide des antiques primitives Dos FindFirst et
FindNext.
Ces primitives avaient été mises au goût du jour par les composants
tDirectoryListBox, tFileListBox et tFilterComboBox de Delphi 1. Borland a
placé ces composants dans la catégorie des "deprecated", alors que je ne
connaisse rien qui les remplace. J'avais écrit à l'époque un article sur les
"Outlook", mais je me souviens que ce fut une certaine galère. Les tTreeView
de Delphi 2 offrent une panoplie impressionnante de fonctionalités, mais il
faut les charger. Et finalement même avec les tDirectoryListBox, il faut
encore trouver un moyen d'analyser plusieurs répertoires (alors que le
composant n'affiche qu'un répertoire courant).
Nous sommes donc à la case départ, en employant FindFirst et FindNext.
Il y for longtemps, j'avais écrit quelques routines qui employaient ces
primitives, et les avais d'ailleurs publiées dans le livres 'Delphi dBase' pour
permettre l'effacement d'un répertoire (l'utilitaire effaçait récursivement les
fichiers des répertoires les plus profonds avant de supprimer les répertoires
contenant des fichiers).
Je coupais et collais vaillamment ces procédures d'utilitaire en utilitaire,
sans problème majeur. Je les ai même portées en Kylix pour permettre le
transfert de répertoires complets par FTP (je n'utilise pas Linux sur Windows,
Samba ou autre. Mon seul lien entre le PC Linux et le PC Windows est le réseau
local. D'où la nécessité d'utiliser FTP).
Comme les mécaniques de balayages de répertoire seront utilisées dans d'autres
articles (liste des ancres HTML etc), j'ai décidé de plublier ces routines.
2 - Utilisation
2.1 - Un exemple simple
Côté utilisateur, le programmeur:
- appelle la procédure handle_files_in_dirs en fournissant le chemin de base
et la procédure à appeler pour chaque fichier
- écrit la procédure de traitement qui est passée en paramètre à
handle_files_in_dirs
Voici l'exemple qui est utilisé dans le programme de test:
procedure display_file_names(p_level: Integer; p_dir, p_file_name: String;
p_pt_data: Pointer);
// -- the call-back
begin
display(f_spaces(2* p_level)+ p_file_name);
end; // display_file_names
procedure TForm1.list_names_Click(Sender: TObject);
var l_path, l_extension: String;
begin
l_path:= dir_.Text;
l_extension:= '.PAS';
handle_all_files_recursive(1, l_path, l_extension,
[e_dir_recursive, e_dir_handle_file], display_file_names, Nil);
end; // list_names_Click
|
2.2 - L'interface de l'unité
L'interface est la suivante:
type t_pr_handle_file= procedure(p_level: Integer; p_dir, p_file_name: String;
p_pt_data: Pointer);
t_dir_handling_type= (e_dir_recursive,
e_dir_display_dir, e_dir_display_file, e_dir_display_all_file, e_dir_display_debug,
e_dir_indent,
e_dir_handle_dir, e_dir_handle_file);
t_dir_handling_types= set of t_dir_handling_type;
procedure handle_all_files_recursive(p_level: Integer; p_strict_path, p_extension: String;
p_dir_handling_types: t_dir_handling_types;
p_pr_handle_file: t_pr_handle_file; p_pt_data: Pointer);
|
Pour les données:
- t_pr_handle_file est le type procédural de la fonction appelée pour chaque
fichier ou répertoire trouvé. Les paramètres sont:
- p_level: permet l'affichage indenté ou l'utilisation de la profondeur
dans l'arborescence
- p_dir: le chemin courant (terminé par \)
- p_file_name:
- si un fichier est trouvé, le nom de fichier
- si un répertoire est trouvé, le nom du dernier segment (si dans
"c:\a\b\" il y a un sous-répertoire "c" c'est ce segment qui est
fourni)
- p_pt_data: un pointeur pour échanger des données entre la procédure
appelante et la procédure de traitement
- t_dir_handling_type: spécifie les types de traitements actuellement prévus:
- e_dir_recursive: analyser les sous-répertoires
- e_dir_display_dir: afficher le répertoire lors du balayage
- e_dir_display_file: afficher le nom du fichier
- e_dir_display_all_file: afficher le nom des fichiers (même ceux ne
correspondant pas à l'extension retenue)
- e_dir_display_debug: affichage de mise au point
- e_dir_indent: les affichages précédents sont indentés
- e_dir_handle_dir: appeler la procédure de traitement pour chaque
répertoire trouvé
- e_dir_handle_file: appeler la procédure de traitement pour chaque
fichier ayant l'extension demandée
- t_dir_handling_types: l'ensemble des options de traitement
La procédure de traitement elle-même a les paramètres suivants:
- p_level: le niveau dans l'arborescence
- p_strict_path: le chemin de départ (à fournir sans le "\" final)
- p_extension: l'extension à analyser. Par exemple ".Html" ou ".pas". Le
point doit être fourni, la casse est sans importance (le test est effectué
sur les extensions majuscules)
- p_dir_handling_types: les options retenues. Essentiellement s'il faut
récurser ou non, et s'il faut traiter les fichiers et / ou les répertoires
- p_pt_data: le pointeur pour échanger des données
2.3 - Un exemple avec échange de données
Voici un exemple nous permettant de calculer la taille de tous les fichiers
*.PAS d'une arborescence:
type t_pt_size= pInteger;
procedure add_file_size(p_level: Integer; p_dir, p_file_name: String;
p_pt_data: Pointer);
// -- the call-back
var l_file: File;
begin
Assign(l_file, p_dir+ p_file_name);
Reset(l_file, 1);
Inc(t_pt_size(p_pt_data)^, FileSize(l_file));
Close(l_file);
end; // add_file_size
procedure TForm1.sizes_Click(Sender: TObject);
var l_path, l_extension: String;
l_total_size: Integer;
begin
l_path:= dir_.Text;
l_extension:= '.PAS';
handle_all_files_recursive(1, l_path, l_extension,
[e_dir_recursive, e_dir_handle_file], add_file_size, @ l_total_size);
display('Total size: '+ IntToStr(l_total_size)+ ' bytes');
end;
|
Au point de vue traitement:
Notons que:
- Nous avons défini t_pt_size comme étant un pointeur d'entier. Le surtypage
aurait naturellement été possible en utilisant directement pInteger, mais
si les données à échanger sont plus complexes, il faudra utiliser un
RECORD pour définir les données, et un pointeur vers ce RECORD pour
accéder aux données. Pour récupérer la taille et le nombre de fichiers, nous
pourrions utiliser:
TYPE t_size_and_count= RECORD
m_size, m_count: INTEGER;
END;
t_pt_size_and_count= ^t_size_and_count;
...
WITH t_pt_size_and_count(p_pt_data)^ DO
BEGIN
Inc(m_count);
Inc(m_size, FileSize(l_file));
END;
...
VAR l_size_and_count: t_size_and_count;
handle_files_in_dirs(.... , @ l_size_and_count);
WITH l_size_and_count DO
display(IntToStr(m_count)+ ' '+ IntToStr(m_size));
|
- pour réaliser ce cumul de taille nous aurions pu passer la taille de chaque
fichier qui est présente dans tSearchRec. Il aurait suffi d'ajouter ce
paramètre à la procédure de traitement, ou même d'exporter tout le
tSearchRec. Cette utilisation étant trop rare, cette solution n'a pas été
retenue
2.4 - Répertoires
L'unité est prévue pour être placée dans:
C:
programs
colibri_helpers
units
Vous pouvez naturellement changer cette organisation par Projet | Options |
Directories
2.5 - Directives de compilation
Les directives de compilation sont:
- R+ (vérification des intervalles)
- S+ (vérification de la pile)
- pas d'optimisation
3 - Programmation
3.1 - Programmation
L'essentiel du code s'appuie sur les fonctionalités de FindFirst et FindNext,
qui est des plus classiques.
Le plus délicat a été de définir les paramètres à envoyer à la procédure de
traitement. Pas assez de paramètres nécessitaient une réécriture de
handle_files_in_dirs, et trop de paramètres rendent la mémorisation difficile.
Notez d'ailleurs que le texte de u_handle_files_in_dirs contient au début, en
commentaire, un exemple d'appel pour faciliter le copier-coller.
Les paramètres choisis permettent:
- la gestion de la profondeur (pour limiter, par exemple à un certain nombre
de niveaux)
- la séparation du chemin et du nom de fichier (ce qui évite ExtractFilePath
ou autre dissécation)
- le passage de paramètres de tout type, au prix d'un surtypage. Une solution
utilisable en parallèle est de déclarer des globales initialisées par la
procédure d'appel et mises à jour dans la call-back. Plus sûr, mais moins
élégant.
4 - Améliorations
Cette version n'est pas objet. Il n'y a en effet pas de données à mémoriser.
Le principal problème, du point de vue style de programmation, est la présence
de la procédure call-back globale, qui nécessite l'utilisation de types ou
données globales pour échanger des informations entre la procédure appelant le
balayage et la procédure réalisant le travail sur chaque fichier.
De mon point de vue, il serait bien plus sympathique de pouvoir appeler:
procedure TForm1.sizes_Click(Sender: TObject);
type t_pt_size= pInteger;
procedure add_file_size(p_level: Integer; p_dir, p_file_name: String;
p_pt_data: Pointer);
// -- the call-back
var l_file: File;
begin
Assign(l_file, p_dir+ p_file_name);
Reset(l_file, 1);
Inc(t_pt_size(p_pt_data)^, FileSize(l_file));
Close(l_file);
end; // add_file_size
var l_path, l_extension: String;
l_total_size: Integer;
begin // sizes_Click
l_path:= dir_.Text;
l_extension:= '.PAS';
handle_all_files_recursive(1, l_path, l_extension,
[e_dir_recursive, e_dir_handle_file], add_file_size, @ l_total_size);
display('Total size: '+ IntToStr(l_total_size)+ ' bytes');
end; // sizes_Click
|
Nous avions publié toute une série d'articles dans Pascalissime permettant
l'utilisation d'itérateurs en Pascal. Ces itérateurs avaient été employés dans
les articles sur les graphismes avec facettes (le programme passant son temps à
parcourir les objets, eux-mêmes composés de facettes, composées de segments
etc.). Les itérateurs avaient donc été ajoutés aux conteneurs tels que les
listes et les arbres.
La technique n'était pas très compliquée: il fallait simplement se débrouiller
pour que le code compilé puisse accéder aux données locales. Quelques lignes
d'assembleur Inline avaient fait l'affaire (encore que ces quelques lignes,
simples conceptuellement, avait coûté quelques heures de mise au point).
Les itérateurs, avec les primitives du style FOREACH avaient d'ailleurs été
introduites par Borland par la suite, du temps de Turbo Vision je crois (TP6,
TP7 BP7). Ils ont été retirés avec l'arrivée de Delphi. Il est certain qu'en
cherchant un peu sur le Web, on devrait trouver des implémentations de ce type
d'itérateurs, mais j'avoue que je n'ai pas cherché. Si quelqu'un a l'info...
En fait si le problème est d'éviter l'utilisation d'une procédure call-back
externe, une autre solution est de construire la liste et de retourner la liste
complète. Une légère modification de handle_files_in_dirs permet de construire
une tStringList. Ou encore, nous pouvons encapsuler les chemins et fichiers
dans une classe complète, ce qui a été fait dans la classe c_files_in_dirs. Il
semble me souvenir que Charlie Calvert dans "Delphi Unleashed" avait utilisé
FindFirst et FindNext comme exemple pour créer un composant du type
tFileListBox. Une autre solution encore serait de construire un tTreeView:
nous ne serions plus très loin d'un explorateur Windows complet.
Mentionnons aussi que nous avons choisi volontairement de ne filtrer que sur
les extensions. En fait FindFirst et FindNext permettent de fournir un filtre
(par exemple 'gestion2002*.dat') et le type de fichier (faAnyFile dans notre
cas). Par rapport à mon utilisation du balayage des répertoires, cette option
ne s'est pas avérée nécessaire (la procédure de traitement peut toujours, elle,
filtrer).
Cette version de balayage de répertoires est récente, et a été écrite pour
pouvoir publier les articles suivants. Je n'ai pas ainsi vérifié que les
traitements de récursivité finale sont possibles (effacer les fichiers puis
effacer le répertoire les contenant, ou calculer la taille d'un répertoire).
5 - Télécharger
Vous pouvez télécharger:
Avec les mentions d'usage:
- j'apprécie tous les commentaires, remarques ou critiques
- signalez-moi les bugs que vous trouverez.
L'auteur
John COLIBRI est passionné par le développement
Delphi et les applications de Bases de Données. Il a écrit de nombreux livres
et articles, et partage son temps entre la
développement de projets pour
ses clients, le conseil et la formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des
stages
de formation Delphi, base de données, Ado.Net, Asp.Net et UML qu'il
anime personellement tous les mois, à Paris, en province ou sur site client.
|