I. Préambule

En se promenant sur la toile, vous verrez beaucoup de questions à ce propos, certains se plaignant même de dysfonctionnements. Autant le dire tout de suite, les actions de dossiers fonctionnent… à condition de connaître leurs règles et surtout leurs limites ! Il est possible d'en contourner certaines, mais pas toutes : autant le savoir avant de se lancer.

Bien qu'il soit aussi possible (et très facile !) d'utiliser Automator pour les actions de dossiers, cet article porte uniquement sur leur programmation en AppleScript.

Tous les exemples de cet article ont été testés sur un iMac27 (i7, Snow Leopard). De nombreux tests (mais pas tous !) ont été faits sur un iMac20 (Core duo, Snow Leopard) sur un Power Mac (G5 bipro, Tiger) et sur un iMac24 (i5, Mountain Lion). Je n'ai constaté aucune différence d'exécution (hormis la vitesse !), ce qui permet de penser que tout ce qui suit fonctionne sur tous les systèmes, au moins depuis Tiger.
Tous les codes sont libres d'utilisation.

Les actions de dossier permettent de déclencher automatiquement des scripts, en général après l'ajout de fichiers dans un dossier, mais aussi liés à d'autres événements sur le dossier.

Par exemple, glisser les fichiers images de formats différents dans un dossier peut les convertir automatiquement en format JPG de taille identique. Ou encore de changer leur nom avec un incrément ou avec la date de leur ajout dans le dossier.

Il est possible de limiter les types, faire des conversions vidéo, texte ou PDF, les envoyer vers un serveur distant, les imprimer… Bref tout ce que vous pouvez imaginer. Il existe des actions toutes faites, vous pouvez en créer avec Automator, mais aussi écrire vos propres scripts. Dans ce dernier cas, cet article est fait pour vous aider.

II. Création des actions de dossiers

II-A. Création et enregistrement

Avec l'AppleScript Editeur (dossier Utilitaires), il faut créer son script en l'entourant du « handler » adéquat qui dépend de l'action.

Exemple de script pour ajouts de fichiers :

 
Sélectionnez
on adding folder items to Mon_Dossier after receiving Liste_Fichiers
 
    -- Insérer votre script ci-dessous
    -- La variable Liste_Fichiers contient la liste des noms de fichiers ajoutés
    -- Mon_Dossier contient le nom du dossier dans lequel les fichiers sont ajoutés.
 
end adding folder items to

Enregistrez votre programme sous forme de script (extension « scpt ») dans le chemin : Bibliothèque/Scripts/Folder Action Scripts.

Si la bibliothèque est celle de l'utilisateur, l'action n'est disponible que pour cet utilisateur. Pour la rendre disponible à d'autres utilisateurs de la machine, il faut créer le même chemin à partir de la bibliothèque située à la racine du disque (avec les droits administrateurs).

Attention, ce chemin n'existe pas par défaut. Il faut éventuellement créer les dossiers « Scripts » et « Folder Action Scripts » soi-même. Le dossier bibliothèque n'étant plus visible directement dans Mountain Lion, il faut utiliser le menu du « Finder Aller… » en appuyant sur la touche Alt pour y avoir accès.

