Skip to content

Commit 243759c

Browse files
committed
Merge pull request #114 from Hybrid-Force/feature/blob-file-upload
Construct AV.FIle with blob and upload directly to Qiniu
2 parents e6f4460 + 2a2f730 commit 243759c

File tree

2 files changed

+210
-22
lines changed

2 files changed

+210
-22
lines changed

lib/file.js

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,9 @@
329329
this._metaData.size = data.length;
330330
} else if (data && data.base64) {
331331
this._source = AV.Promise.as(data.base64, guessedType);
332+
} else if (data && data.blob) {
333+
this._source = AV.Promise.as(data.blob, guessedType);
334+
this._isBlob = true;
332335
} else if (typeof(File) !== "undefined" && data instanceof File) {
333336
this._source = readAsync(data, type);
334337
} else if(AV._isNode && Buffer.isBuffer(data)) {
@@ -499,39 +502,72 @@
499502
return request._thenRunCallbacks(options);
500503
},
501504

505+
/**
506+
* Request Qiniu upload token
507+
* @param {string} type
508+
* @return {AV.Promise} Resolved with the response
509+
* @private
510+
*/
511+
_qiniuToken: function(type) {
512+
var self = this;
513+
//Create 16-bits uuid as qiniu key.
514+
var extName;
515+
if (AV._isNode) {
516+
var path = require('path');
517+
extName = path.extname(self._name);
518+
} else {
519+
var nameParts = self._name.split('.');
520+
extName = '.' + nameParts[nameParts.length - 1];
521+
}
522+
var hexOctet = function() {
523+
return Math.floor((1+Math.random())*0x10000).toString(16).substring(1);
524+
};
525+
var key = hexOctet() + hexOctet() + hexOctet() + hexOctet() + hexOctet()
526+
+ extName;
527+
528+
var data = {
529+
key: key,
530+
ACL: self._acl,
531+
name:self._name,
532+
mime_type: type,
533+
metaData: self._metaData
534+
};
535+
if(type && self._metaData.mime_type == null)
536+
self._metaData.mime_type = type;
537+
self._qiniu_key = key;
538+
return AV._request("qiniu", null, null, 'POST', data);
539+
},
540+
541+
/**
542+
* @callback UploadProgressCallback
543+
* @param {XMLHttpRequestProgressEvent} event - The progress event with 'loaded' and 'total' attributes
544+
*/
502545
/**
503546
* Saves the file to the AV cloud.
547+
* @param {Object} saveOptions
548+
* @param {UploadProgressCallback} [saveOptions.onProgress]
504549
* @param {Object} options A Backbone-style options object.
505550
* @return {AV.Promise} Promise that is resolved when the save finishes.
506551
*/
507-
save: function(options) {
552+
save: function() {
553+
var options = null;
554+
var saveOptions = {};
555+
if(arguments.length === 1) {
556+
options = arguments[0];
557+
} else if(arguments.length === 2) {
558+
saveOptions = arguments[0];
559+
options = arguments[1];
560+
}
508561
var self = this;
509562
if (!self._previousSave) {
510563
if(self._source){
511564
if(AV._isNode){
512-
//Use qiniu sdk to upload files to qiniu.
513-
var qiniu = require('qiniu');
514-
var path = require('path');
515565
self._previousSave = self._source.then(function(base64, type) {
516-
//Create 16-bits uuid as qiniu key.
517-
var hexOctet = function() {
518-
return Math.floor((1+Math.random())*0x10000).toString(16).substring(1);
519-
};
520-
var key = hexOctet() + hexOctet() + hexOctet() + hexOctet()
521-
+ path.extname(self._name);
522-
var data = {
523-
key: key,
524-
ACL: self._acl,
525-
name:self._name,
526-
mime_type: type,
527-
metaData: self._metaData,
528-
};
529-
if(type && self._metaData.mime_type == null)
530-
self._metaData.mime_type = type;
531-
self._qiniu_key = key;
532566
self._base64 = base64;
533-
return AV._request("qiniu", null, null, 'POST', data);
567+
return self._qiniuToken(type);
534568
}).then(function(response) {
569+
//Use qiniu sdk to upload files to qiniu.
570+
var qiniu = require('qiniu');
535571
self._url = response.url;
536572
self._bucket = response.bucket;
537573
self.id = response.objectId;
@@ -553,6 +589,66 @@
553589
self.destroy();
554590
}
555591
});
592+
return promise;
593+
});
594+
} else if (this._isBlob) {
595+
self._previousSave = self._source.then(function(blob, type) {
596+
self._blob = blob;
597+
return self._qiniuToken(type);
598+
}).then(function(response) {
599+
self._url = response.url;
600+
self._bucket = response.bucket;
601+
self.id = response.objectId;
602+
//Get the uptoken to upload files to qiniu.
603+
var uptoken = response.token;
604+
605+
var data = new FormData();
606+
data.append("file", self._blob, self._name);
607+
data.append("key", self._qiniu_key);
608+
data.append("token", uptoken);
609+
610+
var promise = new AV.Promise();
611+
var handled = false;
612+
613+
var xhr = new AV.XMLHttpRequest();
614+
615+
xhr.upload.addEventListener('progress', function(e) {
616+
if (e.lengthComputable) {
617+
saveOptions.onProgress && saveOptions.onProgress(e);
618+
}
619+
}, false);
620+
621+
xhr.onreadystatechange = function() {
622+
if (xhr.readyState === 4) {
623+
if (handled) {
624+
return;
625+
}
626+
handled = true;
627+
628+
delete self._qiniu_key;
629+
delete self._blob;
630+
if (xhr.status >= 200 && xhr.status < 300) {
631+
var response;
632+
try {
633+
response = JSON.parse(xhr.responseText);
634+
} catch (e) {
635+
promise.reject(e);
636+
self.destroy();
637+
}
638+
if (response) {
639+
promise.resolve(self);
640+
} else {
641+
promise.reject(response);
642+
}
643+
} else {
644+
promise.reject(xhr);
645+
self.destroy();
646+
}
647+
}
648+
};
649+
xhr.open('POST', 'http://upload.qiniu.com', true);
650+
xhr.send(data);
651+
556652
return promise;
557653
});
558654
} else {
@@ -563,7 +659,7 @@
563659
_ContentType: type,
564660
ACL: self._acl,
565661
mime_type: type,
566-
metaData: self._metaData,
662+
metaData: self._metaData
567663
};
568664
return AV._request("files", self._name, null, 'POST', data);
569665
}).then(function(response) {

test/file_blob.html

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<button onclick='upload ();'>upload</button>
2+
<progress id="progressBar" value="0"></progress>
3+
<html>
4+
<head>
5+
<title>Test</title>
6+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
9+
<script src="../node_modules/mocha/mocha.js"></script>
10+
<script src="../node_modules/expect.js/expect.js"></script>
11+
12+
<!--AV Test init-->
13+
14+
15+
<script type="text/javascript">var serverURL="http://stg.paas.com/";</script>
16+
<script src="../dist/av.js"></script>
17+
<script>
18+
var hasBlobConstructor = window.Blob && (function () {
19+
try {
20+
return Boolean(new Blob());
21+
} catch (e) {
22+
return false;
23+
}
24+
}()),
25+
hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
26+
(function () {
27+
try {
28+
return new Blob([new Uint8Array(100)]).size === 100;
29+
} catch (e) {
30+
return false;
31+
}
32+
}()),
33+
BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
34+
window.MozBlobBuilder || window.MSBlobBuilder,
35+
dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
36+
window.ArrayBuffer && window.Uint8Array && function (dataURI) {
37+
var byteString,
38+
arrayBuffer,
39+
intArray,
40+
i,
41+
mimeString,
42+
bb;
43+
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
44+
// Convert base64 to raw binary data held in a string:
45+
byteString = atob(dataURI.split(',')[1]);
46+
} else {
47+
// Convert base64/URLEncoded data component to raw binary data:
48+
byteString = decodeURIComponent(dataURI.split(',')[1]);
49+
}
50+
// Write the bytes of the string to an ArrayBuffer:
51+
arrayBuffer = new ArrayBuffer(byteString.length);
52+
intArray = new Uint8Array(arrayBuffer);
53+
for (i = 0; i < byteString.length; i += 1) {
54+
intArray[i] = byteString.charCodeAt(i);
55+
}
56+
// Separate out the mime component:
57+
mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
58+
// Write the ArrayBuffer (or ArrayBufferView) to a blob:
59+
if (hasBlobConstructor) {
60+
return new Blob(
61+
[hasArrayBufferViewSupport ? intArray : arrayBuffer],
62+
{type: mimeString}
63+
);
64+
}
65+
bb = new BlobBuilder();
66+
bb.append(arrayBuffer);
67+
return bb.getBlob(mimeString);
68+
};
69+
function upload (){
70+
AV.serverURL="https://cn-stg1.avoscloud.com";
71+
AV._initialize('mxrb5nn3qz7drek0etojy5lh4yrwjnk485lqajnsgjwfxrb5', 'd7sbus0d81mrum4tko4t8gl74b27vl0rh762ff7ngrb6ymmq', 'l0n9wu3kwnrtf2cg1b6w2l87nphzpypgff6240d0lxui2mm4');
72+
AV._useMasterKey = true;
73+
AV.setProduction(true);
74+
75+
var name = "photo.png";
76+
77+
var dataUrl = 'data:image/jpeg;base64,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';
78+
var blob = dataURLtoBlob(dataUrl);
79+
var avFile = new AV.File(name, {blob: blob});
80+
var progressBar = document.getElementById("progressBar");
81+
avFile.save({onProgress: function(e) {
82+
console.debug(e.loaded + '/' + e.total);
83+
progressBar.max = e.total;
84+
progressBar.value = e.loaded;
85+
}}, null).then(function() {
86+
console.info('File saved');
87+
console.dir(avFile);
88+
}, function(error) {
89+
console.error(error);
90+
});
91+
}
92+
</script>

0 commit comments

Comments
 (0)