diff --git a/oxt/description/description_en.txt b/oxt/description/description_en.txt
new file mode 100644
index 0000000..227b0ef
--- /dev/null
+++ b/oxt/description/description_en.txt
@@ -0,0 +1,2 @@
+Extension for document settings configuration.
+Provides a way to create setttings backups and restore from it.
diff --git a/oxt/description/description_ru.txt b/oxt/description/description_ru.txt
new file mode 100644
index 0000000..7bfc9de
--- /dev/null
+++ b/oxt/description/description_ru.txt
@@ -0,0 +1,2 @@
+Расширение для управления настройками документов
+Предоставляет возможность создания резервных копий настроек и восстановления из них.
diff --git a/icons/addon_icon.png b/oxt/icons/addon_icon.png
similarity index 100%
rename from icons/addon_icon.png
rename to oxt/icons/addon_icon.png
diff --git a/oxt/icons/button_icon.png b/oxt/icons/button_icon.png
new file mode 100644
index 0000000..e89adc0
Binary files /dev/null and b/oxt/icons/button_icon.png differ
diff --git a/license_en.txt b/oxt/license_en.txt
similarity index 97%
rename from license_en.txt
rename to oxt/license_en.txt
index daea221..2fa6114 100644
--- a/license_en.txt
+++ b/oxt/license_en.txt
@@ -1,6 +1,6 @@
The author of this extension is:
Georgy Litvinov (public@litvinovg.pro)
-Copyright 2020.
+Copyright 2021.
This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3 of the License.
diff --git a/license_ru.txt b/oxt/license_ru.txt
similarity index 98%
rename from license_ru.txt
rename to oxt/license_ru.txt
index 44f691a..b970b21 100644
--- a/license_ru.txt
+++ b/oxt/license_ru.txt
@@ -1,6 +1,6 @@
Автор расширения:
Георгий Литвинов (public@litvinovg.pro)
-Copyright 2020.
+Copyright 2021.
Это расширение является свободным программным обеспечением, вы можете распространять и/или модифицировать его в соответствии с лицензией GNU Lesser General Public License версии 3.0, опубликованной Free Software Foundation.
diff --git a/releasenotes.txt b/oxt/releasenotes.txt
similarity index 63%
rename from releasenotes.txt
rename to oxt/releasenotes.txt
index 3d2554f..e3b59b3 100644
--- a/releasenotes.txt
+++ b/oxt/releasenotes.txt
@@ -1,2 +1,4 @@
+0.0.6 Added settings backup and restore options
+0.0.5 Removed obsoleted workaround
0.0.3 Apply only to writer documents
0.0.1 First public release with workaround for https://bugs.documentfoundation.org/show_bug.cgi?id=134782
diff --git a/src/main/java/logback.xml b/src/main/java/logback.xml
new file mode 100644
index 0000000..ff31ee6
--- /dev/null
+++ b/src/main/java/logback.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level - %msg%n
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/pro/litvinovg/docsettings/DialogHelper.java b/src/main/java/pro/litvinovg/docsettings/DialogHelper.java
new file mode 100644
index 0000000..3b9deac
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/DialogHelper.java
@@ -0,0 +1,185 @@
+package pro.litvinovg.docsettings;
+
+
+import java.io.File;
+
+import com.sun.star.awt.MessageBoxType;
+import com.sun.star.awt.Point;
+import com.sun.star.awt.XButton;
+import com.sun.star.awt.XComboBox;
+import com.sun.star.awt.XControl;
+import com.sun.star.awt.XControlContainer;
+import com.sun.star.awt.XControlModel;
+import com.sun.star.awt.XDialog;
+import com.sun.star.awt.XDialogEventHandler;
+import com.sun.star.awt.XDialogProvider2;
+import com.sun.star.awt.XFixedText;
+import com.sun.star.awt.XListBox;
+import com.sun.star.awt.XMessageBox;
+import com.sun.star.awt.XMessageBoxFactory;
+import com.sun.star.awt.XTextComponent;
+import com.sun.star.awt.XToolkit;
+import com.sun.star.awt.XWindow;
+import com.sun.star.awt.XWindowPeer;
+import com.sun.star.beans.PropertyVetoException;
+import com.sun.star.beans.UnknownPropertyException;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.lang.WrappedTargetException;
+import com.sun.star.uno.Exception;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+
+public class DialogHelper {
+
+ /**
+ * Create a dialog from an xdl file.
+ *
+ * @param xdlFile
+ * The filename in the `dialog` folder
+ * @param context
+ * @return XDialog
+ */
+ public static XDialog createDialog(String xdlFile, XComponentContext context, XDialogEventHandler handler) {
+ Object oDialogProvider;
+ try {
+ oDialogProvider = context.getServiceManager().createInstanceWithContext("com.sun.star.awt.DialogProvider2",
+ context);
+ XDialogProvider2 xDialogProv = (XDialogProvider2) UnoRuntime.queryInterface(XDialogProvider2.class,
+ oDialogProvider);
+ File dialogFile = FileHelper.getDialogFilePath(xdlFile, context);
+ return xDialogProv.createDialogWithHandler(convertToURL(context, dialogFile), handler);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /** Returns a URL to be used with XDialogProvider to create a dialog */
+ public static String convertToURL(XComponentContext xContext, File dialogFile) {
+ String sURL = null;
+ try {
+ com.sun.star.ucb.XFileIdentifierConverter xFileConverter = (com.sun.star.ucb.XFileIdentifierConverter) UnoRuntime
+ .queryInterface(com.sun.star.ucb.XFileIdentifierConverter.class, xContext.getServiceManager()
+ .createInstanceWithContext("com.sun.star.ucb.FileContentProvider", xContext));
+ sURL = xFileConverter.getFileURLFromSystemPath("", dialogFile.getAbsolutePath());
+ } catch (com.sun.star.uno.Exception ex) {
+ return null;
+ }
+ return sURL;
+ }
+
+ /** Returns a button (XButton) from a dialog */
+ public static XButton getButton(XDialog dialog, String componentId) {
+ XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class,
+ dialog);
+ Object control = xDlgContainer.getControl(componentId);
+ return (XButton) UnoRuntime.queryInterface(XButton.class, control);
+ }
+
+ /** Returns a text field (XTextComponent) from a dialog */
+ public static XTextComponent getEditField(XDialog dialog, String componentId) {
+ XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class,
+ dialog);
+ Object control = xDlgContainer.getControl(componentId);
+ return (XTextComponent) UnoRuntime.queryInterface(XTextComponent.class, control);
+ }
+
+ /** Returns a Combo box (XComboBox) from a dialog */
+ public static XComboBox getCombobox(XDialog dialog, String componentId) {
+ XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class,
+ dialog);
+ Object control = xDlgContainer.getControl(componentId);
+ return (XComboBox) UnoRuntime.queryInterface(XComboBox.class, control);
+ }
+
+ /** Returns a List box (XListBox) from a dialog */
+ public static XListBox getListBox(XDialog dialog, String componentId) {
+ XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class,
+ dialog);
+ Object control = xDlgContainer.getControl(componentId);
+ return (XListBox) UnoRuntime.queryInterface(XListBox.class, control);
+ }
+
+ /** Returns a label (XFixedText) from a dialog */
+ public static XFixedText getLabel(XDialog dialog, String componentId) {
+ XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class,
+ dialog);
+ Object control = xDlgContainer.getControl(componentId);
+ return (XFixedText) UnoRuntime.queryInterface(XFixedText.class, control);
+ }
+
+ public static void EnableButton(XDialog dialog, String componentId, boolean enable) {
+ XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class,
+ dialog);
+ // retrieve the control that we want to disable or enable
+ XControl xControl = UnoRuntime.queryInterface(XControl.class, xDlgContainer.getControl(componentId));
+ XPropertySet xModelPropertySet = UnoRuntime.queryInterface(XPropertySet.class, xControl.getModel());
+ try {
+ xModelPropertySet.setPropertyValue("Enabled", Boolean.valueOf(enable));
+ } catch (IllegalArgumentException | UnknownPropertyException | PropertyVetoException
+ | WrappedTargetException e) {
+ return;
+ }
+ }
+
+ /** Set the focus to an input field */
+ public static void SetFocus(XTextComponent editField) {
+ XWindow xControlWindow = UnoRuntime.queryInterface(XWindow.class, editField);
+ xControlWindow.setFocus();
+ }
+
+ public static void setPosition(XDialog dialog, int posX, int posY) {
+ XControlModel xDialogModel = UnoRuntime.queryInterface(XControl.class, dialog).getModel();
+ XPropertySet xPropSet = UnoRuntime.queryInterface(XPropertySet.class, xDialogModel);
+ try {
+ xPropSet.setPropertyValue("PositionX", posX);
+ xPropSet.setPropertyValue("PositionY", posY);
+ } catch (com.sun.star.lang.IllegalArgumentException | UnknownPropertyException | PropertyVetoException
+ | WrappedTargetException e) {
+ return;
+ }
+ }
+
+ public static Point getPosition(XDialog dialog) {
+ int posX = 0;
+ int posY = 0;
+ XControlModel xDialogModel = UnoRuntime.queryInterface(XControl.class, dialog).getModel();
+ XPropertySet xPropSet = UnoRuntime.queryInterface(XPropertySet.class, xDialogModel);
+ try {
+ posX = (int) xPropSet.getPropertyValue("PositionX");
+ posY = (int) xPropSet.getPropertyValue("PositionY");
+ } catch (UnknownPropertyException | WrappedTargetException e) {
+ }
+ return new Point(posX, posY);
+ }
+
+ public static void showInfoMessage(XComponentContext context, XDialog dialog, String message) {
+ showMessageBox(context, dialog, MessageBoxType.INFOBOX, "Info", message);
+ }
+
+ public static void showWarningMessage(XComponentContext context, XDialog dialog, String message) {
+ showMessageBox(context, dialog, MessageBoxType.WARNINGBOX, "Warning", message);
+ }
+
+ public static void showErrorMessage(XComponentContext context, XDialog dialog, String message) {
+ showMessageBox(context, dialog, MessageBoxType.ERRORBOX, "Error", message);
+ }
+
+ public static void showMessageBox(XComponentContext context, XDialog dialog, MessageBoxType type, String sTitle, String sMessage) {
+ XToolkit xToolkit;
+ try {
+ xToolkit = UnoRuntime.queryInterface(XToolkit.class,
+ context.getServiceManager().createInstanceWithContext("com.sun.star.awt.Toolkit", context));
+ } catch (Exception e) {
+ return;
+ }
+ XMessageBoxFactory xMessageBoxFactory = UnoRuntime.queryInterface(XMessageBoxFactory.class, xToolkit);
+ XWindowPeer xParentWindowPeer = UnoRuntime.queryInterface(XWindowPeer.class, dialog);
+ XMessageBox xMessageBox = xMessageBoxFactory.createMessageBox(xParentWindowPeer, type,
+ com.sun.star.awt.MessageBoxButtons.BUTTONS_OK, sTitle, sMessage);
+ if (xMessageBox == null)
+ return;
+
+ xMessageBox.execute();
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java b/src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java
new file mode 100644
index 0000000..d9ea1ea
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java
@@ -0,0 +1,121 @@
+package pro.litvinovg.docsettings;
+
+import java.awt.EventQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.star.lang.XSingleComponentFactory;
+
+//import pro.litvinovg.docsettings.gui.ConfigurationWindow;
+//import pro.litvinovg.docsettings.gui.ConversionExecutor;
+
+import com.sun.star.lib.uno.helper.Factory;
+import com.sun.star.lib.uno.helper.WeakBase;
+import com.sun.star.registry.XRegistryKey;
+import com.sun.star.uno.XComponentContext;
+
+import pro.litvinovg.docsettings.document.ODFDocument;
+import pro.litvinovg.docsettings.gui.MainWindow;
+import pro.litvinovg.docsettings.settings.Backups;
+import pro.litvinovg.docsettings.settings.Settings;
+
+public final class DocSettingsExtension extends WeakBase
+ implements com.sun.star.lang.XServiceInfo, com.sun.star.task.XJobExecutor {
+ private final XComponentContext context;
+ private static final String m_implementationName = DocSettingsExtension.class.getName();
+ private static final String[] m_serviceNames = { "pro.litvinovg.libreoffice.DocSettings" };
+ private static final Logger logger = LoggerFactory.getLogger(DocSettingsExtension.class);
+
+ public DocSettingsExtension(XComponentContext componentContext) {
+ context = componentContext;
+ };
+
+ public static XSingleComponentFactory __getComponentFactory(String sImplementationName) {
+ XSingleComponentFactory xFactory = null;
+
+ if (sImplementationName.equals(m_implementationName))
+ xFactory = Factory.createComponentFactory(DocSettingsExtension.class, m_serviceNames);
+ return xFactory;
+ }
+
+ public static boolean __writeRegistryServiceInfo(XRegistryKey xRegistryKey) {
+ return Factory.writeRegistryServiceInfo(m_implementationName, m_serviceNames, xRegistryKey);
+ }
+
+ // com.sun.star.lang.XServiceInfo:
+ public String getImplementationName() {
+ return m_implementationName;
+ }
+
+ public boolean supportsService(String sService) {
+ int len = m_serviceNames.length;
+
+ for (int i = 0; i < len; i++) {
+ if (sService.equals(m_serviceNames[i]))
+ return true;
+ }
+ return false;
+ }
+
+ public String[] getSupportedServiceNames() {
+ return m_serviceNames;
+ }
+
+ // com.sun.star.task.XJobExecutor:
+ public void trigger(String action) {
+ switch (action) {
+ case "openGUI":
+ try {
+ MainWindow.runGUI(context);
+ } catch (Throwable e) {
+ DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage());
+ }
+ break;
+ case "onLoad":
+ try {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ onLoad();
+ } catch (Exception e) {
+ DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage());
+ }
+ }
+ });
+ } catch (Throwable e) {
+ DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage());
+ }
+ break;
+ default:
+ DialogHelper.showErrorMessage(context, null, "Unknown action: " + action);
+ }
+ }
+
+ private void onLoad() {
+ ODFDocument document = null;
+ try {
+ document = new ODFDocument(context);
+ } catch (Exception e) {
+ logger.debug("Ops", e);
+ return;
+ }
+ Backups backups = new Backups();
+ ExtensionConfig config = ExtensionConfig.getInstance();
+ document.readSettings();
+ document.readBackups(backups);
+ if (!document.hasBackups(backups)) {
+ backups.create(document.getCurrentSettings());
+ document.writeBackups(backups);
+ return;
+ }
+ if (config.getAutoRestore()) {
+ Settings settings = backups.getLatest();
+ boolean isSettingsChanged = document.compareWithBackup(settings);
+ if (isSettingsChanged) {
+ document.restoreSettings(settings);
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java b/src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java
new file mode 100644
index 0000000..050d96b
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java
@@ -0,0 +1,41 @@
+package pro.litvinovg.docsettings;
+
+import java.util.HashMap;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ExtensionConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(ExtensionConfig.class);
+ private static final String AUTO_RESTORE = "autoRestore";
+ private static ExtensionConfig extensionSettings = null;
+ private Preferences storage;
+
+ public static ExtensionConfig getInstance() {
+ if (extensionSettings != null) {
+ return extensionSettings;
+ }
+ extensionSettings = new ExtensionConfig();
+ return extensionSettings;
+ }
+
+ private ExtensionConfig() {
+ storage = Preferences.userRoot().node(this.getClass().getName());
+ }
+
+ public boolean getAutoRestore() {
+ return storage.getBoolean(AUTO_RESTORE, false);
+ }
+
+ public void setAutoRestore(boolean value) {
+ storage.putBoolean(AUTO_RESTORE, value);
+ try {
+ storage.sync();
+ } catch (BackingStoreException e) {
+ logger.debug("Ops", e);
+ }
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/FileHelper.java b/src/main/java/pro/litvinovg/docsettings/FileHelper.java
new file mode 100644
index 0000000..458c5d1
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/FileHelper.java
@@ -0,0 +1,60 @@
+package pro.litvinovg.docsettings;
+
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import com.sun.star.deployment.PackageInformationProvider;
+import com.sun.star.deployment.XPackageInformationProvider;
+import com.sun.star.uno.Exception;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.util.XURLTransformer;
+
+public class FileHelper {
+
+ final static String DIALOG_RESOURCES = "dialog/";
+
+ /**
+ * Returns a path to a dialog file
+ */
+ public static File getDialogFilePath(String xdlFile, XComponentContext xContext) {
+ return getFilePath(DIALOG_RESOURCES + xdlFile, xContext);
+ }
+
+ /**
+ * Returns a file path for a file in the installed extension, or null on failure.
+ */
+ public static File getFilePath(String file, XComponentContext xContext) {
+ XPackageInformationProvider xPackageInformationProvider = PackageInformationProvider.get(xContext);
+ String location = xPackageInformationProvider.getPackageLocation("org.libreoffice.example.starterproject");
+ Object oTransformer;
+ try {
+ oTransformer = xContext.getServiceManager().createInstanceWithContext("com.sun.star.util.URLTransformer", xContext);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ XURLTransformer xTransformer = (XURLTransformer)UnoRuntime.queryInterface(XURLTransformer.class, oTransformer);
+ com.sun.star.util.URL[] oURL = new com.sun.star.util.URL[1];
+ oURL[0] = new com.sun.star.util.URL();
+ oURL[0].Complete = location + "/" + file;
+ xTransformer.parseStrict(oURL);
+ URL url;
+ try {
+ url = new URL(oURL[0].Complete);
+ } catch (MalformedURLException e1) {
+ return null;
+ }
+ File f;
+ try {
+ f = new File(url.toURI());
+ } catch (URISyntaxException e1) {
+ return null;
+ }
+ return f;
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/Localizer.java b/src/main/java/pro/litvinovg/docsettings/Localizer.java
new file mode 100644
index 0000000..61bf112
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/Localizer.java
@@ -0,0 +1,62 @@
+package pro.litvinovg.docsettings;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+public class Localizer {
+
+ private Locale currentLocale;
+ private String resourceBundle = "pro.litvinovg.docsettings.localizations.docsettings";
+ private ResourceBundle translations;
+ private boolean isOld = false;
+ private static Localizer localizer = null;
+ private Localizer() {
+ currentLocale = Locale.getDefault();
+ translations = ResourceBundle.getBundle(resourceBundle, currentLocale);
+ this.isOld = isOldJava();
+ }
+
+ public static Localizer getInstance() {
+ if (localizer != null) {
+ return localizer;
+ }
+ localizer = new Localizer();
+ return localizer;
+ }
+ public static String get(String name) {
+ Localizer.getInstance();
+ return localizer.getTranslation(name);
+ }
+
+ public String getTranslation(String name) {
+ if (translations != null && translations.containsKey(name)) {
+ String translation = translations.getString(name);
+ if (isOld) {
+ try {
+ return new String(translation.getBytes("ISO-8859-1"), "UTF-8");
+ } catch(UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ } else {
+ return translation;
+ }
+ }
+ return name;
+
+ }
+ private boolean isOldJava() {
+ try {
+ String versionString = System.getProperty("java.class.version");
+ Float version = Float.parseFloat(versionString);
+ if (version < 53) {
+ return true;
+ }
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes
new file mode 100644
index 0000000..53818d7
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes
@@ -0,0 +1 @@
+pro.litvinovg.docsettings.DocSettingsExtension
\ No newline at end of file
diff --git a/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java
new file mode 100644
index 0000000..a0adc3e
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java
@@ -0,0 +1,175 @@
+/*************************************************************************
+ *
+ * The Contents of this file are made available subject to the terms of
+ * either of the GNU Lesser General Public License Version 2.1
+ *
+ * Sun Microsystems Inc., October, 2000
+ *
+ *
+ * GNU Lesser General Public License Version 2.1
+ * =============================================
+ * Copyright 2000 by Sun Microsystems, Inc.
+ * 901 San Antonio Road, Palo Alto, CA 94303, USA
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * The Initial Developer of the Original Code is: Sun Microsystems, Inc..
+ *
+ * Copyright: 2002 by Sun Microsystems, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * Contributor(s): Cedric Bosdonnat
+ *
+ *
+ ************************************************************************/
+package pro.litvinovg.docsettings;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import com.sun.star.lang.XSingleComponentFactory;
+import com.sun.star.registry.XRegistryKey;
+
+/**
+ * Component main registration class.
+ *
+ * This class should not be modified.
+ *
+ * @author Cedric Bosdonnat aka. cedricbosdo
+ *
+ */
+public class RegistrationHandler {
+
+ /**
+ * Get a component factory for the implementations handled by this class.
+ *
+ * This method calls all the methods of the same name from the classes listed
+ * in the RegistrationHandler.classes
file. This method
+ * should not be modified.
+ *
+ * @param pImplementationName the name of the implementation to create.
+ *
+ * @return the factory which can create the implementation.
+ */
+ public static XSingleComponentFactory __getComponentFactory(String sImplementationName ) {
+ XSingleComponentFactory xFactory = null;
+
+ Class[] classes = findServicesImplementationClasses();
+
+ int i = 0;
+ while (i < classes.length && xFactory == null) {
+ Class clazz = classes[i];
+ if ( sImplementationName.equals( clazz.getCanonicalName() ) ) {
+ try {
+ Class[] getTypes = new Class[]{String.class};
+ Method getFactoryMethod = clazz.getMethod("__getComponentFactory", getTypes);
+ Object o = getFactoryMethod.invoke(null, sImplementationName);
+ xFactory = (XSingleComponentFactory)o;
+ } catch (Exception e) {
+ // Nothing to do: skip
+ System.err.println("Error happened");
+ e.printStackTrace();
+ }
+ }
+ i++;
+ }
+ return xFactory;
+ }
+
+ /**
+ * Writes the services implementation informations to the UNO registry.
+ *
+ * This method calls all the methods of the same name from the classes listed
+ * in the RegistrationHandler.classes
file. This method
+ * should not be modified.
+ *
+ * @param pRegistryKey the root registry key where to write the informations.
+ *
+ * @return true
if the informations have been successfully written
+ * to the registry key, false
otherwise.
+ */
+ public static boolean __writeRegistryServiceInfo(XRegistryKey xRegistryKey ) {
+
+ Class[] classes = findServicesImplementationClasses();
+
+ boolean success = true;
+ int i = 0;
+ while (i < classes.length && success) {
+ Class clazz = classes[i];
+ try {
+ Class[] writeTypes = new Class[]{XRegistryKey.class};
+ Method getFactoryMethod = clazz.getMethod("__writeRegistryServiceInfo", writeTypes);
+ Object o = getFactoryMethod.invoke(null, xRegistryKey);
+ success = success && ((Boolean)o).booleanValue();
+ } catch (Exception e) {
+ success = false;
+ e.printStackTrace();
+ }
+ i++;
+ }
+ return success;
+ }
+
+ /**
+ * @return all the UNO implementation classes.
+ */
+ private static Class[] findServicesImplementationClasses() {
+
+ ArrayList classes = new ArrayList();
+
+ InputStream in = RegistrationHandler.class.getResourceAsStream("RegistrationHandler.classes");
+ LineNumberReader reader = new LineNumberReader(new InputStreamReader(in));
+
+ try {
+ String line = reader.readLine();
+ while (line != null) {
+ if (!line.equals("")) {
+ line = line.trim();
+ try {
+ Class clazz = Class.forName(line);
+
+ Class[] writeTypes = new Class[]{XRegistryKey.class};
+ Class[] getTypes = new Class[]{String.class};
+
+ Method writeRegMethod = clazz.getMethod("__writeRegistryServiceInfo", writeTypes);
+ Method getFactoryMethod = clazz.getMethod("__getComponentFactory", getTypes);
+
+ if (writeRegMethod != null && getFactoryMethod != null) {
+ classes.add(clazz);
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ line = reader.readLine();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ reader.close();
+ in.close();
+ } catch (Exception e) {};
+ }
+
+ return classes.toArray(new Class[classes.size()]);
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java b/src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java
new file mode 100644
index 0000000..ca4bc98
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java
@@ -0,0 +1,27 @@
+package pro.litvinovg.docsettings;
+
+import java.io.File;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import pro.litvinovg.docsettings.document.ODFDocument;
+import pro.litvinovg.docsettings.settings.Backups;
+
+public class StandaloneApplication {
+ private static final Logger logger = LoggerFactory.getLogger(StandaloneApplication.class);
+
+ public static void main(String args[]) {
+ if (args.length == 0 ) {
+ logger.debug("Error. No arguments found. Exit.");
+ return;
+ }
+ final String filePath = args[0];
+ File file = new File(filePath);
+ if (!file.exists()) {
+ logger.debug("Error. File doesn't exist. Exit.");
+ return;
+ }
+
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java b/src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java
new file mode 100644
index 0000000..4494a42
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java
@@ -0,0 +1,357 @@
+package pro.litvinovg.docsettings.document;
+
+import static pro.litvinovg.docsettings.settings.StorageUtils.getDocFromString;
+import static pro.litvinovg.docsettings.settings.StorageUtils.getSettingsElement;
+
+import java.awt.EventQueue;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tika.Tika;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.beans.XPropertyContainer;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.beans.XPropertySetInfo;
+import com.sun.star.chart.TimeUnit;
+import com.sun.star.document.XDocumentProperties;
+import com.sun.star.document.XDocumentPropertiesSupplier;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XDesktop;
+import com.sun.star.frame.XDispatchHelper;
+import com.sun.star.frame.XDispatchProvider;
+import com.sun.star.frame.XModel;
+import com.sun.star.frame.XStorable;
+import com.sun.star.lang.XComponent;
+import com.sun.star.lang.XMultiComponentFactory;
+import com.sun.star.lang.XServiceInfo;
+import com.sun.star.text.XTextDocument;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.util.XModifiable;
+
+import pro.litvinovg.docsettings.DialogHelper;
+import pro.litvinovg.docsettings.Localizer;
+import pro.litvinovg.docsettings.gui.MainWindow;
+import pro.litvinovg.docsettings.settings.Backups;
+import pro.litvinovg.docsettings.settings.ODFSetting;
+import pro.litvinovg.docsettings.settings.SettingFactory;
+import pro.litvinovg.docsettings.settings.Settings;
+import pro.litvinovg.docsettings.settings.SettingsComparator;
+
+public class ODFDocument {
+
+
+ private static final Logger logger = LoggerFactory.getLogger(ODFDocument.class);
+
+ private XComponentContext context = null;
+ private XDesktop xDesktop = null;
+ private XMultiComponentFactory multiComponentFactory = null;
+ private XComponent currentDocument = null;
+ private String fileName = "";
+ public static final String SETTINGS = "settings.xml";
+ public static final String META = "meta.xml";
+ private Settings currentSettings = null;
+
+ private XStorable storable = null;
+
+ private XComponentLoader loader = null;
+
+ private String url = null;
+
+ public Settings getCurrentSettings() {
+ if (currentSettings.getSize() == 0) {
+ readSettings();
+ }
+ return currentSettings;
+ }
+
+ private String getSettingsContent() {
+ return readInternalFile(SETTINGS);
+ }
+
+ private void setSettingsContent(String settingsContent) {
+ writeInternalFile(SETTINGS, settingsContent);
+ }
+
+ private String getMetaContent() {
+ return readInternalFile(META);
+ }
+
+ /*
+ * private void setMetaContent(String newMetaContent) { writeInternalFile(META,
+ * newMetaContent); }
+ */
+
+ public ODFDocument(XComponentContext componentContext) throws Exception {
+ this.currentSettings = new Settings();
+ if (componentContext != null) {
+ context = componentContext;
+ multiComponentFactory = context.getServiceManager();
+
+ Object oDesktop = multiComponentFactory.createInstanceWithContext("com.sun.star.frame.Desktop", context);
+ xDesktop = UnoRuntime.queryInterface(XDesktop.class, oDesktop);
+ currentDocument = xDesktop.getCurrentComponent();
+ if (currentDocument == null) {
+ logger.debug("xDesktop.getCurrentComponent() returned null");
+ throw new Exception(Localizer.get("error_document_is_null"));
+ }
+ XModel model = UnoRuntime.queryInterface(XModel.class, currentDocument);
+ if (!isSupported(currentDocument, "com.sun.star.text.TextDocument")) {
+ throw new Exception(Localizer.get("error_document_type_is_not_supported"));
+ }
+ loader = UnoRuntime.queryInterface(XComponentLoader.class, xDesktop);
+ XTextDocument textDoc = UnoRuntime.queryInterface(XTextDocument.class, currentDocument);
+ if (textDoc == null) {
+ throw new Exception("Error. TextDoc is null.");
+ }
+ storable = UnoRuntime.queryInterface(XStorable.class, textDoc);
+
+ if (storable == null || storable.isReadonly()) {
+ throw new Exception(Localizer.get("error_document_is_ro"));
+ }
+ if (!storable.hasLocation() || storable.getLocation().isEmpty()) {
+ throw new Exception(Localizer.get("error_document_is_not_saved"));
+ }
+
+ url = model.getURL();
+ URI uri = new URI(url);
+ File file = new File(uri);
+ if (!file.exists()) {
+ throw new Exception(Localizer.get("error_document_is_null"));
+ }
+ if (!file.canRead()) {
+ throw new Exception(Localizer.get("error_document_is_ro"));
+ }
+ if (!"application/vnd.oasis.opendocument.text".equals(getMimeType(file))) {
+ throw new Exception(Localizer.get("error_document_type_is_not_supported"));
+ }
+ fileName = file.getAbsolutePath();
+ }
+ }
+
+ private String getMimeType(File file) {
+ Tika tika = new Tika();
+ InputStream is;
+ String mime = "";
+ try {
+ is = new FileInputStream(file);
+ mime = tika.detect(is);
+ } catch (IOException e) {
+ logger.debug("Ops", e);
+ }
+ return mime;
+ }
+
+ public void restoreSettings(Settings settings) {
+ closeDocument();
+ restoreFrom(settings);
+ reOpenDoc();
+ }
+
+ public XPropertyContainer getUserDefinedProps(){
+ XDocumentPropertiesSupplier docPropertiesSupplier = UnoRuntime.queryInterface(XDocumentPropertiesSupplier.class, currentDocument);
+ XDocumentProperties docProperties = docPropertiesSupplier.getDocumentProperties();
+ XPropertyContainer userDefinedProperties = docProperties.getUserDefinedProperties();
+ return userDefinedProperties;
+ }
+
+ public void readSettings() {
+ flushSettings();
+ try {
+ Node configSettings = getSettingsElement(getDocFromString(getSettingsContent()));
+ NodeList settingNodes = configSettings.getChildNodes();
+ for (int i = 0; i < settingNodes.getLength(); i++) {
+ Node settingNode = settingNodes.item(i);
+ ODFSetting setting = SettingFactory.create(settingNode);
+ currentSettings.add(setting);
+ }
+ } catch (Exception e) {
+ logger.debug(e.getMessage());
+ logger.debug("Ops!",e);
+ }
+ }
+
+ private void flushSettings() {
+ currentSettings.clear();
+ }
+
+ public void restoreFrom(Settings settings) {
+ String newSettingsContent;
+ try {
+ newSettingsContent = settings.toSettingsContent(getSettingsContent());
+ writeInternalFile(SETTINGS, newSettingsContent);
+
+ } catch (Exception e) {
+ logger.debug("Restore failed", e);
+ }
+ }
+
+ public void readBackups(Backups backups) {
+ backups.readBackupsFromMetadata(getUserDefinedProps());
+ }
+
+ public boolean hasBackups(Backups backups) {
+ return !backups.isEmpty();
+ }
+
+ public void writeBackups(Backups backups) {
+ backups.writeBackupsToMetadata(getUserDefinedProps());
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void printText(String text) {
+ DialogHelper.showErrorMessage(context, null, text);
+ }
+
+ public void printFileName() {
+ DialogHelper.showErrorMessage(context, null, fileName);
+ }
+
+ private String readInternalFile(String internalFile) {
+ Map env = new HashMap<>();
+ String result = "";
+ FileSystem zipfs = null;
+ env.put("create", "true");
+ Path path = Paths.get(fileName);
+ URI uri = URI.create("jar:" + path.toUri());
+ try {
+ zipfs = FileSystems.newFileSystem(uri, env);
+ Path settingsPath = zipfs.getPath(internalFile);
+ byte[] encoded = Files.readAllBytes(settingsPath);
+ result = new String(encoded, "UTF-8");
+ } catch (IOException e) {
+ logger.debug(e.getMessage());
+ logger.debug("Ops!",e);
+ e.printStackTrace();
+ } finally {
+ try {
+ zipfs.close();
+ } catch (IOException e) {
+ logger.debug("Ops", e);
+ e.printStackTrace();
+ }
+ }
+ return result;
+ }
+
+ private void writeInternalFile(String internalFile, String contents) {
+ Map env = new HashMap<>();
+ BufferedWriter writer = null;
+ FileSystem zipfs = null;
+ env.put("create", "true");
+ Path path = Paths.get(fileName);
+ URI uri = URI.create("jar:" + path.toUri());
+ try {
+ zipfs = FileSystems.newFileSystem(uri, env);
+ Path internalPath = zipfs.getPath(internalFile);
+ writer = Files.newBufferedWriter(internalPath, StandardCharsets.UTF_8, StandardOpenOption.WRITE);
+ writer.write(contents);
+ } catch (IOException e) {
+ logger.debug(e.getMessage());
+ logger.debug("Ops!",e);
+ } finally {
+ try {
+ writer.close();
+ zipfs.close();
+ } catch (IOException e) {
+ logger.debug(e.getLocalizedMessage());
+ logger.debug("Ops!",e);
+ }
+ }
+ }
+
+
+ public void reOpenDoc(){
+ try {
+ XComponent doc = loader.loadComponentFromURL(url, "_default", 0, new PropertyValue[0]);
+ } catch (Exception e) {
+ System.out.println("Failed to open the document");
+ }
+ ;
+ }
+
+ public void closeDocument() {
+ if (context != null) {
+ executedispatch("CloseDoc", new PropertyValue[0]);
+ } else {
+ logger.debug("Can't close document. No context.");
+ }
+ }
+
+ public void reloadDocument() {
+ if (context != null) {
+ executedispatch("Reload", new PropertyValue[0]);
+ } else {
+ logger.debug("Can't reload document. No context.");
+ }
+ }
+
+ private boolean isSupported(Object object, String service) {
+ XServiceInfo info = UnoRuntime.queryInterface(XServiceInfo.class, object);
+ if (info == null) {
+ return false;
+ }
+ return info.supportsService(service);
+ }
+
+ private boolean executedispatch( String cmd, PropertyValue[] props) {
+ XDispatchHelper helper = createInstance(XDispatchHelper.class, "com.sun.star.frame.DispatchHelper");
+ if (helper == null) {
+ logger.debug("Could not create dispatch helper for command " + cmd);
+ return false;
+ }
+ try {
+ Object oDesktop = context.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", context);
+ XDesktop xDesktop = UnoRuntime.queryInterface(XDesktop.class, oDesktop);
+ XDispatchProvider provider = UnoRuntime.queryInterface(XDispatchProvider.class, xDesktop.getCurrentFrame());
+ helper.executeDispatch(provider, ".uno:" + cmd, "", 0, props);
+ return true;
+ } catch (java.lang.Exception e) {
+ logger.debug("Could not dispatch '" + cmd + "':\n " + e);
+ }
+ return false;
+ }
+
+ private T createInstance(Class aType, String serviceName) {
+ if ((context == null)) {
+ logger.debug("No libreoffice context. Cant create instance of " + aType.toString());
+ return null;
+ }
+ T interfaceObj = null;
+ try {
+ Object o = context.getServiceManager().createInstanceWithContext(serviceName, context);
+ interfaceObj = UnoRuntime.queryInterface(aType, o);
+ } catch (Exception e) {
+ logger.debug("Couldn't create interface for '" + serviceName + "': " + e);
+ }
+ return interfaceObj;
+ }
+
+ public boolean compareWithBackup(Settings backedUpSettings) {
+ return SettingsComparator.isEqual(getCurrentSettings(), backedUpSettings);
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java b/src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java
new file mode 100644
index 0000000..c633172
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java
@@ -0,0 +1,10 @@
+package pro.litvinovg.docsettings.exceptions;
+
+public class MalformedXmlException extends Exception{
+
+ private static final long serialVersionUID = 1L;
+
+ public MalformedXmlException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java b/src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java
new file mode 100644
index 0000000..89d4107
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java
@@ -0,0 +1,202 @@
+package pro.litvinovg.docsettings.gui;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.DefaultListModel;
+import javax.swing.DefaultListSelectionModel;
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.LayoutStyle.ComponentPlacement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import pro.litvinovg.docsettings.ExtensionConfig;
+import pro.litvinovg.docsettings.Localizer;
+import pro.litvinovg.docsettings.document.ODFDocument;
+import pro.litvinovg.docsettings.settings.Backups;
+import pro.litvinovg.docsettings.settings.Settings;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.SwingConstants;
+
+public class BackupPanel extends JPanel {
+
+ private ExtensionConfig config;
+ private ODFDocument document;
+ private Backups backups;
+ private JButton btn_close;
+ private JButton btn_restore;
+ private JButton btn_create;
+ private JButton btn_delete;
+ private JList lst_backups;
+ private DefaultListModel backupListModel;
+ private static final Logger logger = LoggerFactory.getLogger(BackupPanel.class);
+ private JCheckBox cb_stick_latest_backup;
+ private JFrame frame;
+
+
+ public BackupPanel(JFrame frame, ODFDocument document, Backups backups) {
+ this.frame = frame;
+ this.backups = backups;
+ this.config = ExtensionConfig.getInstance();
+ this.document = document;
+ initComponents();
+ initEvents();
+ }
+
+ private void initEvents() {
+ onCreateButtonClick();
+ onDeleteButtonClick();
+ onRestoreButtonClick();
+ onCloseButtonClick();
+ onCheckBoxClick();
+ }
+
+ private void initComponents() {
+
+ backupListModel = new DefaultListModel();
+ populateList();
+ lst_backups = new JList(backupListModel);
+ lst_backups.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
+
+ JLabel lb_backups = new JLabel(Localizer.get("backup_panel_description"));
+
+ btn_delete = new JButton(Localizer.get("delete_backup_button"));
+ btn_create = new JButton(Localizer.get("create_backup_button"));
+ btn_restore = new JButton(Localizer.get("restore_button"));
+ btn_close = new JButton(Localizer.get("close_window_button"));
+
+ cb_stick_latest_backup = new JCheckBox(Localizer.get("stick_latest_backup_cb_label"));
+ cb_stick_latest_backup.setSelected(config.getAutoRestore());
+ cb_stick_latest_backup.setVerticalAlignment(SwingConstants.TOP);
+
+ GroupLayout groupLayout = new GroupLayout(this);
+ groupLayout.setHorizontalGroup(
+ groupLayout.createParallelGroup(Alignment.LEADING)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addGap(43)
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING, false)
+ .addComponent(lb_backups, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addComponent(lst_backups, GroupLayout.PREFERRED_SIZE, 354, GroupLayout.PREFERRED_SIZE)
+ .addGap(26)
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING, false)
+ .addComponent(cb_stick_latest_backup, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(btn_close, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(btn_delete, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 269, Short.MAX_VALUE)
+ .addComponent(btn_create, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(btn_restore, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
+ .addContainerGap(288, Short.MAX_VALUE))
+ );
+ groupLayout.setVerticalGroup(
+ groupLayout.createParallelGroup(Alignment.LEADING)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(lb_backups, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)
+ .addGap(14)
+ .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+ .addComponent(lst_backups, GroupLayout.PREFERRED_SIZE, 384, GroupLayout.PREFERRED_SIZE)
+ .addGroup(groupLayout.createSequentialGroup()
+ .addComponent(btn_delete)
+ .addGap(18)
+ .addComponent(btn_create)
+ .addGap(18)
+ .addComponent(btn_restore)
+ .addGap(33)
+ .addComponent(cb_stick_latest_backup, GroupLayout.PREFERRED_SIZE, 83, GroupLayout.PREFERRED_SIZE)
+ .addGap(132)
+ .addComponent(btn_close)))
+ .addContainerGap(74, Short.MAX_VALUE))
+ );
+ setLayout(groupLayout);
+ }
+
+ private void populateList() {
+ backupListModel.clear();
+ int i = 0;
+ for(String date : backups.getBackupTimeLabels()) {
+ backupListModel.add(i,date);
+ i++;
+ }
+ }
+
+ private void onCreateButtonClick() {
+ btn_create.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ if (document == null) {
+ return;
+ }
+ backups.create(document.getCurrentSettings());
+ document.writeBackups(backups);
+ populateList();
+ }
+ });
+ }
+
+ private void onDeleteButtonClick() {
+ btn_delete.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ if (lst_backups.isSelectionEmpty()) {
+ return;
+ }
+ if (document == null) {
+ return;
+ }
+ String selected = (String) lst_backups.getSelectedValue();
+ backups.removeByDateTimeLabel(selected);
+ document.writeBackups(backups);
+ populateList();
+ }
+ });
+ }
+
+ private void onRestoreButtonClick() {
+ btn_restore.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ if (lst_backups.isSelectionEmpty()) {
+ JOptionPane.showMessageDialog(frame, Localizer.get("no_backup_selected"));
+ return;
+ }
+ if (document == null) {
+ JOptionPane.showMessageDialog(frame, Localizer.get("error_document_not_found"));
+ return;
+ }
+ String selected = (String) lst_backups.getSelectedValue();
+ Settings backup;
+ try {
+ backup = backups.getByDateTimeLabel(selected);
+ document.restoreSettings(backup);
+ MainWindow.getSingleFrame().setVisible(false);
+ MainWindow.getSingleFrame().dispose();
+ } catch (Exception e) {
+ logger.debug("Restore failed", e);
+ JOptionPane.showMessageDialog(frame, Localizer.get("restore_failed \n" + e.getLocalizedMessage()));
+ }
+ }
+ });
+ }
+
+ private void onCloseButtonClick() {
+ btn_close.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ MainWindow.getSingleFrame().setVisible(false);
+ MainWindow.getSingleFrame().dispose();
+ }
+ });
+ }
+
+ private void onCheckBoxClick() {
+ cb_stick_latest_backup.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ config.setAutoRestore(cb_stick_latest_backup.isSelected());
+ }
+ });
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java b/src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java
new file mode 100644
index 0000000..75013ba
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java
@@ -0,0 +1,42 @@
+package pro.litvinovg.docsettings.gui;
+
+import java.awt.BorderLayout;
+import java.awt.EventQueue;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+public class ChangedSettings extends JFrame {
+
+ private JPanel contentPane;
+
+ /**
+ * Launch the application.
+ */
+ public static void main(String[] args) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ ChangedSettings frame = new ChangedSettings();
+ frame.setVisible(true);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ /**
+ * Create the frame.
+ */
+ public ChangedSettings() {
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setBounds(100, 100, 450, 300);
+ contentPane = new JPanel();
+ contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
+ contentPane.setLayout(new BorderLayout(0, 0));
+ setContentPane(contentPane);
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java b/src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java
new file mode 100644
index 0000000..736a21e
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java
@@ -0,0 +1,123 @@
+package pro.litvinovg.docsettings.gui;
+
+import java.awt.EventQueue;
+import java.awt.Toolkit;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+import java.awt.GridLayout;
+import javax.swing.JTabbedPane;
+import javax.swing.UIManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.star.uno.XComponentContext;
+
+import pro.litvinovg.docsettings.DialogHelper;
+import pro.litvinovg.docsettings.DocSettingsExtension;
+import pro.litvinovg.docsettings.ExtensionConfig;
+import pro.litvinovg.docsettings.Localizer;
+import pro.litvinovg.docsettings.document.ODFDocument;
+import pro.litvinovg.docsettings.settings.Backups;
+
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import javax.swing.BoxLayout;
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+
+import javax.swing.JList;
+
+public class MainWindow extends JFrame {
+
+ private static final long serialVersionUID = 1L;
+ private static JFrame frame = null;
+ private Localizer localizer;
+ private ODFDocument document;
+ private Backups backups;
+ private static final Logger logger = LoggerFactory.getLogger(MainWindow.class);
+
+
+ public static void main(String[] args) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ MainWindow window = new MainWindow();
+ frame = window;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public static void runGUI(XComponentContext context) {
+
+ if (frame != null) {
+ frame.dispose();
+ }
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Throwable e) {
+ String message = e.getLocalizedMessage();
+ JOptionPane.showMessageDialog(frame, message);
+ }
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ MainWindow window = new MainWindow(context);
+ frame = window;
+ } catch (Throwable e) {
+ DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage());
+ logger.debug("Ops", e);
+ }
+ }
+ });
+
+ }
+
+ public static JFrame getSingleFrame() {
+ return frame;
+ }
+
+ public MainWindow() {
+ backups = new Backups();
+ initialize();
+ }
+
+ public MainWindow(XComponentContext context) throws Exception {
+ document = new ODFDocument(context);
+ backups = new Backups();
+ document.readSettings();
+ document.readBackups(backups);
+ initialize();
+ }
+
+ /**
+ * Initialize the contents of the frame.
+ */
+ private void initialize() {
+ setIconImage(Toolkit.getDefaultToolkit().getImage(MainWindow.class.getResource("/pro/litvinovg/docsettings/resources/icon.png")));
+ setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ getContentPane().setLayout(new GridLayout(1, 0, 0, 0));
+ localizer = Localizer.getInstance();
+ this.setBounds(100, 100, 750, 500);
+ this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ this.setVisible(true);
+
+ JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+ getContentPane().add(tabbedPane);
+
+ JPanel backupPanel = new BackupPanel(this, document, backups);
+ tabbedPane.addTab(localizer.getTranslation("backups_tab_name"), null, backupPanel, null);
+
+ //JPanel settingsPanel = new SettingsPanel(this, document);
+ //tabbedPane.addTab(localizer.getTranslation("settings_tab_name"), null, settingsPanel, null);
+
+
+
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java b/src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java
new file mode 100644
index 0000000..49eff11
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java
@@ -0,0 +1,33 @@
+package pro.litvinovg.docsettings.gui;
+
+import javax.swing.JPanel;
+
+import pro.litvinovg.docsettings.ExtensionConfig;
+import pro.litvinovg.docsettings.Localizer;
+import pro.litvinovg.docsettings.document.ODFDocument;
+import java.awt.GridLayout;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+
+public class SettingsPanel extends JPanel {
+
+ private ExtensionConfig config;
+ private Localizer localizer;
+ private ODFDocument document;
+ private JFrame frame;
+
+ public SettingsPanel(JFrame mainWindow, ODFDocument document) {
+ this.frame = mainWindow;
+ this.document = document;
+ this.config = ExtensionConfig.getInstance();
+ this.localizer = Localizer.getInstance();
+ setLayout(new GridLayout(1, 0, 0, 0));
+ initComponents();
+ }
+
+ private void initComponents() {
+
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties
new file mode 100644
index 0000000..c548a78
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties
@@ -0,0 +1,15 @@
+close_window_button = Close
+restore_button = Restore from backup
+create_backup_button = Create backup
+delete_backup_button = Delete backup
+settings_tab_name = Settings
+backups_tab_name = Backups
+backup_panel_description = Manage document settings backups
+no_timestamp_found_error = No backup dates found
+stick_latest_backup_cb_label = Block settings
+no_backup_selected = Select backup and try again
+error_document_not_found = Document not found.
+error_document_is_not_saved = Document is not saved. Save document before proceeding.
+error_document_is_ro = Document opened in read only mode.
+error_document_is_null = Document not found
+error_document_type_is_not_supported = Document type is not supported.
\ No newline at end of file
diff --git a/src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties
new file mode 100644
index 0000000..9561147
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties
@@ -0,0 +1,15 @@
+close_window_button = Закрыть
+restore_button = Восстановить из резервной копии
+create_backup_button = Создать резервную копию
+delete_backup_button = Удалить резервную копию
+settings_tab_name = Настройки
+backups_tab_name = Резервные копии
+backup_panel_description = Управление резервными копиями настроек документов
+no_timestamp_found_error = Не найдено дат резервных копий
+stick_latest_backup_cb_label = Заблокировать изменение настроек
+no_backup_selected = Выберите резервную копию и попробуйте снова
+error_document_not_found = Ошибка. Документ не найден.
+error_document_is_not_saved = Документ не сохранен. Сохраните документ и попробуйте снова.
+error_document_is_ro = Документ открыт в режиме только для чтения.
+error_document_is_null = Ошибка. Документ не найден.
+error_document_type_is_not_supported = Тип документа не поддерживается. Сохраните документ в формате ODT.
\ No newline at end of file
diff --git a/src/main/java/pro/litvinovg/docsettings/resources/icon.png b/src/main/java/pro/litvinovg/docsettings/resources/icon.png
new file mode 100644
index 0000000..e89adc0
Binary files /dev/null and b/src/main/java/pro/litvinovg/docsettings/resources/icon.png differ
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/Backups.java b/src/main/java/pro/litvinovg/docsettings/settings/Backups.java
new file mode 100644
index 0000000..fcd87b9
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/Backups.java
@@ -0,0 +1,445 @@
+package pro.litvinovg.docsettings.settings;
+
+import static pro.litvinovg.docsettings.settings.StorageUtils.*;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeMap;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.sun.star.beans.IllegalTypeException;
+import com.sun.star.beans.Property;
+import com.sun.star.beans.PropertyExistException;
+import com.sun.star.beans.UnknownPropertyException;
+import com.sun.star.beans.XPropertyContainer;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.beans.XPropertySetInfo;
+import com.sun.star.lang.IllegalArgumentException;
+import com.sun.star.lang.WrappedTargetException;
+import com.sun.star.uno.UnoRuntime;
+
+import pro.litvinovg.docsettings.exceptions.MalformedXmlException;
+
+public class Backups {
+
+ private static final short REMOVABLE_PROPERTY = 128;
+ private static final int OO_XML_MAX_LENGTH = 65535;
+ private static final Logger logger = LoggerFactory.getLogger(Backups.class);
+ private static final String BACKUP_NAME_PREFIX = "SerializedSettings-";
+ private TreeMap backups = null;
+
+ public Backups() {
+ backups = new TreeMap();
+ }
+
+ public void add(Long unixTimeStamp, Settings backup) {
+ backups.put(unixTimeStamp, backup);
+ }
+ public void clear() {
+ backups.clear();
+ }
+ public boolean isEmpty() {
+ return backups.isEmpty();
+ }
+
+ public void create(Settings currentSettings) {
+ backups.put( getUnixTime(), currentSettings);
+ }
+
+ public Settings getBackupByTimeStamp(Long timeStamp) {
+ return backups.get(timeStamp);
+ }
+
+ public Settings getLatest() {
+ return backups.lastEntry().getValue();
+ }
+
+ public Set getTimeStamps() {
+ return backups.keySet();
+ }
+
+ public void readBackupsFromMetadata(XPropertyContainer userDefinedProps) {
+ clear();
+ TreeMap> backupMap = getBackupMap(userDefinedProps);
+ Map serializedBackups = combineBackupStrings(backupMap);
+ for (Entry entry : serializedBackups.entrySet()) {
+ Long unixTimeStamp = entry.getKey();
+ String backupString = entry.getValue();
+ if (!backupString.isEmpty()) {
+ backupString = decodeBase64(backupString);
+ readBackup(unixTimeStamp, backupString);
+ }
+ }
+ }
+
+ public void writeBackupsToMetadata(XPropertyContainer userDefinedProps) {
+ cleanBackupMetadata(userDefinedProps);
+ for (Entry backup : backups.entrySet()) {
+ try {
+ addBackupEntries( userDefinedProps, backup);
+ } catch (MalformedXmlException | ParserConfigurationException | TransformerException e) {
+ logger.debug("Ops", e);
+ }
+ }
+ }
+
+ private void cleanBackupMetadata(XPropertyContainer userDefinedProps) {
+ XPropertySet propertySet = UnoRuntime.queryInterface(XPropertySet.class, userDefinedProps);
+ XPropertySetInfo propSetInfo = propertySet.getPropertySetInfo();
+ Property[] props = propSetInfo.getProperties();
+ for (int i = 0; i < props.length; i++) {
+ try {
+ String name = props[i].Name;
+ if (name.startsWith(BACKUP_NAME_PREFIX)) {
+ userDefinedProps.removeProperty(name);
+ }
+ } catch (Exception e) {
+ logger.debug("Ops", e);
+ }
+ }
+ }
+
+ private void addBackupEntries(XPropertyContainer userDefinedProps, Entry backup) throws MalformedXmlException, ParserConfigurationException, TransformerException {
+ String backupString = serializeSettingsXML(backup.getValue());
+ backupString = encodeBase64(backupString);
+ List parts = splitByOOMaxLength(backupString);
+ for (int i = 0; i < parts.size();i++) {
+ String part = parts.get(i);
+ String name = formatBackupMetaName(backup.getKey(),i);
+ try {
+ userDefinedProps.addProperty(name, REMOVABLE_PROPERTY, part);
+ } catch (IllegalArgumentException e) {
+ logger.debug("Ops", e);
+ } catch (PropertyExistException e) {
+ //it is ok
+ logger.debug("Ops", e);
+ } catch (IllegalTypeException e) {
+ logger.debug("Ops", e);
+ }
+
+ }
+ }
+
+ private void readBackup(Long unixTimeStamp, String backupString) {
+ Document backupDoc;
+ try {
+ backupDoc = getDocFromString(backupString);
+ Element configSettings = (Element) getSettingsElement(backupDoc);
+ NodeList settingNodes = configSettings.getChildNodes();
+ Settings backup = readSettingsFromNodeList(settingNodes);
+ add(unixTimeStamp,backup);
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ logger.debug("Ops", e);
+ } catch (Exception e) {
+ logger.debug("Ops", e);
+ }
+ }
+
+ private TreeMap> getBackupMap(XPropertyContainer userDefinedProps) {
+ TreeMap> backupMap = new TreeMap>();
+
+ XPropertySet propertySet = UnoRuntime.queryInterface(XPropertySet.class, userDefinedProps);
+ XPropertySetInfo propSetInfo = propertySet.getPropertySetInfo();
+ Property[] props = propSetInfo.getProperties();
+ for (int i = 0; i < props.length; i++) {
+ try {
+ Property prop = props[i];
+ String name = prop.Name;
+ if (name.startsWith(BACKUP_NAME_PREFIX)) {
+ Long timeStamp = Long.parseLong(getTimeStampFromString(name));
+ Long order = Long.parseLong(getOrderFromString(name));
+ String stringPart = propertySet.getPropertyValue(prop.Name).toString();
+ Map backupParts = null;
+ if (backupMap.containsKey(timeStamp)) {
+ backupParts = backupMap.get(timeStamp);
+ } else {
+ backupParts = new TreeMap();
+ backupMap.put(timeStamp, backupParts);
+ }
+ backupParts.put(order, stringPart);
+ }
+ //logger.debug("Prop name "+ prop.Name + " Prop value " + propertySet.getPropertyValue(prop.Name));
+ } catch (UnknownPropertyException | WrappedTargetException e) {
+ logger.debug("Ops", e);
+ }
+ }
+ return backupMap;
+ }
+
+ public void readBackupsFromXML(String metaContent) {
+ clear();
+ try {
+ Document doc = getDocFromString(metaContent);
+ Map serializedBackups = getSerializedBackups(doc);
+ for (Entry entry : serializedBackups.entrySet()) {
+ Long unixTimeStamp = entry.getKey();
+ String backupString = entry.getValue();
+ if (!backupString.isEmpty()) {
+ backupString = decodeBase64(backupString);
+ Document backupDoc = getDocFromString(backupString);
+ Element configSettings = (Element) getSettingsElement(backupDoc);
+ NodeList settingNodes = configSettings.getChildNodes();
+ Settings backup = readSettingsFromNodeList(settingNodes);
+ add(unixTimeStamp,backup);
+ }
+ }
+ } catch (Exception e) {
+ logger.debug(e.getMessage());
+ logger.debug("Ops!",e);
+ }
+ }
+
+ private Settings readSettingsFromNodeList(NodeList settingNodes) throws MalformedXmlException {
+ Settings settings = new Settings();
+ for (int i = 0; i < settingNodes.getLength(); i++) {
+ Node settingNode = settingNodes.item(i);
+ ODFSetting setting = SettingFactory.create(settingNode);
+ settings.add(setting);
+ }
+ return settings;
+ }
+
+ public String getNewMetaContent(String metaContent) {
+ String cleanedMeta = getCleanedMetaContent(metaContent);
+ String newMetaContent = "";
+ try {
+ Document doc = getDocFromString(cleanedMeta);
+ for (Entry backup : backups.entrySet()) {
+ addXMLBackupEntries( doc, backup.getKey(), backup.getValue());
+ }
+ newMetaContent = serializeDoc(doc);
+ } catch (Exception e) {
+ logger.debug(e.getMessage());
+ logger.debug("Ops!",e);
+ }
+ return newMetaContent;
+ }
+
+
+
+ private void addXMLBackupEntries(Document doc, Long timeStamp, Settings settings) throws MalformedXmlException, ParserConfigurationException, TransformerException {
+ String backupString = serializeSettingsXML(settings);
+ backupString = encodeBase64(backupString);
+ List parts = splitByOOMaxLength(backupString);
+ for (int i = 0; i < parts.size();i++) {
+ String part = parts.get(i);
+ addMetadataEntry(doc,timeStamp,part, i);
+ }
+ }
+
+ private void addMetadataEntry(Document doc, Long timeStamp, String string, int i) throws MalformedXmlException {
+ Element element = doc.createElement("meta:user-defined");
+ String name = formatBackupMetaName(timeStamp,i);
+ element.setAttribute("meta:name", name);
+ element.setTextContent(string);
+ Node metaRoot = getMetaRootElement(doc);
+ metaRoot.appendChild(element);
+ }
+
+ private String formatBackupMetaName(Long timeStamp, int i) {
+ String result = BACKUP_NAME_PREFIX + timeStamp.toString() + "-" + i;
+ return result;
+ }
+
+ private List splitByOOMaxLength(String text) {
+ List array = new ArrayList();
+ int index = 0;
+ while (index < text.length()) {
+ array.add(text.substring(index, Math.min(index + OO_XML_MAX_LENGTH, text.length())));
+ index += OO_XML_MAX_LENGTH;
+ }
+ return array;
+ }
+
+ private String serializeSettingsXML(Settings settings) throws ParserConfigurationException, TransformerException {
+ Document doc = createNewDoc();
+ Element root = doc.createElementNS("urn:oasis:names:tc:opendocument:xmlns:config:1.0","config:config-item-set");
+ root.setAttribute("xmlns:config", "urn:oasis:names:tc:opendocument:xmlns:config:1.0");
+ root.setAttribute("xmlns:ooo", "http://openoffice.org/2004/office");
+ root.setAttribute("config:name", "ooo:configuration-settings");
+ doc.appendChild(root);
+ appendSettingNodesToDoc(settings, doc, root);
+ String result = serializeDoc(doc);
+ return result;
+ }
+
+
+ private String getCleanedMetaContent(String metaContent) {
+ String cleanedMetaContent = "";
+ try {
+ Document doc = getDocFromString(metaContent);
+ NodeList nodes = getBackupEntries(doc);
+ removeNodes(nodes);
+ cleanedMetaContent = serializeDoc(doc);
+ } catch (Exception e) {
+ logger.debug(e.getMessage());
+ logger.debug("Ops!",e);
+ }
+ return cleanedMetaContent;
+ }
+
+
+
+ private Map getSerializedBackups(Document doc) throws MalformedXmlException {
+ NodeList nodes = getBackupEntries(doc);
+ TreeMap> backupStringMap = sortBackupStrings(nodes);
+ Map result = combineBackupStrings(backupStringMap);
+ return result;
+ }
+
+ private NodeList getBackupEntries(Document doc) throws MalformedXmlException {
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr;
+ NodeList nodes;
+ try {
+ expr = xpath.compile("//*[local-name() ='user-defined'][@*[local-name() ='name' and starts-with(.,'"+ BACKUP_NAME_PREFIX + "')]]");
+ nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ } catch (XPathExpressionException e) {
+ logger.debug(e.getLocalizedMessage());
+ throw new MalformedXmlException("Xpath expression failed while looking for backup entries");
+ }
+ return nodes;
+ }
+
+ private Map combineBackupStrings(TreeMap> backupMap) {
+ TreeMap result = new TreeMap();
+ for(Map.Entry> entry: backupMap.entrySet()) {
+ Long timeStamp = entry.getKey();
+ Map parts = entry.getValue();
+ String combinedString = "";
+ for(Map.Entry part: parts.entrySet()) {
+ String serlializedString = part.getValue();
+ combinedString += serlializedString;
+ }
+ result.put(timeStamp, combinedString);
+ }
+ return result;
+ }
+
+ private TreeMap> sortBackupStrings(NodeList nodes) throws MalformedXmlException {
+ TreeMap> backupMap = new TreeMap>();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ Long timeStamp = getTimeStampFromNode(node);
+ Long order = getOrderFromNode(node);
+ String stringPart = node.getTextContent();
+ Map backupParts = null;
+ if (backupMap.containsKey(timeStamp)) {
+ backupParts = backupMap.get(timeStamp);
+ } else {
+ backupParts = new TreeMap();
+ backupMap.put(timeStamp, backupParts);
+ }
+ backupParts.put(order, stringPart);
+ }
+ return backupMap;
+ }
+
+ private Long getTimeStampFromNode(Node node) throws MalformedXmlException {
+ String name = node.getAttributes().getNamedItem("meta:name").getNodeValue();
+ String ts = getTimeStampFromString(name);
+ Long timeStamp = null;
+ try {
+ timeStamp = Long.parseLong(ts);
+ } catch (NumberFormatException e) {
+ throw new MalformedXmlException("timestamp not found for metadata backup entry " + name);
+ }
+ return timeStamp;
+ }
+
+ private String getTimeStampFromString(String name) {
+ return name.replaceAll(BACKUP_NAME_PREFIX, "").replaceAll("-[0-9]$","");
+ }
+
+ private Long getOrderFromNode(Node node) throws MalformedXmlException {
+ String name = node.getAttributes().getNamedItem("meta:name").getNodeValue();
+ String or = getOrderFromString(name);
+ Long order;
+ try {
+ order = Long.parseLong(or);
+ } catch (NumberFormatException e) {
+ throw new MalformedXmlException("order not found for metadata backup entry " + name);
+ }
+ return order;
+ }
+
+ private String getOrderFromString(String name) {
+ return name.replaceAll(BACKUP_NAME_PREFIX + "[0-9]+-", "");
+ }
+
+ private long getUnixTime() {
+ return System.currentTimeMillis() / 1000L;
+ }
+
+ public String[] getBackupTimeLabels() {
+ String[] timeLabels = new String[backups.size()];
+ int i = 0;
+ for(Long backupTime: backups.keySet()) {
+ timeLabels[i] = getDateTimeLabelsFromUnix(backupTime);
+ i++;
+ }
+ return timeLabels;
+ }
+
+ private String getDateTimeLabelsFromUnix(Long timeStamp){
+ ZoneId zoneId = TimeZone.getDefault().toZoneId();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
+ final Instant instant = Instant.ofEpochSecond(timeStamp);
+ LocalDateTime time = LocalDateTime.ofInstant(instant, zoneId);
+ return formatter.format(time);
+ }
+
+ private Long getUnixFromDateTimeLabels(String formattedTime){
+ ZoneId zoneId = TimeZone.getDefault().toZoneId();
+ DateTimeFormatter parseFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
+ LocalDateTime time = LocalDateTime.parse(formattedTime, parseFormat);
+ return time.atZone(zoneId).toEpochSecond();
+ }
+
+ public void removeByDateTimeLabel(String dateTimeLabel) {
+ if (dateTimeLabel == null || dateTimeLabel.isEmpty()) {
+ return;
+ }
+ Long unix = getUnixFromDateTimeLabels(dateTimeLabel);
+ backups.remove(unix);
+ }
+
+ public Settings getByDateTimeLabel(String dateTimeLabel) throws Exception {
+ if (dateTimeLabel == null || dateTimeLabel.isEmpty()) {
+ throw new Exception("date time not found");
+ }
+ Long unix = getUnixFromDateTimeLabels(dateTimeLabel);
+ Settings result = backups.get(unix);
+ if (result == null) {
+ throw new Exception("backup not found");
+ }
+ return result;
+ }
+
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java b/src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java
new file mode 100644
index 0000000..1eda21c
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java
@@ -0,0 +1,66 @@
+package pro.litvinovg.docsettings.settings;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ComparatorResults {
+
+ private static final Logger logger = LoggerFactory.getLogger(ComparatorResults.class);
+
+ private List removedSettings = new ArrayList();
+ private List newSetting = new ArrayList();
+ private Map changedSettings = new HashMap();
+
+ public boolean hasChanges() {
+ return removedSettings.isEmpty() && newSetting.isEmpty() && changedSettings.isEmpty();
+ }
+
+ public void addRemovedProperty(ODFSetting setting){
+ removedSettings.add(setting);
+ }
+
+ public void addNewProperty(ODFSetting setting){
+ newSetting.add(setting);
+ }
+
+ public void addChangedProperty(ODFSetting oldSetting,ODFSetting newSetting){
+ changedSettings.put(oldSetting, newSetting);
+ }
+
+ public List getRemovedProperties() {
+ return removedSettings;
+ }
+
+ public List getAddedProperties() {
+ return newSetting;
+ }
+
+ public Map getChangedProperties() {
+ return changedSettings;
+ }
+
+ public void log() {
+ logger.info("New settings:");
+ for (Iterator iterator = newSetting.iterator(); iterator.hasNext();) {
+ ODFSetting odfSetting = (ODFSetting) iterator.next();
+ logger.debug(odfSetting.getName());
+ }
+ logger.info("Removed settings:");
+ for (Iterator iterator = removedSettings.iterator(); iterator.hasNext();) {
+ ODFSetting odfSetting = (ODFSetting) iterator.next();
+ logger.info(odfSetting.getName());
+ }
+ logger.info("Changed settings:");
+ for (Entry entry : changedSettings.entrySet()) {
+ logger.info(entry.getKey().getName() + "\nChanged from "+ entry.getKey().getValue() + " to " + entry.getValue().getValue() );
+ }
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java b/src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java
new file mode 100644
index 0000000..00cf036
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java
@@ -0,0 +1,11 @@
+package pro.litvinovg.docsettings.settings;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+public interface ODFSetting {
+ public String getName();
+ public String getValue();
+ public String getType();
+ public void addToDocumentElement(Document doc, Node root);
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java b/src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java
new file mode 100644
index 0000000..bf9874e
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java
@@ -0,0 +1,24 @@
+package pro.litvinovg.docsettings.settings;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.star.uno.XComponentContext;
+
+import pro.litvinovg.docsettings.DialogHelper;
+
+public class ODFSettings {
+
+ private XComponentContext context;
+ private static final Logger logger = LoggerFactory.getLogger(ODFSettings.class);
+
+ public ODFSettings(XComponentContext context ) {
+ this.context = context;
+ }
+
+ public void printText(String text) {
+ DialogHelper.showErrorMessage(context, null, text);
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java b/src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java
new file mode 100644
index 0000000..17a1a17
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java
@@ -0,0 +1,64 @@
+package pro.litvinovg.docsettings.settings;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
+
+import pro.litvinovg.docsettings.exceptions.MalformedXmlException;
+
+import static pro.litvinovg.docsettings.settings.StorageUtils.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import pro.litvinovg.docsettings.exceptions.MalformedXmlException;
+
+public class SettingFactory {
+ private static final String TYPE_INT = "int";
+ private static final String TYPE_BASE64_BINARY = "base64Binary";
+ private static final String TYPE_STRING = "string";
+ private static final String TYPE_SHORT = "short";
+ private static final String TYPE_BOOLEAN = "boolean";
+ private static final Logger logger = LoggerFactory.getLogger(SettingFactory.class);
+
+ public static ODFSetting create(Node node) throws MalformedXmlException {
+ Node typeNode = node.getAttributes().getNamedItem("config:type");
+ if (typeNode == null) {
+ logger.debug("nodeType is null");
+ throw new MalformedXmlException("Node " + node.getNodeName() + " config:type attribute not found.");
+ }
+ String type = typeNode.getNodeValue();
+ if (type == null || type.isEmpty()) {
+ logger.debug("node type value is null or empty");
+ throw new MalformedXmlException("Node " + node.getNodeName() + " config:type attribute not found.");
+ }
+ if (TYPE_BOOLEAN.equals(type)||
+ TYPE_SHORT.equals(type)||
+ TYPE_STRING.equals(type)||
+ TYPE_BASE64_BINARY.equals(type)||
+ TYPE_INT.equals(type)) {
+ return new SimpleSetting(node);
+ }
+ logger.debug("node type not recognized");
+ throw new MalformedXmlException("Node " + node.getNodeName());
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/Settings.java b/src/main/java/pro/litvinovg/docsettings/settings/Settings.java
new file mode 100644
index 0000000..68b76a8
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/Settings.java
@@ -0,0 +1,78 @@
+package pro.litvinovg.docsettings.settings;
+
+import static pro.litvinovg.docsettings.settings.StorageUtils.*;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class Settings {
+ private static final Logger logger = LoggerFactory.getLogger(Settings.class);
+
+
+ private List settings = null;
+
+ public Settings() {
+ this.settings = new ArrayList();
+ }
+
+ public void add(ODFSetting setting) {
+ settings.add(setting);
+ }
+
+ public void remove(int index) {
+ settings.remove(index);
+ }
+
+ public void remove(ODFSetting setting) {
+ settings.remove(setting);
+ }
+
+ public int getSize() {
+ return settings.size();
+ }
+
+ public ODFSetting get(int num) {
+ return settings.get(num);
+ }
+
+ public ODFSetting getByName(String name) {
+ for (Iterator iterator = settings.iterator(); iterator.hasNext();) {
+ ODFSetting odfSetting = (ODFSetting) iterator.next();
+ if (name.equals(odfSetting.getName())) {
+ return odfSetting;
+ }
+ }
+ return null;
+ }
+
+ public void clear() {
+ settings.clear();
+ }
+
+ public boolean isEmpty() {
+ return settings.isEmpty();
+ }
+
+ public String toSettingsContent(String content) throws Exception {
+ Document doc = getDocFromString(content);
+ Node settingsContainer = getSettingsElement(doc);
+ NodeList settingNodes = settingsContainer.getChildNodes();
+ removeNodes(settingNodes);
+ appendSettingNodesToDoc(this, doc, settingsContainer);
+ String newSettingsContent = serializeDoc(doc);
+ return newSettingsContent;
+ }
+
+ public void addAll(Settings inputSettings) {
+ for (int i = 0; i < inputSettings.getSize(); i++) {
+ this.add(inputSettings.get(i));
+ }
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java b/src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java
new file mode 100644
index 0000000..ee2331d
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java
@@ -0,0 +1,48 @@
+package pro.litvinovg.docsettings.settings;
+
+public class SettingsComparator {
+
+ private static final String RSID = "Rsid";
+
+ public static boolean isEqual(Settings currentSettings, Settings backedUpSettings) {
+ Settings newSettings = new Settings();
+ newSettings.addAll(currentSettings);
+ Settings oldSettings = new Settings();
+ oldSettings.addAll(backedUpSettings);
+ ComparatorResults results = compare(newSettings, oldSettings);
+ if (results.hasChanges()) {
+ results.log();
+ }
+ return !results.hasChanges();
+ }
+
+ private static ComparatorResults compare(Settings newSettings, Settings oldSettings) {
+ ComparatorResults results = new ComparatorResults();
+ while (!newSettings.isEmpty()) {
+ ODFSetting newSet = newSettings.get(0);
+ ODFSetting oldSet = oldSettings.getByName(newSet.getName());
+ if (oldSet == null) {
+ results.addNewProperty(newSet);
+ } else {
+ if (!(newSet.getValue()).equals(oldSet.getValue()) && !isExcluded(newSet.getName())) {
+ results.addChangedProperty(oldSet, newSet);
+ }
+ oldSettings.remove(oldSet);
+ }
+ newSettings.remove(newSet);
+ }
+ while (!oldSettings.isEmpty()) {
+ ODFSetting oldSet = oldSettings.get(0);
+ results.addRemovedProperty(oldSet);
+ }
+ return results;
+ }
+
+ private static boolean isExcluded(String name) {
+ if (name.equals(RSID)) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java b/src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java
new file mode 100644
index 0000000..2fbcb0b
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java
@@ -0,0 +1,53 @@
+package pro.litvinovg.docsettings.settings;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import pro.litvinovg.docsettings.exceptions.MalformedXmlException;
+
+public class SimpleSetting implements ODFSetting {
+
+ private static final Logger logger = LoggerFactory.getLogger(SimpleSetting.class);
+ protected String name;
+ protected String value;
+ protected String type;
+
+ public SimpleSetting(Node node) throws MalformedXmlException {
+ this.name = getNodeName(node);
+ this.value = node.getTextContent();
+ this.type = node.getAttributes().getNamedItem("config:type").getNodeValue();
+ }
+ protected String getNodeName(Node node) throws MalformedXmlException {
+ Node nameAttr = node.getAttributes().getNamedItem("config:name");
+ if (nameAttr == null) {
+ logger.debug("name attribute not found while creating simple property");
+ throw new MalformedXmlException("Node " + node.getNodeName() + " config:name attribute not found.");
+ }
+ return nameAttr.getNodeValue();
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+ @Override
+ public String getType() {
+ return type;
+ }
+ @Override
+ public void addToDocumentElement(Document doc, Node rootElement) {
+ Element element = doc.createElement("config:config-item");
+ element.setAttribute("config:name", getName());
+ element.setAttribute("config:type", getType());
+ element.setTextContent(getValue());
+ rootElement.appendChild(element);
+ }
+}
diff --git a/src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java b/src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java
new file mode 100644
index 0000000..02f5ee2
--- /dev/null
+++ b/src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java
@@ -0,0 +1,126 @@
+package pro.litvinovg.docsettings.settings;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Base64;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import pro.litvinovg.docsettings.exceptions.MalformedXmlException;
+
+public class StorageUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger(StorageUtils.class);
+
+ public static Document getDocFromString(String xmlString) throws ParserConfigurationException, SAXException, IOException {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ Document doc = db.parse(new InputSource(new StringReader(xmlString)));
+ return doc;
+ }
+
+ public static Document createNewDoc() throws ParserConfigurationException {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ Document doc = db.newDocument();
+ return doc;
+ }
+
+ public static String serializeDoc(Document doc) throws TransformerException {
+ StringWriter output = new StringWriter();
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.transform(new DOMSource(doc), new StreamResult(output));
+ String str = output.toString();
+ return str;
+ }
+
+ public static String printNode(Node node) throws TransformerException {
+ StringWriter output = new StringWriter();
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.transform(new DOMSource(node), new StreamResult(output));
+ String str = output.toString();
+ return str;
+ }
+
+
+ public static String encodeBase64(String string) {
+ return new String(Base64.getEncoder().encode(string.getBytes()));
+ }
+
+ public static String decodeBase64(String serializedMetaSettings) {
+ return new String(Base64.getDecoder().decode(serializedMetaSettings));
+ }
+
+ public static void removeNodes(NodeList nodes) {
+ for (int i = nodes.getLength()-1; i >= 0; i--) {
+ Node node = nodes.item(i);
+ node.getParentNode().removeChild(node);
+ }
+ }
+
+ public static void appendSettingNodesToDoc(Settings settings, Document doc, Node root) {
+ for(int i = 0; i< settings.getSize();i++) {
+ ODFSetting setting = settings.get(i);
+ setting.addToDocumentElement(doc,root);
+ }
+ }
+
+ public static Node getSettingsElement(Document doc) throws Exception {
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr = xpath.compile("//*[local-name() ='config-item-set'][@*[local-name() ='name' and .='ooo:configuration-settings']]");
+ NodeList configItems = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ Node configSettings = configItems.item(0);
+ if (configSettings == null) {
+ throw new MalformedXmlException("No configuration settings element found");
+ }
+ return configSettings;
+ }
+
+ public static Node getMetaRootElement(Document doc) throws MalformedXmlException {
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr;
+ NodeList nodes;
+ try {
+ expr = xpath.compile("//*[local-name() ='meta']");
+ nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ if (nodes.getLength() == 0 ) {
+ throw new MalformedXmlException("Xpath expression failed while looking for main meta element");
+ }
+ } catch (XPathExpressionException e) {
+ logger.debug(e.getLocalizedMessage());
+ logger.debug("Ops!",e);
+ throw new MalformedXmlException("Xpath expression failed while looking for main meta element");
+ }
+ Node result = nodes.item(0);
+ return result;
+ }
+
+
+
+}
diff --git a/src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java b/src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java
new file mode 100644
index 0000000..b359e95
--- /dev/null
+++ b/src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java
@@ -0,0 +1,14 @@
+package pro.litvinovg.docsettings.exceptions;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class MalformedXmlExceptionTest {
+
+ @Test
+ void test() {
+ fail("Not yet implemented");
+ }
+
+}
diff --git a/src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java b/src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java
new file mode 100644
index 0000000..0437935
--- /dev/null
+++ b/src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java
@@ -0,0 +1,14 @@
+package pro.litvinovg.docsettings.processor;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class ODFSettingsTest {
+
+ @Test
+ void test() {
+ fail("Not yet implemented");
+ }
+
+}
diff --git a/testFiles/settings-old.xml b/testFiles/settings-old.xml
new file mode 100644
index 0000000..dbd7bdd
--- /dev/null
+++ b/testFiles/settings-old.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ 0
+ 0
+ 21625
+ 9213
+ true
+ false
+
+
+ view2
+ 3401
+ 2501
+ 0
+ 0
+ 21624
+ 9211
+ 0
+ 1
+ false
+ 214
+ false
+ true
+
+
+
+
+ false
+
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ 0
+ true
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 517402
+ true
+ false
+ true
+ true
+ true
+ true
+ false
+ false
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ 1
+ 0
+
+ 0
+ false
+ true
+ false
+
+ false
+ false
+
+ true
+
+ false
+ true
+ false
+ false
+
+ false
+ true
+ true
+ false
+ true
+ high-resolution
+ false
+ true
+ true
+ true
+ false
+ true
+ false
+
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ true
+ 648908
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ true
+ true
+
+
+
diff --git a/testFiles/settings.xml b/testFiles/settings.xml
new file mode 100644
index 0000000..c457777
--- /dev/null
+++ b/testFiles/settings.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ 0
+ 0
+ 21625
+ 9213
+ true
+ false
+
+
+ view2
+ 3401
+ 2501
+ 0
+ 0
+ 21624
+ 9211
+ 0
+ 1
+ false
+ 214
+ false
+ true
+
+
+
+
+ false
+
+ false
+ false
+ false
+ false
+ true
+ true
+ true
+ 0
+ true
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ 517402
+ false
+ false
+ true
+ false
+ true
+ true
+ false
+ false
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ true
+ false
+ false
+ 1
+ 0
+
+ 0
+ false
+ true
+ false
+
+ false
+ false
+
+ false
+
+ false
+ true
+ false
+ false
+
+ false
+ true
+ false
+ false
+ true
+ high-resolution
+ false
+ true
+ true
+ true
+ false
+ true
+ false
+
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ true
+ 639374
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+
+
+
diff --git a/testFiles/testFile (copy).odt b/testFiles/testFile (copy).odt
new file mode 100644
index 0000000..f77a76b
Binary files /dev/null and b/testFiles/testFile (copy).odt differ
diff --git a/testFiles/testFile.odt b/testFiles/testFile.odt
new file mode 100644
index 0000000..97f16dd
Binary files /dev/null and b/testFiles/testFile.odt differ