Merge branch 'release/1.0.0' into production
This commit is contained in:
commit
50cd49dd9a
506
LICENSE
506
LICENSE
|
@ -1,19 +1,493 @@
|
||||||
Copyright (c) 2013 StrongLoop, Inc.
|
Copyright (c) 2013-2014 StrongLoop, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
loopback-storage-service uses a 'dual license' model. Users may use
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
loopback-storage-service under the terms of the Artistic 2.0 license, or under
|
||||||
in the Software without restriction, including without limitation the rights
|
the StrongLoop License. The text of both is included below.
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
Artistic License 2.0
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
Copyright (c) 2000-2006, The Perl Foundation.
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
of this license document, but changing it is not allowed.
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
Preamble
|
||||||
THE SOFTWARE.
|
|
||||||
|
This license establishes the terms under which a given free software
|
||||||
|
Package may be copied, modified, distributed, and/or redistributed.
|
||||||
|
The intent is that the Copyright Holder maintains some artistic
|
||||||
|
control over the development of that Package while still keeping the
|
||||||
|
Package available as open source and free software.
|
||||||
|
|
||||||
|
You are always permitted to make arrangements wholly outside of this
|
||||||
|
license directly with the Copyright Holder of a given Package. If the
|
||||||
|
terms of this license do not permit the full use that you propose to
|
||||||
|
make of the Package, you should contact the Copyright Holder and seek
|
||||||
|
a different licensing arrangement.
|
||||||
|
|
||||||
|
Definitions
|
||||||
|
|
||||||
|
"Copyright Holder" means the individual(s) or organization(s)
|
||||||
|
named in the copyright notice for the entire Package.
|
||||||
|
|
||||||
|
"Contributor" means any party that has contributed code or other
|
||||||
|
material to the Package, in accordance with the Copyright Holder's
|
||||||
|
procedures.
|
||||||
|
|
||||||
|
"You" and "your" means any person who would like to copy,
|
||||||
|
distribute, or modify the Package.
|
||||||
|
|
||||||
|
"Package" means the collection of files distributed by the
|
||||||
|
Copyright Holder, and derivatives of that collection and/or of
|
||||||
|
those files. A given Package may consist of either the Standard
|
||||||
|
Version, or a Modified Version.
|
||||||
|
|
||||||
|
"Distribute" means providing a copy of the Package or making it
|
||||||
|
accessible to anyone else, or in the case of a company or
|
||||||
|
organization, to others outside of your company or organization.
|
||||||
|
|
||||||
|
"Distributor Fee" means any fee that you charge for Distributing
|
||||||
|
this Package or providing support for this Package to another
|
||||||
|
party. It does not mean licensing fees.
|
||||||
|
|
||||||
|
"Standard Version" refers to the Package if it has not been
|
||||||
|
modified, or has been modified only in ways explicitly requested
|
||||||
|
by the Copyright Holder.
|
||||||
|
|
||||||
|
"Modified Version" means the Package, if it has been changed, and
|
||||||
|
such changes were not explicitly requested by the Copyright
|
||||||
|
Holder.
|
||||||
|
|
||||||
|
"Original License" means this Artistic License as Distributed with
|
||||||
|
the Standard Version of the Package, in its current version or as
|
||||||
|
it may be modified by The Perl Foundation in the future.
|
||||||
|
|
||||||
|
"Source" form means the source code, documentation source, and
|
||||||
|
configuration files for the Package.
|
||||||
|
|
||||||
|
"Compiled" form means the compiled bytecode, object code, binary,
|
||||||
|
or any other form resulting from mechanical transformation or
|
||||||
|
translation of the Source form.
|
||||||
|
|
||||||
|
|
||||||
|
Permission for Use and Modification Without Distribution
|
||||||
|
|
||||||
|
(1) You are permitted to use the Standard Version and create and use
|
||||||
|
Modified Versions for any purpose without restriction, provided that
|
||||||
|
you do not Distribute the Modified Version.
|
||||||
|
|
||||||
|
|
||||||
|
Permissions for Redistribution of the Standard Version
|
||||||
|
|
||||||
|
(2) You may Distribute verbatim copies of the Source form of the
|
||||||
|
Standard Version of this Package in any medium without restriction,
|
||||||
|
either gratis or for a Distributor Fee, provided that you duplicate
|
||||||
|
all of the original copyright notices and associated disclaimers. At
|
||||||
|
your discretion, such verbatim copies may or may not include a
|
||||||
|
Compiled form of the Package.
|
||||||
|
|
||||||
|
(3) You may apply any bug fixes, portability changes, and other
|
||||||
|
modifications made available from the Copyright Holder. The resulting
|
||||||
|
Package will still be considered the Standard Version, and as such
|
||||||
|
will be subject to the Original License.
|
||||||
|
|
||||||
|
|
||||||
|
Distribution of Modified Versions of the Package as Source
|
||||||
|
|
||||||
|
(4) You may Distribute your Modified Version as Source (either gratis
|
||||||
|
or for a Distributor Fee, and with or without a Compiled form of the
|
||||||
|
Modified Version) provided that you clearly document how it differs
|
||||||
|
from the Standard Version, including, but not limited to, documenting
|
||||||
|
any non-standard features, executables, or modules, and provided that
|
||||||
|
you do at least ONE of the following:
|
||||||
|
|
||||||
|
(a) make the Modified Version available to the Copyright Holder
|
||||||
|
of the Standard Version, under the Original License, so that the
|
||||||
|
Copyright Holder may include your modifications in the Standard
|
||||||
|
Version.
|
||||||
|
|
||||||
|
(b) ensure that installation of your Modified Version does not
|
||||||
|
prevent the user installing or running the Standard Version. In
|
||||||
|
addition, the Modified Version must bear a name that is different
|
||||||
|
from the name of the Standard Version.
|
||||||
|
|
||||||
|
(c) allow anyone who receives a copy of the Modified Version to
|
||||||
|
make the Source form of the Modified Version available to others
|
||||||
|
under
|
||||||
|
|
||||||
|
(i) the Original License or
|
||||||
|
|
||||||
|
(ii) a license that permits the licensee to freely copy,
|
||||||
|
modify and redistribute the Modified Version using the same
|
||||||
|
licensing terms that apply to the copy that the licensee
|
||||||
|
received, and requires that the Source form of the Modified
|
||||||
|
Version, and of any works derived from it, be made freely
|
||||||
|
available in that license fees are prohibited but Distributor
|
||||||
|
Fees are allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Distribution of Compiled Forms of the Standard Version
|
||||||
|
or Modified Versions without the Source
|
||||||
|
|
||||||
|
(5) You may Distribute Compiled forms of the Standard Version without
|
||||||
|
the Source, provided that you include complete instructions on how to
|
||||||
|
get the Source of the Standard Version. Such instructions must be
|
||||||
|
valid at the time of your distribution. If these instructions, at any
|
||||||
|
time while you are carrying out such distribution, become invalid, you
|
||||||
|
must provide new instructions on demand or cease further distribution.
|
||||||
|
If you provide valid instructions or cease distribution within thirty
|
||||||
|
days after you become aware that the instructions are invalid, then
|
||||||
|
you do not forfeit any of your rights under this license.
|
||||||
|
|
||||||
|
(6) You may Distribute a Modified Version in Compiled form without
|
||||||
|
the Source, provided that you comply with Section 4 with respect to
|
||||||
|
the Source of the Modified Version.
|
||||||
|
|
||||||
|
|
||||||
|
Aggregating or Linking the Package
|
||||||
|
|
||||||
|
(7) You may aggregate the Package (either the Standard Version or
|
||||||
|
Modified Version) with other packages and Distribute the resulting
|
||||||
|
aggregation provided that you do not charge a licensing fee for the
|
||||||
|
Package. Distributor Fees are permitted, and licensing fees for other
|
||||||
|
components in the aggregation are permitted. The terms of this license
|
||||||
|
apply to the use and Distribution of the Standard or Modified Versions
|
||||||
|
as included in the aggregation.
|
||||||
|
|
||||||
|
(8) You are permitted to link Modified and Standard Versions with
|
||||||
|
other works, to embed the Package in a larger work of your own, or to
|
||||||
|
build stand-alone binary or bytecode versions of applications that
|
||||||
|
include the Package, and Distribute the result without restriction,
|
||||||
|
provided the result does not expose a direct interface to the Package.
|
||||||
|
|
||||||
|
|
||||||
|
Items That are Not Considered Part of a Modified Version
|
||||||
|
|
||||||
|
(9) Works (including, but not limited to, modules and scripts) that
|
||||||
|
merely extend or make use of the Package, do not, by themselves, cause
|
||||||
|
the Package to be a Modified Version. In addition, such works are not
|
||||||
|
considered parts of the Package itself, and are not subject to the
|
||||||
|
terms of this license.
|
||||||
|
|
||||||
|
|
||||||
|
General Provisions
|
||||||
|
|
||||||
|
(10) Any use, modification, and distribution of the Standard or
|
||||||
|
Modified Versions is governed by this Artistic License. By using,
|
||||||
|
modifying or distributing the Package, you accept this license. Do not
|
||||||
|
use, modify, or distribute the Package, if you do not accept this
|
||||||
|
license.
|
||||||
|
|
||||||
|
(11) If your Modified Version has been derived from a Modified
|
||||||
|
Version made by someone other than you, you are nevertheless required
|
||||||
|
to ensure that your Modified Version complies with the requirements of
|
||||||
|
this license.
|
||||||
|
|
||||||
|
(12) This license does not grant you the right to use any trademark,
|
||||||
|
service mark, tradename, or logo of the Copyright Holder.
|
||||||
|
|
||||||
|
(13) This license includes the non-exclusive, worldwide,
|
||||||
|
free-of-charge patent license to make, have made, use, offer to sell,
|
||||||
|
sell, import and otherwise transfer the Package with respect to any
|
||||||
|
patent claims licensable by the Copyright Holder that are necessarily
|
||||||
|
infringed by the Package. If you institute patent litigation
|
||||||
|
(including a cross-claim or counterclaim) against any party alleging
|
||||||
|
that the Package constitutes direct or contributory patent
|
||||||
|
infringement, then this Artistic License to you shall terminate on the
|
||||||
|
date that such litigation is filed.
|
||||||
|
|
||||||
|
(14) Disclaimer of Warranty:
|
||||||
|
THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
|
||||||
|
IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
|
||||||
|
NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
|
||||||
|
LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
|
||||||
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
StrongLoop License
|
||||||
|
|
||||||
|
STRONGLOOP SUBSCRIPTION AGREEMENT
|
||||||
|
PLEASE READ THIS AGREEMENT CAREFULLY BEFORE YOU AGREE TO THESE TERMS. IF YOU
|
||||||
|
ARE ACTING ON BEHALF OF AN ENTITY, THEN YOU REPRESENT THAT YOU HAVE THE
|
||||||
|
AUTHORITY TO ENTER INTO THIS AGREEMENT ON BEHALF OF THAT ENTITY. IF YOU DO NOT
|
||||||
|
AGREE TO THESE TERMS, YOU SHOULD NOT AGREE TO THE TERMS OF THIS AGREEMENT OR
|
||||||
|
INSTALL OR USE THE SOFTWARE.
|
||||||
|
This StrongLoop Subscription Agreement ("Agreement") is made by and between
|
||||||
|
StrongLoop, Inc. ("StrongLoop") with its principal place of business at 107 S.
|
||||||
|
B St, Suite 220, San Mateo, CA 94401 and the person or entity entering into this
|
||||||
|
Agreement ("Customer"). The effective date ("Effective Date") of this Agreement
|
||||||
|
is the date Customer agrees to these terms or installs or uses the Software (as
|
||||||
|
defined below). This Agreement applies to Customer's use of the Software but it
|
||||||
|
shall be superseded by any signed agreement between you and StrongLoop
|
||||||
|
concerning the Software.
|
||||||
|
1. Subscriptions and Licenses.
|
||||||
|
1.1 Subscriptions. StrongLoop offers five different subscription levels to its
|
||||||
|
customers, each as more particularly described on StrongLoop's website located
|
||||||
|
at www.strongloop.com (the "StrongLoop Site"): (1) Free; (2) Developer; (3)
|
||||||
|
Professional; (4) Gold; and (5) Platinum. The actual subscription level
|
||||||
|
applicable to Customer (the "Subscription") will be specified in the purchase
|
||||||
|
order that Customer issues to StrongLoop. This Agreement applies to Customer
|
||||||
|
regardless of the level of the Subscription selected by Customer and whether or
|
||||||
|
not Customer upgrades or downgrades its Subscription. StrongLoop hereby agrees
|
||||||
|
to provide the services as described on the StrongLoop Site for each
|
||||||
|
Subscription level during the term for which Customer has purchased the
|
||||||
|
applicable Subscription, subject to Customer paying the fees applicable to the
|
||||||
|
Subscription level purchased, if any (the "Subscription Fees"). StrongLoop may
|
||||||
|
modify the services to be provided under any Subscription upon notice to
|
||||||
|
Customer.
|
||||||
|
1.2 License Grant. Subject to the terms and conditions of this Agreement,
|
||||||
|
StrongLoop grants to Customer, during the Subscription Term (as defined in
|
||||||
|
Section 7.1 (Term and Termination) of this Agreement, a limited, non-exclusive,
|
||||||
|
non-transferable right and license, to install and use the StrongLoop Suite
|
||||||
|
software (the "Software") and the documentation made available electronically as
|
||||||
|
part of the Software (the "Documentation"), either of which may be modified
|
||||||
|
during the Term (as defined in Section 7.1 below), solely for development,
|
||||||
|
production and commercial purposes so long as Customer is using the Software to
|
||||||
|
run only one process on a given operating system at a time. This Agreement,
|
||||||
|
including but not limited to the license and restrictions contained herein,
|
||||||
|
apply to Customer regardless of whether Customer accesses the Software via
|
||||||
|
download from the StrongLoop Site or through a third-party website or service,
|
||||||
|
even if Customer acquired the Software prior to agreeing to this Agreement.
|
||||||
|
1.3 License Restrictions. Customer shall not itself, or through any parent,
|
||||||
|
subsidiary, affiliate, agent or other third party:
|
||||||
|
1.3.1 sell, lease, license, distribute, sublicense or otherwise transfer
|
||||||
|
in whole or in part, any Software or the Documentation to a third party;
|
||||||
|
or
|
||||||
|
1.3.2 decompile, disassemble, translate, reverse engineer or otherwise
|
||||||
|
attempt to derive source code from the Software, in whole or in part, nor
|
||||||
|
shall Customer use any mechanical, electronic or other method to trace,
|
||||||
|
decompile, disassemble, or identify the source code of the Software or
|
||||||
|
encourage others to do so, except to the limited extent, if any, that
|
||||||
|
applicable law permits such acts notwithstanding any contractual
|
||||||
|
prohibitions, provided, however, before Customer exercises any rights that
|
||||||
|
Customer believes to be entitled to based on mandatory law, Customer shall
|
||||||
|
provide StrongLoop with thirty (30) days prior written notice and provide
|
||||||
|
all reasonably requested information to allow StrongLoop to assess
|
||||||
|
Customer's claim and, at StrongLoop's sole discretion, to provide
|
||||||
|
alternatives that reduce any adverse impact on StrongLoop's intellectual
|
||||||
|
property or other rights; or
|
||||||
|
1.3.3 allow access or permit use of the Software by any users other than
|
||||||
|
Customer's employees or authorized third-party contractors who are
|
||||||
|
providing services to Customer and agree in writing to abide by the terms
|
||||||
|
of this Agreement, provided further that Customer shall be liable for any
|
||||||
|
failure by such employees and third-party contractors to comply with the
|
||||||
|
terms of this Agreement and no usage restrictions, if any, shall be
|
||||||
|
exceeded; or
|
||||||
|
1.3.4 create, develop, license, install, use, or deploy any third party
|
||||||
|
software or services to circumvent or provide access, permissions or
|
||||||
|
rights which violate the license keys embedded within the Software; or
|
||||||
|
1.3.5 modify or create derivative works based upon the Software or
|
||||||
|
Documentation; or disclose the results of any benchmark test of the
|
||||||
|
Software to any third party without StrongLoop's prior written approval;
|
||||||
|
or
|
||||||
|
1.3.6 change any proprietary rights notices which appear in the Software
|
||||||
|
or Documentation; or
|
||||||
|
1.3.7 use the Software as part of a time sharing or service bureau
|
||||||
|
purposes or in any other resale capacity.
|
||||||
|
1.4 Third-Party Software. The Software may include individual certain software
|
||||||
|
that is owned by third parties, including individual open source software
|
||||||
|
components (the "Third-Party Software"), each of which has its own copyright and
|
||||||
|
its own applicable license conditions. Such third-party software is licensed to
|
||||||
|
Customer under the terms of the applicable third-party licenses and/or copyright
|
||||||
|
notices that can be found in the LICENSES file, the Documentation or other
|
||||||
|
materials accompanying the Software, except that Sections 5 (Warranty
|
||||||
|
Disclaimer) and 6 (Limitation of Liability) also govern Customer's use of the
|
||||||
|
third-party software. Customer agrees to comply with the terms and conditions
|
||||||
|
of the relevant third-party software licenses.
|
||||||
|
2. Support Services. StrongLoop has no obligation to provide any support for
|
||||||
|
the Software other than the support services specifically described on the
|
||||||
|
StrongLoop Site for the Subscription level procured by Customer. However,
|
||||||
|
StrongLoop has endeavored to establish a community of users of the Software who
|
||||||
|
have provided their own feedback, hints and advice regarding their experiences
|
||||||
|
in using the Software. You can find that community and user feedback on the
|
||||||
|
StrongLoop Site. The use of any information, content or other materials from,
|
||||||
|
contained in or on the StrongLoop Site are subject to the StrongLoop website
|
||||||
|
terms of use located here http://www.strongloop.com/terms-of-service.
|
||||||
|
3. Confidentiality. For purposes of this Agreement, "Confidential Information"
|
||||||
|
means any and all information or proprietary materials (in every form and media)
|
||||||
|
not generally known in the relevant trade or industry and which has been or is
|
||||||
|
hereafter disclosed or made available by StrongLoop to Customer in connection
|
||||||
|
with the transactions contemplated under this Agreement, including (i) all trade
|
||||||
|
secrets, (ii) existing or contemplated Software, services, designs, technology,
|
||||||
|
processes, technical data, engineering, techniques, methodologies and concepts
|
||||||
|
and any related information, and (iii) information relating to business plans,
|
||||||
|
sales or marketing methods and customer lists or requirements. For a period of
|
||||||
|
five (5) years from the date of disclosure of the applicable Confidential
|
||||||
|
Information, Customer shall (i) hold the Confidential Information in trust and
|
||||||
|
confidence and avoid the disclosure or release thereof to any other person or
|
||||||
|
entity by using the same degree of care as it uses to avoid unauthorized use,
|
||||||
|
disclosure, or dissemination of its own Confidential Information of a similar
|
||||||
|
nature, but not less than reasonable care, and (ii) not use the Confidential
|
||||||
|
Information for any purpose whatsoever except as expressly contemplated under
|
||||||
|
this Agreement; provided that, to the extent the Confidential Information
|
||||||
|
constitutes a trade secret under law, Customer agrees to protect such
|
||||||
|
information for so long as it qualifies as a trade secret under applicable law.
|
||||||
|
Customer shall disclose the Confidential Information only to those of its
|
||||||
|
employees and contractors having a need to know such Confidential Information
|
||||||
|
and shall take all reasonable precautions to ensure that such employees and
|
||||||
|
contractors comply with the provisions of this Section. The obligations of
|
||||||
|
Customer under this Section shall not apply to information that Customer can
|
||||||
|
demonstrate (i) was in its possession at the time of disclosure and without
|
||||||
|
restriction as to confidentiality, (ii) at the time of disclosure is generally
|
||||||
|
available to the public or after disclosure becomes generally available to the
|
||||||
|
public through no breach of agreement or other wrongful act by Customer, (iii)
|
||||||
|
has been received from a third party without restriction on disclosure and
|
||||||
|
without breach of agreement by Customer, or (iv) is independently developed by
|
||||||
|
Customer without regard to the Confidential Information. In addition, Customer
|
||||||
|
may disclose Confidential Information as required to comply with binding orders
|
||||||
|
of governmental entities that have jurisdiction over it; provided that Customer
|
||||||
|
gives StrongLoop reasonable written notice to allow StrongLoop to seek a
|
||||||
|
protective order or other appropriate remedy, discloses only such Confidential
|
||||||
|
Information as is required by the governmental entity, and uses commercially
|
||||||
|
reasonable efforts to obtain confidential treatment for any Confidential
|
||||||
|
Information disclosed. Notwithstanding the above, Customer agrees that
|
||||||
|
StrongLoop, its employees and agents shall be free to use and employ their
|
||||||
|
general skills, know-how, and expertise, and to use, disclose, and employ any
|
||||||
|
generalized ideas, concepts, know-how, methods, techniques or skills gained or
|
||||||
|
learned during the Term or thereafter.
|
||||||
|
4. Ownership. StrongLoop shall retain all intellectual property and proprietary
|
||||||
|
rights in the Software, Documentation, and related works, including but not
|
||||||
|
limited to any derivative work of the foregoing and StrongLoop's licensors shall
|
||||||
|
retain all intellectual property and proprietary rights in any Third-Party
|
||||||
|
Software that may be provided with or as a part of the Software. Customer shall
|
||||||
|
do nothing inconsistent with StrongLoop's or its licensors' title to the
|
||||||
|
Software and the intellectual property rights embodied therein, including, but
|
||||||
|
not limited to, transferring, loaning, selling, assigning, pledging, or
|
||||||
|
otherwise disposing, encumbering, or suffering a lien or encumbrance upon or
|
||||||
|
against any interest in the Software. The Software (including any Third-Party
|
||||||
|
Software) contain copyrighted material, trade secrets and other proprietary
|
||||||
|
material of StrongLoop and/or its licensors.
|
||||||
|
5. Warranty Disclaimer. THE SOFTWARE (INCLUDING ANY THIRD-PARTY SOFTWARE) AND
|
||||||
|
DOCUMENTATION MADE AVAILABLE TO CUSTOMER ARE PROVIDED "AS-IS" AND STRONGLOOP,
|
||||||
|
ON BEHALF OF ITSELF AND ITS LICENSORS, EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY
|
||||||
|
KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, TITLE,
|
||||||
|
PERFORMANCE, AND ACCURACY AND ANY IMPLIED WARRANTIES ARISING FROM STATUTE,
|
||||||
|
COURSE OF DEALING, COURSE OF PERFORMANCE, OR USAGE OF TRADE. STRONGLOOP DOES
|
||||||
|
NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR
|
||||||
|
ERROR-FREE, THAT DEFECTS IN THE SOFTWARE WILL BE CORRECTED OR THAT THE SOFTWARE
|
||||||
|
WILL PROVIDE OR ENSURE ANY PARTICULAR RESULTS OR OUTCOME. NO ORAL OR WRITTEN
|
||||||
|
INFORMATION OR ADVICE GIVEN BY STRONGLOOP OR ITS AUTHORIZED REPRESENTATIVES
|
||||||
|
SHALL CREATE A WARRANTY OR IN ANY WAY INCREASE THE SCOPE OF THIS WARRANTY.
|
||||||
|
STRONGLOOP IS NOT OBLIGATED TO PROVIDE CUSTOMER WITH UPGRADES TO THE SOFTWARE,
|
||||||
|
BUT MAY ELECT TO DO SO IN ITS SOLE DISCRETION. SOME JURISDICTIONS DO NOT ALLOW
|
||||||
|
THE EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT APPLY TO
|
||||||
|
CUSTOMER.WITHOUT LIMITING THE GENERALITY OF THE FOREGOING DISCLAIMER, THE
|
||||||
|
SOFTWARE AND DOCUMENTATION ARE NOT DESIGNED, MANUFACTURED OR INTENDED FOR USE IN
|
||||||
|
THE PLANNING, CONSTRUCTION, MAINTENANCE, CONTROL, OR DIRECT OPERATION OF NUCLEAR
|
||||||
|
FACILITIES, AIRCRAFT NAVIGATION, CONTROL OR COMMUNICATION SYSTEMS, WEAPONS
|
||||||
|
SYSTEMS, OR DIRECT LIFE SUPPORT SYSTEMS.
|
||||||
|
6. Limitation of Liability.
|
||||||
|
6.1 Exclusion of Liability. IN NO EVENT WILL STRONGLOOP OR ITS LICENSORS
|
||||||
|
BE LIABLE UNDER THIS AGREEMENT FOR ANY INDIRECT, RELIANCE, PUNITIVE,
|
||||||
|
CONSEQUENTIAL, SPECIAL, EXEMPLARY, OR INCIDENTAL DAMAGES OF ANY KIND AND
|
||||||
|
HOWEVER CAUSED (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF
|
||||||
|
BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION AND
|
||||||
|
THE LIKE), EVEN IF STRONGLOOP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
|
DAMAGES. CUSTOMER BEARS FULL RESPONSIBILITY FOR USE OF THE SOFTWARE AND
|
||||||
|
THE SUBSCRIPTION AND STRONGLOOP DOES NOT GUARANTEE THAT THE USE OF THE
|
||||||
|
SOFTWARE AND SUBSCRIPTION WILL ENSURE THAT CUSTOMER'S NETWORK WILL BE
|
||||||
|
AVAILABLE, SECURE, MONITORED OR PROTECTED AGAINST ANY DOWNTIME, DENIAL OF
|
||||||
|
SERVICE ATTACKS, SECUITY BREACHES, HACKERS AND THE LIKE. IN NO EVENT WILL
|
||||||
|
STRONGLOOP'S CUMULATIVE LIABILITY FOR ANY DAMAGES, LOSSES AND CAUSES OF
|
||||||
|
ACTION (WHETHER IN CONTRACT, TORT, INCLUDING NEGLIGENCE, OR OTHERWISE)
|
||||||
|
ARISING OUT OF OR RELATED TO THIS AGREEMENT EXCEED THE GREATER OF ONE
|
||||||
|
HUNDRED DOLLARS (US$100) OR THE TOTAL SUBSCRIPTION FEES PAID BY CUSTOMER
|
||||||
|
TO STRONGLOOP IN THE TWELVE (12) MONTHS PRECEDING THE DATE THE CLAIM
|
||||||
|
ARISES.
|
||||||
|
6.2 Limitation of Damages. IN NO EVENT WILL STRONGLOOP'S LICENSORS HAVE
|
||||||
|
ANY LIABILITY FOR ANY CLAIM ARISING IN CONNECTION WITH THIS AGREEMENT.
|
||||||
|
THE PROVISIONS OF THIS SECTION 6 ALLOCATE RISKS UNDER THIS AGREEMENT
|
||||||
|
BETWEEN CUSTOMER, STRONGLOOP AND STRONGLOOP'S SUPPLIERS. THE FOREGOING
|
||||||
|
LIMITATIONS, EXCLUSIONS AND DISCLAIMERS APPLY TO THE MAXIMUM EXTENT
|
||||||
|
PERMITTED BY APPLICABLE LAW, EVEN IF ANY REMEDY FAILS IN ITS ESSENTIAL
|
||||||
|
PURPOSE.
|
||||||
|
6.3 Failure of Essential Purpose. THE PARTIES AGREE THAT THESE
|
||||||
|
LIMITATIONS SHALL APPLY EVEN IF THIS AGREEMENT OR ANY LIMITED REMEDY
|
||||||
|
SPECIFIED HEREIN IS FOUND TO HAVE FAILED OF ITS ESSENTIAL PURPOSE.
|
||||||
|
6.4 Allocation of Risk. The sections on limitation of liability and
|
||||||
|
disclaimer of warranties allocate the risks in the Agreement between the
|
||||||
|
parties. This allocation is an essential element of the basis of the
|
||||||
|
bargain between the parties.
|
||||||
|
7. Term and Termination.
|
||||||
|
7.1 This Agreement shall commence on the Effective Date and continue for so long
|
||||||
|
as Customer has a valid Subscription and is current on the payment of any
|
||||||
|
Subscription Fees required to be paid for that Subscription (the "Subscription
|
||||||
|
Term"). Either party may terminate this Agreement immediately upon written
|
||||||
|
notice to the other party, and the Subscription and licenses granted hereunder
|
||||||
|
automatically terminate upon the termination of this Agreement. This Agreement
|
||||||
|
will terminate immediately without notice from StrongLoop if Customer fails to
|
||||||
|
comply with or otherwise breaches any provision of this Agreement.
|
||||||
|
7.2 All Sections other than Section 1.1 (Subscriptions) and 1.2 (Licenses) shall
|
||||||
|
survive the expiration or termination of this Agreement.
|
||||||
|
8. Subscription Fees and Payments. StrongLoop, Customer agrees to pay
|
||||||
|
StrongLoop the Subscription Fees as described on the StrongLoop Site for the
|
||||||
|
Subscription purchased unless a different amount has been agreed to in a
|
||||||
|
separate agreement between Customer and StrongLoop. In addition, Customer shall
|
||||||
|
pay all sales, use, value added, withholding, excise taxes and other tax, duty,
|
||||||
|
custom and similar fees levied upon the delivery or use of the Software and the
|
||||||
|
Subscriptions described in this Agreement. Fees shall be invoiced in full upon
|
||||||
|
StrongLoop's acceptance of Customer's purchase order for the Subscription. All
|
||||||
|
invoices shall be paid in US dollars and are due upon receipt and shall be paid
|
||||||
|
within thirty (30) days. Payments shall be made without right of set-off or
|
||||||
|
chargeback. If Customer does not pay the invoices when due, StrongLoop may
|
||||||
|
charge interest at one percent (1%) per month or the highest rate permitted by
|
||||||
|
law, whichever is lower, on the unpaid balance from the original due date. If
|
||||||
|
Customer fails to pay fees in accordance with this Section, StrongLoop may
|
||||||
|
suspend fulfilling its obligations under this Agreement (including but not
|
||||||
|
limited to suspending the services under the Subscription) until payment is
|
||||||
|
received by StrongLoop. If any applicable law requires Customer to withhold
|
||||||
|
amounts from any payments to StrongLoop under this Agreement, (a) Customer shall
|
||||||
|
effect such withholding, remit such amounts to the appropriate taxing
|
||||||
|
authorities and promptly furnish StrongLoop with tax receipts evidencing the
|
||||||
|
payments of such amounts and (b) the sum payable by Customer upon which the
|
||||||
|
deduction or withholding is based shall be increased to the extent necessary to
|
||||||
|
ensure that, after such deduction or withholding, StrongLoop receives and
|
||||||
|
retains, free from liability for such deduction or withholding, a net amount
|
||||||
|
equal to the amount StrongLoop would have received and retained absent the
|
||||||
|
required deduction or withholding.
|
||||||
|
9. General.
|
||||||
|
9.1 Compliance with Laws. Customer shall abide by all local, state, federal and
|
||||||
|
international laws, rules, regulations and orders applying to Customer's use of
|
||||||
|
the Software, including, without limitation, the laws and regulations of the
|
||||||
|
United States that may restrict the export and re-export of certain commodities
|
||||||
|
and technical data of United States origin, including the Software. Customer
|
||||||
|
agrees that it will not export or re-export the Software without the appropriate
|
||||||
|
United States or foreign government licenses.
|
||||||
|
9.2 Entire Agreement. This Agreement constitutes the entire agreement between
|
||||||
|
the parties concerning the subject matter hereof. This Agreement supersedes all
|
||||||
|
prior or contemporaneous discussions, proposals and agreements between the
|
||||||
|
parties relating to the subject matter hereof. No amendment, modification or
|
||||||
|
waiver of any provision of this Agreement shall be effective unless in writing
|
||||||
|
and signed by both parties. Any additional or different terms on any purchase
|
||||||
|
orders issued by Customer to StrongLoop shall not be binding on either party,
|
||||||
|
are hereby rejected by StrongLoop and void.
|
||||||
|
9.3 Severability. If any provision of this Agreement is held to be invalid or
|
||||||
|
unenforceable, the remaining portions shall remain in full force and effect and
|
||||||
|
such provision shall be enforced to the maximum extent possible so as to effect
|
||||||
|
the intent of the parties and shall be reformed to the extent necessary to make
|
||||||
|
such provision valid and enforceable.
|
||||||
|
9.4 Waiver. No waiver of rights by either party may be implied from any actions
|
||||||
|
or failures to enforce rights under this Agreement.
|
||||||
|
9.5 Force Majeure. Neither party shall be liable to the other for any delay or
|
||||||
|
failure to perform due to causes beyond its reasonable control (excluding
|
||||||
|
payment of monies due).
|
||||||
|
9.6 No Third Party Beneficiaries. Unless otherwise specifically stated, the
|
||||||
|
terms of this Agreement are intended to be and are solely for the benefit of
|
||||||
|
StrongLoop and Customer and do not create any right in favor of any third party.
|
||||||
|
9.7 Governing Law and Jurisdiction. This Agreement shall be governed by the
|
||||||
|
laws of the State of California, without reference to the principles of
|
||||||
|
conflicts of law. The provisions of the Uniform Computerized Information
|
||||||
|
Transaction Act and United Nations Convention on Contracts for the International
|
||||||
|
Sale of Goods shall not apply to this Agreement. The parties shall attempt to
|
||||||
|
resolve any dispute related to this Agreement informally, initially through
|
||||||
|
their respective management, and then by non-binding mediation in San Francisco
|
||||||
|
County, California. Any litigation related to this Agreement shall be brought
|
||||||
|
in the state or federal courts located in San Francisco County, California, and
|
||||||
|
only in those courts and each party irrevocably waives any objections to such
|
||||||
|
venue.
|
||||||
|
9.8 Notices. All notices must be in writing and shall be effective three (3)
|
||||||
|
days after the date sent to the other party's headquarters, Attention Chief
|
||||||
|
Financial Officer.
|
||||||
|
|
154
README.md
154
README.md
|
@ -1,27 +1,60 @@
|
||||||
# loopback-storage-service
|
# loopback-storage-service
|
||||||
|
|
||||||
LoopBack Storage Service
|
LoopBack storage service provides Node.js and REST APIs to manage binary contents
|
||||||
|
using pluggable storage providers, such as local file systems, Amazon S3, or
|
||||||
|
Rackspace cloud files. We use [pkgcloud](https://github.com/pkgcloud/pkgcloud) to support the cloud based
|
||||||
|
storage services including:
|
||||||
|
|
||||||
## Storage
|
- Amazon
|
||||||
|
- Rackspace
|
||||||
|
- Openstack
|
||||||
|
- Azure
|
||||||
|
|
||||||
The `loopback-storage-service` module is designed to make it easy to upload and download files to various infrastructure providers.
|
The binary artifacts are organized with containers and files. A container is the
|
||||||
|
collection of files. Each file will belong to a container.
|
||||||
|
|
||||||
To get started with a `loopback-storage-service` provider just create one:
|
## Define a model with the loopback-storage-service connector
|
||||||
|
|
||||||
``` js
|
LoopBack exposes the APIs using a model that is attached to a data source configured
|
||||||
var storageService = require('loopback-storage-service')({
|
with the loopback-storage-service connector.
|
||||||
//
|
|
||||||
// The name of the provider (e.g. "file")
|
|
||||||
//
|
|
||||||
provider: 'provider-name',
|
|
||||||
|
|
||||||
//
|
|
||||||
// ... Provider specific credentials
|
|
||||||
//
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Each compute provider takes different credentials to authenticate; these details about each specific provider can be found below:
|
var ds = loopback.createDataSource({
|
||||||
|
connector: require('loopback-storage-service'),
|
||||||
|
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
|
* Amazon
|
||||||
|
|
||||||
|
@ -41,53 +74,62 @@ Each compute provider takes different credentials to authenticate; these details
|
||||||
apiKey: '...'
|
apiKey: '...'
|
||||||
}
|
}
|
||||||
|
|
||||||
* Azure
|
* OpenStack
|
||||||
|
|
||||||
* Local File System
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
provider: 'filesystem',
|
provider: 'openstack',
|
||||||
root: '/tmp/storage'
|
username: 'your-user-name',
|
||||||
|
password: 'your-password',
|
||||||
|
authUrl: 'https://your-identity-service'
|
||||||
}
|
}
|
||||||
|
|
||||||
Each instance of `storage.Client` returned from `storage.createClient` has a set of uniform APIs:
|
* Azure
|
||||||
|
|
||||||
### Container
|
|
||||||
* `storageService.getContainers(function (err, containers) { })`
|
|
||||||
* `storageService.createContainer(options, function (err, container) { })`
|
|
||||||
* `storageService.destroyContainer(containerName, function (err) { })`
|
|
||||||
* `storageService.getContainer(containerName, function (err, container) { })`
|
|
||||||
|
|
||||||
### File
|
{
|
||||||
* `storageService.upload(options, function (err) { })`
|
provider: 'azure',
|
||||||
* `storageService.download(options, function (err) { })`
|
storageAccount: "test-storage-account", // Name of your storage account
|
||||||
* `storageService.getFiles(container, function (err, files) { })`
|
storageAccessKey: "test-storage-access-key" // Access key for storage account
|
||||||
* `storageService.getFile(container, file, function (err, server) { })`
|
}
|
||||||
* `storageService.removeFile(container, file, function (err) { })`
|
|
||||||
|
|
||||||
Both the `.upload(options)` and `.download(options)` have had **careful attention paid to make sure they are pipe and stream capable:**
|
|
||||||
|
|
||||||
### Upload a File
|
## REST APIs
|
||||||
``` js
|
|
||||||
var storage = require('loopback-storage-service'),
|
|
||||||
fs = require('fs');
|
|
||||||
|
|
||||||
var storageService = storage({ /* ... */ });
|
|
||||||
|
|
||||||
fs.createReadStream('a-file.txt').pipe(storageService.uploadStream('a-container','remote-file-name.txt'));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Download a File
|
- GET /api/containers
|
||||||
``` js
|
|
||||||
var storage = require('loopback-storage-service'),
|
|
||||||
fs = require('fs');
|
|
||||||
|
|
||||||
var storageService = storage({ /* ... */ });
|
|
||||||
|
|
||||||
storageService.downloadStream({
|
|
||||||
container: 'a-container',
|
|
||||||
remote: 'remote-file-name.txt'
|
|
||||||
}).pipe(fs.createWriteStream('a-file.txt'));
|
|
||||||
```
|
|
||||||
|
|
||||||
|
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,8 @@
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{ "title": "LoopBack Storage Service", "depth": 2 },
|
||||||
|
"lib/storage-service.js",
|
||||||
|
{ "title": "Storage Handler API", "depth": 3 },
|
||||||
|
"lib/storage-handler.js"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
providers-private.json
|
|
@ -0,0 +1,57 @@
|
||||||
|
var StorageService = require('../').StorageService;
|
||||||
|
var path = require('path');
|
||||||
|
var providers = null;
|
||||||
|
try {
|
||||||
|
providers = require('./providers-private.json');
|
||||||
|
} catch(err) {
|
||||||
|
providers = require('./providers.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
function listContainersAndFiles(ss) {
|
||||||
|
ss.getContainers(function (err, containers) {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('----------- %s (%d) ---------------', ss.provider, containers.length);
|
||||||
|
containers.forEach(function (c) {
|
||||||
|
console.log('[%s] %s/', ss.provider, c.name);
|
||||||
|
c.getFiles(function (err, files) {
|
||||||
|
files.forEach(function (f) {
|
||||||
|
console.log('[%s] ... %s', ss.provider, f.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var rs = new StorageService({
|
||||||
|
provider: 'rackspace',
|
||||||
|
username: providers.rackspace.username,
|
||||||
|
apiKey: providers.rackspace.apiKey,
|
||||||
|
region: providers.rackspace.region
|
||||||
|
});
|
||||||
|
|
||||||
|
listContainersAndFiles(rs);
|
||||||
|
|
||||||
|
var s3 = new StorageService({
|
||||||
|
provider: 'amazon',
|
||||||
|
key: providers.amazon.key,
|
||||||
|
keyId: providers.amazon.keyId
|
||||||
|
});
|
||||||
|
|
||||||
|
listContainersAndFiles(s3);
|
||||||
|
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var stream = s3.uploadStream('con1', 'test.jpg');
|
||||||
|
fs.createReadStream(path.join(__dirname, 'test.jpg')).pipe(stream);
|
||||||
|
|
||||||
|
var local = StorageService({
|
||||||
|
provider: 'filesystem',
|
||||||
|
root: path.join(__dirname, 'storage')
|
||||||
|
});
|
||||||
|
|
||||||
|
listContainersAndFiles(local);
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
var loopback = require('loopback')
|
|
||||||
, app = module.exports = loopback();
|
|
||||||
|
|
||||||
// var StorageService = require('../');
|
|
||||||
|
|
||||||
// expose a rest api
|
|
||||||
app.use(loopback.rest());
|
|
||||||
|
|
||||||
app.configure(function () {
|
|
||||||
app.set('port', process.env.PORT || 3000);
|
|
||||||
});
|
|
||||||
|
|
||||||
var ds = loopback.createDataSource({
|
|
||||||
connector: require('../lib/storage-connector'),
|
|
||||||
provider: 'filesystem',
|
|
||||||
root: '/tmp/storage'
|
|
||||||
});
|
|
||||||
|
|
||||||
var Container = ds.createModel('container', {name: String});
|
|
||||||
|
|
||||||
console.log(Container);
|
|
||||||
Container.getContainers(console.log);
|
|
||||||
|
|
||||||
console.log('shared', Container.getContainers.shared);
|
|
||||||
|
|
||||||
app.model(Container);
|
|
||||||
|
|
||||||
/*
|
|
||||||
var handler = new StorageService({provider: 'filesystem', root: '/tmp/storage'});
|
|
||||||
|
|
||||||
app.service('storage', handler);
|
|
||||||
|
|
||||||
app.get('/', function (req, res, next) {
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
var form = "<html><body><h1>Storage Service Demo</h1>" +
|
|
||||||
"<a href='/download'>List all containers</a><p>" +
|
|
||||||
"Upload to container c1: <p>" +
|
|
||||||
"<form method='POST' enctype='multipart/form-data' action='/upload/c1'>"
|
|
||||||
+ "File to upload: <input type=file name=uploadedFiles multiple=true><br>"
|
|
||||||
+ "Notes about the file: <input type=text name=note><br>"
|
|
||||||
+ "<input type=submit value=Upload></form>" +
|
|
||||||
"</body></html>";
|
|
||||||
res.send(form);
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
app.listen(app.get('port'));
|
|
||||||
console.log('http://127.0.0.1:' + app.get('port'));
|
|
118
example/app.js
118
example/app.js
|
@ -1,91 +1,45 @@
|
||||||
var StorageService = require('../');
|
var loopback = require('loopback')
|
||||||
|
, app = module.exports = loopback();
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
var rs = StorageService({
|
app.use(app.router);
|
||||||
provider: 'rackspace',
|
|
||||||
username: 'strongloop',
|
// expose a rest api
|
||||||
apiKey: 'your-rackspace-api-key'
|
app.use('/api', loopback.rest());
|
||||||
|
|
||||||
|
app.use(loopback.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
|
||||||
|
app.configure(function () {
|
||||||
|
app.set('port', process.env.PORT || 3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Container
|
var ds = loopback.createDataSource({
|
||||||
|
connector: require('../index'),
|
||||||
rs.getContainers(function (err, containers) {
|
provider: 'filesystem',
|
||||||
if (err) {
|
root: path.join(__dirname, 'storage')
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
containers.forEach(function (c) {
|
|
||||||
console.log('rackspace: ', c.name);
|
|
||||||
c.getFiles(function (err, files) {
|
|
||||||
files.forEach(function (f) {
|
|
||||||
console.log('....', f.name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var container = ds.createModel('container');
|
||||||
|
|
||||||
|
app.model(container);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
client.createContainer(options, function (err, container) { });
|
app.get('/', function (req, res, next) {
|
||||||
client.destroyContainer(containerName, function (err) { });
|
res.setHeader('Content-Type', 'text/html');
|
||||||
client.getContainer(containerName, function (err, container) { });
|
var form = "<html><body><h1>Storage Service Demo</h1>" +
|
||||||
|
"<a href='/api/containers'>List all containers</a><p>" +
|
||||||
// File
|
"Upload to container c1: <p>" +
|
||||||
|
"<form method='POST' enctype='multipart/form-data' action='/containers/container1/upload'>"
|
||||||
client.upload(options, function (err) { });
|
+ "File to upload: <input type=file name=uploadedFiles multiple=true><br>"
|
||||||
client.download(options, function (err) { });
|
+ "Notes about the file: <input type=text name=note><br>"
|
||||||
client.getFiles(container, function (err, files) { });
|
+ "<input type=submit value=Upload></form>" +
|
||||||
client.getFile(container, file, function (err, server) { });
|
"</body></html>";
|
||||||
client.removeFile(container, file, function (err) { });
|
res.send(form);
|
||||||
*/
|
res.end();
|
||||||
|
|
||||||
|
|
||||||
var s3 = StorageService({
|
|
||||||
provider: 'amazon',
|
|
||||||
key: 'your-amazon-key',
|
|
||||||
keyId: 'your-amazon-key-id'
|
|
||||||
});
|
|
||||||
|
|
||||||
s3.getContainers(function (err, containers) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
containers.forEach(function (c) {
|
|
||||||
console.log('amazon: ', c.name);
|
|
||||||
c.getFiles(function (err, files) {
|
|
||||||
files.forEach(function (f) {
|
|
||||||
console.log('....', f.name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
var stream = s3.uploadStream('con1','test.jpg');
|
|
||||||
var input = fs.createReadStream(path.join(__dirname, 'test.jpg')).pipe(stream);
|
|
||||||
|
|
||||||
|
|
||||||
var local = StorageService({
|
|
||||||
provider: 'filesystem',
|
|
||||||
root: path.join(__dirname, 'storage')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Container
|
|
||||||
|
|
||||||
local.getContainers(function (err, containers) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
containers.forEach(function (c) {
|
|
||||||
console.log('filesystem: ', c.name);
|
|
||||||
c.getFiles(function (err, files) {
|
|
||||||
files.forEach(function (f) {
|
|
||||||
console.log('....', f.name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.listen(app.get('port'));
|
||||||
|
console.log('http://127.0.0.1:' + app.get('port'));
|
||||||
|
|
|
@ -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,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-storage-service">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>
|
|
@ -1,78 +1,78 @@
|
||||||
var StorageService = require('../');
|
var StorageService = require('../').StorageService;
|
||||||
|
var providers = require('./providers.json');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
app.configure(function () {
|
app.configure(function () {
|
||||||
app.set('port', process.env.PORT || 3001);
|
app.set('port', process.env.PORT || 3001);
|
||||||
app.set('views', __dirname + '/views');
|
app.set('views', __dirname + '/views');
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.use(express.favicon());
|
app.use(express.favicon());
|
||||||
// app.use(express.logger('dev'));
|
// app.use(express.logger('dev'));
|
||||||
app.use(express.methodOverride());
|
app.use(express.methodOverride());
|
||||||
app.use(app.router);
|
app.use(app.router);
|
||||||
});
|
});
|
||||||
|
|
||||||
var handler = new StorageService(
|
var handler = new StorageService(
|
||||||
{
|
{
|
||||||
provider: 'amazon',
|
provider: 'amazon',
|
||||||
key: 'your-amazon-key',
|
key: providers.amazon.key,
|
||||||
keyId: 'your-amazon-key-id'
|
keyId: providers.amazon.keyId
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/', function (req, res, next) {
|
app.get('/', function (req, res, next) {
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
var form = "<html><body><h1>Storage Service Demo</h1>" +
|
var form = "<html><body><h1>Storage Service Demo</h1>" +
|
||||||
"<a href='/download'>List all containers</a><p>" +
|
"<a href='/download'>List all containers</a><p>" +
|
||||||
"Upload to container con1: <p>" +
|
"Upload to container con1: <p>" +
|
||||||
"<form method='POST' enctype='multipart/form-data' action='/upload/con1'>"
|
"<form method='POST' enctype='multipart/form-data' action='/upload/con1'>"
|
||||||
+ "File to upload: <input type=file name=uploadedFiles multiple=true><br>"
|
+ "File to upload: <input type=file name=uploadedFiles multiple=true><br>"
|
||||||
+ "Notes about the file: <input type=text name=note><br>"
|
+ "Notes about the file: <input type=text name=note><br>"
|
||||||
+ "<input type=submit value=Upload></form>" +
|
+ "<input type=submit value=Upload></form>" +
|
||||||
"</body></html>";
|
"</body></html>";
|
||||||
res.send(form);
|
res.send(form);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/upload/:container', function (req, res, next) {
|
app.post('/upload/:container', function (req, res, next) {
|
||||||
handler.upload(req, res, function (err, result) {
|
handler.upload(req, res, function (err, result) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
res.send(200, result);
|
res.send(200, result);
|
||||||
} else {
|
} else {
|
||||||
res.send(500, err);
|
res.send(500, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download', function (req, res, next) {
|
app.get('/download', function (req, res, next) {
|
||||||
handler.getContainers(function (err, containers) {
|
handler.getContainers(function (err, containers) {
|
||||||
var html = "<html><body><h1>Containers</h1><ul>";
|
var html = "<html><body><h1>Containers</h1><ul>";
|
||||||
containers.forEach(function (f) {
|
containers.forEach(function (f) {
|
||||||
html += "<li><a href='/download/" + f.name + "'>" + f.name + "</a></li>"
|
html += "<li><a href='/download/" + f.name + "'>" + f.name + "</a></li>"
|
||||||
});
|
|
||||||
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
|
||||||
res.send(200, html);
|
|
||||||
});
|
});
|
||||||
|
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
||||||
|
res.send(200, html);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download/:container', function (req, res, next) {
|
app.get('/download/:container', function (req, res, next) {
|
||||||
handler.getFiles(req.params.container, function (err, files) {
|
handler.getFiles(req.params.container, function (err, files) {
|
||||||
var html = "<html><body><h1>Files in container " + req.params.container + "</h1><ul>";
|
var html = "<html><body><h1>Files in container " + req.params.container + "</h1><ul>";
|
||||||
files.forEach(function (f) {
|
files.forEach(function (f) {
|
||||||
html += "<li><a href='/download/" + f.container + "/" + f.name + "'>" + f.container + "/" + f.name + "</a></li>"
|
html += "<li><a href='/download/" + f.container + "/" + f.name + "'>" + f.container + "/" + f.name + "</a></li>"
|
||||||
});
|
|
||||||
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
|
||||||
res.send(200, html);
|
|
||||||
});
|
});
|
||||||
|
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
||||||
|
res.send(200, html);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download/:container/:file', function (req, res, next) {
|
app.get('/download/:container/:file', function (req, res, next) {
|
||||||
handler.download(req, res, function (err, result) {
|
handler.download(req.params.container, req.params.file, res, function (err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.send(500, err);
|
res.send(500, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(app.get('port'));
|
app.listen(app.get('port'));
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
var StorageService = require('../');
|
var StorageService = require('../').StorageService;
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
app.configure(function () {
|
app.configure(function () {
|
||||||
app.set('port', process.env.PORT || 3001);
|
app.set('port', process.env.PORT || 3001);
|
||||||
app.set('views', __dirname + '/views');
|
app.set('views', __dirname + '/views');
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.use(express.favicon());
|
app.use(express.favicon());
|
||||||
// app.use(express.logger('dev'));
|
// app.use(express.logger('dev'));
|
||||||
app.use(express.methodOverride());
|
app.use(express.methodOverride());
|
||||||
app.use(app.router);
|
app.use(app.router);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the container
|
// Create the container
|
||||||
|
@ -20,58 +20,58 @@ mkdirp.sync('/tmp/storage/con1');
|
||||||
var handler = new StorageService({provider: 'filesystem', root: '/tmp/storage'});
|
var handler = new StorageService({provider: 'filesystem', root: '/tmp/storage'});
|
||||||
|
|
||||||
app.get('/', function (req, res, next) {
|
app.get('/', function (req, res, next) {
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
var form = "<html><body><h1>Storage Service Demo</h1>" +
|
var form = "<html><body><h1>Storage Service Demo</h1>" +
|
||||||
"<a href='/download'>List all containers</a><p>" +
|
"<a href='/download'>List all containers</a><p>" +
|
||||||
"Upload to container con1: <p>" +
|
"Upload to container con1: <p>" +
|
||||||
"<form method='POST' enctype='multipart/form-data' action='/upload/con1'>"
|
"<form method='POST' enctype='multipart/form-data' action='/upload/con1'>"
|
||||||
+ "File to upload: <input type=file name=uploadedFiles multiple=true><br>"
|
+ "File to upload: <input type=file name=uploadedFiles multiple=true><br>"
|
||||||
+ "Notes about the file: <input type=text name=note><br>"
|
+ "Notes about the file: <input type=text name=note><br>"
|
||||||
+ "<input type=submit value=Upload></form>" +
|
+ "<input type=submit value=Upload></form>" +
|
||||||
"</body></html>";
|
"</body></html>";
|
||||||
res.send(form);
|
res.send(form);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/upload/:container', function (req, res, next) {
|
app.post('/upload/:container', function (req, res, next) {
|
||||||
handler.upload(req, res, function (err, result) {
|
handler.upload(req, res, function (err, result) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
res.send(200, result);
|
res.send(200, result);
|
||||||
} else {
|
} else {
|
||||||
res.send(500, err);
|
res.send(500, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download', function (req, res, next) {
|
app.get('/download', function (req, res, next) {
|
||||||
handler.getContainers(function (err, containers) {
|
handler.getContainers(function (err, containers) {
|
||||||
var html = "<html><body><h1>Containers</h1><ul>";
|
var html = "<html><body><h1>Containers</h1><ul>";
|
||||||
containers.forEach(function (f) {
|
containers.forEach(function (f) {
|
||||||
html += "<li><a href='/download/" + f.name + "'>" + f.name + "</a></li>"
|
html += "<li><a href='/download/" + f.name + "'>" + f.name + "</a></li>"
|
||||||
});
|
|
||||||
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
|
||||||
res.send(200, html);
|
|
||||||
});
|
});
|
||||||
|
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
||||||
|
res.send(200, html);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download/:container', function (req, res, next) {
|
app.get('/download/:container', function (req, res, next) {
|
||||||
handler.getFiles(req.params.container, function (err, files) {
|
handler.getFiles(req.params.container, function (err, files) {
|
||||||
var html = "<html><body><h1>Files in container " + req.params.container + "</h1><ul>";
|
var html = "<html><body><h1>Files in container " + req.params.container + "</h1><ul>";
|
||||||
files.forEach(function (f) {
|
files.forEach(function (f) {
|
||||||
html += "<li><a href='/download/" + f.container + "/" + f.name + "'>" + f.container + "/" + f.name + "</a></li>"
|
html += "<li><a href='/download/" + f.container + "/" + f.name + "'>" + f.container + "/" + f.name + "</a></li>"
|
||||||
});
|
|
||||||
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
|
||||||
res.send(200, html);
|
|
||||||
});
|
});
|
||||||
|
html += "</ul><p><a href='/'>Home</a></p></body></html>";
|
||||||
|
res.send(200, html);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/download/:container/:file', function (req, res, next) {
|
app.get('/download/:container/:file', function (req, res, next) {
|
||||||
handler.download(req, res, function (err, result) {
|
handler.download(req.params.container, req.params.file, res, function (err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.send(500, err);
|
res.send(500, err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(app.get('port'));
|
app.listen(app.get('port'));
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
var StorageConnector = require('./lib/storage-connector');
|
||||||
|
StorageConnector.StorageService = require('./lib/storage-service');
|
||||||
|
|
||||||
|
module.exports = StorageConnector;
|
||||||
|
|
100
lib/factory.js
100
lib/factory.js
|
@ -1,22 +1,84 @@
|
||||||
|
var pkgcloud = require('pkgcloud');
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Patch the prototype for a given subclass of Container or File
|
||||||
|
* @param {Function} cls The subclass
|
||||||
|
*/
|
||||||
|
function patchBaseClass(cls) {
|
||||||
|
var proto = cls.prototype;
|
||||||
|
var found = false;
|
||||||
|
// Find the prototype that owns the _setProperties method
|
||||||
|
while (proto
|
||||||
|
&& proto.constructor !== pkgcloud.storage.Container
|
||||||
|
&& proto.constructor !== pkgcloud.storage.File) {
|
||||||
|
if (proto.hasOwnProperty('_setProperties')) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
proto = Object.getPrototypeOf(proto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
proto = cls.prototype;
|
||||||
|
}
|
||||||
|
var m1 = proto._setProperties;
|
||||||
|
proto._setProperties = function (details) {
|
||||||
|
// Use an empty object to receive the calculated properties from details
|
||||||
|
var receiver = {};
|
||||||
|
m1.call(receiver, details);
|
||||||
|
// Apply the calculated properties to this
|
||||||
|
for (var p in receiver) {
|
||||||
|
this[p] = receiver[p];
|
||||||
|
}
|
||||||
|
// Keep references to raw and the calculated properties
|
||||||
|
this._rawMetadata = details;
|
||||||
|
this._metadata = receiver; // Use _metadata to avoid conflicts
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.toJSON = function () {
|
||||||
|
return this._metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.getMetadata = function () {
|
||||||
|
return this._metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.getRawMetadata = function () {
|
||||||
|
return this._rawMetadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
* Patch the pkgcloud Container/File classes so that the metadata are separately
|
||||||
|
* stored for JSON serialization
|
||||||
|
*
|
||||||
|
* @param {String} provider The name of the storage provider
|
||||||
|
*/
|
||||||
|
function patchContainerAndFileClass(provider) {
|
||||||
|
var storageProvider = getProvider(provider).storage;
|
||||||
|
|
||||||
|
patchBaseClass(storageProvider.Container);
|
||||||
|
patchBaseClass(storageProvider.File);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create a client instance based on the options
|
* Create a client instance based on the options
|
||||||
* @param options
|
* @param options
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
function createClient(options) {
|
function createClient(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
var provider = options.provider || 'filesystem';
|
var provider = options.provider || 'filesystem';
|
||||||
var handler;
|
var handler;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to load the provider from providers folder
|
// Try to load the provider from providers folder
|
||||||
handler = require('./providers/' + provider);
|
handler = require('./providers/' + provider);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Fall back to pkgcloud
|
// Fall back to pkgcloud
|
||||||
handler = require('pkgcloud').storage;
|
handler = require('pkgcloud').storage;
|
||||||
}
|
}
|
||||||
|
patchContainerAndFileClass(provider);
|
||||||
return handler.createClient(options);
|
return handler.createClient(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,13 +87,13 @@ function createClient(options) {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
function getProvider(provider) {
|
function getProvider(provider) {
|
||||||
try {
|
try {
|
||||||
// Try to load the provider from providers folder
|
// Try to load the provider from providers folder
|
||||||
return require('./providers/' + provider);
|
return require('./providers/' + provider);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Fall back to pkgcloud
|
// Fall back to pkgcloud
|
||||||
return require('pkgcloud').providers[provider];
|
return require('pkgcloud').providers[provider];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.createClient = createClient;
|
module.exports.createClient = createClient;
|
||||||
|
|
150
lib/index.js
150
lib/index.js
|
@ -1,150 +0,0 @@
|
||||||
var factory = require('./factory');
|
|
||||||
var handler = require('./storage-handler');
|
|
||||||
|
|
||||||
var storage = require('pkgcloud').storage;
|
|
||||||
|
|
||||||
module.exports = StorageService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param options The options to create a provider
|
|
||||||
* @returns {StorageService}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function StorageService(options) {
|
|
||||||
if (!(this instanceof StorageService)) {
|
|
||||||
return new StorageService(options);
|
|
||||||
}
|
|
||||||
this.provider = options.provider;
|
|
||||||
this.client = factory.createClient(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.getContainers = function (cb) {
|
|
||||||
return this.client.getContainers(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.createContainer = function (options, cb) {
|
|
||||||
options = options || {};
|
|
||||||
if('object' === typeof options && !(options instanceof storage.Container)) {
|
|
||||||
var Container = factory.getProvider(this.provider).Container;
|
|
||||||
options = new Container(this.client, options);
|
|
||||||
}
|
|
||||||
return this.client.createContainer(options, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.destroyContainer = function (container, cb) {
|
|
||||||
return this.client.destroyContainer(container, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.getContainer = function (container, cb) {
|
|
||||||
return this.client.getContainer(container, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// File related functions
|
|
||||||
StorageService.prototype.uploadStream = function (container, file, options, cb) {
|
|
||||||
if(!cb && typeof options === 'function') {
|
|
||||||
cb = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
options = options || {};
|
|
||||||
if(container) options.container = container;
|
|
||||||
if(file) options.remote = file;
|
|
||||||
|
|
||||||
return this.client.upload(options, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.downloadStream = function (container, file, options, cb) {
|
|
||||||
if(!cb && typeof options === 'function') {
|
|
||||||
cb = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
options = options || {};
|
|
||||||
if(container) options.container = container;
|
|
||||||
if(file) options.remote = file;
|
|
||||||
|
|
||||||
return this.client.download(options, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.getFiles = function (container, download, cb) {
|
|
||||||
return this.client.getFiles(container, download, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.getFile = function (container, file, cb) {
|
|
||||||
return this.client.getFile(container, file, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.removeFile = function (container, file, cb) {
|
|
||||||
return this.client.removeFile(container, file, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.upload = function (req, res, cb) {
|
|
||||||
return handler.upload(this.client, req, res, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.prototype.download = function (req, res, cb) {
|
|
||||||
return handler.download(this.client, req, res, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageService.modelName = 'storage';
|
|
||||||
|
|
||||||
StorageService.prototype.getContainers.shared = true;
|
|
||||||
StorageService.prototype.getContainers.accepts = [];
|
|
||||||
StorageService.prototype.getContainers.returns = {arg: 'containers', type: 'array'};
|
|
||||||
StorageService.prototype.getContainers.http = [
|
|
||||||
{verb: 'get', path: '/'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.getContainer.shared = true;
|
|
||||||
StorageService.prototype.getContainer.accepts = [{arg: 'container', type: 'string'}];
|
|
||||||
StorageService.prototype.getContainer.returns = {arg: 'container', type: 'object'};
|
|
||||||
StorageService.prototype.getContainer.http = [
|
|
||||||
{verb: 'get', path: '/:container'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.createContainer.shared = true;
|
|
||||||
StorageService.prototype.createContainer.accepts = [{arg: 'options', type: 'object'}];
|
|
||||||
StorageService.prototype.createContainer.returns = {arg: 'container', type: 'object'};
|
|
||||||
StorageService.prototype.createContainer.http = [
|
|
||||||
{verb: 'post', path: '/'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.destroyContainer.shared = true;
|
|
||||||
StorageService.prototype.destroyContainer.accepts = [{arg: 'container', type: 'string'}];
|
|
||||||
StorageService.prototype.destroyContainer.returns = {};
|
|
||||||
StorageService.prototype.destroyContainer.http = [
|
|
||||||
{verb: 'delete', path: '/:container'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.getFiles.shared = true;
|
|
||||||
StorageService.prototype.getFiles.accepts = [{arg: 'container', type: 'string'}];
|
|
||||||
StorageService.prototype.getFiles.returns = {arg: 'files', type: 'array'};
|
|
||||||
StorageService.prototype.getFiles.http = [
|
|
||||||
{verb: 'get', path: '/:container/files'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.getFile.shared = true;
|
|
||||||
StorageService.prototype.getFile.accepts = [{arg: 'container', type: 'string'}, {arg: 'file', type: 'string'}];
|
|
||||||
StorageService.prototype.getFile.returns = {arg: 'file', type: 'object'};
|
|
||||||
StorageService.prototype.getFile.http = [
|
|
||||||
{verb: 'get', path: '/:container/files/:file'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.removeFile.shared = true;
|
|
||||||
StorageService.prototype.removeFile.accepts = [{arg: 'container', type: 'string'}, {arg: 'file', type: 'string'}];
|
|
||||||
StorageService.prototype.removeFile.returns = {};
|
|
||||||
StorageService.prototype.removeFile.http = [
|
|
||||||
{verb: 'delete', path: '/:container/files/:file'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.upload.shared = true;
|
|
||||||
StorageService.prototype.upload.accepts = [{arg: 'req', type: 'undefined', 'http': {source: 'req'}}];
|
|
||||||
StorageService.prototype.upload.returns = {arg: 'result', type: 'object'};
|
|
||||||
StorageService.prototype.upload.http = [
|
|
||||||
{verb: 'post', path: '/:container/upload/:file'}
|
|
||||||
];
|
|
||||||
|
|
||||||
StorageService.prototype.download.shared = true;
|
|
||||||
StorageService.prototype.download.accepts = [{arg: 'req', type: 'undefined', 'http': {source: 'req'}}];
|
|
||||||
StorageService.prototype.download.returns = {arg: 'res', type: 'stream'};
|
|
||||||
StorageService.prototype.download.http = [
|
|
||||||
{verb: 'get', path: '/:container/download/:file'}
|
|
||||||
];
|
|
|
@ -4,15 +4,15 @@ var util = require('util');
|
||||||
exports.Container = Container;
|
exports.Container = Container;
|
||||||
|
|
||||||
function Container(client, details) {
|
function Container(client, details) {
|
||||||
base.Container.call(this, client, details);
|
base.Container.call(this, client, details);
|
||||||
};
|
}
|
||||||
|
|
||||||
util.inherits(Container, base.Container);
|
util.inherits(Container, base.Container);
|
||||||
|
|
||||||
Container.prototype._setProperties = function(details) {
|
Container.prototype._setProperties = function (details) {
|
||||||
for(var k in details) {
|
for (var k in details) {
|
||||||
if(typeof details[k] !== 'function') {
|
if (typeof details[k] !== 'function') {
|
||||||
this[k] = details[k];
|
this[k] = details[k];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -4,15 +4,15 @@ var util = require('util');
|
||||||
exports.File = File;
|
exports.File = File;
|
||||||
|
|
||||||
function File(client, details) {
|
function File(client, details) {
|
||||||
base.File.call(this, client, details);
|
base.File.call(this, client, details);
|
||||||
};
|
}
|
||||||
|
|
||||||
util.inherits(File, base.File);
|
util.inherits(File, base.File);
|
||||||
|
|
||||||
File.prototype._setProperties = function(details) {
|
File.prototype._setProperties = function (details) {
|
||||||
for(var k in details) {
|
for (var k in details) {
|
||||||
if(typeof details[k] !== 'function') {
|
if (typeof details[k] !== 'function') {
|
||||||
this[k] = details[k];
|
this[k] = details[k];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
|
@ -3,215 +3,251 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
async = require('async'),
|
async = require('async'),
|
||||||
File = require('./file').File,
|
File = require('./file').File,
|
||||||
Container = require('./container').Container;
|
Container = require('./container').Container;
|
||||||
|
|
||||||
|
module.exports.storage = module.exports; // To make it consistent with pkgcloud
|
||||||
|
|
||||||
module.exports.File = File;
|
module.exports.File = File;
|
||||||
module.exports.Container = Container;
|
module.exports.Container = Container;
|
||||||
module.exports.Client = FileSystemProvider;
|
module.exports.Client = FileSystemProvider;
|
||||||
module.exports.createClient = function (options) {
|
module.exports.createClient = function (options) {
|
||||||
return new FileSystemProvider(options);
|
return new FileSystemProvider(options);
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileSystemProvider(options) {
|
function FileSystemProvider(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
this.root = options.root;
|
this.root = options.root;
|
||||||
var exists = fs.existsSync(this.root);
|
var exists = fs.existsSync(this.root);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new Error('Path does not exist: ' + this.root);
|
throw new Error('FileSystemProvider: Path does not exist: ' + this.root);
|
||||||
}
|
}
|
||||||
var stat = fs.statSync(this.root);
|
var stat = fs.statSync(this.root);
|
||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
throw new Error('Invalid directory: ' + this.root);
|
throw new Error('FileSystemProvider: Invalid directory: ' + this.root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var namePattern = new RegExp('[^' + path.sep + '/]+');
|
var namePattern = new RegExp('[^' + path.sep + '/]+');
|
||||||
|
|
||||||
function validateName(name, cb) {
|
function validateName(name, cb) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name)));
|
cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name)));
|
||||||
if(!cb) {
|
if (!cb) {
|
||||||
console.error('Invalid name: ', name);
|
console.error('FileSystemProvider: Invalid name: ', name);
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
var match = namePattern.exec(name);
|
return false;
|
||||||
if (match && match.index === 0 && match[0].length === name.length) {
|
}
|
||||||
return true;
|
var match = namePattern.exec(name);
|
||||||
} else {
|
if (match && match.index === 0 && match[0].length === name.length) {
|
||||||
cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name)));
|
return true;
|
||||||
if(!cb) {
|
} else {
|
||||||
console.error('Invalid name: ', name);
|
cb && process.nextTick(cb.bind(null,
|
||||||
}
|
new Error('FileSystemProvider: Invalid name: ' + name)));
|
||||||
return false;
|
if (!cb) {
|
||||||
|
console.error('FileSystemProvider: Invalid name: ', name);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container related functions
|
/*!
|
||||||
FileSystemProvider.prototype.getContainers = function (cb) {
|
* Populate the metadata from file stat into props
|
||||||
var self = this;
|
* @param {fs.Stats} stat The file stat instance
|
||||||
fs.readdir(self.root, function (err, files) {
|
* @param {Object} props The metadata object
|
||||||
var containers = [];
|
*/
|
||||||
var tasks = [];
|
function populateMetadata(stat, props) {
|
||||||
files.forEach(function (f) {
|
for (var p in stat) {
|
||||||
tasks.push(fs.stat.bind(null, path.join(self.root, f)));
|
switch (p) {
|
||||||
});
|
case 'size':
|
||||||
async.parallel(tasks, function (err, stats) {
|
case 'atime':
|
||||||
if (err) {
|
case 'mtime':
|
||||||
cb && cb(err);
|
case 'ctime':
|
||||||
} else {
|
props[p] = stat[p];
|
||||||
stats.forEach(function (stat, index) {
|
break;
|
||||||
if (stat.isDirectory()) {
|
}
|
||||||
var name = files[index];
|
}
|
||||||
var props = {name: name};
|
|
||||||
for (var p in stat) {
|
|
||||||
props[p] = stat[p];
|
|
||||||
}
|
|
||||||
var container = new Container(self, props);
|
|
||||||
containers.push(container);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cb && cb(err, containers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileSystemProvider.prototype.getContainers = function (cb) {
|
||||||
|
var self = this;
|
||||||
|
fs.readdir(self.root, function (err, files) {
|
||||||
|
var containers = [];
|
||||||
|
var tasks = [];
|
||||||
|
files.forEach(function (f) {
|
||||||
|
tasks.push(fs.stat.bind(fs, path.join(self.root, f)));
|
||||||
|
});
|
||||||
|
async.parallel(tasks, function (err, stats) {
|
||||||
|
if (err) {
|
||||||
|
cb && cb(err);
|
||||||
|
} else {
|
||||||
|
stats.forEach(function (stat, index) {
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
var name = files[index];
|
||||||
|
var props = {name: name};
|
||||||
|
populateMetadata(stat, props);
|
||||||
|
var container = new Container(self, props);
|
||||||
|
containers.push(container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cb && cb(err, containers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.createContainer = function (options, cb) {
|
FileSystemProvider.prototype.createContainer = function (options, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var name = options.name;
|
var name = options.name;
|
||||||
validateName(name, cb) && fs.mkdir(path.join(this.root, name), options, function (err) {
|
var dir = path.join(this.root, name);
|
||||||
cb && cb(err, new Container(self, {name: name}));
|
validateName(name, cb) && fs.mkdir(dir, options, function (err) {
|
||||||
|
if(err) {
|
||||||
|
return cb && cb(err);
|
||||||
|
}
|
||||||
|
fs.stat(dir, function (err, stat) {
|
||||||
|
var container = null;
|
||||||
|
if (!err) {
|
||||||
|
var props = {name: name};
|
||||||
|
populateMetadata(stat, props);
|
||||||
|
container = new Container(self, props);
|
||||||
|
}
|
||||||
|
cb && cb(err, container);
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.destroyContainer = function (containerName, cb) {
|
FileSystemProvider.prototype.destroyContainer = function (containerName, cb) {
|
||||||
if (!validateName(containerName, cb)) return;
|
if (!validateName(containerName, cb)) return;
|
||||||
|
|
||||||
var dir = path.join(this.root, containerName);
|
var dir = path.join(this.root, containerName);
|
||||||
fs.readdir(dir, function (err, files) {
|
fs.readdir(dir, function (err, files) {
|
||||||
var tasks = [];
|
var tasks = [];
|
||||||
files.forEach(function (f) {
|
files.forEach(function (f) {
|
||||||
tasks.push(fs.unlink.bind(null, path.join(dir, f)));
|
tasks.push(fs.unlink.bind(fs, path.join(dir, f)));
|
||||||
});
|
|
||||||
async.parallel(tasks, function (err) {
|
|
||||||
if (err) {
|
|
||||||
cb && cb(err);
|
|
||||||
} else {
|
|
||||||
fs.rmdir(dir, cb);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
async.parallel(tasks, function (err) {
|
||||||
|
if (err) {
|
||||||
|
cb && cb(err);
|
||||||
|
} else {
|
||||||
|
fs.rmdir(dir, cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.getContainer = function (containerName, cb) {
|
FileSystemProvider.prototype.getContainer = function (containerName, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!validateName(containerName, cb)) return;
|
if (!validateName(containerName, cb)) return;
|
||||||
var dir = path.join(this.root, containerName);
|
var dir = path.join(this.root, containerName);
|
||||||
fs.stat(dir, function (err, stat) {
|
fs.stat(dir, function (err, stat) {
|
||||||
var container = null;
|
var container = null;
|
||||||
if (!err) {
|
if (!err) {
|
||||||
var props = {name: containerName};
|
var props = {name: containerName};
|
||||||
for (var p in stat) {
|
populateMetadata(stat, props);
|
||||||
props[p] = stat[p];
|
container = new Container(self, props);
|
||||||
}
|
}
|
||||||
container = new Container(self, props);
|
cb && cb(err, container);
|
||||||
}
|
});
|
||||||
cb && cb(err, container);
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// File related functions
|
// File related functions
|
||||||
FileSystemProvider.prototype.upload = function (options, cb) {
|
FileSystemProvider.prototype.upload = function (options, cb) {
|
||||||
var container = options.container;
|
var container = options.container;
|
||||||
if (!validateName(container, cb)) return;
|
if (!validateName(container, cb)) return;
|
||||||
var file = options.remote;
|
var file = options.remote;
|
||||||
if (!validateName(file, cb)) return;
|
if (!validateName(file, cb)) return;
|
||||||
var filePath = path.join(this.root, container, file);
|
var filePath = path.join(this.root, container, file);
|
||||||
|
|
||||||
var fileOpts = {flags: 'w+',
|
var fileOpts = {flags: options.flags || 'w+',
|
||||||
encoding: null,
|
encoding: options.encoding || null,
|
||||||
mode: 0666 };
|
mode: options.mode || 0666
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
return fs.createWriteStream(filePath, fileOpts);
|
return fs.createWriteStream(filePath, fileOpts);
|
||||||
}
|
} catch (e) {
|
||||||
|
cb && cb(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.download = function (options, cb) {
|
FileSystemProvider.prototype.download = function (options, cb) {
|
||||||
var container = options.container;
|
var container = options.container;
|
||||||
if (!validateName(container, cb)) return;
|
if (!validateName(container, cb)) return;
|
||||||
var file = options.remote;
|
var file = options.remote;
|
||||||
if (!validateName(file, cb)) return;
|
if (!validateName(file, cb)) return;
|
||||||
|
|
||||||
var filePath = path.join(this.root, container, file);
|
var filePath = path.join(this.root, container, file);
|
||||||
|
|
||||||
var fileOpts = {flags: 'r',
|
var fileOpts = {flags: 'r',
|
||||||
autoClose: true };
|
autoClose: true };
|
||||||
|
|
||||||
|
try {
|
||||||
return fs.createReadStream(filePath, fileOpts);
|
return fs.createReadStream(filePath, fileOpts);
|
||||||
|
} catch (e) {
|
||||||
}
|
cb && cb(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.getFiles = function (container, download, cb) {
|
FileSystemProvider.prototype.getFiles = function (container, download, cb) {
|
||||||
if (typeof download === 'function' && !(download instanceof RegExp)) {
|
if (typeof download === 'function' && !(download instanceof RegExp)) {
|
||||||
cb = download;
|
cb = download;
|
||||||
download = false;
|
download = false;
|
||||||
}
|
}
|
||||||
var self = this;
|
var self = this;
|
||||||
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) {
|
||||||
var files = [];
|
var files = [];
|
||||||
var tasks = [];
|
var tasks = [];
|
||||||
entries.forEach(function (f) {
|
entries.forEach(function (f) {
|
||||||
tasks.push(fs.stat.bind(null, path.join(dir, f)));
|
tasks.push(fs.stat.bind(fs, path.join(dir, f)));
|
||||||
});
|
|
||||||
async.parallel(tasks, function (err, stats) {
|
|
||||||
if (err) {
|
|
||||||
cb && cb(err);
|
|
||||||
} else {
|
|
||||||
stats.forEach(function (stat, index) {
|
|
||||||
if (stat.isFile()) {
|
|
||||||
var props = {container: container, name: entries[index]};
|
|
||||||
for (var p in stat) {
|
|
||||||
props[p] = stat[p];
|
|
||||||
}
|
|
||||||
var file = new File(self, props);
|
|
||||||
files.push(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cb && cb(err, files);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
async.parallel(tasks, function (err, stats) {
|
||||||
}
|
if (err) {
|
||||||
|
cb && cb(err);
|
||||||
|
} else {
|
||||||
|
stats.forEach(function (stat, index) {
|
||||||
|
if (stat.isFile()) {
|
||||||
|
var props = {container: container, name: entries[index]};
|
||||||
|
populateMetadata(stat, props);
|
||||||
|
var file = new File(self, props);
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cb && cb(err, files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.getFile = function (container, file, cb) {
|
FileSystemProvider.prototype.getFile = function (container, file, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!validateName(container, cb)) return;
|
if (!validateName(container, cb)) return;
|
||||||
if (!validateName(file, cb)) return;
|
if (!validateName(file, cb)) return;
|
||||||
var filePath = path.join(this.root, container, file);
|
var filePath = path.join(this.root, container, file);
|
||||||
fs.stat(filePath, function (err, stat) {
|
fs.stat(filePath, function (err, stat) {
|
||||||
var f = null;
|
var f = null;
|
||||||
if (!err) {
|
if (!err) {
|
||||||
var props = {container: container, name: file};
|
var props = {container: container, name: file};
|
||||||
for (var p in stat) {
|
populateMetadata(stat, props);
|
||||||
props[p] = stat[p];
|
f = new File(self, props);
|
||||||
}
|
}
|
||||||
f = new File(self, props);
|
cb && cb(err, f);
|
||||||
}
|
});
|
||||||
cb && cb(err, f);
|
};
|
||||||
});
|
|
||||||
}
|
FileSystemProvider.prototype.getUrl = function (options) {
|
||||||
|
options = options || {};
|
||||||
|
var filePath = path.join(this.root, options.container, options.path);
|
||||||
|
return filePath;
|
||||||
|
};
|
||||||
|
|
||||||
FileSystemProvider.prototype.removeFile = function (container, file, cb) {
|
FileSystemProvider.prototype.removeFile = function (container, file, cb) {
|
||||||
if (!validateName(container, cb)) return;
|
if (!validateName(container, cb)) return;
|
||||||
if (!validateName(file, cb)) return;
|
if (!validateName(file, cb)) return;
|
||||||
|
|
||||||
var filePath = path.join(this.root, container, file);
|
var filePath = path.join(this.root, container, file);
|
||||||
fs.unlink(filePath, cb);
|
fs.unlink(filePath, cb);
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
var StorageService = require('./index');
|
var StorageService = require('./storage-service');
|
||||||
/**
|
/**
|
||||||
* Export the initialize method to Loopback data
|
* Export the initialize method to Loopback data
|
||||||
* @param dataSource
|
* @param dataSource
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
exports.initialize = function (dataSource, callback) {
|
exports.initialize = function (dataSource, callback) {
|
||||||
var settings = dataSource.settings || {};
|
var settings = dataSource.settings || {};
|
||||||
|
|
||||||
var connector = new StorageService(settings);
|
var connector = new StorageService(settings);
|
||||||
dataSource.connector = connector;
|
dataSource.connector = connector;
|
||||||
dataSource.connector.dataSource = dataSource;
|
dataSource.connector.dataSource = dataSource;
|
||||||
|
|
||||||
connector.DataAccessObject = function() {};
|
connector.DataAccessObject = function () {
|
||||||
for (var m in StorageService.prototype) {
|
};
|
||||||
var method = StorageService.prototype[m];
|
for (var m in StorageService.prototype) {
|
||||||
if ('function' === typeof method) {
|
var method = StorageService.prototype[m];
|
||||||
connector.DataAccessObject[m] = method.bind(connector);
|
if ('function' === typeof method) {
|
||||||
for(var k in method) {
|
connector.DataAccessObject[m] = method.bind(connector);
|
||||||
connector.DataAccessObject[m][k] = method[k];
|
for (var k in method) {
|
||||||
}
|
connector.DataAccessObject[m][k] = method[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connector.define = function(model, properties, settings) {};
|
connector.define = function (model, properties, settings) {
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -4,122 +4,124 @@ var StringDecoder = require('string_decoder').StringDecoder;
|
||||||
/**
|
/**
|
||||||
* Handle multipart/form-data upload to the storage service
|
* Handle multipart/form-data upload to the storage service
|
||||||
* @param provider The storage service provider
|
* @param provider The storage service provider
|
||||||
* @param req The HTTP request
|
* @param {Request} req The HTTP request
|
||||||
* @param res The HTTP response
|
* @param {Response} res The HTTP response
|
||||||
* @param cb The callback
|
* @param {String} container The container name
|
||||||
|
* @param {Function} cb The callback
|
||||||
*/
|
*/
|
||||||
exports.upload = function (provider, req, res, cb) {
|
exports.upload = function (provider, req, res, container, cb) {
|
||||||
var form = new IncomingForm(this.options);
|
var form = new IncomingForm(this.options);
|
||||||
var container = req.params.container;
|
container = container || req.params.container;
|
||||||
var fields = {}, files = {};
|
var fields = {}, files = {};
|
||||||
form.handlePart = function (part) {
|
form.handlePart = function (part) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (part.filename === undefined) {
|
if (part.filename === undefined) {
|
||||||
var value = ''
|
var value = ''
|
||||||
, decoder = new StringDecoder(this.encoding);
|
, decoder = new StringDecoder(this.encoding);
|
||||||
|
|
||||||
part.on('data', function (buffer) {
|
part.on('data', function (buffer) {
|
||||||
self._fieldsSize += buffer.length;
|
self._fieldsSize += buffer.length;
|
||||||
if (self._fieldsSize > self.maxFieldsSize) {
|
if (self._fieldsSize > self.maxFieldsSize) {
|
||||||
self._error(new Error('maxFieldsSize exceeded, received ' + self._fieldsSize + ' bytes of field data'));
|
self._error(new Error('maxFieldsSize exceeded, received ' + self._fieldsSize + ' bytes of field data'));
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
value += decoder.write(buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
part.on('end', function () {
|
|
||||||
var values = fields[part.name];
|
|
||||||
if(values === undefined) {
|
|
||||||
values = [value];
|
|
||||||
fields[part.name] = values;
|
|
||||||
} else {
|
|
||||||
values.push(value);
|
|
||||||
}
|
|
||||||
self.emit('field', part.name, value);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
value += decoder.write(buffer);
|
||||||
|
});
|
||||||
|
|
||||||
this._flushing++;
|
part.on('end', function () {
|
||||||
|
var values = fields[part.name];
|
||||||
var file = {
|
if (values === undefined) {
|
||||||
container: container,
|
values = [value];
|
||||||
name: part.filename,
|
fields[part.name] = values;
|
||||||
type: part.mime
|
} else {
|
||||||
};
|
values.push(value);
|
||||||
|
|
||||||
self.emit('fileBegin', part.name, file);
|
|
||||||
|
|
||||||
var headers = {};
|
|
||||||
if('content-type' in part.headers) {
|
|
||||||
headers['content-type'] = part.headers['content-type'];
|
|
||||||
}
|
}
|
||||||
var writer = provider.upload({container: container, remote: part.filename});
|
self.emit('field', part.name, value);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var endFunc = function () {
|
this._flushing++;
|
||||||
self._flushing--;
|
|
||||||
var values = files[part.name];
|
|
||||||
if(values === undefined) {
|
|
||||||
values = [file];
|
|
||||||
files[part.name] = values;
|
|
||||||
} else {
|
|
||||||
values.push(file);
|
|
||||||
}
|
|
||||||
self.emit('file', part.name, file);
|
|
||||||
self._maybeEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
var file = {
|
||||||
part.on('data', function (buffer) {
|
container: container,
|
||||||
self.pause();
|
name: part.filename,
|
||||||
writer.write(buffer, function () {
|
type: part.mime
|
||||||
// pkgcloud stream doesn't make callbacks
|
|
||||||
});
|
|
||||||
self.resume();
|
|
||||||
});
|
|
||||||
|
|
||||||
part.on('end', function () {
|
|
||||||
|
|
||||||
writer.end(); // pkgcloud stream doesn't make callbacks
|
|
||||||
endFunc();
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
part.pipe(writer, { end: false });
|
|
||||||
part.on("end", function() {
|
|
||||||
writer.end();
|
|
||||||
endFunc();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
form.parse(req, function (err, _fields, _files) {
|
self.emit('fileBegin', part.name, file);
|
||||||
if(err) {
|
|
||||||
console.error(err);
|
var headers = {};
|
||||||
}
|
if ('content-type' in part.headers) {
|
||||||
cb && cb(err, {files: files, fields: fields});
|
headers['content-type'] = part.headers['content-type'];
|
||||||
|
}
|
||||||
|
var writer = provider.upload({container: container, remote: part.filename});
|
||||||
|
|
||||||
|
var endFunc = function () {
|
||||||
|
self._flushing--;
|
||||||
|
var values = files[part.name];
|
||||||
|
if (values === undefined) {
|
||||||
|
values = [file];
|
||||||
|
files[part.name] = values;
|
||||||
|
} else {
|
||||||
|
values.push(file);
|
||||||
|
}
|
||||||
|
self.emit('file', part.name, file);
|
||||||
|
self._maybeEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
part.on('data', function (buffer) {
|
||||||
|
self.pause();
|
||||||
|
writer.write(buffer, function () {
|
||||||
|
// pkgcloud stream doesn't make callbacks
|
||||||
|
});
|
||||||
|
self.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
part.on('end', function () {
|
||||||
|
|
||||||
|
writer.end(); // pkgcloud stream doesn't make callbacks
|
||||||
|
endFunc();
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
part.pipe(writer, { end: false });
|
||||||
|
part.on("end", function () {
|
||||||
|
writer.end();
|
||||||
|
endFunc();
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
form.parse(req, function (err, _fields, _files) {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
cb && cb(err, {files: files, fields: fields});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle download from a container/file
|
* Handle download from a container/file
|
||||||
* @param provider The storage service provider
|
* @param provider The storage service provider
|
||||||
* @param req The HTTP request
|
* @param {Request} req The HTTP request
|
||||||
* @param res The HTTP response
|
* @param {Response} res The HTTP response
|
||||||
* @param cb The callback
|
* @param {String} container The container name
|
||||||
|
* @param {String} file The file name
|
||||||
|
* @param {Function} cb The callback
|
||||||
*/
|
*/
|
||||||
exports.download = function(provider, req, res, cb) {
|
exports.download = function (provider, req, res, container, file, cb) {
|
||||||
var reader = provider.download({
|
var reader = provider.download({
|
||||||
container: req.params.container,
|
container: container || req && req.params.container,
|
||||||
remote: req.params.file
|
remote: file || req && req.params.file
|
||||||
});
|
});
|
||||||
reader.pipe(res);
|
res.type(file);
|
||||||
reader.on('error', function(err) {
|
reader.pipe(res);
|
||||||
cb && cb(err);
|
reader.on('error', function (err) {
|
||||||
});
|
res.type('application/json');
|
||||||
reader.on('end', function(err, result) {
|
res.send(500, { error: err });
|
||||||
cb && cb(err, result);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,262 @@
|
||||||
|
var factory = require('./factory');
|
||||||
|
var handler = require('./storage-handler');
|
||||||
|
|
||||||
|
var storage = require('pkgcloud').storage;
|
||||||
|
|
||||||
|
module.exports = StorageService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage service constructor. Properties of options object depend on the storage service provider.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @options {Object} options The options to create a provider; see below;
|
||||||
|
* @prop {Object} connector <!-- What is this? -->
|
||||||
|
* @prop {String} provider Use 'filesystem' for local file system. Other supported values are: 'amazon', 'rackspace', 'azure', and 'openstack'.
|
||||||
|
* @prop {String} root With 'filesystem' provider, the path to the root of storage directory.
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
function StorageService(options) {
|
||||||
|
if (!(this instanceof StorageService)) {
|
||||||
|
return new StorageService(options);
|
||||||
|
}
|
||||||
|
this.provider = options.provider;
|
||||||
|
this.client = factory.createClient(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function map(obj) {
|
||||||
|
return obj;
|
||||||
|
/*
|
||||||
|
if (!obj || typeof obj !== 'object') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
var data = {};
|
||||||
|
for (var i in obj) {
|
||||||
|
if (obj.hasOwnProperty(i) && typeof obj[i] !== 'function'
|
||||||
|
&& typeof obj[i] !== 'object') {
|
||||||
|
if (i === 'newListener' || i === 'delimiter' || i === 'wildcard') {
|
||||||
|
// Skip properties from the base class
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
data[i] = obj[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all storage service containers.
|
||||||
|
* @param {Function} callback Callback function; parameters: err - error message, containers - object holding all containers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
StorageService.prototype.getContainers = function (cb) {
|
||||||
|
this.client.getContainers(function (err, containers) {
|
||||||
|
if (err) {
|
||||||
|
cb(err, containers);
|
||||||
|
} else {
|
||||||
|
cb(err, containers.map(function (c) {
|
||||||
|
return map(c);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new storage service container. Other option properties depend on the provider.
|
||||||
|
*
|
||||||
|
* @options {Object} options The options to create a provider; see below;
|
||||||
|
* @prop {Object} connector <!-- WHAT IS THIS? -->
|
||||||
|
* @prop {String} provider Storage service provider. Use 'filesystem' for local file system. Other supported values are: 'amazon', 'rackspace', 'azure', and 'openstack'.
|
||||||
|
* @prop {String} root With 'filesystem' provider, the path to the root of storage directory.
|
||||||
|
* @prop {String}
|
||||||
|
* @param {Function} callback Callback function.
|
||||||
|
*/
|
||||||
|
|
||||||
|
StorageService.prototype.createContainer = function (options, cb) {
|
||||||
|
options = options || {};
|
||||||
|
if ('object' === typeof options && !(options instanceof storage.Container)) {
|
||||||
|
var Container = factory.getProvider(this.provider).Container;
|
||||||
|
options = new Container(this.client, options);
|
||||||
|
}
|
||||||
|
return this.client.createContainer(options, function (err, container) {
|
||||||
|
return cb(err, map(container));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an existing storage service container.
|
||||||
|
* @param {Object} container Container object.
|
||||||
|
* @param {Function} callback Callback function.
|
||||||
|
*/
|
||||||
|
StorageService.prototype.destroyContainer = function (container, cb) {
|
||||||
|
return this.client.destroyContainer(container, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a container by name.
|
||||||
|
* @param {Object} container Container object.
|
||||||
|
* @param {Function} callback Callback function.
|
||||||
|
*/
|
||||||
|
StorageService.prototype.getContainer = function (container, cb) {
|
||||||
|
return this.client.getContainer(container, function (err, container) {
|
||||||
|
return cb(err, map(container));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stream for uploading
|
||||||
|
* @param {Object} container Container object.
|
||||||
|
* @param {String} file IS THIS A FILE?
|
||||||
|
* @options options See below.
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
|
StorageService.prototype.uploadStream = function (container, file, options, cb) {
|
||||||
|
if (!cb && typeof options === 'function') {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
if (container) {
|
||||||
|
options.container = container;
|
||||||
|
}
|
||||||
|
if (file) {
|
||||||
|
options.remote = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.upload(options, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stream for downloading.
|
||||||
|
* @param {Object} container Container object.
|
||||||
|
* @param {String} file Path to file.
|
||||||
|
* @options {Object} options See below. <!-- What are the options -->
|
||||||
|
* @param {Function} callback Callback function
|
||||||
|
*/
|
||||||
|
StorageService.prototype.downloadStream = function (container, file, options, cb) {
|
||||||
|
if (!cb && typeof options === 'function') {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
options = options || {};
|
||||||
|
if (container) {
|
||||||
|
options.container = container;
|
||||||
|
}
|
||||||
|
if (file) {
|
||||||
|
options.remote = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.download(options, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all files within the given container.
|
||||||
|
* @param {Object} container Container object.
|
||||||
|
* @param {Function} download
|
||||||
|
* @param {Function} callback Callback function
|
||||||
|
*/
|
||||||
|
StorageService.prototype.getFiles = function (container, download, cb) {
|
||||||
|
return this.client.getFiles(container, download, function (err, files) {
|
||||||
|
if (err) {
|
||||||
|
cb(err, files);
|
||||||
|
} else {
|
||||||
|
cb(err, files.map(function (f) {
|
||||||
|
return map(f);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageService.prototype.getFile = function (container, file, cb) {
|
||||||
|
return this.client.getFile(container, file, function (err, f) {
|
||||||
|
return cb(err, map(f));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageService.prototype.removeFile = function (container, file, cb) {
|
||||||
|
return this.client.removeFile(container, file, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageService.prototype.upload = function (req, res, cb) {
|
||||||
|
return handler.upload(this.client, req, res, req.params.container, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageService.prototype.download = function (container, file, res, cb) {
|
||||||
|
return handler.download(this.client, null, res, container, file, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageService.modelName = 'storage';
|
||||||
|
|
||||||
|
StorageService.prototype.getContainers.shared = true;
|
||||||
|
StorageService.prototype.getContainers.accepts = [];
|
||||||
|
StorageService.prototype.getContainers.returns = {arg: 'containers', type: 'array', root: true};
|
||||||
|
StorageService.prototype.getContainers.http =
|
||||||
|
{verb: 'get', path: '/'};
|
||||||
|
|
||||||
|
StorageService.prototype.getContainer.shared = true;
|
||||||
|
StorageService.prototype.getContainer.accepts = [
|
||||||
|
{arg: 'container', type: 'string'}
|
||||||
|
];
|
||||||
|
StorageService.prototype.getContainer.returns = {arg: 'container', type: 'object', root: true};
|
||||||
|
StorageService.prototype.getContainer.http =
|
||||||
|
{verb: 'get', path: '/:container'};
|
||||||
|
|
||||||
|
StorageService.prototype.createContainer.shared = true;
|
||||||
|
StorageService.prototype.createContainer.accepts = [
|
||||||
|
{arg: 'options', type: 'object', http: {source: 'body'}}
|
||||||
|
];
|
||||||
|
StorageService.prototype.createContainer.returns = {arg: 'container', type: 'object', root: true};
|
||||||
|
StorageService.prototype.createContainer.http =
|
||||||
|
{verb: 'post', path: '/'};
|
||||||
|
|
||||||
|
StorageService.prototype.destroyContainer.shared = true;
|
||||||
|
StorageService.prototype.destroyContainer.accepts = [
|
||||||
|
{arg: 'container', type: 'string'}
|
||||||
|
];
|
||||||
|
StorageService.prototype.destroyContainer.returns = {};
|
||||||
|
StorageService.prototype.destroyContainer.http =
|
||||||
|
{verb: 'delete', path: '/:container'};
|
||||||
|
|
||||||
|
StorageService.prototype.getFiles.shared = true;
|
||||||
|
StorageService.prototype.getFiles.accepts = [
|
||||||
|
{arg: 'container', type: 'string'}
|
||||||
|
];
|
||||||
|
StorageService.prototype.getFiles.returns = {arg: 'files', type: 'array', root: true};
|
||||||
|
StorageService.prototype.getFiles.http =
|
||||||
|
{verb: 'get', path: '/:container/files'};
|
||||||
|
|
||||||
|
StorageService.prototype.getFile.shared = true;
|
||||||
|
StorageService.prototype.getFile.accepts = [
|
||||||
|
{arg: 'container', type: 'string'},
|
||||||
|
{arg: 'file', type: 'string'}
|
||||||
|
];
|
||||||
|
StorageService.prototype.getFile.returns = {arg: 'file', type: 'object', root: true};
|
||||||
|
StorageService.prototype.getFile.http =
|
||||||
|
{verb: 'get', path: '/:container/files/:file'};
|
||||||
|
|
||||||
|
StorageService.prototype.removeFile.shared = true;
|
||||||
|
StorageService.prototype.removeFile.accepts = [
|
||||||
|
{arg: 'container', type: 'string'},
|
||||||
|
{arg: 'file', type: 'string'}
|
||||||
|
];
|
||||||
|
StorageService.prototype.removeFile.returns = {};
|
||||||
|
StorageService.prototype.removeFile.http =
|
||||||
|
{verb: 'delete', path: '/:container/files/:file'};
|
||||||
|
|
||||||
|
StorageService.prototype.upload.shared = true;
|
||||||
|
StorageService.prototype.upload.accepts = [
|
||||||
|
{arg: 'req', type: 'object', 'http': {source: 'req'}},
|
||||||
|
{arg: 'res', type: 'object', 'http': {source: 'res'}}
|
||||||
|
];
|
||||||
|
StorageService.prototype.upload.returns = {arg: 'result', type: 'object'};
|
||||||
|
StorageService.prototype.upload.http =
|
||||||
|
{verb: 'post', path: '/:container/upload'};
|
||||||
|
|
||||||
|
StorageService.prototype.download.shared = true;
|
||||||
|
StorageService.prototype.download.accepts = [
|
||||||
|
{arg: 'container', type: 'string', 'http': {source: 'path'}},
|
||||||
|
{arg: 'file', type: 'string', 'http': {source: 'path'}},
|
||||||
|
{arg: 'res', type: 'object', 'http': {source: 'res'}}
|
||||||
|
];
|
||||||
|
StorageService.prototype.download.http =
|
||||||
|
{verb: 'get', path: '/:container/download/:file'};
|
15
package.json
15
package.json
|
@ -2,25 +2,28 @@
|
||||||
"name": "loopback-storage-service",
|
"name": "loopback-storage-service",
|
||||||
"description": "Loopback Storage Service",
|
"description": "Loopback Storage Service",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "lib/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"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pkgcloud": "~0.8.14",
|
"pkgcloud": "~0.9.4",
|
||||||
"async": "~0.2.9"
|
"async": "~0.2.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"express": "~3.4.0",
|
"express": "~3.4.0",
|
||||||
"loopback": "1.x.x",
|
"loopback": "1.x.x",
|
||||||
"formidable": "~1.0.14",
|
"formidable": "~1.0.14",
|
||||||
"mocha": "~1.14.0",
|
"mocha": "~1.18.2",
|
||||||
"supertest": "~0.8.1",
|
"supertest": "~0.10.0",
|
||||||
"mkdirp": "~0.3.5"
|
"mkdirp": "~0.3.5"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/strongloop/loopback-storage-service.git"
|
"url": "https://github.com/strongloop/loopback-storage-service.git"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": {
|
||||||
|
"name": "Dual Artistic-2.0/StrongLoop",
|
||||||
|
"url": "https://github.com/strongloop/loopback-strorage-service/blob/master/LICENSE"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
282
test/fs.test.js
282
test/fs.test.js
|
@ -3,145 +3,159 @@ var FileSystemProvider = require('../lib/providers/filesystem/index.js').Client;
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
|
function verifyMetadata(fileOrContainer, name) {
|
||||||
|
assert(fileOrContainer.getMetadata());
|
||||||
|
assert.equal(fileOrContainer.getMetadata().name, name);
|
||||||
|
assert(fileOrContainer.getMetadata().uid === undefined);
|
||||||
|
assert(fileOrContainer.getMetadata().gid === undefined);
|
||||||
|
assert(fileOrContainer.getMetadata().atime);
|
||||||
|
assert(fileOrContainer.getMetadata().ctime);
|
||||||
|
assert(fileOrContainer.getMetadata().mtime);
|
||||||
|
assert.equal(typeof fileOrContainer.getMetadata().size, 'number');
|
||||||
|
}
|
||||||
|
|
||||||
describe('FileSystem based storage provider', function () {
|
describe('FileSystem based storage provider', function () {
|
||||||
|
|
||||||
describe('container apis', function () {
|
describe('container apis', function () {
|
||||||
var client = null;
|
var client = null;
|
||||||
it('should require an existing directory as the root', function (done) {
|
it('should require an existing directory as the root', function (done) {
|
||||||
client = new FileSystemProvider({root: path.join(__dirname, 'storage')});
|
client = new FileSystemProvider({root: path.join(__dirname, 'storage')});
|
||||||
process.nextTick(done);
|
process.nextTick(done);
|
||||||
});
|
|
||||||
|
|
||||||
it('should complain if the root directory doesn\'t exist', function (done) {
|
|
||||||
try {
|
|
||||||
client = new FileSystemProvider({root: path.join(__dirname, '_storage')});
|
|
||||||
process.nextTick(done.bind(null, 'Error'));
|
|
||||||
} catch (err) {
|
|
||||||
// Should be here
|
|
||||||
process.nextTick(done);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an empty list of containers', function (done) {
|
|
||||||
client.getContainers(function (err, containers) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(0, containers.length);
|
|
||||||
done(err, containers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a new container', function (done) {
|
|
||||||
client.createContainer({name: 'c1'}, function (err, container) {
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get a container c1', function (done) {
|
|
||||||
client.getContainer('c1', function (err, container) {
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not get a container c2', function (done) {
|
|
||||||
client.getContainer('c2', function (err, container) {
|
|
||||||
assert(err);
|
|
||||||
done(null, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return one container', function (done) {
|
|
||||||
client.getContainers(function (err, containers) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(1, containers.length);
|
|
||||||
done(err, containers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should destroy a container c1', function (done) {
|
|
||||||
client.destroyContainer('c1', function (err, container) {
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not get a container c1 after destroy', function (done) {
|
|
||||||
client.getContainer('c1', function (err, container) {
|
|
||||||
assert(err);
|
|
||||||
done(null, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('file apis', function () {
|
it('should complain if the root directory doesn\'t exist', function (done) {
|
||||||
var fs = require('fs');
|
try {
|
||||||
var client = new FileSystemProvider({root: path.join(__dirname, 'storage')});
|
client = new FileSystemProvider({root: path.join(__dirname, '_storage')});
|
||||||
|
process.nextTick(done.bind(null, 'Error'));
|
||||||
it('should create a new container', function (done) {
|
} catch (err) {
|
||||||
client.createContainer({name: 'c1'}, function (err, container) {
|
// Should be here
|
||||||
assert(!err);
|
process.nextTick(done);
|
||||||
done(err, container);
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should upload a file', function (done) {
|
|
||||||
var writer = client.upload({container: 'c1', remote: 'f1.txt'});
|
|
||||||
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
|
|
||||||
writer.on('finish', done);
|
|
||||||
writer.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should download a file', function (done) {
|
|
||||||
var reader = client.download({
|
|
||||||
container: 'c1',
|
|
||||||
remote: 'f1.txt'
|
|
||||||
});
|
|
||||||
reader.pipe(fs.createWriteStream(path.join(__dirname, 'files/f1_downloaded.txt')));
|
|
||||||
reader.on('end', done);
|
|
||||||
reader.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get files for a container', function (done) {
|
|
||||||
client.getFiles('c1', function (err, files) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(1, files.length);
|
|
||||||
done(err, files);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get a file', function (done) {
|
|
||||||
client.getFile('c1', 'f1.txt', function (err, f) {
|
|
||||||
assert(!err);
|
|
||||||
assert.ok(f);
|
|
||||||
done(err, f);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove a file', function (done) {
|
|
||||||
client.removeFile('c1', 'f1.txt', function (err) {
|
|
||||||
assert(!err);
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get no files from a container', function (done) {
|
|
||||||
client.getFiles('c1', function (err, files) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(0, files.length);
|
|
||||||
done(err, files);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should destroy a container c1', function (done) {
|
|
||||||
client.destroyContainer('c1', function (err, container) {
|
|
||||||
// console.error(err);
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return an empty list of containers', function (done) {
|
||||||
|
client.getContainers(function (err, containers) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(0, containers.length);
|
||||||
|
done(err, containers);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new container', function (done) {
|
||||||
|
client.createContainer({name: 'c1'}, function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
verifyMetadata(container, 'c1');
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a container c1', function (done) {
|
||||||
|
client.getContainer('c1', function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
verifyMetadata(container, 'c1');
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get a container c2', function (done) {
|
||||||
|
client.getContainer('c2', function (err, container) {
|
||||||
|
assert(err);
|
||||||
|
done(null, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return one container', function (done) {
|
||||||
|
client.getContainers(function (err, containers) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(1, containers.length);
|
||||||
|
done(err, containers);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy a container c1', function (done) {
|
||||||
|
client.destroyContainer('c1', function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get a container c1 after destroy', function (done) {
|
||||||
|
client.getContainer('c1', function (err, container) {
|
||||||
|
assert(err);
|
||||||
|
done(null, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('file apis', function () {
|
||||||
|
var fs = require('fs');
|
||||||
|
var client = new FileSystemProvider({root: path.join(__dirname, 'storage')});
|
||||||
|
|
||||||
|
it('should create a new container', function (done) {
|
||||||
|
client.createContainer({name: 'c1'}, function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload a file', function (done) {
|
||||||
|
var writer = client.upload({container: 'c1', remote: 'f1.txt'});
|
||||||
|
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
|
||||||
|
writer.on('finish', done);
|
||||||
|
writer.on('error', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should download a file', function (done) {
|
||||||
|
var reader = client.download({
|
||||||
|
container: 'c1',
|
||||||
|
remote: 'f1.txt'
|
||||||
|
});
|
||||||
|
reader.pipe(fs.createWriteStream(path.join(__dirname, 'files/f1_downloaded.txt')));
|
||||||
|
reader.on('end', done);
|
||||||
|
reader.on('error', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get files for a container', function (done) {
|
||||||
|
client.getFiles('c1', function (err, files) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(1, files.length);
|
||||||
|
done(err, files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a file', function (done) {
|
||||||
|
client.getFile('c1', 'f1.txt', function (err, f) {
|
||||||
|
assert(!err);
|
||||||
|
assert.ok(f);
|
||||||
|
verifyMetadata(f, 'f1.txt');
|
||||||
|
done(err, f);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a file', function (done) {
|
||||||
|
client.removeFile('c1', 'f1.txt', function (err) {
|
||||||
|
assert(!err);
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get no files from a container', function (done) {
|
||||||
|
client.getFiles('c1', function (err, files) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(0, files.length);
|
||||||
|
done(err, files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy a container c1', function (done) {
|
||||||
|
client.destroyContainer('c1', function (err, container) {
|
||||||
|
// console.error(err);
|
||||||
|
assert(!err);
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
test.jpg
|
|
@ -1,4 +1,4 @@
|
||||||
var StorageService = require('../lib/index.js');
|
var StorageService = require('../lib/storage-service.js');
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
@ -7,124 +7,127 @@ var storageService = new StorageService({root: path.join(__dirname, 'storage'),
|
||||||
|
|
||||||
describe('Storage service', function () {
|
describe('Storage service', function () {
|
||||||
|
|
||||||
describe('container apis', function () {
|
describe('container apis', function () {
|
||||||
|
|
||||||
it('should return an empty list of containers', function (done) {
|
it('should return an empty list of containers', function (done) {
|
||||||
storageService.getContainers(function (err, containers) {
|
storageService.getContainers(function (err, containers) {
|
||||||
assert(!err);
|
assert(!err);
|
||||||
assert.equal(0, containers.length);
|
assert.equal(0, containers.length);
|
||||||
done(err, containers);
|
done(err, containers);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a new container', function (done) {
|
|
||||||
storageService.createContainer({name: 'c1'}, function (err, container) {
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get a container c1', function (done) {
|
|
||||||
storageService.getContainer('c1', function (err, container) {
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not get a container c2', function (done) {
|
|
||||||
storageService.getContainer('c2', function (err, container) {
|
|
||||||
assert(err);
|
|
||||||
done(null, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return one container', function (done) {
|
|
||||||
storageService.getContainers(function (err, containers) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(1, containers.length);
|
|
||||||
done(err, containers);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should destroy a container c1', function (done) {
|
|
||||||
storageService.destroyContainer('c1', function (err, container) {
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not get a container c1 after destroy', function (done) {
|
|
||||||
storageService.getContainer('c1', function (err, container) {
|
|
||||||
assert(err);
|
|
||||||
done(null, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('file apis', function () {
|
it('should create a new container', function (done) {
|
||||||
var fs = require('fs');
|
storageService.createContainer({name: 'c1'}, function (err, container) {
|
||||||
|
assert(!err);
|
||||||
it('should create a new container', function (done) {
|
assert(container.getMetadata());
|
||||||
storageService.createContainer({name: 'c1'}, function (err, container) {
|
done(err, container);
|
||||||
assert(!err);
|
});
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should upload a file', function (done) {
|
|
||||||
var writer = storageService.uploadStream('c1', 'f1.txt');
|
|
||||||
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
|
|
||||||
writer.on('finish', done);
|
|
||||||
writer.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should download a file', function (done) {
|
|
||||||
var reader = storageService.downloadStream('c1','f1.txt');
|
|
||||||
reader.pipe(fs.createWriteStream(path.join(__dirname, 'files/f1_downloaded.txt')));
|
|
||||||
reader.on('end', done);
|
|
||||||
reader.on('error', done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get files for a container', function (done) {
|
|
||||||
storageService.getFiles('c1', function (err, files) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(1, files.length);
|
|
||||||
done(err, files);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get a file', function (done) {
|
|
||||||
storageService.getFile('c1', 'f1.txt', function (err, f) {
|
|
||||||
assert(!err);
|
|
||||||
assert.ok(f);
|
|
||||||
done(err, f);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove a file', function (done) {
|
|
||||||
storageService.removeFile('c1', 'f1.txt', function (err) {
|
|
||||||
assert(!err);
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get no files from a container', function (done) {
|
|
||||||
storageService.getFiles('c1', function (err, files) {
|
|
||||||
assert(!err);
|
|
||||||
assert.equal(0, files.length);
|
|
||||||
done(err, files);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should destroy a container c1', function (done) {
|
|
||||||
storageService.destroyContainer('c1', function (err, container) {
|
|
||||||
// console.error(err);
|
|
||||||
assert(!err);
|
|
||||||
done(err, container);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get a container c1', function (done) {
|
||||||
|
storageService.getContainer('c1', function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
assert(container.getMetadata());
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get a container c2', function (done) {
|
||||||
|
storageService.getContainer('c2', function (err, container) {
|
||||||
|
assert(err);
|
||||||
|
done(null, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return one container', function (done) {
|
||||||
|
storageService.getContainers(function (err, containers) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(1, containers.length);
|
||||||
|
done(err, containers);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy a container c1', function (done) {
|
||||||
|
storageService.destroyContainer('c1', function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get a container c1 after destroy', function (done) {
|
||||||
|
storageService.getContainer('c1', function (err, container) {
|
||||||
|
assert(err);
|
||||||
|
done(null, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('file apis', function () {
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
it('should create a new container', function (done) {
|
||||||
|
storageService.createContainer({name: 'c1'}, function (err, container) {
|
||||||
|
assert(!err);
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload a file', function (done) {
|
||||||
|
var writer = storageService.uploadStream('c1', 'f1.txt');
|
||||||
|
fs.createReadStream(path.join(__dirname, 'files/f1.txt')).pipe(writer);
|
||||||
|
writer.on('finish', done);
|
||||||
|
writer.on('error', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should download a file', function (done) {
|
||||||
|
var reader = storageService.downloadStream('c1', 'f1.txt');
|
||||||
|
reader.pipe(fs.createWriteStream(path.join(__dirname, 'files/f1_downloaded.txt')));
|
||||||
|
reader.on('end', done);
|
||||||
|
reader.on('error', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get files for a container', function (done) {
|
||||||
|
storageService.getFiles('c1', function (err, files) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(1, files.length);
|
||||||
|
done(err, files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a file', function (done) {
|
||||||
|
storageService.getFile('c1', 'f1.txt', function (err, f) {
|
||||||
|
assert(!err);
|
||||||
|
assert.ok(f);
|
||||||
|
assert(f.getMetadata());
|
||||||
|
done(err, f);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a file', function (done) {
|
||||||
|
storageService.removeFile('c1', 'f1.txt', function (err) {
|
||||||
|
assert(!err);
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get no files from a container', function (done) {
|
||||||
|
storageService.getFiles('c1', function (err, files) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(0, files.length);
|
||||||
|
done(err, files);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should destroy a container c1', function (done) {
|
||||||
|
storageService.destroyContainer('c1', function (err, container) {
|
||||||
|
// console.error(err);
|
||||||
|
assert(!err);
|
||||||
|
done(err, container);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
var request = require('supertest');
|
||||||
|
var loopback = require('loopback');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var app = loopback();
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
// expose a rest api
|
||||||
|
app.use(loopback.rest());
|
||||||
|
|
||||||
|
var ds = loopback.createDataSource({
|
||||||
|
connector: require('../lib/storage-connector'),
|
||||||
|
provider: 'filesystem',
|
||||||
|
root: path.join(__dirname, 'images')
|
||||||
|
});
|
||||||
|
|
||||||
|
var Container = ds.createModel('container');
|
||||||
|
app.model(Container);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Verify that the JSON response has the correct metadata properties.
|
||||||
|
* Please note the metadata vary by storage providers. This test assumes
|
||||||
|
* the 'filesystem' provider.
|
||||||
|
*
|
||||||
|
* @param {String} containerOrFile The container/file object
|
||||||
|
* @param {String} [name] The name to be checked if not undefined
|
||||||
|
*/
|
||||||
|
function verifyMetadata(containerOrFile, name) {
|
||||||
|
assert(containerOrFile);
|
||||||
|
|
||||||
|
// Name
|
||||||
|
if (name) {
|
||||||
|
assert.equal(containerOrFile.name, name);
|
||||||
|
}
|
||||||
|
// No sensitive information
|
||||||
|
assert(containerOrFile.uid === undefined);
|
||||||
|
assert(containerOrFile.gid === undefined);
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
assert(containerOrFile.atime);
|
||||||
|
assert(containerOrFile.ctime);
|
||||||
|
assert(containerOrFile.mtime);
|
||||||
|
|
||||||
|
// Size
|
||||||
|
assert.equal(typeof containerOrFile.size, 'number');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('storage service', function () {
|
||||||
|
var server = null;
|
||||||
|
before(function (done) {
|
||||||
|
server = app.listen(3000, function () {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a container', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.post('/containers')
|
||||||
|
.send({name: 'test-container'})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
verifyMetadata(res.body, 'test-container');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a container', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers/test-container')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
verifyMetadata(res.body, 'test-container');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list containers', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
assert(Array.isArray(res.body));
|
||||||
|
assert.equal(res.body.length, 2);
|
||||||
|
res.body.forEach(function(c) {
|
||||||
|
verifyMetadata(c);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a container', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.del('/containers/test-container')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list containers after delete', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
assert(Array.isArray(res.body));
|
||||||
|
assert.equal(res.body.length, 1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list files', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers/album1/files')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
assert(Array.isArray(res.body));
|
||||||
|
res.body.forEach(function(f) {
|
||||||
|
verifyMetadata(f);
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uploads files', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.post('/containers/album1/upload')
|
||||||
|
.attach('image', path.join(__dirname, '../example/test.jpg'))
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
assert.deepEqual(res.body, {"result": {"files": {"image": [
|
||||||
|
{"container": "album1", "name": "test.jpg", "type": "image/jpeg"}
|
||||||
|
]}, "fields": {}}});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get file by name', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers/album1/files/test.jpg')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
verifyMetadata(res.body, 'test.jpg');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('downloads files', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers/album1/download/test.jpg')
|
||||||
|
.expect('Content-Type', 'image/jpeg')
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a file', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.del('/containers/album1/files/test.jpg')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200, function (err, res) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports errors if it fails to find the file to download', function (done) {
|
||||||
|
|
||||||
|
request('http://localhost:3000')
|
||||||
|
.get('/containers/album1/download/test_not_exist.jpg')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(500, function (err, res) {
|
||||||
|
assert(res.body.error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue