Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

0.5.0 release.

  • Loading branch information...
commit 94ce5494b8b03a71297efcb295c9cb7df33dc5de 0 parents
@andrerod andrerod authored
Showing with 19,088 additions and 0 deletions.
  1. +9 −0 .gitignore
  2. +1 −0  .npmignore
  3. +176 −0 LICENSE.txt
  4. +165 −0 README.md
  5. +10 −0 examples/blobuploader/helpers.js
  6. +10 −0 examples/blobuploader/package.json
  7. BIN  examples/blobuploader/public/Images/delete.png
  8. BIN  examples/blobuploader/public/Images/download.png
  9. +496 −0 examples/blobuploader/public/css/default.css
  10. +48 −0 examples/blobuploader/public/css/reset.css
  11. +129 −0 examples/blobuploader/server.js
  12. +39 −0 examples/blobuploader/views/display.ejs
  13. +4 −0 examples/blobuploader/views/error.ejs
  14. +4 −0 examples/blobuploader/views/index.ejs
  15. +30 −0 examples/blobuploader/views/layout.ejs
  16. +26 −0 examples/blobuploader/views/upload.ejs
  17. +85 −0 examples/blog/blog.js
  18. +11 −0 examples/blog/package.json
  19. +68 −0 examples/blog/public/stylesheets/style.css
  20. +66 −0 examples/blog/public/stylesheets/style.styl
  21. +79 −0 examples/blog/server.js
  22. +19 −0 examples/blog/views/blog_new.ejs
  23. +17 −0 examples/blog/views/blog_show.ejs
  24. +31 −0 examples/blog/views/index.ejs
  25. +8 −0 examples/blog/views/layout.ejs
  26. +220 −0 examples/samples/blobuploaddownloadsample.js
  27. +132 −0 examples/samples/continuationsample.js
  28. +111 −0 examples/samples/leaseblobsample.js
  29. +97 −0 examples/samples/retrypolicysample.js
  30. +188 −0 examples/samples/sassample.js
  31. +219 −0 examples/samples/snapshotsample.js
  32. +89 −0 examples/tasklist/home.js
  33. +11 −0 examples/tasklist/package.json
  34. +66 −0 examples/tasklist/server.js
  35. +60 −0 examples/tasklist/views/home.ejs
  36. +40 −0 examples/tasklist/views/home.jade
  37. +75 −0 lib/azure.js
  38. +137 −0 lib/common/exponentialretrypolicyfilter.js
  39. +117 −0 lib/common/linearretrypolicyfilter.js
  40. +94 −0 lib/diagnostics/logger.js
  41. +342 −0 lib/http/webresource.js
  42. +2,513 −0 lib/services/blob/blobservice.js
  43. +47 −0 lib/services/blob/hmacsha256sign.js
  44. +86 −0 lib/services/blob/models/blobresult.js
  45. +93 −0 lib/services/blob/models/blocklistresult.js
  46. +92 −0 lib/services/blob/models/containeraclresult.js
  47. +77 −0 lib/services/blob/models/containerresult.js
  48. +52 −0 lib/services/blob/models/leaseresult.js
  49. +58 −0 lib/services/blob/models/listblobsresultcontinuation.js
  50. +54 −0 lib/services/blob/models/listcontainersresultcontinuation.js
  51. +190 −0 lib/services/blob/models/servicepropertiesresult.js
  52. +173 −0 lib/services/blob/sharedaccesssignature.js
  53. +144 −0 lib/services/blob/sharedkey.js
  54. +118 −0 lib/services/blob/sharedkeylite.js
  55. +54 −0 lib/services/queue/models/listqueuesresultcontinuation.js
  56. +90 −0 lib/services/queue/models/queuemessageresult.js
  57. +52 −0 lib/services/queue/models/queueresult.js
  58. +180 −0 lib/services/queue/models/servicepropertiesresult.js
  59. +744 −0 lib/services/queue/queueservice.js
  60. +710 −0 lib/services/serviceclient.js
  61. +310 −0 lib/services/table/atomhandler.js
  62. +246 −0 lib/services/table/batchserviceclient.js
  63. +73 −0 lib/services/table/models/queryentitiesresultcontinuation.js
  64. +59 −0 lib/services/table/models/querytablesresultcontinuation.js
  65. +178 −0 lib/services/table/models/servicepropertiesresult.js
  66. +86 −0 lib/services/table/sharedkeylitetable.js
  67. +89 −0 lib/services/table/sharedkeytable.js
  68. +285 −0 lib/services/table/tablequery.js
  69. +892 −0 lib/services/table/tableservice.js
  70. +86 −0 lib/util/base64.js
  71. +1,823 −0 lib/util/constants.js
  72. +77 −0 lib/util/iso8061date.js
  73. +80 −0 lib/util/util.js
  74. +36 −0 package.json
  75. +376 −0 projects/VS/Azure.csproj
  76. +20 −0 projects/VS/Azure.sln
  77. +187 −0 test/azure-tests.js
  78. +143 −0 test/common/exponentialretrypolicyfilter-tests.js
  79. +143 −0 test/common/linearretrypolicyfilter-tests.js
  80. +24 −0 test/extendedtestlist.txt
  81. +44 −0 test/runextendedtests.js
  82. +63 −0 test/runjshint.js
  83. +44 −0 test/runtests.js
  84. +272 −0 test/services/blob/blobservice-longrunning-tests.js
  85. +1,222 −0 test/services/blob/blobservice-tests.js
  86. +142 −0 test/services/blob/filters-tests.js
  87. +157 −0 test/services/blob/sharedaccesssignature-tests.js
  88. +51 −0 test/services/blob/sharedkey-tests.js
  89. +105 −0 test/services/blob/sharedkeylite-tests.js
  90. +129 −0 test/services/queue/listqueuesresultcontinuation-tests.js
  91. +460 −0 test/services/queue/queueservice-tests.js
  92. +70 −0 test/services/table/atomhandler-tests.js
  93. +84 −0 test/services/table/batchserviceclient-tests.js
  94. +156 −0 test/services/table/queryentitiesresultcontinuation-tests.js
  95. +135 −0 test/services/table/querytablesresultcontinuation-tests.js
  96. +64 −0 test/services/table/sharedkeylitetable-tests.js
  97. +112 −0 test/services/table/sharedkeytable-tests.js
  98. +126 −0 test/services/table/tablequery-tests.js
  99. +377 −0 test/services/table/tableservice-batch-tests.js
  100. +138 −0 test/services/table/tableservice-tablequery-tests.js
  101. +689 −0 test/services/table/tableservice-tests.js
  102. +20 −0 test/testlist.txt
  103. +45 −0 test/util/base64-tests.js
  104. +46 −0 test/util/iso8061date-tests.js
  105. +30 −0 test/util/util-tests.js
  106. +38 −0 test/util/util.js
