1
0

Basic implementation of files API that allows other apps filesystem access

This commit is contained in:
2022-08-13 18:40:13 +02:00
parent bd6f1e43d1
commit a0239ecda6
13 changed files with 237 additions and 34 deletions

View File

@@ -31,6 +31,10 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>files-api</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -26,6 +26,7 @@ public class DashboardWidgetController {
@GetMapping("dashboard/widgets/machineMetrics") @GetMapping("dashboard/widgets/machineMetrics")
public String getDiskUsage(HttpServletRequest request, HttpServletResponse response, Model model) { public String getDiskUsage(HttpServletRequest request, HttpServletResponse response, Model model) {
model.addAttribute("interfaces", metricService.getInterfaces()); model.addAttribute("interfaces", metricService.getInterfaces());
model.addAttribute("updates", metricService.getUpdates()); model.addAttribute("updates", metricService.getUpdates());

27
files-api/pom.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>nbs-cloud-aggregator</artifactId>
<version>18-SNAPSHOT</version>
</parent>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>files-api</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>web-container-registry</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,33 @@
package de.nbscloud.files.api;
import de.nbscloud.webcontainer.registry.App;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
public interface FilesService {
record ContentContainer(boolean directory, Path path, String name, long size,
LocalDateTime lastModified) {
}
public void createAppDirectory(App app);
public void createDirectory(App app, Path path);
public void createFile(App app, Path path, byte[] content);
public void delete(App app, Path path);
public void get(App app, Path path);
/**
* Paths in return list are always relative to the appDir.
*
* @param app to list the files for
* @param path in case of {@link Optional#EMPTY} the appDir is used as start dir. If not empty, path has to be relative
* to the appDir
*/
public List<ContentContainer> list(App app, Optional<Path> path);
}

View File

@@ -21,6 +21,10 @@
<groupId>de.77zzcx7.nbs-cloud</groupId> <groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>web-container-config</artifactId> <artifactId>web-container-config</artifactId>
</dependency> </dependency>
<dependency>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>files-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@@ -0,0 +1,54 @@
package de.nbscloud.files;
import de.nbscloud.files.config.FilesConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.file.Path;
@Component
public class AppLocationTracker implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(AppLocationTracker.class);
@Autowired
private FilesConfig filesConfig;
@Autowired
private LocationTracker locationTracker;
private Path baseDirPath;
public Path resolve(String appId, Path path) {
validate(Path.of(appId));
validate(path);
final Path appPath = this.baseDirPath.resolve(appId);
return appPath.resolve(path);
}
private void validate(Path path) {
if(path == null) {
throw new IllegalStateException("Null");
}
if(path.toString().contains("..")) {
throw new IllegalStateException("Relative path");
}
if (path.isAbsolute()) {
throw new IllegalStateException("Absolute path: " + path);
}
if(!this.baseDirPath.resolve(path).normalize().startsWith(this.baseDirPath)) {
throw new IllegalStateException("Illegal path: " + path);
}
}
@Override
public void afterPropertiesSet() throws Exception {
this.baseDirPath = this.locationTracker.getAppDir();
}
}

View File

@@ -1,5 +1,6 @@
package de.nbscloud.files; package de.nbscloud.files;
import de.nbscloud.files.api.FilesService.ContentContainer;
import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.config.FilesConfig;
import de.nbscloud.files.exception.FileSystemServiceException; import de.nbscloud.files.exception.FileSystemServiceException;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@@ -20,6 +21,7 @@ import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@@ -29,10 +31,6 @@ public class FileSystemService {
private static final Logger logger = LoggerFactory.getLogger(FileSystemService.class); private static final Logger logger = LoggerFactory.getLogger(FileSystemService.class);
public static record ContentContainer(boolean directory, Path path, String name, long size,
LocalDateTime lastModified) {
}
public enum SortOrder { public enum SortOrder {
/** /**
* First by type (directories first), then by name ignoring case * First by type (directories first), then by name ignoring case
@@ -95,6 +93,14 @@ public class FileSystemService {
} }
} }
void createDirectory(Path path) {
try {
Files.createDirectories(path);
} catch (IOException e) {
throw new FileSystemServiceException("Could not create directory", e);
}
}
public void createFile(String name, byte[] content) { public void createFile(String name, byte[] content) {
try { try {
Files.write(this.locationTracker.resolve(name), content, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); Files.write(this.locationTracker.resolve(name), content, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
@@ -278,7 +284,7 @@ public class FileSystemService {
} }
} }
public List<ContentContainer> list(SortOrder sortOrder) { List<ContentContainer> list(Path startPath, SortOrder sortOrder, Function<Path, Path> relativizer) {
try { try {
List<ContentContainer> contentList = Files.list(this.locationTracker.getCurrentLocation()) List<ContentContainer> contentList = Files.list(this.locationTracker.getCurrentLocation())
.filter(path -> { .filter(path -> {
@@ -295,7 +301,7 @@ public class FileSystemService {
final boolean isDir = Files.isDirectory(path); final boolean isDir = Files.isDirectory(path);
return new ContentContainer(isDir, return new ContentContainer(isDir,
this.locationTracker.getRelativeToBaseDir(path), relativizer.apply(path),
path.getFileName().toString(), path.getFileName().toString(),
isDir ? FileUtils.sizeOfDirectory(path.toFile()) : Files.size(path), isDir ? FileUtils.sizeOfDirectory(path.toFile()) : Files.size(path),
LocalDateTime.ofInstant(Files.getLastModifiedTime(path) LocalDateTime.ofInstant(Files.getLastModifiedTime(path)
@@ -313,6 +319,10 @@ public class FileSystemService {
} }
} }
public List<ContentContainer> list(SortOrder sortOrder) {
return list(this.locationTracker.getCurrentLocation(), sortOrder, path -> this.locationTracker.getRelativeToBaseDir(path));
}
public List<String> collectDirs(String sourceFile) { public List<String> collectDirs(String sourceFile) {
try { try {
final List<String> resultList = new ArrayList<>(); final List<String> resultList = new ArrayList<>();

View File

@@ -0,0 +1,61 @@
package de.nbscloud.files;
import de.nbscloud.files.api.FilesService;
import de.nbscloud.files.exception.FileSystemServiceException;
import de.nbscloud.webcontainer.registry.App;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
@Service
public class FilesServiceImpl implements FilesService {
@Autowired
private FileSystemService fileSystemService;
@Autowired
private AppLocationTracker locationTracker;
private Path resolve(App app, Path path) {
return this.locationTracker.resolve(app.getId(), path);
}
@Override
public void createAppDirectory(App app) {
try {
this.fileSystemService.createDirectory(resolve(app, Path.of("")));
}
catch(FileSystemServiceException e) {
// Ignore, may happen if the dir already exists
}
}
@Override
public void createDirectory(App app, Path path) {
this.fileSystemService.createDirectory(resolve(app, path));
}
@Override
public void createFile(App app, Path path, byte[] content) {
this.fileSystemService.createFile(resolve(app, path), content);
}
@Override
public void delete(App app, Path path) {
this.fileSystemService.delete(resolve(app, path));
}
@Override
public void get(App app, Path path) {
this.fileSystemService.get(resolve(app, path));
}
@Override
public List<ContentContainer> list(App app, Optional<Path> path) {
final Path appPath = resolve(app, Path.of(""));
final Path p = path.map(tmpPath -> resolve(app, tmpPath)).orElse(appPath);
return this.fileSystemService.list(p, FileSystemService.SortOrder.NATURAL, callbackPath -> appPath.relativize(callbackPath));
}
}

View File

@@ -3,6 +3,7 @@ package de.nbscloud.files;
import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.config.FilesConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -10,7 +11,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@Component @Component
public class LocationTracker { public class LocationTracker implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(LocationTracker.class); private static final Logger logger = LoggerFactory.getLogger(LocationTracker.class);
@Autowired @Autowired
@@ -25,7 +26,8 @@ public class LocationTracker {
this.currentLocation = this.baseDirPath.resolve(""); this.currentLocation = this.baseDirPath.resolve("");
} }
public void init() { @Override
public void afterPropertiesSet() throws Exception {
this.baseDirPath = Paths.get(this.filesConfig.getBaseDir()); this.baseDirPath = Paths.get(this.filesConfig.getBaseDir());
this.currentLocation = this.baseDirPath.resolve(""); this.currentLocation = this.baseDirPath.resolve("");
@@ -35,7 +37,11 @@ public class LocationTracker {
public void reset() { public void reset() {
logger.debug("Reset location"); logger.debug("Reset location");
init(); try {
afterPropertiesSet();
} catch (Exception e) {
// Cannot happen
}
} }
Path getCurrentLocation() { Path getCurrentLocation() {
@@ -50,6 +56,10 @@ public class LocationTracker {
return this.baseDirPath.relativize(other); return this.baseDirPath.relativize(other);
} }
public Path getAppDir() {
return this.baseDirPath.resolve(this.filesConfig.getAppDir());
}
private Path getTrashBin() { private Path getTrashBin() {
return this.baseDirPath.resolve(this.filesConfig.getTrashBinName()); return this.baseDirPath.resolve(this.filesConfig.getTrashBinName());
} }
@@ -72,24 +82,6 @@ public class LocationTracker {
this.currentLocation = this.baseDirPath.resolve(navigateTo); this.currentLocation = this.baseDirPath.resolve(navigateTo);
} }
public void validate(String path) {
// Absolut paths are allowed, however, they have to be under baseDir
if(path == null) {
throw new IllegalStateException("Null");
}
if(path.contains("..")) {
throw new IllegalStateException("Relative path");
}
final Path tmpPath = Path.of(path);
if(!tmpPath.normalize().startsWith(this.baseDirPath)) {
throw new IllegalStateException("Illegal path: " + path);
}
}
private void validate_internal(String navigateTo) { private void validate_internal(String navigateTo) {
if(navigateTo == null) { if(navigateTo == null) {
throw new IllegalStateException("Null"); throw new IllegalStateException("Null");

View File

@@ -14,6 +14,7 @@ public class FilesConfig {
private String trashBinName; private String trashBinName;
private int truncateFileNameChars; private int truncateFileNameChars;
private String sharesName; private String sharesName;
private String appDir;
public String getBaseDir() { public String getBaseDir() {
return baseDir; return baseDir;
@@ -62,4 +63,12 @@ public class FilesConfig {
public void setSharesName(String sharesName) { public void setSharesName(String sharesName) {
this.sharesName = sharesName; this.sharesName = sharesName;
} }
public String getAppDir() {
return appDir;
}
public void setAppDir(String appDir) {
this.appDir = appDir;
}
} }

View File

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import de.nbscloud.files.FileSystemService; import de.nbscloud.files.FileSystemService;
import de.nbscloud.files.LocationTracker; import de.nbscloud.files.LocationTracker;
import de.nbscloud.files.Share; import de.nbscloud.files.Share;
import de.nbscloud.files.api.FilesService.ContentContainer;
import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.config.FilesConfig;
import de.nbscloud.files.exception.FileSystemServiceException; import de.nbscloud.files.exception.FileSystemServiceException;
import de.nbscloud.files.form.RenameForm; import de.nbscloud.files.form.RenameForm;
@@ -338,12 +339,12 @@ public class FilesController implements InitializingBean {
return retList; return retList;
} }
private List<FileSystemService.ContentContainer> getContent(FileSystemService.SortOrder order) { private List<ContentContainer> getContent(FileSystemService.SortOrder order) {
final List<FileSystemService.ContentContainer> contentList = this.fileSystemService.list(order); final List<ContentContainer> contentList = this.fileSystemService.list(order);
if (!this.locationTracker.isBasePath()) { if (!this.locationTracker.isBasePath()) {
contentList.add(0, contentList.add(0,
new FileSystemService.ContentContainer(true, new ContentContainer(true,
this.locationTracker.getParent(), this.locationTracker.getParent(),
"..", "..",
0L, 0L,
@@ -382,8 +383,6 @@ public class FilesController implements InitializingBean {
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
this.locationTracker.init();
if (this.filesConfig.isUseTrashBin()) { if (this.filesConfig.isUseTrashBin()) {
try { try {
this.fileSystemService.createDirectory(this.filesConfig.getTrashBinName()); this.fileSystemService.createDirectory(this.filesConfig.getTrashBinName());

View File

@@ -26,3 +26,6 @@ nbs-cloud.files.truncateFileNameChars=130
# Knob to configure the name of the shares directory # Knob to configure the name of the shares directory
nbs-cloud.files.sharesName=nbs.internal/nbs.shares nbs-cloud.files.sharesName=nbs.internal/nbs.shares
# Knob to configure the name of the app directory
nbs-cloud.files.appDir=nbs.internal/apps

View File

@@ -17,6 +17,7 @@
<modules> <modules>
<module>files</module> <module>files</module>
<module>files-api</module>
<module>web-container</module> <module>web-container</module>
<module>web-container-registry</module> <module>web-container-registry</module>
<module>notes</module> <module>notes</module>
@@ -63,6 +64,11 @@
<artifactId>files</artifactId> <artifactId>files</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>files-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>de.77zzcx7.nbs-cloud</groupId> <groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>web-container-registry</artifactId> <artifactId>web-container-registry</artifactId>