Pour faciliter l'installation d'un script, en particulier si vous le distribuez à des utilisateurs débutants, le script ci-dessous enregistre automatiquement votre action à la bonne place en prenant soin de créer les dossiers nécessaires. Il pourrait être complété par la création du lien avec un dossier (voir ajout d'action par un script - chapitre III.B).

Ajout d'un script dans la bibliothèque utilisateur
Sélectionnez
-- Installation d'un script pour Action de dossier dans la bibliothèque 
-- © PBell 2011 09 
 
set F_Script to "Scripts"
set F_Action to "Folder Action Scripts"
 
-- Sélection du script à installer
tell application "Finder"
    set Mon_Script to (choose file with prompt "Sélectionner le script à installer" without invisible) as alias
    if name extension of Mon_Script is not "scpt" then
        display dialog "Désolé, le fichier " & (name of Mon_Script) & ¬
            " n'est pas un script." & return & return & "L'installation a échoué" buttons {"Fin"}
        return
    end if
 
    set Ma_Bibli to path to library folder from user domain as alias
    -- Le dossier "Scripts" dans la Bibliothèque utilisateur existe ?  Sinon, on le crée
    if not (exists (folder F_Script of Ma_Bibli)) then
        make new folder at Ma_Bibli with properties {name:F_Script}
    end if
 
    -- Le dossier "Folder Action Scripts" existe ?  Sinon, on le crée dans le dossier Scripts
    set Doss_Script to ((Ma_Bibli as string) & F_Script & ":") as alias
    if not (exists folder F_Action of Doss_Script) then
        make new folder at (folder F_Script of Ma_Bibli) with properties {name:F_Action}
    end if
    delay 1 -- parfois le Finder prend un peu de temps !
 
    -- Copie du script à l'emplacement voulu
    try
        copy file Mon_Script to (folder F_Action of Doss_Script)
    on error
        display dialog "Erreur lors de la copie du fichier" buttons {"Abandonner"}
    end try
end tell

Les plus perfectionnistes peuvent modifier et enregistrer ce script comme une application et insérer, dans le paquet qui en résulte, le fichier du script à ajouter. Ce faisant, un simple double-clic sur l'icône de l'application installera tout sans rien demander à l'utilisateur !

II-B. Les types d'action de dossier

Il y a cinq types d'actions de dossiers :

  • « on Opening folder Mon_Dossier » : appelée après ouverture de la fenêtre du dossier ;
  • « on Closing forlder window for Mon_Dossier » : lorsque la fenêtre du dossier se ferme ;
  • « on moving folder window for Mon_Dossier from Ancienne_Position » : quand la fenêtre est déplacée ou redimensionnée;
  • « on removing folder items from Mon_Dossier after losing Liste_Fichiers » : lorsque des fichiers sont supprimés du dossier ;
  • « on adding folder items to Mon_Dossier after receiving Liste_Fichiers » : lorsque des fichiers sont ajoutés au dossier.

Mon_Dossier est l'alias du dossier concerné.

Ancienne_Position est de type « bounding rectangle » (les coordonnées du rectangle de la fenêtre).

Liste_Fichiers est de type liste et contient un ou plusieurs fichiers concernés avec leur chemin d'accès (attention, voir chapitre V concernant cette variable).

La dernière action, « adding », est, de loin, la plus souvent utilisée. C'est aussi celle qui peut poser le plus de problèmes. C'est la raison pour laquelle nous allons nous y intéresser en détail.

On constate qu'il n'y a malheureusement pas d'action déclenchée lors de la modification d'un fichier. Il existe d'autres méthodes pour cela, mais pas d'action de dossier !

III. Comment assigner les actions à des dossiers

III-A. Assignation manuelle

Une fois le script créé et enregistré en bonne place, le plus simple est de sélectionner le dossier dans le Finder, faire un clic droit sur celui-ci, et choisir « Configuration des actions de dossier » (menu ci-dessous à gauche). Sur la liste qui apparaît (ci-dessous à droite), sélectionnez votre action de dossier, puis validez avec le bouton « Joindre ».

Image non disponible Image non disponible

La liste des scripts de dossier intègre toutes les actions Automator et AppleScript provenant aussi bien de la bibliothèque utilisateur que de la bibliothèque racine du disque.


Voici l'écran d'assignation tel qu'il devrait être.
Ne pas oublier d'activer les actions de dossier dans la case en haut à gauche de cette fenêtre.
Cette validation n'est nécessaire que la première fois où vous utilisez les actions de dossier.

Il est possible d'assigner plusieurs actions à un même dossier : sélectionnez votre dossier dans la colonne de gauche, puis utilisez les boutons « + » et « - » en bas de la colonne de droite pour ajouter ou supprimer des actions de ce dossier. Compte tenu du mécanisme interne de déclenchement des actions, je déconseille aux débutants l'utilisation de plus d'une action par dossier.

Il est aussi possible d'assigner des actions à des dossiers via des scripts (voir assignation en masse).

Pour les curieux, les liens entre dossiers et actions sont dans le fichier utilisateur/bibliothèque/Preferences/com.apple.FolderActions.plist, éditable avec l'utilitaire Property List Editor fourni avec le kit de développement Xcode d'Apple ou bien un éditeur de texte quelconque en prenant soin de conserver les balises.

Normalement, vous n'avez pas à vous occuper de ce fichier. Cependant, ce fichier peut devenir assez gros et entraîner des lenteurs, car il n'est pas mis à jour automatiquement lors de la suppression d'un dossier.

Pour le maintenir propre, il faut s'astreindre, avant de supprimer un dossier lié à une action, à supprimer ce lien avec les boutons « - » côté script d'abord (colonne de droite), puis l'entrée sur le dossier lui-même (colonne de gauche), après avoir sélectionné les éléments dans leur liste respective (voir fenêtre ci-dessus).

III-B. Assignation en masse et propagation aux sous‑dossiers

L'action de dossier, Adding, n'est déclenchée que si un fichier est ajouté dans ce dossier. Elle n'est pas activée lors de l'ajout d'un fichier dans un sous‑dossier de ce dossier. Il est cependant possible de contourner cette limitation avec des scripts.

Imaginons que vous ayez un dossier parent contenant de nombreux sous-dossiers :

  • un script peut ajouter une action de dossier à tous les sous‑dossiers d'un dossier parent sélectionné ;
  • une action de dossier sur le dossier parent peut assigner automatiquement un script d'action à tout nouveau sous‑dossier ajouté.

Les scripts ci-dessous correspondent aux deux méthodes, la seconde étant plus souple. Ils sont suffisamment commentés pour que vous puissiez les adapter à vos besoins.

Ce script gère les erreurs en cas d'ajout (si l'action existe déjà pour certains des sous‑dossiers) et en cas de suppression (si l'action n'existe pas dans tous les sous‑dossiers). Il contient les instructions sur les actions (création, lecture et suppression) qui montrent les syntaxes à utiliser dans chaque cas.

 
Sélectionnez
-- Ce script : ajoute ou supprime la même action de dossier à tous les sous-dossiers d'un dossier parent 
-- © PBell 2010-05 & 2013-01
-- Demande à l'utilisateur s'il veut ajouter ou supprimer les actions des sous-dossiers du dossier parent
 
