add firefogg.jsm, this should move back to firefogg later

This commit is contained in:
j 2010-08-13 18:55:28 +02:00
parent edc69304e0
commit 166268a30c

299
OxFF/modules/firefogg.jsm Normal file
View file

@ -0,0 +1,299 @@
// -*- coding: utf-8 -*-
// vi:si:et:sw=2:sts=2:ts=2
let EXPORTED_SYMBOLS = [ "FirefoggUploader" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
function FirefoggUploader(parent, path, url, data) {
this._parent = parent;
this._path = path;
this._url = url;
this._chunkUrl = false;
this._data = data;
this._status = 'initiated';
this.encodingStatus = 'encoding';
this.progress = 0.0;
this.chunkSize = 1024*1024; //1MB, is there a need to make this variable?
this.chunksUploaded = 0;
this.chunkStatus = {};
this._done_cb = function(upload) {};
this._onProgress = function(progress) {};
this.filename = 'video.ogv';
this.canceled = false;
this._retries = 0;
this._maxRetry = -1;
this.ready = false;
}
/*
Internal helper object dealing with chunked upload as defined at
http://firefogg.org/dev/chunk_post.html
*/
FirefoggUploader.prototype = {
start: function() {
var boundary = "--------XX" + Math.random();
this._status = 'requesting chunk upload';
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
//XMLHttpRequest status callbacks
var upload = this;
function transferComplete(evt) {
var response = {};
upload.responseText = evt.target.responseText;
try {
response = JSON.parse(evt.target.responseText);
//dump(evt.target.responseText);
} catch(e) {
dump('FAILED to parse response:\n\n');
dump(evt.target.responseText);
response = {};
}
if (response.maxRetry) {
upload._maxRetry = response.maxRetry;
}
upload._chunkUrl = response.uploadUrl;
if (upload._chunkUrl) {
//dump('now tracking chunk uploads to ' + upload._chunkUrl + '\n');
upload._status = 'uploading';
upload._progress = 0.0;
upload._uploadChunkId = 0;
upload.uploadChunk(0, false);
} else {
upload._status = 'upload failed, no upload url provided';
upload._progress = -1;
upload._parent.cancel();
return;
}
}
function transferFailed(evt) {
upload._status = 'uplaod failed';
upload._progress = -1;
upload.responseText = evt.target.responseText;
//dump(evt.target.responseText);
}
req.addEventListener("load", transferComplete, false);
req.addEventListener("error", transferFailed, false);
req.open("POST", this._url);
var formData = "";
for (key in this._data) {
formData += "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" + this._data[key] + "\r\n";
}
formData += "--" + boundary + "--\r\n";
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var formStream = converter.convertToInputStream(formData);
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
req.setRequestHeader("Content-length", formStream.available());
req.send(formStream);
},
onProgress: function(cb) {
this._onProgress = cb;
},
done: function(done_cb) {
//used by Firefogg to indicate that encoding is done
//provies callback that is called once upload is done too
this.encodingStatus = 'done';
this._done_cb = done_cb;
},
uploadChunk: function(chunkId, last) {
/*
upload chunk with given chunkId, last indicated if this is the last chunk.
once upload is done next chunk is queued
*/
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(this._path);
this.filename = file.leafName;
this.filename = this.filename.replace(/\(\.\d+\).ogv/, '.ogv');
this.filename = this.filename.replace(/\(\.\d+\).webm/, '.webm');
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
fileStream.init(file, -1, -1, false);
fileStream.QueryInterface(Ci.nsISeekableStream);
var f = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
f.setInputStream(fileStream);
var bytesAvailable = fileStream.available();
var chunk = false;
var chunkOffset = chunkId * this.chunkSize;
this.progress = parseFloat(chunkOffset)/bytesAvailable;
if (last) {
//last chunk, read add remaining data
fileStream.seek(fileStream.NS_SEEK_SET, chunkOffset);
chunk = f.readBytes(bytesAvailable-chunkOffset);
}
else if (this.ready && bytesAvailable >= chunkOffset + this.chunkSize) {
//read next chunk
fileStream.seek(fileStream.NS_SEEK_SET, chunkOffset);
chunk = f.readBytes(this.chunkSize);
} else {
if (this.encodingStatus == 'done' && this._status == 'uploading') {
//uploading is done and last chunk is < chunkSize, upload remaining data
this._status = 'success';
this.uploadChunk(chunkId, true);
} else {
//encoding not ready. wait for 2 seconds and try again
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
function(obj, chunkId, last) {
return function() { obj.uploadChunk(chunkId, last) }
}(this, chunkId, last),
2000, Ci.nsITimer.TYPE_ONE_SHOT);
}
f.close();
fileStream.close();
return;
}
f.close();
fileStream.close();
//FIXME: should this be checked so it does not get called again?
this.chunkStatus[chunkId] = {};
//dump('POST ' + this._chunkUrl + ' uploading chunk ' + chunkId +' ('+chunk.length+' bytes)\n');
var boundary = "--------XX" + Math.random();
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
var upload = this;
function transferComplete(evt) {
//if server resonds with valid {result=1} and status was is still 'uploading' go on to next chunk
upload.responseText = evt.target.responseText;
try {
var response = JSON.parse(evt.target.responseText);
} catch(e) {
dump('FAILED to parse response:\n\n');
dump(evt.target.responseText);
var response = {};
}
/*
if (upload.canceled) {
//FIXME: should we let the server know that upload was canceled?
}
else */
/*
dump('\n\n');
dump(evt.target.responseText);
dump('\n************\n\n');
*/
if (response.done == 1) {
//upload finished, update state and expose result
upload.resultUrl = response.resultUrl;
upload.progress = 1;
if (upload._done_cb)
upload._done_cb(upload);
//reset retry counter
upload._retries = 0;
}
else if (response.result == 1) {
//start uploading next chunk
upload._uploadChunkId = chunkId + 1;
upload.uploadChunk(upload._uploadChunkId, false);
//upload status
upload.chunkStatus[chunkId].progress = 1;
upload.chunkStatus[chunkId].loaded = evt.loaded;
upload.chunksUploaded++;
//reset retry counter
upload._retries = 0;
} else {
//failed to upload, try again in 3 second
//dump('could not parse response, failed to upload, will try again in 3 second\n');
//dump(evt.target.responseText);
if (upload.max_retry > 0 && upload._retries > upload.max_retry) {
upload.cancel();
upload._status = 'uplaod failed';
upload._progress = -1;
} else {
upload._retries++;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
function(obj, chunkId, last) {
return function() { obj.uploadChunk(chunkId, last); };
}(upload, chunkId, last), 3000, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
}
function transferFailed(evt) {
//failed to upload, try again in 3 second
//dump('transferFailed, will try again in 3 second\n');
if (upload.max_retry > 0 && upload._retries > upload.max_retry) {
upload.cancel();
upload._status = 'uplaod failed';
upload._progress = -1;
} else {
upload._retries++;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(
function(obj, chunkId, last) {
return function() { obj.uploadChunk(chunkId, last); };
}(upload, chunkId, last), 3000, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
function updateProgress(evt) {
if (evt.lengthComputable) {
upload.progress = parseFloat(chunkOffset + evt.loaded)/bytesAvailable;
upload.chunkStatus[chunkId].loaded = evt.loaded;
upload.chunkStatus[chunkId].loaded = evt.total;
//dump('progress: chunk ' + chunkId + ' uploaded ' + evt.loaded + '\n');
upload._onProgress(upload.progress);
}
}
req.upload.addEventListener("progress", updateProgress, false);
req.addEventListener("load", transferComplete, false);
req.addEventListener("error", transferFailed, false);
req.open("POST", this._chunkUrl);
var chunk = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"chunk\"; filename=\"" + this.filename + "\"\r\n" +
"Content-type: video/ogg\r\n\r\n" + chunk;
var chunkStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
chunkStream.setData(chunk, chunk.length);
// write done flag into stream
var formData = "\r\n";
var _data = {
chunkId: chunkId
};
if (last) {
_data['done'] = 1;
}
for (key in _data) {
formData += "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" + _data[key] + "\r\n";
}
formData += "--" + boundary + "--\r\n";
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var formStream = converter.convertToInputStream(formData);
var multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"].createInstance(Ci.nsIMultiplexInputStream);
multiStream.appendStream(chunkStream);
multiStream.appendStream(formStream);
//send mupltiplrex stream
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
req.setRequestHeader("Content-length", multiStream.available());
//this._current_req = req;
req.send(multiStream);
},
cancel: function() {
this.canceled = true;
if (this._current_req) {
this._current_req.abort();
this._current_req = null;
}
},
}