Sunday, July 25, 2010

11:20 PM
1
It was about 2 months back when I started hunting for an upload meter “How To?” guide on net. Came across a lot of links actually but I must say none of them actually gave me what I wanted to have. Some talked of some patched PHP version to achieve the same and some talked everything except an upload meter.

Finally I was able to come up with the trick involved (thanks to sources on net and some work on my side). Here I will explain in short how can we achieve this on windows and unix both:

APC (Alternate PHP Cache) : 

Before we go on to develop an upload meter we will need to have this component configured. Without APC its almost impossible to have an upload tracking script working on your server in PHP. Basically, when we upload a file to a server (windows or linux) the file first goes to the /tmp folder (DEFAULT) and then using the php’s move_uploaded_file($FileTemp,$FileUploadLocation) command we move that file from /tmp folder to the location we finally want it to be.

Now the only problem is that, the file name in the /tmp is quite nondeterministic. There is no way to predict the file name in /tmp. Hence by simple methods we cannot track the status of upload for that particular file. We need a handler to that file, through which we can track its upload progress.

Although APC is not meant for this, but it does provide us a method to bind a unique name to this uploading file. Lets first see how to install APC on windows and unix.

Installing APC on windows : 

On windows the installation process is quite simple. Simply download this zipped folder from here. Extract it and you will find, folders corresponding to each PHP version released. Identify your PHP version, copy the corresponding DLL file from that folder and place it under C:/PHP/ext and C:/Windows (if you have your php.ini file placed there).

Now open your php.ini file, and add the following lines:

extension=php_apc.dll
apc.shm_segments=1
apc.optimization=0
apc.shm_size=128
apc.ttl=7200
apc.user_ttl=7200
apc.num_files_hint=1024
apc.mmap_file_mask=/tmp/apc.XXXXXX
apc.enable_cli=1
apc.rfc1867=on
apc.max_file_size=10M


These lines must be placed after all the extension definition in the php.ini file. I won’t go into explaining the various parameters here, you can search for them and you will find plenty of documentation for that. As of now you can care only about apc.rfc1867=on (without this file upload meter won’t work, this tells APC to track file uploading) and apc.max_file_size=10M (this specifies max file size to cache which is otherwise 1Mb by default).

Now simply restart your apache server, and see your php information i.e. phpinfo(). You must see a section APC in the phpinfo(). If you can see that section, Congratulations APC installation is done for you

Installing APC on unix (Debian or Ubuntu) : 

I have done this on debian and ubuntu, but similar must be the process for other OS too. Simply install the following modules:

  • apt-get install apache2-dev,apache2-threaded-dev,php-pear,php5-dev,make
  • ln -s /usr/bin/apxs2 /usr/bin/apxs (APC won’t install without this symlink)
  • pecl install apc (input Yes if prompted)
  • Add extension=apc.so in php.ini
  • /etc/init.d/apache2 restart
  • pecl upgrade apc
  • /etc/init.d/apache2 restart
  • Add apc.rfc1867=on in php.ini for tracking file upload

Coding your Upload meter (index.php): 

So we are almost there. Just a few lines of code and we have our upload tracker ready. Let us first create an html form.

<html>
  <head>
    <script type=”text/javascript” src=”upload.js”></script>
  </head>
  <body>
    <form id=”upload” name=”upload” target=”frame” action=”utility.php” method=”POST” enctype=”multipart/form-data”>
     
      <!– Setting this enables apc upload tracking –>
      <input type=”hidden” name=”APC_UPLOAD_PROGRESS” id=”fileKey” value=”<?php echo md5(uniqid(rand(),true)); ?>”/>
     
      <!– Hidden field for the upload form –>
      <input type=”hidden” id=”uploadfile” name=”uploadfile” value=”uploadfile”/>
     
      <!– DIV containing the file chooser box, it will be replaced by the filename after upload –>
      <div id=”attachment”>
        <input id=”fileloc” name=”fileloc” size=”30? type=”file” onchange=”getProgress();document.upload.submit();return false;”/>
      </div>
     
      <!– DIV showing the upload progress bar –>
      <div id=”showProgress” style=”margin-top:5px;width:205px;height:15px;border:1px solid #93B9D9;display:none”>
        <div id=”currentStatus” style=”width:0px;height:15px;background-color:#B2D281?></div>
      </div>
     
      <!– iframe handling all the upload –>
      <iframe name=”frame” style=”display:none”></iframe>
     
      <!– DIV which catches all the error and other messages –>
      <div id=”upload-note”></div>
    </form>
  </body>
