1.3.1.3. fejezet, File API (FileReader) - file feltöltés részletekben

Az AJAX push művelettel nagy méretű fájlokat részletekben tölthetünk fel. A böngésző cache túl kicsi egy 100 MiB-es fájl feltöltéséhez a FileReader-el. Emellett félbeszakítható vagy felfüggeszthető a feltöltés.

<!DOCTYPE html>
<html lang="hu-HU">
<head>
<meta charset="utf-8">
<script src="jquery-2.1.3.js"></script>
<style>
  #progress_bar {
    margin: 10px 0;
    padding: 3px;
    border: 1px solid #000;
    font-size: 14px;
    clear: both;
    opacity: 0;
    -moz-transition: opacity 1s linear;
    -o-transition: opacity 1s linear;
    -webkit-transition: opacity 1s linear;
  }
  #progress_bar.loading {
    opacity: 1.0;
  }
  #progress_bar .percent {
    background-color: #99ccff;
    height: auto;
    width: 0;
  }
</style>
</head>
<body>
<input type="file" id="files" name="file" />
<button onclick="abortRead();">Cancel read</button><br/>
<button id="commandPost" onclick="savePart(0,'myfile','base64','content');">Post</button>
<div id="progress_bar"><div class="percent">0%</div></div>
<script>
  var reader;
  var progress = document.querySelector('.percent');
  var buffsize = 2*1024*1024;
  var filename;
  var filetype;
 
  function abortRead() {
    reader.abort();
  }
 
  function errorHandler(evt) {
    switch(evt.target.error.code) {
      case evt.target.error.NOT_FOUND_ERR:
        alert('File Not Found!');
        break;
      case evt.target.error.NOT_READABLE_ERR:
        alert('File is not readable');
        break;
      case evt.target.error.ABORT_ERR:
        break; // noop
      default:
        alert('An error occurred reading this file.');
    };
  }
 
  function updateProgress(evt) {
    // evt is an ProgressEvent.
    if (evt.lengthComputable) {
      var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
      // Increase the progress bar length.
      if (percentLoaded < 100) {
        progress.style.width = percentLoaded + '%';
        progress.textContent = percentLoaded + '%';
      }
    }
  }
 
  function _arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
  }
 
  function savePart(partid,fname,ftype,sliceData) {
	console.log("partid:"+partid);
	console.log("filename:"+fname);
	var base64String = _arrayBufferToBase64(sliceData);
	$.ajax({
		url:  "/pushfile.php/",
		type: "post",			
		async: false,
		dataType: "text",
		cache: false,
		data: {	
			'contentType' : ftype,
			'partno' : partid,
			'fname' : fname,				
			'slice' : base64String
		},
		success: function(data, status) {
		},
		error: function(xhr, desc, err) {
		  console.log(xhr);
		  console.log("Details: " + desc + "\nError:" + err);
		}
	});
  }
 
  function handleFileSelect(evt) {
    // Reset progress indicator on new file selection.
    progress.style.width = '0%';
    progress.textContent = '0%';
 
	filename = evt.target.files[0].name;
	filetype = evt.target.files[0].type;
 
	var fsize = evt.target.files[0].size;
	var start = 0;
	var end = (start + buffsize < fsize ? buffsize : fsize);
 
	reader = new FileReader();
	reader.onerror = errorHandler;
	reader.onprogress = updateProgress;
	reader.onabort = function(e) {
	  alert('File read cancelled');
	};
	reader.onloadstart = function(e) {
	  document.getElementById('progress_bar').className = 'loading';
	};		
	reader.onload = function(e) {
		//setTimeout("savePart("+e.target.partno+",'"+filename+"','"+filetype+"','"+e.target.result+"')", 500);
		savePart(e.target.partno,filename,filetype,e.target.result);
	};
	reader.onloadend = function(e) {
	  if ((start<end) && (end<fsize)) {		
		start = end;
		end = start + buffsize < fsize ? start + buffsize : fsize;
		this.partno += 1;
		reader.readAsArrayBuffer(evt.target.files[0].slice(start,end));		
	  } else {
		  progress.style.width = '100%';
		  progress.textContent = '100%';
		  setTimeout("document.getElementById('progress_bar').className='';", 2000);
	  }
	};
 
	reader.partno = 0;
	reader.readAsArrayBuffer(evt.target.files[0].slice(start,end));
  }
 
document.getElementById('files').addEventListener('change', handleFileSelect, false);
 
</script>

Figyeljünk a buffer méretre, ne legyen nagyobb, mint a szerver által megengedett maximális POST mérete (php.ini - post_max_size). Ha mégis nagyobb buffer méretet alkalmazunk, a php $_POST változóba nem kerül adat (JQuery, php, $_POST, empty), így nincs mit feldolgoznia, leáll a cgi. Ilyenkor az error logban pl. ezt olvashatjuk:

PHP Warning:  Unknown: POST Content-Length of 14734420 bytes exceeds the limit of 8388608 bytes in Unknown on line 0

<?php
$fname 
mb_convert_encoding($_POST["fname"],"ISO-8859-2","UTF-8");
$partno $_POST["partno"];
$f fopen("c:\\temp\\parts\\$fname",($partno == "w" :"a"));
fwrite($fbase64_decode($_POST["slice"]));
fclose($f);
?>

Persze a részeket össze kell fűzni, de hogyan, ez már nem a File API leírás része. Jelen kódban a partno változótól függ a file létrehozás, vagy hozzáfűzés.

<?php
$f 
fopen("c:\\temp\\parts\\$fname",($partno == "w" :"a"));
?>

Hivatkozás az eredeti leírásra: itt