diff --git a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/AzureBlobConnection.java b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/AzureBlobConnection.java index ea11d7be46..f5d662cfce 100644 --- a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/AzureBlobConnection.java +++ b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/AzureBlobConnection.java @@ -27,9 +27,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.ws.rs.GET; +import javax.ws.rs.PUT; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import org.jclouds.azure.storage.blob.domain.ContainerMetadataList; +import org.jclouds.azure.storage.blob.options.CreateContainerOptions; import org.jclouds.azure.storage.blob.xml.AccountNameEnumerationResultsHandler; import org.jclouds.azure.storage.filters.SharedKeyAuthentication; import org.jclouds.azure.storage.options.ListOptions; @@ -56,6 +59,8 @@ public interface AzureBlobConnection { /** * The List Containers operation returns a list of the containers under the specified account. + *

+ * The 2009-07-17 version of the List Containers operation times out after 30 seconds. * * @param listOptions * controls the number or type of results requested @@ -67,4 +72,19 @@ public interface AzureBlobConnection { @Query(key = "comp", value = "list") ContainerMetadataList listContainers(ListOptions... listOptions); + /** + * The Create Container operation creates a new container under the specified account. If the + * container with the same name already exists, the operation fails. + *

+ * The container resource includes metadata and properties for that container. It does not + * include a list of the blobs contained by the container. + * + * @see CreateContainerOptions + * + */ + @PUT + @Path("{container}") + @Query(key = "restype", value = "container") + boolean createContainer(@PathParam("container") String container, + CreateContainerOptions... options); } diff --git a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/domain/ContainerMetadataList.java b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/domain/ContainerMetadataList.java index 673a21f459..3fd5292ede 100644 --- a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/domain/ContainerMetadataList.java +++ b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/domain/ContainerMetadataList.java @@ -31,36 +31,37 @@ import java.util.List; * */ public class ContainerMetadataList { + private String prefix; + private String marker; private int maxResults; private List containerMetadata; private String nextMarker; - public ContainerMetadataList(int maxResults, List containerMetadata, - String nextMarker) { + @Override + public String toString() { + return "ContainerMetadataList [containerMetadata=" + containerMetadata + ", marker=" + marker + + ", maxResults=" + maxResults + ", nextMarker=" + nextMarker + ", prefix=" + prefix + + "]"; + } + + public ContainerMetadataList(String prefix, String marker, int maxResults, + List containerMetadata, String nextMarker) { + this.prefix = prefix; + this.marker = marker; this.maxResults = maxResults; this.containerMetadata = containerMetadata; this.nextMarker = nextMarker; } - public int getMaxResults() { - return maxResults; - } - - public List getContainerMetadata() { - return containerMetadata; - } - - public String getNextMarker() { - return nextMarker; - } - @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((containerMetadata == null) ? 0 : containerMetadata.hashCode()); + result = prime * result + ((marker == null) ? 0 : marker.hashCode()); result = prime * result + maxResults; result = prime * result + ((nextMarker == null) ? 0 : nextMarker.hashCode()); + result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); return result; } @@ -78,6 +79,11 @@ public class ContainerMetadataList { return false; } else if (!containerMetadata.equals(other.containerMetadata)) return false; + if (marker == null) { + if (other.marker != null) + return false; + } else if (!marker.equals(other.marker)) + return false; if (maxResults != other.maxResults) return false; if (nextMarker == null) { @@ -85,13 +91,52 @@ public class ContainerMetadataList { return false; } else if (!nextMarker.equals(other.nextMarker)) return false; + if (prefix == null) { + if (other.prefix != null) + return false; + } else if (!prefix.equals(other.prefix)) + return false; return true; } - @Override - public String toString() { - return "ContainerMetadataList [containerMetadata=" + containerMetadata + ", maxResults=" - + maxResults + ", nextMarker=" + nextMarker + "]"; + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } + + public void setMarker(String marker) { + this.marker = marker; + } + + public String getMarker() { + return marker; + } + + public void setMaxResults(int maxResults) { + this.maxResults = maxResults; + } + + public int getMaxResults() { + return maxResults; + } + + public void setContainerMetadata(List containerMetadata) { + this.containerMetadata = containerMetadata; + } + + public List getContainerMetadata() { + return containerMetadata; + } + + public void setNextMarker(String nextMarker) { + this.nextMarker = nextMarker; + } + + public String getNextMarker() { + return nextMarker; } } diff --git a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/options/CreateContainerOptions.java b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/options/CreateContainerOptions.java new file mode 100644 index 0000000000..ff761fd68f --- /dev/null +++ b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/options/CreateContainerOptions.java @@ -0,0 +1,74 @@ +package org.jclouds.azure.storage.blob.options; + +import java.util.Map.Entry; + +import org.jclouds.azure.storage.reference.AzureStorageHeaders; +import org.jclouds.http.options.BaseHttpRequestOptions; + +import com.google.common.collect.Multimap; + +/** + * Contains options supported in the REST API for the Create Container operation.

+ * Usage

The recommended way to instantiate a CreateContainerOptions object is to statically + * import CreateContainerOptions.* and invoke a static creation method followed by an instance + * mutator (if needed): + *

+ * + * import static org.jclouds.azure.storage.blob.options.PutBucketOptions.Builder.* + * import org.jclouds.azure.storage.blob.AzureBlobConnection; + *

+ * AzureBlobConnection connection = // get connection + * boolean createdWithPublicAcl = connection.createContainer("containerName", withPublicAcl()); + * * + * + * @see + * @author Adrian Cole + */ +public class CreateContainerOptions extends BaseHttpRequestOptions { + public static final CreateContainerOptions NONE = new CreateContainerOptions(); + + /** + * Indicates whether a container may be accessed publicly + */ + public CreateContainerOptions withPublicAcl() { + this.headers.put("x-ms-prop-publicaccess", "true"); + return this; + } + + /** + * A name-value pair to associate with the container as metadata. + * + * Note that these are stored at the server under the prefix: x-ms-meta- + */ + public CreateContainerOptions withMetadata(Multimap metadata) { + for (Entry entry : metadata.entries()) { + if (entry.getKey().startsWith(AzureStorageHeaders.USER_METADATA_PREFIX)) + headers.put(entry.getKey(), entry.getValue()); + else + headers + .put(AzureStorageHeaders.USER_METADATA_PREFIX + entry.getKey(), entry + .getValue()); + } + return this; + } + + public static class Builder { + + /** + * @see CreateContainerOptions#withPublicAcl() + */ + public static CreateContainerOptions withPublicAcl() { + CreateContainerOptions options = new CreateContainerOptions(); + return options.withPublicAcl(); + } + + /** + * @see CreateContainerOptions#withMetadata(Multimap) + */ + public static CreateContainerOptions withMetadata(Multimap metadata) { + CreateContainerOptions options = new CreateContainerOptions(); + return options.withMetadata(metadata); + } + + } +} diff --git a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandler.java b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandler.java index 562ba25bc7..cab4a3db58 100755 --- a/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandler.java +++ b/azure/storage/blob/core/src/main/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandler.java @@ -47,7 +47,9 @@ import com.google.inject.Inject; public class AccountNameEnumerationResultsHandler extends ParseSax.HandlerWithResult { - private List containers = new ArrayList(); + private List containerMetadata = new ArrayList(); + private String prefix; + private String marker; private int maxResults; private String nextMarker; private URI currentUrl; @@ -64,16 +66,20 @@ public class AccountNameEnumerationResultsHandler extends } public ContainerMetadataList getResult() { - return new ContainerMetadataList(maxResults, containers, nextMarker); + return new ContainerMetadataList(prefix, marker, maxResults, containerMetadata, nextMarker); } public void endElement(String uri, String name, String qName) { if (qName.equals("MaxResults")) { maxResults = Integer.parseInt(currentText.toString().trim()); + } else if (qName.equals("Marker")) { + marker = currentText.toString().trim(); + } else if (qName.equals("Prefix")) { + prefix = currentText.toString().trim(); } else if (qName.equals("NextMarker")) { nextMarker = currentText.toString().trim(); } else if (qName.equals("Container")) { - containers.add(new ContainerMetadata(currentUrl, currentLastModified, currentETag)); + containerMetadata.add(new ContainerMetadata(currentUrl, currentLastModified, currentETag)); currentUrl = null; currentLastModified = null; currentETag = null; diff --git a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionLiveTest.java b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionLiveTest.java index 8c9f06e2a2..939e1cc9b7 100644 --- a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionLiveTest.java +++ b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionLiveTest.java @@ -1,13 +1,22 @@ package org.jclouds.azure.storage.blob; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URL; +import java.security.SecureRandom; + import org.jclouds.azure.storage.blob.domain.ContainerMetadataList; +import org.jclouds.azure.storage.blob.options.CreateContainerOptions; +import org.jclouds.azure.storage.options.ListOptions; import org.jclouds.azure.storage.reference.AzureStorageConstants; import org.jclouds.logging.log4j.config.Log4JLoggingModule; +import org.jclouds.util.Utils; import org.testng.annotations.BeforeGroups; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableMultimap; import com.google.inject.Injector; /** @@ -24,6 +33,8 @@ public class AzureBlobConnectionLiveTest { .getProperty(AzureStorageConstants.PROPERTY_AZURESTORAGE_KEY); protected AzureBlobConnection connection; + private String containerPrefix = System.getProperty("user.name") + "-azureblob"; + @BeforeGroups(groups = { "live" }) public void setupConnection() { Injector injector = AzureBlobContextBuilder.newBuilder(sysAzureStorageAccount, @@ -42,4 +53,60 @@ public class AzureBlobConnectionLiveTest { } + String privateContainer; + String publicContainer; + + @Test(timeOut = 5 * 60 * 1000) + public void testCreateContainer() throws Exception { + boolean created = false; + while (!created) { + privateContainer = containerPrefix + new SecureRandom().nextInt(); + try { + created = connection.createContainer(privateContainer, CreateContainerOptions.Builder + .withMetadata(ImmutableMultimap.of("foo", "bar"))); + } catch (UndeclaredThrowableException e) { + // HttpResponseException htpe = (HttpResponseException) e.getCause().getCause(); + // TODO: check if already created and see what the error is, and continue + throw e; + } + } + ContainerMetadataList response = connection.listContainers(); + assert null != response; + long containerCount = response.getContainerMetadata().size(); + assertTrue(containerCount >= 1); + // TODO ... check to see the container actually exists + } + + @Test(timeOut = 5 * 60 * 1000) + public void testCreatePublicContainer() throws Exception { + boolean created = false; + while (!created) { + publicContainer = containerPrefix + new SecureRandom().nextInt(); + try { + created = connection.createContainer(publicContainer, CreateContainerOptions.Builder + .withPublicAcl()); + } catch (UndeclaredThrowableException e) { + // HttpResponseException htpe = (HttpResponseException) e.getCause().getCause(); + // TODO: check if already created and see what the error is, and continue + throw e; + } + } + + URL url = new URL(String.format("http://%s.blob.core.windows.net/%s", sysAzureStorageAccount, + publicContainer)); + Utils.toStringAndClose(url.openStream()); + } + + @Test + public void testListContainersWithOptions() throws Exception { + + ContainerMetadataList response = connection.listContainers(ListOptions.Builder.prefix( + privateContainer).maxResults(1)); + assert null != response; + long initialContainerCount = response.getContainerMetadata().size(); + assertTrue(initialContainerCount >= 0); + assertEquals(privateContainer, response.getPrefix()); + assertEquals(1, response.getMaxResults()); + + } } diff --git a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionTest.java b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionTest.java index fd0e8e120c..6669189f5a 100644 --- a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionTest.java +++ b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/AzureBlobConnectionTest.java @@ -1,5 +1,6 @@ package org.jclouds.azure.storage.blob; +import static org.jclouds.azure.storage.blob.options.CreateContainerOptions.Builder.withPublicAcl; import static org.jclouds.azure.storage.options.ListOptions.Builder.maxResults; import static org.testng.Assert.assertEquals; @@ -9,6 +10,7 @@ import java.util.Collections; import javax.ws.rs.HttpMethod; +import org.jclouds.azure.storage.blob.options.CreateContainerOptions; import org.jclouds.azure.storage.blob.xml.config.AzureBlobParserModule; import org.jclouds.azure.storage.options.ListOptions; import org.jclouds.azure.storage.reference.AzureStorageConstants; @@ -18,11 +20,13 @@ import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpUtils; import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; import org.jclouds.http.functions.ParseSax; +import org.jclouds.http.functions.ReturnTrueIf2xx; import org.jclouds.rest.JaxrsAnnotationProcessor; import org.jclouds.rest.config.JaxrsModule; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableMultimap; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.name.Names; @@ -39,8 +43,10 @@ public class AzureBlobConnectionTest { private static final Class listOptionsVarargsClass = new ListOptions[] {} .getClass(); + private static final Class createContainerOptionsVarargsClass = new CreateContainerOptions[] {} + .getClass(); - public void testListServers() throws SecurityException, NoSuchMethodException { + public void testListContainers() throws SecurityException, NoSuchMethodException { Method method = AzureBlobConnection.class .getMethod("listContainers", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); @@ -57,7 +63,7 @@ public class AzureBlobConnectionTest { assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } - public void testListServersOptions() throws SecurityException, NoSuchMethodException { + public void testListContainersOptions() throws SecurityException, NoSuchMethodException { Method method = AzureBlobConnection.class .getMethod("listContainers", listOptionsVarargsClass); URI endpoint = URI.create("http://localhost"); @@ -76,6 +82,47 @@ public class AzureBlobConnectionTest { assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); } + public void testCreateContainers() throws SecurityException, NoSuchMethodException { + Method method = AzureBlobConnection.class.getMethod("createContainer", String.class, + createContainerOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, + new Object[] { "container" }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/container"); + assertEquals(httpMethod.getEndpoint().getQuery(), "restype=container"); + assertEquals(httpMethod.getMethod(), HttpMethod.PUT); + assertEquals(httpMethod.getHeaders().size(), 2); + assertEquals(httpMethod.getHeaders().get("x-ms-version"), Collections + .singletonList("2009-07-17")); + assertEquals(httpMethod.getHeaders().get("Content-Length"), Collections.singletonList("0")); + assertEquals(processor.createResponseParser(method).getClass(), ReturnTrueIf2xx.class); + // TODO check generic type of response parser + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + + public void testCreateContainersOptions() throws SecurityException, NoSuchMethodException { + Method method = AzureBlobConnection.class.getMethod("createContainer", String.class, + createContainerOptionsVarargsClass); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = processor.createRequest(endpoint, method, new Object[] { + "container", withPublicAcl().withMetadata(ImmutableMultimap.of("foo", "bar")) }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/container"); + assertEquals(httpMethod.getEndpoint().getQuery(), "restype=container"); + assertEquals(httpMethod.getMethod(), HttpMethod.PUT); + assertEquals(httpMethod.getHeaders().size(), 4); + assertEquals(httpMethod.getHeaders().get("x-ms-version"), Collections + .singletonList("2009-07-17")); + assertEquals(httpMethod.getHeaders().get("x-ms-meta-foo"), Collections.singletonList("bar")); + assertEquals(httpMethod.getHeaders().get("x-ms-prop-publicaccess"), Collections + .singletonList("true")); + assertEquals(httpMethod.getHeaders().get("Content-Length"), Collections.singletonList("0")); + assertEquals(processor.createResponseParser(method).getClass(), ReturnTrueIf2xx.class); + // TODO check generic type of response parser + assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null); + } + JaxrsAnnotationProcessor processor; @BeforeClass diff --git a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/options/CreateContainerOptionsTest.java b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/options/CreateContainerOptionsTest.java new file mode 100644 index 0000000000..8ecf05a6af --- /dev/null +++ b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/options/CreateContainerOptionsTest.java @@ -0,0 +1,52 @@ +package org.jclouds.azure.storage.blob.options; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.azure.storage.reference.AzureStorageHeaders; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; + +/** + * Tests behavior of {@code CreateContainerOptions} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "azurestorage.CreateContainerOptionsTest") +public class CreateContainerOptionsTest { + + public void testPublicAcl() { + CreateContainerOptions options = new CreateContainerOptions().withPublicAcl(); + assertEquals(ImmutableList.of("true"), options.buildRequestHeaders().get( + "x-ms-prop-publicaccess")); + } + + public void testPublicAclStatic() { + CreateContainerOptions options = CreateContainerOptions.Builder.withPublicAcl(); + assertEquals(ImmutableList.of("true"), options.buildRequestHeaders().get( + "x-ms-prop-publicaccess")); + } + + public void testMetadata() { + CreateContainerOptions options = new CreateContainerOptions().withMetadata(ImmutableMultimap + .of("test", "foo")); + assertEquals(ImmutableList.of("foo"), options.buildRequestHeaders().get( + AzureStorageHeaders.USER_METADATA_PREFIX + "test")); + } + + public void testMetadataAlreadyPrefixed() { + CreateContainerOptions options = new CreateContainerOptions().withMetadata(ImmutableMultimap + .of(AzureStorageHeaders.USER_METADATA_PREFIX + "test", "foo")); + assertEquals(ImmutableList.of("foo"), options.buildRequestHeaders().get( + AzureStorageHeaders.USER_METADATA_PREFIX + "test")); + } + + public void testMetadataStatic() { + CreateContainerOptions options = CreateContainerOptions.Builder + .withMetadata(ImmutableMultimap.of("test", "foo")); + assertEquals(ImmutableList.of("foo"), options.buildRequestHeaders().get( + AzureStorageHeaders.USER_METADATA_PREFIX + "test")); + } + +} diff --git a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandlerTest.java b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandlerTest.java index 1773754eda..f2fd18e3c9 100644 --- a/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandlerTest.java +++ b/azure/storage/blob/core/src/test/java/org/jclouds/azure/storage/blob/xml/AccountNameEnumerationResultsHandlerTest.java @@ -46,7 +46,7 @@ public class AccountNameEnumerationResultsHandlerTest extends BaseHandlerTest { public void testApplyInputStream() { InputStream is = getClass().getResourceAsStream("/test_list_containers.xml"); - ContainerMetadataList list = new ContainerMetadataList(3, ImmutableList.of( + ContainerMetadataList list = new ContainerMetadataList(null, null, 3, ImmutableList.of( new ContainerMetadata(URI.create("http://myaccount.blob.core.windows.net/audio"), dateService.rfc822DateParse("Wed, 13 Aug 2008 20:39:39 GMT"), HttpUtils .fromHexString("0x8CACB9BD7C6B1B2")), new ContainerMetadata(URI @@ -62,4 +62,24 @@ public class AccountNameEnumerationResultsHandlerTest extends BaseHandlerTest { ContainerMetadataList result = parser.parse(is); assertEquals(result, list); } + + public void testApplyInputStreamWithOptions() { + InputStream is = getClass().getResourceAsStream("/test_list_containers_options.xml"); + ContainerMetadataList list = new ContainerMetadataList("prefix", "marker", 1, ImmutableList + .of(new ContainerMetadata( + URI.create("http://myaccount.blob.core.windows.net/audio"), dateService + .rfc822DateParse("Wed, 13 Aug 2008 20:39:39 GMT"), HttpUtils + .fromHexString("0x8CACB9BD7C6B1B2")), new ContainerMetadata(URI + .create("http://myaccount.blob.core.windows.net/images"), dateService + .rfc822DateParse("Wed, 14 Aug 2008 20:39:39 GMT"), HttpUtils + .fromHexString("0x8CACB9BD7C1EEEC")), new ContainerMetadata(URI + .create("http://myaccount.blob.core.windows.net/textfiles"), dateService + .rfc822DateParse("Wed, 15 Aug 2008 20:39:39 GMT"), HttpUtils + .fromHexString("0x8CACB9BD7BACAC3")) + + ), "video"); + ParseSax parser = parserFactory.createContainerMetadataListParser(); + ContainerMetadataList result = parser.parse(is); + assertEquals(result, list); + } } diff --git a/azure/storage/blob/core/src/test/resources/test_list_containers_options.xml b/azure/storage/blob/core/src/test/resources/test_list_containers_options.xml new file mode 100644 index 0000000000..115b6a85da --- /dev/null +++ b/azure/storage/blob/core/src/test/resources/test_list_containers_options.xml @@ -0,0 +1,24 @@ + + + prefix + marker + 1 + + + http://myaccount.blob.core.windows.net/audio + Wed, 13 Aug 2008 20:39:39 GMT + 0x8CACB9BD7C6B1B2 + + + http://myaccount.blob.core.windows.net/images + Wed, 14 Aug 2008 20:39:39 GMT + 0x8CACB9BD7C1EEEC + + + http://myaccount.blob.core.windows.net/textfiles + Wed, 15 Aug 2008 20:39:39 GMT + 0x8CACB9BD7BACAC3 + + + video + \ No newline at end of file