set Reponse to display dialog "Voulez-vous ajouter ou supprimer des actions de dossier sur tous les sous-dossiers d'un dossier parent ?" & return ¬
    & return & "(limité à un seul sous-niveau de dossier)" buttons {"Annuler", "Ajouter", "Supprimer"}
set Choix to button returned of Reponse
 
-- Récupération du chemin par défaut des Folder Action Scripts
tell application "System Events"
    set Script_Folder to (path to Folder Action scripts folder from user domain) as string
end tell
 
tell application "Finder"
    -- Sélection du dossier parent 
    set Dos_Parent to (choose folder with prompt "Sélectionner le dossier parent")
    -- Récupération des sous-dossiers du dossier parent
    set Sous_Dossiers to every folder of folder Dos_Parent
 
    -- Sélection du script à ajouter ou supprimer (dans le dossier folder action scripts!)
    set Mon_Script to (choose file with prompt ¬
        "Sélectionner le script à assigner/supprimer" default location Script_Folder as alias)
 
    if Choix = "Ajouter" then
        set Mon_Texte to "Êtes-vous certain de vouloir assigner le script à tous ces dossiers ?"
    else
        set Mon_Texte to "Êtes-vous certain de vouloir supprimer le script de tous ces dossiers ?"
    end if
    display dialog "Le dossier parent '" & (name of Dos_Parent) & "' contient " & ¬
        (count of Sous_Dossiers) & " sous-dossiers." & return & return & Mon_Texte
 
    -- boucle sur chaque sous-dossier du dossier parent 
    repeat with Un_Dossier in Sous_Dossiers
        tell application "System Events"
            if Choix = "Ajouter" then
                -- Ajout de l'action avec Try au cas  cette action existe déjà
                try
                    attach action to Un_Dossier as alias using (Mon_Script as string)
                end try
            else
                -- Suppression de l'action de dossier : on lit toutes les actions         
                set Mes_Actions to attached scripts (Un_Dossier as alias)
                -- On boucle juste pour montrer la syntaxe de lecture des actions
                repeat with Mon_Action in Mes_Actions
                    if name of Mon_Action = name of Mon_Script then
                        try
                            remove action from (Un_Dossier as alias) ¬
                                using action name (name of Mon_Action)
                        end try
                    end if
                end repeat -- fin boucle sur les actions
            end if
        end tell
    end repeat -- fin de boucle sur les sous dossiers
 
    display dialog "Action effectuée sur les sous-dossiers" buttons {"OK"} ¬
        default button 1 giving up after 5
end tell

Ce script gère un seul niveau de sous‑dossier. Pour gérer plusieurs niveaux (sous-sous-dossiers…) il doit être adapté et faire appel à la récursivité, ce qui sort du cadre de cet article.

Ce second script est une action de dossier à assigner au dossier parent, avant création de sous-dossiers. Ensuite, dès l'ajout d'un sous‑dossier, ce dernier sera automatiquement associé à une action de dossier prédéfinie (qui doit donc exister en bonne place).

 
Sélectionnez
-- Ce script est déclenché par une action de dossier "add item" sur le dossier parent
-- si l'action est un l'ajout d'un sous-dossier,
-- alors ce script assigne une action de dossier prédéfinie à ce nouveau sous-dossier
-- © PBell 2012-05 
 
on adding folder items to Mon_Dossier after receiving Liste_Fichiers
 
    -- Mettre ici le nom du script à ajouter à la création d'un sous-dossier
    -- Ce script doit exister dans votre Folder Action Script !!
    set Script_sous_Dossier to "Mon script de sous dossier.scpt"
 
    -- Récupération du chemin par défaut des Folder Action Scripts dans le domaine utilisateur
    tell application "System Events" to set Dossier_Scripts to ¬
        (path to Folder Action scripts folder from user domain) as string
    set Mon_Script to (Dossier_Scripts & Script_sous_Dossier)
 
    -- Vérification que le script prédéfini existe. Sinon, on quitte le script !
    tell application "Finder"
        if not (exists file Mon_Script) then return
 
        -- Boucle sur chaque item ajouté = un ou plusieurs sous-dossiers)
        repeat with Un_Item in Liste_Fichiers
            if (kind of Un_Item) is "dossier" then
                -- C'est un dossier, on ajoute l'action de dossier
                tell application "System Events" to attach action to Un_Item as alias ¬
                    using (Mon_Script as string)
            end if
        end repeat -- fin de boucle sur les items ajoutés
    end tell
