Запись файла в чанках с использованием Ajax и FileReader

Я работаю над созданием загрузчика файлов, где содержимое будет записываться на удаленный сервер порциями с использованием стороннего API. API обеспечивает WriteFileChunk() метод, который принимает 3 параметра, путь к целевому файлу, начальную позицию (Int64) и данные (строку) байтов.

Каждый раз, когда FileReader получает порцию максимального поддерживаемого размера (16 КБ), мне нужно использовать Ajax, чтобы передать это в файл PHP и записать его с помощью API. Я подозреваю, что это должно быть сделано в onprogress Событие FileReader, однако я в некоторой растерянности, учитывая, что я не могу найти подобные примеры.

Как лучше всего реализовать это с помощью FileReader, обеспечив загрузку каждого чанка перед записью следующего? Если onprogress лучший выбор, как я могу получить текущие данные чанка?

$(document).ready(function()
{
function uploadFile()
{
var files = document.getElementById('file').files;

if (!files.length)
{
alert('Please select a file!');
return;
}

var file = files[0];
var first_byte = 0;
var last_byte = file.size - 1;

// File Reader
var reader = new FileReader();
reader.onerror = function(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;
default:
alert('An error occurred reading this file.');
};
};
reader.onprogress = function(evt)
{
if (evt.lengthComputable)
{
var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
console.log(percentLoaded + "%");

if (percentLoaded < 100)
{
$("#upload_progress").progressbar('value', percentLoaded);
}
}
};
reader.onabort = function(evt)
{
alert('File Upload Cancelled');
};
reader.onloadstart = function(evt)
{
$("#upload_progress").progressbar({
value: 0,
max: 100
});
};
reader.onload = function(evt)
{
$("#upload_progress").progressbar('value', 100);
};
reader.onloadend = function(evt)
{
if (evt.target.readyState == FileReader.DONE) // DONE == 2
{
alert("Upload Complete!");
//console.log(evt.target.result);
}
};

var blob = file.slice(first_byte, last_byte + 1);
reader.readAsBinaryString(blob);
}

fileupload_dialog = $( "#dialog-fileupload" ).dialog(
{
autoOpen: false,
height: 175,
width: 350,
modal: true,
buttons:
{
"Upload File": uploadFile
},
close: function()
{
form[ 0 ].reset();
}
});

form = fileupload_dialog.find( "form" ).on( "submit", function( event )
{
event.preventDefault();
uploadFile();
});

$("#file_upload a").click(function()
{
event.preventDefault();
fileupload_dialog.dialog( "open" );
});
});

1

Решение

Основная проблема здесь заключается в том, что FileReader необходимо будет прочитать весь файл в память, прежде чем он вернет нам любые полезные данные через result свойство, означающее, что вы не можете получить фрагменты из файла во время чтения файла (и событие прогресса не предоставит / не укажет на какие-либо данные):

Это свойство [result] действителен только после операции чтения
полный […]

Источник

Поскольку весь файл загружается в память, на самом деле не было бы никакой пользы в том, чтобы разделить процесс чтения (если бы это было возможно), кроме уменьшения небольшого отставания, возможно, между двумя процессами.

Я бы предложил следующий подход, основанный на вышеизложенном:

  • Загрузить весь файл в память, но как ArrayBuffer
  • Рассчитать количество необходимых сегментов (Math.ceil(fileLength/chunkSize))
  • Создать чанк, используя Uint8Array вид для ArrayBuffer используя аргументы смещения и длины куска
  • Отправьте чанк, дождитесь ответа асинхронно, переходите к следующему чанку, пока длина не станет равной <= 0 байт.

При необходимости чанк можно преобразовать в BLOB-объект перед его отправкой:

var chunkBlob = new Blob([chunk], {type: "application/octet-stream"});

Пример процесса

Пример псевдо-сервера, ожидающий произвольно 100 мс между каждым блоком 16 КБ:

file.onchange = function(e) {
var fr = new FileReader();
fr.onprogress = function(e) {progress.value = e.loaded / e.total};
fr.onload = startUpload.bind(fr);
progress.style.display = "inline-block";
fr.readAsArrayBuffer(e.target.files[0]);
}

// Main upload code
function startUpload() {

// calculate sizes
var chunkSize = 16<<10;
var buffer = this.result;
var fileSize = buffer.byteLength;
var segments = Math.ceil(fileSize / chunkSize);
var count = 0;
progress.value = 0;

// start "upload"(function upload() {
var segSize = Math.min(chunkSize, fileSize - count * chunkSize);
if (segSize > 0) {
var chunk = new Uint8Array(buffer, count++ * chunkSize, segSize); // get a chunk
progress.value = count / segments;
// send chunk to server (here pseudo cycle for demo purpose)
setTimeout(upload, 100); // when upload OK, call function again for the next block
}
else {
alert("Done");
progress.style.display = "none";
}
})()
}
body {font:16px sans-serif;margin:20px 0 0 20px}
<label>Select any file: <input type=file id="file"></label><br>
<progress id="progress" value=0 max=1 style="display:none" />
0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]