I have a php-script that updates the download counter of the appropriate file (it sends headers with file). But if I click cancel button on save file dialog the counter is incremented too. It seems wrong.
My idea is to send ajax request and decrement counter if cancel button is clicked. But how to detect cancel button click?
You should try this Track file download progress with Javascript and do the increment when download 100% done
I solved my problem using something like downloading with parts of the file using fread in PHP.
It looks like this:
// send file (it shows save file dialog)
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($filepath));
header('Content-Disposition: filename='.$filename);
flush();
$file = fopen($filepath, "r");
while(!feof($file))
{
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
// flush the content to the browser
flush();
// sleep one second
sleep(1);
}
// if all are right and file was downloaded
if (feof($file)) {
update_current_counter_or_insert_new_one_in_DATABASE();
}
fclose($file);
This link helps me to solve (php.net readfile article). So, it works for me more or less.
Related
Hello ive searched everywhere to find the answer however none of the solutions ive tried helped
What i am building is a site which connects to Youtube to allow users to search and download videos as MP3 files. I have built the site with the search etc however i am having a problem with the download part (ive worked out how to get the youtube audio file). The format for the audio is originally audio/mp4 so i need to convert it to mp3 however first i need to get the file on the server
So on the download page ive made a script that sends an ajax request to the server to start downloading the file. It then sends a request to a different page every few seconds to find out the progress and update it on the page the user is viewing.
However the problem is while the video is downloading the whole website freezes (all the pages dont load until the file is fully downloaded) and so when the script tries to find out the progress it cant until its fully done.
The file which downloads:
<?php
session_start();
if (isset($_GET['yt_vid']) && isset($_GET['yrt'])) {
set_time_limit(0); // to prevent the script from stopping execution
include "assets/functions.php";
define('CHUNK', (1024 * 8 * 1024));
if ($_GET['yrt'] == "gphj") {
$vid = $_GET['yt_vid'];
$mdvid = md5($vid);
if (!file_exists("assets/videos/" . $mdvid . ".mp4")) { // check if the file already exists, if not proceed to downloading it
$url = urlScraper($vid); // urlScraper function is a function to get the audio file, it sends a simple curl request and takes less than a second to complete
if (!isset($_SESSION[$mdvid])) {
$_SESSION[$mdvid] = array(time(), 0, retrieve_remote_file_size($url));
}
$file = fopen($url, "rb");
$localfile_name = "assets/videos/" . $mdvid . ".mp4"; // The file is stored on the server so it doesnt have to be downloaded every time
$localfile = fopen($localfile_name, "w");
$time = time();
while (!feof($file)) {
$_SESSION[$mdvid][1] = (int)$_SESSION[$mdvid][1] + 1;
file_put_contents($localfile_name, fread($file, CHUNK), FILE_APPEND);
}
echo "Execution time: " . (time() - $time);
fclose($file);
fclose($localfile);
$result = curl_result($url, "body");
} else {
echo "Failed.";
}
}
}
?>
I also had that problem in the past, the reason that it does not work is because the session can only be once open for writing.
What you need to do is modify your download script and use session_write_close() each time directly after writing to the session.
like:
session_start();
if (!isset($_SESSION[$mdvid])) {
$_SESSION[$mdvid] = array(time(), 0, retrieve_remote_file_size($url));
}
session_write_close();
and also in the while
while (!feof($file)) {
session_start();
$_SESSION[$mdvid][1] = (int)$_SESSION[$mdvid][1] + 1;
session_write_close();
file_put_contents($localfile_name, fread($file, CHUNK), FILE_APPEND);
}
In public/templates/calendar.html I have
<a href="" id="secret_download_button" style="display:none" download="">
In the same file I have a button (download qr), i make an ajax call from javascript, the qr gets created in /public/uploads/thumbs/qrcodes/'filename'
the ajax calls is finished and the following function is called which is in
public/javascript/some.js
function (data) {
$('#secret_download_button').attr('href',data.content);
$('#secret_download_button').attr('download','somename');
$('#secret_download_button').click();
});
data.content = public/upload/thumbs/qrcodes/someqr.png (example)
I need to use relative paths, not absolute paths. What am I doing wrong ? I am guessing that I am setting the href path wrong.
Also from what I read online this solution is not supported by IE. Is there another, simpler, more elegant way of doing this ( I need to be able to specify the name of the file which will be downloaded )
Edit
Solved it server-side in the end. thanks. For anyone else having the same problem I used this:
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($activity_name . '.png').'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($root . $path . $file_name));
readfile($root . $path . $file_name);
exit;
You're right, the download attribute is not supported by IE nor Edge (supported in Edge 13+) : http://caniuse.com/#feat=download
To have a cross-browser solution, you will have to open the url in the current window :
function (data) {
window.location.href = data.content;
});
or a new window :
function (data) {
window.open(data.content);
});
Two limitations:
you can't set the filename client-side, you have to set it server side
the browser will not download the file if it can read it (like images, pdf...), you will have to use the "save as"
I have a page with some forms and input fields, which the user fills in and then they are sent to a php page via Ajax and $_POST.
And then the php File writes the output to a txt file. - that works just fine. My problem is I am trying to force the user to download it on that same page that creates the file, after the file is created and I can't seem to get it to work, nothing happens besides the file being created:
Here the code where the .txt File is created (this works nice):
$myfile = fopen("test.txt", "w") or die("Unable to open file!");
foreach ($URLsArray as &$url) {
$row=$SomeArray[$keys[$index]]."\t".$SomeArray[$keys[$index]]."\t".$SomeArray[$keys[$index]]."\t".$SomeArray[$keys[$index]]."\t".$url."\n";
fwrite($myfile, $zeile);
$index = $index + 1;
}
fclose($myfile);
And here the code, where I try to force the download: (after the fclose)
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename('test.txt'));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize('test.txt'));
readfile('test.txt');
exit;
And when I try this I get the error: "Unexpected token A". "A" is the first letter in the test.txt, which is created.
And I know that there are a lot of similar questions, but not one solution worked for me.
I hope someone can help me :)
Instead of doing this backend with PHP you could try to do this on the frontend part. The easiest solution is to write some JavaScript code which adds an iframe to your webpage. The iframe should then have a href to the file you want the user to download.
Okay here my solution: #CBroe thanks for the hint with the background request, I would have tried for ages to get the download work in the php file. So what I did:
In PHP: I echo the filename after the .txt file is created:
echo json_encode(array('filename' => "your_Data".time().".txt"));
Than in JavaScript I read the filename in the success method and put the link in an <a> element. Later that will be a button which is set active.
success : function(data) {
$("#button_get_file_download").attr("href", "urlToFolder"+data.filename);
},
I have an input field where I paste a download url.
After that, the I use an AJAX request to get the fileinfos such as headerinfo, content-length, mime type & in case I use curl accept-ranges.
I then start a consecutive loop of xhr2 requests with ranges to my php file.
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=0-1024
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=1024-2048
....
I can also change it to
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=0-1024
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=1025-2049
....
depending where my script starts to read the file.
My first approach was using cUrl & setting the ranges
<?php
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$_GET['url']);
curl_setopt($ch,CURLOPT_RANGE,$_GET['range']);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$result=curl_exec($ch);
curl_close($ch);
echo $result;
?>
works great but if the range chunks are bigger than 1mb there is no animation on the client side onprogress event using ajax.
i prolly could use a custom CURLOPT_READFUNCTION... but i don't know how that works... so i changed approach and used the simple fopen
<?php
$r=explode('-',$_GET['range']);//get (from to) ranges
$cc=($r[1]-$r[0]); //Calculate Client Chunk length
$sc=128; //Set the Server chunk length
$b=""; //Buffer
$bytes=0; //bytes read
$h=fopen($_GET['url'],"rb"); // open the url
fseek($h,$r[0]); // jump to the from pointer retrieved from links
while($bytes<$cc){ //while bytes read is smaller than my client chunk
$sc=(($bytes+$sc)>$cc?($cc-$bytes):$sc); //prolley an error here
//if the server chunk + bytes read is bigger than the client chunk
//then the server chunk is clinet chunk - bytes read
$b=fread($h,$sc); // read the buffer
$bytes+=strlen($b); //add the buffer length to bytes read
echo $b;// echo the buffer
ob_flush(); // flush
flush(); // flush
}
fclose($h); //close
?>
now this works ... I get the right animation on the client and also the final size is correct the pointers should be ok (0-1024,1024-2048) as I use fseek && fread.
but the file is corrupt.
Now after some tests ... this is very slow.
A better approach would be cUrl with CURLOPT_READFUNCTION or fsoket open...
so I guess:
<?php
function $READ(){
//here i need small chuncks of the response flushed.
}
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$_GET['url']);
curl_setopt($ch,CURLOPT_RANGE,$_GET['range']);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_READFUNCTION,$READ);
$result=curl_exec($ch);
curl_close($ch);
echo $result;
?>
If you have a better solution I'm open to everything that uses javascript and php.
The point of this is to create a download manager with resume that stores the file into the window.webkitRequestFileSystem without filling the memory of the browser.
Let's say the client has chunks of 8mb and the server chunks are 256kb ..
then every 8mb of the chunk is appended to a file previously created with window.webkitRequestFileSystem
and every 256kb I have an update of the average download speed and this way I can create a nice animation.
The php on the server uses only 256kb ram and the client browser can empty the garbage collection every 8mb (theoretically).
EDIT2
For this code I found a solution:
the code allows you to get ranges for example:0-100
and get the output of this 100bytes chunked !!
this allows you to have a AJAX script that has a continuos flawless PROGRESSBAR
<?php
function w($ch,$chunk){
echo $chunk;
ob_flush();
flush();
return strlen($chunk);
};
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$_GET['url']);
curl_setopt($ch,CURLOPT_RANGE,$_GET['range']);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_WRITEFUNCTION,w);
curl_exec($ch);
curl_close($ch);
?>
But I hope you guys have a better solution at all!! thanks
I could get it to work with PHP curl's CURLOPT_WRITEFUNCTION callback setting. The following example callback function curl_write_flush intended for that curl option writes every chunk received and flushes the output to the browser.
<?php
/**
* CURLOPT_WRITEFUNCTION which flushes the output buffer and the SAPI buffer.
*
* #param resource $curl_handle
* #param string $chunk
*/
function curl_write_flush($curl_handle, $chunk)
{
echo $chunk;
ob_flush(); // flush output buffer (Output Control configuration specific)
flush(); // flush output body (SAPI specific)
return strlen($chunk); // tell Curl there was output (if any).
};
$curl_handle = curl_init($_GET['url']);
curl_setopt($curl_handle, CURLOPT_RANGE, $_GET['range']);
curl_setopt($curl_handle, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, 'curl_write_flush');
curl_exec($curl_handle);
curl_close($curl_handle);
I tried with small files and big files and it works great but you can't set custom chunk size.
Download stream is the same speed as I can get with my ISP.
If you have anything better i'm open for any answer.
I have an unusual requirement. Essentially I need a way so that, when the user clicks on a link or button, they will receive a PDF. The tricky part here is that the server won't process the request at all unless a custom header is sent with it (otherwise it deems the person logged out and sends them to the login screen).
At the moment the way the header works cannot be changed so please don't dwell on it; it will get changed in the future and is an internal application that I have no control over.
The options I have explored:
Using an iframe or simply opening a new window with some sort of path that will return the PDF. This can't work because I cannot specify the required header for the PDF and would be redirected before reaching the PDF itself.
Using a form and submitting the request can't work because I can't
add any custom headers to forms (only XHR and plugins can, AFAIK).
Using XHR can't work because, while it can add the header and
retrieve the file, there is no way to save it on the client side.
It would appear my only options at this point are essentially:
Use some sort of plugin such as Flash or Silverlight to request the file.
Force the change of the requirement much earlier than expected so that a header is no longer required.
Is there anything I am missing here? I'm hoping someone can either verify my findings or point me to something I missed because, as far as I can tell, there isn't really anything I can do here.
EDIT: This seems apt and confirms what I was thinking: XMLHttpRequest to open PDF in browser
Tested to work in chrome:
function toBinaryString(data) {
var ret = [];
var len = data.length;
var byte;
for (var i = 0; i < len; i++) {
byte=( data.charCodeAt(i) & 0xFF )>>> 0;
ret.push( String.fromCharCode(byte) );
}
return ret.join('');
}
var xhr = new XMLHttpRequest;
xhr.open( "GET", "/test.pdf" ); //I had test.pdf this on my local server
xhr.addEventListener( "load", function(){
var data = toBinaryString(this.responseText);
data = "data:application/pdf;base64,"+btoa(data);
document.location = data;
}, false);
xhr.setRequestHeader("magic", "header" );
xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined;" );
xhr.send(null);
You can change application/pdf to application/octet-stream to have download prompt. But it's pretty easy to download from the chrome's reader as well.
In firefox nothing happens I guess it's because I don't have a plugin to deal with application/pdf installed. Changing to application/octet-stream will prompt a dl.
With IE I suppose you need some kind of VBScript/ActiveX hackery
If the file is huge, using data uri might crash the browser, in that case you can use BlobBuilder and Object URLs.
Instead of linking to the .PDF file, instead do something like
Download my eBook
which outputs a custom header, opens the PDF (binary safe) and prints the data to the user's browser, then they can choose to save the PDF despite their browser settings. The pdf_server.php should look like this:
header("Content-Type: application/octet-stream");
$file = $_GET["file"] .".pdf";
header("Content-Disposition: attachment; filename=" . urlencode($file));
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");
header("Content-Length: " . filesize($file));
flush(); // this doesn't really matter.
$fp = fopen($file, "r");
while (!feof($fp))
{
echo fread($fp, 65536);
flush(); // this is essential for large downloads
}
fclose($fp);
EDIT: The only way to add headers to a request from inside a browser (client-side) is use the XmlHttpRequest setRequestHeader method.
xhr.setRequestHeader('custom-header', 'value');