end adding folder items to

IV. Règles de programmation des actions de dossier

Contrairement aux scripts habituels et aux processus Automator, les actions de dossier ne sont pas appelées par l'utilisateur, mais par le système lui-même.

Le mécanisme exact est hors de notre propos, mais ce qu'il faut retenir pour simplifier c'est que l'action de dossier se déroule en arrière-plan, et ce, même si vous voyez certains de ses effets. En conséquence, toute erreur rencontrée lors de l'exécution sera invisible, le script s'arrêtera sans aucun message visible (mis à part dans l'utilitaire Console). Vous ne saurez pas ce qui a eu lieu, ce qui n'a pas eu lieu, ni quand il s'est arrêté ! Il est donc particulièrement important d'écrire des scripts qui gèrent correctement toutes les erreurs possibles, faute de quoi, vous aurez des surprises (mauvaises, bien sûr !).

Les blocs « try/end try » et autres « if » vous aideront selon ce que vous voulez faire. Pour les scripts un peu complexes, je vous conseille fortement de les développer en mode script standard pour tracer toutes les erreurs possibles avec l'Éditeur AppleScript.

Pensez juste à ajouter, au début, une sélection d'un dossier et de fichiers de ce dossier pour simuler l'action de dossier comme indiqué ci-dessous :

 
Sélectionnez
-- Simulation de l'action de dossier d'ajout de fichiers sur un script standard
 
-- Sélection du dossier
set Mon_Dossier to ((choose folder with prompt "Choisissez le dossier test :" without invisibles) ¬
    as alias) as string
 
-- Sélection des fichiers ajoutés
set Liste_Fichiers to choose file with prompt "selectionnez fichiers ajoutés" default location ¬
    ((Mon_Dossier) as alias) with multiple selections allowed
 
-- Insérez votre script ici
-- Vous pouvez exécuter ce script dans l'Éditeur AppleScript pour le mettre au point

Une fois le script mis au point, vous supprimez les instructions de sélection du dossier et des fichiers et vous encadrez votre script par les « handler » adéquats comme ci-dessous :

 
Sélectionnez
on adding folder items to Mon_Dossier after receiving Liste_Fichiers
 
    -- votre script ici, tel qu'il a été mis au point (sans la sélection du dossier et des fichiers bien sûr !)
 
end adding folder items to

Enfin, une petite précision : si votre script doit se déclencher à l'ajout d'un élément dans le dossier, et qu'il prévoit l'ajout d'un fichier de trace ou d'un sous-dossier dans ce même dossier, pensez à traiter la récursivité que cela va engendrer. En effet, votre script, en ajoutant un élément, va lui-même déclencher une action de dossier… donc il va s'appeler !

Votre script doit donc prévoir de ne rien faire lorsqu'un élément qu'il génère est ajouté au dossier. À titre d'exemple, le script ci-dessous prévoit d'ajouter un sous-dossier (s'il n'existe pas déjà) dans lequel sont enregistrés les fichiers dont l'extension n'est pas retenue.

 
Sélectionnez
(*  Script d'action de dossier : 
 
Lors de l'ajout de fichiers dans le dossier, on vérifie leur type 
Si le type est incorrect, le fichier sera copié dans un sous-dossier de rejet
 
Si le dossier de rejet n'existe pas déjà, on le crée dans le dossier.
*)
 
on adding folder items to Mon_Dossier after receiving Liste_Fichiers
 
    --Nom du sous dossier qui recevra les fichiers dont l'extension est incorrecte
    set Dos_Rejet to "Mes_rejets"
    -- Liste des extensions de fichier acceptées
    set liste_Ext to {"jpg", "txt"}
 
    tell application "Finder"
        -- Pour éviter que l'ajout du dossier Dos_Rejet ne crée aussi un événement,
        -- On teste ce qui est ajouté et, éventuellement, on ne fait rien !
        if name of first item of Liste_Fichiers is Dos_Rejet then return
 
        -- Vérifie si le sous dossier existe déjà, sinon, le crée
        if not (exists folder Dos_Rejet of Mon_Dossier) then
            make new folder at Mon_Dossier with properties {name:Dos_Rejet}
            -- Attention, cet ajout génère un event add folder items !! -> d'où le premier test !
        end if
 
        -- On crée une liste de fichiers à rejeter vers le sous-dossier de rejet
        set Liste_Rejet to {}
        repeat with Mon_Item in Liste_Fichiers
            if name extension of Mon_Item is not in liste_Ext then
                set end of Liste_Rejet to Mon_Item
            end if
        end repeat
 
        -- On transfère les fichiers incorrects dans le dossier de rejet
        move Liste_Rejet to folder Dos_Rejet of Mon_Dossier as alias
 
        display dialog "Opération terminée"
    end tell
