Skip to content

Commit

Permalink
feat: FileSystemProvider::readAttributes for basic and gcs views (#1066)
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsbasjes committed Dec 5, 2022
1 parent b55a162 commit 2f13792
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Singleton;
Expand Down Expand Up @@ -839,11 +841,93 @@ whose name ends in slash (and these files aren't always zero-size).
}

@Override
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) {
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
throws IOException {
// TODO(#811): Java 7 NIO defines at least eleven string attributes we'd want to support
// (eg. BasicFileAttributeView and PosixFileAttributeView), so rather than a partial
// implementation we rely on the other overload for now.
throw new UnsupportedOperationException();

// Partial implementation for a few commonly used ones: basic, gcs
String[] split = attributes.split(":", 2);
if (split.length != 2) {
throw new UnsupportedOperationException();
}
String view = split[0];
List<String> attributeNames = Arrays.asList(split[1].split(","));
boolean allAttributes = attributeNames.size() == 1 && attributeNames.get(0).equals("*");

BasicFileAttributes fileAttributes;

Map<String, Object> results = new TreeMap<>();
switch (view) {
case "gcs":
fileAttributes = readAttributes(path, CloudStorageFileAttributes.class, options);
break;
case "basic":
fileAttributes = readAttributes(path, BasicFileAttributes.class, options);
break;
default:
throw new UnsupportedOperationException();
}

if (fileAttributes == null) {
throw new UnsupportedOperationException();
}

// BasicFileAttributes
if (allAttributes || attributeNames.contains("lastModifiedTime")) {
results.put("lastModifiedTime", fileAttributes.lastModifiedTime());
}
if (allAttributes || attributeNames.contains("lastAccessTime")) {
results.put("lastAccessTime", fileAttributes.lastAccessTime());
}
if (allAttributes || attributeNames.contains("creationTime")) {
results.put("creationTime", fileAttributes.creationTime());
}
if (allAttributes || attributeNames.contains("isRegularFile")) {
results.put("isRegularFile", fileAttributes.isRegularFile());
}
if (allAttributes || attributeNames.contains("isDirectory")) {
results.put("isDirectory", fileAttributes.isDirectory());
}
if (allAttributes || attributeNames.contains("isSymbolicLink")) {
results.put("isSymbolicLink", fileAttributes.isSymbolicLink());
}
if (allAttributes || attributeNames.contains("isOther")) {
results.put("isOther", fileAttributes.isOther());
}
if (allAttributes || attributeNames.contains("size")) {
results.put("size", fileAttributes.size());
}

// CloudStorageFileAttributes
if (fileAttributes instanceof CloudStorageFileAttributes) {
CloudStorageFileAttributes cloudStorageFileAttributes =
(CloudStorageFileAttributes) fileAttributes;
if (allAttributes || attributeNames.contains("etag")) {
results.put("etag", cloudStorageFileAttributes.etag());
}
if (allAttributes || attributeNames.contains("mimeType")) {
results.put("mimeType", cloudStorageFileAttributes.mimeType());
}
if (allAttributes || attributeNames.contains("acl")) {
results.put("acl", cloudStorageFileAttributes.acl());
}
if (allAttributes || attributeNames.contains("cacheControl")) {
results.put("cacheControl", cloudStorageFileAttributes.cacheControl());
}
if (allAttributes || attributeNames.contains("contentEncoding")) {
results.put("contentEncoding", cloudStorageFileAttributes.contentEncoding());
}
if (allAttributes || attributeNames.contains("contentDisposition")) {
results.put("contentDisposition", cloudStorageFileAttributes.contentDisposition());
}
if (allAttributes || attributeNames.contains("userMetadata")) {
results.put("userMetadata", cloudStorageFileAttributes.userMetadata());
}
}

return results;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.cloud.storage.contrib.nio;

import static com.google.cloud.storage.Acl.Role.OWNER;
import static com.google.cloud.storage.contrib.nio.CloudStorageFileSystem.forBucket;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
Expand All @@ -26,10 +27,17 @@
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import com.google.cloud.storage.Acl;
import com.google.cloud.storage.Acl.User;
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
import com.google.cloud.testing.junit4.MultipleAttemptsRule;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.testing.NullPointerTester;
import java.io.IOException;
Expand All @@ -49,10 +57,13 @@
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
Expand Down Expand Up @@ -817,6 +828,96 @@ public void getUserAgentStartsWithCorrectToken() {
.startsWith("gcloud-java-nio/");
}

@Test
public void testReadAttributes() throws IOException {
CloudStorageFileSystem fileSystem = forBucket("dummy");
CloudStorageFileSystemProvider fileSystemProvider = spy(fileSystem.provider());

BasicFileAttributes attributesBasic = mock(BasicFileAttributes.class);
// BasicFileAttributes
when(attributesBasic.creationTime()).thenReturn(FileTime.fromMillis(1L));
when(attributesBasic.lastModifiedTime()).thenReturn(FileTime.fromMillis(2L));
when(attributesBasic.lastAccessTime()).thenReturn(FileTime.fromMillis(3L));
when(attributesBasic.isRegularFile()).thenReturn(true);
when(attributesBasic.isDirectory()).thenReturn(true);
when(attributesBasic.isSymbolicLink()).thenReturn(true);
when(attributesBasic.isOther()).thenReturn(true);
when(attributesBasic.size()).thenReturn(42L);

CloudStorageFileAttributes attributesGcs = mock(CloudStorageFileAttributes.class);
// BasicFileAttributes
when(attributesGcs.creationTime()).thenReturn(FileTime.fromMillis(1L));
when(attributesGcs.lastModifiedTime()).thenReturn(FileTime.fromMillis(2L));
when(attributesGcs.lastAccessTime()).thenReturn(FileTime.fromMillis(3L));
when(attributesGcs.isRegularFile()).thenReturn(true);
when(attributesGcs.isDirectory()).thenReturn(true);
when(attributesGcs.isSymbolicLink()).thenReturn(true);
when(attributesGcs.isOther()).thenReturn(true);
when(attributesGcs.size()).thenReturn(42L);

List<Acl> acls = ImmutableList.of(Acl.newBuilder(new User("Foo"), OWNER).build());

// CloudStorageFileAttributes
when(attributesGcs.etag()).thenReturn(Optional.of("TheEtag"));
when(attributesGcs.mimeType()).thenReturn(Optional.of("TheMimeType"));
when(attributesGcs.acl()).thenReturn(Optional.of(acls));
when(attributesGcs.cacheControl()).thenReturn(Optional.of("TheCacheControl"));
when(attributesGcs.contentEncoding()).thenReturn(Optional.of("TheContentEncoding"));
when(attributesGcs.contentDisposition()).thenReturn(Optional.of("TheContentDisposition"));
when(attributesGcs.userMetadata()).thenReturn(new TreeMap<>());

CloudStoragePath path1 = CloudStoragePath.getPath(fileSystem, "/");
when(fileSystemProvider.readAttributes(path1, BasicFileAttributes.class))
.thenReturn(attributesBasic);
when(fileSystemProvider.readAttributes(path1, CloudStorageFileAttributes.class))
.thenReturn(attributesGcs);

Map<String, Object> expectedBasic = new TreeMap<>();
// BasicFileAttributes
expectedBasic.put("creationTime", FileTime.fromMillis(1L));
expectedBasic.put("lastModifiedTime", FileTime.fromMillis(2L));
expectedBasic.put("lastAccessTime", FileTime.fromMillis(3L));
expectedBasic.put("isRegularFile", true);
expectedBasic.put("isDirectory", true);
expectedBasic.put("isSymbolicLink", true);
expectedBasic.put("isOther", true);
expectedBasic.put("size", 42L);

assertEquals(expectedBasic, fileSystemProvider.readAttributes(path1, "basic:*"));

Map<String, Object> expectedGcs = new TreeMap<>(expectedBasic);
// CloudStorageFileAttributes
expectedGcs.put("etag", Optional.of("TheEtag"));
expectedGcs.put("mimeType", Optional.of("TheMimeType"));
expectedGcs.put("acl", Optional.of(acls));
expectedGcs.put("cacheControl", Optional.of("TheCacheControl"));
expectedGcs.put("contentEncoding", Optional.of("TheContentEncoding"));
expectedGcs.put("contentDisposition", Optional.of("TheContentDisposition"));
expectedGcs.put("userMetadata", new TreeMap<>());

assertEquals(expectedGcs, fileSystemProvider.readAttributes(path1, "gcs:*"));

Map<String, Object> expectedSpecific = new TreeMap<>();
expectedSpecific.put("lastModifiedTime", FileTime.fromMillis(2L));
expectedSpecific.put("isSymbolicLink", true);
expectedSpecific.put("isOther", true);

// Asking for attributes that should NOT be known because we ask for basic view !
assertEquals(
expectedSpecific,
fileSystemProvider.readAttributes(
path1, "basic:lastModifiedTime,isSymbolicLink,isOther,etag,cacheControl"));

// Add the attributes that are only known in gcs view
expectedSpecific.put("etag", Optional.of("TheEtag"));
expectedSpecific.put("cacheControl", Optional.of("TheCacheControl"));

assertEquals(
expectedSpecific,
fileSystemProvider.readAttributes(
path1, "gcs:lastModifiedTime,isSymbolicLink,isOther,etag,cacheControl"));
}

private static CloudStorageConfiguration permitEmptyPathComponents(boolean value) {
return CloudStorageConfiguration.builder().permitEmptyPathComponents(value).build();
}
Expand Down

0 comments on commit 2f13792

Please sign in to comment.