PHP download counter

Hello,

I'm trying to come up with a counter for file accesses. I have a file on a web and currently have the following working code:

The file that launches the download:
<?php
   header('Content-type: application/exe');
   header('Content-Disposition: attachment; filename="'testFile.exe"');
   readfile('testFile.exe');
   
   require("count.php");
   addDownload(); //defined on count.php

   exit();
?>

The count.php file:
<?php

function addDownload(){

      $numVisits=0;
      $file = fopen("counter.txt","r") or exit("Error");

      if( flock($file,LOCK_EX) ){
            
                                $numVisits = fread($file,9);
            $numVisits+=1;
            fclose($file);
            $file = fopen("counter.txt","w") or exit("Error");
            fprintf($file,"%d",$numVisits) or exit("Error");
            fclose($file);
            flock($file,LOCK_UN);
      }
                else fclose($file);              
}
?>

Is this OK? If there are two simultaneous accesses to the file, will it get corrupted? Will the blocked access wait until it can access the file? Also, this doesn't count if the file is accessed directly, without going thru the webpage - is it possible to count that type of access?

Thanks in advance! :)
mortiAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

basiclifeCommented:
From my understanding... it won't corrupt but it will die with an "error" and not record the download if two processes try to access the file at the same time. I had a similar problem and have to admit I cheated - I put the data in a MySQL table and let MySQL deal with it. Not ideal, I know esp. for such a small purpose but... Failing that you probably ned some sort of file that is appended to and then the info is collated regularly.
WoodyRoundUpCommented:
what about if you add the count.php at the top before the download?
with that, you will be having less problem, I supposed.
basiclifeCommented:
Possibly marginally so, but still not 100% reliable in a multi-thread environment. and in this case, we need 100% otherwise, the count will fail. Having said that, at least by using flock, you won't corrupt the file which means you'll never reset it to 0 accidentally. If you're willing to accept a few missed counts, this should be fine.
Learn SQL Server Core 2016

This course will introduce you to SQL Server Core 2016, as well as teach you about SSMS, data tools, installation, server configuration, using Management Studio, and writing and executing queries.

mortiAuthor Commented:
"Possibly marginally so, but still not 100% reliable in a multi-thread environment. and in this case, we need 100% otherwise, the count will fail. "

I know, hence this post. I was there was a way of having a thread blocked on waiting for the file lock to be released. Using a database table is the best solution, because the BD will handle concurrency on the transactions, but I can't use that solution, that's why I had to do this.

But anyway, the code doesn't count if the file is accessed directly, without going thru the webpage - is it possible to count that type of access?
WoodyRoundUpCommented:
I think if you want to count that access, you will need to look at your web log.
That should tells you what file are being accessed on which date and which time.

There is Open Source Stats Applications that will retrieve data from your log file, and display it in nice format.

http://sourceforge.net/projects/awstats/

They are using Perl but.
basiclifeCommented:
One method would be... if the flock fails, pause a fraction of a second then retry. If you pout the logging code after the download starts, it should only delay the following page - which is not overly important. Make sure you don't exceed the mex execution time for the script but it will mean the every "thread" will wait until it can write.

something like:

while not(flock(blah)) {
    pause 1/2 second
}
write and unlock file

You may need to check that this will execute at least once... can't remember my loops right now as I'm quite tired. sorry. One of them will do it right though... as to the direct file access, you could also use mod_rewrite to redirect any request for the file to your script with appropriate variables. mod_rewrite is a pain in the backside to get working just right, however. Depends how important it is. I'm not a great fan of security through obscurity but... don't let anyone know where the real file is, pass the download through a script and noone will be able to touch it. You could also put the original file in a directory which noone can read, just your script. All the DL script would have to do is pass through the file to the browser. You would have to add headers for file name, etc... I seem to remember a reference to this in one of the file access functions in the PHP manual.
basiclifeCommented:
* If you pout the logging code after... =  If you PUT the logging code after...
basiclifeCommented:
Actually, having just looked at your code again it DOES pass the file through so just put testfile.exe in a directory that returns a 403: Forbidden if anyone tries to access it.

You could add the following to a .htaccess file...

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.* - [L]
RewriteRule ^download/(.*)  download.php?filename=$1 [L]

Put simply... if a real location is requested, send it, if a /download/??? is requested, redirect to download.php and pass it the variable $_REQUEST['filename'] to be ???

so, you could even publish the link /download/testfile.exe and your script would handle it and log the download.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
basiclifeCommented:
Thanks for the points but I have to ask why a grade of B? What would you have liked me to include to make it worth an A?
mortiAuthor Commented:
I'm sorry for only replying now; the B was simply due to the fact that I don't have access to an .htacess, but you're right, your answer deserved an A. Sorry for that!
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.