end adding folder items to

V. Fonctionnement spécifique de l'action « on adding folder items » et ses limites

L'instruction est : on adding folder items to Mon_Dossier after receiving Liste_Fichiers.

Le script doit se terminer par : end adding folder items to.

V-A. Que contient réellement le paramètre Liste_Fichiers ?

Compte tenu des termes utilisés, notamment le mot « after », on peut s'attendre à ce que l'ajout de n fichiers simultanément dans un dossier déclenche le script avec Liste_Fichiers contenant les n fichiers ajoutés, donc après l'ajout complet.

C'est parfois le cas, mais pas toujours ! Tout dépend du temps… mis par le système pour faire cet ajout.

En pratique tout ce passe avec deux processus parallèles, non synchronisés :

  1. copie ou transfert des fichiers sélectionnés dans le dossier ;
  2. temporisation interne, puis lancement du script d'action de dossier.

Lorsque le processus 1 est fini avant le 2, Liste_Fichiers recevra TOUS les fichiers ajoutés.

Si le processus 1 n'est que partiellement fini, le processus 2 ne recevra qu'une partie des fichiers ajoutés…

Et les autres ? Eh bien ils feront l'objet d'un nouvel appel au script d'action de dossier ! Plus tard, après… au bout d'un certain temps pour paraphraser Fernand Raynaud !

Exemple avec ajout de quatre fichiers qui vont déclencher trois actions :

Image non disponible

Chaque action déclenchée aura des paramètres différents : la première avec le fichier 1, la seconde contiendra les fichiers 2 et 3, car au début de cette action, le fichier 2 est ajouté et le 3 a commencé sa copie. Enfin la dernière action avec seulement le fichier 4.

On s'attendait pourtant à seulement une action avec les quatre fichiers !

Le problème se complique pour trois raisons :

  • la vitesse du processus 1 dépend des supports physiques, de la taille des fichiers et du type d'ajout (copie ou transfert) ;
  • la vitesse du processus 2 et sa temporisation dépendent de la machine et de son OS. Les variations sont cependant faibles ;
  • le processus 2 ne se déclenche pas « after » la copie, mais peu de temps après le début de cette copie (aïe, aïe ! voir paragraphe V.B).

Il est facile de comprendre que la vitesse de l'ajout dépend des caractéristiques des disques/volumes d'origine et de destination (celui du dossier). Il faut se souvenir qu'elle va aussi dépendre de la méthode : copie ou transfert, via le Finder, une application, une commande shell Unix ou une autre machine en réseau.

Rappel :

La copie garde le fichier à la source et l'ajoute à la destination. Elle s'exécute soit avec la commande cp en Unix, soit en glissant les fichiers d'un volume à l'autre sur le Finder, soit en glissant les fichiers entre deux emplacements d'un même volume avec la touche Alt.

Le transfert ne peut se faire qu'entre deux dossiers d'un même volume, soit avec la commande mv en Unix, soit avec un simple glisser sur le Finder. En fait, cette commande ne fait qu'écrire un nouveau chemin pour le fichier, sans réécrire ce dernier.

Lors d'un transfert, il y a de fortes chances que la vitesse soit telle que l'action de dossier reçoive la liste de tous les fichiers car le transfert est très rapide ! Lors d'une copie, la réponse dépendra de la taille des fichiers et des vitesses des matériels.

Pour s'en convaincre, rien de tel qu'un exemple pratique : prenez un dossier D1 avec quatre fichiers : deux petits de 4Ko, deux gros de 2Go chacun au moins (des vidéos par exemple).

Prenez un dossier D2, sur le même volume/disque auquel vous assignez l'action de dossier contenant le script ci-dessous qui se contente d'afficher, pendant une seconde après le lancement de l'action, le nombre de fichiers reçus par l'action et le nom du premier :

 
Sélectionnez
on adding folder items to Mon_Dossier after receiving Liste_Fichiers
 
    tell application "Finder"
        display dialog "Nb=" & (count of Liste_Fichiers) & return & ¬
            (name of first item of Liste_Fichiers) giving up after 1
    end tell
 
end adding folder items to

Réalisez ensuite la copie (glissez les quatre fichiers de D1 vers D2 tout en maintenant la touche Alt).

La barre habituelle de progression du Finder apparaît indiquant la copie en cours de quatre fichiers. Soudain, avant la fin de la copie, une fenêtre indiquant « Nb=x » et le nom d'un des fichiers. Cette fenêtre disparaît au bout d'une seconde. Puis sans doute quelques secondes plus tard, de nouveau une fenêtre Nb=y, et ainsi de suite… Vous pouvez avoir deux ou trois fois la fenêtre.

