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/crawls", this::storageModelCrawls, storageCrawlsRenderer::render);
|
||||||
Spark.get("/public/storage/processed", this::storageModelProcessed, storageProcessedRenderer::render);
|
Spark.get("/public/storage/processed", this::storageModelProcessed, storageProcessedRenderer::render);
|
||||||
Spark.get("/public/storage/:id", this::storageDetailsModel, storageDetailsRenderer::render);
|
Spark.get("/public/storage/:id", this::storageDetailsModel, storageDetailsRenderer::render);
|
||||||
|
Spark.get("/public/storage/:id/file", controlFileStorageService::downloadFileFromStorage);
|
||||||
|
|
||||||
|
|
||||||
final HtmlRedirect redirectToServices = new HtmlRedirect("/services");
|
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;
|
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.control.model.*;
|
||||||
import nu.marginalia.db.storage.FileStorageService;
|
import nu.marginalia.db.storage.FileStorageService;
|
||||||
import nu.marginalia.db.storage.model.*;
|
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.Request;
|
||||||
import spark.Response;
|
import spark.Response;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ControlFileStorageService {
|
public class ControlFileStorageService {
|
||||||
private final HikariDataSource dataSource;
|
private final HikariDataSource dataSource;
|
||||||
private final FileStorageService fileStorageService;
|
private final FileStorageService fileStorageService;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ControlFileStorageService(HikariDataSource dataSource, FileStorageService fileStorageService) {
|
public ControlFileStorageService(HikariDataSource dataSource, FileStorageService fileStorageService) {
|
||||||
@ -107,9 +111,47 @@ public class ControlFileStorageService {
|
|||||||
public FileStorageWithRelatedEntries getFileStorageWithRelatedEntries(FileStorageId id) throws SQLException {
|
public FileStorageWithRelatedEntries getFileStorageWithRelatedEntries(FileStorageId id) throws SQLException {
|
||||||
var storage = fileStorageService.getStorage(id);
|
var storage = fileStorageService.getStorage(id);
|
||||||
var related = getRelatedEntries(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) {
|
private List<FileStorage> getRelatedEntries(FileStorageId id) {
|
||||||
List<FileStorage> ret = new ArrayList<>();
|
List<FileStorage> ret = new ArrayList<>();
|
||||||
try (var conn = dataSource.getConnection();
|
try (var conn = dataSource.getConnection();
|
||||||
@ -131,4 +173,30 @@ public class ControlFileStorageService {
|
|||||||
}
|
}
|
||||||
return ret;
|
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,7 +24,28 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
{{/with}}
|
{{/with}}
|
||||||
<h2>Actions</h2>
|
|
||||||
|
{{#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}}
|
{{#with storage.self}}
|
||||||
{{#if isCrawlable}}
|
{{#if isCrawlable}}
|
||||||
<form method="post" action="/storage/{{storage.id}}/crawl">
|
<form method="post" action="/storage/{{storage.id}}/crawl">
|
||||||
|
Loading…
Reference in New Issue
Block a user