2

I need to allow files to be downloaded, but have to track the number of times they were downloaded by each user.

I have a simple chunk of code here that allows users to download a file, and then the callback increments the counter in the datastore for that user when its done.

This works fine if a user is NOT using a download manager. But if they are (ie: DownThemAll) then the manager makes multiple HTTP requests and the callback gets called everytime. This spikes the download count and makes it unreliable. I've tried short circuiting multiple requests by watching a session variable - which in theory would have cut multiple requests down to just one. But it appeared DownThemAll didn't share the same session across requests, so it didn't work.

Is there a way to make this work?

    return \Response::stream(function() use ($stream, $file, $callback) {
        while (!feof($stream)) {
            echo fread($stream, 1024);
        }
        fclose($stream);
        Log::info('Resource downloaded', ['resource' => $file]);
        if (is_callable($callback)) {
            call_user_func($callback);
        }
    }, 200, $headers);
veilig
  • 5,085
  • 10
  • 48
  • 86

2 Answers2

0

You can try a token system and only increment the downloads based on consumed tokens.

Have a controller method that generates a token to download the file. This token generation is what increments the download counter. Next, have a method that receives the token, marks it as consumed (so no other requests/increments will happen with this token), and then initiates the transfer. You can mark the token either before the transfer or at end of file, either way but if you do it before then download managers will not have access to download the resource.

This request token for download system also helps prevent direct linking to a resource.

If you want an open downloading system with a counter then I would suggest logging all http requests with the header and browser information and then group the multiple http requests in an alloted window by ip/header as one count. HTTP_USER_AGENT and REMOTE_ADDR/HTTP_X_REAL_IP may be all you need to group buy but I am not sure.

dasper
  • 692
  • 4
  • 13
0

I was able to get this to work by monitoring the handler position against the size of the download. It appears that this works.

        return \Response::stream(function() use ($stream, $size, $file, $callback) {
            while (!feof($stream)) {
                echo fread($stream, 1024);
            }   

            //  Only log downloaded and increment counter when the EOF
            //  of the stream is ACTUALLY reached. This continues to allow
            //  individual downloads to work, but also allows download
            //  managers to send concurrent requests and only increment
            //  when the whole file is sent.
            if (ftell($stream) == $size) {
                Log::info('Resource downloaded', ['resource' => $file]);
                if (is_callable($callback)) {
                    call_user_func($callback);
                }
            }
            fclose($stream);
        }, 200, $headers);
veilig
  • 5,085
  • 10
  • 48
  • 86