Merge branch 'release/1.0.6' into production

This commit is contained in:
Ryan Graham 2014-11-27 13:30:37 -08:00
commit ed974929a4
33 changed files with 1450 additions and 124 deletions

72
CHANGES.md Normal file
View File

@ -0,0 +1,72 @@
2014-11-27, Version 1.0.6
=========================
* Remove expired credentials (Ryan Graham)
* Update storage-service.js (Tony Sukiennik)
* Update index.js (Tony Sukiennik)
* Allow the Sample to easily changed providers (tonysoft)
* GetFiles issues (tonysoft)
* Add contribution guidelines (Ryan Graham)
* Put link to official documentation (Rand McKinney)
* Add loopback 2.0 example (Raymond Feng)
* Clarify the need for app.model(container) (Brian Dunnette)
2014-07-01, Version 1.0.5
=========================
* Bump version (Raymond Feng)
* Update README (Raymond Feng)
* Update module name (Raymond Feng)
2014-06-26, Version 1.0.4
=========================
* Bump version (Raymond Feng)
* Fix amazon s3 container handling (Raymond Feng)
2014-06-16, Version 1.0.3
=========================
* Bump version (Raymond Feng)
* Fix issue-15 (Raymond Feng)
2014-05-30, Version 1.0.2
=========================
* Add formidable as dependency (cgole)
* Update docs.json (Rand McKinney)
* Doc upload and download functions (Rand McKinney)
* Bump version (Raymond Feng)
* Update jsdocs (Raymond Feng)
* Clean up JS Doc (crandmck)
* Add param info to constructor JSDoc (crandmck)
* Work on JSDoc comments (crandmck)
2014-04-04, Version 1.0.0
=========================
* First release!

