tracking download counts

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
permutations
Forum Commoner
Posts: 52
Joined: Sat Dec 07, 2002 11:45 am

tracking download counts

Post by permutations »

I want to track how many times people download a zip file that's on my site. Right now I'm counting the number of times that people click the "Download now" button, but the download could be cancelled so this isn't completely accurate. In the daily Webalizer statistics, there's a count for the number of accesses to this zip file. Is there a way to obtain this in real time (versus analyzing the daily server statistics)? What's the best way to track download counts?
User avatar
d1223m
Forum Commoner
Posts: 80
Joined: Mon Mar 31, 2003 5:15 am
Location: UK, West Sussex

Post by d1223m »

you could have the classic download script.

make a script called download.php which takes a GET variable containing the file to download. This script simply has to passthru the file and add one to the dowload count.

This means that all your links "somefile.zip" become "download.php?f=somefile.zip"

This way you may be able to use the php function connection_abort ( iirc ) that tells you if the user jumped ship
permutations
Forum Commoner
Posts: 52
Joined: Sat Dec 07, 2002 11:45 am

Post by permutations »

I'm not familiar with the "classic download script" so I'm having trouble following your description. Can you give me more details, or point me somewhere I can see an example of a download script that uses this approach?

Thank you.
User avatar
d1223m
Forum Commoner
Posts: 80
Joined: Mon Mar 31, 2003 5:15 am
Location: UK, West Sussex

Post by d1223m »

maybe something like this for your download.php

Code: Select all

<?
$fileDir='/path/to/downloadable/files/';
$fileName=$_GET['file'];
$completeFilePath=$fileDir.'/'.$fileName;

header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header("Content-type: application/octet-stream\nContent-Disposition: inline; filename="".$fileName.""\nContent-length: ".(string)(filesize($completeFilePath)));

$fd=fopen($completeFilePath,'r');
fpassthru($fd);

