diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java index 4675af5e..5a193ce7 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java @@ -140,6 +140,7 @@ public class FileDao { // Update the file fileDb.setDocumentId(file.getDocumentId()); + fileDb.setName(file.getName()); fileDb.setContent(file.getContent()); fileDb.setOrder(file.getOrder()); fileDb.setMimeType(file.getMimeType()); diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/LuceneDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/LuceneDao.java index e3bafc2f..67d733e0 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/LuceneDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/LuceneDao.java @@ -1,15 +1,14 @@ package com.sismics.docs.core.dao.lucene; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - +import com.sismics.docs.core.model.context.AppContext; +import com.sismics.docs.core.model.jpa.Document; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.util.LuceneUtil; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.DirectoryReader; -import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil; import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; @@ -19,11 +18,9 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; -import com.sismics.docs.core.model.context.AppContext; -import com.sismics.docs.core.model.jpa.Document; -import com.sismics.docs.core.model.jpa.File; -import com.sismics.docs.core.util.LuceneUtil; -import com.sismics.docs.core.util.LuceneUtil.LuceneRunnable; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Lucene DAO. @@ -37,23 +34,20 @@ public class LuceneDao { * @param fileList List of files */ public void rebuildIndex(final List documentList, final List fileList) { - LuceneUtil.handle(new LuceneRunnable() { - @Override - public void run(IndexWriter indexWriter) throws Exception { - // Empty index - indexWriter.deleteAll(); - - // Add all documents - for (Document document : documentList) { - org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document); - indexWriter.addDocument(luceneDocument); - } - - // Add all files - for (File file : fileList) { - org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file); - indexWriter.addDocument(luceneDocument); - } + LuceneUtil.handle(indexWriter -> { + // Empty index + indexWriter.deleteAll(); + + // Add all documents + for (Document document : documentList) { + org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document); + indexWriter.addDocument(luceneDocument); + } + + // Add all files + for (File file : fileList) { + org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file); + indexWriter.addDocument(luceneDocument); } }); } @@ -64,12 +58,9 @@ public class LuceneDao { * @param document Document to add */ public void createDocument(final Document document) { - LuceneUtil.handle(new LuceneRunnable() { - @Override - public void run(IndexWriter indexWriter) throws Exception { - org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document); - indexWriter.addDocument(luceneDocument); - } + LuceneUtil.handle(indexWriter -> { + org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document); + indexWriter.addDocument(luceneDocument); }); } @@ -79,27 +70,21 @@ public class LuceneDao { * @param file File to add */ public void createFile(final File file) { - LuceneUtil.handle(new LuceneRunnable() { - @Override - public void run(IndexWriter indexWriter) throws Exception { - org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file); - indexWriter.addDocument(luceneDocument); - } + LuceneUtil.handle(indexWriter -> { + org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file); + indexWriter.addDocument(luceneDocument); }); } - + /** * Update document index. * * @param document Updated document */ public void updateDocument(final Document document) { - LuceneUtil.handle(new LuceneRunnable() { - @Override - public void run(IndexWriter indexWriter) throws Exception { - org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document); - indexWriter.updateDocument(new Term("id", document.getId()), luceneDocument); - } + LuceneUtil.handle(indexWriter -> { + org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document); + indexWriter.updateDocument(new Term("id", document.getId()), luceneDocument); }); } @@ -109,12 +94,7 @@ public class LuceneDao { * @param id Document ID to delete */ public void deleteDocument(final String id) { - LuceneUtil.handle(new LuceneRunnable() { - @Override - public void run(IndexWriter indexWriter) throws Exception { - indexWriter.deleteDocuments(new Term("id", id)); - } - }); + LuceneUtil.handle(indexWriter -> indexWriter.deleteDocuments(new Term("id", id))); } /** @@ -123,7 +103,7 @@ public class LuceneDao { * @param searchQuery Search query on title and description * @param fullSearchQuery Search query on all fields * @return List of document IDs - * @throws Exception + * @throws Exception e */ public Set search(String searchQuery, String fullSearchQuery) throws Exception { // Escape query and add quotes so QueryParser generate a PhraseQuery diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index 33860c5e..3955cc82 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -139,8 +139,8 @@ public class FileResource extends BaseResource { /** * Attach a file to a document. * - * @api {post} /file/:fileId Attach a file to a document - * @apiName PostFile + * @api {post} /file/:fileId/attach Attach a file to a document + * @apiName PostFileAttach * @apiGroup File * @apiParam {String} fileId File ID * @apiParam {String} id Document ID @@ -156,7 +156,7 @@ public class FileResource extends BaseResource { * @return Response */ @POST - @Path("{id: [a-z0-9\\-]+}") + @Path("{id: [a-z0-9\\-]+}/attach") public Response attach( @PathParam("id") String id, @FormParam("id") String documentId) { @@ -215,6 +215,48 @@ public class FileResource extends BaseResource { .add("status", "ok"); return Response.ok().entity(response.build()).build(); } + + /** + * Update a file. + * + * @api {post} /file/:id Update a file + * @apiName PostFile + * @apiGroup File + * @apiParam {String} id File ID + * @apiParam {String} name Name + * @apiSuccess {String} status Status OK + * @apiError (client) ForbiddenError Access denied + * @apiError (client) ValidationError Validation error + * @apiPermission user + * @apiVersion 1.6.0 + * + * @param id File ID + * @return Response + */ + @POST + @Path("{id: [a-z0-9\\-]+}") + public Response update(@PathParam("id") String id, + @FormParam("name") String name) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + + // Get the file + File file = findFile(id, null); + + // Validate input data + name = ValidationUtil.validateLength(name, "name", 1, 200, false); + + // Update the file + FileDao fileDao = new FileDao(); + file.setName(name); + fileDao.update(file); + + // Always return OK + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } /** * Reorder files. @@ -362,24 +404,10 @@ public class FileResource extends BaseResource { } // Get the file - FileDao fileDao = new FileDao(); - AclDao aclDao = new AclDao(); - File file = fileDao.getFile(id); - if (file == null) { - throw new NotFoundException(); - } - - if (file.getDocumentId() == null) { - // It's an orphan file - if (!file.getUserId().equals(principal.getId())) { - // But not ours - throw new ForbiddenClientException(); - } - } else if (!aclDao.checkPermission(file.getDocumentId(), PermType.WRITE, getTargetIdList(null))) { - throw new NotFoundException(); - } - + File file = findFile(id, null); + // Delete the file + FileDao fileDao = new FileDao(); fileDao.delete(file.getId(), principal.getId()); // Update the user quota @@ -448,29 +476,10 @@ public class FileResource extends BaseResource { } // Get the file - FileDao fileDao = new FileDao(); - UserDao userDao = new UserDao(); - File file = fileDao.getFile(fileId); - if (file == null) { - throw new NotFoundException(); - } - - if (file.getDocumentId() == null) { - // It's an orphan file - if (!file.getUserId().equals(principal.getId())) { - // But not ours - throw new ForbiddenClientException(); - } - } else { - // Check document accessibility - AclDao aclDao = new AclDao(); - if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, getTargetIdList(shareId))) { - throw new ForbiddenClientException(); - } - } + File file = findFile(fileId, shareId); - // Get the stored file + UserDao userDao = new UserDao(); java.nio.file.Path storedFile; String mimeType; boolean decrypt; @@ -535,7 +544,7 @@ public class FileResource extends BaseResource { } return builder.build(); } - + /** * Returns all files from a document, zipped. * @@ -605,4 +614,34 @@ public class FileResource extends BaseResource { .header("Content-Disposition", "attachment; filename=\"" + documentDto.getTitle().replaceAll("\\W+", "_") + ".zip\"") .build(); } + + /** + * Find a file with access rights checking. + * + * @param fileId File ID + * @param shareId Share ID + * @return File + */ + private File findFile(String fileId, String shareId) { + FileDao fileDao = new FileDao(); + File file = fileDao.getFile(fileId); + if (file == null) { + throw new NotFoundException(); + } + + if (file.getDocumentId() == null) { + // It's an orphan file + if (!file.getUserId().equals(principal.getId())) { + // But not ours + throw new ForbiddenClientException(); + } + } else { + // Check document accessibility + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, getTargetIdList(shareId))) { + throw new ForbiddenClientException(); + } + } + return file; + } } diff --git a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentEdit.js index 78dae5ff..047c2fd6 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentEdit.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentEdit.js @@ -103,7 +103,7 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $ var attachOrphanFiles = function(data) { var promises = []; _.each($scope.orphanFiles, function(fileId) { - promises.push(Restangular.one('file/' + fileId).post('', { id: data.id })); + promises.push(Restangular.one('file/' + fileId).post('attach', { id: data.id })); }); $scope.orphanFiles = []; return $q.all(promises); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewContent.js b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewContent.js index 0317163f..f5631693 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewContent.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewContent.js @@ -3,7 +3,7 @@ /** * Document view content controller. */ -angular.module('docs').controller('DocumentViewContent', function ($scope, $rootScope, $stateParams, Restangular, $dialog, $state, Upload, $translate) { +angular.module('docs').controller('DocumentViewContent', function ($scope, $rootScope, $stateParams, Restangular, $dialog, $state, Upload, $translate, $uibModal) { /** * Configuration for file sorting. */ @@ -131,4 +131,30 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root } }); }; + + /** + * Rename a file. + */ + $scope.renameFile = function (file) { + $uibModal.open({ + templateUrl: 'partial/docs/file.rename.html', + controller: 'FileRename', + resolve: { + file: function () { + return angular.copy(file); + } + } + }).result.then(function (fileUpdated) { + if (fileUpdated === null) { + return; + } + + // Rename the file + Restangular.one('file/' + file.id).post('', { + name: fileUpdated.name + }).then(function () { + file.name = fileUpdated.name; + }) + }); + }; }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/controller/document/FileRename.js b/docs-web/src/main/webapp/src/app/docs/controller/document/FileRename.js new file mode 100644 index 00000000..d2fb6dbc --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/controller/document/FileRename.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * File rename controller. + */ +angular.module('docs').controller('FileRename', function ($scope, file, $uibModalInstance) { + $scope.file = file; + $scope.close = function(file) { + $uibModalInstance.close(file); + } +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index 01459c2b..fba724a4 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -69,6 +69,7 @@ + diff --git a/docs-web/src/main/webapp/src/locale/en.json b/docs-web/src/main/webapp/src/locale/en.json index 7bb5dac1..c77223a7 100644 --- a/docs-web/src/main/webapp/src/locale/en.json +++ b/docs-web/src/main/webapp/src/locale/en.json @@ -194,6 +194,10 @@ "previous": "Previous", "next": "Next", "not_found": "File not found" + }, + "edit": { + "title": "Edit file", + "name": "Filename" } }, "tag": { @@ -531,6 +535,7 @@ "export": "Export", "edit": "Edit", "delete": "Delete", + "rename": "Rename", "loading": "Loading...", "send": "Send", "enabled": "Enabled", diff --git a/docs-web/src/main/webapp/src/partial/docs/document.default.html b/docs-web/src/main/webapp/src/partial/docs/document.default.html index d101bca8..6fd61a74 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.default.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.default.html @@ -13,7 +13,7 @@
-
{{ file.name }}
+
{{ file.name }}
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.content.html b/docs-web/src/main/webapp/src/partial/docs/document.view.content.html index 8b3dc182..e6788318 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.content.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.content.html @@ -55,7 +55,7 @@
-
{{ file.name }}
+
{{ file.name }}
@@ -67,9 +67,13 @@ diff --git a/docs-web/src/main/webapp/src/partial/docs/file.rename.html b/docs-web/src/main/webapp/src/partial/docs/file.rename.html new file mode 100644 index 00000000..761c5456 --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/file.rename.html @@ -0,0 +1,16 @@ + +
+ + +
diff --git a/docs-web/src/main/webapp/src/style/main.less b/docs-web/src/main/webapp/src/style/main.less index 779bfa70..b55baf59 100644 --- a/docs-web/src/main/webapp/src/style/main.less +++ b/docs-web/src/main/webapp/src/style/main.less @@ -207,6 +207,8 @@ ul.tag-tree { white-space: normal; display: block; text-overflow: ellipsis; + margin-right: auto !important; + margin-left: auto !important; } .file-info { diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java index 102391fd..17b3f8e3 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java @@ -135,7 +135,25 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertEquals(163510L, files.getJsonObject(0).getJsonNumber("size").longValue()); Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id")); Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name")); - + + // Rename a file + target().path("file/" + file1Id) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) + .post(Entity.form(new Form() + .param("name", "Pale Blue Dot")), JsonObject.class); + + // Get all files from a document + json = target().path("/file/list") + .queryParam("id", document1Id) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) + .get(JsonObject.class); + files = json.getJsonArray("files"); + Assert.assertEquals(2, files.size()); + Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); + Assert.assertEquals("Pale Blue Dot", files.getJsonObject(0).getString("name")); + // Reorder files target().path("/file/reorder").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file1Token) @@ -301,7 +319,7 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertNotNull(document1Id); // Attach a file to a document - target().path("/file/" + file1Id).request() + target().path("/file/" + file1Id + "/attach").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, file3Token) .post(Entity.form(new Form() .param("id", document1Id)), JsonObject.class);