Closes #119: Keep and display original file name

This commit is contained in:
jendib 2016-12-07 01:28:52 +01:00
parent 4f7fcbfdf0
commit bb3faca533
13 changed files with 94 additions and 34 deletions

View File

@ -10,6 +10,8 @@ import javax.persistence.Table;
import javax.persistence.Transient; import javax.persistence.Transient;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.sismics.util.mime.MimeTypeUtil;
/** /**
* File entity. * File entity.
@ -38,12 +40,18 @@ public class File implements Loggable {
@Column(name = "FIL_IDUSER_C", length = 36, nullable = false) @Column(name = "FIL_IDUSER_C", length = 36, nullable = false)
private String userId; private String userId;
/**
* Name.
*/
@Column(name = "FIL_NAME_C", length = 200)
private String name;
/** /**
* MIME type. * MIME type.
*/ */
@Column(name = "FIL_MIMETYPE_C", length = 100) @Column(name = "FIL_MIMETYPE_C", length = 100)
private String mimeType; private String mimeType;
/** /**
* OCR-ized content. * OCR-ized content.
*/ */
@ -92,6 +100,15 @@ public class File implements Loggable {
this.documentId = documentId; this.documentId = documentId;
} }
public String getName() {
return name;
}
public File setName(String name) {
this.name = name;
return this;
}
public String getMimeType() { public String getMimeType() {
return mimeType; return mimeType;
} }
@ -153,6 +170,7 @@ public class File implements Loggable {
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
.add("id", id) .add("id", id)
.add("name", name)
.toString(); .toString();
} }
@ -160,4 +178,17 @@ public class File implements Loggable {
public String toMessage() { public String toMessage() {
return documentId; 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;
}
} }

View File

@ -20,5 +20,9 @@ public class MimeType {
public static final String OFFICE_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; 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"; public static final String DEFAULT = "application/octet-stream";
} }

View File

