2

I have a number of different hosting accounts set up for clients and need to calculate the amount of storage space being used on each account, which would update regularly.

I have a database set up to record each clients storage usage.

I attempted this first using a PHP file on each account, run by a Cron Job. If run manually by myself, it would output the correct filesize and update the correct size to the database, although when run from the Cron Job, it would output 0.

I then attempted to run this file from a Cron Job from the main account but figured this wouldn't actually work as my hosting would block files from another server and I would end up with the same result as before.

I am now playing around with FTP access to each account from a Cron Job from the main account which looks something like below, the only problem is I don't know how to calculate directory size rather than single file sizes using FTP access, and don't know how to reiterate this way? Hoping somebody might be able to help here before I end up going around in circles?

I will also add the previous first attempt too.

$ftp_conn = ftp_connect($ftp_host, 21, 420) or die("Could not connect to server");
$ftp_login = ftp_login($ftp_conn, $ftp_username, 'mypassword');

$total_size = 0;                    
$contents = ftp_nlist($ftp_conn, ".");
// output $contents
foreach($contents as $folder){

    while($search == true){

        if($folder == '..' || $folder == '.'){

        } else {
            $file = $folder; 
            $res = ftp_size($ftp_conn, $file);

            if ($res != -1) {
                $total_size = $total_size + $res;
            } else {
                $total_size = $total_size;
            }
        }
    }
}

ftp_close($ftp_conn); 

This doesn't work as it doesn't calculate folder sizes and I don't know how to open the reiterate using this method?


This second script did work but would only work if opened manually, and return 0 if run by the cron job.

class Directory_Calculator {

    function calculate_whole_directory($directory)
    {
        if ($handle = opendir($directory))
        {
            $size = 0;
            $folders = 0;
            $files = 0;

            while (false !== ($file = readdir($handle)))
            {
                if ($file != "." && $file != "..")
                {
                    if(is_dir($directory.$file))
                    {
                    $array = $this->calculate_whole_directory($directory.$file.'/');
                    $size += $array['size'];
                    $files += $array['files'];
                    $folders += $array['folders'];
                    }
                    else
                    {
                    $size += filesize($directory.$file);
                    $files++;
                    }
                }
            }
            closedir($handle);
        }

        $folders++;

        return array('size' => $size, 'files' => $files, 'folders' => $folders);
    }

}

/* Path to Directory - IMPORTANT: with '/' at the end */
$directory = '../public_html/'; 

// return an array with: size, total files & folders
$array = $directory_size->size($directory); 

$size_of_site = $array['size'];

echo $size_of_site;

Please bare in mind that I am currently testing and none of the MySQLi or PHP scripts are secure yet.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Snappysites
  • 804
  • 1
  • 10
  • 41
  • Have you double-checked to make sure your cmd-line php and apache php are configured the same? (cron jobs use the cmd line php) – IncredibleHat Jan 10 '18 at 22:14
  • Yes, the cron job does work and I can see the results, but for some reason it returns 0 when its run this way - when it is run manually, it seems to work? – Snappysites Jan 10 '18 at 22:16
  • 1
    Ah, you run it manually via cmd-line as well then? That is odd about cron. It may be since cron is running as root, while your cmd-line manual run is by your user? Unless you are su in as well, then thats not the case. Huh... Just rolling it around in my head. Whenever I've come across discrepencies running something manually vs from a cron job, usually ended up being apache vs cmd, or root vs apache user, n such. Which may not be your problems :( – IncredibleHat Jan 10 '18 at 22:17
  • No sorry, when I run it manually, I mean I navigate to the PHP file using my browser, not from the cmd line. Sorry I wasn't more clear there. – Snappysites Jan 10 '18 at 22:19
  • When you do that second script which works manually, but 0 with cron... are all your account directories actually 'local' (`$directory = '../public_html/'; `)? If so, then yeah, no need to go the FTP route, since all directories are on the same server as the script. So that cuts down that to less code to think about. ... or I got confused by the question LOL! I'm pulling a blank. – IncredibleHat Jan 10 '18 at 22:23
  • Yes, they are all local, this second script was within the `public_html/` directory. So it should work every time... And it does work, just doesn't seem to perform the calculations when run with Cron Job, which is odd right? – Snappysites Jan 10 '18 at 22:29

1 Answers1

2

If your server supports MLSD command and you have PHP 7.2 or newer, you can use ftp_mlsd function:

function calculate_whole_directory($ftp_conn, $directory)
{
    $files = ftp_mlsd($ftp_conn, $directory) or die("Cannot list $directory");
    $result = 0;
    foreach ($files as $file)
    { 
        if (($file["type"] == "cdir") || ($file["type"] == "pdir"))
        {
            $size = 0;
        }
        else if ($file["type"] == "dir")
        {
            $size = calculate_whole_directory($ftp_conn, $directory."/".$file["name"]);
        }
        else
        {
            $size = intval($file["size"]);
        }
        $result += $size;
    } 

    return $result; 
} 

If you do not have PHP 7.2, you can try to implement the MLSD command on your own. For a start, see user comment of the ftp_rawlist command:
https://www.php.net/manual/en/function.ftp-rawlist.php#101071


If you cannot use MLSD, you will particularly have problems telling if an entry is a file or folder. While you can use the ftp_size trick, as you do, calling ftp_size for each entry can take ages.

But if you need to work against one specific FTP server only, you can use ftp_rawlist to retrieve a file listing in a platform-specific format and parse that.

The following code assumes a common *nix format.

function calculate_whole_directory($ftp_conn, $directory)
{
    $lines = ftp_rawlist($ftp_conn, $directory) or die("Cannot list $directory");
    $result = 0;
    foreach ($lines as $line)
    { 
        $tokens = preg_split("/\s+/", $line, 9); 
        $name = $tokens[8];
        if ($tokens[0][0] === 'd')
        {
            $size = calculate_whole_directory($ftp_conn, "$directory/$name");
        }
        else
        {
            $size = intval($tokens[4]);
        }
        $result += $size;
    } 

    return $result; 
} 

Based on PHP FTP recursive directory listing.


Regarding cron: I'd guess that the cron does not start your script with a correct working directory, so you calculate a size of a non-existing directory.

Use an absolute path here:

$directory = '../public_html/'; 

Though you better add some error checking so that you can see yourself what goes wrong.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
  • 1
    Thanks for a descriptive answer, this method is probably the best solution to my problem. It seems I may be stuck manually processing the script but at least it calculates correctly! Thanks. – Snappysites Jan 11 '18 at 11:51