From c8dc5802bc8342b3c00e9a22cef1d0a77fa7b02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Thu, 13 Feb 2020 10:30:28 +0100 Subject: [PATCH] GH-46 Adds Webdav support --- viewer/viewer-awt/build.gradle | 12 +- viewer/viewer-awt/pom.xml | 13 ++ .../org/icepdf/ri/common/SwingController.java | 84 ++++++++++- .../ri/common/WindowManagementCallback.java | 3 + .../icepdf/ri/common/views/Controller.java | 8 + .../org/icepdf/ri/util/DavFileClient.java | 139 ++++++++++++++++++ .../java/org/icepdf/ri/viewer/Launcher.java | 35 ++++- .../org/icepdf/ri/viewer/WindowManager.java | 5 + .../ri/resources/MessageBundle.properties | 9 ++ 9 files changed, 290 insertions(+), 18 deletions(-) create mode 100644 viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java diff --git a/viewer/viewer-awt/build.gradle b/viewer/viewer-awt/build.gradle index 818699932..e0f606c86 100644 --- a/viewer/viewer-awt/build.gradle +++ b/viewer/viewer-awt/build.gradle @@ -7,7 +7,7 @@ applicationDefaultJvmArgs = ["-Xms64m", "-Xmx1024m"] def sectionName = 'org/icepdf/ri/' def baseJarName = 'icepdf' -def baseAppendixName = 'viewer' +def baseAppendixName = 'viewer' // generatePomFileForViewerJarPublication publishing { @@ -48,12 +48,12 @@ jar { doFirst { manifest { - attributes ('Created-By': System.getProperty('java.version') + ' (' + System.getProperty('java.vendor') + ')') + attributes('Created-By': System.getProperty('java.version') + ' (' + System.getProperty('java.vendor') + ')') // executable jar attributes("Main-Class": 'org.icepdf.ri.viewer.Main') if (!configurations.runtime.isEmpty()) { attributes('Class-Path': - configurations.runtime.collect{it.name}.join(' ')) + configurations.runtime.collect { it.name }.join(' ')) } } } @@ -61,7 +61,7 @@ jar { manifest { // section names attributes attributes("Implementation-Title": "${baseName + '-' + appendix}", "${sectionName}") - attributes("Implementation-Version": "${VERSION + (RELEASE_TYPE?.trim()? '-' + RELEASE_TYPE:'')}", "${sectionName}") + attributes("Implementation-Version": "${VERSION + (RELEASE_TYPE?.trim() ? '-' + RELEASE_TYPE : '')}", "${sectionName}") attributes("Implementation-Vendor": "${COMPANY}", "${sectionName}") } } @@ -75,7 +75,7 @@ task sourcesJar(type: Jar, dependsOn: classes) { classifier = 'sources' manifest { attributes("Implementation-Title": "${baseName + '-' + appendix}", "${sectionName}") - attributes("Implementation-Version": "${VERSION + (RELEASE_TYPE?.trim()? '-' + RELEASE_TYPE:'')}", "${sectionName}") + attributes("Implementation-Version": "${VERSION + (RELEASE_TYPE?.trim() ? '-' + RELEASE_TYPE : '')}", "${sectionName}") attributes("Implementation-Vendor": "${COMPANY}", "${sectionName}") } from sourceSets.main.allSource @@ -98,4 +98,6 @@ artifacts { dependencies { compile project(':core:core-awt') + compile 'com.github.lookfirst:sardine:5.9' + compile 'org.apache.tika:tika-core:1.23' } diff --git a/viewer/viewer-awt/pom.xml b/viewer/viewer-awt/pom.xml index 27f2889ca..b1e1a830f 100644 --- a/viewer/viewer-awt/pom.xml +++ b/viewer/viewer-awt/pom.xml @@ -82,4 +82,17 @@ + + + com.github.lookfirst + sardine + 5.9 + + + org.apache.tika + tika-core + 1.23 + + + diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java index c689fe463..265722586 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java @@ -15,6 +15,7 @@ */ package org.icepdf.ri.common; +import com.github.sardine.impl.SardineException; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.stage.FileChooser; @@ -52,10 +53,7 @@ import org.icepdf.ri.common.views.annotations.AnnotationState; import org.icepdf.ri.common.views.annotations.summary.AnnotationSummaryFrame; import org.icepdf.ri.common.views.destinations.DestinationComponent; -import org.icepdf.ri.util.BareBonesBrowserLaunch; -import org.icepdf.ri.util.ViewerPropertiesManager; -import org.icepdf.ri.util.TextExtractionTask; -import org.icepdf.ri.util.URLAccess; +import org.icepdf.ri.util.*; import org.icepdf.ri.viewer.WindowManager; import javax.print.attribute.PrintRequestAttributeSet; @@ -273,7 +271,7 @@ public class SwingController extends ComponentAdapter // sub controller for document text searching. protected DocumentSearchController documentSearchController; - + private DavFileClient pdfClient; protected Document document; protected boolean disposed; @@ -2732,6 +2730,62 @@ protected Object doInBackground() throws Exception { } } + /** + * Opens a document specified by the DavFileClient. Asks the user their password if needed + * @param davClient The client + */ + public void openDocument(final DavFileClient davClient) { + pdfClient = davClient; + JPanel panel = new JPanel(); + if (pdfClient.getPassword() == null) { + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + JLabel label = new JLabel(MessageFormat.format(messageBundle.getString("viewer.dialog.dav.password.label"), pdfClient.getName())); + JPasswordField field = new JPasswordField(); + panel.add(label); + panel.add(field); + String[] options = {messageBundle.getString("viewer.dialog.dav.password.button.ok"), messageBundle.getString("viewer.dialog.dav.password.button.cancel")}; + int option = JOptionPane.showOptionDialog(getViewerFrame(), panel, messageBundle.getString("viewer.dialog.dav.password.title"), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, + null, options, options[0]); + if (option == JOptionPane.OK_OPTION) { + pdfClient.setPassword(new String(field.getPassword())); + } else if (option == JOptionPane.CANCEL_OPTION) { + return; + } + } + try { + InputStream stream = pdfClient.getStream(); + document = new Document(); + document.setInputStream(new BufferedInputStream(stream), pdfClient.getUrl()); + commonNewDocumentHandling(pdfClient.getUrl()); + } catch (SardineException e) { + SwingUtilities.invokeLater(() -> { + org.icepdf.ri.util.Resources.showMessageDialog( + viewer, + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.sardine.exception.title", + "viewer.dialog.sardine.exception.msg", + e.getMessage() != null ? e.getMessage() : e.toString()); + //401 is probably wrong password + if (e.getStatusCode() == 401) { + openDocument(davClient); + } + }); + } catch (IOException | PDFException | PDFSecurityException e) { + SwingUtilities.invokeLater(() -> org.icepdf.ri.util.Resources.showMessageDialog( + viewer, + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.dav.exception.title", + "viewer.dialog.dav.exception.msg", + e.getMessage() != null ? e.getMessage() : e.toString())); + } finally { + setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_PAN); + } + } + + /** * Opens a Document via the specified InputStream. This method is a convenience method provided for * backwards compatibility. @@ -3129,7 +3183,9 @@ public void commonNewDocumentHandling(String fileDescription) { // add to the main pdfContentPanel the document peer if (viewer != null) { - Object[] messageArguments = new Object[]{fileDescription}; + final File f = new File(fileDescription); + final String argument = pdfClient == null ? (f.exists() ? f.getName() : fileDescription) : pdfClient.getName(); + Object[] messageArguments = {argument}; MessageFormat formatter = new MessageFormat( messageBundle.getString("viewer.window.title.open.default")); viewer.setTitle(formatter.format(messageArguments)); @@ -3439,6 +3495,22 @@ public void saveFile() { messageBundle, "viewer.dialog.saveAs.noUpdates.title", "viewer.dialog.saveAs.noUpdates.msg"); + } else if (pdfClient != null) { + try { + //TODO no choice but to dump file to append changes as long as IncrementalUpdater is obfuscated + File tmp = Files.createTempFile(pdfClient.getName(), "." + FileExtensionUtils.pdf).toFile(); + try (final OutputStream out = new BufferedOutputStream(new FileOutputStream(tmp))) { + document.saveToOutputStream(out); + } + try (final InputStream pdfIn = new BufferedInputStream(new FileInputStream(tmp))) { + pdfClient.save(pdfIn); + } + tmp.delete(); + savedChanges = document.getStateManager().getChanges(); + } catch (IOException e) { + logger.log(Level.FINE, "IOException while saving dav", e); + saveFileAs(); + } } else { if (saveFilePath != null && !saveFilePath.isEmpty()) { File out = new File(saveFilePath); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java index c814e1eea..e884782e5 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java @@ -16,6 +16,7 @@ package org.icepdf.ri.common; import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.ViewerPropertiesManager; import javax.swing.*; @@ -37,6 +38,8 @@ public interface WindowManagementCallback { void newWindow(URL url); + void newWindow(DavFileClient fileClient); + void disposeWindow(Controller controller, JFrame viewer, Preferences preferences); void minimiseAllWindows(); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java index b59e37623..37c12b7b5 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java @@ -21,6 +21,7 @@ import org.icepdf.ri.common.ViewModel; import org.icepdf.ri.common.WindowManagementCallback; import org.icepdf.ri.common.utility.outline.OutlineItemTreeNode; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.ViewerPropertiesManager; import java.awt.*; @@ -237,6 +238,13 @@ public interface Controller extends PropertyChangeListener { */ void openDocument(final URL location); + /** + * Opens a document specified by the given DavFileClient + * + * @param davFileClient The dav file client + */ + void openDocument(DavFileClient davFileClient); + /** * Load the specified file in a new Viewer RI window. * diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java new file mode 100644 index 000000000..99edb93c2 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java @@ -0,0 +1,139 @@ +package org.icepdf.ri.util; + +import com.github.sardine.DavResource; +import com.github.sardine.Sardine; +import com.github.sardine.SardineFactory; +import org.apache.tika.Tika; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class DavFileClient { + + private final Sardine sardine; + private final String url; + private final String username; + private final String folderUrl; + private final String name; + private final String ext; + private final boolean readOnly; + private String password; + private int revision = 0; + private File file; + private InputStream stream; + private String mimeType; + + public DavFileClient(final String url) { + this(url, null, null); + } + + public DavFileClient(final String url, final String username, final String password) { + this(url, username, password, false); + } + + public DavFileClient(final String url, final String username, final String password, final boolean readOnly) { + this.url = url; + this.username = username; + this.password = password; + this.sardine = username == null || password == null ? SardineFactory.begin() : SardineFactory.begin(username, password); + final String[] split = url.split("/"); + folderUrl = Arrays.stream(split).limit(split.length - 1).collect(Collectors.joining("/")); + final String[] names = split[split.length - 1].split("\\."); + name = names[0]; + ext = names[1]; + this.readOnly = readOnly; + } + + public List getFolderContents() throws IOException { + return sardine.list(folderUrl); + } + + public File getFile() throws IOException { + if (file == null) { + file = File.createTempFile(name, "." + ext); + try (final InputStream stream = sardine.get(url)) { + Files.copy(stream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + mimeType = new Tika().detect(file); + } + } + return file; + } + + public InputStream getStream() throws IOException { + if (stream == null || stream.available() == 0) { + this.stream = sardine.get(url); + } + return stream; + } + + public void save(final InputStream inputStream) throws IOException { + if (!readOnly) { + if (inputStream.markSupported()) { + mimeType = new Tika().detect(stream); + } + if (mimeType == null || mimeType.equals("application/octet-stream")) { + mimeType = new Tika().detect(name + "." + ext); + } + final String newUrl = folderUrl + "/" + name + "." + ext; + sardine.put(newUrl, inputStream, mimeType); + revision += 1; + } + } + + public void delete() throws IOException { + if (!readOnly) { + sardine.delete(url); + } + } + + public boolean exists() throws IOException { + return sardine.exists(url); + } + + public String getName() { + return name; + } + + public String getExt() { + return ext; + } + + public int getRevision() { + return revision; + } + + public String getMimeType() { + return mimeType; + } + + public String getFolderUrl() { + return folderUrl; + } + + public String getUrl() { + return url; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public void setPassword(final String password) { + this.password = password; + sardine.setCredentials(username, password); + } + + public boolean isReadOnly() { + return readOnly; + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java index effb182b5..169b0810f 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java @@ -17,9 +17,10 @@ import org.icepdf.core.util.Defs; import org.icepdf.ri.common.ViewModel; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.FontPropertiesManager; -import org.icepdf.ri.util.ViewerPropertiesManager; import org.icepdf.ri.util.URLAccess; +import org.icepdf.ri.util.ViewerPropertiesManager; import javax.swing.*; import java.text.MessageFormat; @@ -66,6 +67,9 @@ public static void main(String[] argv) { String contentURL = ""; String contentFile = ""; + String password = ""; + String contentDav = ""; + String user = ""; // parse command line arguments for (int i = 0; i < argv.length; i++) { if (i == argv.length - 1) { //each argument requires another @@ -80,6 +84,15 @@ public static void main(String[] argv) { case "-loadurl": contentURL = argv[++i].trim(); break; + case "-loaddav": + contentDav = argv[++i].trim(); + break; + case "-user": + user = argv[++i].trim(); + break; + case "-password": + password = argv[++i].trim(); + break; default: brokenUsage = true; break; @@ -96,7 +109,7 @@ public static void main(String[] argv) { System.exit(1); } // start the viewer - run(contentFile, contentURL, messageBundle); + run(contentFile, contentURL, contentDav, user, password, messageBundle); } /** @@ -110,6 +123,9 @@ public static void main(String[] argv) { */ private static void run(String contentFile, String contentURL, + String contentDav, + String user, + String password, ResourceBundle messageBundle) { // initiate the properties manager. @@ -128,13 +144,13 @@ private static void run(String contentFile, // application instance WindowManager windowManager = WindowManager.createInstance(propertiesManager, messageBundle); - if (contentFile != null && contentFile.length() > 0) { + if (contentFile != null && !contentFile.isEmpty()) { windowManager.newWindow(contentFile); ViewModel.setDefaultFilePath(contentFile); } // load a url if specified - if (contentURL != null && contentURL.length() > 0) { + if (contentURL != null && !contentURL.isEmpty()) { URLAccess urlAccess = URLAccess.doURLAccess(contentURL); urlAccess.closeConnection(); if (urlAccess.errorMessage != null) { @@ -157,12 +173,17 @@ private static void run(String contentFile, ViewModel.setDefaultURL(urlAccess.urlLocation); urlAccess.dispose(); } + if (contentDav != null && !contentDav.isEmpty()) { + windowManager.newWindow(new DavFileClient(contentDav, user, password)); + } + // Start an empy viewer if there was no command line parameters - if (((contentFile == null || contentFile.length() == 0) && - (contentURL == null || contentURL.length() == 0)) + if (((contentFile == null || contentFile.isEmpty()) && + (contentURL == null || contentURL.isEmpty()) && + (contentDav == null || contentDav.isEmpty())) || (windowManager.getNumberOfWindows() == 0) - ) { + ) { windowManager.newWindow(""); } } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java index 24100b489..0a425bc68 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java @@ -21,6 +21,7 @@ import org.icepdf.ri.common.views.Controller; import org.icepdf.ri.common.views.DocumentViewController; import org.icepdf.ri.common.views.DocumentViewControllerImpl; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.ViewerPropertiesManager; import javax.swing.*; @@ -111,6 +112,10 @@ public void newWindow(URL location) { controller.openDocument(location); } + public void newWindow(DavFileClient davFileClient) { + commonWindowCreation().openDocument(davFileClient); + } + protected Controller commonWindowCreation() { Controller controller = new SwingController(messageBundle); controller.setWindowManagementCallback(this); diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties index 9544a9ddc..cc7485a2f 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties @@ -371,6 +371,15 @@ viewer.dialog.security.cancelButton.label=Cancel viewer.dialog.security.cancelButton.mnemonic=C ## Open URL Dialog viewer.dialog.openURL.title=Open URL +## Open Webdav Dialog +viewer.dialog.dav.password.title=Enter your password +viewer.dialog.dav.password.label=Enter your password to open {0} +viewer.dialog.dav.password.button.ok=Ok +viewer.dialog.dav.password.button.cancel=Cancel +viewer.dialog.sardine.exception.title=Communication error +viewer.dialog.sardine.exception.msg=Couldn't open document due to {0} +viewer.dialog.dav.exception.title=Webdav error +viewer.dialog.dav.exception.msg=Couldn't open document due to {0} ### Save a Copy Dialog viewer.dialog.saveAs.title=Save As viewer.dialog.saveAs.extensionError.title=ICEsoft ICEpdf - Save Error