Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.storage.blob.nio;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;

/**
* Provides support for basic file attributes.
*
* The operations supported by this view and the attributes it reads are a strict subset of
* {@link AzureBlobFileAttributeView} and has the same network behavior. Therefore, while this type is offered for
* compliance with the NIO spec, {@link AzureBlobFileAttributeView} is generally preferred.
*
* {@link #setTimes(FileTime, FileTime, FileTime)} is not supported.
*
* {@inheritDoc}
*/
public final class AzureBasicFileAttributeView implements BasicFileAttributeView {

private final Path path;

AzureBasicFileAttributeView(Path path) {
this.path = path;
}

/**
* Returns {@code "azureBasic"}
* {@inheritDoc}
*/
@Override
public String name() {
return "azureBasic";
}

/**
* {@inheritDoc}
*/
@Override
public AzureBasicFileAttributes readAttributes() throws IOException {
return new AzureBasicFileAttributes(path);
}

/**
* Unsupported.
*
* @throws UnsupportedOperationException Operation not supported.
* {@inheritDoc}
*/
@Override
public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.storage.blob.nio;

import com.azure.core.util.logging.ClientLogger;
import com.azure.storage.blob.models.BlobProperties;
import com.azure.storage.blob.models.BlobStorageException;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
* Provides support for basic file attributes.
* <p>
* The properties available on this type are a strict subset of {@link AzureBlobFileAttributes}, and the two types have
* the same network behavior. Therefore, while this type is offered for compliance with the NIO spec,
* {@link AzureBlobFileAttributes} is generally preferred.
* <p>
* Some attributes are not supported. Refer to the javadocs on each method for more information.
* {@inheritDoc}
*/
public class AzureBasicFileAttributes implements BasicFileAttributes {
private final ClientLogger logger = new ClientLogger(AzureBasicFileAttributes.class);

// For verifying parameters on FileSystemProvider.readAttributes
static final Set<String> ATTRIBUTE_STRINGS;
static {
Set<String> set = new HashSet<>();
set.add("lastModifiedTime");
set.add("isRegularFile");
set.add("isDirectory");
set.add("isSymbolicLink");
set.add("isOther");
set.add("size");
set.add("creationTime");
ATTRIBUTE_STRINGS = Collections.unmodifiableSet(set);
}

private final BlobProperties properties;

/*
There are some work-arounds we could do to try to accommodate virtual directories such as making a checkDirStatus
call before or after getProperties to throw an appropriate error or adding an isVirtualDirectory method. However,
the former wastes network time only to throw a slightly more specific error when we will throw on 404 anyway. The
latter introduces virtual directories into the actual code path/api surface. While we are clear in our docs about
the possible pitfalls of virtual directories, and customers should be aware of it, they shouldn't have to code
against it. Therefore, we fall back to documenting that reading attributes on a virtual directory will throw.
*/
AzureBasicFileAttributes(Path path) throws IOException {
try {
this.properties = new AzureResource(path).getBlobClient().getProperties();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if properties change while someone is holding reference to this object? I.e. lastModifiedTime shifts between calling this constructor and lastModifiedTime()?
Could you check what happens if you use local file system?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the attributes always return the same value once you've read them, and if you want an updated value, you need to make another call to read attributes, which is what we do here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, thanks for checking.

} catch (BlobStorageException e) {
throw LoggingUtility.logError(logger, new IOException(e));
}
}

/**
* {@inheritDoc}
*/
@Override
public FileTime lastModifiedTime() {
return FileTime.from(properties.getLastModified().toInstant());
}

/**
* Unsupported.
* @throws UnsupportedOperationException Operation not supported.
* {@inheritDoc}
*/
@Override
public FileTime lastAccessTime() {
throw new UnsupportedOperationException();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to add @throws to javadocs for unsupported apis.

}

/**
* {@inheritDoc}
*/
@Override
public FileTime creationTime() {
return FileTime.from(properties.getCreationTime().toInstant());
}

/**
* {@inheritDoc}
*/
@Override
public boolean isRegularFile() {
return !this.properties.getMetadata().getOrDefault(AzureResource.DIR_METADATA_MARKER, "false").equals("true");
}

/**
* {@inheritDoc}
* <p>
* Will only return true if the directory is a concrete directory. See
* {@link AzureFileSystemProvider#createDirectory(Path, FileAttribute[])} for more information on virtual and
* concrete directories.
*/
@Override
public boolean isDirectory() {
return !this.isRegularFile();
}

/**
* @return false. Symbolic links are not supported.
*/
@Override
public boolean isSymbolicLink() {
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this throw UnsupportedOperationException if it isn't supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. In some sense it's just always false, right? But maybe it makes it more clear that sym links aren't supported if I throw?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think returning always false is correct. Throwing UnsupportedOperationException would mean "I cannot check if file is symlink or not` in plain English. I believe our file system doesn't have symlinks so all files are real by definition, therefore we know how to check if they're symlinks or not.

}

/**
* @return false
*/
@Override
public boolean isOther() {
return false;
}

/**
* {@inheritDoc}
*/
@Override
public long size() {
return properties.getBlobSize();
}

/**
* Unsupported.
* @throws UnsupportedOperationException Operation not supported.
* {@inheritDoc}
*/
@Override
public Object fileKey() {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.storage.blob.nio;

import com.azure.core.util.logging.ClientLogger;
import com.azure.storage.blob.models.AccessTier;
import com.azure.storage.blob.models.BlobHttpHeaders;
import com.azure.storage.blob.models.BlobStorageException;
import com.azure.storage.blob.specialized.BlobClientBase;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

/**
* A file attribute view that provides a view of attributes specific to files stored as blobs in Azure Storage.
* <p>
* All attributes are retrieved from the file system as a bulk operation.
* <p>
* {@link #setTimes(FileTime, FileTime, FileTime)} is not supported.
*/
public final class AzureBlobFileAttributeView implements BasicFileAttributeView {
private final ClientLogger logger = new ClientLogger(AzureBlobFileAttributeView.class);

static final String ATTR_CONSUMER_ERROR = "Exception thrown by attribute consumer";

private final Path path;

AzureBlobFileAttributeView(Path path) {
this.path = path;
}

@SuppressWarnings("unchecked")
static Map<String, Consumer<Object>> setAttributeConsumers(AzureBlobFileAttributeView view) {
Map<String, Consumer<Object>> map = new HashMap<>();
map.put("blobHttpHeaders", obj -> {
try {
view.setBlobHttpHeaders((BlobHttpHeaders) obj);
} catch (IOException e) {
throw LoggingUtility.logError(view.logger, new UncheckedIOException(ATTR_CONSUMER_ERROR, e));
}
});
map.put("metadata", obj -> {
try {
Map<String, String> m = (Map<String, String>) obj;
if (m == null) {
throw LoggingUtility.logError(view.logger, new ClassCastException());
}
view.setMetadata(m);
} catch (IOException e) {
throw LoggingUtility.logError(view.logger, new UncheckedIOException(ATTR_CONSUMER_ERROR, e));
}
});
map.put("tier", obj -> {
try {
view.setTier((AccessTier) obj);
} catch (IOException e) {
throw LoggingUtility.logError(view.logger, new UncheckedIOException(ATTR_CONSUMER_ERROR, e));
}
});

return map;
}

/**
* Returns "azureBlob".
* {@inheritDoc}
*/
@Override
public String name() {
return "azureBlob";
}

/**
* Reads the file attributes as a bulk operation.
*
* Gets a fresh copy every time it is called.
* @return {@link AzureBlobFileAttributes}
* @throws IOException if an IOException occurs.
*/
@Override
public AzureBlobFileAttributes readAttributes() throws IOException {
return new AzureBlobFileAttributes(path);
}

/**
* Sets the {@link BlobHttpHeaders} as an atomic operation.
* <p>
* See {@link BlobClientBase#setHttpHeaders(BlobHttpHeaders)} for more information.
* @param headers {@link BlobHttpHeaders}
* @throws IOException if an IOException occurs.
*/
public void setBlobHttpHeaders(BlobHttpHeaders headers) throws IOException {
try {
new AzureResource(this.path).getBlobClient().setHttpHeaders(headers);
} catch (BlobStorageException e) {
throw LoggingUtility.logError(logger, new IOException(e));
}
}

/**
* Sets the metadata as an atomic operation.
* <p>
* See {@link BlobClientBase#setMetadata(Map)} for more information.
* @param metadata The metadata to associate with the blob
* @throws IOException if an IOException occurs.
*/
public void setMetadata(Map<String, String> metadata) throws IOException {
try {
new AzureResource(this.path).getBlobClient().setMetadata(metadata);
} catch (BlobStorageException e) {
throw LoggingUtility.logError(logger, new IOException(e));
}
}

/**
* Sets the {@link AccessTier} on the file.
*
* See {@link BlobClientBase#setAccessTier(AccessTier)} for more information.
* @param tier {@link AccessTier}
* @throws IOException if an IOException occurs.
*/
public void setTier(AccessTier tier) throws IOException {
try {
new AzureResource(this.path).getBlobClient().setAccessTier(tier);
} catch (BlobStorageException e) {
throw LoggingUtility.logError(logger, new IOException(e));
}
}

/**
* Unsupported.
*
* @throws UnsupportedOperationException Operation not supported.
* {@inheritDoc}
*/
@Override
public void setTimes(FileTime fileTime, FileTime fileTime1, FileTime fileTime2) throws IOException {
throw new UnsupportedOperationException();
}
}
Loading