condo 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,8 +32,8 @@ This has numerous advantages over traditional Form Data style post uploads too.
32
32
  * Resumability when uploading large files
33
33
 
34
34
 
35
- Support for all major browsers and IE10.
36
- * Tested in Firefox 4, Safari 6 and Chromes latest stable
35
+ Support for all major browsers
36
+ * Tested in Firefox 4, Safari 6, Chromes latest stable and IE10 (IE10 on Win7)
37
37
 
38
38
 
39
39
  h2. Usage
@@ -73,7 +73,8 @@ See the "example application":https://github.com/cotag/condo_example which imple
73
73
  If you are using "Condo Interface":https://github.com/cotag/condo_interface then you may want to do the following:
74
74
  # Create an index for your controller @def index; end@
75
75
  # Create an index.html.erb in your view with:
76
- #* @<div class="uploads-container" data-ng-app="CondoUploader"><%= render "condo_interface/upload" %></div>@
76
+ # Make sure your AngularJS app includes: @angular.module('YourApp', ['CondoUploader', 'CondoInterface']);@
77
+ #* @<div data-ng-app="YourApp"><%= render "condo_interface/upload" %></div>@
77
78
 
78
79
  Alternative you could load an AngularJS template linking to <%= asset_path('templates/_upload.html') %>
79
80
 
@@ -1,7 +1,5 @@
1
- //= require condo/spark-md5
1
+ //= require condo/abstract-md5
2
2
  //= require condo/base64
3
+ //= require condo/broadcaster
3
4
  //= require condo/uploader
4
- //= require condo/amazon
5
- //= require condo/rackspace
6
- //= require condo/google
7
5
  //= require condo/controller
