Add playlists, Fix download ranges

This commit is contained in:
2022-10-27 21:45:44 +02:00
parent 438a3413af
commit 0107ce9a45
8 changed files with 85 additions and 13 deletions

View File

@@ -0,0 +1,11 @@
package sh.rhiobet.lalafin;
import io.smallrye.config.ConfigMapping;
@ConfigMapping(prefix = "lalafin")
public interface LalafinConfiguration {
public String base_url();
public String user_management_url();
}

View File

@@ -70,7 +70,7 @@ public class FilePublicAPI {
if (fileInfoBase instanceof FileInfo) { if (fileInfoBase instanceof FileInfo) {
return fileServeService.serveFile((FileInfo) fileInfoBase, range); return fileServeService.serveFile((FileInfo) fileInfoBase, range);
} else if (fileInfoBase instanceof FolderInfo) { } else if (fileInfoBase instanceof FolderInfo) {
return fileServeService.serveFolder((FolderInfo) fileInfoBase); return fileServeService.serveFolder((FolderInfo) fileInfoBase, null);
} else { } else {
return Response.status(Response.Status.NOT_FOUND).build(); return Response.status(Response.Status.NOT_FOUND).build();
} }

View File

@@ -27,4 +27,8 @@ public class FileTokenProvider {
} }
} }
public String getUsername() {
return this.username;
}
} }

View File