@ -21,32 +21,30 @@ public class MimeTypeUtil {
* Try to guess the MIME type of a file by its magic number (header). * Try to guess the MIME type of a file by its magic number (header).
* *
* @param is Stream to inspect * @param is Stream to inspect
* @param name File name
* @return MIME type * @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]; byte[] headerBytes = new byte[64];
is.mark(headerBytes.length); is.mark(headerBytes.length);
int readCount = is.read(headerBytes); is.read(headerBytes);
is.reset(); is.reset();
return guessMimeType(headerBytes, name);
if (readCount <= 0) {
throw new IOException("Cannot read input file");
}
return guessMimeType(headerBytes);
} }
/** /**
* Try to guess the MIME type of a file by its magic number (header). * Try to guess the MIME type of a file by its magic number (header).
* *
* @param headerBytes File header (first bytes) * @param headerBytes File header (first bytes)
* @param name File name
* @return MIME type * @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"); String header = new String(headerBytes, "US-ASCII");
// Detect by header bytes
if (header.startsWith("PK")) { if (header.startsWith("PK")) {
return MimeType.APPLICATION_ZIP; return MimeType.APPLICATION_ZIP;
} else if (header.startsWith("GIF87a") || header.startsWith("GIF89a")) { } 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)) { } else if (headerBytes[0] == ((byte) 0x25) && headerBytes[1] == ((byte) 0x50) && headerBytes[2] == ((byte) 0x44) && headerBytes[3] == ((byte) 0x46)) {
return MimeType.APPLICATION_PDF; 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; return MimeType.DEFAULT;
} }
@ -86,7 +93,7 @@ public class MimeTypeUtil {
case MimeType.OFFICE_DOCUMENT: case MimeType.OFFICE_DOCUMENT:
return "docx"; return "docx";
default: default:
return null; return "bin";
} }
} }

View File

@ -1 +1 @@
db.version=10 db.version=11

View File

@ -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';

View File

@ -1,3 +1,3 @@
api.current_version=${project.version} api.current_version=${project.version}
api.min_version=1.0 api.min_version=1.0
db.version=10 db.version=11

View File

@ -124,9 +124,11 @@ public class FileResource extends BaseResource {
InputStream fileInputStream = new ByteArrayInputStream(fileData); InputStream fileInputStream = new ByteArrayInputStream(fileData);
// Validate mime type // Validate mime type
String name = fileBodyPart.getContentDisposition() != null ?
fileBodyPart.getContentDisposition().getFileName() : null;
String mimeType; String mimeType;
try { try {
mimeType = MimeTypeUtil.guessMimeType(fileInputStream); mimeType = MimeTypeUtil.guessMimeType(fileInputStream, name);
} catch (IOException e) { } catch (IOException e) {
throw new ServerException("ErrorGuessMime", "Error guessing mime type", e); throw new ServerException("ErrorGuessMime", "Error guessing mime type", e);
} }
@ -150,6 +152,7 @@ public class FileResource extends BaseResource {
File file = new File(); File file = new File();
file.setOrder(order); file.setOrder(order);
file.setDocumentId(documentId); file.setDocumentId(documentId);
file.setName(name);
file.setMimeType(mimeType); file.setMimeType(mimeType);
file.setUserId(principal.getId()); file.setUserId(principal.getId());
String fileId = fileDao.create(file, 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 {Object[]} files List of files
* @apiSuccess {String} files.id ID * @apiSuccess {String} files.id ID
* @apiSuccess {String} files.mimetype MIME type * @apiSuccess {String} files.mimetype MIME type
* @apiSuccess {String} files.name File name
* @apiSuccess {String} files.document_id Document ID * @apiSuccess {String} files.document_id Document ID
* @apiSuccess {String} files.create_date Create date (timestamp) * @apiSuccess {String} files.create_date Create date (timestamp)
* @apiSuccess {String} files.size File size (in bytes) * @apiSuccess {String} files.size File size (in bytes)
@ -376,6 +380,7 @@ public class FileResource extends BaseResource {
try { try {
files.add(Json.createObjectBuilder() files.add(Json.createObjectBuilder()
.add("id", fileDb.getId()) .add("id", fileDb.getId())
.add("name", JsonUtil.nullable(fileDb.getName()))
.add("mimetype", fileDb.getMimeType()) .add("mimetype", fileDb.getMimeType())
.add("document_id", JsonUtil.nullable(fileDb.getDocumentId())) .add("document_id", JsonUtil.nullable(fileDb.getDocumentId()))
.add("create_date", fileDb.getCreateDate().getTime()) .add("create_date", fileDb.getCreateDate().getTime())
@ -579,6 +584,7 @@ public class FileResource extends BaseResource {
} }
return Response.ok(stream) return Response.ok(stream)
.header("Content-Disposition", "inline; filename=" + file.getFullName("data"))
.header("Content-Type", mimeType) .header("Content-Type", mimeType)
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24)) .header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24))
.build(); .build();
@ -636,7 +642,7 @@ public class FileResource extends BaseResource {
// Files are encrypted by the creator of them // Files are encrypted by the creator of them
User user = userDao.getById(file.getUserId()); User user = userDao.getById(file.getUserId());
try (InputStream decryptedStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey())) { 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); zipOutputStream.putNextEntry(zipEntry);
ByteStreams.copy(decryptedStream, zipOutputStream); ByteStreams.copy(decryptedStream, zipOutputStream);
zipOutputStream.closeEntry(); zipOutputStream.closeEntry();

View File

@ -46,6 +46,7 @@
<a ng-click="openFile(file)"> <a ng-click="openFile(file)">
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }} | {{ file.size | filesize }}" tooltip-placement="top" /> <img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }} | {{ file.size | filesize }}" tooltip-placement="top" />
</a> </a>
<div class="caption btn btn-link file-name" ng-click="openFile(file)">{{ file.name }}</div>
<div class="caption" ng-show="document.writable"> <div class="caption" ng-show="document.writable">
<div class="pull-left"> <div class="pull-left">
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div> <div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>

View File

@ -125,6 +125,13 @@
overflow: visible; overflow: visible;
} }
// File name
.file-name {
word-wrap: break-word;
white-space: normal;
display: block;
}
// Fields bound to datepicker // Fields bound to datepicker
input[readonly][datepicker-popup] { input[readonly][datepicker-popup] {
cursor: pointer; cursor: pointer;

View File

@ -1,3 +1,3 @@
api.current_version=${project.version} api.current_version=${project.version}
api.min_version=1.0 api.min_version=1.0
db.version=10 db.version=11

View File

@ -1,3 +1,3 @@
api.current_version=${project.version} api.current_version=${project.version}
api.min_version=1.0 api.min_version=1.0
db.version=10 db.version=11

View File

@ -34,7 +34,7 @@ public class TestDocumentResource extends BaseJerseyTest {
/** /**
* Test the document resource. * Test the document resource.
* *
* @throws Exception * @throws Exception e
*/ */
@Test @Test
public void testDocumentResource() throws Exception { public void testDocumentResource() throws Exception {
@ -372,7 +372,7 @@ public class TestDocumentResource extends BaseJerseyTest {
/** /**
* Test ODT extraction. * Test ODT extraction.
* *
* @throws Exception * @throws Exception e
*/ */
@Test @Test
public void testOdtExtraction() throws Exception { public void testOdtExtraction() throws Exception {
@ -425,13 +425,13 @@ public class TestDocumentResource extends BaseJerseyTest {
InputStream is = (InputStream) response.getEntity(); InputStream is = (InputStream) response.getEntity();
byte[] fileBytes = ByteStreams.toByteArray(is); 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.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. * Test DOCX extraction.
* *
* @throws Exception * @throws Exception e
*/ */
@Test @Test
public void testDocxExtraction() throws Exception { public void testDocxExtraction() throws Exception {
@ -484,13 +484,13 @@ public class TestDocumentResource extends BaseJerseyTest {
InputStream is = (InputStream) response.getEntity(); InputStream is = (InputStream) response.getEntity();
byte[] fileBytes = ByteStreams.toByteArray(is); 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.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. * Test PDF extraction.
* *
* @throws Exception * @throws Exception e
*/ */
@Test @Test
public void testPdfExtraction() throws Exception { public void testPdfExtraction() throws Exception {
@ -543,6 +543,6 @@ public class TestDocumentResource extends BaseJerseyTest {
InputStream is = (InputStream) response.getEntity(); InputStream is = (InputStream) response.getEntity();
byte[] fileBytes = ByteStreams.toByteArray(is); 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.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));
} }
} }

View File

@ -94,7 +94,7 @@ public class TestFileResource extends BaseJerseyTest {
.get(); .get();
InputStream is = (InputStream) response.getEntity(); InputStream is = (InputStream) response.getEntity();
byte[] fileBytes = ByteStreams.toByteArray(is); 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); Assert.assertTrue(fileBytes.length > 0);
// Get the thumbnail data // Get the thumbnail data
@ -106,7 +106,7 @@ public class TestFileResource extends BaseJerseyTest {
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
is = (InputStream) response.getEntity(); is = (InputStream) response.getEntity();
fileBytes = ByteStreams.toByteArray(is); 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); Assert.assertTrue(fileBytes.length > 0);
// Get the web data // Get the web data
@ -118,13 +118,13 @@ public class TestFileResource extends BaseJerseyTest {
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
is = (InputStream) response.getEntity(); is = (InputStream) response.getEntity();
fileBytes = ByteStreams.toByteArray(is); 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); Assert.assertTrue(fileBytes.length > 0);
// Check that the files are not readable directly from FS // Check that the files are not readable directly from FS
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file1Id); Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file1Id);
try (InputStream storedFileInputStream = new BufferedInputStream(Files.newInputStream(storedFile))) { 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 // Get all files from a document
@ -136,8 +136,10 @@ public class TestFileResource extends BaseJerseyTest {
JsonArray files = json.getJsonArray("files"); JsonArray files = json.getJsonArray("files");
Assert.assertEquals(2, files.size()); Assert.assertEquals(2, files.size());
Assert.assertEquals(file1Id, files.getJsonObject(0).getString("id")); 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(163510L, files.getJsonObject(0).getJsonNumber("size").longValue());
Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id")); Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id"));
Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name"));
// Reorder files // Reorder files
target().path("/file/reorder").request() target().path("/file/reorder").request()
@ -166,7 +168,7 @@ public class TestFileResource extends BaseJerseyTest {
.get(); .get();
is = (InputStream) response.getEntity(); is = (InputStream) response.getEntity();
fileBytes = ByteStreams.toByteArray(is); fileBytes = ByteStreams.toByteArray(is);
Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(fileBytes)); Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(fileBytes, null));
// Deletes a file // Deletes a file
json = target().path("/file/" + file1Id).request() json = target().path("/file/" + file1Id).request()
@ -278,7 +280,7 @@ public class TestFileResource extends BaseJerseyTest {
.get(); .get();
InputStream is = (InputStream) response.getEntity(); InputStream is = (InputStream) response.getEntity();
byte[] fileBytes = ByteStreams.toByteArray(is); 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); Assert.assertEquals(163510, fileBytes.length);
// Create a document // Create a document