menu
  Home  ==>  articles  ==>  colibri_helpers  ==>  u_handle_files_in_dirs   

u_handle_files_in_dirs - John COLIBRI.


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_levelIntegerp_dirp_file_nameString;
          p_pt_dataPointer);
        // -- the call-back
      begin
        display(f_spaces(2* p_level)+ p_file_name);
      end// display_file_names

    procedure TForm1.list_names_Click(SenderTObject);
      var l_pathl_extensionString;
      begin
        l_path:= dir_.Text;
        l_extension:= '.PAS';

        handle_all_files_recursive(1, l_pathl_extension,
            [e_dir_recursivee_dir_handle_file], display_file_namesNil);
      end// list_names_Click


2.2 - L'interface de l'unité

L'interface est la suivante:

    type t_pr_handle_fileprocedure(p_levelIntegerp_dirp_file_nameString;
            p_pt_dataPointer);

         t_dir_handling_type= (e_dir_recursive,
             e_dir_display_dire_dir_display_filee_dir_display_all_filee_dir_display_debug,
             e_dir_indent,
             e_dir_handle_dire_dir_handle_file);
         t_dir_handling_typesset of t_dir_handling_type;

    procedure handle_all_files_recursive(p_levelIntegerp_strict_pathp_extensionString;
        p_dir_handling_typest_dir_handling_types;
        p_pr_handle_filet_pr_handle_filep_pt_dataPointer);

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_sizepInteger;

    procedure add_file_size(p_levelIntegerp_dirp_file_nameString;
          p_pt_dataPointer);
        // -- the call-back
      var l_fileFile;
      begin
        Assign(l_filep_dirp_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(SenderTObject);
      var l_pathl_extensionString;
          l_total_sizeInteger;
      begin
        l_path:= dir_.Text;
        l_extension:= '.PAS';

        handle_all_files_recursive(1, l_pathl_extension,
            [e_dir_recursivee_dir_handle_file], add_file_size, @ l_total_size);

        display('Total size: 'IntToStr(l_total_size)+ ' bytes');    
      end;

Au point de vue traitement:

  • le cumul se fait dans la variable locale l_total_size
  • l'adresse de cette variable est passée lors de l'appel en utilisant @
  • la procédure de traitement calcule la taille de chaque fichier par FileSize, et incrémente le cumul des octets en surtypant le paramètre p_pt_data:

    Inc(t_pt_size(p_pt_data)^, FileSize(l_file));

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_countRECORD
                                       m_sizem_countINTEGER;
                                     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_sizeFileSize(l_file));
                END;
              
              ...
              VAR l_size_and_countt_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(SenderTObject);
      type t_pt_sizepInteger;

      procedure add_file_size(p_levelIntegerp_dirp_file_nameString;
            p_pt_dataPointer);
          // -- the call-back
        var l_fileFile;
        begin
          Assign(l_filep_dirp_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_pathl_extensionString;
          l_total_sizeInteger;

      begin // sizes_Click
        l_path:= dir_.Text;
        l_extension:= '.PAS';

        handle_all_files_recursive(1, l_pathl_extension,
            [e_dir_recursivee_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 le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) 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, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Créé: fev-02. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
      – u_types_constants
      – u_strings
      – u_loaded
      – u_c_basic_object
      – u_c_display
      – u_dir
      – u_file
      – u_display_hex
      – u_c_file_name
      – u_c_basic_file
      – u_c_log
      – u_c_line
      – handle_files
      – u_c_path_segments
      – u_c_text_file
      – u_c_direct_acccess
      – u_c_string_file
      – u_c_file_of
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog