Skip to content
This repository was archived by the owner on Mar 14, 2025. It is now read-only.

Commit cdf0cfe

Browse files
Merge pull request #1 from cryptomator/feature/localfs
Implemented a "local" CloudProvider
2 parents c8935ae + a4e7727 commit cdf0cfe

File tree

5 files changed

+399
-0
lines changed

5 files changed

+399
-0
lines changed

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<properties>
1515
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1616

17+
<guava.version>29.0-jre</guava.version>
1718
<junit.jupiter.version>5.6.2</junit.jupiter.version>
1819
<mockito.version>3.3.3</mockito.version>
1920
<hamcrest.version>2.2</hamcrest.version>
@@ -43,6 +44,11 @@
4344
</distributionManagement>
4445

4546
<dependencies>
47+
<dependency>
48+
<groupId>com.google.guava</groupId>
49+
<artifactId>guava</artifactId>
50+
<version>${guava.version}</version>
51+
</dependency>
4652

4753
<!-- Test -->
4854
<dependency>
@@ -103,6 +109,11 @@
103109
<release>11</release>
104110
</configuration>
105111
</plugin>
112+
<plugin>
113+
<groupId>org.apache.maven.plugins</groupId>
114+
<artifactId>maven-surefire-plugin</artifactId>
115+
<version>2.22.2</version>
116+
</plugin>
106117
</plugins>
107118
</build>
108119
</project>

