Add playlists, Fix download ranges
This commit is contained in:
11
src/main/java/sh/rhiobet/lalafin/LalafinConfiguration.java
Normal file
11
src/main/java/sh/rhiobet/lalafin/LalafinConfiguration.java
Normal 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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,8 @@ public class FileTokenProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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>
|
|
||||||
</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}
|
||||||
|
|||||||
Reference in New Issue
Block a user