</html>


The code is self explainatory
  1. , if not I have put in some comments in there. Here is a short explaination again:
    • upload.js file will handle the ajax calls later on and also will take care of incrementing the upload status bar.
    • <input type=”hidden” name=”APC_UPLOAD_PROGRESS” id=”fileKey” value=”<?php echo md5(uniqid(rand(),true)); ?>”/> , this is the factor which allows us to keep a track of our file. This must always be declared before the file uploading input box. Basically it tells APC to associate value=”[SPECIFIED_ABOVE]” with the uploading file. This values serves as a key and using this key we will later on track the file.
    • The form submits into an iframe. 
    • <div id=”attachment”></div> contains the file chooser input box, which submits the form as soon as it receives a file to upload.
    • id=”showProgress” and name=”frame” are self explanatory. I have used <div id=”upload-note”></div> to post upload error messages in case of an error.

  1. Coding your Upload meter (utility.php): utility.php is the file which handles the file uploading process. The form submits into this file through the iframe.
<?php
  if((isset($_POST['uploadfile'])) && ($_POST['uploadfile'] == “uploadfile”)) {
   
    // Gather File data (name,size,mimetype,tmp_name)
    $FileName = $_FILES['fileloc']['name'];
    $FileSize = round($_FILES['fileloc']['size']/1024);
    $FileType = $_FILES['fileloc']['type'];
    $FileTemp = $_FILES['fileloc']['tmp_name'];
   
   
    // Get file extension
    $extension = strtolower(substr($FileName,strrpos($FileName,’.')+1));
   
    // If filename is blank, exit with a message
    if($FileName == “”) {
      echo “<script type=’text/javascript’>”;
      echo “parent.document.getElementById(‘upload-note’).innerHTML = ‘<font size=2 color=#990000>Kindly choose a file to upload</font>’”;
      echo “</script>”;
      exit;
    }
   
    // If filesize is > 10 Mb, exit with a message
    if($FileSize >= “10240?) {
      echo “<script type=’text/javascript’>”;
      echo “parent.document.getElementById(‘upload-note’).innerHTML = ‘<font size=2 color=#990000>OOPS! The file exceeds the maximum limit of 10 Mb</font>’”;
      echo “</script>”;
      exit;
    }
   
    // Exit if file’s extension is not jpg
    if($extension != “jpg”) {
      echo “<script type=’text/javascript’>”;
      echo “parent.document.getElementById(‘upload-note’).innerHTML = ‘<font size=2 color=#990000>OOPS! Only jpg file formats are supported</font>’”;
      echo “</script>”;
      exit;
    }
   
    // Choose a final upload location
    $FileUploadLocation = “upload/”.$FileName.”.”.strtolower($extension);
   
    // Move the uploaded file from temp to final upload location
    if(move_uploaded_file($FileTemp,$FileUploadLocation)) {
      echo “<script type=’text/javascript’>”;
      echo “parent.document.getElementById(‘upload-note’).style.display=’none’;”;
      echo “parent.document.getElementById(‘attachment’).innerHTML = ‘<input CHECKED type=\”checkbox\”><font color=#003399 size=2><b>”.$FileName.”</b> (“.$FileType.”) “.$FileSize.” Kb</font>’;”;
      echo “</script>”;
    }
    else {
      echo “<script type=’text/javascript’>”;
      echo “parent.document.getElementById(‘upload-note’).innerHTML = ‘<font size=2 color=#990000>OOPS! An error occurred, please try again</font>’”;
      echo “</script>”;
    }
   
  }
 
?>

  1. I have tried to include enough comments to make this code self explanatory. If you find any thing which is not self explainable kindly drop a comment and I shall reply to it.
    In short this file takes the file size, file name, file tmp url. Check for the file, if its valid and within constraints. i.e. Max file size 10 Mb, File format jpg etc etc…..


  2. Coding your Progress Status script (progress.php): 

    progress.php is the script to which we will make all the ajax calls, passing it the “filekey” defined in index.php above. Using that filekey it will return the upload status of the file associated with that key.

    Essentially, it returns 3 things. Done, Current, Total i.e. Done (meaning if the upload is complete), Current (meaning how much has been uploaded already), Total (meaning whats the total size expected).

    Hence using these 3 params we will make over upload meter progress bar. Lets see the javascript function doing all these ajax requests and maintaining the progress bar.

  3. Coding the JavaScript (upload.js): As soon as the form gets submitted, it calls the getProgress() function inside this javascript function. This function starts making calls to progress.php. And then depending upon what it returns, it manages the progress bar. Here is the javascript doing the same:

function getProgress() {
  var xmlhttp = false;
  var id = document.getElementById(‘fileKey’).value;
  var url = “http://localhost/FileUploadMeter/progress.php?id=” + id + “&nc=” + (Math.random()*100000);
  try {
    xmlhttp = new ActiveXObject(“Msxml2.XMLHTTP”);
  }
  catch(e) {
    try {
      xmlhttp = new ActiveXObject(“Microsoft.XMLHTTP”);
    }
    catch(oc) {
      xmlhttp = null;
    }
  }
  if (!xmlhttp && typeof XMLHttpRequest != “undefined”) {
    xmlhttp = new XMLHttpRequest();
  }
  if(!xmlhttp) {
    document.getElementById(‘xmlhttp-note’).style.display = ‘inline’;
    return false;
  }
  xmlhttp.onreadystatechange = function() {
    if(xmlhttp.readyState!=4) {
    }
    if ((xmlhttp.readyState==4)&&(xmlhttp.status == 200)) {
      var response = xmlhttp.responseText;
      if(response != “”) {
        if(response != “Success”) {
          var d = eval(“(” + response + “)”);
          // Update Progress Bar
          if(d.total != 0 && d.current != 0) {
            document.getElementById(’showProgress’).style.display = ‘block’;
            var percentDone = (d.current/d.total)*100;
            // Calculate the length of the uploaded file
            var percentLength = parseInt(document.getElementById(’showProgress’).style.width)*percentDone/100;
            // Update the length of uploaded file
            document.getElementById(‘currentStatus’).style.width = Math.round(percentLength) + ‘px’;
            if(d.current < d.total) {
              setTimeout(“getProgress();”,1000);
            }
            else {
              document.getElementById(’showProgress’).style.display = ‘none’;
            }
          }
        }
        else {
          document.getElementById(’showProgress’).style.display = ‘none’;
        }
      }
      else {
        if(document.getElementById(‘upload-note’).innerHTML == ”) {
          setTimeout(“getProgress();”,1000);
        }
      }
    }
  }
  xmlhttp.open(‘GET’,url,true);
  xmlhttp.send(null);
}

  1. In the middle we can see that it checks for the returned response. If response == “”, it means we made an ajax call even before the upload started. If response != “” AND response != “Success”, it means that the upload is in progress and we need to show the appropriate progress bar.

    Progress bar is calculated based on the statistics returned. I hope its quite a simple one and again self explainatory.

    The links which I found on internet were using hell lot of unnecessary things, just to make this progress bar. Some were using Yahoo’s YUI library, some JQuery and what not and I thought may be I should make it as simple as possible.
So thats it. Here you have your upload meter ready and running.

Enjoy and Thanks for reading till here

1 comments:

Tom Boutell said...

Where is progress.php? Looks like the most important piece is missing... the bit that actually gets the status of the upload in response to the AJAX requests.