diff --git a/docs-core/pom.xml b/docs-core/pom.xml
index 3ef7d079..dcde9a75 100644
--- a/docs-core/pom.xml
+++ b/docs-core/pom.xml
@@ -113,8 +113,8 @@
- org.bitlet
- weupnp
+ org.imgscalr
+ imgscalr-lib
diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/ArticleDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/ArticleDao.java
deleted file mode 100644
index cc418ebf..00000000
--- a/docs-core/src/main/java/com/sismics/docs/core/dao/lucene/ArticleDao.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.sismics.docs.core.dao.lucene;
-
-
-/**
- * Lucene Article DAO.
- *
- * @author bgamard
- */
-public class ArticleDao {
-
-}
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 bde9ac2e..9e1d6916 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
@@ -16,4 +16,6 @@ public class MimeType {
public static final String IMAGE_GIF = "image/gif";
public static final String APPLICATION_ZIP = "application/zip";
+
+ public static final String APPLICATION_PDF = "application/pdf";
}
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 a8c1565b..1fd5f556 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
@@ -37,6 +37,8 @@ public class MimeTypeUtil {
return MimeType.IMAGE_PNG;
} else if (headerBytes[0] == ((byte) 0x00) && headerBytes[1] == ((byte) 0x00) && headerBytes[2] == ((byte) 0x01) && headerBytes[3] == ((byte) 0x00)) {
return MimeType.IMAGE_X_ICON;
+ } else if (headerBytes[0] == ((byte) 0x25) && headerBytes[1] == ((byte) 0x50) && headerBytes[2] == ((byte) 0x44) && headerBytes[3] == ((byte) 0x46)) {
+ return MimeType.APPLICATION_PDF;
}
return null;
diff --git a/docs-parent/pom.xml b/docs-parent/pom.xml
index fca3671a..92ac753f 100644
--- a/docs-parent/pom.xml
+++ b/docs-parent/pom.xml
@@ -34,7 +34,7 @@
r1564.2.01.0.5
- 0.1.2
+ 4.21.9.18-m4.1.0.Final
@@ -430,9 +430,9 @@
- org.bitlet
- weupnp
- ${org.bitlet.weupnp.version}
+ org.imgscalr
+ imgscalr-lib
+ ${org.imgscalr.imgscalr-lib.version}
diff --git a/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java b/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java
index 3a695c05..076ff60f 100644
--- a/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java
+++ b/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java
@@ -105,21 +105,24 @@ public class RequestContextFilter implements Filter {
} catch (Exception e) {
ThreadLocalContext.cleanup();
- log.error("An exception occured, rolling back current transaction", e);
+ // IOException are thrown if the client closes the connection before completion
+ if (!(e instanceof IOException)) {
+ log.error("An exception occured, rolling back current transaction", e);
- // If an unprocessed error comes up from the application layers (Jersey...), rollback the transaction (should not happen)
- if (em.isOpen()) {
- if (em.getTransaction() != null && em.getTransaction().isActive()) {
- em.getTransaction().rollback();
- }
-
- try {
- em.close();
- } catch (Exception ce) {
- log.error("Error closing entity manager", ce);
+ // If an unprocessed error comes up from the application layers (Jersey...), rollback the transaction (should not happen)
+ if (em.isOpen()) {
+ if (em.getTransaction() != null && em.getTransaction().isActive()) {
+ em.getTransaction().rollback();
+ }
+
+ try {
+ em.close();
+ } catch (Exception ce) {
+ log.error("Error closing entity manager", ce);
+ }
}
+ throw new ServletException(e);
}
- throw new ServletException(e);
}
ThreadLocalContext.cleanup();
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 02366947..805cc57f 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
@@ -1,9 +1,7 @@
package com.sismics.docs.rest.resource;
import java.io.BufferedInputStream;
-import java.io.FilenameFilter;
import java.io.InputStream;
-import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -33,6 +31,8 @@ import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.ValidationUtil;
+import com.sismics.util.FileUtil;
+import com.sismics.util.ImageUtil;
import com.sismics.util.mime.MimeTypeUtil;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;
@@ -129,8 +129,8 @@ public class FileResource extends BaseResource {
file.setMimeType(mimeType);
String fileId = fileDao.create(file);
- // Copy the incoming stream content into the storage directory
- Files.copy(is, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId));
+ // Save the file
+ FileUtil.save(is, file);
// Always return ok
JSONObject response = new JSONObject();
@@ -222,22 +222,36 @@ public class FileResource extends BaseResource {
@Path("{id: [a-z0-9\\-]+}/data")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response data(
- @PathParam("id") final String id) throws JSONException {
+ @PathParam("id") final String id,
+ @QueryParam("thumbnail") boolean thumbnail) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the file
- java.io.File storageDirectory = DirectoryUtil.getStorageDirectory();
- java.io.File[] matchingFiles = storageDirectory.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(java.io.File dir, String name) {
- return name.startsWith(id);
- }
- });
- final java.io.File storageFile = matchingFiles[0];
+ FileDao fileDao = new FileDao();
+ File file = null;
+ try {
+ file = fileDao.getFile(id);
+ } catch (NoResultException e) {
+ throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
+ }
- return Response.ok(storageFile)
+
+ // Get the stored file
+ java.io.File storedfile = null;
+ if (thumbnail) {
+ if (ImageUtil.isImage(file.getMimeType())) {
+ storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), id + "_thumb").toFile();
+ } else {
+ storedfile = new java.io.File(getClass().getResource("/image/file.png").getFile());
+ }
+ } else {
+ storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), id).toFile();
+ }
+
+ return Response.ok(storedfile)
+ .header("Content-Type", file.getMimeType())
.build();
}
}
diff --git a/docs-web/src/main/webapp/js/app.js b/docs-web/src/main/webapp/js/app.js
index bcb775d1..737c2cf4 100644
--- a/docs-web/src/main/webapp/js/app.js
+++ b/docs-web/src/main/webapp/js/app.js
@@ -67,14 +67,10 @@ var App = angular.module('docs', ['ui.state', 'ui.bootstrap', 'ui.keypress', 're
})
.state('document.view.file', {
url: '/file/:fileId',
- onEnter: function($stateParams, $state, $dialog) {
- $dialog.dialog({
- keyboard: true,
- templateUrl: 'partial/file.view.html',
+ views: {
+ 'file': {
controller: 'FileView'
- }).open().then(function(result) {
- $state.transitionTo('document.view', { id: $stateParams.id });
- });
+ }
}
})
.state('login', {
diff --git a/docs-web/src/main/webapp/js/controller/DocumentEdit.js b/docs-web/src/main/webapp/js/controller/DocumentEdit.js
index 873055d7..67df4045 100644
--- a/docs-web/src/main/webapp/js/controller/DocumentEdit.js
+++ b/docs-web/src/main/webapp/js/controller/DocumentEdit.js
@@ -3,7 +3,7 @@
/**
* Document edition controller.
*/
-App.controller('DocumentEdit', function($scope, $http, $state, $stateParams, Restangular) {
+App.controller('DocumentEdit', function($scope, $q, $http, $state, $stateParams, Restangular) {
/**
* Returns true if in edit mode (false in add mode).
*/
@@ -28,37 +28,55 @@ App.controller('DocumentEdit', function($scope, $http, $state, $stateParams, Res
if ($scope.isEdit()) {
promise = Restangular
- .one('document', $stateParams.id)
- .post('', $scope.document);
- promise.then(function(data) {
- $scope.loadDocuments();
- $state.transitionTo('document.view', { id: $stateParams.id });
- })
+ .one('document', $stateParams.id)
+ .post('', $scope.document);
} else {
promise = Restangular
- .one('document')
- .put($scope.document);
- promise.then(function(data) {
- $scope.document = {};
- $scope.loadDocuments();
- });
+ .one('document')
+ .put($scope.document);
}
// Upload files after edition
- // TODO Handle file upload progression and errors
promise.then(function(data) {
- _.each($scope.files, function(file) {
+ var promises = [];
+ $scope.fileProgress = 0;
+
+ _.each($scope.newFiles, function(file) {
+ // Build the payload
var formData = new FormData();
formData.append('id', data.id);
formData.append('file', file);
- $.ajax({
- url: 'api/file',
- type: 'PUT',
- data: formData,
- processData: false,
- contentType: false
+
+ // Send the file
+ var promiseFile = $http.put('api/file',
+ formData, {
+ headers: { 'Content-Type': false },
+ transformRequest: function(data) { return data; }
});
+
+ // TODO Handle progression when $q.notify will be released
+
+ promiseFile.then(function() {
+ $scope.fileProgress += 100 / _.size($scope.newFiles);
+ });
+
+ // Store the promise for later
+ promises.push(promiseFile);
});
+
+ // When all files upload are over, move on
+ var promiseAll = $q.all(promises);
+ if ($scope.isEdit()) {
+ promiseAll.then(function(data) {
+ $scope.loadDocuments();
+ $state.transitionTo('document.view', { id: $stateParams.id });
+ });
+ } else {
+ promiseAll.then(function(data) {
+ $scope.document = {};
+ $scope.loadDocuments();
+ });
+ }
});
};
diff --git a/docs-web/src/main/webapp/js/controller/FileView.js b/docs-web/src/main/webapp/js/controller/FileView.js
index cd2e4d20..e61e72fc 100644
--- a/docs-web/src/main/webapp/js/controller/FileView.js
+++ b/docs-web/src/main/webapp/js/controller/FileView.js
@@ -3,34 +3,62 @@
/**
* File view controller.
*/
-App.controller('FileView', function($rootScope, $state, $scope, $stateParams) {
- $scope.id = $stateParams.fileId;
-
- /**
- * Navigate to the next file.
- */
- $scope.nextFile = function() {
- _.each($rootScope.files, function(value, key, list) {
- if (value.id == $scope.id) {
- var next = $rootScope.files[key + 1];
- if (next) {
- $state.transitionTo('document.view.file', { id: $stateParams.id, fileId: next.id });
+App.controller('FileView', function($dialog, $state, $stateParams) {
+ var dialog = $dialog.dialog({
+ keyboard: true,
+ templateUrl: 'partial/file.view.html',
+ controller: function($rootScope, $scope, $state, $stateParams) {
+ $scope.id = $stateParams.fileId;
+
+ // Search current file
+ _.each($rootScope.files, function(value, key, list) {
+ if (value.id == $scope.id) {
+ $scope.file = value;
}
- }
- });
- };
+ });
+
+ /**
+ * Navigate to the next file.
+ */
+ $scope.nextFile = function() {
+ _.each($rootScope.files, function(value, key, list) {
+ if (value.id == $scope.id) {
+ var next = $rootScope.files[key + 1];
+ if (next) {
+ dialog.close({});
+ $state.transitionTo('document.view.file', { id: $stateParams.id, fileId: next.id });
+ }
+ }
+ });
+ };
+
+ /**
+ * Navigate to the previous file.
+ */
+ $scope.previousFile = function() {
+ _.each($rootScope.files, function(value, key, list) {
+ if (value.id == $scope.id) {
+ var previous = $rootScope.files[key - 1];
+ if (previous) {
+ dialog.close({});
+ $state.transitionTo('document.view.file', { id: $stateParams.id, fileId: previous.id });
+ }
+ }
+ });
+ };
+
+ /**
+ * Open the file in a new window.
+ */
+ $scope.openFile = function() {
+ window.open('api/file/' + $scope.id + '/data');
+ };
+ }
+ });
- /**
- * Navigate to the previous file.
- */
- $scope.previousFile = function() {
- _.each($rootScope.files, function(value, key, list) {
- if (value.id == $scope.id) {
- var previous = $rootScope.files[key - 1];
- if (previous) {
- $state.transitionTo('document.view.file', { id: $stateParams.id, fileId: previous.id });
- }
- }
- });
- };
+ dialog.open().then(function(result) {
+ if (result == null) {
+ $state.transitionTo('document.view', { id: $stateParams.id });
+ }
+ });
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/partial/document.edit.html b/docs-web/src/main/webapp/partial/document.edit.html
index aa618cac..04684a35 100644
--- a/docs-web/src/main/webapp/partial/document.edit.html
+++ b/docs-web/src/main/webapp/partial/document.edit.html
@@ -14,11 +14,15 @@
-
+
-
\ No newline at end of file
+
+
+
+
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/partial/document.view.html b/docs-web/src/main/webapp/partial/document.view.html
index 4713eee8..4e0ae103 100644
--- a/docs-web/src/main/webapp/partial/document.view.html
+++ b/docs-web/src/main/webapp/partial/document.view.html
@@ -14,7 +14,7 @@
+
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/partial/file.view.html b/docs-web/src/main/webapp/partial/file.view.html
index 14587db5..a4338c57 100644
--- a/docs-web/src/main/webapp/partial/file.view.html
+++ b/docs-web/src/main/webapp/partial/file.view.html
@@ -3,5 +3,15 @@
+
+
+
+
+
+
+
+
+
+
+
-
\ 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 fec94c5b..604484c4 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
@@ -76,16 +76,29 @@ public class TestFileResource extends BaseJerseyTest {
// Get the file data
fileResource = resource().path("/file/" + file1Id + "/data");
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
- response = fileResource.get(ClientResponse.class);
+ MultivaluedMapImpl getParams = new MultivaluedMapImpl();
+ getParams.putSingle("thumbnail", false);
+ response = fileResource.queryParams(getParams).get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
InputStream is = response.getEntityInputStream();
byte[] fileBytes = ByteStreams.toByteArray(is);
Assert.assertEquals(163510, fileBytes.length);
+ // Get the thumbnail data
+ fileResource = resource().path("/file/" + file1Id + "/data");
+ fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
+ getParams = new MultivaluedMapImpl();
+ getParams.putSingle("thumbnail", true);
+ response = fileResource.queryParams(getParams).get(ClientResponse.class);
+ Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
+ is = response.getEntityInputStream();
+ fileBytes = ByteStreams.toByteArray(is);
+ Assert.assertEquals(41935, fileBytes.length);
+
// Get all files from a document
fileResource = resource().path("/file/list");
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
- MultivaluedMapImpl getParams = new MultivaluedMapImpl();
+ getParams = new MultivaluedMapImpl();
getParams.putSingle("id", document1Id);
response = fileResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);