Merge branch 'release/1.0.6' into production
This commit is contained in:
commit
ed974929a4
|
@ -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!
|
|
@ -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
124
README.md
|
@ -12,126 +12,4 @@ storage services including:
|
||||||
- Openstack
|
- Openstack
|
||||||
- Azure
|
- Azure
|
||||||
|
|
||||||
The binary artifacts are organized with containers and files. A container is the
|
> Please see the [Storage Service Documenation](http://docs.strongloop.com/display/LB/Storage+service).
|
||||||
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
|
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
/client/
|
||||||
|
/node_modules/
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
.idea
|
||||||
|
.project
|
||||||
|
*.sublime-*
|
||||||
|
.DS_Store
|
||||||
|
*.seed
|
||||||
|
*.log
|
||||||
|
*.csv
|
||||||
|
*.dat
|
||||||
|
*.out
|
||||||
|
*.pid
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
node_modules
|
||||||
|
coverage
|
||||||
|
*.tgz
|
||||||
|
*.xml
|
|
@ -0,0 +1,3 @@
|
||||||
|
## Client
|
||||||
|
|
||||||
|
This is the place for your application front-end files.
|
|
@ -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
|
@ -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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -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>
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = function enableAuthentication(server) {
|
||||||
|
// enable authentication
|
||||||
|
server.enableAuth();
|
||||||
|
};
|
|
@ -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);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = function mountRestApi(server) {
|
||||||
|
var restApiRoot = server.get('restApiRoot');
|
||||||
|
server.use(restApiRoot, server.loopback.rest());
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"restApiRoot": "/api",
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 3000,
|
||||||
|
"url": "http://localhost:3000/"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"db": {
|
||||||
|
"name": "db",
|
||||||
|
"connector": "memory"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"name": "storage",
|
||||||
|
"connector": "loopback-component-storage",
|
||||||
|
"provider": "filesystem",
|
||||||
|
"root": "./server/storage"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = function(Container) {
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "container",
|
||||||
|
"base": "Model",
|
||||||
|
"properties": {},
|
||||||
|
"validations": [],
|
||||||
|
"relations": {},
|
||||||
|
"acls": [],
|
||||||
|
"methods": []
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Hello....
|
|
@ -0,0 +1 @@
|
||||||
|
Hello....
|
Binary file not shown.
After Width: | Height: | Size: 344 KiB |
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
Upload test
|
|
@ -0,0 +1 @@
|
||||||
|
Hello....
|
|
@ -199,6 +199,7 @@ FileSystemProvider.prototype.getFiles = function (container, options, cb) {
|
||||||
if (!validateName(container, cb)) return;
|
if (!validateName(container, cb)) return;
|
||||||
var dir = path.join(this.root, container);
|
var dir = path.join(this.root, container);
|
||||||
fs.readdir(dir, function (err, entries) {
|
fs.readdir(dir, function (err, entries) {
|
||||||
|
entries = entries || [];
|
||||||
var files = [];
|
var files = [];
|
||||||
var tasks = [];
|
var tasks = [];
|
||||||
entries.forEach(function (f) {
|
entries.forEach(function (f) {
|
||||||
|
|
|
@ -155,6 +155,11 @@ StorageService.prototype.downloadStream = function (container, file, options, cb
|
||||||
* @param {Object[]} files An array of file metadata objects
|
* @param {Object[]} files An array of file metadata objects
|
||||||
*/
|
*/
|
||||||
StorageService.prototype.getFiles = function (container, options, cb) {
|
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) {
|
return this.client.getFiles(container, options, function (err, files) {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err, files);
|
cb(err, files);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-component-storage",
|
"name": "loopback-component-storage",
|
||||||
"description": "Loopback Storage Service",
|
"description": "Loopback Storage Service",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/mocha --timeout 30000 test/*test.js"
|
"test": "./node_modules/.bin/mocha --timeout 30000 test/*test.js"
|
||||||
|
@ -25,5 +25,8 @@
|
||||||
"license": {
|
"license": {
|
||||||
"name": "Dual Artistic-2.0/StrongLoop",
|
"name": "Dual Artistic-2.0/StrongLoop",
|
||||||
"url": "https://github.com/strongloop/loopback-strorage-service/blob/master/LICENSE"
|
"url": "https://github.com/strongloop/loopback-strorage-service/blob/master/LICENSE"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"sl-blip": "http://blip.strongloop.com/loopback-component-storage@1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue