mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-24 05:18:58 +00:00
(converter) File listing and download for file storage
This commit is contained in:
parent
a5d980ee56
commit
66bb12e55a
@ -85,6 +85,7 @@ public class ControlService extends Service {
|
||||
Spark.get("/public/storage/crawls", this::storageModelCrawls, storageCrawlsRenderer::render);
|
||||
Spark.get("/public/storage/processed", this::storageModelProcessed, storageProcessedRenderer::render);
|
||||
Spark.get("/public/storage/:id", this::storageDetailsModel, storageDetailsRenderer::render);
|
||||
Spark.get("/public/storage/:id/file", controlFileStorageService::downloadFileFromStorage);
|
||||
|
||||
|
||||
final HtmlRedirect redirectToServices = new HtmlRedirect("/services");
|
||||
|
@ -0,0 +1,15 @@
|
||||
package nu.marginalia.control.model;
|
||||
|
||||
import nu.marginalia.db.storage.model.FileStorage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record FileStorageFileModel(String filename,
|
||||
String type,
|
||||
String size
|
||||
) {
|
||||
|
||||
public boolean isDownloadable() {
|
||||
return type.equals("file");
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ import nu.marginalia.db.storage.model.FileStorageType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record FileStorageWithRelatedEntries(FileStorageWithActions self, List<FileStorage> related) {
|
||||
public record FileStorageWithRelatedEntries(FileStorageWithActions self,
|
||||
List<FileStorage> related,
|
||||
List<FileStorageFileModel> files
|
||||
) {
|
||||
|
||||
}
|
||||
|
@ -7,19 +7,23 @@ import lombok.SneakyThrows;
|
||||
import nu.marginalia.control.model.*;
|
||||
import nu.marginalia.db.storage.FileStorageService;
|
||||
import nu.marginalia.db.storage.model.*;
|
||||
import nu.marginalia.mqsm.graph.AbstractStateGraph;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
@Singleton
|
||||
public class ControlFileStorageService {
|
||||
private final HikariDataSource dataSource;
|
||||
private final FileStorageService fileStorageService;
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Inject
|
||||
public ControlFileStorageService(HikariDataSource dataSource, FileStorageService fileStorageService) {
|
||||
@ -107,9 +111,47 @@ public class ControlFileStorageService {
|
||||
public FileStorageWithRelatedEntries getFileStorageWithRelatedEntries(FileStorageId id) throws SQLException {
|
||||
var storage = fileStorageService.getStorage(id);
|
||||
var related = getRelatedEntries(id);
|
||||
return new FileStorageWithRelatedEntries(new FileStorageWithActions(storage), related);
|
||||
|
||||
List<FileStorageFileModel> files = new ArrayList<>();
|
||||
|
||||
try (var filesStream = Files.list(storage.asPath())) {
|
||||
filesStream
|
||||
.map(this::createFileModel)
|
||||
.sorted(Comparator
|
||||
.comparing(FileStorageFileModel::type)
|
||||
.thenComparing(FileStorageFileModel::filename)
|
||||
)
|
||||
.forEach(files::add);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.error("Failed to list files in storage", ex);
|
||||
}
|
||||
|
||||
return new FileStorageWithRelatedEntries(new FileStorageWithActions(storage), related, files);
|
||||
}
|
||||
|
||||
private FileStorageFileModel createFileModel(Path p) {
|
||||
try {
|
||||
String type = Files.isRegularFile(p) ? "file" : "directory";
|
||||
String size;
|
||||
if (Files.isDirectory(p)) {
|
||||
size = "-";
|
||||
}
|
||||
else {
|
||||
long sizeBytes = Files.size(p);
|
||||
|
||||
if (sizeBytes < 1024) size = sizeBytes + " B";
|
||||
else if (sizeBytes < 1024 * 1024) size = sizeBytes / 1024 + " KB";
|
||||
else if (sizeBytes < 1024 * 1024 * 1024) size = sizeBytes / (1024 * 1024) + " MB";
|
||||
else size = sizeBytes / (1024 * 1024 * 1024) + " GB";
|
||||
}
|
||||
|
||||
return new FileStorageFileModel(p.toFile().getName(), type, size);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
private List<FileStorage> getRelatedEntries(FileStorageId id) {
|
||||
List<FileStorage> ret = new ArrayList<>();
|
||||
try (var conn = dataSource.getConnection();
|
||||
@ -131,4 +173,30 @@ public class ControlFileStorageService {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Object downloadFileFromStorage(Request request, Response response) throws SQLException {
|
||||
var fileStorageId = FileStorageId.parse(request.params("id"));
|
||||
String filename = request.queryParams("name");
|
||||
|
||||
Path root = fileStorageService.getStorage(fileStorageId).asPath();
|
||||
Path filePath = root.resolve(filename).normalize();
|
||||
|
||||
if (!filePath.startsWith(root)) {
|
||||
response.status(403);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (filePath.endsWith(".txt") || filePath.endsWith(".log")) response.type("text/plain");
|
||||
else response.type("application/octet-stream");
|
||||
|
||||
try (var is = Files.newInputStream(filePath)) {
|
||||
is.transferTo(response.raw().getOutputStream());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.error("Failed to download file", ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,27 @@
|
||||
</tr>
|
||||
</table>
|
||||
{{/with}}
|
||||
|
||||
{{#if storage.files}}
|
||||
<h1>Contents </h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Type</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
{{#each storage.files}}
|
||||
<tr>
|
||||
<td>
|
||||
{{#if downloadable}}<a href="/storage/{{storage.self.storage.id}}/file?name={{filename}}">{{filename}}</a></td>
|
||||
{{else}} {{filename}} {{/if}}
|
||||
<td>{{type}}</td>
|
||||
<td>{{size}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/if}}
|
||||
|
||||
<h2>Actions</h2>
|
||||
{{#with storage.self}}
|
||||
{{#if isCrawlable}}
|
||||
|
Loading…
Reference in New Issue
Block a user