@@ -11,6 +11,7 @@ public class FolderInfo extends FileInfoBase {
public String publicPersistentUrl; public String publicPersistentUrl;
public Set<FileInfoBase> content; public Set<FileInfoBase> content;
public FileInfo playlist;
public FolderInfo(String filename, String thumbnailUrl, String directUrl, String viewUrl, public FolderInfo(String filename, String thumbnailUrl, String directUrl, String viewUrl,
String publicPersistentUrl) { String publicPersistentUrl) {

View File

@@ -11,6 +11,7 @@ import java.util.List;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.PathSegment;
import sh.rhiobet.lalafin.LalafinConfiguration;
import sh.rhiobet.lalafin.api.advent.AdventAccessService; import sh.rhiobet.lalafin.api.advent.AdventAccessService;
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration; import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
import sh.rhiobet.lalafin.api.internal.FileTokenProvider; import sh.rhiobet.lalafin.api.internal.FileTokenProvider;
@@ -20,6 +21,9 @@ import sh.rhiobet.lalafin.api.model.FolderInfo;
@ApplicationScoped @ApplicationScoped
public class FileInfoService { public class FileInfoService {
@Inject
LalafinConfiguration lalafinConfiguration;
@Inject @Inject
FileApiConfiguration fileApiConfiguration; FileApiConfiguration fileApiConfiguration;
@@ -96,6 +100,12 @@ public class FileInfoService {
} }
FolderInfo folderInfo = new FolderInfo(requestedFilename, requestedThumbUrl, FolderInfo folderInfo = new FolderInfo(requestedFilename, requestedThumbUrl,
"/file" + requestedUri + "/", "/view" + requestedUri + "/1"); "/file" + requestedUri + "/", "/view" + requestedUri + "/1");
Path playlistPath = null;
if (fileTokenProvider != null) {
playlistPath = Paths.get("/lalafin/file" + requestedPath
+ "/.playlists/" + fileTokenProvider.getUsername() + ".m3u");
}
StringBuilder playlistContent = new StringBuilder();
try { try {
Files.list(path).forEach(p -> { Files.list(path).forEach(p -> {
String fileName = p.getFileName().toString(); String fileName = p.getFileName().toString();
@@ -130,6 +140,8 @@ public class FileInfoService {
+ fileTokenProvider + fileTokenProvider
.getFileToken(requestedUri + "/" + fileUri) .getFileToken(requestedUri + "/" + fileUri)
+ "/" + fileUri; + "/" + fileUri;
playlistContent.append(lalafinConfiguration.base_url()
+ ((FileInfo) contentInfo).publicApiUrl + "\n");
} }
if (fileName.endsWith(".zip")) { if (fileName.endsWith(".zip")) {
contentInfo.viewUrl = "/view" + requestedUri + "/" + fileUri + "/1"; contentInfo.viewUrl = "/view" + requestedUri + "/" + fileUri + "/1";
@@ -171,6 +183,19 @@ public class FileInfoService {
} }
folderInfo.content.add(contentInfo); folderInfo.content.add(contentInfo);
}); });
if (playlistPath != null) {
playlistPath.getParent().toFile().mkdirs();
Files.writeString(playlistPath, playlistContent.toString());
String playlistFileUri = URLEncoder.encode(fileTokenProvider.getUsername()
+ ".m3u", StandardCharsets.UTF_8).replace("+", "%20");
FileInfo playlistInfo = new FileInfo(
fileTokenProvider.getUsername() + ".m3u",
requestedUri + "/.playlists/" + playlistFileUri);
playlistInfo.publicApiUrl = "/api/public/file/token/"
+ fileTokenProvider.getFileToken(requestedUri + "/.playlists/"
+ playlistFileUri) + "/" + playlistFileUri;
folderInfo.playlist = playlistInfo;
}
} catch (IOException ignored) { } catch (IOException ignored) {
} }
return folderInfo; return folderInfo;

View File

@@ -71,7 +71,8 @@ public class FileResource {
return Response.status(Response.Status.MOVED_PERMANENTLY) return Response.status(Response.Status.MOVED_PERMANENTLY)
.location(URI.create(uriInfo.getRequestUri().getRawPath() + "/")).build(); .location(URI.create(uriInfo.getRequestUri().getRawPath() + "/")).build();
} }
return fileServeService.serveFolder((FolderInfo) fileInfoBase); return fileServeService.serveFolder((FolderInfo) fileInfoBase,
securityIdentity.getPrincipal().getName());
} else if (fileInfoBase instanceof FileInfo) { } else if (fileInfoBase instanceof FileInfo) {
return fileServeService.serveFile((FileInfo) fileInfoBase, range); return fileServeService.serveFile((FileInfo) fileInfoBase, range);
} }

View File

@@ -14,6 +14,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.ResponseBuilder;
import io.quarkus.qute.Location; import io.quarkus.qute.Location;
import io.quarkus.qute.Template; import io.quarkus.qute.Template;
import sh.rhiobet.lalafin.LalafinConfiguration;
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration; import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
import sh.rhiobet.lalafin.api.model.FileInfo; import sh.rhiobet.lalafin.api.model.FileInfo;
import sh.rhiobet.lalafin.api.model.FileInfoBase; import sh.rhiobet.lalafin.api.model.FileInfoBase;
@@ -21,20 +22,25 @@ import sh.rhiobet.lalafin.api.model.FolderInfo;
@ApplicationScoped @ApplicationScoped
public class FileServeService { public class FileServeService {
@Inject
LalafinConfiguration lalafinConfiguration;
@Inject @Inject
FileApiConfiguration fileApiConfiguration; FileApiConfiguration fileApiConfiguration;
@Location("directory-index.html") @Location("directory-index.html")
Template directoryTemplate; Template directoryTemplate;
public Response serveFolder(FolderInfo folderInfo) { public Response serveFolder(FolderInfo folderInfo, String username) {
// Look for index file // Look for index file
for (FileInfoBase content : folderInfo.content) { for (FileInfoBase content : folderInfo.content) {
if (content instanceof FileInfo && content.filename.startsWith("index.")) { if (content instanceof FileInfo && content.filename.startsWith("index.")) {
return this.serveFile((FileInfo) content, null); return this.serveFile((FileInfo) content, null);
} }
} }
ResponseBuilder response = Response.ok(directoryTemplate.data("info", folderInfo).render()); ResponseBuilder response = Response.ok(directoryTemplate.data("info", folderInfo,
"username", username, "user_url", lalafinConfiguration.user_management_url())
.render());
response.header("Content-Type", "text/html"); response.header("Content-Type", "text/html");
return response.build(); return response.build();
} }
@@ -47,19 +53,34 @@ public class FileServeService {
ResponseBuilder response; ResponseBuilder response;
long fileSize = channel.size(); long fileSize = channel.size();
if (range != null) { if (range != null) {
long rangeStart = Long.parseLong(range.substring(6, range.length() - 1)); range = range.substring(6);
String[] rangeSplitted = range.split("-");
long rangeStart = 0;
if (!range.startsWith("-")) {
rangeStart = Long.parseLong(rangeSplitted[0]);
}
long rangeEnd = fileSize - 1;
if (rangeSplitted.length == 2) {
rangeEnd = Long.parseLong(rangeSplitted[1]);
}
if (rangeEnd + 1 > fileSize) {
return Response.status(
Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE).build();
}
InputStream is = Channels.newInputStream(channel); InputStream is = Channels.newInputStream(channel);
is.skip(rangeStart); is.skip(rangeStart);
response = Response.ok(path.toFile()); response = Response.ok(path.toFile());
response.entity(is); response.entity(is.readNBytes((int) (rangeEnd + 1 - rangeStart)));
response.header("Content-Length", Long.toString(fileSize - rangeStart)); response.header("Content-Length",
Long.toString(rangeEnd + 1 - rangeStart));
response.status(Response.Status.PARTIAL_CONTENT); response.status(Response.Status.PARTIAL_CONTENT);
response.header("Content-Range", response.header("Content-Range",
"bytes " + rangeStart + "-" + (fileSize - 1) + "/" + fileSize); "bytes " + rangeStart + "-" + rangeEnd + "/" + fileSize);
} else { } else {
response = Response.ok(path.toFile()); response = Response.ok(path.toFile());
response.header("Content-Length", Long.toString(fileSize)); response.header("Content-Length", Long.toString(fileSize));
} }
response.header("Accept-Ranges", "bytes");
response.header("Content-Disposition", response.header("Content-Disposition",
"inline; filename=\"" + fileInfo.filename + "\""); "inline; filename=\"" + fileInfo.filename + "\"");
response.header("Content-Type", FileHelper.getMimeType(fileInfo.filename)); response.header("Content-Type", FileHelper.getMimeType(fileInfo.filename));

View File

@@ -4,16 +4,25 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<link rel="stylesheet" href="/style/sakura.css"> <link rel="stylesheet" href="/style/sakura.css">
<link rel="stylesheet" href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css">
<title>{info.filename}</title> <title>{info.filename}</title>
</head> </head>
<body> <body>
<h1>{info.filename}</h1> <h1>{info.filename}</h1>
{#if !info.filename is '/'} {#if info.filename ne '/'}
<a href="{info.directUrl}..">back</a> <a href="{info.directUrl}.."><i class="las la-chevron-left"></i></a>
<span style="float:right;"> {#else}
<a href="{info.viewUrl}">viewer</a> &nbsp;
</span>
{/if} {/if}
<span style="float:right;">
{#if info.playlist}
<a href="{info.playlist.publicApiUrl}"><i class="las la-file-video"></i></a>
{/if}
<a href="{info.viewUrl}"><i class="las la-book-reader"></i></a>
{#if username and user_url}
| <a href="{user_url}" target="_blank">{username}<i class="las la-user-cog"></i></a>
{/if}
</span>
<hr /> <hr />
<table style="table-layout: fixed; text-align: center"> <table style="table-layout: fixed; text-align: center">
{#each info.content} {#each info.content}