diff --git a/src/main/java/ru/ulstu/file/FileController.java b/src/main/java/ru/ulstu/file/FileController.java index e60626b..ac08ac1 100644 --- a/src/main/java/ru/ulstu/file/FileController.java +++ b/src/main/java/ru/ulstu/file/FileController.java @@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile; import ru.ulstu.configuration.Constants; import ru.ulstu.core.model.response.Response; import ru.ulstu.file.model.FileData; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.file.service.FileService; import java.io.IOException; @@ -51,7 +52,7 @@ public class FileController { } @PostMapping("/uploadTmpFile") - public Response upload(@RequestParam("file") MultipartFile multipartFile) throws IOException { - return new Response(fileService.uploadToTmpDir(multipartFile)); + public Response upload(@RequestParam("file") MultipartFile multipartFile) throws IOException { + return new Response(fileService.createFromMultipartFile(multipartFile)); } } diff --git a/src/main/java/ru/ulstu/file/model/FileDataDto.java b/src/main/java/ru/ulstu/file/model/FileDataDto.java new file mode 100644 index 0000000..919cd80 --- /dev/null +++ b/src/main/java/ru/ulstu/file/model/FileDataDto.java @@ -0,0 +1,76 @@ +package ru.ulstu.file.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FileDataDto { + private Integer id; + private String name; + private String fileName; + private String tmpFileName; + private boolean deleted; + + public FileDataDto() { + } + + @JsonCreator + public FileDataDto(@JsonProperty("id") Integer id, + @JsonProperty("name") String name, + @JsonProperty("fileName") String fileName, + @JsonProperty("tmpFileName") String tmpFileName) { + this.id = id; + this.name = name; + this.fileName = fileName; + this.tmpFileName = tmpFileName; + } + + public FileDataDto(FileData fileData) { + this.id = fileData.getId(); + this.name = fileData.getName(); + } + + public FileDataDto(String fileName, String tmpFileName) { + this.fileName = fileName; + this.tmpFileName = tmpFileName; + } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getTmpFileName() { + return tmpFileName; + } + + public void setTmpFileName(String tmpFileName) { + this.tmpFileName = tmpFileName; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + +} diff --git a/src/main/java/ru/ulstu/file/service/FileService.java b/src/main/java/ru/ulstu/file/service/FileService.java index 2f1fb6d..ad9f946 100644 --- a/src/main/java/ru/ulstu/file/service/FileService.java +++ b/src/main/java/ru/ulstu/file/service/FileService.java @@ -1,15 +1,20 @@ package ru.ulstu.file.service; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import ru.ulstu.file.model.FileData; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.file.repostory.FileRepository; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.UUID; import static java.nio.charset.StandardCharsets.UTF_8; @@ -29,13 +34,13 @@ public class FileService { public FileData createFileFromTmp(String tmpFileName) throws IOException { FileData fileData = new FileData(); fileData.setData(getTmpFile(tmpFileName)); - fileData.setName(getTmpFileName(tmpFileName)); + fileData.setSize(getTmpFileSize(tmpFileName)); fileData.setCreateDate(new Date()); return fileRepository.save(fileData); } public String uploadToTmpDir(MultipartFile multipartFile) throws IOException { - String tmpFileName = String.valueOf(System.currentTimeMillis()); + String tmpFileName = String.valueOf(System.currentTimeMillis()) + UUID.randomUUID(); Files.write(getTmpFilePath(tmpFileName), multipartFile.getBytes()); String meta = multipartFile.getOriginalFilename() + "\n" + multipartFile.getSize(); Files.write(getTmpFileMetaPath(tmpFileName), meta.getBytes(UTF_8)); @@ -78,4 +83,39 @@ public class FileService { public void deleteFile(FileData fileData) { fileRepository.delete(fileData); } + + public List saveOrCreate(List fileDtos) throws IOException { + List files = new ArrayList<>(); + for (FileDataDto file : fileDtos) { + files.add(file.getId() != null ? update(file) : create(file)); + } + return files; + } + + @Transactional + public FileData update(FileDataDto fileDataDto) { + FileData file = fileRepository.findOne(fileDataDto.getId()); + return fileRepository.save(copyFromDto(file, fileDataDto)); + } + + @Transactional + public FileData create(FileDataDto fileDataDto) throws IOException { + FileData newFile = createFileFromTmp(fileDataDto.getTmpFileName()); + copyFromDto(newFile, fileDataDto); + return fileRepository.save(newFile); + } + + private FileData copyFromDto(FileData fileData, FileDataDto fileDataDto) { + fileData.setName(fileDataDto.getName()); + return fileData; + } + + @Transactional + public void delete(Integer fileId) { + fileRepository.delete(fileRepository.findOne(fileId)); + } + + public FileDataDto createFromMultipartFile(MultipartFile multipartFile) throws IOException { + return new FileDataDto(multipartFile.getOriginalFilename(), uploadToTmpDir(multipartFile)); + } } diff --git a/src/main/java/ru/ulstu/paper/model/Paper.java b/src/main/java/ru/ulstu/paper/model/Paper.java index c09fd28..b2f8327 100644 --- a/src/main/java/ru/ulstu/paper/model/Paper.java +++ b/src/main/java/ru/ulstu/paper/model/Paper.java @@ -17,7 +17,6 @@ import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Temporal; @@ -30,6 +29,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; + @Entity public class Paper extends BaseEntity implements UserContainer { public enum PaperStatus { @@ -77,9 +77,11 @@ public class Paper extends BaseEntity implements UserContainer { private Boolean locked = false; - @ManyToOne - @JoinColumn(name = "file_id") - private FileData fileData; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "paper_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + private List files = new ArrayList<>(); @ManyToMany(fetch = FetchType.EAGER) private Set authors = new HashSet<>(); @@ -132,12 +134,12 @@ public class Paper extends BaseEntity implements UserContainer { this.locked = locked; } - public FileData getFileData() { - return fileData; + public List getFiles() { + return files; } - public void setFileData(FileData fileData) { - this.fileData = fileData; + public void setFiles(List files) { + this.files = files; } public String getTitle() { diff --git a/src/main/java/ru/ulstu/paper/model/PaperDto.java b/src/main/java/ru/ulstu/paper/model/PaperDto.java index 3405768..18284f4 100644 --- a/src/main/java/ru/ulstu/paper/model/PaperDto.java +++ b/src/main/java/ru/ulstu/paper/model/PaperDto.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.commons.lang3.StringUtils; import org.hibernate.validator.constraints.NotEmpty; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.deadline.model.Deadline; import ru.ulstu.user.model.UserDto; @@ -30,10 +31,7 @@ public class PaperDto { private List deadlines = new ArrayList<>(); private String comment; private Boolean locked; - private String tmpFileName; - private Integer fileId; - private String fileName; - private Date fileCreateDate; + private List files = new ArrayList<>(); private Set authorIds; private Set authors; private Integer filterAuthorId; @@ -51,7 +49,7 @@ public class PaperDto { @JsonProperty("deadlines") List deadlines, @JsonProperty("comment") String comment, @JsonProperty("locked") Boolean locked, - @JsonProperty("tmpFileName") String tmpFileName, + @JsonProperty("files") List files, @JsonProperty("authorIds") Set authorIds, @JsonProperty("authors") Set authors) { this.id = id; @@ -62,10 +60,7 @@ public class PaperDto { this.deadlines = deadlines; this.comment = comment; this.locked = locked; - this.tmpFileName = tmpFileName; - this.fileId = null; - this.fileName = null; - this.fileCreateDate = null; + this.files = files; this.authors = authors; } @@ -78,10 +73,7 @@ public class PaperDto { this.deadlines = paper.getDeadlines(); this.comment = paper.getComment(); this.locked = paper.getLocked(); - this.tmpFileName = null; - this.fileId = paper.getFileData() == null ? null : paper.getFileData().getId(); - this.fileName = paper.getFileData() == null ? null : paper.getFileData().getName(); - this.fileCreateDate = paper.getFileData() == null ? null : paper.getFileData().getCreateDate(); + this.files = convert(paper.getFiles(), FileDataDto::new); this.authorIds = convert(paper.getAuthors(), user -> user.getId()); this.authors = convert(paper.getAuthors(), UserDto::new); } @@ -150,36 +142,12 @@ public class PaperDto { this.locked = locked; } - public String getTmpFileName() { - return tmpFileName; + public List getFiles() { + return files; } - public void setTmpFileName(String tmpFileName) { - this.tmpFileName = tmpFileName; - } - - public Integer getFileId() { - return fileId; - } - - public void setFileId(Integer fileId) { - this.fileId = fileId; - } - - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public Date getFileCreateDate() { - return fileCreateDate; - } - - public void setFileCreateDate(Date fileCreateDate) { - this.fileCreateDate = fileCreateDate; + public void setFiles(List files) { + this.files = files; } public Set getAuthors() { diff --git a/src/main/java/ru/ulstu/paper/service/PaperService.java b/src/main/java/ru/ulstu/paper/service/PaperService.java index 9df8be2..a664d00 100644 --- a/src/main/java/ru/ulstu/paper/service/PaperService.java +++ b/src/main/java/ru/ulstu/paper/service/PaperService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.ulstu.deadline.model.Deadline; import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.file.model.FileDataDto; import ru.ulstu.file.service.FileService; import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.PaperDto; @@ -19,8 +20,8 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; import static org.springframework.util.ObjectUtils.isEmpty; import static ru.ulstu.core.util.StreamApiUtils.convert; import static ru.ulstu.paper.model.Paper.PaperStatus.ATTENTION; @@ -65,7 +66,7 @@ public class PaperService { return findAllDto() .stream() .filter(paper -> paper.getStatus() != COMPLETED && paper.getStatus() != FAILED) - .collect(Collectors.toList()); + .collect(toList()); } public PaperDto findOneDto(Integer id) { @@ -88,9 +89,9 @@ public class PaperService { paper.setTitle(paperDto.getTitle()); paper.setUpdateDate(new Date()); paper.setDeadlines(deadlineService.saveOrCreate(paperDto.getDeadlines())); - if (paperDto.getTmpFileName() != null) { - paper.setFileData(fileService.createFileFromTmp(paperDto.getTmpFileName())); - } + paper.setFiles(fileService.saveOrCreate(paperDto.getFiles().stream() + .filter(f -> !f.isDeleted()) + .collect(toList()))); paper.getAuthors().clear(); if (paperDto.getAuthorIds() != null && !paperDto.getAuthorIds().isEmpty()) { paperDto.getAuthorIds().forEach(authorIds -> paper.getAuthors().add(userService.findById(authorIds))); @@ -103,9 +104,13 @@ public class PaperService { Paper paper = paperRepository.findOne(paperDto.getId()); Paper.PaperStatus oldStatus = paper.getStatus(); Set oldAuthors = new HashSet<>(paper.getAuthors()); - if (paperDto.getTmpFileName() != null && paper.getFileData() != null) { - fileService.deleteFile(paper.getFileData()); + + for (FileDataDto file : paperDto.getFiles().stream() + .filter(f -> f.isDeleted() && f.getId() != null) + .collect(toList())) { + fileService.delete(file.getId()); } + paperRepository.save(copyFromDto(paper, paperDto)); paper.getAuthors().forEach(author -> { @@ -124,9 +129,6 @@ public class PaperService { @Transactional public void delete(Integer paperId) throws IOException { Paper paper = paperRepository.findOne(paperId); - if (paper.getFileData() != null) { - fileService.deleteFile(paper.getFileData()); - } paperRepository.delete(paper); } @@ -165,7 +167,7 @@ public class PaperService { return statusCompareResult; } return paper1.getTitle().compareTo(paper2.getTitle()); - }).collect(Collectors.toList()); + }).collect(toList()); } public PaperDto findPaper(int id) { @@ -179,7 +181,7 @@ public class PaperService { && (paper.getStatus() == ON_PREPARATION || paper.getStatus() == DRAFT || paper.getStatus() == ATTENTION)) - .collect(Collectors.toList()); + .collect(toList()); papers.forEach(paper -> { Paper.PaperStatus oldStatus = paper.getStatus(); paper.setStatus(Paper.PaperStatus.FAILED); diff --git a/src/main/resources/db/changelog-20190318_000000-schema.xml b/src/main/resources/db/changelog-20190318_000000-schema.xml new file mode 100644 index 0000000..a4f7ec1 --- /dev/null +++ b/src/main/resources/db/changelog-20190318_000000-schema.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/main/resources/db/changelog-master.xml b/src/main/resources/db/changelog-master.xml index b8654ae..dcba122 100644 --- a/src/main/resources/db/changelog-master.xml +++ b/src/main/resources/db/changelog-master.xml @@ -18,5 +18,6 @@ + \ No newline at end of file diff --git a/src/main/resources/public/css/paper.css b/src/main/resources/public/css/paper.css new file mode 100644 index 0000000..6e61d0c --- /dev/null +++ b/src/main/resources/public/css/paper.css @@ -0,0 +1,5 @@ +#files-list .row > div:nth-child(6) { + display: flex; + justify-content: center; + flex-direction: column; +} \ No newline at end of file diff --git a/src/main/resources/public/js/core.js b/src/main/resources/public/js/core.js index 5c9241c..216f411 100644 --- a/src/main/resources/public/js/core.js +++ b/src/main/resources/public/js/core.js @@ -1,7 +1,9 @@ // from config.js /* global urlVersions */ -var urlFileUpload = "/api/1.0/papers/uploadTmpFile"; +var urlFileUpload = "/api/1.0/files/uploadTmpFile"; +var urlFileDownload = "/api/1.0/files/download/"; +var urlFileDownloadTmp = "/api/1.0/files/download-tmp/"; /* exported MessageTypesEnum */ var MessageTypesEnum = { diff --git a/src/main/resources/public/js/file-loader.js b/src/main/resources/public/js/file-loader.js index 9ee16a1..1ba467b 100644 --- a/src/main/resources/public/js/file-loader.js +++ b/src/main/resources/public/js/file-loader.js @@ -42,6 +42,7 @@ function FileLoader(args) { div.append(fileLabel); var fileInput = $("") .attr("type", "file") + .attr("multiple", '') .hide(); fileInput.change(function () { var files = $(this).prop("files"); @@ -75,20 +76,22 @@ function FileLoader(args) { showFeedbackMessage(ALERT_CHOOSE_FILE, MessageTypesEnum.DANGER); return; } - var currentFile = files[0]; - if (!isEmpty(fileExtensions) && fileExtensions.indexOf(getFileExt(currentFile)) === -1) { - showFeedbackMessage(ALERT_UNKNOWN_FILE_EXT, MessageTypesEnum.DANGER); - return; - } - if (currentFile.size === 0) { - showFeedbackMessage(ALERT_EMPTY_FILE, MessageTypesEnum.DANGER); - return; - } - if (currentFile.size / SIZE_TO_MB > MAX_FILE_SIZE_MB) { - showFeedbackMessage(ALERT_MAX_FILE + " " + MAX_FILE_SIZE_MB + "Mb", MessageTypesEnum.DANGER); - return; + for (var i = 0; i < files.length; i++) { + var currentFile = files[i]; + if (!isEmpty(fileExtensions) && fileExtensions.indexOf(getFileExt(currentFile)) === -1) { + showFeedbackMessage(ALERT_UNKNOWN_FILE_EXT, MessageTypesEnum.DANGER); + return; + } + if (currentFile.size === 0) { + showFeedbackMessage(ALERT_EMPTY_FILE, MessageTypesEnum.DANGER); + return; + } + if (MAX_FILE_SIZE_MB != -1 && currentFile.size / SIZE_TO_MB > MAX_FILE_SIZE_MB) { + showFeedbackMessage(ALERT_MAX_FILE + " " + MAX_FILE_SIZE_MB + "Mb", MessageTypesEnum.DANGER); + return; + } + upload(currentFile); } - upload(currentFile); }); buttonGroup.append(uploadButton); diff --git a/src/main/resources/templates/papers/paper.html b/src/main/resources/templates/papers/paper.html index a5ff6b5..a0b11f1 100644 --- a/src/main/resources/templates/papers/paper.html +++ b/src/main/resources/templates/papers/paper.html @@ -3,7 +3,7 @@ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="default" xmlns:th="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/html"> - + @@ -91,6 +91,34 @@

Incorrect title

+ +
+ + +
+ + + + +
+ + + +
+
+ + +
+
+
+ +
+
@@ -166,16 +194,103 @@ new FileLoader({ div: "loader", url: urlFileUpload, - maxSize: 1.5, - extensions: ["doc", "docx", "xls", "jpg", "pdf", "txt", "png"], + maxSize: -1, + extensions: [], callback: function (response) { showFeedbackMessage("Файл успешно загружен"); console.debug(response); + + addNewFile(response); } }); $('.selectpicker').selectpicker(); }); /*]]>*/ + function addNewFile(fileDto) { + var fileNumber = $("#files-list div.row").length; + + var newFileRow = $("
") + .attr("id", 'files' + fileNumber) + .addClass("row"); + + var idInput = $("") + .attr("type", "hidden") + .attr("id", "files" + fileNumber + ".id") + .attr("value", '') + .attr("name", "files[" + fileNumber + "].id"); + newFileRow.append(idInput); + + var flagInput = $("") + .attr("type", "hidden") + .attr("id", "files" + fileNumber + ".deleted") + .attr("value", "false") + .attr("name", "files[" + fileNumber + "].deleted"); + newFileRow.append(flagInput); + + var nameInput = $("") + .attr("type", "hidden") + .attr("id", "files" + fileNumber + ".name") + .attr("value", fileDto.fileName) + .attr("name", "files[" + fileNumber + "].name"); + newFileRow.append(nameInput); + + var tmpFileNameInput = $("") + .attr("type", "hidden") + .attr("id", "files" + fileNumber + ".tmpFileName") + .attr("value", fileDto.tmpFileName) + .attr("name", "files[" + fileNumber + "].tmpFileName"); + newFileRow.append(tmpFileNameInput); + + var nextDiv = $("
") + .addClass("col-2"); + + var nextA = $("") + .addClass("btn btn-danger float-right") + .attr("onclick", "$('#files" + fileNumber + "\\\\.deleted').val('true'); $('#files" + fileNumber + "').hide();") + .append(($("").attr("aria-hidden", "true")).append($("").addClass("fa fa-times"))) + ; + nextDiv.append(nextA) + newFileRow.append(nextDiv); + + var nameDiv = $("
") + .addClass("col-10") + .append($("").text(fileDto.fileName) + .attr("href", 'javascript:void(0)') + .attr("onclick", "downloadFile('" + fileDto.tmpFileName + "',null,'" + fileDto.fileName + "')")); + newFileRow.append(nameDiv); + + $("#files-list").append(newFileRow); + + } + + function downloadFile(tmpName, fileId, downloadName) { + let xhr = new XMLHttpRequest(); + if (fileId != null) xhr.open('GET', urlFileDownload + fileId); + if (tmpName != null) xhr.open('GET', urlFileDownloadTmp + tmpName); + xhr.responseType = 'blob'; + + var formData = new FormData(); + if (fileId != null) formData.append("file-id", fileId); + if (tmpName != null) formData.append("tmp-file-name", tmpName); + + xhr.send(formData); + + xhr.onload = function () { + if (this.status == 200) { + console.debug(this.response); + var blob = new Blob([this.response], {type: '*'}); + let a = document.createElement("a"); + a.style = "display: none"; + document.body.appendChild(a); + let url = window.URL.createObjectURL(blob); + a.href = url; + a.download = downloadName; + a.click(); + window.URL.revokeObjectURL(url); + } else { + } + } + }