Note : Les sceptiques (la confiance n'exclut pas le contrôle…) peuvent remplacer le « Display dialog » par une commande shell « touch » pour écrire dans un fichier de trace qui n'influe pas sur la vitesse du Finder.

Selon la vitesse et l'ordre de sélection de fichiers (petits d'abord, au milieu ou en dernier), vous aurez sans doute deux ou trois appels du script avec un nombre total de Nb égal à 4 (bien sûr, car il y a quatre fichiers en tout et pas un de plus).

Les quatre séquences suivantes sont possibles avec les nombres pour chaque dialogue :1-2-1, 1-1-2, 2-1-1, ou 2-2. Si vous avez 1-1-1-1, votre copie est particulièrement lente !

Supprimez les fichiers du dossier D2 et recommencez avec un transfert (D1 et D2 sur le même volume, glissez simplement les fichiers de D1 à D2). Il est fort probable que vous n'aurez qu'une seule action avec Nb=4 ! Tout simplement parce que dans ce cas, le transfert est plus rapide que le délai de latence de l'action de dossier.

J'ai cherché à tester la limite de la temporisation interne.

Un transfert de 1000 fichiers de 4 Ko chacun ne génère qu'une seule action de dossier avec une liste de 1000 fichiers. Sur une machine rapide (iMac27 quad core i7), la copie de ces fichiers donne le même résultat. Par contre, dès que la liste des fichiers contient un seul fichier de 1 Go au milieu, la copie génèrera au moins deux actions de dossier.

Donc, règle N° 1 : ne jamais supposer que tous les fichiers ajoutés vous arriveront en même temps dans le script.

Lors du test précédent, les lecteurs les plus observateurs auront sans doute remarqué que la fenêtre du script s'affiche AVANT la copie complète des fichiers en question.

Ce n'est pas important pour de nombreux scripts, mais si vous devez modifier les fichiers ajoutés (voire les supprimer s'ils ne correspondent pas à vos critères) et que, en plus les fichiers sont gros (1, 2 ou 10 Go…), alors là, oui, nous avons un problème, car votre script commencera à travailler non pas « after », mais « pendant ».

Il se peut que vous ayez besoin d'être certain que le fichier est bel et bien ajouté dans le dossier. Dans ce cas, il faut que votre script sache détecter quand la copie est effectivement terminée, pour ensuite se dérouler comme vous l'avez prévu. C'est ce que nous allons voir dans le prochain paragraphe.

V-B. Comment forcer le script à bien attendre le « after » de l'instruction ?

Sur la toile, vous trouverez cette question récurrente (détection de fin de copie), pas seulement pour des actions de dossier, parfois avec des réponses fausses ou approximatives. J'ai testé un certain nombre de méthodes que je vous liste ci-dessous : celles qui ne fonctionnent pas et celles qui fonctionnent.

Ce n'est certainement pas exhaustif, alors n'hésitez pas à partager vos propres méthodes.

V-B-1. Les méthodes qui ne fonctionnent pas (pour vous éviter des heures perdues)

D'après la documentation AppleScript, il devrait y avoir plusieurs possibilités pour déterminer si un fichier est en cours de copie ou pas, s'il existe, s'il est en écriture ou bloqué. Malheureusement beaucoup sont inopérantes, dans le cadre d'une action de dossier :

utiliser la propriété « exist » du fichier : non, car elle est « true » dès que l'entrée est inscrite dans le répertoire… Bien avant la fin de copie ! ;

utiliser la propriété « Busy Status » : non, car, bien que Apple demande aux développeurs de la gérer, le Finder ne la gère pas ! (faites comme je dis, pas comme je fais) ;

utiliser la propriété "File Type of Info for" : curieusement, sa valeur est "brok" (testé sur Tiger et Snow Leopard, Moutain Lion à vérifier) si le fichier est en cours de copie via le Finder. Mais elle est vide lors d'un ajout par une application ou la copie par commande Shell Unix. On ne peut donc s'y fier ;

utiliser la commande « open access with write permission » ne donne pas d'erreur pendant la copie, comme si le fichier n'avait pas de contrainte d'accès. Mais on a parfois une erreur lors de l'écriture d'un bloc. Ouf ! Ça c'est normal, mais on ne veut pas forcément écrire sur le fichier ;

attendre que la fenêtre Finder de progression de copie ne soit plus affichée : imparfait car le script peut être face à plusieurs copies simultanées dans le Finder sans savoir quelle fenêtre de copie doit disparaître.

V-B-2. Les méthodes qui fonctionnent

Je n'ai trouvé qu'une méthode sur la toile dont le concept fonctionne vraiment (auteur anonyme, merci à lui). Je l'ai simplement remise en forme ici.

