diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java index 64682bc6..36143622 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java @@ -10,6 +10,8 @@ import javax.persistence.Table; import javax.persistence.Transient; import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; +import com.sismics.util.mime.MimeTypeUtil; /** * File entity. @@ -38,12 +40,18 @@ public class File implements Loggable { @Column(name = "FIL_IDUSER_C", length = 36, nullable = false) private String userId; + /** + * Name. + */ + @Column(name = "FIL_NAME_C", length = 200) + private String name; + /** * MIME type. */ @Column(name = "FIL_MIMETYPE_C", length = 100) private String mimeType; - + /** * OCR-ized content. */ @@ -92,6 +100,15 @@ public class File implements Loggable { this.documentId = documentId; } + public String getName() { + return name; + } + + public File setName(String name) { + this.name = name; + return this; + } + public String getMimeType() { return mimeType; } @@ -153,6 +170,7 @@ public class File implements Loggable { public String toString() { return MoreObjects.toStringHelper(this) .add("id", id) + .add("name", name) .toString(); } @@ -160,4 +178,17 @@ public class File implements Loggable { public String toMessage() { return documentId; } + + /** + * Build the full file name. + * + * @param def Default name if the file doesn't have one. + * @return File name + */ + public String getFullName(String def) { + if (Strings.isNullOrEmpty(name)) { + return def + "." + MimeTypeUtil.getFileExtension(mimeType); + } + return name; + } } diff --git a/docs-core/src/main/java/com/sismics/util/mime/MimeType.java b/docs-core/src/main/java/com/sismics/util/mime/MimeType.java index b844aced..8e77180a 100644 --- a/docs-core/src/main/java/com/sismics/util/mime/MimeType.java +++ b/docs-core/src/main/java/com/sismics/util/mime/MimeType.java @@ -20,5 +20,9 @@ public class MimeType { public static final String OFFICE_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + public static final String TEXT_PLAIN = "text/plain"; + + public static final String TEXT_CSV = "text/csv"; + public static final String DEFAULT = "application/octet-stream"; } diff --git a/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java b/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java index c784de77..124d03ab 100644 --- a/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java +++ b/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java @@ -21,32 +21,30 @@ public class MimeTypeUtil { * Try to guess the MIME type of a file by its magic number (header). * * @param is Stream to inspect + * @param name File name * @return MIME type - * @throws IOException + * @throws IOException e */ - public static String guessMimeType(InputStream is) throws IOException { + public static String guessMimeType(InputStream is, String name) throws IOException { byte[] headerBytes = new byte[64]; is.mark(headerBytes.length); - int readCount = is.read(headerBytes); + is.read(headerBytes); is.reset(); - - if (readCount <= 0) { - throw new IOException("Cannot read input file"); - } - - return guessMimeType(headerBytes); + return guessMimeType(headerBytes, name); } /** * Try to guess the MIME type of a file by its magic number (header). * * @param headerBytes File header (first bytes) + * @param name File name * @return MIME type - * @throws UnsupportedEncodingException + * @throws UnsupportedEncodingException e */ - public static String guessMimeType(byte[] headerBytes) throws UnsupportedEncodingException { + public static String guessMimeType(byte[] headerBytes, String name) throws UnsupportedEncodingException { String header = new String(headerBytes, "US-ASCII"); - + + // Detect by header bytes if (header.startsWith("PK")) { return MimeType.APPLICATION_ZIP; } else if (header.startsWith("GIF87a") || header.startsWith("GIF89a")) { @@ -59,7 +57,16 @@ public class MimeTypeUtil { } else if (headerBytes[0] == ((byte) 0x25) && headerBytes[1] == ((byte) 0x50) && headerBytes[2] == ((byte) 0x44) && headerBytes[3] == ((byte) 0x46)) { return MimeType.APPLICATION_PDF; } - + + // Detect by file extension + if (name != null) { + if (name.endsWith(".txt")) { + return MimeType.TEXT_PLAIN; + } else if (name.endsWith(".csv")) { + return MimeType.TEXT_CSV; + } + } + return MimeType.DEFAULT; } @@ -86,7 +93,7 @@ public class MimeTypeUtil { case MimeType.OFFICE_DOCUMENT: return "docx"; default: - return null; + return "bin"; } } diff --git a/docs-core/src/main/resources/config.properties b/docs-core/src/main/resources/config.properties index 592e6288..1d7a2e79 100644 --- a/docs-core/src/main/resources/config.properties +++ b/docs-core/src/main/resources/config.properties @@ -1 +1 @@ -db.version=10 \ No newline at end of file +db.version=11 \ No newline at end of file diff --git a/docs-core/src/main/resources/db/update/dbupdate-011-0.sql b/docs-core/src/main/resources/db/update/dbupdate-011-0.sql new file mode 100644 index 00000000..4c40f7e8 --- /dev/null +++ b/docs-core/src/main/resources/db/update/dbupdate-011-0.sql @@ -0,0 +1,2 @@ +alter table T_FILE add column FIL_NAME_C varchar(200); +update T_CONFIG set CFG_VALUE_C = '11' where CFG_ID_C = 'DB_VERSION'; diff --git a/docs-web/src/dev/resources/config.properties b/docs-web/src/dev/resources/config.properties index f935e8fa..25d63162 100644 --- a/docs-web/src/dev/resources/config.properties +++ b/docs-web/src/dev/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=10 \ No newline at end of file +db.version=11 \ No newline at end of file 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 8734fea7..eb872a75 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 @@ -124,9 +124,11 @@ public class FileResource extends BaseResource { InputStream fileInputStream = new ByteArrayInputStream(fileData); // Validate mime type + String name = fileBodyPart.getContentDisposition() != null ? + fileBodyPart.getContentDisposition().getFileName() : null; String mimeType; try { - mimeType = MimeTypeUtil.guessMimeType(fileInputStream); + mimeType = MimeTypeUtil.guessMimeType(fileInputStream, name); } catch (IOException e) { throw new ServerException("ErrorGuessMime", "Error guessing mime type", e); } @@ -150,6 +152,7 @@ public class FileResource extends BaseResource { File file = new File(); file.setOrder(order); file.setDocumentId(documentId); + file.setName(name); file.setMimeType(mimeType); file.setUserId(principal.getId()); String fileId = fileDao.create(file, principal.getId()); @@ -338,6 +341,7 @@ public class FileResource extends BaseResource { * @apiSuccess {Object[]} files List of files * @apiSuccess {String} files.id ID * @apiSuccess {String} files.mimetype MIME type + * @apiSuccess {String} files.name File name * @apiSuccess {String} files.document_id Document ID * @apiSuccess {String} files.create_date Create date (timestamp) * @apiSuccess {String} files.size File size (in bytes) @@ -376,6 +380,7 @@ public class FileResource extends BaseResource { try { files.add(Json.createObjectBuilder() .add("id", fileDb.getId()) + .add("name", JsonUtil.nullable(fileDb.getName())) .add("mimetype", fileDb.getMimeType()) .add("document_id", JsonUtil.nullable(fileDb.getDocumentId())) .add("create_date", fileDb.getCreateDate().getTime()) @@ -579,6 +584,7 @@ public class FileResource extends BaseResource { } return Response.ok(stream) + .header("Content-Disposition", "inline; filename=" + file.getFullName("data")) .header("Content-Type", mimeType) .header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24)) .build(); @@ -636,7 +642,7 @@ public class FileResource extends BaseResource { // Files are encrypted by the creator of them User user = userDao.getById(file.getUserId()); try (InputStream decryptedStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey())) { - ZipEntry zipEntry = new ZipEntry(index + "." + MimeTypeUtil.getFileExtension(file.getMimeType())); + ZipEntry zipEntry = new ZipEntry(file.getFullName(Integer.toString(index))); zipOutputStream.putNextEntry(zipEntry); ByteStreams.copy(decryptedStream, zipOutputStream); zipOutputStream.closeEntry(); 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 cc58fdee..c3b0ca74 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 @@ -46,6 +46,7 @@ +
diff --git a/docs-web/src/main/webapp/src/style/main.less b/docs-web/src/main/webapp/src/style/main.less index 84db7663..ee0ecd83 100644 --- a/docs-web/src/main/webapp/src/style/main.less +++ b/docs-web/src/main/webapp/src/style/main.less @@ -125,6 +125,13 @@ overflow: visible; } +// File name +.file-name { + word-wrap: break-word; + white-space: normal; + display: block; +} + // Fields bound to datepicker input[readonly][datepicker-popup] { cursor: pointer; diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties index f935e8fa..25d63162 100644 --- a/docs-web/src/prod/resources/config.properties +++ b/docs-web/src/prod/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=10 \ No newline at end of file +db.version=11 \ No newline at end of file diff --git a/docs-web/src/stress/resources/config.properties b/docs-web/src/stress/resources/config.properties index f935e8fa..25d63162 100644 --- a/docs-web/src/stress/resources/config.properties +++ b/docs-web/src/stress/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=10 \ No newline at end of file +db.version=11 \ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java index d507c74a..6af38820 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java @@ -34,7 +34,7 @@ public class TestDocumentResource extends BaseJerseyTest { /** * Test the document resource. * - * @throws Exception + * @throws Exception e */ @Test public void testDocumentResource() throws Exception { @@ -372,7 +372,7 @@ public class TestDocumentResource extends BaseJerseyTest { /** * Test ODT extraction. * - * @throws Exception + * @throws Exception e */ @Test public void testOdtExtraction() throws Exception { @@ -425,13 +425,13 @@ public class TestDocumentResource extends BaseJerseyTest { InputStream is = (InputStream) response.getEntity(); byte[] fileBytes = ByteStreams.toByteArray(is); Assert.assertTrue(fileBytes.length > 0); // Images rendered from PDF differ in size from OS to OS due to font issues - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); } /** * Test DOCX extraction. * - * @throws Exception + * @throws Exception e */ @Test public void testDocxExtraction() throws Exception { @@ -484,13 +484,13 @@ public class TestDocumentResource extends BaseJerseyTest { InputStream is = (InputStream) response.getEntity(); byte[] fileBytes = ByteStreams.toByteArray(is); Assert.assertTrue(fileBytes.length > 0); // Images rendered from PDF differ in size from OS to OS due to font issues - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); } /** * Test PDF extraction. * - * @throws Exception + * @throws Exception e */ @Test public void testPdfExtraction() throws Exception { @@ -543,6 +543,6 @@ public class TestDocumentResource extends BaseJerseyTest { InputStream is = (InputStream) response.getEntity(); byte[] fileBytes = ByteStreams.toByteArray(is); Assert.assertTrue(fileBytes.length > 0); // Images rendered from PDF differ in size from OS to OS due to font issues - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); } } \ No newline at end of file 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 71766de2..13f70144 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 @@ -94,7 +94,7 @@ public class TestFileResource extends BaseJerseyTest { .get(); InputStream is = (InputStream) response.getEntity(); byte[] fileBytes = ByteStreams.toByteArray(is); - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); Assert.assertTrue(fileBytes.length > 0); // Get the thumbnail data @@ -106,7 +106,7 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); is = (InputStream) response.getEntity(); fileBytes = ByteStreams.toByteArray(is); - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); Assert.assertTrue(fileBytes.length > 0); // Get the web data @@ -118,13 +118,13 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); is = (InputStream) response.getEntity(); fileBytes = ByteStreams.toByteArray(is); - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); Assert.assertTrue(fileBytes.length > 0); // Check that the files are not readable directly from FS Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file1Id); try (InputStream storedFileInputStream = new BufferedInputStream(Files.newInputStream(storedFile))) { - Assert.assertEquals(MimeType.DEFAULT, MimeTypeUtil.guessMimeType(storedFileInputStream)); + Assert.assertEquals(MimeType.DEFAULT, MimeTypeUtil.guessMimeType(storedFileInputStream, null)); } // Get all files from a document @@ -136,8 +136,10 @@ public class TestFileResource extends BaseJerseyTest { JsonArray files = json.getJsonArray("files"); Assert.assertEquals(2, files.size()); Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); + Assert.assertEquals("PIA00452.jpg", files.getJsonObject(0).getString("name")); 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")); // Reorder files target().path("/file/reorder").request() @@ -166,7 +168,7 @@ public class TestFileResource extends BaseJerseyTest { .get(); is = (InputStream) response.getEntity(); fileBytes = ByteStreams.toByteArray(is); - Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(fileBytes, null)); // Deletes a file json = target().path("/file/" + file1Id).request() @@ -278,7 +280,7 @@ public class TestFileResource extends BaseJerseyTest { .get(); InputStream is = (InputStream) response.getEntity(); byte[] fileBytes = ByteStreams.toByteArray(is); - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes)); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes, null)); Assert.assertEquals(163510, fileBytes.length); // Create a document