diff --git a/.travis.yml b/.travis.yml index 51c40faf..6415cfd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,20 @@ before_install: - sudo apt-get -qq update - sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn - sudo apt-get -y -q install haveged && sudo service haveged start +after_success: + - mvn -Pprod -DskipTests clean install + - docker login -u $DOCKER_USER -p $DOCKER_PASS + - export REPO=sismics/docs + - export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` + - docker build -f Dockerfile -t $REPO:$COMMIT . + - docker tag $REPO:$COMMIT $REPO:$TAG + - docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER + - docker push $REPO env: global: - - TESSDATA_PREFIX=/usr/share/tesseract-ocr - - LC_NUMERIC=C \ No newline at end of file + - TESSDATA_PREFIX=/usr/share/tesseract-ocr + - LC_NUMERIC=C + - secure: LRGpjWORb0qy6VuypZjTAfA8uRHlFUMTwb77cenS9PPRBxuSnctC531asS9Xg3DqC5nsRxBBprgfCKotn5S8nBSD1ceHh84NASyzLSBft3xSMbg7f/2i7MQ+pGVwLncusBU6E/drnMFwZBleo+9M8Tf96axY5zuUp90MUTpSgt0= + - secure: bCDDR6+I7PmSkuTYZv1HF/z98ANX/SFEESUCqxVmV5Gs0zFC0vQXaPJQ2xaJNRop1HZBFMZLeMMPleb0iOs985smpvK2F6Rbop9Tu+Vyo0uKqv9tbZ7F8Nfgnv9suHKZlL84FNeUQZJX6vsFIYPEJ/r7K5P/M0PdUy++fEwxEhU= + - secure: ewXnzbkgCIHpDWtaWGMa1OYZJ/ki99zcIl4jcDPIC0eB3njX/WgfcC6i0Ke9mLqDqwXarWJ6helm22sNh+xtQiz6isfBtBX+novfRt9AANrBe3koCMUemMDy7oh5VflBaFNP0DVb8LSCnwf6dx6ZB5E9EB8knvk40quc/cXpGjY= + - COMMIT=${TRAVIS_COMMIT::8} diff --git a/README.md b/README.md index d2f9eb4f..0228e56a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,16 @@ Download -------- The latest release is downloadable here: in WAR format. -You will need a Java webapp server to run it, like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/) +You will need a Java webapp server to run it, like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/). +The default admin password is "admin". Don't forget to change it before going to production. + +Install with Docker +------------------- + +From a Docker host, run this command to download and install Sismics Docs. The server will run on . +The default admin password is "admin". Don't forget to change it before going to production. + + docker run --rm --name sismics_docs_latest -d -p 8100:8080 -v sismics_docs_latest:/data sismics/docs:latest How to build Docs from the sources ---------------------------------- diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java index 526371e7..52a02c44 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java @@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit; import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.EventBus; +import com.lowagie.text.FontFactory; import com.sismics.docs.core.constant.ConfigType; import com.sismics.docs.core.dao.jpa.ConfigDao; import com.sismics.docs.core.listener.async.DocumentCreatedAsyncListener; @@ -20,6 +21,7 @@ import com.sismics.docs.core.listener.async.RebuildIndexAsyncListener; import com.sismics.docs.core.listener.sync.DeadEventListener; import com.sismics.docs.core.model.jpa.Config; import com.sismics.docs.core.service.IndexingService; +import com.sismics.docs.core.util.PdfUtil; import com.sismics.util.EnvironmentUtil; /** @@ -58,11 +60,14 @@ public class AppContext { */ private AppContext() { resetEventBus(); - + + // Start indexing service ConfigDao configDao = new ConfigDao(); Config luceneStorageConfig = configDao.getById(ConfigType.LUCENE_DIRECTORY_STORAGE); indexingService = new IndexingService(luceneStorageConfig != null ? luceneStorageConfig.getValue() : null); indexingService.startAsync(); + + PdfUtil.registerFonts(); } /** diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java index 2a005fce..515a0d3f 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java @@ -1,26 +1,24 @@ package com.sismics.docs.core.util; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.imageio.ImageIO; - +import com.sismics.docs.core.model.jpa.File; +import com.sismics.tess4j.Tesseract; +import com.sismics.util.ImageUtil; import org.imgscalr.Scalr; import org.imgscalr.Scalr.Method; import org.imgscalr.Scalr.Mode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.sismics.docs.core.model.jpa.File; -import com.sismics.tess4j.Tesseract; -import com.sismics.util.ImageUtil; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; /** * File entity utilities. @@ -64,11 +62,12 @@ public class FileUtil { private static String ocrFile(InputStream inputStream, String language) { Tesseract instance = Tesseract.getInstance(); String content = null; - BufferedImage image = null; + BufferedImage image; try { image = ImageIO.read(inputStream); } catch (IOException e) { log.error("Error reading the image", e); + return null; } // Upscale and grayscale the image @@ -92,10 +91,9 @@ public class FileUtil { * Save a file on the storage filesystem. * * @param inputStream Unencrypted input stream - * @param pdf + * @param pdfInputStream PDF input stream * @param file File to save * @param privateKey Private key used for encryption - * @throws Exception */ public static void save(InputStream inputStream, InputStream pdfInputStream, File file, String privateKey) throws Exception { Cipher cipher = EncryptionUtil.getEncryptionCipher(privateKey); @@ -114,9 +112,8 @@ public class FileUtil { * @param inputStream Unencrypted input stream * @param pdfInputStream Unencrypted PDF input stream * @param cipher Cipher to use for encryption - * @throws Exception */ - public static void saveVariations(File file, InputStream inputStream, InputStream pdfInputStream, Cipher cipher) throws Exception { + private static void saveVariations(File file, InputStream inputStream, InputStream pdfInputStream, Cipher cipher) throws Exception { BufferedImage image = null; if (ImageUtil.isImage(file.getMimeType())) { image = ImageIO.read(inputStream); @@ -151,7 +148,6 @@ public class FileUtil { * Remove a file from the storage filesystem. * * @param file File to delete - * @throws IOException */ public static void delete(File file) throws IOException { Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId()); diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/PdfUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/PdfUtil.java index f1268545..38b2b074 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/PdfUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/PdfUtil.java @@ -1,18 +1,18 @@ package com.sismics.docs.core.util; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; - -import javax.imageio.ImageIO; - +import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import com.google.common.io.Closer; +import com.google.common.io.Resources; +import com.lowagie.text.*; +import com.lowagie.text.pdf.PdfWriter; +import com.sismics.docs.core.dao.jpa.dto.DocumentDto; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.util.pdf.PdfPage; +import com.sismics.util.ImageUtil; +import com.sismics.util.mime.MimeType; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.multipdf.PDFMergerUtility; import org.apache.pdfbox.pdmodel.PDDocument; @@ -32,13 +32,15 @@ import org.odftoolkit.odfdom.doc.OdfTextDocument; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Strings; -import com.google.common.io.Closer; -import com.sismics.docs.core.dao.jpa.dto.DocumentDto; -import com.sismics.docs.core.model.jpa.File; -import com.sismics.docs.core.util.pdf.PdfPage; -import com.sismics.util.ImageUtil; -import com.sismics.util.mime.MimeType; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; /** * PDF utilities. @@ -86,7 +88,6 @@ public class PdfUtil { * @param inputStream InputStream * @param reset Reset the stream after usage * @return PDF input stream - * @throws Exception */ public static InputStream convertToPdf(File file, InputStream inputStream, boolean reset) throws Exception { if (file.getMimeType().equals(MimeType.APPLICATION_PDF)) { @@ -101,18 +102,48 @@ public class PdfUtil { if (file.getMimeType().equals(MimeType.OPEN_DOCUMENT_TEXT)) { return convertOpenDocumentText(inputStream, reset); } - + + if (file.getMimeType().equals(MimeType.TEXT_PLAIN) || file.getMimeType().equals(MimeType.TEXT_CSV)) { + return convertTextPlain(inputStream, reset); + } + // PDF conversion not necessary/possible return null; } - + + /** + * Convert a text plain document to PDF. + * + * @param inputStream Unecnrypted input stream + * @param reset Reset the stream after usage + * @return PDF input stream + */ + private static InputStream convertTextPlain(InputStream inputStream, boolean reset) throws Exception { + Document output = new Document(PageSize.A4, 40, 40, 40, 40); + ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); + PdfWriter.getInstance(output, pdfOutputStream); + + output.open(); + String content = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); + Font font = FontFactory.getFont("LiberationMono-Regular"); + Paragraph paragraph = new Paragraph(content, font); + paragraph.setAlignment(Element.ALIGN_LEFT); + output.add(paragraph); + output.close(); + + if (reset) { + inputStream.reset(); + } + + return new ByteArrayInputStream(pdfOutputStream.toByteArray()); + } + /** * Convert an open document text file to PDF. * * @param inputStream Unencrypted input stream * @param reset Reset the stream after usage * @return PDF input stream - * @throws Exception */ private static InputStream convertOpenDocumentText(InputStream inputStream, boolean reset) throws Exception { ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); @@ -131,7 +162,6 @@ public class PdfUtil { * @param inputStream Unencrypted input stream * @param reset Reset the stream after usage * @return PDF input stream - * @throws Exception */ private static InputStream convertOfficeDocument(InputStream inputStream, boolean reset) throws Exception { ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); @@ -153,7 +183,6 @@ public class PdfUtil { * @param metadata Add a page with metadata * @param margin Margins in millimeters * @return PDF input stream - * @throws IOException */ public static InputStream convertToPdf(DocumentDto documentDto, List fileList, boolean fitImageToPage, boolean metadata, int margin) throws Exception { @@ -282,7 +311,6 @@ public class PdfUtil { * * @param inputStream PDF document * @return Render of the first page - * @throws IOException */ public static BufferedImage renderFirstPage(InputStream inputStream) throws IOException { try (PDDocument pdfDocument = PDDocument.load(inputStream)) { @@ -290,4 +318,20 @@ public class PdfUtil { return renderer.renderImage(0); } } + + /** + * Register fonts. + */ + public static void registerFonts() { + URL url = Resources.getResource("fonts/LiberationMono-Regular.ttf"); + try (InputStream is = url.openStream()) { + Path file = Files.createTempFile("sismics_docs_font_mono", ".ttf"); + try (OutputStream os = Files.newOutputStream(file)) { + ByteStreams.copy(is, os); + } + FontFactory.register(file.toAbsolutePath().toString(), "LiberationMono-Regular"); + } catch (IOException e) { + log.error("Error loading font", e); + } + } } 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 124d03ab..740a6c7c 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 @@ -78,22 +78,26 @@ public class MimeTypeUtil { */ public static String getFileExtension(String mimeType) { switch (mimeType) { - case MimeType.APPLICATION_ZIP: - return "zip"; - case MimeType.IMAGE_GIF: - return "gif"; - case MimeType.IMAGE_JPEG: - return "jpg"; - case MimeType.IMAGE_PNG: - return "png"; - case MimeType.APPLICATION_PDF: - return "pdf"; - case MimeType.OPEN_DOCUMENT_TEXT: - return "odt"; - case MimeType.OFFICE_DOCUMENT: - return "docx"; - default: - return "bin"; + case MimeType.APPLICATION_ZIP: + return "zip"; + case MimeType.IMAGE_GIF: + return "gif"; + case MimeType.IMAGE_JPEG: + return "jpg"; + case MimeType.IMAGE_PNG: + return "png"; + case MimeType.APPLICATION_PDF: + return "pdf"; + case MimeType.OPEN_DOCUMENT_TEXT: + return "odt"; + case MimeType.OFFICE_DOCUMENT: + return "docx"; + case MimeType.TEXT_PLAIN: + return "txt"; + case MimeType.TEXT_CSV: + return "csv"; + default: + return "bin"; } } diff --git a/docs-core/src/main/resources/fonts/LiberationMono-Regular.ttf b/docs-core/src/main/resources/fonts/LiberationMono-Regular.ttf new file mode 100644 index 00000000..1a39bc75 Binary files /dev/null and b/docs-core/src/main/resources/fonts/LiberationMono-Regular.ttf differ 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 58eb6bc8..b106a4e1 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 @@ -13,6 +13,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; +import com.lowagie.text.FontFactory; import org.apache.log4j.Level; import org.apache.log4j.PatternLayout; import org.apache.log4j.RollingFileAppender; @@ -71,6 +72,9 @@ public class RequestContextFilter implements Filter { AppContext.getInstance(); } }); + + // Register fonts + FontFactory.registerDirectories(); } @Override diff --git a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentView.js b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentView.js index 1c1ce194..41281950 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentView.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentView.js @@ -23,7 +23,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta */ $scope.comment = ''; $scope.addComment = function() { - if ($scope.comment.length == 0) { + if ($scope.comment.length === 0) { return; } @@ -40,8 +40,19 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta * Delete a comment. */ $scope.deleteComment = function(comment) { - Restangular.one('comment', comment.id).remove().then(function() { - $scope.comments.splice($scope.comments.indexOf(comment), 1); + var title = 'Delete comment'; + var msg = 'Do you really want to delete this comment?'; + var btns = [ + {result: 'cancel', label: 'Cancel'}, + {result: 'ok', label: 'OK', cssClass: 'btn-primary'} + ]; + + $dialog.messageBox(title, msg, btns, function (result) { + if (result === 'ok') { + Restangular.one('comment', comment.id).remove().then(function() { + $scope.comments.splice($scope.comments.indexOf(comment), 1); + }); + } }); }; @@ -57,7 +68,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta ]; $dialog.messageBox(title, msg, btns, function (result) { - if (result == 'ok') { + if (result === 'ok') { Restangular.one('document', document.id).remove().then(function() { $scope.loadDocuments(); $state.go('document.default'); @@ -74,7 +85,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta templateUrl: 'partial/docs/document.share.html', controller: 'DocumentModalShare' }).result.then(function (name) { - if (name == null) { + if (name === null) { return; } @@ -100,18 +111,19 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta var title = 'Shared document'; var msg = 'You can share this document by giving this link. ' + 'Note that everyone having this link can see the document.
' + - ''; + ''; var btns = [ {result: 'unshare', label: 'Unshare', cssClass: 'btn-danger'}, {result: 'close', label: 'Close'} ]; $dialog.messageBox(title, msg, btns, function (result) { - if (result == 'unshare') { + if (result === 'unshare') { // Unshare this document and update the local shares Restangular.one('share', share.id).remove().then(function () { $scope.document.acls = _.reject($scope.document.acls, function(s) { - return share.id == s.id; + return share.id === s.id; }); }); } 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 c15b146b..dd0a6aca 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 @@ -10,9 +10,9 @@ -
+
- +
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.edit.html b/docs-web/src/main/webapp/src/partial/docs/document.edit.html index 68fca1e4..30e9fc9b 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.edit.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.edit.html @@ -129,7 +129,7 @@
- +
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.html b/docs-web/src/main/webapp/src/partial/docs/document.view.html index 3e94f557..017c0b1e 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.html @@ -40,9 +40,10 @@