Avant de la trouver, j'en avais créé une qui, j'avoue, n'est pas très élégante, mais j'adore la commande Unix ls ! (elle est souvent beaucoup plus rapide qu'AppleScript et puis cela fait un peu travailler les neurones).

Méthode basée sur la taille du fichier : si la taille du fichier ne change plus, c'est que tout est copié ! C'est la plus simple.

 
Sélectionnez
set Mon_Fichier to "dossier:fichier" -- mettre ici le chemin et nom de votre fichier
 
tell application "Finder"
    try
        set ASize to -1
        repeat until (size of Mon_Fichier) = ASize
            set ASize to (size of Mon_Fichier)
            delay 0.3
        end repeat
    end try
end tell
 
display dialog "Le fichier est totalement copié !"

Méthode basée sur les résultats de la commande shell/Unix ls -l. Cette commande affiche, en début de ligne, les autorisations Unix du fichier :

  • avant copie -> erreur car pas de fichier !
  • pendant copie Finder -> -rwxr-xr-x@ (@ en position 11 et débute par -rwx)
  • pendant copie Unix -> -rwx------
  • après copie : -rwx, mais plus de @ ni de suite "------". La valeur exacte dépend des autorisations sur le fichier (user, group…).
  • Note : j'ai cherché sur le Net, sans succès, la signification du « @ » à la fin des autorisations.
 
Sélectionnez
set Mon_Fichier to "dossier:fichier" -- mettre ici le chemin et nom de votre fichier
 
set F_Unix to quoted form of POSIX path of Mon_Fichier
set LS_Debut to ""
set PosArobase to "@"
set LS_Fin to "------"
set T to "xxxxxxxxxxxxx"
repeat until (LS_Debut = "-rwx") and not (PosArobase = "@") and not (LS_Fin = "------")
    try
        set T to do shell script "ls -l " & F_Unix
    on error
        set T to "xxxxxxxxxxxxx"
    end try
    set LS_Debut to text 1 thru 4 of T
    set LS_Fin to text 5 thru 10 of T
    set PosArobase to character 11 of T
end repeat
 
display dialog "Le fichier est totalement copié !"

Pour résumer, si vous manipulez de petits fichiers et que vous changez les noms, pas de souci pour votre action de dossier.

Si vous manipulez de gros fichiers et que vous voulez les traiter une fois entièrement copiés (et seulement à ce moment), vous devez insérer l'une de ces méthodes dans votre script.

La méthode la plus « propre », c'est-à-dire lisible, consiste à faire une boucle sur les fichiers de Liste_Fichiers et, dans cette boucle, d'insérer l'un de ces scripts. Votre script continuera ensuite dans la boucle, seulement lorsque la copie du fichier en question sera terminée.

Ainsi chaque fichier sera traité par le script après sa copie, pas pendant.

On peut aussi n'utiliser seulement qu'une fois l'un de ces scripts, avant la boucle de traitement sur Liste_Fichiers. Il suffit de vérifier si le dernier élément de cette liste (instruction last item of Liste_Fichiers) a terminé sa copie. Ainsi on est certain que tous les fichiers sont copiés avant de démarrer l'action de dossier.

En revanche, on attend effectivement la dernière copie pour débuter le script. Bien qu'il fonctionne, le risque est qu'une future version du Finder fasse du parallélisme (« multithreading ») à l'intérieur d'une seule action de copie. Alors vous ne pourrez plus savoir si le dernier fichier de la liste est bien celui qui sera traité en dernier.

Les versions actuelles du Finder font du parallélisme entre les tâches de copies, mais pas à l'intérieur d'une tâche de copie multiple. Pour combien de temps encore ?

À vous de voir en fonction de ce que fera le reste de votre script ! En tout cas, vous voilà prévenu.

V-C. Exemple pratique sur le dossier de téléchargement

Afin de mettre en pratique le problème de synchronisation des tâches, voici ci-dessous un exemple basé sur l'ajout, via votre navigateur favori, d'un fichier dans le dossier téléchargement. Il est à peu près certain dans ce cas que le script d'action de dossier débutera avant que le fichier soit entièrement téléchargé. Il est tout aussi probable que l'action ne comportera qu'un fichier ajouté.

L'action elle-même copiera le fichier téléchargé dans un sous-dossier en fonction du type de fichier. Si ce sous-dossier n'existe pas, l'action le crée (gestion des erreurs) et le script passe outre ce nouvel ajout.

Il y a aussi une astuce afin de contourner un comportement particulier du navigateur Safari. En effet, celui-ci, lors d'un téléchargement, crée parfois un fichier temporaire qu'il va renommer ensuite, une fois le chargement effectué.

Par exemple, il va déclencher l'action de dossier avec l'ajout d'un fichier Fichier.Ext.Download, dont l'extension est « download » puis modifier le nom en Fichier.Ext dont l'extension deviendra « Ext ». Curieusement (et heureusement !), la référence alias du fichier reste la même ce qui permet sa copie.

Cependant, il faut bien tenir compte de cela pour assigner ce fichier au dossier adéquat (celui qui doit contenir vos fichiers avec l'extension « Ext »).

Le petit script ci-dessous contourne le problème en copiant immédiatement le nom en mémoire (avant fin de téléchargement) pour en extraire l'extension qui servira à l'assignation du sous-dossier voulu. Il a été testé avec Safari et plusieurs sites et types de fichiers. Il est suffisamment commenté pour que vous puissiez l'adapter à votre cas particulier.

Bien sûr, ce script est à assigner comme action du dossier téléchargement (celui que vous avez sélectionné dans les préférences de votre navigateur).

 
Sélectionnez
-- Action de dossier sur le dossier Téléchargement :
-- Transfère les fichiers vers des sous-dossiers par type
-- une fois leur téléchargement terminé
 
-- Les sous-dossiers regroupent les fichiers par groupes d'extensions
-- Pour chaque groupe sont définis les types et le nom du sous-dossier
property FImage : {"JPG", "jpg", "png", "TIFF"}
property DImage : "Mes_Images"
 
property FTexte : {"txt", "doc", "docx", "odt"}
property DTexte : "Mes_Textes"
 
property FVideo : {"dv", "DV", "mov", "MOV", "VOB", "vob", "divx", "mp4", "MP4"}
property DVideo : "Mes_Videos"
 
property FAudio : {"m4a", "M4A", "aif", "mp3", "aiff"}
property DAudio : "Mes_Musiques"
 
-- Pour toutes les autres extensions (impossible de lister tous les types !)
property DAutres : "Autres formats"
 
on adding folder items to Mon_Dossier after receiving Liste_Fichiers
    tell application "Finder"
        -- Définit une liste de tous les sous-dossiers possibles
        set Dossiers_Ajout to {DImage, DTexte, DVideo, DAudio, DAutres}
        -- Boucle sur chaque fichier ajouté dans le dossier de téléchargement
        repeat with Mon_Item in Liste_Fichiers
            set Mon_Ext to name extension of Mon_Item
            set Mon_Nom to name of Mon_Item
            -- Safari télécharge parfois en créant un nom temporaire avec "download"
            -- Exemple : photo.jpg.download
            -- Cela déclenche l'action de dossier, mais Safari change ensuite le nom du fichier!!
            -- Si Safari change avant que l'action de dossier ne démarre, cela crée une erreur
            -- On commence donc par prendre le nom sans le ".download" s'il existe
            -- On extrait l'extension pour attribution du dossier
            if Mon_Ext is "download" then
                -- On supprime le download (les neuf derniers caractères)
                set Mon_Temp to text 1 thru ((length of Mon_Nom) - 9) of Mon_Nom
                -- Recherche du premier "." en partant de la fin pour l'extension
                set I to length of Mon_Temp
                repeat while character I of Mon_Temp is not "."
                    set I to I - 1
                end repeat
                set Mon_Ext to text (I + 1) thru (length of Mon_Temp) of Mon_Temp
                set Mon_Nom to text 1 thru I of Mon_Temp
            end if
 
            -- attente de fin de téléchargement (vérification que la taille ne change plus)
            try
                set Mon_Fichier to Mon_Item as alias
                set OldSize to -1
                repeat until (size of Mon_Fichier) = OldSize
                    set OldSize to (size of Mon_Fichier)
                    delay 0.5
                end repeat
            end try
            -- On teste si l'ajout n'est pas justement l'un des sous-dossiers
            -- Si c'est le cas, la boucle ne fait rien et passe à l'item suivant !
            if Mon_Nom is not in Dossiers_Ajout then
                -- Détermination du sous-dossier via l'extension
                if Mon_Ext is in FImage then
                    set Sous_Dossier to DImage
                else if Mon_Ext is in FTexte then
                    set Sous_Dossier to DTexte
                else if Mon_Ext is in FVideo then
                    set Sous_Dossier to DVideo
                else if Mon_Ext is in FAudio then
                    set Sous_Dossier to DAudio
                else
                    set Sous_Dossier to DAutres
                end if
 
                -- Vérification existence du sous-dossier, création si nécessaire
                if not (exists folder Sous_Dossier of Mon_Dossier) then
                    make new folder at Mon_Dossier with properties {name:Sous_Dossier}
                    -- attention, cet ajout génère encore un event add folder items !! -> d'où le premier test !
                end if
 
                -- Transfert du fichier vers le sous-dossier adéquat
                move Mon_Item to folder Sous_Dossier of Mon_Dossier as alias
 
            end if -- si l'item n'est pas un sous-dossier
        end repeat -- fin de boucle sur chaque item ajouté
 
    end tell
end adding folder items to

J'espère que ces explications et les exemples vous seront utiles pour écrire et gérer des actions de dossiers.

Cet outil si puissant que les autres OS n'ont pas…

Bon courage !

Merci à Claude Leloup pour sa patience et ses corrections.

Venez discustez de cette article sur le forum 1 commentaire Donner une note à l'article (4.5)