Maintenance d'un dossier
de fichiers temporaires

Dans un article précédent je vous présentais un système de génération à la volée de miniatures avec mise en cache. Peut-être vous êtes vous demandé ce qui allait advenir de toutes ces miniatures mises en cache ? Parce que générer des miniatures c'est sympa, mais si c'est pour saturer l'espace disque de notre hébergement c'est peut être pas la meilleure chose.

La fonction que je vous présente aujourd'hui a pour but de maintenir votre dossier de fichiers temporaires dans les limites du raisonnable. Elle inspecte les fichiers du dossier temporaire et lorsque la taille totale de ces fichiers dépasse la taille maximale multipliée par le ratio de seuil alors elle supprime les fichiers les plus anciens jusqu'à ce que la taille totale soit inférieure à la taille maximale. Le ratio de seuil servant de déclencheur pour un nettoyage tout en douceur.

La fonction fera de son mieux lors de déclenchements concurrents grâce à un ingénieux système de verrou, qui ne prendra pas la tête (ni les ressources CPU) de votre hébergeur.

La fonction

Mesdames et Messieurs, la fonction :

<?php

function wd_clean_repository($root$maxsize=512$threshold_ratio=.5)
{
    if (!is_dir($root))
    {
        return;
    }

    $dh = opendir($root);

    if (!$dh)
    {
        return;
    }

    #
    # create file list, with the filename as key and ctime and size as value.
    # we set the ctime first to be able to sort the file by ctime when necessary.
    #

    $files = array();
    $totalsize = 0;

    while (($file = readdir($dh)) !== false)
    {
        if ($file{0} == '.')
        {
            continue;
        }

        $stat = stat($root . $file);

        $files[$file] = array($stat['ctime']$stat['size']);

        $totalsize += $stat['size'];
    }

    closedir($dh);

    if ($totalsize < ($maxsize + $maxsize * $threshold_ratio) * 1024)
    {
        #
        # There is enough space in the repository. We don't need to delete any file.
        #

        return;
    }

    #
    # The repository is completely full, we need to make some space.
    # We create an array with the files to delete. Files are added until
    # we get back to the size limit.
    #

    asort($files);

    $deletesize = $totalsize - ($maxsize * 1024);

    $deletelist = array();

    foreach ($files as $file => $stat)
    {
        list($size) = $stat;

        $deletesize -= $size;

        $deletelist[] = $root . $file;

        if ($deletesize < 0)
        {
            break;
        }
    }

    #
    # obtain exclusive lock to delete files
    #

    $lockfile = $root . DIRECTORY_SEPARATOR . '.lock';

    $lh = fopen($lockfile'w+');

    if (!$lh)
    {
        WdDebug::trigger('Unable to open lock %file'array('%file' => $lockfile));

        return;
    }

    #
    # We will try $n time to obtain the exclusive lock
    #

    $n = 10;

    while (!flock($lh, LOCK_EX | LOCK_NB))
    {
        #
        # If the lock is not obtained we sleep for 0 to 100 milliseconds.
        # We sleep to avoid CPU load, and we sleep for a random time
        # to avoid collision.
        #

        usleep(round(rand(0100) * 1000));

        if (!--$n)
        {
            #
            # We were unable to obtain the lock in time.
            # We exit silently.
            #

            return;
        }
    }

    #
    # The lock was obtained, we can now delete the files
    #

    foreach ($deletelist as $file)
    {
        unlink($file);
    }

    #
    # and release the lock.
    #

    fclose($lh);
}

Exemple d'utilisation

Et voici un exemple d'utilisation, c'est pas très compliqué.

<?php

wd_clean_repository($_SERVER['DOCUMENT_ROOT'] . '/path_to_repository/');

Notez quand même la nécessité de fournir un chemin absolu.

On peut également définir la taille du cache (en Ko), ainsi que le ratio de seuil. Voici ce que l'on pourrait utiliser pour un cache d'un méga et 512 Ko de seuil (soit 1,5Mo au maximum) :

<?php

wd_clean_repository($_SERVER['DOCUMENT_ROOT'] . '/path_to_repository/'1024, .5);

Laisser un commentaire

Un commentaire

Savageman
Savageman

Première visite de ce blog qui m'a l'air bien sympathique ! :) C'est pourquoi je me tente dans un conseil.

Au niveau du lock, plutôt que d'attendre une durée aléatoire avec usleep(), je conseille un flock() en mode non bloquant (qui retourne directement) combiné à la variable par référence $would de cette même fonction.

2 cas possibles : 1) $would == false => c'est bon, on a eu le lock exclusif 2) $would == true => on a pas eu le lock, mais on peut facilement attendre qu'il soit libéré avec un flock($lh, LOCK_SH); flock($lh, LOCK_UN);

Si on a pas réussi à avoir le lock exclusif, c'est que quelqu'un l'a déjà. On prend alors un lock partagé – mode bloquant (qui va donc attendre que le lock exclusif soit libéré), puis on le libère tout de suite derrière. Ce moment est l'opportunité de réessayer d'obtenir un lock exclusif (puisqu'il vient juste d'être libéré).

@+