Basic implementation of files API that allows other apps filesystem access
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.nbscloud.files;
|
||||
|
||||
import de.nbscloud.files.api.FilesService.ContentContainer;
|
||||
import de.nbscloud.files.config.FilesConfig;
|
||||
import de.nbscloud.files.exception.FileSystemServiceException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@@ -20,6 +21,7 @@ import java.time.ZoneId;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
@@ -29,10 +31,6 @@ public class FileSystemService {
|
||||
|
||||
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 {
|
||||
/**
|
||||
* 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) {
|
||||
try {
|
||||
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 {
|
||||
List<ContentContainer> contentList = Files.list(this.locationTracker.getCurrentLocation())
|
||||
.filter(path -> {
|
||||
@@ -295,7 +301,7 @@ public class FileSystemService {
|
||||
final boolean isDir = Files.isDirectory(path);
|
||||
|
||||
return new ContentContainer(isDir,
|
||||
this.locationTracker.getRelativeToBaseDir(path),
|
||||
relativizer.apply(path),
|
||||
path.getFileName().toString(),
|
||||
isDir ? FileUtils.sizeOfDirectory(path.toFile()) : Files.size(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) {
|
||||
try {
|
||||
final List<String> resultList = new ArrayList<>();
|
||||
|
||||
61
files/src/main/java/de/nbscloud/files/FilesServiceImpl.java
Normal file
61
files/src/main/java/de/nbscloud/files/FilesServiceImpl.java
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
|
||||
@@ -10,7 +11,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@Component
|
||||
public class LocationTracker {
|
||||
public class LocationTracker implements InitializingBean {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LocationTracker.class);
|
||||
|
||||
@Autowired
|
||||
@@ -25,7 +26,8 @@ public class LocationTracker {
|
||||
this.currentLocation = this.baseDirPath.resolve("");
|
||||
}
|
||||
|
||||
public void init() {
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.baseDirPath = Paths.get(this.filesConfig.getBaseDir());
|
||||
this.currentLocation = this.baseDirPath.resolve("");
|
||||
|
||||
@@ -35,7 +37,11 @@ public class LocationTracker {
|
||||
public void reset() {
|
||||
logger.debug("Reset location");
|
||||
|
||||
init();
|
||||
try {
|
||||
afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
// Cannot happen
|
||||
}
|
||||
}
|
||||
|
||||
Path getCurrentLocation() {
|
||||
@@ -50,6 +56,10 @@ public class LocationTracker {
|
||||
return this.baseDirPath.relativize(other);
|
||||
}
|
||||
|
||||
public Path getAppDir() {
|
||||
return this.baseDirPath.resolve(this.filesConfig.getAppDir());
|
||||
}
|
||||
|
||||
private Path getTrashBin() {
|
||||
return this.baseDirPath.resolve(this.filesConfig.getTrashBinName());
|
||||
}
|
||||
@@ -72,24 +82,6 @@ public class LocationTracker {
|
||||
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) {
|
||||
if(navigateTo == null) {
|
||||
throw new IllegalStateException("Null");
|
||||
|
||||
@@ -14,6 +14,7 @@ public class FilesConfig {
|
||||
private String trashBinName;
|
||||
private int truncateFileNameChars;
|
||||
private String sharesName;
|
||||
private String appDir;
|
||||
|
||||
public String getBaseDir() {
|
||||
return baseDir;
|
||||
@@ -62,4 +63,12 @@ public class FilesConfig {
|
||||
public void setSharesName(String sharesName) {
|
||||
this.sharesName = sharesName;
|
||||
}
|
||||
|
||||
public String getAppDir() {
|
||||
return appDir;
|
||||
}
|
||||
|
||||
public void setAppDir(String appDir) {
|
||||
this.appDir = appDir;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.nbscloud.files.FileSystemService;
|
||||
import de.nbscloud.files.LocationTracker;
|
||||
import de.nbscloud.files.Share;
|
||||
import de.nbscloud.files.api.FilesService.ContentContainer;
|
||||
import de.nbscloud.files.config.FilesConfig;
|
||||
import de.nbscloud.files.exception.FileSystemServiceException;
|
||||
import de.nbscloud.files.form.RenameForm;
|
||||
@@ -338,12 +339,12 @@ public class FilesController implements InitializingBean {
|
||||
return retList;
|
||||
}
|
||||
|
||||
private List<FileSystemService.ContentContainer> getContent(FileSystemService.SortOrder order) {
|
||||
final List<FileSystemService.ContentContainer> contentList = this.fileSystemService.list(order);
|
||||
private List<ContentContainer> getContent(FileSystemService.SortOrder order) {
|
||||
final List<ContentContainer> contentList = this.fileSystemService.list(order);
|
||||
|
||||
if (!this.locationTracker.isBasePath()) {
|
||||
contentList.add(0,
|
||||
new FileSystemService.ContentContainer(true,
|
||||
new ContentContainer(true,
|
||||
this.locationTracker.getParent(),
|
||||
"..",
|
||||
0L,
|
||||
@@ -382,8 +383,6 @@ public class FilesController implements InitializingBean {
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.locationTracker.init();
|
||||
|
||||
if (this.filesConfig.isUseTrashBin()) {
|
||||
try {
|
||||
this.fileSystemService.createDirectory(this.filesConfig.getTrashBinName());
|
||||
|
||||
@@ -25,4 +25,7 @@ nbs-cloud.files.trashBinName=nbs.internal/nbs.trashbin
|
||||
nbs-cloud.files.truncateFileNameChars=130
|
||||
|
||||
# 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
|
||||
Reference in New Issue
Block a user