tags:

views:

169

answers:

2

I have a php script on a web server that uploads a file to another remote server via ftp_put.

How can I display the current upload progress to the user?

The only similar system I've seen is for file uploads from the user, with ajax requests to check the local size of the uploaded file on the server.

The equivalent system would be ajax requests to the web server, that then checked file sizes on the remote server and returned that data to the user's clientscript.

This seems horribly inefficient to me. Is there a better way?

+1  A: 

The PHP function ftp_put will not return until it has completed, so as you said, the only way (using ftp_put) would be to poll the remote FTP server and see what the file size is, which might not work anyway because often the uploads go to a temporary file, only being put in place when they are 100% complete.

WHat I would do instead is spawn a second process to do the uploading, and use something like wget or curl. You could parse the output from curl and extract the progress.

So I suggest (and this is just a suggestion, there's a variety of ways to do this...)

  1. User uploads file to your PHP script
  2. Your PHP script generates a unique ID $uploadId
  3. Your PHP script uses exec() to call curl to upload the file, spawning curl as a background job and redirecting the output to some file like "curlprogress-$uploadId.txt"
  4. Your PHP script displays a page to the user which has a periodiclly executing AJAX call to something like: upload_progress.php?id=$uploadId
  5. upload_progress.php examines "curlprogress-$uploadId.txt", extracts and returns the percentage done.
  6. Find a way to clean up the curl status files. Possibly rather than executing curl directly in step 3, you execute a bash script which calls curl and automatically redirects it's output, deleting the file when it's done. Or, just have upload_progress.php delete the "curlprogress-$uploadId.txt" file if the progress is 100%.

Note this isn't easy. I would provide code but we're talking hours of development here ;-)

Josh
To add, if OP is using PHP 5.3, he can take advantage of the CURLOPT_PROGRESSFUNCTION option. That might allow him to eliminate some steps from your idea.
webbiedave
@webbiedave: Good advice. I admit (reluctantly) that I haven't given 5.3 as much time as I should being a PHP developer, so I am not familiar with `CURLOPT_PROGRESSFUNCTION`. However that seems like it could make this a hell of a lot easier!!!
Josh
I may be mistaken, but doesn't curl _not_ output progress data for ftp transactions? From the curl man: `It is not the same case for FTP upload as that operation does not spit out any response data to the terminal.`
Billiam
@Billiam: Hmmm... I'm sure there's a way to make this work but it may require another language, does the machine have ruby installed?
Josh
A: 

If ftp server on other machine supports REST command (restart uploading from certain point) there is dirty way to implement this:

  1. create temp file
  2. put X bytes to this file from the file you want to upload
  3. upload temp file
  4. write status to another file (or session, but not sure if it will work)
  5. append another X bytes to temp file
  6. Upload temp file starting form X bytes
  7. crite status to file
  8. repeat 5-7 until whole file is uploaded
  9. delete temp & status files.

Sample code:

$fs = filesize('file.bin');
define('FTP_CHUNK_SIZE', intval($fs * 0.1) ); // upload ~10% per iteration

$ftp = ftp_connect('localhost') or die('Unable to connect to FTP server');
ftp_login($ftp, 'login', 'pass') or die('FTP login failed');

$localfile = fopen('file.bin','rb');

$i = 0;
while( $i < $fs )
{
    $tmpfile = fopen('tmp_ftp_upload.bin','ab');
    fwrite($tmpfile, fread($localfile, FTP_CHUNK_SIZE));
    fclose($tmpfile);

    ftp_put($ftp, 'remote_file.bin', 'tmp_ftp_upload.bin', FTP_BINARY, $i);
    // Remember to put $i as last argument above

    $progress = (100 * round( ($i += FTP_CHUNK_SIZE)  / $fs, 2 ));
    file_put_contents('ftp_progress.txt', "Progress: {$progress}%");
}
fclose($localfile);
unlink('ftp_progress.txt');
unlink('tmp_ftp_upload.bin'); // delete when done

And file to check with ajax:

if(file_exists('ftp_progress.txt'))
    echo file_get_contents('ftp_progress.txt');
else
    echo 'Progress: 0%';
exit;
dev-null-dweller