// add one to your file download counters here 
?>
to check if the download was complete ( after a brief look at this page http://www.php.net/manual/en/features.c ... ndling.php ) i think you need to register a shutdown function ( register_shutdown_function() ) and inside that function check the value of the function connection_aborted(). This will return true if the connection was aborted. To count only files that have been fully downloaded put the code to add one to your counters inside the shutdown function and only run it if connection_aborted()!=true;
permutations
Forum Commoner
Posts: 52
Joined: Sat Dec 07, 2002 11:45 am

Post by permutations »

Looking at the message three-up about "classic download script" a little more, I realize that's what I'm already doing. The problem with this approach is that it counts clicks on the download button, but there's no way to tell if the file was really downloaded. I'll have to read up on that connection_aborted() function. I hadn't heard of that.

What do these header commands do:

header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
header("Content-type: application/octet-stream\nContent-Disposition: inline;

Also, what is the reason for obtaining a file pointer and calling fpassthru? What does that accomplish?
Last edited by permutations on Thu Apr 03, 2003 8:55 am, edited 1 time in total.
Jim
Forum Contributor
Posts: 238
Joined: Fri Apr 19, 2002 5:26 am
Location: Near Austin, Texas

Post by Jim »

I think he's pointing you toward something like this, assuming you keep a count of all downloads on a text file:

Link to file: http://www.yourdomain.com/download.php?file=filename

Code: Select all

<?
/*****    Download.php  *****/

//First, set the file variable properly.  File = name of file in string

$file = $_GET['file'];


if($file) {

//Check to see if that file exists
if(file_exists("/link/to/downloads/$file") == false)  {

die("This file does not exist!")
} else {

//Open text file containing download count

$file = 'count.php';
$open = fopen($file , 'r');
$count = fread($handle , filesize($file));
fclose($open);

//Add one to the count

$newcount = $count+1;

//Open count file and write new count to it

$file = 'count.php';
$count = $newcount;
$open = fopen($file , 'w');
fwrite($open , $count);
fclose($open);

}
}


?>
Understand that the above is an outline of what I think will work, and isn't yet tested. Also note that you don't have to use a text file to do the count, nor do you have to do an enmasse count for all files downloaded. You can set it up to count only the downloads of each particluar file.

Hope this helps!
User avatar
d1223m
Forum Commoner
Posts: 80
Joined: Mon Mar 31, 2003 5:15 am
Location: UK, West Sussex

Post by d1223m »

>What do these header commands do:
>header('Cache-Control: no-cache, must-revalidate');
>header('Pragma: no-cache');
>header("Content-type: application/octet-stream\nContent-Disposition: inline;

To be honest i ripped the downbload script from fpassthru. these headers force the download box to be displayed.

>Also, what is the reason for obtaining a file pointer and calling fpassthru? >What does that accomplish?

I thought to use it so you can actually see if the download has finished. If you just point them at a file ( eg somefile.zip ) then how can you tell they have finished the download? You cant with connection_aborted as its not a php script!

This soloution uses the php script to pass the file thru php just like you would send html normally ( altho the headers let the browser knwo its a download ). Also because of the "power" of php we can detect an aborted connection ( unlike just our regular somefile.zip ).

Providing just a link to somefile.zip means that your power of control stops as soon as they click the link. The passthru method gives you control to the very end *grin*
permutations
Forum Commoner
Posts: 52
Joined: Sat Dec 07, 2002 11:45 am

Post by permutations »

Sorry to be dense, but what do you mean you "ripped the download script from fpassthru". Is "fpassthru" a place in this context? Where did you see the script?

I'm still not understanding how using fpassthru to read the file can tell me whether the user has completed the download. I read the description of fpassthru, and it seems to just return the number of bytes in the file. What can I do with this? How does it tell me when the download has finished?

By the way, I store the download count in a MySQL database, rather than a text file.

... Oh wait, I think you mean the user notes on the fpassthru page of the php.net manual, yes? I see reference to it there. I need to understand what these lines do. Manual time...
Jim
Forum Contributor
Posts: 238
Joined: Fri Apr 19, 2002 5:26 am
Location: Near Austin, Texas

Post by Jim »

I think those headers simply keep the page from caching information that might be important in keeping the count on the download page correct.

Look at my solution and see if it makes any sense to you. I'm not sure whether or not I have something there, but I must admit I'm very interested in seeing exactly what that code might do.

I wish I weren't at school right now, or I'd do some testing on my computer ;)
permutations
Forum Commoner
Posts: 52
Joined: Sat Dec 07, 2002 11:45 am

Post by permutations »

Good grief, this was a pain in the neck--mainly because of IE, which requires special handling. But I did find a working solution. Here it is:

Code: Select all

<?php

$fileDir = '/home/mydir/public_html/files/';
$fileName = 'myfile.zip';
$completeFilePath=$fileDir.$fileName;

// IE cannot download without a cache, so clear these headers
if(strpos($HTTP_SERVER_VARS&#1111;'HTTP_USER_AGENT'], 'MSIE'))&#123;
	header("Pragma: ");
	header("Cache-Control: ");
&#125;
else &#123; //prevent caching
	header('Pragma: no-cache');
	header('Cache-Control: no-cache, must-revalidate');
&#125;

header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=".$fileName);
header("Content-Length: ".filesize("$completeFilePath"));

$fn=fopen($completeFilePath,'rb');
fpassthru($fn);

require_once('Connections/conMyDB.php');
mysql_select_db($database_conMyDB, $conMyDB);

// increase download count
$query = "UPDATE utilities SET DLCount=(DLCount+1) WHERE UtilityID = $UtilityID";
$rs = mysql_query($query, $conMyDB);

?>
One other comment... fpassthru() loads the entire file into memory, which can be a problem for large files. In that case, it's best to replace fpassthru with this:

Code: Select all

while(!feof($fn)) &#123; // this replacement for fpassthru uses less memory (for large files)
  $buffer = fread($fn, 4096);
  print $buffer;
&#125;
I tested all this and it works perfectly in all the browsers I tested (IE6, Netscape 4, Mozilla aka Netscape 7).
User avatar
d1223m
Forum Commoner
Posts: 80
Joined: Mon Mar 31, 2003 5:15 am
Location: UK, West Sussex

Post by d1223m »

yey youve got it!

now to detect broken downloads you can do something like this i think:

Code: Select all

function onshutdown() &#123;
  if (connection_aborted()) &#123;
      // the download was aborted so dont add one to the download
  &#125; else &#123;
      // the download was ok so add one to the download count
  &#125;
&#125;

register_shutdown_function(onShutdown);
im not 100% sure it will work but from what i understand of register_shutdown_fnuction,connection_aborted and fpassthru it should be
permutations
Forum Commoner
Posts: 52
Joined: Sat Dec 07, 2002 11:45 am

Post by permutations »

I don't need to do anything else to detect broken downloads. The counter is only increased if the download is completed, which is what I was after.

If you hadn't pointed me to the fpassthru page in the PHP manual, I wouldn't have found this solution. So thank you.
User avatar
d1223m
Forum Commoner
Posts: 80
Joined: Mon Mar 31, 2003 5:15 am
Location: UK, West Sussex

Post by d1223m »

lol - well im glad we got there.
i know ill want to do the same thing someday
Post Reply