151
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,151 @@
### Contributing ###
Thank you for your interest in `loopback-component-storage`, an open source project
administered by StrongLoop.
Contributing to `loopback-component-storage` is easy. In a few simple steps:
* Ensure that your effort is aligned with the project's roadmap by
talking to the maintainers, especially if you are going to spend a
lot of time on it.
* Make something better or fix a bug.
* Adhere to code style outlined in the [Google C++ Style Guide][] and
[Google Javascript Style Guide][].
* Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-component-storage)
* Submit a pull request through Github.
### Contributor License Agreement ###
```
Individual Contributor License Agreement
By signing this Individual Contributor License Agreement
("Agreement"), and making a Contribution (as defined below) to
StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and
agree to the following terms and conditions for Your present and
future Contributions submitted to StrongLoop. Except for the license
granted in this Agreement to StrongLoop and recipients of software
distributed by StrongLoop, You reserve all right, title, and interest
in and to Your Contributions.
1. Definitions
"You" or "Your" shall mean the copyright owner or the individual
authorized by the copyright owner that is entering into this
Agreement with StrongLoop.
"Contribution" shall mean any original work of authorship,
including any modifications or additions to an existing work, that
is intentionally submitted by You to StrongLoop for inclusion in,
or documentation of, any of the products owned or managed by
StrongLoop ("Work"). For purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication
sent to StrongLoop or its representatives, including but not
limited to communication or electronic mailing lists, source code
control systems, and issue tracking systems that are managed by,
or on behalf of, StrongLoop for the purpose of discussing and
improving the Work, but excluding communication that is
conspicuously marked or otherwise designated in writing by You as
"Not a Contribution."
2. You Grant a Copyright License to StrongLoop
Subject to the terms and conditions of this Agreement, You hereby
grant to StrongLoop and recipients of software distributed by
StrongLoop, a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable copyright license to reproduce, prepare
derivative works of, publicly display, publicly perform,
sublicense, and distribute Your Contributions and such derivative
works under any license and without any restrictions.
3. You Grant a Patent License to StrongLoop
Subject to the terms and conditions of this Agreement, You hereby
grant to StrongLoop and to recipients of software distributed by
StrongLoop a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this Section)
patent license to make, have made, use, offer to sell, sell,
import, and otherwise transfer the Work under any license and
without any restrictions. The patent license You grant to
StrongLoop under this Section applies only to those patent claims
licensable by You that are necessarily infringed by Your
Contributions(s) alone or by combination of Your Contributions(s)
with the Work to which such Contribution(s) was submitted. If any
entity institutes a patent litigation against You or any other
entity (including a cross-claim or counterclaim in a lawsuit)
alleging that Your Contribution, or the Work to which You have
contributed, constitutes direct or contributory patent
infringement, any patent licenses granted to that entity under
this Agreement for that Contribution or Work shall terminate as
of the date such litigation is filed.
4. You Have the Right to Grant Licenses to StrongLoop
You represent that You are legally entitled to grant the licenses
in this Agreement.
If Your employer(s) has rights to intellectual property that You
create, You represent that You have received permission to make
the Contributions on behalf of that employer, that Your employer
has waived such rights for Your Contributions, or that Your
employer has executed a separate Corporate Contributor License
Agreement with StrongLoop.
5. The Contributions Are Your Original Work
You represent that each of Your Contributions are Your original
works of authorship (see Section 8 (Submissions on Behalf of
Others) for submission on behalf of others). You represent that to
Your knowledge, no other person claims, or has the right to claim,
any right in any intellectual property right related to Your
Contributions.
You also represent that You are not legally obligated, whether by
entering into an agreement or otherwise, in any way that conflicts
with the terms of this Agreement.
You represent that Your Contribution submissions include complete
details of any third-party license or other restriction (including,
but not limited to, related patents and trademarks) of which You
are personally aware and which are associated with any part of
Your Contributions.
6. You Don't Have an Obligation to Provide Support for Your Contributions
You are not expected to provide support for Your Contributions,
except to the extent You desire to provide support. You may provide
support for free, for a fee, or not at all.
6. No Warranties or Conditions
StrongLoop acknowledges that unless required by applicable law or
agreed to in writing, You provide Your Contributions on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR
FITNESS FOR A PARTICULAR PURPOSE.
7. Submission on Behalf of Others
If You wish to submit work that is not Your original creation, You
may submit it to StrongLoop separately from any Contribution,
identifying the complete details of its source and of any license
or other restriction (including, but not limited to, related
patents, trademarks, and license agreements) of which You are
personally aware, and conspicuously marking the work as
"Submitted on Behalf of a Third-Party: [named here]".
8. Agree to Notify of Change of Circumstances
You agree to notify StrongLoop of any facts or circumstances of
which You become aware that would make these representations
inaccurate in any respect. Email us at callback@strongloop.com.
```
[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml

124
README.md
View File

@ -12,126 +12,4 @@ storage services including:
- Openstack
- Azure
The binary artifacts are organized with containers and files. A container is the
collection of files. Each file will belong to a container.
## Define a model with the loopback-component-storage connector
LoopBack exposes the APIs using a model that is attached to a data source configured
with the loopback-component-storage connector.
var ds = loopback.createDataSource({
connector: require('loopback-component-storage'),
provider: 'filesystem',
root: path.join(__dirname, 'storage')
});
var container = ds.createModel('container');
The following methods are mixed into the model class:
- getContainers(cb): List all containers
- createContainer(options, cb): Create a new container
- destroyContainer(container, cb): Destroy an existing container
- getContainer(container, cb): Look up a container by name
- uploadStream(container, file, options, cb): Get the stream for uploading
- downloadStream(container, file, options, cb): Get the stream for downloading
- getFiles(container, download, cb): List all files within the given container
- getFile(container, file, cb): Look up a file by name within the given container
- removeFile(container, file, cb): Remove a file by name within the given container
- upload(req, res, cb): Handle the file upload at the server side
- download(container, file, res, cb): Handle the file download at the server side
## Configure the storage providers
Each storage provider takes different settings; these details about each specific
provider can be found below:
* Local File System
{
provider: 'filesystem',
root: '/tmp/storage'
}
* Amazon
{
provider: 'amazon',
key: '...',
keyId: '...'
}
* Rackspace
{
provider: 'rackspace',
username: '...',
apiKey: '...'
}
* OpenStack
{
provider: 'openstack',
username: 'your-user-name',
password: 'your-password',
authUrl: 'https://your-identity-service'
}
* Azure
{
provider: 'azure',
storageAccount: "test-storage-account", // Name of your storage account
storageAccessKey: "test-storage-access-key" // Access key for storage account
}
## REST APIs
- GET /api/containers
List all containers
- GET /api/containers/:container
Get information about a container by name
- POST /api/containers
Create a new container
- DELETE /api/containers/:container
Delete an existing container by name
- GET /api/containers/:container/files
List all files within a given container by name
- GET /api/containers/:container/files/:file
Get information for a file within a given container by name
- DELETE /api/containers/:container/files/:file
Delete a file within a given container by name
- POST /api/containers/:container/upload
Upload one or more files into the given container by name. The request body should
use [multipart/form-data](https://www.ietf.org/rfc/rfc2388.txt) which the file input
type for HTML uses.
- GET /api/containers/:container/download/:file
Download a file within a given container by name
> Please see the [Storage Service Documenation](http://docs.strongloop.com/display/LB/Storage+service).

13
example-2.0/.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -0,0 +1,2 @@
/client/
/node_modules/

21
example-2.0/.jshintrc Normal file
View File

@ -0,0 +1,21 @@
{
"node": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"eqeqeq": true,
"eqnull": true,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"newcap": true,
"nonew": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": false,
"trailing": true,
"sub": true,
"maxlen": 80
}

16
example-2.0/.npmignore Normal file
View File

@ -0,0 +1,16 @@
.idea
.project
*.sublime-*
.DS_Store
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.swp
*.swo
node_modules
coverage
*.tgz
*.xml

View File

@ -0,0 +1,3 @@
## Client
This is the place for your application front-end files.

View File

@ -0,0 +1,685 @@
/*
Angular File Upload v0.3.3.1
https://github.com/nervgh/angular-file-upload
*/
(function(angular, factory) {
if (typeof define === 'function' && define.amd) {
define('angular-file-upload', ['angular'], function(angular) {
return factory(angular);
});
} else {
return factory(angular);
}
}(angular || null, function(angular) {
var app = angular.module('angularFileUpload', []);
// It is attached to an element that catches the event drop file
app.directive('ngFileDrop', [ '$fileUploader', function ($fileUploader) {
'use strict';
return {
// don't use drag-n-drop files in IE9, because not File API support
link: !$fileUploader.isHTML5 ? angular.noop : function (scope, element, attributes) {
element
.bind('drop', function (event) {
var dataTransfer = event.dataTransfer ?
event.dataTransfer :
event.originalEvent.dataTransfer; // jQuery fix;
if (!dataTransfer) return;
event.preventDefault();
event.stopPropagation();
scope.$broadcast('file:removeoverclass');
scope.$emit('file:add', dataTransfer.files, scope.$eval(attributes.ngFileDrop));
})
.bind('dragover', function (event) {
var dataTransfer = event.dataTransfer ?
event.dataTransfer :
event.originalEvent.dataTransfer; // jQuery fix;
event.preventDefault();
event.stopPropagation();
dataTransfer.dropEffect = 'copy';
scope.$broadcast('file:addoverclass');
})
.bind('dragleave', function () {
scope.$broadcast('file:removeoverclass');
});
}
};
}])
// It is attached to an element which will be assigned to a class "ng-file-over" or ng-file-over="className"
app.directive('ngFileOver', function () {
'use strict';
return {
link: function (scope, element, attributes) {
scope.$on('file:addoverclass', function () {
element.addClass(attributes.ngFileOver || 'ng-file-over');
});
scope.$on('file:removeoverclass', function () {
element.removeClass(attributes.ngFileOver || 'ng-file-over');
});
}
};
});
// It is attached to <input type="file"> element like <ng-file-select="options">
app.directive('ngFileSelect', [ '$fileUploader', function ($fileUploader) {
'use strict';
return {
link: function (scope, element, attributes) {
$fileUploader.isHTML5 || element.removeAttr('multiple');
element.bind('change', function () {
scope.$emit('file:add', $fileUploader.isHTML5 ? this.files : this, scope.$eval(attributes.ngFileSelect));
($fileUploader.isHTML5 && element.attr('multiple')) && element.prop('value', null);
});
element.prop('value', null); // FF fix
}
};
}]);
app.factory('$fileUploader', [ '$compile', '$rootScope', '$http', '$window', function ($compile, $rootScope, $http, $window) {
'use strict';
/**
* Creates a uploader
* @param {Object} params
* @constructor
*/
function Uploader(params) {
angular.extend(this, {
scope: $rootScope,
url: '/',
alias: 'file',
queue: [],
headers: {},
progress: null,
autoUpload: false,
removeAfterUpload: false,
method: 'POST',
filters: [],
formData: [],
isUploading: false,
_nextIndex: 0,
_timestamp: Date.now()
}, params);
// add the base filter
this.filters.unshift(this._filter);
this.scope.$on('file:add', function (event, items, options) {
event.stopPropagation();
this.addToQueue(items, options);
}.bind(this));
this.bind('beforeupload', Item.prototype._beforeupload);
this.bind('in:progress', Item.prototype._progress);
this.bind('in:success', Item.prototype._success);
this.bind('in:cancel', Item.prototype._cancel);
this.bind('in:error', Item.prototype._error);
this.bind('in:complete', Item.prototype._complete);
this.bind('in:progress', this._progress);
this.bind('in:complete', this._complete);
}
Uploader.prototype = {
/**
* Link to the constructor
*/
constructor: Uploader,
/**
* The base filter. If returns "true" an item will be added to the queue
* @param {File|Input} item
* @returns {boolean}
* @private
*/
_filter: function (item) {
return angular.isElement(item) ? true : !!item.size;
},
/**
* Registers a event handler
* @param {String} event
* @param {Function} handler
* @return {Function} unsubscribe function
*/
bind: function (event, handler) {
return this.scope.$on(this._timestamp + ':' + event, handler.bind(this));
},
/**
* Triggers events
* @param {String} event
* @param {...*} [some]
*/
trigger: function (event, some) {
arguments[ 0 ] = this._timestamp + ':' + event;
this.scope.$broadcast.apply(this.scope, arguments);
},
/**
* Checks a support the html5 uploader
* @returns {Boolean}
* @readonly
*/
isHTML5: !!($window.File && $window.FormData),
/**
* Adds items to the queue
* @param {FileList|File|HTMLInputElement} items
* @param {Object} [options]
*/
addToQueue: function (items, options) {
var length = this.queue.length;
var list = 'length' in items ? items : [items];
angular.forEach(list, function (file) {
// check a [File|HTMLInputElement]
var isValid = !this.filters.length ? true : this.filters.every(function (filter) {
return filter.call(this, file);
}, this);
// create new item
var item = new Item(angular.extend({
url: this.url,
alias: this.alias,
headers: angular.copy(this.headers),
formData: angular.copy(this.formData),
removeAfterUpload: this.removeAfterUpload,
method: this.method,
uploader: this,
file: file
}, options));
if (isValid) {
this.queue.push(item);
this.trigger('afteraddingfile', item);
} else {
this.trigger('whenaddingfilefailed', item);
}
}, this);
if (this.queue.length !== length) {
this.trigger('afteraddingall', this.queue);
this.progress = this._getTotalProgress();
}
this._render();
this.autoUpload && this.uploadAll();
},
/**
* Remove items from the queue. Remove last: index = -1
* @param {Item|Number} value
*/
removeFromQueue: function (value) {
var index = this.getIndexOfItem(value);
var item = this.queue[ index ];
item.isUploading && item.cancel();
this.queue.splice(index, 1);
item._destroy();
this.progress = this._getTotalProgress();
},
/**
* Clears the queue
*/
clearQueue: function () {
this.queue.forEach(function (item) {
item.isUploading && item.cancel();
item._destroy();
}, this);
this.queue.length = 0;
this.progress = 0;
},
/**
* Returns a index of item from the queue
* @param {Item|Number} value
* @returns {Number}
*/
getIndexOfItem: function (value) {
return angular.isObject(value) ? this.queue.indexOf(value) : value;
},
/**
* Returns not uploaded items
* @returns {Array}
*/
getNotUploadedItems: function () {
return this.queue.filter(function (item) {
return !item.isUploaded;
});
},
/**
* Returns items ready for upload
* @returns {Array}
*/
getReadyItems: function() {
return this.queue
.filter(function(item) {
return item.isReady && !item.isUploading;
})
.sort(function(item1, item2) {
return item1.index - item2.index;
});
},
/**
* Uploads a item from the queue
* @param {Item|Number} value
*/
uploadItem: function (value) {
var index = this.getIndexOfItem(value);
var item = this.queue[ index ];
var transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport';
item.index = item.index || this._nextIndex++;
item.isReady = true;
if (this.isUploading) {
return;
}
this.isUploading = true;
this[ transport ](item);
},
/**
* Cancels uploading of item from the queue
* @param {Item|Number} value
*/
cancelItem: function(value) {
var index = this.getIndexOfItem(value);
var item = this.queue[ index ];
var prop = this.isHTML5 ? '_xhr' : '_form';
item[prop] && item[prop].abort();
},
/**
* Uploads all not uploaded items of queue
*/
uploadAll: function () {
var items = this.getNotUploadedItems().filter(function(item) {
return !item.isUploading;
});
items.forEach(function(item) {
item.index = item.index || this._nextIndex++;
item.isReady = true;
}, this);
items.length && this.uploadItem(items[ 0 ]);
},
/**
* Cancels all uploads
*/
cancelAll: function() {
this.getNotUploadedItems().forEach(function(item) {
this.cancelItem(item);
}, this);
},
/**
* Updates angular scope
* @private
*/
_render: function() {
this.scope.$$phase || this.scope.$digest();
},
/**
* Returns the total progress
* @param {Number} [value]
* @returns {Number}
* @private
*/
_getTotalProgress: function (value) {
if (this.removeAfterUpload) {
return value || 0;
}
var notUploaded = this.getNotUploadedItems().length;
var uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length;
var ratio = 100 / this.queue.length;
var current = (value || 0) * ratio / 100;
return Math.round(uploaded * ratio + current);
},
/**
* The 'in:progress' handler
* @private
*/
_progress: function (event, item, progress) {
var result = this._getTotalProgress(progress);
this.trigger('progressall', result);
this.progress = result;
this._render();
},
/**
* The 'in:complete' handler
* @private
*/
_complete: function () {
var item = this.getReadyItems()[ 0 ];
this.isUploading = false;
if (angular.isDefined(item)) {
this.uploadItem(item);
return;
}
this.trigger('completeall', this.queue);
this.progress = this._getTotalProgress();
this._render();
},
/**
* The XMLHttpRequest transport
* @private
*/
_xhrTransport: function (item) {
var xhr = item._xhr = new XMLHttpRequest();
var form = new FormData();
var that = this;
this.trigger('beforeupload', item);
item.formData.forEach(function(obj) {
angular.forEach(obj, function(value, key) {
form.append(key, value);
});
});
form.append(item.alias, item.file);
xhr.upload.onprogress = function (event) {
var progress = event.lengthComputable ? event.loaded * 100 / event.total : 0;
that.trigger('in:progress', item, Math.round(progress));
};
xhr.onload = function () {
var response = that._transformResponse(xhr.response);
var event = that._isSuccessCode(xhr.status) ? 'success' : 'error';
that.trigger('in:' + event, xhr, item, response);
that.trigger('in:complete', xhr, item, response);
};
xhr.onerror = function () {
that.trigger('in:error', xhr, item);
that.trigger('in:complete', xhr, item);
};
xhr.onabort = function () {
that.trigger('in:cancel', xhr, item);
that.trigger('in:complete', xhr, item);
};
xhr.open(item.method, item.url, true);
angular.forEach(item.headers, function (value, name) {
xhr.setRequestHeader(name, value);
});
xhr.send(form);
},
/**
* The IFrame transport
* @private
*/
_iframeTransport: function (item) {
var form = angular.element('<form style="display: none;" />');
var iframe = angular.element('<iframe name="iframeTransport' + Date.now() + '">');
var input = item._input;
var that = this;
item._form && item._form.replaceWith(input); // remove old form
item._form = form; // save link to new form
this.trigger('beforeupload', item);
input.prop('name', item.alias);
item.formData.forEach(function(obj) {
angular.forEach(obj, function(value, key) {
form.append(angular.element('<input type="hidden" name="' + key + '" value="' + value + '" />'));
});
});
form.prop({
action: item.url,
method: item.method,
target: iframe.prop('name'),
enctype: 'multipart/form-data',
encoding: 'multipart/form-data' // old IE
});
iframe.bind('load', function () {
// fixed angular.contents() for iframes
var html = iframe[0].contentDocument.body.innerHTML;
var xhr = { response: html, status: 200, dummy: true };
var response = that._transformResponse(xhr.response);
that.trigger('in:success', xhr, item, response);
that.trigger('in:complete', xhr, item, response);
});
form.abort = function() {
var xhr = { status: 0, dummy: true };
iframe.unbind('load').prop('src', 'javascript:false;');
form.replaceWith(input);
that.trigger('in:cancel', xhr, item);
that.trigger('in:complete', xhr, item);
};
input.after(form);
form.append(input).append(iframe);
form[ 0 ].submit();
},
/**
* Checks whether upload successful
* @param {Number} status
* @returns {Boolean}
* @private
*/
_isSuccessCode: function(status) {
return (status >= 200 && status < 300) || status === 304;
},
/**
* Transforms the server response
* @param {*} response
* @returns {*}
* @private
*/
_transformResponse: function (response) {
$http.defaults.transformResponse.forEach(function (transformFn) {
response = transformFn(response);
});
return response;
}
};
/**
* Create a item
* @param {Object} params
* @constructor
*/
function Item(params) {
// fix for old browsers
if (!Uploader.prototype.isHTML5) {
var input = angular.element(params.file);
var clone = $compile(input.clone())(params.uploader.scope);
var value = input.val();
params.file = {
lastModifiedDate: null,
size: null,
type: 'like/' + value.slice(value.lastIndexOf('.') + 1).toLowerCase(),
name: value.slice(value.lastIndexOf('/') + value.lastIndexOf('\\') + 2)
};
params._input = input;
clone.prop('value', null); // FF fix
input.css('display', 'none').after(clone); // remove jquery dependency
}
angular.extend(this, {
isReady: false,
isUploading: false,
isUploaded: false,
isSuccess: false,
isCancel: false,
isError: false,
progress: null,
index: null
}, params);
}
Item.prototype = {
/**
* Link to the constructor
*/
constructor: Item,
/**
* Removes a item
*/
remove: function () {
this.uploader.removeFromQueue(this);
},
/**
* Uploads a item
*/
upload: function () {
this.uploader.uploadItem(this);
},
/**
* Cancels uploading
*/
cancel: function() {
this.uploader.cancelItem(this);
},
/**
* Destroys form and input
* @private
*/
_destroy: function() {
this._form && this._form.remove();
this._input && this._input.remove();
delete this._form;
delete this._input;
},
/**
* The 'beforeupload' handler
* @param {Object} event
* @param {Item} item
* @private
*/
_beforeupload: function (event, item) {
item.isReady = true;
item.isUploading = true;
item.isUploaded = false;
item.isSuccess = false;
item.isCancel = false;
item.isError = false;
item.progress = 0;
},
/**
* The 'in:progress' handler
* @param {Object} event
* @param {Item} item
* @param {Number} progress
* @private
*/
_progress: function (event, item, progress) {
item.progress = progress;
item.uploader.trigger('progress', item, progress);
},
/**
* The 'in:success' handler
* @param {Object} event
* @param {XMLHttpRequest} xhr
* @param {Item} item
* @param {*} response
* @private
*/
_success: function (event, xhr, item, response) {
item.isReady = false;
item.isUploading = false;
item.isUploaded = true;
item.isSuccess = true;
item.isCancel = false;
item.isError = false;
item.progress = 100;
item.index = null;
item.uploader.trigger('success', xhr, item, response);
},
/**
* The 'in:cancel' handler
* @param {Object} event
* @param {XMLHttpRequest} xhr
* @param {Item} item
* @private
*/
_cancel: function(event, xhr, item) {
item.isReady = false;
item.isUploading = false;
item.isUploaded = false;
item.isSuccess = false;
item.isCancel = true;
item.isError = false;
item.progress = 0;
item.index = null;
item.uploader.trigger('cancel', xhr, item);
},
/**
* The 'in:error' handler
* @param {Object} event
* @param {XMLHttpRequest} xhr
* @param {Item} item
* @param {*} response
* @private
*/
_error: function (event, xhr, item, response) {
item.isReady = false;
item.isUploading = false;
item.isUploaded = true;
item.isSuccess = false;
item.isCancel = false;
item.isError = true;
item.progress = 100;
item.index = null;
item.uploader.trigger('error', xhr, item, response);
},
/**
* The 'in:complete' handler
* @param {Object} event
* @param {XMLHttpRequest} xhr
* @param {Item} item
* @param {*} response
* @private
*/
_complete: function (event, xhr, item, response) {
item.uploader.trigger('complete', xhr, item, response);
item.removeAfterUpload && item.remove();
}
};
return {
create: function (params) {
return new Uploader(params);
},
isHTML5: Uploader.prototype.isHTML5
};
}])
return app;
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,97 @@
angular.module('app', ['angularFileUpload'])
// The example of the full functionality
.controller('TestController',function ($scope, $fileUploader) {
'use strict';
// create a uploader with options
var uploader = $scope.uploader = $fileUploader.create({
scope: $scope, // to automatically update the html. Default: $rootScope
url: '/api/containers/container1/upload',
formData: [
{ key: 'value' }
],
filters: [
function (item) { // first user filter
console.info('filter1');
return true;
}
]
});
// ADDING FILTERS
uploader.filters.push(function (item) { // second user filter
console.info('filter2');
return true;
});
// REGISTER HANDLERS
uploader.bind('afteraddingfile', function (event, item) {
console.info('After adding a file', item);
});
uploader.bind('whenaddingfilefailed', function (event, item) {
console.info('When adding a file failed', item);
});
uploader.bind('afteraddingall', function (event, items) {
console.info('After adding all files', items);
});
uploader.bind('beforeupload', function (event, item) {
console.info('Before upload', item);
});
uploader.bind('progress', function (event, item, progress) {
console.info('Progress: ' + progress, item);
});
uploader.bind('success', function (event, xhr, item, response) {
console.info('Success', xhr, item, response);
$scope.$broadcast('uploadCompleted', item);
});
uploader.bind('cancel', function (event, xhr, item) {
console.info('Cancel', xhr, item);
});
uploader.bind('error', function (event, xhr, item, response) {
console.info('Error', xhr, item, response);
});
uploader.bind('complete', function (event, xhr, item, response) {
console.info('Complete', xhr, item, response);
});
uploader.bind('progressall', function (event, progress) {
console.info('Total progress: ' + progress);
});
uploader.bind('completeall', function (event, items) {
console.info('Complete all', items);
});
}
).controller('FilesController', function ($scope, $http) {
$scope.load = function () {
$http.get('/api/containers/container1/files').success(function (data) {
console.log(data);
$scope.files = data;
});
};
$scope.delete = function (index, id) {
$http.delete('/api/containers/container1/files/' + encodeURIComponent(id)).success(function (data, status, headers) {
$scope.files.splice(index, 1);
});
};
$scope.$on('uploadCompleted', function(event) {
console.log('uploadCompleted event received');
$scope.load();
});
});

View File

@ -0,0 +1,202 @@
<!DOCTYPE html>
<html id="ng-app" ng-app="app"> <!-- id="ng-app" IE<8 -->
<head>
<title>LoopBack Storage Service Demo</title>
<link rel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"/>
<!-- Fix for old browsers -->
<script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<!--<script src="../bower_components/angular/angular.js"></script>-->
<script src="http://code.angularjs.org/1.2.9/angular.min.js"></script>
<script src="angular-file-upload.js"></script>
<script src="controllers.js"></script>
<style>
.my-drop-zone {
border: dotted 3px lightgray;
}
.ng-file-over {
border: dotted 3px red;
}
/* Default class applied to drop zones on over */
.another-file-over-class {
border: dotted 3px green;
}
html, body {
height: 100%;
}
</style>
</head>
<!-- 1. ng-file-drop | ng-file-drop="options" -->
<body ng-controller="TestController" ng-file-drop>
<div class="container">
<div class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand"
href="https://github.com/strongloop/loopback-component-storage">LoopBack
Storage Service</a>
</div>
</div>
<div class="row">
<div class="col-md-3">
<h3>Select files</h3>
<div ng-show="uploader.isHTML5">
<!-- 3. ng-file-over | ng-file-over="className" -->
<div class="well my-drop-zone" ng-file-over>
Base drop zone
</div>
<!-- Example: ng-file-drop | ng-file-drop="options" -->
<div class="well my-drop-zone" ng-file-drop="{ url: '/foo' }"
ng-file-over="another-file-over-class">
Another drop zone with its own settings
</div>
</div>
<!-- 2. ng-file-select | ng-file-select="options" -->
Multiple
<input ng-file-select type="file" multiple/><br/>
Single
<input ng-file-select type="file"/>
</div>
<div class="col-md-9" style="margin-bottom: 40px">
<h3>Upload queue</h3>
<p>Queue length: {{ uploader.queue.length }}</p>
<table class="table">
<thead>
<tr>
<th width="50%">Name</th>
<th ng-show="uploader.isHTML5">Size</th>
<th ng-show="uploader.isHTML5">Progress</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in uploader.queue">
<td><strong>{{ item.file.name }}</strong></td>
<td ng-show="uploader.isHTML5" nowrap>{{
item.file.size/1024/1024|number:2 }} MB
</td>
<td ng-show="uploader.isHTML5">
<div class="progress" style="margin-bottom: 0;">
<div class="progress-bar" role="progressbar"
ng-style="{ 'width': item.progress + '%' }"></div>
</div>
</td>
<td class="text-center">
<span ng-show="item.isSuccess"><i
class="glyphicon glyphicon-ok"></i></span>
<span ng-show="item.isCancel"><i
class="glyphicon glyphicon-ban-circle"></i></span>
<span ng-show="item.isError"><i
class="glyphicon glyphicon-remove"></i></span>
</td>
<td nowrap>
<button type="button" class="btn btn-success btn-xs"
ng-click="item.upload()"
ng-disabled="item.isReady || item.isUploading || item.isSuccess">
<span class="glyphicon glyphicon-upload"></span>
Upload
</button>
<button type="button" class="btn btn-warning btn-xs"
ng-click="item.cancel()"
ng-disabled="!item.isUploading">
<span class="glyphicon glyphicon-ban-circle"></span>
Cancel
</button>
<button type="button" class="btn btn-danger btn-xs"
ng-click="item.remove()">
<span class="glyphicon glyphicon-trash"></span>
Remove
</button>
</td>
</tr>
</tbody>
</table>
<div>
<p>
Queue progress:
<div class="progress" style="">
<div class="progress-bar" role="progressbar"
ng-style="{ 'width': uploader.progress + '%' }"></div>
</div>
</p>
<button type="button" class="btn btn-success btn-s"
ng-click="uploader.uploadAll()"
ng-disabled="!uploader.getNotUploadedItems().length">
<span class="glyphicon glyphicon-upload"></span> Upload all
</button>
<button type="button" class="btn btn-warning btn-s"
ng-click="uploader.cancelAll()"
ng-disabled="!uploader.isUploading">
<span class="glyphicon glyphicon-ban-circle"></span> Cancel
all
</button>
<button type="button" class="btn btn-danger btn-s"
ng-click="uploader.clearQueue()"
ng-disabled="!uploader.queue.length">
<span class="glyphicon glyphicon-trash"></span> Remove all
</button>
</div>
</div>
<div class="col-md-9" style="margin-bottom: 40px"
ng-controller="FilesController" data-ng-init="load()">
<h3>Files in the container</h3>
<table class="table">
<tbody>
<tr ng-repeat="file in files">
<td>
<a href="/api/containers/container1/download/{{file.name}}"><strong>{{
file.name }}</strong></a></td>
<td>
<td>
<button type="button" class="btn btn-danger btn-xs"
ng-click="delete($index, file.name)"
title="Delete the file">
<span class="glyphicon glyphicon-trash"></span>
Remove
</button>
</td>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

20
example-2.0/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "loopback-example-storage",
"version": "0.0.0",
"main": "server/server.js",
"scripts": {
"pretest": "jshint ."
},
"dependencies": {
"compression": "^1.0.3",
"errorhandler": "^1.1.1",
"loopback": "^2.0.0",
"loopback-boot": "^2.0.0",
"loopback-component-storage": "~1.0.5",
"loopback-datasource-juggler": "^2.7.0",
"serve-favicon": "^2.0.1"
},
"optionalDependencies": {
"loopback-explorer": "^1.1.0"
}
}

View File

@ -0,0 +1,4 @@
module.exports = function enableAuthentication(server) {
// enable authentication
server.enableAuth();
};

View File

@ -0,0 +1,23 @@
module.exports = function mountLoopBackExplorer(server) {
var explorer;
try {
explorer = require('loopback-explorer');
} catch(err) {
console.log(
'Run `npm install loopback-explorer` to enable the LoopBack explorer'
);
return;
}
var restApiRoot = server.get('restApiRoot');
var explorerApp = explorer(server, { basePath: restApiRoot });
server.use('/explorer', explorerApp);
server.once('started', function() {
var baseUrl = server.get('url').replace(/\/$/, '');
// express 4.x (loopback 2.x) uses `mountpath`
// express 3.x (loopback 1.x) uses `route`
var explorerPath = explorerApp.mountpath || explorerApp.route;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
});
};

View File

@ -0,0 +1,4 @@
module.exports = function mountRestApi(server) {
var restApiRoot = server.get('restApiRoot');
server.use(restApiRoot, server.loopback.rest());
};

View File

@ -0,0 +1,6 @@
{
"restApiRoot": "/api",
"host": "0.0.0.0",
"port": 3000,
"url": "http://localhost:3000/"
}

View File

@ -0,0 +1,12 @@
{
"db": {
"name": "db",
"connector": "memory"
},
"storage": {
"name": "storage",
"connector": "loopback-component-storage",
"provider": "filesystem",
"root": "./server/storage"
}
}

View File

@ -0,0 +1,31 @@
{
"_meta": {
"sources": [
"../common/models",
"./models"
]
},
"User": {
"dataSource": "db"
},
"AccessToken": {
"dataSource": "db",
"public": false
},
"ACL": {
"dataSource": "db",
"public": false
},
"RoleMapping": {
"dataSource": "db",
"public": false
},
"Role": {
"dataSource": "db",
"public": false
},
"container": {
"dataSource": "storage",
"public": true
}
}

View File

@ -0,0 +1,3 @@
module.exports = function(Container) {
};

View File

@ -0,0 +1,9 @@
{
"name": "container",
"base": "Model",
"properties": {},
"validations": [],
"relations": {},
"acls": [],
"methods": []
}

View File

@ -0,0 +1,11 @@
{
"rackspace": {
"username": "your-rackspace-username",
"apiKey": "your-rackspace-api-key",
"region": "DFW"
},
"amazon": {
"key": "your-amazon-key",
"keyId": "your-amazon-key-id"
}
}

View File

@ -0,0 +1,43 @@
var loopback = require('loopback');
var boot = require('loopback-boot');
var app = module.exports = loopback();
// Set up the /favicon.ico
app.use(loopback.favicon());
// request pre-processing middleware
app.use(loopback.compress());
// -- Add your pre-processing middleware here --
// boot scripts mount components like REST API
boot(app, __dirname);
// -- Mount static files here--
// All static middleware should be registered at the end, as all requests
// passing the static middleware are hitting the file system
// Example:
var path = require('path');
app.use(loopback.static(path.resolve(__dirname, '../client')));
// Requests that get this far won't be handled
// by any middleware. Convert them into a 404 error
// that will be handled later down the chain.
app.use(loopback.urlNotFound());
// The ultimate error handler.
app.use(loopback.errorHandler());
app.start = function() {
// start the web server
return app.listen(function() {
app.emit('started');
console.log('Web server listening at: %s', app.get('url'));
});
};
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}

View File

@ -0,0 +1 @@
Hello....

View File

@ -0,0 +1 @@
Hello....

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

View File

@ -0,0 +1 @@
Upload test

View File

@ -0,0 +1 @@
Hello....

View File

@ -199,6 +199,7 @@ FileSystemProvider.prototype.getFiles = function (container, options, cb) {
if (!validateName(container, cb)) return;
var dir = path.join(this.root, container);
fs.readdir(dir, function (err, entries) {
entries = entries || [];
var files = [];
var tasks = [];
entries.forEach(function (f) {

View File

@ -155,6 +155,11 @@ StorageService.prototype.downloadStream = function (container, file, options, cb
* @param {Object[]} files An array of file metadata objects
*/
StorageService.prototype.getFiles = function (container, options, cb) {
if(typeof options === 'function' && !cb) {
// options argument is not present
cb = options;
options = {};
}
return this.client.getFiles(container, options, function (err, files) {
if (err) {
cb(err, files);

View File

@ -1,7 +1,7 @@
{
"name": "loopback-component-storage",
"description": "Loopback Storage Service",
"version": "1.0.5",
"version": "1.0.6",
"main": "index.js",
"scripts": {
"test": "./node_modules/.bin/mocha --timeout 30000 test/*test.js"
@ -25,5 +25,8 @@
"license": {
"name": "Dual Artistic-2.0/StrongLoop",
"url": "https://github.com/strongloop/loopback-strorage-service/blob/master/LICENSE"
},
"optionalDependencies": {
"sl-blip": "http://blip.strongloop.com/loopback-component-storage@1.0.6"
}
}