src/main/java/module-info.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module org.cryptomator.cloudaccess {
2+
exports org.cryptomator.cloudaccess;
23
exports org.cryptomator.cloudaccess.api;
4+
5+
requires com.google.common;
36
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.cryptomator.cloudaccess;
2+
3+
import java.nio.file.Path;
4+
5+
import org.cryptomator.cloudaccess.api.CloudProvider;
6+
import org.cryptomator.cloudaccess.localfs.LocalFsCloudProvider;
7+
8+
public class CloudAccess {
9+
10+
private CloudAccess() {
11+
}
12+
13+
/**
14+
* Creates a new CloudProvider which provides access to the given <code>folder</code>. Mainly for test purposes.
15+
*
16+
* @param folder An existing folder on the (local) default file system.
17+
* @return A cloud access provider that provides access to the given local directory.
18+
*/
19+
static CloudProvider toLocalFileSystem(Path folder) {
20+
return new LocalFsCloudProvider(folder);
21+
}
22+
23+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package org.cryptomator.cloudaccess.localfs;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.nio.channels.Channels;
6+
import java.nio.channels.FileChannel;
7+
import java.nio.file.CopyOption;
8+
import java.nio.file.FileVisitOption;
9+
import java.nio.file.FileVisitResult;
10+
import java.nio.file.Files;
11+
import java.nio.file.LinkOption;
12+
import java.nio.file.Path;
13+
import java.nio.file.SimpleFileVisitor;
14+
import java.nio.file.StandardCopyOption;
15+
import java.nio.file.StandardOpenOption;
16+
import java.nio.file.attribute.BasicFileAttributes;
17+
import java.util.ArrayList;
18+
import java.util.EnumSet;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.concurrent.CompletionStage;
23+
24+
import com.google.common.io.ByteStreams;
25+
import com.google.common.io.MoreFiles;
26+
import com.google.common.io.RecursiveDeleteOption;
27+
import org.cryptomator.cloudaccess.api.CloudItemList;
28+
import org.cryptomator.cloudaccess.api.CloudItemMetadata;
29+
import org.cryptomator.cloudaccess.api.CloudItemType;
30+
import org.cryptomator.cloudaccess.api.CloudProvider;
31+
import org.cryptomator.cloudaccess.api.ProgressListener;
32+
33+
public class LocalFsCloudProvider implements CloudProvider {
34+
35+
private static final Path ABS_ROOT = Path.of("/");
36+
37+
private final Path root;
38+
39+
public LocalFsCloudProvider(Path root) {
40+
this.root = root;
41+
}
42+
43+
private Path resolve(Path cloudPath) {
44+
Path relPath = ABS_ROOT.relativize(cloudPath);
45+
return root.resolve(relPath);
46+
}
47+
48+
private CloudItemMetadata createMetadata(Path fullPath, BasicFileAttributes attr) {
49+
var relPath = root.relativize(fullPath);
50+
var type = attr.isDirectory() ? CloudItemType.FOLDER : attr.isRegularFile() ? CloudItemType.FILE : CloudItemType.UNKNOWN;
51+
var modifiedDate = Optional.of(attr.lastModifiedTime().toInstant());
52+
var size = Optional.of(attr.size());
53+
return new CloudItemMetadata(relPath.getFileName().toString(), ABS_ROOT.resolve(relPath), type, modifiedDate, size);
54+
}
55+
56+
@Override
57+
public CompletionStage<CloudItemMetadata> itemMetadata(Path node) {
58+
Path path = resolve(node);
59+
try {
60+
var attr = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
61+
var metadata = createMetadata(path, attr);
62+
return CompletableFuture.completedFuture(metadata);
63+
} catch (IOException e) {
64+
return CompletableFuture.failedFuture(e);
65+
}
66+
}
67+
68+
@Override
69+
public CompletionStage<CloudItemList> list(Path folder, Optional<String> pageToken) {
70+
Path folderPath = resolve(folder);
71+
try {
72+
List<CloudItemMetadata> items = new ArrayList<>();
73+
Files.walkFileTree(folderPath, EnumSet.noneOf(FileVisitOption.class), 1, new SimpleFileVisitor<>() {
74+
75+
@Override
76+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
77+
items.add(createMetadata(file, attrs));
78+
return FileVisitResult.CONTINUE;
79+
}
80+
});
81+
return CompletableFuture.completedFuture(new CloudItemList(items, Optional.empty()));
82+
} catch (IOException e) {
83+
return CompletableFuture.failedFuture(e);
84+
}
85+
}
86+
87+
@Override
88+
public CompletionStage<InputStream> read(Path file, long offset, long count, ProgressListener progressListener) {
89+
Path filePath = resolve(file);
90+
try {
91+
var ch = Files.newByteChannel(filePath, StandardOpenOption.READ);
92+
ch.position(offset);
93+
return CompletableFuture.completedFuture(ByteStreams.limit(Channels.newInputStream(ch), count));
94+
} catch (IOException e) {
95+
return CompletableFuture.failedFuture(e);
96+
}
97+
}
98+
99+
@Override
100+
public CompletionStage<CloudItemMetadata> write(Path file, boolean replace, InputStream data, ProgressListener progressListener) {
101+
Path filePath = resolve(file);
102+
var options = replace
103+
? EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
104+
: EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
105+
106+
try (var ch = FileChannel.open(filePath, options)) {
107+
var size = ch.transferFrom(Channels.newChannel(data), 0, Long.MAX_VALUE);
108+
var modifiedDate = Files.getLastModifiedTime(filePath).toInstant();
109+
var metadata = new CloudItemMetadata(file.getFileName().toString(), file, CloudItemType.FILE, Optional.of(modifiedDate), Optional.of(size));
110+
return CompletableFuture.completedFuture(metadata);
111+
} catch (IOException e) {
112+
return CompletableFuture.failedFuture(e);
113+
}
114+
}
115+
116+
@Override
117+
public CompletionStage<Path> createFolder(Path folder) {
118+
Path folderPath = resolve(folder);
119+
try {
120+
Files.createDirectory(folderPath);
121+
return CompletableFuture.completedFuture(folder);
122+
} catch (IOException e) {
123+
return CompletableFuture.failedFuture(e);
124+
}
125+
}
126+
127+
@Override
128+
public CompletionStage<Void> delete(Path node) {
129+
Path path = resolve(node);
130+
try {
131+
MoreFiles.deleteRecursively(path, RecursiveDeleteOption.ALLOW_INSECURE);
132+
return CompletableFuture.completedFuture(null);
133+
} catch (IOException e) {
134+
return CompletableFuture.failedFuture(e);
135+
}
136+
}
137+
138+
@Override
139+
public CompletionStage<Path> move(Path source, Path target, boolean replace) {
140+
Path src = resolve(source);
141+
Path dst = resolve(target);
142+
try {
143+
var options = replace ? EnumSet.of(StandardCopyOption.REPLACE_EXISTING) : EnumSet.noneOf(StandardCopyOption.class);
144+
Files.move(src, dst, options.toArray(CopyOption[]::new));
145+
return CompletableFuture.completedFuture(target);
146+
} catch (IOException e) {
147+
return CompletableFuture.failedFuture(e);
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)