9 .gitignore
@@ -0,0 +1,9 @@
+node_modules/
+*.suo
+*.user
+projects/VS/*.XML
+projects/VS/_ReSharper*
+projects/VS/obj/
+projects/VS/bin/
+projects/Eclipse
+targets/*
1  .npmignore
@@ -0,0 +1 @@
+.git*
176 LICENSE.txt
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
165 README.md
@@ -0,0 +1,165 @@
+<h1>Windows Azure SDK for Node.js</h1>
+<p>This project provides a set of Node.js packages that make it easy to access
+the Windows Azure storage and queue services. For documentation on how
+to host Node.js applications on Windows Azure, please see the
+<a href="http://www.windowsazure.com/en-us/develop/nodejs/">Windows Azure
+Node.js Developer Center</a>.</p>
+
+<h1>Features</h1>
+<ul>
+ <li>Tables
+ <ul>
+ <li>create and delete tables</li>
+ <li>create, query, insert, update, merge, and delete entities</li>
+ </li>
+ <li>Blobs
+ <ul>
+ <li>create, list, and delete containers, work with container metadata
+ and permissions, list blobs in container</li>
+ <li>create block and page blobs (from a stream, a file, or a string),
+ work with blob blocks and pages, delete blobs</li>
+ <li>work with blob properties, metadata, leases, snapshot a blob</li>
+ </li>
+ <li>Queues
+ <ul>
+ <li>create, list, and delete queues, and work with queue metadata</li>
+ <li>create, get, peek, update, delete messages</li>
+ </li>
+</ul>
+
+<h1>Getting Started</h1>
+<h2>Download Source Code</h2>
+<p>To get the source code of the SDK via <strong>git</strong> just type:<br/>
+<pre>git clone https://github.com/WindowsAzure/azure-sdk-for-node.git<br/>cd ./azure-sdk-for-node</pre>
+</p>
+
+<h2>Download Package</h2>
+<p>Alternatively, to get the source code via the Node Package Manager (npm), type<br/>
+<pre>npm install azure</pre>
+<p>You can use these packages against the cloud Windows Azure Services, or against
+the local Storage Emulator.</p>
+<ol>
+ <li>To use the cloud services, you need to first create an account with
+ Windows Azure. You need to set the AZURE_STORAGE_ACCOUNT and the AZURE_STORAGE_ACCESS_KEY
+ environment variables to the storage account name and primary access key you
+ obtain from the Azure Portal.</li>
+ <li>To use the Storage Emulator, make sure the latest version of the
+ Windows Azure SDK is installed on the machine, and set the EMULATED environment
+ variable to any value ("true", "1", etc.)</li>
+</ol>
+
+<h1>Usage</h1>
+<h2>Table Storage</h2>
+<p>To ensure a table exists, call <strong>createTableIfNotExists</strong>:</p>
+<pre>
+var tableService = azure.createTableService();
+tableService.createTableIfNotExists('tasktable', function(error){
+ if(!error){
+ // Table exists
+ }
+});
+</pre>
+<p>A new entity can be added by calling <strong>insertEntity</strong>:</p>
+<pre>
+var tableService = azure.createTableService(),
+ task1 = {
+ PartitionKey : 'tasksSeattle',
+ RowKey: '1',
+ Description: 'Take out the trash',
+ DueDate: new Date(2011, 12, 14, 12)
+ };
+tableService.insertEntity('tasktable', task1, function(error){
+ if(!error){
+ // Entity inserted
+ }
+});
+</pre>
+<p>The method <strong>queryEntity</strong> can then be used to fetch the entity that was just inserted:</p>
+<pre>
+var tableService = azure.createTableService();
+tableService.queryEntity('tasktable', 'tasksSeattle', '1', function(error, serverEntity){
+ if(!error){
+ // Entity available in serverEntity variable
+ }
+});
+</pre>
+<h2>Blob Storage</h2>
+<p>The <strong>createContainerIfNotExists</strong> method can be used to create a
+container in which to store a blob:</p>
+<pre>
+var blobService = azure.createBlobService();
+blobService.createContainerIfNotExists('taskcontainer', {publicAccessLevel : 'blob'}, function(error){
+ if(!error){
+ // Container exists and is public
+ }
+});
+</pre>
+<p>To upload a file (assuming it is called task1-upload.txt, it contains the exact text "hello world" (no quotation marks), and it is placed in the same folder as the script below), the method <strong>createBlockBlobFromStream</strong> can be used:</p>
+<pre>
+var blobService = azure.createBlobService();
+blobService.createBlockBlobFromStream('taskcontainer', 'task1', fs.createReadStream('task1-upload.txt'), 11, function(error){
+ if(!error){
+ // Blob uploaded
+ }
+});
+</pre>
+<p>To download the blob and write it to the file system, the <strong>getBlobToStream</strong> method can be used:</p>
+<pre>
+var blobService = azure.createBlobService();
+blobService.getBlobToStream('taskcontainer', 'task1', fs.createWriteStream('task1-download.txt'), function(error, serverBlob){
+ if(!error){
+ // Blob available in serverBlob.blob variable
+ }
+});
+</pre>
+<h2>Queues</h2>
+<p>The <strong>createQueueIfNotExists</strong> method can be used to ensure a queue exists:</p>
+<pre>
+var queueService = azure.createQueueService();
+queueService.createQueueIfNotExists('taskqueue', function(error){
+ if(!error){
+ // Queue exists
+ }
+});
+</pre>
+<p>The <strong>createMessage</strong> method can then be called to insert the message into the queue:</p>
+<pre>
+var queueService = azure.createQueueService();
+queueService.createMessage('taskqueue', "Hello world!", function(error){
+ if(!error){
+ // Message inserted
+ }
+});
+</pre>
+<p>It is then possible to call the <strong>getMessage</strong> method, process the message and then call <strong>deleteMessage</strong> inside the callback. This two-step process ensures messages don't get lost when they are removed from the queue.</p>
+<pre>
+var queueService = azure.createQueueService(),
+ queueName = 'taskqueue';
+queueService.getMessages(queueName, function(error, serverMessages){
+ if(!error){
+ // Process the message in less than 30 seconds, the message
+ // text is available in serverMessages[0].messagetext
+
+ queueService.deleteMessage(queueName, serverMessages[0].messageid, serverMessages[0].popreceipt, function(error){
+ if(!error){
+ // Message deleted
+ }
+ });
+ }
+});
+</pre>
+<p><strong>For more examples please see the <a href="http://www.windowsazure.com/en-us/develop/nodejs/">
+Windows Azure Node.js Developer Center</a>.</strong></p>
+
+<h1>Need Help?</h1>
+<p>Be sure to check out the Windows Azure <a href="http://go.microsoft.com/fwlink/?LinkId=234489">
+Developer Forums on Stack Overflow</a> if you have trouble with the provided code.</p>
+
+<h1>Feedback</h1>
+<p>For feedback related specifically to this SDK, please use the Issues
+section of the repository.</p>
+<p>For general suggestions about Windows Azure please use our
+<a href="http://www.mygreatwindowsazureidea.com/forums/34192-windows-azure-feature-voting">UserVoice forum</a>.</p>
+
+<h1>Learn More</h1>
+<a href="http://www.windowsazure.com/en-us/develop/nodejs/">Windows Azure Node.js Developer Center</a>
10 examples/blobuploader/helpers.js
@@ -0,0 +1,10 @@
+//Helper functions
+var exports = module.exports;
+
+exports.renderError = function(response, message) {
+ response.render("error.ejs", {
+ locals: {
+ title: message ? message : "Error"
+ }
+ });
+};
10 examples/blobuploader/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "productmanager"
+ , "version": "0.0.1"
+ , "private": true
+ , "dependencies": {
+ "express": ">= 2.4.7"
+ , "ejs": ">= 0.4.3"
+ , "formidable": ">= 1.0.6"
+ }
+}
BIN  examples/blobuploader/public/Images/delete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  examples/blobuploader/public/Images/download.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
496 examples/blobuploader/public/css/default.css
@@ -0,0 +1,496 @@
+/*#region -----------------------------Main Layout*/
+html {
+ height: 100%;
+ min-height: 700px;
+}
+
+a {
+ outline: none;
+}
+
+li {
+ margin: 5px 0 5px;
+}
+
+body {
+ background: #ebf0f2;
+ font-family: Actor, sans-serif;
+ font-size: 100%;
+ color: #434343;
+ height: 100%;
+ min-height: 900px;
+}
+
+.mainWrapper {
+ position: relative;
+ background-color: rgba(204, 204, 204, 0.75);
+ width: 850px;
+ margin: 0 auto;
+ min-height: 100%;
+}
+
+.mainLayout {
+ clear: both;
+ width: 850px;
+ padding-bottom: 25px;
+ height: 100%;
+}
+
+.mainSection {
+ position: relative;
+ margin: 50px 0 25px 90px;
+}
+
+header, footer {
+ font-weight: bold;
+ font-size: 0.9em;
+ color: #e6e2df;
+ background-color: rgba(0, 0, 0, 0.53);
+ text-transform: uppercase;
+}
+
+.login {
+ float: right;
+ padding-right: 12px;
+}
+
+.loginLink {
+ color: #e6e2df;
+}
+
+ .loginLink:hover {
+ color: #ebf0f2;
+ }
+
+.welcomeUser {
+ color: #92a5bc;
+ font-size: 0.9em;
+ margin-right: 12px;
+}
+
+.no-rgba header, .no-rgba footer {
+ background-color: #666666;
+}
+
+header {
+ height: 30px;
+ padding: 15px 0px 0px 10px;
+}
+
+ .mainTitle a {
+ color: #CCC;
+ }
+
+ .mainTitle a:hover {
+ color: #CCC;
+ }
+
+footer {
+ text-align: center;
+ position: absolute;
+ padding-top: 6px;
+ height: 25px;
+ width: 850px;
+ bottom: 0;
+}
+
+ footer a {
+ color: #e6e2df;
+ }
+
+ footer a:hover {
+ color: #ebf0f2;
+ }
+
+.splashTitle {
+ font-size: 4em;
+ position: absolute;
+ top: 25%;
+ left: 38%;
+}
+
+.pageTitle {
+ font-size: 3em;
+ margin-bottom: 20px;
+ margin-left: -29px;
+}
+
+.pageValidation{
+ margin-top: 25px;
+ margin-bottom: 10px;
+ display: none;
+}
+
+.mainMenu {
+ position: absolute;
+ left: 27%;
+ top: 34%;
+ list-style: none;
+ display: inline;
+}
+
+.mainMenu li {
+ float: left;
+}
+
+.mainMenu li a {
+ background-position: center;
+ display: block;
+ padding: 105px 15px 10px 15px;
+ text-align: center;
+ color: Black;
+ font-size: 1.1em;
+ width: 100px;
+}
+
+.createList {
+ background: url("Images/CreateList.png") no-repeat;
+}
+
+.claimItems {
+ background: url("Images/ClaimItems.png") no-repeat;
+}
+
+.getGifts{
+ background: url("Images/GetGifts.png") no-repeat;
+}
+
+.loginMenu {
+ position: absolute;
+ top: 33%;
+ left: 42%;
+ margin-bottom: 25px;
+}
+
+.loginMenu li {
+ float: left;
+ border-right: 2px solid;
+ padding: 0 12px 0 12px;
+}
+
+.loginMenu li:last-child {
+ border-right: none;
+}
+
+h1 {
+ font-size: 1.6em;
+}
+
+h2 {
+ font-size: 1.3em;
+}
+
+
+/*#endregion*/
+/*#region -----------------------------Primary Tags*/
+a {
+ color: #577fae;
+ text-decoration: none;
+}
+
+ a:hover {
+ color: #92a5bc;
+ }
+
+ a#home {
+ color: #91a1ab;
+ }
+/*#endregion*/
+/*#region -----------------------------Menu*/
+.nav {
+ float: left;
+ list-style: none;
+ margin-left: -10px;
+}
+
+ .nav li {
+ float: left;
+ margin-left: 18px;
+ padding: 0;
+ }
+
+ .nav li a {
+ color: #e6e2df;
+ }
+
+ .nav li a:hover {
+ color: #ebf0f2;
+ }
+/*#endregion*/
+/*#region -----------------------------Form Layout*/
+form {
+ margin-top: 20px;
+}
+
+fieldset {
+ border: none;
+ margin: 0;
+ padding: 0;
+}
+
+ fieldset legend {
+ display: none;
+ }
+
+ fieldset ol {
+ padding: 0;
+ list-style: none;
+ }
+
+ fieldset ol li {
+ padding-bottom: 5px;
+ }
+
+ fieldset label {
+ display: block;
+ font-size: 1.0em;
+ font-weight: 600;
+ }
+
+ fieldset label.checkbox {
+ display: inline;
+ }
+
+ fieldset input[type="text"],
+ fieldset input[type="password"],
+ fieldset select {
+ border: 1px solid #e2e2e2;
+ color: #333;
+ font-size: 1.0em;
+ margin: 5px 0 6px 0;
+ padding: 5px;
+ width: 300px;
+ }
+
+ fieldset input[type="text"]:focus,
+ fieldset input[type="password"]:focus {
+ border: 1px solid #7ac0da;
+ }
+
+ fieldset input[type="submit"], .submit {
+ background-color: #92a5bc;
+ border: solid 1px #787878;
+ cursor: pointer;
+ font-size: 1.2em;
+ font-weight: 600;
+ padding: 7px;
+ margin: 10px 0 15px;
+ }
+
+ fieldset select {
+
+ }
+/*#endregion*/
+/*#region -----------------------------Validation*/
+.message-info {
+ border: solid 1px;
+ clear: both;
+ padding: 20px 20px;
+ margin: 20px 0 15px;
+ width: 300px;
+}
+
+.validation-summary-valid {
+ display: none;
+}
+
+.validation-summary-errors {
+ clear: both;
+ display: block;
+ color: #e80c4d;
+ font-weight: bold;
+ padding: 20px 0 10px 0;
+}
+
+ .validation-summary-errors span {
+ font-size: 1.0em;
+ text-align: left;
+ }
+
+ .validation-summary-errors li {
+ font-size: 0.9em;
+ list-style: disc;
+ text-align: left;
+ margin: 2px 0 2px;
+ }
+
+.message-success {
+ color: #0ab317;
+ font-size: 1.0em;
+ font-weight: bold;
+ margin: 20px 0 10px 0;
+}
+
+.error {
+ color: #e80c4d;
+}
+
+.error-label {
+ color: #e80c4d;
+ font-weight: bold;
+}
+
+.error-label + input[type="text"],
+.error-label + input[type="password"] {
+ border: solid 1px #e80c4d;
+}
+
+.field-validation-error {
+ display: inline;
+ color: #e80c4d;
+ font-weight: bold;
+ font-size: 1.0em;
+}
+
+.field-validation-valid {
+ display: none;
+}
+/*#endregion*/
+/*#region -----------------------------Main Lists*/
+.listEntry, .itemEntry {
+ width: 650px;
+ height: 80px;
+ background-color: #92a5bc;
+ margin: 20px 0 20px 0;
+ padding: 10px;
+}
+.listTitle {
+ font-size: 1.3em;
+ color: #e6e2df;
+}
+ .listTitle:hover {
+ color: #e6e2df;
+ }
+.listDescription, .itemDescription {
+ margin: 7px 25px;
+}
+.listDefault {
+ color: #ebf0f2;
+ float: right;
+ font-size: 1.0em;
+}
+.itemTitle {
+ font-size: 1.3em;
+}
+.itemClaimed {
+ color: #ebf0f2;
+ font-size: 1.1em;
+ vertical-align: top;
+}
+.itemPriceTag {
+ border-top: 17px solid transparent;
+ border-bottom: 17px solid transparent;
+ border-right: 17px solid #eae071;
+ width: 0;
+ height: 0;
+ margin: -25px 35px;
+ float: right;
+}
+.itemPrice {
+ background-color: #eae071;
+ padding: 5px;
+ float: right;
+ width: 75px;
+ margin: -4px -35px;
+ font-size: 1.5em;
+ text-indent: -2px;
+ text-align: right;
+}
+.itemLink {
+ font-size: 1.0em;
+ color: #e6e2df;
+}
+ .itemLink:hover {
+ color: #e6e2df;
+ }
+.editList {
+ width: 48px;
+ height: 48px;
+ display: block;
+ float: right;
+ padding-right: 7px;
+ background: url("Images/edit.png") no-repeat;
+}
+.addListItem {
+ width: 48px;
+ height: 48px;
+ display: block;
+ float: right;
+ padding-right: 12px;
+ background: url("Images/add.png") no-repeat;
+}
+.downloadBlob {
+ display: block;
+ float: right;
+ width: 48px;
+ height: 48px;
+ background: url("/Images/download.png") no-repeat;
+}
+.deleteBlob {
+ display: block;
+ float: right;
+ width: 48px;
+ height: 48px;
+ background: url("/Images/delete.png") no-repeat;
+}
+.blobItem {
+ clear: both;
+ width: 650px;
+ padding: 15px 0 15px;
+}
+.claimItem {
+ width: 48px;
+ height: 48px;
+ display: block;
+ float: right;
+ padding-right: 7px;
+ background: url("Images/claim.png") no-repeat;
+}
+.createNewList {
+ height: 64px;
+ width: 64px;
+ background: url("Images/CreateList.png") no-repeat;
+ display: block;
+ margin-left: 293px;
+ text-indent: -9999px;
+}
+.createNewListText {
+ margin-left: 287px;
+ font-size: 1.3em;
+}
+.noscriptsubmit {
+ display: block;
+ float: right;
+}
+.scriptsubmit {
+ display: none;
+ float: right;
+}
+/*#endregion*/
+/*#region -----------------------------Tables*/
+table th {
+ padding: 8px;
+ border: none;
+ background-color: #dedede;
+}
+table td {
+ border: none;
+ padding: 8px;
+ background-color: #ffffff;
+ vertical-align: middle;
+}
+/*#endregion*/
+/*#region -----------------------------Misc*/
+.jquery-dialog {
+ display: none;
+}
+
+.imageLink {
+ border: 0px;
+}
+
+.formActions a {
+ text-decoration: underline;
+ padding: 0 3px 0 3px;
+}
+/*#endregion*/
48 examples/blobuploader/public/css/reset.css
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
129 examples/blobuploader/server.js
@@ -0,0 +1,129 @@
+var express = require('express');
+var formidable = require('formidable');
+var azure = require('./../../lib/azure');
+var helpers = require('./helpers.js');
+
+var app = module.exports = express.createServer();
+// Global request options, set the retryPolicy
+var blobClient = azure.createBlobService(azure.ServiceClient.DEVSTORE_STORAGE_ACCOUNT, azure.ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY, azure.ServiceClient.DEVSTORE_BLOB_HOST).withFilter(new azure.ExponentialRetryPolicyFilter());
+var containerName = 'webpi';
+
+//Configuration
+app.configure(function () {
+ app.set('views', __dirname + '/views');
+ app.set('view engine', 'ejs');
+ app.use(express.methodOverride());
+ // app.use(express.logger());
+ app.use(app.router);
+ app.use(express.static(__dirname + '/public'));
+});
+
+app.configure('development', function () {
+ app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
+});
+
+app.configure('production', function () {
+ app.use(express.errorHandler());
+});
+
+app.param(':id', function (req, res, next) {
+ next();
+});
+
+//Routes
+app.get('/', function (req, res) {
+ res.render('index.ejs', { locals: {
+ title: 'Welcome'
+ }
+ });
+});
+
+app.get('/Upload', function (req, res) {
+ res.render('upload.ejs', { locals: {
+ title: 'Upload File'
+ }
+ });
+});
+
+app.get('/Display', function (req, res) {
+ blobClient.listBlobs(containerName, function (error, blobs) {
+ res.render('display.ejs', { locals: {
+ title: 'List of Blobs',
+ serverBlobs: blobs
+ }
+ });
+ });
+});
+
+app.get('/Download/:id', function (req, res) {
+ blobClient.getBlobProperties(containerName, req.params.id, function (err, blobInfo) {
+ if (err === null) {
+ res.header('content-type', blobInfo.contentType);
+ res.header('content-disposition', 'attachment; filename=' + blobInfo.metadata.filename);
+ blobClient.getBlobToStream(containerName, req.params.id, res, function () { });
+ } else {
+ helpers.renderError(res);
+ }
+ });
+});
+
+app.post('/uploadhandler', function (req, res) {
+ var form = new formidable.IncomingForm();
+
+ form.parse(req, function (err, fields, files) {
+ var formValid = true;
+ if (fields.itemName === '') {
+ helpers.renderError(res);
+ formValid = false;
+ }
+
+ if (formValid) {
+ var extension = files.uploadedFile.name.split('.').pop();
+ var newName = fields.itemName + '.' + extension;
+
+ var options = {
+ contentType: files.uploadedFile.type,
+ metadata: { fileName: newName }
+ };
+
+ blobClient.createBlockBlobFromFile(containerName, fields.itemName, files.uploadedFile.path, options, function (error) {
+ if (error != null) {
+ helpers.renderError(res);
+ } else {
+ res.redirect('/Display');
+ }
+ });
+ } else {
+ helpers.renderError(res);
+ }
+ });
+});
+
+app.post('/Delete/:id', function (req, res) {
+ blobClient.deleteBlob(containerName, req.params.id, function (error) {
+ if (error != null) {
+ helpers.renderError(res);
+ } else {
+ res.redirect('/Display');
+ }
+ });
+});
+
+blobClient.createContainerIfNotExists(containerName, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ setPermissions();
+ }
+});
+
+function setPermissions() {
+ blobClient.setContainerAcl(containerName, azure.Constants.BlobConstants.BlobContainerPublicAccessType.BLOB, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ app.listen(process.env.port || 1337);
+ console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
+ }
+ });
+}
39 examples/blobuploader/views/display.ejs
@@ -0,0 +1,39 @@
+<script>
+ function deleteBlob(blobId) {
+ $("#DeleteConfirmationDialog" + blobId).dialog(
+ {
+ resizable: false, height: 250, width: 300, modal: true,
+ buttons: {
+ 'OK': function() {
+ document.forms['deleteBlobForm'+blobId].submit();
+ },
+ 'Cancel': function() {
+ $(this).dialog("close");
+ }
+ }
+ }
+ );
+ }
+</script>
+<section class="mainSection">
+ <h1 class="pageTitle">Blobs Available</h1>
+ <% if (serverBlobs.length > 0) { %>
+ <ul>
+ <% for (var i = 0; i < serverBlobs.length; i++) { %>
+ <li class="blobItem">
+ <%= serverBlobs[i].name %>
+ <a class="downloadBlob" href="/Download/<%= serverBlobs[i].name %>"></a>
+ <a class="deleteBlob" href="javascript:deleteBlob(<%= i %>)"></a>
+ <form id="deleteBlobForm<%= i %>" action="/Delete/<%= serverBlobs[i].name %>" method="post">
+ </form>
+ <div id="DeleteConfirmationDialog<%= i %>" class="jquery-dialog" title="Delete Blob">
+ <span class="ui-icon ui-icon-alert ui-icon-float"></span>
+ <p>Are you sure you want to delete <%= serverBlobs[i].name %>?</p>
+ </div>
+ </li>
+ <% } %>
+ </ul>
+ <% } else { %>
+ Nothing to see here. <a href="./Upload">Click to upload<a/>.
+ <% } %>
+</section>
4 examples/blobuploader/views/error.ejs
@@ -0,0 +1,4 @@
+<section class="mainSection">
+ <h1 class="pageTitle">Error</h1>
+ <p>There was a problem processing your request. Please try again.</p>
+</section>
4 examples/blobuploader/views/index.ejs
@@ -0,0 +1,4 @@
+<section class="mainSection">
+ <h1 class="pageTitle">Blob Uploader</h1>
+ <p>Use this site to upload to Azure blob storage</p>
+</section>
30 examples/blobuploader/views/layout.ejs
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title><%-title%></title>
+ <link href="/css/reset.css" rel="stylesheet"/>
+ <link href="/css/default.css" rel="stylesheet"/>
+ <link href="http://fonts.googleapis.com/css?family=Actor" rel="stylesheet"/>
+ <link href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
+ <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.4.min.js"></script>
+ <script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/jquery-ui.min.js"></script>
+ <script src="http://ajax.aspnetcdn.com/ajax/modernizr/modernizr-2.0.6-development-only.js"></script>
+ </head>
+ <body>
+ <div class="mainWrapper">
+ <header>
+ <ul class="nav">
+ <li><a href="/">Home</a></li>
+ <li><a href="/Display">List</a></li>
+ <li><a href="/Upload">Upload</a></li>
+ </ul>
+ </header>
+ <div class="mainLayout">
+ <%-body%>
+ </div>
+ <footer>
+ Powered by Node in Azure
+ </footer>
+ </div>
+ </body>
+</html>
26 examples/blobuploader/views/upload.ejs
@@ -0,0 +1,26 @@
+<script>
+ function submitForm() {
+ if ($("#itemName").val().length === 0) {
+ $("#itemNameError").html("You must provide an Name or Id!");
+ } else {
+ document.forms['upload'].submit();
+ }
+ }
+</script>
+<section class="mainSection">
+ <h1 class="pageTitle">Upload MSI</h1>
+ <form id="upload" action="/uploadhandler" method="post" enctype="multipart/form-data">
+ <fieldset>
+ <legend>Upload</legend>
+ <ol>
+ <li>
+ <label for="itemName">Name/ID</label>
+ <input type="text" id="itemName" name="itemName" />
+ <span id="itemNameError" class="error"></span>
+ </li>
+ <input type="file" name="uploadedFile" />
+ </ol>
+ <input type="button" class="submit" value="Upload" title="Upload" onclick="javascript:submitForm()" />
+ </fieldset>
+ </form>
+</section>
85 examples/blog/blog.js
@@ -0,0 +1,85 @@
+var azure = require('./../../lib/azure');
+var ServiceClient = azure.ServiceClient;
+var TableQuery = azure.TableQuery;
+var uuid = require('node-uuid');
+
+var tableName = 'posts';
+var partition = 'part1';
+
+Blog = function () {
+ this.tableClient = azure.createTableService(ServiceClient.DEVSTORE_STORAGE_ACCOUNT, ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY, ServiceClient.DEVSTORE_TABLE_HOST);
+};
+
+Blog.prototype.findAll = function (callback) {
+ var tableQuery = TableQuery.select()
+ .from(tableName);
+
+ this.tableClient.queryEntities(tableQuery, callback);
+};
+
+Blog.prototype.findById = function (id, callback) {
+ this.tableClient.queryEntity(tableName, partition, id, callback);
+};
+
+Blog.prototype.destroy = function (id, callback) {
+ var entity = { PartitionKey: partition, RowKey: id };
+ this.tableClient.deleteEntity(tableName, entity, callback);
+};
+
+Blog.prototype.save = function (posts, callback) {
+ if (!Array.isArray(posts)) {
+ posts = [posts];
+ }
+
+ this.savePosts(posts, callback);
+};
+
+// this could be implemented using batch but for the sake of using both methods use the individual requests + callback.
+Blog.prototype.savePosts = function (posts, callback) {
+ if (posts.length === 0) {
+ callback();
+ }
+ else {
+ var post = posts.pop();
+ post.created_at = new Date();
+
+ if (post.comments === undefined)
+ post.comments = [];
+
+ for (var j = 0; j < post.comments.length; j++) {
+ post.comments[j].created_at = new Date();
+ }
+
+ post.PartitionKey = partition;
+ post.RowKey = uuid();
+
+ var provider = this;
+ this.tableClient.insertEntity(tableName, post, function () {
+ // Insert remaining posts recursively
+ provider.savePosts(posts, callback);
+ });
+ }
+};
+
+Blog.prototype.init = function () {
+ var provider = this;
+ this.tableClient.createTableIfNotExists(tableName, function (err, created) {
+ if (created) {
+ console.log('Setting up demo data ...');
+
+ provider.tableClient.beginBatch();
+
+ var now = new Date().toString();
+ provider.tableClient.insertEntity(tableName, { PartitionKey: partition, RowKey: uuid(), title: 'Post one', body: 'Body one', created_at: now });
+ provider.tableClient.insertEntity(tableName, { PartitionKey: partition, RowKey: uuid(), title: 'Post two', body: 'Body two', created_at: now });
+ provider.tableClient.insertEntity(tableName, { PartitionKey: partition, RowKey: uuid(), title: 'Post three', body: 'Body three', created_at: now });
+ provider.tableClient.insertEntity(tableName, { PartitionKey: partition, RowKey: uuid(), title: 'Post four', body: 'Body four', created_at: now });
+
+ provider.tableClient.commitBatch(function () {
+ console.log('Done');
+ });
+ }
+ });
+};
+
+exports.Blog = Blog;
11 examples/blog/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "application-name"
+ , "version": "0.0.1"
+ , "private": true
+ , "dependencies": {
+ "express": ">= 2.4.7"
+ , "stylus": ">= 0.0.1"
+ , "ejs": ">= 0.4.3"
+ , "node-uuid": ">= 1.2.0"
+ }
+}
68 examples/blog/public/stylesheets/style.css
@@ -0,0 +1,68 @@
+body {
+ font-family: Tahoma, "Helvetica Neue", Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ text-stroke: 1px rgba(255,255,255,0.10);
+ color: #555;
+ background-color: #f3f4f5;
+}
+h1,
+h2 {
+ margin: 0;
+ font-size: 22px;
+ color: #343434;
+}
+h1 {
+ font-size: 60px;
+ text-align: center;
+ margin-bottom: 20px;
+}
+#posts {
+ margin-bottom: 20px;
+ margin-left: auto;
+ margin-right: auto;
+ background-color: #fff;
+ width: 80%;
+}
+#posts #newPost {
+ float: right;
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+#posts #deletePost {
+ float: right;
+}
+#posts .post {
+ border-style: solid;
+ border-width: 1px;
+ border-color: #d3d2d2;
+ padding: 20px;
+}
+#posts .post .created_at {
+ font-size: 10px;
+ padding-bottom: 5px;
+}
+#posts .post .title {
+ font-weight: bold;
+ text-decoration: underline;
+ padding-bottom: 5px;
+}
+.post {
+ border-style: solid;
+ border-width: 1px;
+ border-color: #d3d2d2;
+ padding: 20px;
+ background-color: #fff;
+}
+.post #editPostSubmitDiv {
+ padding-top: 20px;
+}
+.post #editPostTitle {
+ width: 300px;
+}
+.post #editPostBodyDiv {
+ padding-top: 20px;
+}
+.post #editPostBody {
+ width: 100%;
+ height: 90px;
+}
66 examples/blog/public/stylesheets/style.styl
@@ -0,0 +1,66 @@
+body
+ font-family Tahoma, "Helvetica Neue", Arial, Helvetica, sans-serif
+ font-size 13px
+ text-stroke 1px rgba(255, 255, 255, 0.1)
+ color #555
+ background-color #F3F4F5
+
+h1, h2
+ margin 0
+ font-size 22px
+ color #343434
+
+h1
+ font-size 60px
+ text-align center
+ margin-bottom 20px
+
+#posts
+ margin-bottom 20px
+ margin-left auto
+ margin-right auto
+ background-color #fff
+ width 80%
+
+ #newPost
+ float right
+ padding-top 20px
+ padding-bottom 20px
+
+ #deletePost
+ float right
+
+ .post
+ border-style solid;
+ border-width 1px
+ border-color #D3D2D2
+ padding 20px
+
+ .created_at
+ font-size 10px
+ padding-bottom 5px
+
+ .title
+ font-weight bold
+ text-decoration underline
+ padding-bottom 5px
+
+.post
+ border-style solid
+ border-width 1px
+ border-color #D3D2D2
+ padding 20px
+ background-color #fff
+
+ #editPostSubmitDiv
+ padding-top 20px
+
+ #editPostTitle
+ width 300px
+
+ #editPostBodyDiv
+ padding-top 20px
+
+ #editPostBody
+ width 100%
+ height 90px
79 examples/blog/server.js
@@ -0,0 +1,79 @@
+// Based on:
+// http://howtonode.org/express-mongodb
+
+var express = require('express');
+var Blog = require('./blog').Blog;
+
+var app = module.exports = express.createServer();
+
+// Configuration
+
+app.configure(function () {
+ app.set('views', __dirname + '/views');
+ app.set('view engine', 'ejs');
+ app.use(express.bodyParser());
+ app.use(express.methodOverride());
+ app.use(require('stylus').middleware({ src: __dirname + '/public' }));
+ app.use(app.router);
+ app.use(express.static(__dirname + '/public'));
+});
+
+app.configure('development', function () {
+ app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
+});
+
+app.configure('production', function () {
+ app.use(express.errorHandler());
+});
+
+// Routes
+
+var blog = new Blog();
+blog.init();
+
+app.get('/', function (req, res) {
+ blog.findAll(function (error, posts) {
+ res.render('index.ejs', { locals: {
+ title: 'Blog',
+ posts: posts
+ }
+ });
+ });
+});
+
+app.get('/blog/new', function (req, res) {
+ res.render('blog_new.ejs', { locals: {
+ title: 'New Blog Post'
+ }
+ });
+});
+
+app.post('/blog/new', function (req, res) {
+ blog.save({
+ title: req.param('title'),
+ body: req.param('body')
+ },
+ function () {
+ res.redirect('/');
+ });
+});
+
+app.get('/blog/delete/:id', function (req, res) {
+ blog.destroy(req.params.id, function () {
+ res.redirect('/');
+ });
+});
+
+app.get('/blog/:id', function (req, res) {
+ blog.findById(req.params.id, function (error, post) {
+ res.render('blog_show.ejs', {
+ locals: {
+ title: post.title,
+ post: post
+ }
+ });
+ });
+});
+
+app.listen(process.env.port || 1337);
+console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
19 examples/blog/views/blog_new.ejs
@@ -0,0 +1,19 @@
+<h1>
+ <%-title%>
+</h1>
+<form method="post">
+ <div class="post">
+ <div>
+ <div>Title</div>
+ <input type="text" name="title" id="editPostTitle" />
+ </div>
+ <div id="editPostBodyDiv">
+ <div>Body</div>
+ <textarea name="body" rows="20" id="editPostBody"></textarea>
+ </div>
+ </div>
+
+ <div id="editPostSubmitDiv">
+ <input id="editPostSubmit" type="submit" value="Save" />
+ </div>
+</form>
17 examples/blog/views/blog_show.ejs
@@ -0,0 +1,17 @@
+<h1>
+ <%-title%>
+</h1>
+<div class="post">
+ <% if (post.created_at) {%>
+ <div class="created_at">
+ <p>
+ Posted on: <%-post.created_at%>
+ </p>
+ </div>
+ <% }%>
+ <div class="body">
+ <p>
+ <%-post.body%>
+ </p>
+ </div>
+</div>
31 examples/blog/views/index.ejs
@@ -0,0 +1,31 @@
+<h1>
+ <%-title%>
+</h1>
+
+<div id="posts">
+ <% posts.forEach(function(post){ %>
+ <div class="post">
+ <div class="title">
+ <a href="/blog/<%-post.RowKey%>">
+ <%-post.title%>
+ </a>
+ </div>
+
+ <% if (post.created_at) {%>
+ <div class="created_at">
+ Posted on: <%-post.created_at%>
+ </div>
+ <% }%>
+
+ <div class="body">
+ <p>
+ <%-post.body%>
+ </p>
+ </div>
+
+ <a id="deletePost" href="/blog/delete/<%-post.RowKey%>">Delete</a>
+ </div>
+ <% }) %>
+
+ <a id="newPost" href="/blog/new">New blog post</a>
+</div>
8 examples/blog/views/layout.ejs
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <link rel="stylesheet" href="/stylesheets/style.css">
+ </head>
+ <body>
+ <%-body%>
+ </body>
+</html>
220 examples/samples/blobuploaddownloadsample.js
@@ -0,0 +1,220 @@
+/**
+* 1. Demonstrates how to upload all files from a given directory in parallel
+*
+* 2. Demonstrates how to download all files from a given blob container to a given destination directory.
+*
+* 3. Demonstrate making requests using AccessConditions.
+*/
+
+var azure = require('../../lib/azure');
+var BlobConstants = azure.Constants.BlobConstants;
+var ServiceClient = azure.ServiceClient;
+var CloudBlobClient = azure.CloudBlobClient;
+
+var util = require('util');
+var path = require('path');
+var fs = require('fs');
+
+var container = 'updownsample';
+var blob = 'updownsample';
+var blobAccess = 'updownaccesssample';
+
+var blobService = azure.createBlobService();
+
+var processArguments = process.argv;
+
+function createContainer() {
+ // Step 0: Create the container.
+ blobService.createContainerIfNotExists(container, function (error) {
+ if (error) {
+ console.log(error);
+ }
+ else {
+ console.log('Created the container ' + container);
+ uploadSample();
+ }
+ });
+}
+
+function uploadSample() {
+ // Sample 1 : Demonstrates how to upload all files from a given directoy
+ uploadBlobs(processArguments[2], container, function () {
+
+ // Sample 2 : Demonstrates how to download all files from a given
+ // blob container to a given destination directory.
+ downloadBlobs(container, processArguments[3], function () {
+
+ // Sample 3 : Demonstrate making requests using AccessConditions.
+ requestAccessConditionSample(container);
+ });
+ });
+}
+
+function uploadBlobs(sourceDirectoryPath, containerName, callback) {
+ // Step 0 : validate directory is valid.
+ if (!path.existsSync(sourceDirectoryPath)) {
+ console.log(sourceDirectoryPath + ' is an invalid directory path.');
+ } else {
+ listFilesUpload(sourceDirectoryPath, containerName, callback);
+ }
+}
+
+function listFilesUpload(sourceDirectoryPath, containerName, callback) {
+ // Step 1 : Search the directory and generate a list of files to upload.
+ walk(sourceDirectoryPath, function (error, files) {
+ if (error) {
+ console.log(error);
+ } else {
+ uploadFilesParallel(files, containerName, callback);
+ }
+ });
+}
+
+function uploadFilesParallel(files, containerName, callback) {
+ var finished = 0;
+
+ // Step 3 : generate and schedule an upload for each file
+ files.forEach(function (file) {
+ var blobName = file.replace(/^.*[\\\/]/, '');
+
+ blobService.createBlockBlobFromFile(containerName, blobName, file, function (error) {
+ finished++;
+
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Blob ' + blobName + ' upload finished.');
+
+ if (finished === files.length) {
+ // Step 4 : Wait until all workers complete and the blobs are uploaded
+ // to the server.
+ console.log('All files uploaded');
+ callback();
+ }
+ }
+ });
+ });
+}
+
+function downloadBlobs(containerName, destinationDirectoryPath, callback) {
+ // Step 0. Validate directory
+ if (!path.existsSync(destinationDirectoryPath)) {
+ console.log(destinationDirectoryPath + ' is an invalid directory path.');
+ } else {
+ downloadFilesParallel(containerName, destinationDirectoryPath, callback);
+ }
+}
+
+function downloadFilesParallel(containerName, destinationDirectoryPath, callback) {
+ // NOTE: does not handle pagination.
+ blobService.listBlobs(containerName, function (error, blobs) {
+ if (error) {
+ console.log(error);
+ } else {
+ var blobsDownloaded = 0;
+
+ blobs.forEach(function (blob) {
+ blobService.getBlobToFile(containerName, blob.name, destinationDirectoryPath + '/' + blob.name, function (error2) {
+ blobsDownloaded++;
+
+ if (error2) {
+ console.log(error2);
+ } else {
+ console.log('Blob ' + blob.name + ' download finished.');
+
+ if (blobsDownloaded === blobs.length) {
+ // Step 4 : Wait until all workers complete and the blobs are downloaded
+ console.log('All files downloaded');
+ callback();
+ }
+ }
+ });
+ });
+ }
+ });
+}
+
+function requestAccessConditionSample(containerName) {
+ // Step 1: Create a blob.
+ blobService.createBlockBlobFromText(containerName, blobAccess, 'hello', function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Created the blob ' + blobAccess);
+ downloadBlobProperties(containerName, blobAccess);
+ }
+ });
+}
+
+function downloadBlobProperties(containerName, blobName) {
+ // Step 2 : Download the blob attributes to get the ETag.
+ blobService.getBlobProperties(containerName, blobName, function (error, blob) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Blob Etag is: ' + blob.etag);
+ testAccess(containerName, blobName, blob.etag);
+ }
+ });
+}
+
+function testAccess(containerName, blobName, etag) {
+ // Step 2: Use the If-not-match ETag condition to access the blob. By
+ // using the IfNoneMatch condition we are asserting that the blob needs
+ // to have been modified in order to complete the request. In this
+ // sample no other client is accessing the blob, so this will fail as
+ // expected.
+
+ var options = { accessConditions: { 'If-None-Match': etag} };
+ blobService.createBlockBlobFromText(containerName, blobName, 'new hello', options, function (error) {
+ if (error) {
+ console.log('Got an expected exception. Details:');
+ console.log(error);
+ } else {
+ console.log('Blob was incorrectly updated');
+ }
+ });
+}
+
+if (processArguments.length > 5 || processArguments.length < 4) {
+ console.log('Incorrect number of arguments');
+}
+else if (processArguments.length == 5) {
+ // Adding a third argument on the command line, whatever it is, will delete the container before running the sample.
+ blobService.deleteContainer(container, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ createContainer();
+ }
+ });
+}
+else {
+ createContainer();
+}
+
+// Utilitary functions
+
+var walk = function (dir, done) {
+ var results = [];
+ fs.readdir(dir, function (err, list) {
+ if (err) return done(err);
+ var i = 0;
+ (function next() {
+ var file = list[i++];
+ if (!file) return done(null, results);
+ file = dir + '/' + file;
+ fs.stat(file, function (err2, stat) {
+ if (stat && stat.isDirectory()) {
+ walk(file, function (err3, res) {
+ results = results.concat(res);
+ next();
+ });
+ } else {
+ results.push(file);
+ next();
+ }
+ });
+ })();
+ });
+};
132 examples/samples/continuationsample.js
@@ -0,0 +1,132 @@
+/**
+* This sample demonstrates how to handle continuation tokens and virtual "pages" of results when performing a listing
+* operation on the blob service.
+*
+* This sample peformsthe following steps:
+*
+* 0. Create container.
+*
+* 1. Creates 50 blobs.
+*
+* 2. List the first 10(page size) blobs.
+*
+* 3. Check whether there are more results.
+*
+* 4. List the next 10(page size) blobs.
+*
+*/
+
+var azure = require('../../lib/azure');
+var BlobConstants = azure.Constants.BlobConstants;
+var ServiceClient = azure.ServiceClient;
+var CloudBlobClient = azure.CloudBlobClient;
+
+var util = require('util');
+
+var container = 'contsample';
+var blob = 'contsample';
+
+/**
+* The total number of the blobs.
+*/
+var totalBlobsCount = 50;
+
+/**
+* The number of the blobs in one page.
+*/
+var pageSize = 10;
+
+var blobService = azure.createBlobService();
+
+function createContainer() {
+ // Step 0: Create the container.
+ blobService.createContainerIfNotExists(container, function (error) {
+ if (error) {
+ console.log(error);
+ }
+ else {
+ console.log('Created the container ' + container);
+ createBlobs(totalBlobsCount);
+ }
+ });
+}
+
+function createBlobs(currentBlobsCount) {
+ // Step 1 : upload totalBlobsCount blobs to the container.
+ blobService.createBlockBlobFromText(container, blob + currentBlobsCount, 'blob' + currentBlobsCount, function (error) {
+ if (error) {
+ console.log(error);
+ } else if (currentBlobsCount > 1) {
+ createBlobs(--currentBlobsCount);
+ } else {
+ console.log('Created ' + totalBlobsCount + ' blobs.');
+ listPages();
+ }
+ });
+}
+
+function listPages() {
+ // Step 2 : Perform a listing in "pages". A Page is a virtual construct
+ // to allow the client to return a certain number of results at a time.
+ // A good example of this is when using the entries in UI or improving
+ // latency by only downloading the needed results.
+
+ // In addition continuation tokens are expected from the blob service
+ // when doing list operations and must be taken in account. For
+ // convenience an iterator is provided via the listBlobs
+ // methods which will handle the continuation token between requests in
+ // a method that is opaque to the user.
+
+ // The first list operation will return up to pageSize blobs, Note there
+ // is no continuation is specified as this is the first request.
+ blobService.listBlobs(container, { maxresults: pageSize }, function (error, page, pageContinuation) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('There are ' + page.length + ' blobs in this page.');
+ listNextPage(pageContinuation);
+ }
+ });
+}
+
+function listNextPage(pageContinuation) {
+ // Step 3 : Check whether there are more results and list them in pages
+ // of pageSize.
+
+ // The hasNextPage() checks to see if the listing
+ // provided a continuation token meaning there are additional results on
+ // the service to be queried. This will be returned even if the
+ // requested "page size" has been satisfied if there are more blobs on
+ // the service.
+
+ if (pageContinuation.hasNextPage()) {
+ // Step 4 : make the next request from the last continuation token.
+
+ pageContinuation.getNextPage(function (error, page, nextPageContinuation) {
+ console.log('There are ' + page.length + ' blobs in this page.');
+ listNextPage(nextPageContinuation);
+ });
+ }
+ else {
+ console.log('Listing blob in pages completed.');
+ }
+}
+
+var arguments = process.argv;
+
+if (arguments.length > 3) {
+ console.log('Incorrect number of arguments');
+}
+else if (arguments.length == 3) {
+ // Adding a third argument on the command line, whatever it is, will delete the container before running the sample.
+ blobService.deleteContainer(container, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ createContainer();
+ }
+ });
+}
+else {
+ createContainer();
+}
111 examples/samples/leaseblobsample.js
@@ -0,0 +1,111 @@
+/**
+* This sample demonstrates how multiple clients can attempt to acquire a lease in order to provide a locking mechanism
+* over write operations on a given blob.
+*
+* To simulate N workers please run multiple instances of this sample. Each instance will :
+*
+* 1. Try to acquire a lease on a uploaded blob.
+*
+* 2. If succeed, then renew the lease, print out the current process holds the lease every 40 seconds until the process
+* is killed.
+*
+* 3. If fail, then sleep for 10 seconds and go back to step 1.
+*
+*/
+
+var azure = require('../../lib/azure');
+var BlobConstants = azure.Constants.BlobConstants;
+var ServiceClient = azure.ServiceClient;
+var CloudBlobClient = azure.CloudBlobClient;
+
+var workerWithLeaseSleepTimeInMs = 40 * 1000;
+var workerWithoutLeaseSleepTimeInMs = 10 * 1000;
+
+var container = 'leasesample';
+var blob = 'leasesample';
+var leaseId;
+
+var blobClient = azure.createBlobService();
+
+function createContainer(callback) {
+ // Step 0: Check if the target container exists.
+ blobClient.createContainerIfNotExists(container, function () {
+ console.log('Created the container ' + container);
+ callback();
+ });
+};
+
+function uploadBlob(callback) {
+ // Step 1: Create text blob.
+ blobClient.createBlockBlobFromText(container, blob, 'Hello world!', function () {
+ console.log('Created the blob ' + blob);
+ callback();
+ });
+};
+
+function getLease(currentLease) {
+ if (currentLease == null) {
+ // Step 2a: this worker doesn't hold a lease, then try to acquire the lease.
+ blobClient.acquireLease(container, blob, function (leaseError, lease) {
+ if (lease != null) {
+ leaseId = lease.id;
+
+ // Succeed in acquiring a lease.
+ console.log('Worker acquired the lease whose ID is ' + leaseId);
+
+ setTimeout(function () {
+ getLease(lease);
+ }, workerWithLeaseSleepTimeInMs);
+ }
+ else {
+ // Fail in acquiring a lease.
+ console.log('Worker failed in acquiring the lease.');
+
+ setTimeout(function () {
+ getLease(null);
+ }, workerWithoutLeaseSleepTimeInMs);
+ }
+ });
+ }
+ else {
+ // Step 2b: This worker holds a lease, then renew the lease.
+
+ // Traditionally there is some work this worker must do while it
+ // holds the lease on the blob prior to releasing it.
+ blobClient.renewLease(container, blob, leaseId, function (leaseError, lease) {
+ if (lease) {
+ console.log('Worker renewed the lease whose ID is ' + lease.id);
+ }
+ else {
+ console.log('Worker failed in renewing the lease.');
+ }
+
+ setTimeout(function () {
+ getLease(lease);
+ }, workerWithLeaseSleepTimeInMs);
+ });
+ }
+};
+
+var arguments = process.argv;
+
+if (arguments.length > 3) {
+ console.log('Incorrect number of arguments');
+}
+else if (arguments.length == 3) {
+ // Adding a third argument on the command line, whatever it is, will delete the container before running the sample.
+ blobClient.deleteContainer(container, function () {
+ createContainer(function () {
+ uploadBlob(function () {
+ getLease(null);
+ });
+ });
+ });
+}
+else {
+ createContainer(function () {
+ uploadBlob(function () {
+ getLease(null);
+ });
+ });
+}
97 examples/samples/retrypolicysample.js
@@ -0,0 +1,97 @@
+/**
+* Demonstrates how to define a customized retry policy.
+*
+* In this sample, we define a customized retry policy which retries on the "The specified container is being deleted"
+* exception besides the server exceptions.
+*
+* Note that only in the cloud(not the storage emulator), "The specified container is being deleted" exceptions will be
+* sent if users immediately recreate a container after delete it.
+*/
+
+var azure = require('../../lib/azure');
+var BlobConstants = azure.Constants.BlobConstants;
+var ServiceClient = azure.ServiceClient;
+var CloudBlobClient = azure.CloudBlobClient;
+var ExponentialRetryPolicyFilter = azure.ExponentialRetryPolicyFilter;
+
+var util = require('util');
+
+var container = 'retrypolicysample';
+
+var blobServiceNoRetry = azure.createBlobService();
+var blobService;
+
+function setRetryPolicy() {
+ // Step 1 : Set the retry policy to customized retry policy which will
+ // retry on any status code other than the excepted one, including
+ // the "The specified container is being deleted" exception .
+
+ var retryOnContainerBeingDeleted = new ExponentialRetryPolicyFilter();
+ retryOnContainerBeingDeleted.retryCount = 3;
+ retryOnContainerBeingDeleted.retryInterval = 30000;
+
+ retryOnContainerBeingDeleted.shouldRetry = function(statusCode, retryData) {
+ console.log('Made the request at ' + new Date().toUTCString() + ', received StatusCode: ' + statusCode);
+
+ var currentCount = (retryData && retryData.retryCount) ? retryData.retryCount : 0;
+
+ return (currentCount < this.retryCount);
+ };
+
+ blobService = blobServiceNoRetry.withFilter(retryOnContainerBeingDeleted);
+ createContainer();
+}
+
+function createContainer() {
+ // Step 2: Create a container with a random name.
+ blobService.createContainer(container, function(error) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Created the container ' + container);
+ deleteContainer();
+ }
+ });
+}
+
+function deleteContainer() {
+ // Step 3 : Delete a container.
+ blobService.deleteContainer(container, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Deleted the container ' + container);
+ createContainerAgain();
+ }
+ });
+};
+
+function createContainerAgain() {
+ // Step 4 : Attempt to create the container immediately while it was still beeing deleted.
+ blobService.createContainer(container, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Created the container ' + container);
+ }
+ });
+};
+
+var arguments = process.argv;
+
+if (arguments.length > 3) {
+ console.log('Incorrect number of arguments');
+}
+else if (arguments.length == 3) {
+ // Adding a third argument on the command line, whatever it is, will delete the container before running the sample.
+ blobServiceNoRetry.deleteContainer(container, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ setRetryPolicy();
+ }
+ });
+}
+else {
+ setRetryPolicy();
+}
188 examples/samples/sassample.js
@@ -0,0 +1,188 @@
+/**
+* In this sample, we demonstrate how to generate and use the blob level shared access signature and the container level
+* shared access signature.
+*
+* In the blob level shared access signature sample, there are the following steps:
+*
+* a. Create a container and a blob.
+*
+* b. Generate a shared access signature URL for the blob.
+*
+* c. Download that blob through that URL (should fail as the policy was never uploaded).
+*
+* In the container level shared access signature sample, there are the following steps:
+*
+* a. Create a container and a blob.
+*
+* b. Upload a "ReadWrite" policy and a "Read" permission to the container, generate their shared access
+* signatures.
+*
+* c. Use both shared access signatures to read that blob (Expect a failure from the "Read" permission shared access signature since it has already expired.).
+*/
+
+var azure = require('../../lib/azure');
+var BlobConstants = azure.Constants.BlobConstants;
+var ServiceClient = azure.ServiceClient;
+var CloudBlobClient = azure.CloudBlobClient;
+
+var util = require('util');
+var ISO8061Date = require('../../lib/util/iso8061date');
+
+var container = 'sassample';
+var blob = 'sassample';
+
+var blobService = azure.createBlobService();
+
+function createContainer() {
+ // Step 0: Create the container.
+ blobService.createContainerIfNotExists(container, function (error) {
+ if (error) {
+ console.log(error);
+ }
+ else {
+ console.log('Created the container ' + container);
+ createBlob();
+ }
+ });
+}
+
+function createBlob() {
+ // Step 1: Create the blob
+ blobService.createBlockBlobFromText(container, blob, 'test blob', function (error) {
+ if (error) {
+ console.log(error);
+ }
+ else {
+ console.log('Created the blob ' + container);
+ downloadBlobUsingSharedAccessSignature();
+ }
+ });
+}
+
+function downloadBlobUsingSharedAccessSignature() {
+ var startDate = new Date();
+ var expiryDate = new Date(startDate);
+ expiryDate.setMinutes(startDate.getMinutes() + 5);
+
+ var sharedAccessPolicy = {
+ AccessPolicy: {
+ Permissions: azure.Constants.BlobConstants.SharedAccessPermissions.READ,
+ Start: startDate,
+ Expiry: expiryDate
+ }
+ };
+
+ var signature = blobService.generateSharedAccessSignature(container, blob, sharedAccessPolicy);
+
+ var sharedBlobService = azure.createBlobService();
+ var sharedAccessSignature = new azure.SharedAccessSignature(sharedBlobService.storageAccount, sharedBlobService.storageAccessKey);
+ sharedBlobService.authenticationProvider = sharedAccessSignature;
+
+ sharedAccessSignature.permissionSet = [signature];
+
+ // Step 3: Download the blob by using the shared access signature URL. Since the read policy was never uploaded this should fail.
+ sharedBlobService.getBlobProperties(container, blob, function (error) {
+ if (error) {
+ console.log('Failed to download the blob since the permission was invalid.');
+ } else {
+ console.log('Downloaded the blob ' + blob + ' by using the shared access signature URL ' + signature.url());
+ }
+
+ createPolicies();
+ });
+}
+
+function createPolicies() {
+ // Step 4: Create a "ReadWrite" policy and a "Read" policy.
+ var readWriteStartDate = new Date();
+ var readWriteExpiryDate = new Date(readWriteStartDate);
+ readWriteExpiryDate.setMinutes(readWriteStartDate.getMinutes() + 10);
+
+ var readWriteSharedAccessPolicy = {
+ Id: 'readwrite',
+ AccessPolicy: {
+ Start: readWriteStartDate,
+ Expiry: readWriteExpiryDate,
+ Permissions: 'rw'
+ }
+ };
+
+ var readSharedAccessPolicy = {
+ Id: 'read',
+ AccessPolicy: {
+ Expiry: readWriteStartDate,
+ Permissions: 'r'
+ }
+ };
+
+ var options = {};
+ options.signedIdentifiers = [readWriteSharedAccessPolicy, readSharedAccessPolicy];
+
+ blobService.setContainerAcl(container, BlobConstants.BlobContainerPublicAccessType.CONTAINER, options, function(error) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Uploaded the permissions for the container ' + container);
+ usePermissions(readWriteSharedAccessPolicy, readSharedAccessPolicy);
+ }
+ });
+}
+
+function usePermissions(readWriteSharedAccessPolicy, readSharedAccessPolicy) {
+ // Step 5: Read, write the blob using the shared access signature from "ReadWrite" policy.
+ var sharedAccessSignature = blobService.generateSharedAccessSignature(container, null, readWriteSharedAccessPolicy);
+
+ var sharedBlobService = azure.createBlobService();
+ var sharedAccessSignatureProvider = new azure.SharedAccessSignature(sharedBlobService.storageAccount, sharedBlobService.storageAccessKey);
+ sharedBlobService.authenticationProvider = sharedAccessSignatureProvider;
+
+ sharedAccessSignatureProvider.permissionSet = [sharedAccessSignature];
+
+ sharedBlobService.getBlobProperties(container, blob, function (error) {
+ if (error) {
+ console.log('Failed to download the blob since the permission was invalid.');
+ } else {
+ console.log('Downloaded the blob ' + blob + ' by using the shared access signature URL ' + sharedAccessSignature.url());
+ }
+
+ useIncorrectPermission(readSharedAccessPolicy);
+ });
+}
+
+function useIncorrectPermission(readSharedAccessPolicy) {
+ // Step 6: Expect an exception from using the already expired "Read" permission to read the blob.
+ var sharedAccessSignature = blobService.generateSharedAccessSignature(container, null, readSharedAccessPolicy);
+
+ var sharedBlobService = azure.createBlobService();
+ var sharedAccessSignatureProvider = new azure.SharedAccessSignature(sharedBlobService.storageAccount, sharedBlobService.storageAccessKey);
+ sharedBlobService.authenticationProvider = sharedAccessSignatureProvider;
+
+ sharedAccessSignatureProvider.permissionSet = [sharedAccessSignature];
+
+ sharedBlobService.getBlobProperties(container, blob, function (error) {
+ if (error) {
+ console.log('Failed to download the blob since the permission was invalid.');
+ } else {
+ console.log('Downloaded the blob ' + blob + ' by using the shared access signature URL ' + sharedAccessSignature.url());
+ }
+ });
+}
+
+var arguments = process.argv;
+
+if (arguments.length > 3) {
+ console.log('Incorrect number of arguments');
+}
+else if (arguments.length == 3) {
+ // Adding a third argument on the command line, whatever it is, will delete the container before running the sample.
+ blobService.deleteContainer(container, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ createContainer();
+ }
+ });
+}
+else {
+ createContainer();
+}
219 examples/samples/snapshotsample.js
@@ -0,0 +1,219 @@
+/**
+* This sample is used to provide an overview of blob snapshots and how to work with them.
+*
+* 1. Upload 3 blocks and commit them.
+*
+* 2. Take a snapshot for that blob.
+*
+* 3. Re-upload one of the three blocks and commit them.
+*
+* 4. Take a snapshot again.
+*
+* 5. List blobs including snapshots.
+*
+* 6. Promote the first snapshot.
+*
+* 7. Delete the first snapshot.
+*
+* 8. List all snapshots for this blob.
+*/
+
+var azure = require('../../lib/azure');
+var BlobConstants = azure.Constants.BlobConstants;
+var ServiceClient = azure.ServiceClient;
+var CloudBlobClient = azure.CloudBlobClient;
+
+var util = require('util');
+
+var container = 'snapshotsample';
+var blob = 'snapshotsample';
+
+var blockId1 = 'b1';
+var blockId2 = 'b2';
+var blockId3 = 'b3';
+
+var blockContent1 = 'content1';
+var blockContent2 = 'content2';
+var blockContentAlternative2 = 'alternative2';
+var blockContent3 = 'content3';
+
+var blobClient = azure.createBlobService();
+
+function createContainer() {
+ // Step 0: Check if the target container exists.
+ blobClient.createContainerIfNotExists(container, function (error) {
+ if (error) {
+ console.log(error);
+ }
+ else {
+ console.log('Created the container ' + container);
+ uploadblockBlobs();
+ }
+ });
+}
+
+function uploadblockBlobs() {
+ // Step 1: Upload 3 blocks and commit them.
+ var blockList = {
+ LatestBlocks: [blockId1, blockId2, blockId3]
+ };
+
+ blobClient.createBlobBlockFromText(blockList.LatestBlocks[0], container, blob, blockContent1, blockContent1.length, function (error1) {
+ if (error1) {
+ console.log(error1);
+ } else {
+ console.log('Uploaded the block whose ID is ' + blockList.LatestBlocks[0]);
+ blobClient.createBlobBlockFromText(blockList.LatestBlocks[1], container, blob, blockContent2, blockContent2.length, function (error2) {
+ if (error2) {
+ console.log(error2);
+ } else {
+ console.log('Uploaded the block whose ID is ' + blockList.LatestBlocks[1]);
+ blobClient.createBlobBlockFromText(blockList.LatestBlocks[2], container, blob, blockContent3, blockContent3.length, function (error3) {
+ if (error3) {
+ console.log(error3);
+ } else {
+ console.log('Uploaded the block whose ID is ' + blockList.LatestBlocks[2]);
+ blobClient.commitBlobBlocks(container, blob, blockList, function (error4) {
+ if (error4) {
+ console.log(error4);
+ }
+ else {
+ console.log('Committed the blob ' + blob);
+ createSnapshot();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+}
+
+function createSnapshot() {
+ // Step 2 : Creates a snapshot.
+ blobClient.createBlobSnapshot(container, blob, function (error, snapshot1) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Created a snapshot for the blob ' + blob);
+
+ createBlob(snapshot1);
+ }
+ });
+}
+
+function createBlob (snapshot) {
+ // Step 3: Update the block 2, commit the blob again.
+ blobClient.createBlobBlockFromText(blockId2, container, blob, blockContentAlternative2, blockContentAlternative2.length, function (error) {
+ if (error) {
+ console.log(error);
+ } else {
+ console.log('Uploaded the block whose ID is ' + blockId2);
+
+ var blockList = {
+ LatestBlocks: [blockId1, blockId2, blockId3]
+ };
+
+ blobClient.commitBlobBlocks(container, blob, blockList, function (error2) {
+ if (error2) {