J'ai un répertoire avec des fichiers qui nécessitent un traitement en lot avec PHP. Les fichiers sont copiés sur le serveur via FTP. Certains fichiers sont très gros et prennent beaucoup de temps à copier. Comment déterminer dans PHP si un fichier est toujours en cours de transfert (afin que je puisse ignorer le traitement de ce fichier et le traiter lors de la prochaine exécution du traitement par lots)?
Une possibilité consiste à obtenir la taille du fichier, d'attendre quelques instants et de vérifier si la taille du fichier est différente. Ce n'est pas étanche car il y a une légère chance que le transfert soit bloqué pendant quelques instants ...
Notre administrateur de serveur a suggéré ftpwho, qui affiche les fichiers actuellement transférés.
http://www.castaglia.org/proftpd/doc/ftpwho.html
La solution consiste donc à analyser la sortie de ftpwho pour voir si un fichier du répertoire est en cours de transfert.
L'un des moyens les plus sûrs consiste à télécharger les fichiers avec un nom temporaire et à les renommer une fois le transfert terminé. Votre programme doit ignorer les fichiers portant le nom temporaire (une simple extension fonctionne parfaitement). Cela nécessite évidemment que le client (uploader) coopère, ce qui n’est donc pas idéal.
[Cela vous permet également de supprimer les transferts échoués (partiels) après une période donnée si vous en avez besoin.]
Tout ce qui est basé sur l'interrogation de la taille du fichier est racé et dangereux.
Une autre méthode (qui requiert également la coopération de l’éditeur de fichiers) peut consister à télécharger en premier le hachage et la taille du fichier, puis le fichier lui-même. Cela vous permet de savoir à la fois quand le transfert est effectué et s'il est cohérent. (Il y a beaucoup de variantes autour de cette idée.)
Un élément qui ne nécessite pas la coopération du client est de vérifier si le fichier est ouvert par un autre processus ou non. (Votre façon de le faire dépend du système d'exploitation. Je ne connais pas de PHP intégré qui le fasse. lsof
et/ou fuser
peuvent être utilisés sur diverses plates-formes de type Unix, Windows possède des API pour cela. .) Si le fichier est ouvert par un autre processus, il est probable qu'il ne soit pas encore complet.
Notez que cette dernière approche peut ne pas être infaillible si vous autorisez le redémarrage/la reprise des téléchargements ou si votre logiciel de serveur FTP ne maintient pas le fichier ouvert pendant toute la durée du transfert, donc YMMV.
Certains serveurs FTP autorisent l'exécution de commandes lorsqu'un certain événement se produit. Donc, si votre serveur FTP le permet, vous pouvez créer un schéma de signalisation simple pour que votre application sache que le fichier a été téléchargé avec plus ou moins de succès (plus ou moins parce que vous ne savez pas si l'utilisateur a l'intention de télécharger le fichier.) complètement ou en partie). Le schéma de signalisation peut être aussi simple que la création du fichier "Upload_file_name.ext.complete" et vous contrôlerez l'existence de fichiers avec l'extension ".complete".
Maintenant, vous pouvez vérifier si vous pouvez ouvrir le fichier en écriture. La plupart des serveurs FTP ne vous le permettent pas si le fichier est en cours de téléchargement.
Une autre approche mentionnée par Mat consiste à utiliser des techniques spécifiques au système pour vérifier si le fichier est ouvert par un autre processus.
La meilleure façon de vérifier serait d’essayer d’obtenir un verrou exclusif sur le fichier en utilisant flock. Le processus sftp/ftp utilisera les bibliothèques fopen.
// try and get exclusive lock on file
$fp = fopen($pathname, "r+");
if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
flock($fp, LOCK_UN); // release the lock
fclose($fp);
}
else {
error_log("Failed to get exclusive lock on $pathname. File may be still uploading.");
}
Ce n'est pas vraiment une bonne astuce, mais c'est simple :-), on peut faire la même chose avec filemtime
$result = false;
$tryies = 5;
if (file_exists($filepath)) {
for ($i=0; $i < $tryies; $i++) {
sleep(1);
$filesize[] = filesize($filepath);
}
$filesize = array_unique($filesize);
if (count($filesize) == 1) {
$result = true;
} else {
$result = false;
}
}
return $result;