@@ -0,0 +1,87 @@
1
+ (function (factory) {
2
+ if (typeof define === 'function' && define.amd) {
3
+ // AMD
4
+ define('condo-abstract-md5', ['jquery', 'condo-broadcaster'], factory);
5
+ } else {
6
+ // Browser global
7
+ factory(jQuery);
8
+ }
9
+ }(function ($, uploads, undefined) {
10
+ 'use strict';
11
+
12
+ angular.module('CondoAbstractMd5', ['CondoBroadcaster'])
13
+ .factory('Condo.Md5', ['$rootScope', '$q', 'Condo.Broadcast', function($rootScope, $q, broadcaster) {
14
+
15
+ var hasher,
16
+ queue = [],
17
+ ready = false,
18
+ processing = undefined,
19
+
20
+ //
21
+ // Resolves the hashing promise
22
+ //
23
+ recievedMessage = function(e) {
24
+ if(e.data.success) {
25
+ processing.result.resolve(e.data.result);
26
+ } else {
27
+ processing.result.reject(e.data.result);
28
+ }
29
+
30
+ processing = undefined;
31
+ processNext();
32
+
33
+ if(!$rootScope.$$phase) {
34
+ $rootScope.$apply(); // This triggers the promise response
35
+ }
36
+ },
37
+
38
+ //
39
+ // starts processing the next item if the queue is not empty
40
+ //
41
+ processNext = function() {
42
+ if(ready && processing === undefined && queue.length > 0) {
43
+ processing = queue.pop();
44
+ hasher.postMessage(processing.blob);
45
+ }
46
+ };
47
+
48
+
49
+ if(!!window.Worker) {
50
+ hasher = new Worker('<%= asset_path("condo/md5/hash.worker.js") %>');
51
+ hasher.onmessage = recievedMessage;
52
+ hasher.onerror = function(e) {
53
+ ready = false;
54
+ broadcaster.broadcast('coComponentLoadFailed', ['MD5', 'file fingerprinting']); // Critical error
55
+ };
56
+ ready = true;
57
+ } else {
58
+ $.getScript('<%= asset_path("condo/md5/hash.worker.emulator.js") %>', function() {
59
+ hasher = new CondoHashWorkerEmulator(recievedMessage);
60
+ ready = true;
61
+ processNext(); // It is possible
62
+ }).fail(function(jqxhr, settings, exception) {
63
+ broadcaster.broadcast('coComponentLoadFailed', ['MD5', 'file fingerprinting']);
64
+ });
65
+ }
66
+
67
+
68
+
69
+ return {
70
+ //
71
+ // Will queue a start message and return the hash result
72
+ //
73
+ hash: function(blob) {
74
+ var result = $q.defer();
75
+
76
+ queue.push({
77
+ blob: blob,
78
+ result: result
79
+ });
80
+ processNext();
81
+
82
+ return result.promise;
83
+ }
84
+ };
85
+ }]);
86
+ }));
87
+
@@ -18,20 +18,15 @@
18
18
  (function (factory) {
19
19
  if (typeof define === 'function' && define.amd) {
20
20
  // AMD
21
- define(['jquery', 'spark-md5', 'base64', 'condo_uploader'], factory);
21
+ define(['jquery', 'base64', 'condo-uploader'], factory);
22
22
  } else {
23
23
  // Browser globals
24
- factory(jQuery, window.SparkMD5, window.base64, window.CondoUploader);
24
+ factory(jQuery, window.base64);
25
25
  }
26
- }(function ($, MD5, base64, uploads, undefined) {
26
+ }(function ($, base64) {
27
27
  'use strict';
28
28
 
29
- //
30
- // TODO:: Create an Amazon, google factory etc
31
- // We should split all these into different files too (controller and factories separate from directives and views)
32
- // So we can have different views for the same controller
33
- //
34
- uploads.factory('Condo.AmazonS3', ['$rootScope', '$q', function($rootScope, $q) {
29
+ angular.module('CondoAmazonProvider', ['CondoUploader', 'CondoAbstractMd5']).run(['$q', 'Condo.Registrar', 'Condo.Md5', function($q, registrar, md5) {
35
30
  var PENDING = 0,
36
31
  STARTED = 1,
37
32
  PAUSED = 2,
@@ -82,16 +77,9 @@
82
77
  //
83
78
  // We need to sign our uploads so amazon can confirm they are valid for us
84
79
  // Part numbers can be any number from 1 to 10,000 - inclusive
85
- // TODO:: use http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast
86
- // where available :)
87
80
  //
88
81
  build_request = function(part_number) {
89
- var result = $q.defer(),
90
- reader = new FileReader(),
91
- fail = function(){
92
- result.reject('file read failed');
93
- },
94
- current_part;
82
+ var current_part;
95
83
 
96
84
  if (file.size > part_size) { // If file bigger then 5mb we expect a chunked upload
97
85
  var endbyte = part_number * part_size;
@@ -102,23 +90,15 @@
102
90
  current_part = file;
103
91
  }
104
92
 
105
- reader.onload = function(e) {
106
- result.resolve({
93
+ return md5.hash(current_part).then(function(val) {
94
+ return {
107
95
  data: current_part,
108
- data_id: MD5.hashBinary(e.target.result),
96
+ data_id: val,
109
97
  part_number: part_number
110
- });
111
-
112
-
113
- if(!$rootScope.$$phase) {
114
- $rootScope.$apply(); // This triggers the promise response if required
115
98
  }
116
- };
117
- reader.onerror = fail;
118
- reader.onabort = fail;
119
- reader.readAsBinaryString(current_part);
120
-
121
- return result.promise;
99
+ }, function(reason){
100
+ return $q.reject(reason);
101
+ });
122
102
  },
123
103
 
124
104
  //
@@ -412,12 +392,15 @@
412
392
  }; // END AMAZON
413
393
 
414
394
 
415
- return {
395
+ //
396
+ // Register the residence with the API
397
+ // Dependency injection succeeded
398
+ //
399
+ registrar.register('AmazonS3', {
416
400
  new_upload: function(api, file) {
417
401
  return new Amazon(api, file);
418
402
  }
419
- };
420
-
403
+ });
421
404
  }]);
422
405
 
423
406
  }));
@@ -0,0 +1,54 @@
1
+ /**
2
+ * CoTag Condo Amazon S3 Strategy
3
+ * Direct to cloud resumable uploads for Google Cloud Storage
4
+ *
5
+ * Copyright (c) 2012 CoTag Media.
6
+ *
7
+ * @author Stephen von Takach <steve@cotag.me>
8
+ * @copyright 2012 cotag.me
9
+ *
10
+ *
11
+ * References:
12
+ * * https://github.com/umdjs/umd
13
+ * * https://github.com/addyosmani/jquery-plugin-patterns
14
+ * *
15
+ *
16
+ **/
17
+
18
+ (function (factory) {
19
+ if (typeof define === 'function' && define.amd) {
20
+ // AMD
21
+ define('condo-broadcaster', factory);
22
+ } else {
23
+ // Browser globals
24
+ factory();
25
+ }
26
+ }(function (undefined) {
27
+ 'use strict';
28
+
29
+ angular.module('CondoBroadcaster', []).factory('Condo.Broadcast', ['$rootScope', function($rootScope) {
30
+ // eventBroadcaster is the object created by the factory method.
31
+ var eventBroadcaster = {};
32
+
33
+ // The message is a string or object to carry data with the event.
34
+ eventBroadcaster.message = '';
35
+
36
+ // The event name is a string used to define event types.
37
+ eventBroadcaster.eventName = '';
38
+
39
+ // This method is called from within a controller to define an event and attach data to the eventBroadcaster object.
40
+ eventBroadcaster.broadcast = function(evName, msg) {
41
+ this.message = msg;
42
+ this.eventName = evName;
43
+ this.broadcastItem();
44
+ };
45
+
46
+ // This method broadcasts an event with the specified name.
47
+ eventBroadcaster.broadcastItem = function() {
48
+ $rootScope.$broadcast(this.eventName);
49
+ };
50
+
51
+ return eventBroadcaster;
52
+ }]);
53
+
54
+ }));
@@ -19,7 +19,7 @@
19
19
  (function (factory) {
20
20
  if (typeof define === 'function' && define.amd) {
21
21
  // AMD
22
- define('condo_controller', ['jquery', 'condo_uploader'], factory);
22
+ define('condo-controller', ['jquery', 'condo-uploader'], factory);
23
23
  } else {
24
24
  // Browser globals
25
25
  window.CondoController = factory(jQuery, window.CondoUploader);
@@ -32,33 +32,7 @@
32
32
  //
33
33
  // Create a controller for managing the upload states
34
34
  //
35
- uploads.factory('Condo.Broadcast', ['$rootScope', function($rootScope) {
36
- // eventBroadcaster is the object created by the factory method.
37
- var eventBroadcaster = {};
38
-
39
- // The message is a string or object to carry data with the event.
40
- eventBroadcaster.message = '';
41
-
42
- // The event name is a string used to define event types.
43
- eventBroadcaster.eventName = '';
44
-
45
- // This method is called from within a controller to define an event and attach data to the eventBroadcaster object.
46
- eventBroadcaster.broadcast = function(evName, msg) {
47
- this.message = msg;
48
- this.eventName = evName;
49
- this.broadcastItem();
50
- };
51
-
52
- // This method broadcasts an event with the specified name.
53
- eventBroadcaster.broadcastItem = function() {
54
- $rootScope.$broadcast(this.eventName);
55
- };
56
-
57
- return eventBroadcaster;
58
-
59
-
60
-
61
- }]).controller('Condo.Controller', ['$scope', 'Condo.Api', 'Condo.Broadcast', function($scope, api, broadcaster) {
35
+ uploads.controller('Condo.Controller', ['$scope', 'Condo.Api', 'Condo.Broadcast', function($scope, api, broadcaster) {
62
36
 
63
37
  $scope.uploads = [];
64
38
  $scope.upload_count = 0;
@@ -18,20 +18,15 @@
18
18
  (function (factory) {
19
19
  if (typeof define === 'function' && define.amd) {
20
20
  // AMD
21
- define(['jquery', 'spark-md5', 'base64', 'condo_uploader'], factory);
21
+ define(['jquery', 'base64', 'condo-uploader'], factory);
22
22
  } else {
23
23
  // Browser globals
24
- factory(jQuery, window.SparkMD5, window.base64, window.CondoUploader);
24
+ factory(jQuery, window.base64);
25
25
  }
26
- }(function ($, MD5, base64, uploads, undefined) {
26
+ }(function ($, base64) {
27
27
  'use strict';
28
28
 
29
- //
30
- // TODO:: Create an Amazon, google factory etc
31
- // We should split all these into different files too (controller and factories separate from directives and views)
32
- // So we can have different views for the same controller
33
- //
34
- uploads.factory('Condo.GoogleCloudStorage', ['$rootScope', '$q', function($rootScope, $q) {
29
+ angular.module('CondoGoogleProvider', ['CondoUploader', 'CondoAbstractMd5']).run(['$q', 'Condo.Registrar', 'Condo.Md5', function($q, registrar, md5) {
35
30
  var PENDING = 0,
36
31
  STARTED = 1,
37
32
  PAUSED = 2,
@@ -81,57 +76,15 @@
81
76
 
82
77
  //
83
78
  // We need to sign our uploads so Google can confirm they are valid for us
84
- // TODO:: use http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast
85
- // where available :) - especially important since we have to hash the entire file
86
79
  //
87
- build_request = function(part_number, hash) {
88
- var result = $q.defer(),
89
- reader = new FileReader(),
90
- fail = function(){
91
- result.reject('file read failed');
92
- },
93
- current_part;
94
-
95
- if (part_number == 1) {
96
- hash = new MD5();
97
- }
98
-
99
- if (file.size > part_size) { // If file bigger then 5mb we expect a chunked upload
100
- var endbyte = part_number * part_size;
101
- if (endbyte > file.size)
102
- endbyte = file.size;
103
- current_part = file.slice((part_number - 1) * part_size, endbyte);
104
- } else {
105
- current_part = file;
106
- }
107
-
108
- reader.onload = function(e) {
109
- hash.appendBinary(e.target.result);
110
- result.resolve(hash);
111
-
112
-
113
- if(!$rootScope.$$phase) {
114
- $rootScope.$apply(); // This triggers the promise response if required
115
- }
116
- };
117
- reader.onerror = fail;
118
- reader.onabort = fail;
119
- reader.readAsBinaryString(current_part);
120
-
121
- //
122
- // Chaining promises means the UI will have a chance to update
123
- //
124
- return result.promise.then(function(val){
125
- if ((part_number * part_size) < file.size) {
126
- return build_request(part_number + 1, val);
127
- } else {
128
- return {
129
- data: file,
130
- data_id: base64.encode(hexToBin(val.end()))
131
- }
80
+ build_request = function() {
81
+ return md5.hash(file).then(function(val) {
82
+ return {
83
+ data: file,
84
+ data_id: base64.encode(hexToBin(val))
132
85
  }
133
86
  }, function(reason){
134
- $q.reject(reason);
87
+ return $q.reject(reason);
135
88
  });
136
89
  },
137
90
 
@@ -222,7 +175,7 @@
222
175
  this.state = STARTED;
223
176
  strategy = {}; // This function shouldn't be called twice so we need a state
224
177
 
225
- build_request(1).then(function(result) {
178
+ build_request().then(function(result) {
226
179
  if (self.state != STARTED)
227
180
  return; // upload was paused or aborted as we were reading the file
228
181
 
@@ -288,12 +241,15 @@
288
241
  }; // END GOOGLE
289
242
 
290
243
 
291
- return {
244
+ //
245
+ // Register the residence with the API
246
+ // Dependency injection succeeded
247
+ //
248
+ registrar.register('GoogleCloudStorage', {
292
249
  new_upload: function(api, file) {
293
250
  return new GoogleCloudStorage(api, file);
294
251
  }
295
- };
296
-
252
+ });
297
253
  }]);
298
254
 
299
255
  }));
@@ -0,0 +1,23 @@
1
+ //= require condo/md5/spark-md5
2
+ //= require condo/md5/hasher
3
+
4
+
5
+ function CondoHashWorkerEmulator(callback) {
6
+
7
+ // Create an API that looks like postMessage
8
+ this.postMessage = function (data, portArray) {
9
+ hasher.hash(data); // Clone the data if required JSON.parse(JSON.stringify(message)); // - Don't think it is required
10
+ }
11
+
12
+
13
+ this.terminate = function () {
14
+ // No special clean-up needed.
15
+ }
16
+
17
+ function messageEvtEmulator(rawMessage) {
18
+ callback({ data: rawMessage });
19
+ }
20
+
21
+ // Create an instance of downloader.
22
+ var hasher = new CondoMD5Hasher(messageEvtEmulator);
23
+ }
@@ -0,0 +1,11 @@
1
+ //= require condo/md5/spark-md5
2
+ //= require condo/md5/hasher
3
+
4
+
5
+ var hasher = new CondoMD5Hasher(postMessage); // Accepts the callback as the parameter
6
+
7
+
8
+ // Hook-up worker input
9
+ onmessage = function (e) {
10
+ hasher.hash(e.data);
11
+ }
@@ -0,0 +1,66 @@
1
+
2
+
3
+ var CondoMD5Hasher = (function(global) {
4
+
5
+ var part_size = 1048576; // This is the amount of the file we read into memory as we are building the hash (1mb)
6
+
7
+ return function(callback) {
8
+
9
+
10
+ //
11
+ // responds with: {success: true|false, result: <Object>}
12
+ //
13
+ this.hash = function(blob) {
14
+
15
+ var current_part,
16
+ md5 = new global.SparkMD5(),
17
+ reader = new FileReader(),
18
+ part_number = 0,
19
+ length = Math.ceil(blob.size / part_size),
20
+ fail = function() {
21
+ callback({
22
+ success: false,
23
+ result: 'file read failed'
24
+ });
25
+ },
26
+ hashData = function(e) {
27
+ md5.appendBinary(e.target.result);
28
+ if(part_number * part_size >= blob.size) {
29
+ callback({
30
+ success: true,
31
+ result: md5.end()
32
+ });
33
+ } else {
34
+ processPart();
35
+ }
36
+ },
37
+ processPart = function() {
38
+ var endbyte = 0;
39
+
40
+ part_number += 1;
41
+
42
+ if (blob.size > part_size) { // If blob bigger then part_size we will slice it up
43
+ endbyte = part_number * part_size;
44
+ if (endbyte > blob.size)
45
+ endbyte = blob.size;
46
+
47
+ current_part = blob.slice((part_number - 1) * part_size, endbyte);
48
+ } else {
49
+ current_part = blob;
50
+ }
51
+
52
+ reader.readAsArrayBuffer(current_part);
53
+ };
54
+
55
+
56
+ reader.onload = hashData;
57
+ reader.onerror = fail;
58
+ reader.onabort = fail;
59
+
60
+
61
+ processPart();
62
+ };
63
+ };
64
+
65
+ })(this);
66
+
@@ -1,4 +1,4 @@
1
- /*jslint bitwise: true, nomen: true */
1
+ /*jshint bitwise:false*/
2
2
  /*global unescape*/
3
3
 
4
4
  /**
@@ -45,7 +45,7 @@
45
45
  * document.getElementById("file").addEventListener("change", function() {
46
46
  *
47
47
  * var fileReader = new FileReader(),
48
- * blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice,
48
+ * blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
49
49
  * file = document.getElementById("file").files[0],
50
50
  * chunkSize = 2097152, // read in chunks of 2MB
51
51
  * chunks = Math.ceil(file.size / chunkSize),
@@ -76,24 +76,55 @@
76
76
  * loadNext();
77
77
  * });
78
78
  *
79
- * @TODO: Add support for byteArrays.
80
- * @TODO: Add support for HMAC.
81
79
  * @TODO: Add native support for reading files? Maybe add it as an extension?
82
80
  */
83
81
  (function (factory) {
84
- if (typeof exports === 'object') {
85
- // Node/CommonJS
86
- exports.SparkMD5 = factory();
87
- } else if (typeof define === 'function' && define.amd) {
82
+ if (typeof exports === 'object') {
83
+ // Node/CommonJS
84
+ module.exports = factory();
85
+ } else if (typeof define === 'function' && define.amd) {
88
86
  // AMD
89
87
  define('spark-md5', factory);
90
88
  } else {
91
- // Browser globals
92
- window.SparkMD5 = factory();
89
+ // Browser globals (with support for web workers)
90
+ var glob;
91
+ try {
92
+ glob = window;
93
+ } catch (e) {
94
+ glob = self;
95
+ }
96
+
97
+ glob.SparkMD5 = factory();
93
98
  }
94
99
  }(function (undefined) {
95
100
 
96
- "use strict";
101
+ 'use strict';
102
+
103
+
104
+ /* converts strings to array buffers
105
+ * From: http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
106
+ */
107
+ var str2ab = function (str) {
108
+ var buf = new ArrayBuffer(str.length * 2), // 2 bytes for each char
109
+ bufView = new Uint16Array(buf);
110
+ for (var i = 0, strLen = str.length; i < strLen; i++) {
111
+ bufView[i] = str.charCodeAt(i);
112
+ }
113
+ return buf;
114
+ },
115
+
116
+
117
+ abConcat = function(first, second)
118
+ {
119
+ var firstLength = first.length,
120
+ result = new Uint8Array(firstLength + second.byteLength);
121
+
122
+ result.set(first);
123
+ result.set(new Uint8Array(second), firstLength);
124
+
125
+ return result;
126
+ },
127
+
97
128
 
98
129
  ////////////////////////////////////////////////////////////////////////////
99
130
 
@@ -104,38 +135,33 @@
104
135
  * @see http://www.myersdaily.org/joseph/javascript/md5-text.html
105
136
  * @see http://jsperf.com/md5-shootout/7
106
137
  */
107
-
108
- /* this function is much faster,
109
- so if possible we use it. Some IEs
110
- are the only ones I know of that
111
- need the idiotic second function,
112
- generated by an if clause. */
113
- var add32 = function (a, b) {
138
+
139
+ add32 = function (a, b) {
114
140
  return (a + b) & 0xFFFFFFFF;
115
- };
141
+ },
116
142
 
117
- function cmn(q, a, b, x, s, t) {
143
+ cmn = function (q, a, b, x, s, t) {
118
144
  a = add32(add32(a, q), add32(x, t));
119
145
  return add32((a << s) | (a >>> (32 - s)), b);
120
- }
146
+ },
121
147
 
122
- function ff(a, b, c, d, x, s, t) {
148
+ ff = function(a, b, c, d, x, s, t) {
123
149
  return cmn((b & c) | ((~b) & d), a, b, x, s, t);
124
- }
150
+ },
125
151
 
126
- function gg(a, b, c, d, x, s, t) {
152
+ gg = function(a, b, c, d, x, s, t) {
127
153
  return cmn((b & d) | (c & (~d)), a, b, x, s, t);
128
- }
154
+ },
129
155
 
130
- function hh(a, b, c, d, x, s, t) {
156
+ hh = function(a, b, c, d, x, s, t) {
131
157
  return cmn(b ^ c ^ d, a, b, x, s, t);
132
- }
158
+ },
133
159
 
134
- function ii(a, b, c, d, x, s, t) {
160
+ ii = function(a, b, c, d, x, s, t) {
135
161
  return cmn(c ^ (b | (~d)), a, b, x, s, t);
136
- }
162
+ },
137
163
 
138
- function md5cycle(x, k) {
164
+ md5cycle = function(x, k) {
139
165
  var a = x[0],
140
166
  b = x[1],
141
167
  c = x[2],
@@ -213,47 +239,46 @@
213
239
  x[1] = add32(b, x[1]);
214
240
  x[2] = add32(c, x[2]);
215
241
  x[3] = add32(d, x[3]);
242
+ },
216
243
 
217
- }
218
-
219
- /* there needs to be support for Unicode here,
220
- * unless we pretend that we can redefine the MD-5
221
- * algorithm for multi-byte characters (perhaps
222
- * by adding every four 16-bit characters and
223
- * shortening the sum to 32 bits). Otherwise
224
- * I suggest performing MD-5 as if every character
225
- * was two bytes--e.g., 0040 0025 = @%--but then
226
- * how will an ordinary MD-5 sum be matched?
227
- * There is no way to standardize text to something
228
- * like UTF-8 before transformation; speed cost is
229
- * utterly prohibitive. The JavaScript standard
230
- * itself needs to look at this: it should start
231
- * providing access to strings as preformed UTF-8
232
- * 8-bit unsigned value arrays.
233
- */
234
- function md5blk(s) { /* I figured global was faster. */
244
+ md5blk = function(s) { /* I figured global was faster. */
235
245
  var md5blks = [],
236
246
  i; /* Andy King said do it this way. */
237
247
  for (i = 0; i < 64; i += 4) {
238
- md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
248
+ md5blks[i >> 2] = s[i] + (s[i + 1] << 8) + (s[i + 2] << 16) + (s[i + 3] << 24);
239
249
  }
240
250
  return md5blks;
241
- }
251
+ },
242
252
 
243
- function md51(s) {
253
+ md51 = function(s) {
244
254
  var n = s.length,
245
255
  state = [1732584193, -271733879, -1732584194, 271733878],
246
256
  i,
247
257
  length,
248
- tail;
258
+ tail,
259
+ tmp,
260
+ lo,
261
+ hi;
262
+
249
263
  for (i = 64; i <= n; i += 64) {
250
- md5cycle(state, md5blk(s.substring(i - 64, i)));
264
+ md5cycle(state, md5blk(s.subarray(i - 64, i)));
251
265
  }
252
- s = s.substring(i - 64);
266
+
267
+ //
268
+ // Not sure if it is a bug, however IE10 will always produce a sub array of length 1
269
+ // containing the last element of the parent array if the sub array specified starts
270
+ // beyond the length of the parent array - weird.
271
+ // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue
272
+ //
273
+ if((i - 64) < n)
274
+ s = s.subarray(i - 64);
275
+ else
276
+ s = new Uint8Array(0);
277
+
253
278
  length = s.length;
254
279
  tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
255
280
  for (i = 0; i < length; i += 1) {
256
- tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
281
+ tail[i >> 2] |= s[i] << ((i % 4) << 3);
257
282
  }
258
283
  tail[i >> 2] |= 0x80 << ((i % 4) << 3);
259
284
  if (i > 55) {
@@ -262,43 +287,42 @@
262
287
  tail[i] = 0;
263
288
  }
264
289
  }
265
- tail[14] = n * 8;
290
+
291
+ // Beware that the final length might not fit in 32 bits so we take care of that
292
+ tmp = n * 8;
293
+ tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
294
+ lo = parseInt(tmp[2], 16);
295
+ hi = parseInt(tmp[1], 16) || 0;
296
+
297
+ tail[14] = lo;
298
+ tail[15] = hi;
299
+
266
300
  md5cycle(state, tail);
267
301
  return state;
268
- }
302
+ },
269
303
 
270
- /*jslint vars: true*/
271
- var hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
272
- /*jslint vars: false*/
304
+ hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'],
273
305
 
274
- function rhex(n) {
306
+ rhex = function(n) {
275
307
  var s = '',
276
308
  j;
277
309
  for (j = 0; j < 4; j += 1) {
278
310
  s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
279
311
  }
280
312
  return s;
281
- }
313
+ },
282
314
 
283
- function hex(x) {
315
+ hex = function(x) {
284
316
  var i;
285
317
  for (i = 0; i < x.length; i += 1) {
286
318
  x[i] = rhex(x[i]);
287
319
  }
288
320
  return x.join('');
289
- }
321
+ },
290
322
 
291
- function md5(s) {
323
+ md5 = function(s) {
292
324
  return hex(md51(s));
293
- }
294
-
295
- if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') {
296
- add32 = function (x, y) {
297
- var lsw = (x & 0xFFFF) + (y & 0xFFFF),
298
- msw = (x >> 16) + (y >> 16) + (lsw >> 16);
299
- return (msw << 16) | (lsw & 0xFFFF);
300
- };
301
- }
325
+ },
302
326
 
303
327
  ////////////////////////////////////////////////////////////////////////////
304
328
 
@@ -308,29 +332,24 @@
308
332
  * Use this class to perform an incremental md5, otherwise use the
309
333
  * static methods instead.
310
334
  */
311
- /*jslint vars: true*/
312
- var SparkMD5 = function () {
313
- /*jslint vars: false*/
335
+ SparkMD5 = function () {
314
336
  // call reset to init the instance
315
337
  this.reset();
316
- };
338
+ };
339
+
317
340
 
318
341
  /**
319
342
  * Appends a string.
320
- * A conversion will be applied if an utf8 string is detected.
343
+ * Converts to a byte array.
321
344
  *
322
345
  * @param {String} str The string to be appended
323
346
  *
324
347
  * @return {SparkMD5} The instance itself
325
348
  */
326
349
  SparkMD5.prototype.append = function (str) {
327
- // converts the string to utf8 bytes if necessary
328
- if (/[\u0080-\uFFFF]/.test(str)) {
329
- str = unescape(encodeURIComponent(str));
330
- }
331
350
 
332
- // then append as binary
333
- this.appendBinary(str);
351
+ // convert to array then append as binary
352
+ this.appendBinary(str2ab(str));
334
353
 
335
354
  return this;
336
355
  };
@@ -338,38 +357,27 @@
338
357
  /**
339
358
  * Appends a binary string.
340
359
  *
341
- * @param {String} contents The binary string to be appended
360
+ * @param {ArrayBuffer} contents The binary string to be appended
342
361
  *
343
362
  * @return {SparkMD5} The instance itself
344
363
  */
345
364
  SparkMD5.prototype.appendBinary = function (contents) {
346
- // add to the buffer and increment string total length
347
- var offset = 64 - this._buff.length,
348
- sub = this._buff + contents.substr(0, offset),
349
- length = contents.length,
350
- total;
351
-
352
- this._length += length;
353
-
354
- if (sub.length >= 64) { // if there is 64 bytes accumulated
355
-
356
- md5cycle(this._state, md5blk(sub));
357
-
358
- total = contents.length - 64;
359
-
360
- // while we got bytes to process
361
- while (offset <= total) {
362
- sub = contents.substr(offset, 64);
363
- md5cycle(this._state, md5blk(sub));
364
- offset += 64;
365
- }
366
-
367
- this._buff = contents.substr(offset, 64);
368
-
369
- } else {
370
- this._buff = sub;
365
+ var buff = abConcat(this._buff, contents),
366
+ length = buff.length,
367
+ i;
368
+
369
+ this._length += contents.byteLength;
370
+
371
+ for (i = 64; i <= length; i += 64) {
372
+ md5cycle(this._state, md5blk(buff.subarray(i - 64, i)));
371
373
  }
372
374
 
375
+ // Avoids IE10 weirdness
376
+ if((i - 64) < length)
377
+ this._buff = buff.subarray(i - 64);
378
+ else
379
+ this._buff = new Uint8Array(0);
380
+
373
381
  return this;
374
382
  };
375
383
 
@@ -387,10 +395,13 @@
387
395
  length = buff.length,
388
396
  tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
389
397
  i,
390
- ret;
398
+ ret,
399
+ tmp,
400
+ lo,
401
+ hi;
391
402
 
392
403
  for (i = 0; i < length; i += 1) {
393
- tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);
404
+ tail[i >> 2] |= buff[i] << ((i % 4) << 3);
394
405
  }
395
406
  tail[i >> 2] |= 0x80 << ((i % 4) << 3);
396
407
  if (i > 55) {
@@ -399,7 +410,16 @@
399
410
  tail[i] = 0;
400
411
  }
401
412
  }
402
- tail[14] = this._length * 8;
413
+
414
+ // Do the final computation based on the tail and length
415
+ // Beware that the final length may not fit in 32 bits so we take care of that
416
+ tmp = this._length * 8;
417
+ tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
418
+ lo = parseInt(tmp[2], 16);
419
+ hi = parseInt(tmp[1], 16) || 0;
420
+
421
+ tail[14] = lo;
422
+ tail[15] = hi;
403
423
  md5cycle(this._state, tail);
404
424
 
405
425
  ret = !!raw ? this._state : hex(this._state);
@@ -415,7 +435,7 @@
415
435
  * @return {SparkMD5} The instance itself
416
436
  */
417
437
  SparkMD5.prototype.reset = function () {
418
- this._buff = "";
438
+ this._buff = new Uint8Array(0);
419
439
  this._length = 0;
420
440
  this._state = [1732584193, -271733879, -1732584194, 271733878];
421
441
 
@@ -433,8 +453,7 @@
433
453
  };
434
454
 
435
455
  /**
436
- * Performs the md5 hash on a string.
437
- * A conversion will be applied if utf8 string is detected.
456
+ * Performs the md5 hash on a string as utf16
438
457
  *
439
458
  * @param {String} str The string
440
459
  * @param {Boolean} raw True to get the raw result, false to get the hex result
@@ -442,26 +461,21 @@
442
461
  * @return {String|Array} The result
443
462
  */
444
463
  SparkMD5.hash = function (str, raw) {
445
- // converts the string to utf8 bytes if necessary
446
- if (/[\u0080-\uFFFF]/.test(str)) {
447
- str = unescape(encodeURIComponent(str));
448
- }
449
-
450
- var hash = md51(str);
464
+ var hash = md51(new Uint8Array(str2ab(str)));
451
465
 
452
466
  return !!raw ? hash : hex(hash);
453
467
  };
454
468
 
455
469
  /**
456
- * Performs the md5 hash on a binary string.
470
+ * Performs the md5 hash on a typed array.
457
471
  *
458
- * @param {String} content The binary string
472
+ * @param {ArrayBuffer} content The binary array
459
473
  * @param {Boolean} raw True to get the raw result, false to get the hex result
460
474
  *
461
475
  * @return {String|Array} The result
462
476
  */
463
477
  SparkMD5.hashBinary = function (content, raw) {
464
- var hash = md51(content);
478
+ var hash = md51(new Uint8Array(content));
465
479
 
466
480
  return !!raw ? hash : hex(hash);
467
481
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * CoTag Condo Rackspace Cloud Files Strategy
3
- * Direct to cloud resumable uploads for Amazon S3
3
+ * Direct to cloud resumable uploads for Rackspace Cloud Files
4
4
  *
5
5
  * Copyright (c) 2012 CoTag Media.
6
6
  *
@@ -18,20 +18,15 @@
18
18
  (function (factory) {
19
19
  if (typeof define === 'function' && define.amd) {
20
20
  // AMD
21
- define(['jquery', 'spark-md5', 'condo_uploader'], factory);
21
+ define(['jquery', 'condo-uploader'], factory);
22
22
  } else {
23
23
  // Browser globals
24
- factory(jQuery, window.SparkMD5, window.CondoUploader);
24
+ factory(jQuery);
25
25
  }
26
- }(function ($, MD5, uploads, undefined) {
26
+ }(function ($) {
27
27
  'use strict';
28
28
 
29
- //
30
- // TODO:: Create an Amazon, google factory etc
31
- // We should split all these into different files too (controller and factories separate from directives and views)
32
- // So we can have different views for the same controller
33
- //
34
- uploads.factory('Condo.RackspaceCloudFiles', ['$rootScope', '$q', function($rootScope, $q) {
29
+ angular.module('CondoRackspaceProvider', ['CondoUploader', 'CondoAbstractMd5']).run(['$q', 'Condo.Registrar', 'Condo.Md5', function($q, registrar, md5) {
35
30
  var PENDING = 0,
36
31
  STARTED = 1,
37
32
  PAUSED = 2,
@@ -65,16 +60,9 @@
65
60
 
66
61
  //
67
62
  // We need to sign our uploads so rackspace can confirm they are valid for us
68
- // TODO:: use http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast
69
- // where available :)
70
63
  //
71
64
  build_request = function(part_number) {
72
- var result = $q.defer(),
73
- reader = new FileReader(),
74
- fail = function(){
75
- result.reject('file read failed');
76
- },
77
- current_part;
65
+ var current_part;
78
66
 
79
67
  if (file.size > part_size) { // If file bigger then 5mb we expect a chunked upload
80
68
  var endbyte = part_number * part_size;
@@ -85,23 +73,15 @@
85
73
  current_part = file;
86
74
  }
87
75
 
88
- reader.onload = function(e) {
89
- result.resolve({
76
+ return md5.hash(current_part).then(function(val) {
77
+ return {
90
78
  data: current_part,
91
- data_id: MD5.hashBinary(e.target.result),
79
+ data_id: val,
92
80
  part_number: part_number
93
- });
94
-
95
-
96
- if(!$rootScope.$$phase) {
97
- $rootScope.$apply(); // This triggers the promise response if required
98
81
  }
99
- };
100
- reader.onerror = fail;
101
- reader.onabort = fail;
102
- reader.readAsBinaryString(current_part);
103
-
104
- return result.promise;
82
+ }, function(reason){
83
+ return $q.reject(reason);
84
+ });
105
85
  },
106
86
 
107
87
  //
@@ -332,15 +312,18 @@
332
312
  this.message = reason;
333
313
  }
334
314
  };
335
- }; // END AMAZON
315
+ }; // END RACKSPACE
336
316
 
337
317
 
338
- return {
318
+ //
319
+ // Register the residence with the API
320
+ // Dependency injection succeeded
321
+ //
322
+ registrar.register('RackspaceCloudFiles', {
339
323
  new_upload: function(api, file) {
340
324
  return new Rackspace(api, file);
341
325
  }
342
- };
343
-
326
+ });
344
327
  }]);
345
328
 
346
329
  }));
@@ -19,29 +19,25 @@
19
19
  (function (factory) {
20
20
  if (typeof define === 'function' && define.amd) {
21
21
  // AMD
22
- define('condo_uploader', ['jquery'], factory);
22
+ define('condo-uploader', ['jquery', 'condo-broadcaster'], factory);
23
23
  } else {
24
24
  // Browser globals
25
25
  window.CondoUploader = factory(jQuery);
26
26
  }
27
- }(function ($, undefined) {
27
+ }(function ($) {
28
28
  'use strict';
29
29
 
30
- var uploads = angular.module('CondoUploader', []);
30
+ var uploads = angular.module('CondoUploader', ['CondoBroadcaster']),
31
+ residencies = {};
31
32
 
32
33
 
33
34
  //
34
35
  // Implements the Condo API
35
36
  //
36
- uploads.factory('Condo.Api', ['$http', '$rootScope', '$q', 'Condo.AmazonS3', 'Condo.RackspaceCloudFiles', 'Condo.GoogleCloudStorage', function($http, $rootScope, $q, AmazonS3Condo, RackspaceFilesCondo, GoogleStorageCondo) {
37
+ uploads.factory('Condo.Api', ['$http', '$rootScope', '$q', function($http, $rootScope, $q) {
37
38
 
38
39
 
39
40
  var token = $('meta[name="csrf-token"]').attr('content'),
40
- residencies = {
41
- AmazonS3: AmazonS3Condo,
42
- RackspaceCloudFiles: RackspaceFilesCondo,
43
- GoogleCloudStorage: GoogleStorageCondo
44
- },
45
41
 
46
42
 
47
43
  condoConnection = function(api_endpoint, params) {
@@ -289,7 +285,16 @@
289
285
  });
290
286
  }
291
287
  };
292
- }]);
288
+ }]).factory('Condo.Registrar', function(){
289
+ return {
290
+ //
291
+ // Simple dependency injection allows us to load only the providers we need
292
+ //
293
+ register: function(provider_name, iface) {
294
+ residencies[provider_name] = iface;
295
+ }
296
+ };
297
+ });
293
298
 
294
299
 
295
300
 
@@ -1,3 +1,3 @@
1
1
  module Condo
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: condo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-08 00:00:00.000000000 Z
12
+ date: 2012-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -67,12 +67,17 @@ executables: []
67
67
  extensions: []
68
68
  extra_rdoc_files: []
69
69
  files:
70
+ - app/assets/javascripts/condo/abstract-md5.js.erb
70
71
  - app/assets/javascripts/condo/amazon.js
71
72
  - app/assets/javascripts/condo/base64.js
73
+ - app/assets/javascripts/condo/broadcaster.js
72
74
  - app/assets/javascripts/condo/controller.js
73
75
  - app/assets/javascripts/condo/google.js
76
+ - app/assets/javascripts/condo/md5/hash.worker.emulator.js
77
+ - app/assets/javascripts/condo/md5/hash.worker.js
78
+ - app/assets/javascripts/condo/md5/hasher.js
79
+ - app/assets/javascripts/condo/md5/spark-md5.js
74
80
  - app/assets/javascripts/condo/rackspace.js
75
- - app/assets/javascripts/condo/spark-md5.js
76
81
  - app/assets/javascripts/condo/uploader.js
77
82
  - app/assets/javascripts/condo.js
78
83
  - lib/condo/configuration.rb