Merge pull request 'feature/download' (#7) from feature/download into main
Reviewed-on: https://git.walzen665.de/Walzen665/fileshare-service/pulls/7
This commit is contained in:
commit
f419492cca
@ -1,32 +1,121 @@
|
|||||||
<div class="container mx-auto p-4 mt-4">
|
<div class="container mx-auto p-4 mt-4 animate-in fade-in slide-in-from-left-6 duration-300">
|
||||||
<h1 class="text-4xl font-bold text-center text-gray-800 mb-2">Download File</h1>
|
<h1 class="text-4xl font-bold text-center text-gray-800 mb-2">Download File</h1>
|
||||||
|
<div
|
||||||
|
*ngIf="!fileDownloadStarted && !fileDownloadFinished"
|
||||||
|
>
|
||||||
<p class="text-md text-center text-gray-600 mb-6">Access your files quickly and securely</p>
|
<p class="text-md text-center text-gray-600 mb-6">Access your files quickly and securely</p>
|
||||||
<div class="bg-white shadow-lg rounded-lg p-6 flex flex-col items-center justify-center">
|
<div class="bg-white shadow-lg rounded-lg p-6 flex flex-col items-center justify-center">
|
||||||
<img class="w-28 mt-6 mb-6" src="./assets/cloud-arrow-down-solid.svg">
|
<img class="w-56 mt-6 mb-6" src="./assets/cloud-arrow-down-solid.svg">
|
||||||
<div class="w-full max-w-md mb-6 relative">
|
<input type="text" class="input input-bordered text-center w-full max-w-md mb-6" placeholder="Enter download code/link"
|
||||||
<input type="text" class="input input-bordered w-full pl-10" placeholder="Enter download code/link" />
|
[(ngModel)]="inputFileId"
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
(keydown.enter)="requestDownload()"/>
|
||||||
<span class="text-gray-500 sm:text-sm">
|
|
||||||
<i class="fas fa-link"></i>
|
<div class="mb-6 w-full flex flex-col items-center justify-center">
|
||||||
</span>
|
<input
|
||||||
|
*ngIf="downloadInfo && downloadInfo.passwordProtected && downloadInfo.downloadable"
|
||||||
|
[ngClass]="{'input-error': passwordWrong}"
|
||||||
|
type="password"
|
||||||
|
class="input input-bordered text-center w-full max-w-md"
|
||||||
|
placeholder="Enter file password..."
|
||||||
|
[(ngModel)]="filePassword"/>
|
||||||
|
<div class="label" *ngIf="passwordWrong">
|
||||||
|
<span class="label-text-alt text-red-500">Password wrong | Check capslock</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary w-full max-w-xs flex justify-center items-center mb-4">
|
|
||||||
<i class="fas fa-cloud-download-alt mr-2"></i>
|
<button class="btn btn-primary w-full max-w-md flex justify-center items-center mb-4 mt-3" (click)="requestDownload()">
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
(click)="download_not_possible.showModal()"
|
||||||
|
class="btn btn-secondary btn-outline w-full max-w-md flex justify-center items-center mb-4">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
<p class="text-gray-600 text-center w-2/3">Files are available for a limited time. Ensure the code or link is correct and the file is still downloadable.</p>
|
<p class="text-gray-600 text-center w-2/3">Files are available for a limited time. Ensure the code or link is correct and the file is still downloadable.</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="fileDownloadStarted && !fileDownloadFinished"
|
||||||
|
>
|
||||||
|
<div class="bg-white shadow-lg rounded-lg p-6 flex flex-col items-center justify-center">
|
||||||
|
<div class="radial-progress mb-6" [style.--value]="downloadProgress" style="--size:12rem; --thickness: 2rem;" role="progressbar">{{ downloadProgress | number:'1.0-0' }}%</div>
|
||||||
|
<h3 class="text-xl font-bold text-center text-gray-800 mb-2">Downloading...</h3>
|
||||||
|
<p class="text-gray-600 text-center w-2/3 mb-6">Your file is being downloaded in the background. Please wait...</p>
|
||||||
|
<p class="text-gray-600 text-center w-2/3 mb-6">{{ downloadDuration }} seconds passed.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="fileDownloadFinished"
|
||||||
|
>
|
||||||
|
<div class="bg-white shadow-lg rounded-lg p-6 flex flex-col items-center justify-center">
|
||||||
|
<img class="w-16 mb-6" src="./assets/circle-check-solid.svg" alt="Success">
|
||||||
|
<p class="text-xl font-bold text-center text-gray-800 mb-6">Download complete!</p>
|
||||||
|
<p class="text-gray-600 text-center w-2/3 mb-3">File <strong>{{ fileName }}</strong> has been downloaded and saved to your drive.</p>
|
||||||
|
<p class="text-gray-600 text-center w-2/3 mb-6">The download took {{ downloadDuration }} seconds.</p>
|
||||||
|
<button class="btn btn-primary w-full max-w-md flex justify-center items-center mb-4" >
|
||||||
|
Save to disk (again)
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary w-full max-w-md flex justify-center items-center mb-4" routerLink="/home">
|
||||||
|
Back to home
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dialog #password_modal class="modal">
|
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<div *ngIf="fileDownloadStarted && !fileDownloadFinished" class="card w-96 bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body flex flex-col items-center justify-center">
|
||||||
|
<h2 class="card-title text-center">Did you know?</h2>
|
||||||
|
<p class="text-center">{{ funfact }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<dialog #download_not_possible class="modal">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3 class="font-bold text-lg">Please enter the file password</h3>
|
<h3 class="font-bold text-lg">The file cant be downloaded!</h3>
|
||||||
<p class="py-4">Please enter the download password:</p>
|
<div class="flex items-center justify-center flex-col w-full">
|
||||||
<input type="password" placeholder="Password..." class="input input-bordered w-full max-w-xs" />
|
<img class="w-24 mt-6 mb-6" src="./assets/poop-solid.svg">
|
||||||
<div class="modal-action">
|
<h4 class="text-xl mb-1">Oh no...</h4>
|
||||||
|
<p class="text-gray-400 mb-6">What happened here?</p>
|
||||||
|
<p class="text-gray-800 text-center mb-3">The file you are trying to download is not available anymore.</p>
|
||||||
|
<p class="text-gray-800 text-center mb-6">This issue may occur if the download limit has been reached or the download period has expired.</p>
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<button class="btn">Cancel download</button>
|
<button class="btn">Okay</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<dialog #file_not_found_modal class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg">File not found!</h3>
|
||||||
|
<div class="flex items-center justify-center flex-col w-full">
|
||||||
|
<img class="w-24 mt-6 mb-6" src="./assets/poop-solid.svg">
|
||||||
|
<h4 class="text-xl mb-1">Oh no...</h4>
|
||||||
|
<p class="text-gray-400 mb-6">What happened here?</p>
|
||||||
|
<p class="text-gray-800 text-center mb-3">The file you are trying to download does not exist.</p>
|
||||||
|
<p class="text-gray-800 text-center mb-6">Please check the file ID.</p>
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="btn">Okay</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<div class="invisible h-0 w-0">
|
||||||
|
<img src="./assets/circle-check-solid.svg">
|
||||||
|
</div>
|
||||||
|
@ -1,25 +1,86 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, ElementRef, ViewChild} from '@angular/core';
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import {DevelopmentStore} from "../../store/DevelopmentStore";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {DecimalPipe, NgClass, NgIf} from "@angular/common";
|
||||||
|
import funfacts from "../../assets/funfacts";
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-download',
|
selector: 'app-download',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NgIf,
|
||||||
|
DecimalPipe,
|
||||||
|
RouterLink,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
templateUrl: './download.component.html',
|
templateUrl: './download.component.html',
|
||||||
styleUrl: './download.component.scss'
|
styleUrl: './download.component.scss'
|
||||||
})
|
})
|
||||||
export class DownloadComponent {
|
export class DownloadComponent {
|
||||||
|
|
||||||
constructor() {
|
@ViewChild('download_not_possible') download_not_possible: ElementRef<HTMLDialogElement> | undefined;
|
||||||
|
@ViewChild('file_not_found_modal') file_not_found_modal: ElementRef<HTMLDialogElement> | undefined;
|
||||||
|
|
||||||
|
inputFileId: string = "2402183";
|
||||||
|
fileId: string = "";
|
||||||
|
filePassword: string = "";
|
||||||
|
fileName: string = "";
|
||||||
|
downloadInfo: DownloadInfo | null = null;
|
||||||
|
|
||||||
|
fileDownloadStarted: boolean = false;
|
||||||
|
fileDownloadFinished: boolean = false;
|
||||||
|
waitingForPassword: boolean = false;
|
||||||
|
downloadProgress: number = 0;
|
||||||
|
downloadDuration: string = "";
|
||||||
|
passwordWrong: boolean = false;
|
||||||
|
|
||||||
|
funfact: string = "";
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private developmentStore: DevelopmentStore) {
|
||||||
|
this.funfact = funfacts[Math.floor(Math.random() * funfacts.length)];
|
||||||
this.speedTest();
|
this.speedTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestDownload() {
|
||||||
|
|
||||||
|
if(this.waitingForPassword) {
|
||||||
|
console.log("Requesting download with password");
|
||||||
|
this.downloadFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(this.inputFileId);
|
||||||
|
this.fileId = this.inputFileId; // TODO: Implement link extraction logic
|
||||||
|
|
||||||
|
this.getDownloadInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
processDownloadInfo() {
|
||||||
|
if(!this.downloadInfo?.passwordProtected && this.downloadInfo?.downloadable) {
|
||||||
|
console.log("Proceeding with download");
|
||||||
|
this.downloadFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(!this.downloadInfo?.downloadable) {
|
||||||
|
this.download_not_possible?.nativeElement.showModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(this.downloadInfo?.passwordProtected) {
|
||||||
|
this.waitingForPassword = true;
|
||||||
|
console.log("Password protected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private speedTest() {
|
private speedTest() {
|
||||||
const start = new Date().getTime(); // Start timer
|
const start = new Date().getTime(); // Start timer
|
||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'http://localhost:/api/v1/speed-test',
|
url: this.developmentStore.getBaseUrl() + 'api/v1/speed-test',
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
headers: {
|
headers: {
|
||||||
'Access-Control-Allow-Origin': '*', // Allow CORS
|
'Access-Control-Allow-Origin': '*', // Allow CORS
|
||||||
@ -38,4 +99,94 @@ export class DownloadComponent {
|
|||||||
console.error('Error during download test:', error);
|
console.error('Error during download test:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDownloadInfo() {
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: this.developmentStore.getBaseUrl() + 'api/v1/download-info?fileId=' + this.fileId,
|
||||||
|
responseType: 'json',
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*', // Allow CORS
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
this.downloadInfo = response.data;
|
||||||
|
console.log(response.data);
|
||||||
|
this.processDownloadInfo();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.file_not_found_modal?.nativeElement.showModal();
|
||||||
|
console.error('Error during download info request:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private downloadFile() {
|
||||||
|
const startTime = new Date().getTime();
|
||||||
|
this.fileDownloadStarted = true;
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: this.developmentStore.getBaseUrl() + 'api/v1/download?fileId=' + this.fileId + '&password=' + this.filePassword,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*', // Allow CORS
|
||||||
|
},
|
||||||
|
onDownloadProgress: (progressEvent) => {
|
||||||
|
const endTime = new Date().getTime();
|
||||||
|
const duration = (endTime - startTime) / 1000;
|
||||||
|
this.downloadDuration = duration.toFixed(0);
|
||||||
|
// Calculate the percentage of download completed
|
||||||
|
if(progressEvent.total) {
|
||||||
|
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||||
|
this.downloadProgress = percentCompleted;
|
||||||
|
console.log(percentCompleted + '%'); // Log the percentage or update any progress UI component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
const endTime = new Date().getTime();
|
||||||
|
const duration = (endTime - startTime) / 1000;
|
||||||
|
this.downloadDuration = duration.toFixed(2);
|
||||||
|
this.fileDownloadFinished = true;
|
||||||
|
|
||||||
|
const contentDisposition = response.headers['content-disposition'];
|
||||||
|
let filename = "default_filename"; // Default filename in case parsing fails
|
||||||
|
|
||||||
|
if (contentDisposition) {
|
||||||
|
const filenameRegex = /filename="?([^"]+)"?/;
|
||||||
|
const matches = contentDisposition.match(filenameRegex);
|
||||||
|
if (matches && matches[1]) {
|
||||||
|
filename = matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.fileName = filename;
|
||||||
|
|
||||||
|
const blob = new Blob([response.data], {type: 'application/octet-stream'});
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename; // You can specify a filename here
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
// Clean up by revoking the Blob URL and removing the temporary anchor
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.fileDownloadStarted = false;
|
||||||
|
this.wrongPassword();
|
||||||
|
console.error('Error during download request:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrongPassword() {
|
||||||
|
this.passwordWrong = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface DownloadInfo {
|
||||||
|
downloadable: boolean;
|
||||||
|
passwordProtected: boolean;
|
||||||
|
singleDownload: boolean;
|
||||||
|
fileId: string;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="container mx-auto p-4 mt-4">
|
<div class="container mx-auto p-4 mt-4 animate-in fade-in slide-in-from-right-6 duration-300">
|
||||||
<h1 class="text-4xl font-bold text-center text-gray-800 mb-5">Upload Your File</h1>
|
<h1 class="text-4xl font-bold text-center text-gray-800 mb-5">Upload Your File</h1>
|
||||||
<div class="bg-white shadow-lg rounded-lg lg:p-6 p-3">
|
<div class="bg-white shadow-lg rounded-lg lg:p-6 p-3">
|
||||||
<div *ngIf="!uploadStarted" class="flex flex-col items-center justify-center lg:p-5">
|
<div *ngIf="!uploadStarted" class="flex flex-col items-center justify-center lg:p-5">
|
||||||
|
@ -147,7 +147,7 @@ export class UploadComponent {
|
|||||||
|
|
||||||
axios({
|
axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: 'http://localhost/api/v1/upload-speed-test',
|
url: this.developmentStore.getBaseUrl() + 'api/v1/upload-speed-test',
|
||||||
data: uint8View,
|
data: uint8View,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/octet-stream',
|
'Content-Type': 'application/octet-stream',
|
||||||
|
24
frontend/src/assets/funfacts.ts
Normal file
24
frontend/src/assets/funfacts.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
let funfacts = [
|
||||||
|
"Honey never spoils. Archaeologists have found pots of honey in ancient Egyptian tombs that are over 3,000 years old and still edible.",
|
||||||
|
"Octopuses have three hearts and blue blood.",
|
||||||
|
"Bananas are berries, but strawberries are not.",
|
||||||
|
"The shortest war in history was between Britain and Zanzibar on August 27, 1896. Zanzibar surrendered after 38 minutes.",
|
||||||
|
"A day on Venus is longer than a year on Venus.",
|
||||||
|
"The Eiffel Tower can be 15 cm taller during the summer when the iron heats up and expands.",
|
||||||
|
"Cows have best friends and can become stressed if they are separated.",
|
||||||
|
"A group of flamingos is called a flamboyance.",
|
||||||
|
"The unicorn is the national animal of Scotland.",
|
||||||
|
"More people are killed each year by cows than by sharks.",
|
||||||
|
"The total weight of all the ants on Earth is about the same as the weight of all the humans on Earth.",
|
||||||
|
"Wombat poop is cube-shaped.",
|
||||||
|
"The inventor of the frisbee was turned into a frisbee after he died.",
|
||||||
|
"There are more possible iterations of a game of chess than there are atoms in the known universe.",
|
||||||
|
"The heart of a blue whale is so large that a human can swim through the arteries.",
|
||||||
|
"Vending machines kill 4 times as many people as sharks per year.",
|
||||||
|
"Butterflies taste with their feet.",
|
||||||
|
"In Switzerland, it is illegal to own just one guinea pig because they are prone to loneliness.",
|
||||||
|
"Snails can sleep for up to three years.",
|
||||||
|
"The old Twitter bird actually had a name - Larry."
|
||||||
|
];
|
||||||
|
export default funfacts;
|
1
frontend/src/assets/poop-solid.svg
Normal file
1
frontend/src/assets/poop-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#2b3440" d="M254.4 6.6c3.5-4.3 9-6.5 14.5-5.7C315.8 7.2 352 47.4 352 96c0 11.2-1.9 22-5.5 32H352c35.3 0 64 28.7 64 64c0 19.1-8.4 36.3-21.7 48H408c39.8 0 72 32.2 72 72c0 23.2-11 43.8-28 57c34.1 5.7 60 35.3 60 71c0 39.8-32.2 72-72 72H72c-39.8 0-72-32.2-72-72c0-35.7 25.9-65.3 60-71c-17-13.2-28-33.8-28-57c0-39.8 32.2-72 72-72h13.7C104.4 228.3 96 211.1 96 192c0-35.3 28.7-64 64-64h16.2c44.1-.1 79.8-35.9 79.8-80c0-9.2-1.5-17.9-4.3-26.1c-1.8-5.2-.8-11.1 2.8-15.4z"/></svg>
|
After Width: | Height: | Size: 694 B |
@ -14,6 +14,8 @@ import org.springframework.stereotype.Repository;
|
|||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -44,11 +46,17 @@ public class FileUploadRepository {
|
|||||||
r.db("sharepulse").table("file_uploads").insert(map).run(connection);
|
r.db("sharepulse").table("file_uploads").insert(map).run(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileUpload retrieveFileUploadByFileId(String fileId) {
|
public Optional<FileUpload> retrieveFileUploadByFileId(String fileId) {
|
||||||
return r.db("sharepulse").table("file_uploads")
|
try {
|
||||||
|
FileUpload fileUpload = r.db("sharepulse").table("file_uploads")
|
||||||
.filter(r.hashMap("fileId", fileId))
|
.filter(r.hashMap("fileId", fileId))
|
||||||
.run(connection, FileUpload.class)
|
.run(connection, FileUpload.class)
|
||||||
.next();
|
.next();
|
||||||
|
return Optional.ofNullable(fileUpload);
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateFileUpload(FileUpload updatedFileUpload) {
|
public void updateFileUpload(FileUpload updatedFileUpload) {
|
||||||
|
@ -28,9 +28,9 @@ public class TestRestResource extends ApiRestController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("test")
|
@GetMapping("test")
|
||||||
public String test(@RequestParam String id) {
|
public FileUpload test(@RequestParam String id) {
|
||||||
FileUpload fileUpload = fileUploadRepository.retrieveFileUploadByFileId(id);
|
FileUpload fileUpload = fileUploadRepository.retrieveFileUploadByFileId(id).orElse(null);
|
||||||
return fileUpload != null ? fileUpload.toString() : "FileUpload not found for id: " + id;
|
return fileUpload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("test/download")
|
@GetMapping("test/download")
|
||||||
|
@ -22,6 +22,7 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@ -41,7 +42,11 @@ public class Download extends ApiRestController {
|
|||||||
@RequestParam(value = "password", required = false) String password) throws IOException {
|
@RequestParam(value = "password", required = false) String password) throws IOException {
|
||||||
|
|
||||||
|
|
||||||
FileUpload fileUpload = fileService.getFileUploadByFileId(fileId);
|
Optional<FileUpload> optionalFileUpload = fileService.getFileUploadByFileId(fileId);
|
||||||
|
FileUpload fileUpload = optionalFileUpload.orElse(null);
|
||||||
|
if(optionalFileUpload.isEmpty()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fileSecurityService.verifyDownloadPermission(fileUpload, password);
|
fileSecurityService.verifyDownloadPermission(fileUpload, password);
|
||||||
@ -63,6 +68,7 @@ public class Download extends ApiRestController {
|
|||||||
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||||
headers.add(HttpHeaders.PRAGMA, "no-cache");
|
headers.add(HttpHeaders.PRAGMA, "no-cache");
|
||||||
headers.add(HttpHeaders.EXPIRES, "0");
|
headers.add(HttpHeaders.EXPIRES, "0");
|
||||||
|
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Content-Disposition");
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.headers(headers)
|
.headers(headers)
|
||||||
@ -73,12 +79,19 @@ public class Download extends ApiRestController {
|
|||||||
|
|
||||||
@GetMapping("/download-info")
|
@GetMapping("/download-info")
|
||||||
public ResponseEntity<Object> getDownloadInfo(@RequestParam String fileId) {
|
public ResponseEntity<Object> getDownloadInfo(@RequestParam String fileId) {
|
||||||
FileUpload fileUpload = fileService.getFileUploadByFileId(fileId);
|
Optional<FileUpload> optionalFileUpload = fileService.getFileUploadByFileId(fileId);
|
||||||
|
FileUpload fileUpload = optionalFileUpload.orElse(null);
|
||||||
|
if(optionalFileUpload.isEmpty()) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean downloadable = !fileUpload.isSingleDownload() || fileUpload.getDownloadCount() == 0;
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("fileId", fileUpload.getFileId());
|
response.put("fileId", fileUpload.getFileId());
|
||||||
response.put("passwordProtected", fileUpload.isPasswordProtected());
|
response.put("passwordProtected", fileUpload.isPasswordProtected());
|
||||||
response.put("singleDownload", fileUpload.isSingleDownload());
|
response.put("singleDownload", fileUpload.isSingleDownload());
|
||||||
|
response.put("downloadable", downloadable);
|
||||||
|
|
||||||
return new ResponseEntity<>(response, HttpStatus.OK);
|
return new ResponseEntity<>(response, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,14 @@ public class Upload extends ApiRestController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FileUpload fileUpload = fileService.processUploadedFile(file, request.getRemoteAddr(), passwordProtected, singleDownload, fileDescription);
|
FileUpload fileUpload = fileService.processUploadedFile(file, request.getRemoteAddr(), passwordProtected, singleDownload, fileDescription);
|
||||||
|
|
||||||
|
if(fileUpload == null) {
|
||||||
|
log.debug("File Upload failed for IP: " + request.getRemoteAddr());
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("message", "FileUpload failed. FileName might have invalid characters.");
|
||||||
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("User uploaded file " + file.getOriginalFilename() + " from IP " + request.getRemoteAddr() + " successfully.");
|
log.debug("User uploaded file " + file.getOriginalFilename() + " from IP " + request.getRemoteAddr() + " successfully.");
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
@ -3,7 +3,6 @@ package de.w665.sharepulse.service;
|
|||||||
import de.w665.sharepulse.db.repo.FileUploadRepository;
|
import de.w665.sharepulse.db.repo.FileUploadRepository;
|
||||||
import de.w665.sharepulse.model.FileUpload;
|
import de.w665.sharepulse.model.FileUpload;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.tomcat.util.http.fileupload.FileUtils;
|
import org.apache.tomcat.util.http.fileupload.FileUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -17,6 +16,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@ -70,6 +70,14 @@ public class FileService {
|
|||||||
password = fileSecurityService.generateFilePassword();
|
password = fileSecurityService.generateFilePassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*String encodedFileName = "";
|
||||||
|
try {
|
||||||
|
encodedFileName = URLEncoder.encode(file.getOriginalFilename(), StandardCharsets.UTF_8.toString());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
log.error("Error encoding file name: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}*/
|
||||||
|
|
||||||
FileUpload fileUpload = FileUpload.builder()
|
FileUpload fileUpload = FileUpload.builder()
|
||||||
.fileId(fileId)
|
.fileId(fileId)
|
||||||
.fileName(file.getOriginalFilename())
|
.fileName(file.getOriginalFilename())
|
||||||
@ -104,7 +112,7 @@ public class FileService {
|
|||||||
return new File(getTempDirPath() + File.separator + fileId);
|
return new File(getTempDirPath() + File.separator + fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileUpload getFileUploadByFileId(String fileId) {
|
public Optional<FileUpload> getFileUploadByFileId(String fileId) {
|
||||||
return fileUploadRepository.retrieveFileUploadByFileId(fileId);
|
return fileUploadRepository.retrieveFileUploadByFileId(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user