├── LICENSE
├── README.md
└── azure-blob-upload.js
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Stephen Brannan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-azure-blob-upload
2 | =========================
3 |
4 | AngularJS service for uploading to azure blob storage.
5 |
6 | Provides for the ability to upload an HTML5 File to Azure's Blob Storage. The file is uploaded in chunks to avoid memory issues as a BlockBlob. The upload uses a Shared Access Signature (SAS) to secure the file upload.
7 |
8 | Required dependencies
9 | -----------------------
10 | * [AngularJS] (http://www.angularjs.com)
11 |
12 | Add the Azure Blob Upload to your index.html file
13 | ```HTML
14 |
15 | ```
16 |
17 | After downloading the `azure-blob-upload.js` to your AngularJS project then
18 |
19 | Add `'azureBlobUpload'` to your main angular.module like so
20 | ```javascript
21 | angular.module('myapp', ['myApp.controllers', 'myApp.services', 'azureBlobUpload']);
22 | ````
23 |
24 | How to use
25 | -------------
26 | Add the azureBlob service as a dependency to your controller like so:
27 | ```javascript
28 | angular.module('myapp')
29 | .controller('MainCtrl', ['$scope', 'azureBlob', function ($scope, azureBlob) {
30 | ...
31 | }])
32 | ```
33 |
34 | This will expose the following method
35 |
36 | * azureBlob.upload(config)
37 |
38 | The config object has the following properties
39 |
40 | ```javascript
41 | {
42 | baseUrl: // baseUrl for blob file uri (i.e. http://.blob.core.windows.net//),
43 | sasToken: // Shared access signature querystring key/value prefixed with ?,
44 | file: // File object using the HTML5 File API,
45 | progress: // progress callback function,
46 | complete: // complete callback function,
47 | error: // error callback function,
48 | blockSize: // Use this to override the DefaultBlockSize,
49 | }
50 | ```
51 |
52 |
53 | CORS
54 | -------------
55 |
56 | Cross Origin Resource Sharing (CORS) must be enabled on the azure blob storage account. The following articles can assist with this...
57 |
58 | [Windows Azure Storage Introducing CORS](http://blogs.msdn.com/b/windowsazurestorage/archive/2014/02/03/windows-azure-storage-introducing-cors.aspx)
59 |
60 | [Windows Azure Storage and CORS](http://www.contentmaster.com/azure/windows-azure-storage-cors/)
61 |
62 |
63 | Special Thanks
64 | -------------
65 |
66 | Extreme thanks goes to Gaurav Mantri and his original work using plain JavaScript. Much of it comes from the blob post...
67 | (http://gauravmantri.com/2013/02/16/uploading-large-files-in-windows-azure-blob-storage-using-shared-access-signature-html-and-javascript). I took his original code from here and turned it into a re-usable angular service.
68 |
69 |
70 | Issues & Contribution
71 | -------------
72 |
73 | For questions, bug reports, and feature request please search through existing [issue](https://github.com/kinstephen/angular-azure-blob-upload/issues) and if you don't find and answer open a new one [here](https://github.com/kinstephen/angular-azure-blob-upload/issues/new). If you need support contact me on twitter at @kinstephen
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/azure-blob-upload.js:
--------------------------------------------------------------------------------
1 | /**!
2 | * AngularJS Azure blob upload service with http post and progress.
3 | * @author Stephen Brannan - twitter: @kinstephen
4 | * @version 1.0.1
5 | */
6 | (function () {
7 |
8 | var azureBlobUpload = angular.module('azureBlobUpload', []);
9 |
10 | azureBlobUpload.factory('azureBlob',
11 | ['$log', '$http', azureBlob]);
12 |
13 | function azureBlob($log, $http) {
14 |
15 | var DefaultBlockSize = 1024 * 32 // Default to 32KB
16 |
17 | /* config: {
18 | baseUrl: // baseUrl for blob file uri (i.e. http://.blob.core.windows.net//),
19 | sasToken: // Shared access signature querystring key/value prefixed with ?,
20 | file: // File object using the HTML5 File API,
21 | progress: // progress callback function,
22 | complete: // complete callback function,
23 | error: // error callback function,
24 | blockSize: // Use this to override the DefaultBlockSize
25 | } */
26 | var upload = function (config) {
27 | var state = initializeState(config);
28 |
29 | var reader = new FileReader();
30 | reader.onloadend = function (evt) {
31 | if (evt.target.readyState == FileReader.DONE && !state.cancelled) { // DONE == 2
32 | var uri = state.fileUrl + '&comp=block&blockid=' + state.blockIds[state.blockIds.length - 1];
33 | var requestData = new Uint8Array(evt.target.result);
34 |
35 | $log.log(uri);
36 | $http.put(uri, requestData,
37 | {
38 | headers: {
39 | 'x-ms-blob-type': 'BlockBlob',
40 | 'Content-Type': state.file.type,
41 | },
42 | transformRequest: [],
43 | }).then(function (response) {
44 | $log.log(response.data);
45 | $log.log(response.status);
46 | state.bytesUploaded += requestData.length;
47 |
48 | var percentComplete = ((parseFloat(state.bytesUploaded) / parseFloat(state.file.size)) * 100).toFixed(2);
49 | if (state.progress) state.progress(percentComplete, response.data, response.status, response.headers, response.config);
50 |
51 | uploadFileInBlocks(reader, state);
52 | }, function (response) {
53 | $log.log(response.data);
54 | $log.log(response.status);
55 |
56 | if (state.error) state.error(response.data, response.status, response.headers, response.config);
57 | });
58 | }
59 | };
60 |
61 | uploadFileInBlocks(reader, state);
62 |
63 | return {
64 | cancel: function () {
65 | state.cancelled = true;
66 | }
67 | };
68 | };
69 |
70 | var initializeState = function (config) {
71 | var blockSize = DefaultBlockSize;
72 | if (config.blockSize) blockSize = config.blockSize;
73 |
74 | var maxBlockSize = blockSize; // Default Block Size
75 | var numberOfBlocks = 1;
76 |
77 | var file = config.file;
78 |
79 | var fileSize = file.size;
80 | if (fileSize < blockSize) {
81 | maxBlockSize = fileSize;
82 | $log.log("max block size = " + maxBlockSize);
83 | }
84 |
85 | if (fileSize % maxBlockSize == 0) {
86 | numberOfBlocks = fileSize / maxBlockSize;
87 | } else {
88 | numberOfBlocks = parseInt(fileSize / maxBlockSize, 10) + 1;
89 | }
90 |
91 | $log.log("total blocks = " + numberOfBlocks);
92 |
93 | return {
94 | maxBlockSize: maxBlockSize, //Each file will be split in 256 KB.
95 | numberOfBlocks: numberOfBlocks,
96 | totalBytesRemaining: fileSize,
97 | currentFilePointer: 0,
98 | blockIds: new Array(),
99 | blockIdPrefix: 'block-',
100 | bytesUploaded: 0,
101 | submitUri: null,
102 | file: file,
103 | baseUrl: config.baseUrl,
104 | sasToken: config.sasToken,
105 | fileUrl: config.baseUrl + config.sasToken,
106 | progress: config.progress,
107 | complete: config.complete,
108 | error: config.error,
109 | cancelled: false
110 | };
111 | };
112 |
113 | var uploadFileInBlocks = function (reader, state) {
114 | if (!state.cancelled) {
115 | if (state.totalBytesRemaining > 0) {
116 | $log.log("current file pointer = " + state.currentFilePointer + " bytes read = " + state.maxBlockSize);
117 |
118 | var fileContent = state.file.slice(state.currentFilePointer, state.currentFilePointer + state.maxBlockSize);
119 | var blockId = state.blockIdPrefix + pad(state.blockIds.length, 6);
120 | $log.log("block id = " + blockId);
121 |
122 | state.blockIds.push(btoa(blockId));
123 | reader.readAsArrayBuffer(fileContent);
124 |
125 | state.currentFilePointer += state.maxBlockSize;
126 | state.totalBytesRemaining -= state.maxBlockSize;
127 | if (state.totalBytesRemaining < state.maxBlockSize) {
128 | state.maxBlockSize = state.totalBytesRemaining;
129 | }
130 | } else {
131 | commitBlockList(state);
132 | }
133 | }
134 | };
135 |
136 | var commitBlockList = function (state) {
137 | var uri = state.fileUrl + '&comp=blocklist';
138 | $log.log(uri);
139 |
140 | var requestBody = '';
141 | for (var i = 0; i < state.blockIds.length; i++) {
142 | requestBody += '' + state.blockIds[i] + '';
143 | }
144 | requestBody += '';
145 | $log.log(requestBody);
146 |
147 | $http.put(uri, requestBody,
148 | {
149 | headers: {
150 | 'x-ms-blob-content-type': state.file.type,
151 | }
152 | }).then(function (response) {
153 | $log.log(response.data);
154 | $log.log(response.status);
155 | if (state.complete) state.complete(response.data, response.status, response.headers, response.config);
156 | }, function (response) {
157 | $log.log(response.data);
158 | $log.log(response.status);
159 | if (state.error) state.error(response.data, response.status, response.headers, response.config);
160 | });
161 | };
162 |
163 | var pad = function (number, length) {
164 | var str = '' + number;
165 | while (str.length < length) {
166 | str = '0' + str;
167 | }
168 | return str;
169 | };
170 |
171 | return {
172 | upload: upload,
173 | };
174 | };
175 |
176 | })();
177 |
--------------------------------------------------------------------------------