← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~verzegnassi-stefano/ubuntu-docviewer-app/10-reboot-contenthub-switch-to-qml-apis into lp:ubuntu-docviewer-app/reboot

 

Stefano Verzegnassi has proposed merging lp:~verzegnassi-stefano/ubuntu-docviewer-app/10-reboot-contenthub-switch-to-qml-apis into lp:ubuntu-docviewer-app/reboot.

Commit message:
* Use QML APIs for Content Hub
* Remove toast notifications for imported documents (open documents automatically)

Requested reviews:
  Ubuntu Document Viewer Developers (ubuntu-docviewer-dev)
Related bugs:
  Bug #1469422 in Ubuntu Document Viewer App: "[Doc Viewer] Opening a file from content-hub should open the file or the notfication timeout should be increased"
  https://bugs.launchpad.net/ubuntu-docviewer-app/+bug/1469422

For more details, see:
https://code.launchpad.net/~verzegnassi-stefano/ubuntu-docviewer-app/10-reboot-contenthub-switch-to-qml-apis/+merge/271466

* Use QML APIs for Content Hub
* Remove toast notifications for imported documents (open documents automatically)

*** NOTE ***
In DocviewerApplication and CommandLineParser classes, the argument "--pickMode" is broken.

Since we are going to move the arguments parser in QML too, that will be solved with a future commit.

At the moment, that argument is not used by Autopilot tests and the "reboot" branch is not released yet, therefore no relevant issue has been introduced.
-- 
Your team Ubuntu Document Viewer Developers is requested to review the proposed merge of lp:~verzegnassi-stefano/ubuntu-docviewer-app/10-reboot-contenthub-switch-to-qml-apis into lp:ubuntu-docviewer-app/reboot.
=== modified file 'src/app/CMakeLists.txt'
--- src/app/CMakeLists.txt	2015-09-02 11:31:45 +0000
+++ src/app/CMakeLists.txt	2015-09-19 11:41:47 +0000
@@ -10,7 +10,6 @@
 
 set(docviewer_SRCS
     main.cpp
-    content-communicator.cpp
     command-line-parser.cpp
     docviewer-application.cpp
     urlhandler.cpp

=== removed file 'src/app/content-communicator.cpp'
--- src/app/content-communicator.cpp	2015-07-14 01:35:59 +0000
+++ src/app/content-communicator.cpp	1970-01-01 00:00:00 +0000
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2013 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "content-communicator.h"
-
-#include <QApplication>
-#include <QStandardPaths>
-#include <QMimeDatabase>
-#include <QDebug>
-#include <QFileInfo>
-
-#include <com/ubuntu/content/hub.h>
-#include <com/ubuntu/content/item.h>
-#include <com/ubuntu/content/transfer.h>
-
-
-using namespace com::ubuntu::content;
-
-/*!
- * \brief ContentCommunicator::ContentCommunicator
- * \param parent
- */
-ContentCommunicator::ContentCommunicator(QObject *parent)
-    : ImportExportHandler(parent),
-      m_transfer(nullptr)
-{
-}
-
-/*!
- * \brief ContentCommunicator::registerWithHub Register the handlers provided
- * by ContentCommunicator with the content hub
- */
-void ContentCommunicator::registerWithHub()
-{
-    Hub *hub = Hub::Client::instance();
-    hub->register_import_export_handler(this);
-}
-
-/*!
- * \brief \reimp
- */
-void ContentCommunicator::handle_import(content::Transfer *transfer)
-{
-    // FIXME: If a file is imported from $HOME/Documents, a new copy of the file is created.
-    //   Could be use md5? http://doc.qt.io/qt-5/qml-qtqml-qt.html#md5-method
-    QVariantList importedDocuments;
-    QVector<Item> transferedItems = transfer->collect();
-    foreach (const Item &hubItem, transferedItems) {
-        QFileInfo fi(hubItem.url().toLocalFile());
-
-        QString dir;
-        QString destination;
-        bool rejected = false;
-
-        QMimeDatabase mdb;
-        QMimeType mt = mdb.mimeTypeForFile(hubItem.url().toLocalFile());
-
-        // Check if the item is supported by Ubuntu Document Viewer
-        if (isSupportedMimetype(mt.name())) {
-            /* We don't support formats that use a double extension
-               (e.g. tar.gz), so we can safely use completeBaseName() and
-               suffix() functions, in order to properly detect the name of
-               the document even when there's a dot in the middle of the name.*/
-            QString suffix = fi.suffix();
-            QString filenameWithoutSuffix = fi.completeBaseName();
-
-            if(suffix.isEmpty()) {
-                // If the filename doesn't have an extension add one from the
-                // detected mimetype
-                if(!mt.preferredSuffix().isEmpty()) {
-                    suffix = mt.preferredSuffix();
-                }
-            }
-
-            dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + QDir::separator();
-            destination = QString("%1.%2").arg(dir + filenameWithoutSuffix, suffix);
-
-            // If we already have a file of this name reformat to "filename (copy x).png"
-            // (where x is a number, incremented until we find an available filename)
-            if(QFile::exists(destination)) {
-                /*
-                 TRANSLATORS: This string is used for renaming a copied file,
-                 when a file with the same name already exists in user's
-                 Documents folder.
-
-                 e.g. "Manual_Aquaris_E4.5_ubuntu_EN.pdf" will become
-                      "Manual_Aquaris_E4.5_ubuntu_EN (copy 2).pdf"
-
-                      where "2" is given by the argument "%1"
-                */
-                QString reformattedSuffix = QString(_("copy %1"));
-
-                QRegExp rx(" \\(" + reformattedSuffix.arg(QString("\\d+")) + "\\)");
-                int reformattedSuffixPos = filenameWithoutSuffix.lastIndexOf(rx);
-
-                // Check if the file has already a "copy" suffix
-                if(reformattedSuffixPos != -1) {
-                    // Remove the "copy" suffix. We will re-put it later.
-                    filenameWithoutSuffix.truncate(reformattedSuffixPos);
-                }
-
-                int append = 1;
-                do {
-                    destination = QString("%1 (%2).%3").arg(dir + filenameWithoutSuffix,
-                                                            reformattedSuffix.arg(QString::number(append)),
-                                                            suffix);
-                    append++;
-                } while(QFile::exists(destination));
-            }
-
-            QFile::copy(hubItem.url().toLocalFile(), destination);
-        } else {
-            rejected = true;
-        }
-
-        // Append an entry for the imported document in the list that will be
-        // emitted with the 'documentImported' signal.
-        QVariantMap entry;
-        if (rejected) {
-            entry["fileName"] = fi.fileName();
-        } else {
-            entry["fileName"] = destination;
-        }
-        entry["rejected"] = rejected;
-
-        importedDocuments.append(entry);
-    }
-
-    // Allow content-hub to clean up temporary files in .cache/ once we've
-    // moved them
-    transfer->finalize();
-
-    emit documentImported(importedDocuments);
-}
-
-/*!
- * \brief \reimp
- */
-void ContentCommunicator::handle_export(content::Transfer *transfer)
-{
-    if (m_transfer != nullptr) {
-        qWarning() << "docviewer-app does only one content export at a time";
-        transfer->abort();
-        return;
-    }
-
-    m_transfer = transfer;
-    emit documentRequested();
-    emit selectionTypeChanged();
-    emit singleContentPickModeChanged();
-}
-
-/*!
- * \brief \reimp
- */
-void ContentCommunicator::handle_share(content::Transfer *)
-{
-    qDebug() << Q_FUNC_INFO << "docviewer does not share content";
-}
-
-/*!
- * \brief ContentCommunicator::cancelTransfer aborts the current transfer
- */
-void ContentCommunicator::cancelTransfer()
-{
-    if (!m_transfer) {
-        qWarning() << "No ongoing transfer to cancel";
-        return;
-    }
-
-    m_transfer->abort();
-    m_transfer = nullptr;
-}
-
-/*!
- * \brief ContentCommunicator::returnSocuments returns the given documents
- * via content hub to the requester
- * \param urls
- */
-void ContentCommunicator::returnDocuments(const QVector<QUrl> &urls)
-{
-    if (!m_transfer) {
-        qWarning() << "No ongoing transfer to return a document";
-        return;
-    }
-
-    QVector<Item> items;
-    items.reserve(urls.size());
-    foreach (const QUrl &url, urls) {
-        items.append(Item(url));
-    }
-
-    m_transfer->charge(items);
-    m_transfer = nullptr;
-}
-
-/*!
- * \brief ContentCommunicator::selectionType return if the transfer requests
- * one single item only, or multiple
- * \return
- */
-ContentCommunicator::SelectionType ContentCommunicator::selectionType() const
-{
-    if (!m_transfer)
-        return SingleSelect;
-
-    return static_cast<SelectionType>(m_transfer->selectionType());
-}
-
-/*!
- * \brief ContentCommunicator::singleContentPickMode
- * \return
- */
-bool ContentCommunicator::singleContentPickMode() const
-{
-    if (!m_transfer)
-        return true;
-
-    // FIXME: Shouldn't be Transfer::SelectionType::SingleSelect?
-    return m_transfer->selectionType() == Transfer::SelectionType::single;
-}
-
-/*!
- * \brief ContentCommunicator::isSupportedMimetype returns true if the given
- * mimetype is supported by Ubuntu Document Viewer
- * \param mimetype
- */
-bool ContentCommunicator::isSupportedMimetype(QString mimetype)
-{
-    // TODO: We should use a common shared code for DocumentViewer.DocumentsModel
-    // QML component and ContentHub. That will happen when we'll switch to
-    // QML ContentHub APIs.
-    return (mimetype.startsWith("text/")
-            || mimetype == "application/pdf"
-            || mimetype.startsWith("application/vnd.oasis.opendocument")
-            || mimetype == "application/msword")
-            || mimetype == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
-            || mimetype == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
-            || mimetype == "application/vnd.openxmlformats-officedocument.presentationml.presentation"
-            || mimetype == "application/msword"
-            || mimetype == "application/vnd.ms-excel"
-            || mimetype == "application/vnd.ms-powerpoint";
-}

=== removed file 'src/app/content-communicator.h'
--- src/app/content-communicator.h	2015-04-20 16:24:06 +0000
+++ src/app/content-communicator.h	1970-01-01 00:00:00 +0000
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2013 Canonical, Ltd.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#ifndef CONTENTCOMMUNICATOR_H
-#define CONTENTCOMMUNICATOR_H
-
-#include <com/ubuntu/content/import_export_handler.h>
-#include <com/ubuntu/content/transfer.h>
-
-#include <QUrl>
-#include <QVector>
-#include <libintl.h>
-
-#define _(value) dgettext(GETTEXT_PACKAGE, value)
-
-using namespace com::ubuntu;
-
-/*!
- * Class to handle the communication with the content manager
- */
-class ContentCommunicator : public content::ImportExportHandler
-{
-    Q_OBJECT
-    Q_PROPERTY(bool singleContentPickMode READ singleContentPickMode NOTIFY singleContentPickModeChanged)
-    Q_PROPERTY(SelectionType selectionType READ selectionType NOTIFY selectionTypeChanged)
-    Q_ENUMS(SelectionType)
-
-public:
-    enum SelectionType {
-        SingleSelect = content::Transfer::single,
-        MultiSelect = content::Transfer::multiple
-    };
-
-    ContentCommunicator(QObject *parent = nullptr);
-
-    virtual void handle_import(content::Transfer*);
-    virtual void handle_export(content::Transfer *transfer);
-    virtual void handle_share(content::Transfer*);
-
-    void cancelTransfer();
-    void returnDocuments(const QVector<QUrl> &urls);
-
-    SelectionType selectionType() const;
-    bool singleContentPickMode() const;
-
-    void registerWithHub();
-
-signals:
-    void documentRequested();
-    void documentImported(QVariantList documents);
-    void selectionTypeChanged();
-    void singleContentPickModeChanged();
-
-private:
-    content::Transfer *m_transfer;
-
-    bool isSupportedMimetype(QString mimetype);
-};
-
-#endif // CONTENTCOMMUNICATOR_H

=== modified file 'src/app/docviewer-application.cpp'
--- src/app/docviewer-application.cpp	2015-04-29 15:23:32 +0000
+++ src/app/docviewer-application.cpp	2015-09-19 11:41:47 +0000
@@ -18,7 +18,6 @@
  */
 
 #include "docviewer-application.h"
-#include "content-communicator.h"
 #include "command-line-parser.h"
 #include "urlhandler.h"
 
@@ -38,9 +37,6 @@
 DocViewerApplication::DocViewerApplication(int& argc, char** argv)
     : QApplication(argc, argv),
       m_view(new QQuickView()),
-      m_contentCommunicator(new ContentCommunicator(this)),
-      m_pickModeEnabled(false),
-      m_defaultUiMode(BrowseContentMode),
       m_documentFile(""),
       m_documentLoaded(false)
 {
@@ -73,15 +69,10 @@
 
     registerQML();
 
-    if (m_cmdLineParser->pickModeEnabled())
+    // FIXME: Broken after removal of it.
+    /*if (m_cmdLineParser->pickModeEnabled())
         setDefaultUiMode(DocViewerApplication::PickContentMode);
-
-    QObject::connect(m_contentCommunicator, SIGNAL(documentRequested()),
-                     this, SLOT(switchToPickMode()));
-
-    QObject::connect(m_contentCommunicator, SIGNAL(documentImported()),
-                     this, SLOT(switchToBrowseMode()));
-
+*/
     return true;
 }
 
@@ -172,7 +163,6 @@
 
     // Set ourselves up to expose functionality to run external commands from QML...
     m_view->engine()->rootContext()->setContextProperty("DOC_VIEWER", this);
-    m_view->engine()->rootContext()->setContextProperty("PICKER_HUB", m_contentCommunicator);
 
     QObject::connect(m_view->engine(), SIGNAL(quit()), this, SLOT(quit()));
 
@@ -201,7 +191,6 @@
         qFatal("File: %s does not exist at any of the standard paths!", qPrintable(filePath));
     }
 
-    registerHub();
     m_view->setSource(QUrl::fromLocalFile(qmlfile));
     setDocumentFile(m_cmdLineParser->documentFile());
 
@@ -217,57 +206,6 @@
 }
 
 /*!
- * \brief DocViewerApplication::setDefaultUiMode set the default UI mode. This might
- * get overridden during the lifetime
- * \param mode
- */
-void DocViewerApplication::setDefaultUiMode(DocViewerApplication::UiMode mode)
-{
-    m_defaultUiMode = mode;
-    setUiMode(mode);
-}
-
-/*!
- * \brief DocViewerApplication::setUiMode set's the current UI mode
- * \param mode
- */
-void DocViewerApplication::setUiMode(DocViewerApplication::UiMode mode)
-{
-    bool enablePickMode = (mode == PickContentMode);
-
-    if (enablePickMode != m_pickModeEnabled) {
-        m_pickModeEnabled = enablePickMode;
-        Q_EMIT pickModeEnabledChanged();
-    }
-}
-
-/*!
- * \brief DocViewerApplication::pickModeEnabled returns true if the current UI
- * mode should be for picking acontent
- * \return
- */
-bool DocViewerApplication::pickModeEnabled() const
-{
-    return m_pickModeEnabled;
-}
-
-/*!
- * \brief DocViewerApplication::switchToPickMode
- */
-void DocViewerApplication::switchToPickMode()
-{
-    setUiMode(PickContentMode);
-}
-
-/*!
- * \brief DocViewerApplication::switchToBrowseMode
- */
-void DocViewerApplication::switchToBrowseMode()
-{
-    Q_EMIT browseModeRequested();
-}
-
-/*!
  * \brief DocViewerApplication::setFullScreen
  * Change window state to fullScreen or no state
  */
@@ -292,50 +230,6 @@
     }
 }
 
-/*!
- * \brief DocViewerApplication::returnPickedContent passes the selcted items to the
- * content manager
- * \param variant
- */
-void DocViewerApplication::returnPickedContent(QList<QString> paths)
-{
-    QVector<QUrl> selectedMedias;
-    selectedMedias.reserve(paths.size());
-    foreach (const QString path, paths) {
-        // We handle paths without "file://" prefix, so we need to add it when exporting to content-hub.
-        selectedMedias.append(QUrl("file://" + path));
-    }
-    m_contentCommunicator->returnDocuments(selectedMedias);
-
-    if (m_defaultUiMode == BrowseContentMode) {
-        setUiMode(BrowseContentMode);
-    } else {
-        // give the app and content-hub some time to finish taks (run the event loop)
-        QTimer::singleShot(10, this, SLOT(quit()));
-    }
-}
-
-/*!
- * \brief DocViewerApplication::contentPickingCanceled tell the content manager, that
- * the picking was canceled
- */
-void DocViewerApplication::contentPickingCanceled()
-{
-    m_contentCommunicator->cancelTransfer();
-
-    if (m_defaultUiMode == BrowseContentMode) {
-        setUiMode(BrowseContentMode);
-    } else {
-        // give the app and content-hub some time to finish taks (run the event loop)
-        QTimer::singleShot(10, this, SLOT(quit()));
-    }
-}
-
-void DocViewerApplication::registerHub()
-{
-    m_contentCommunicator->registerWithHub();
-}
-
 void DocViewerApplication::parseUri(const QString &arg)
 {
     if (m_urlHandler->processUri(arg)) {

=== modified file 'src/app/docviewer-application.h'
--- src/app/docviewer-application.h	2015-04-29 15:23:32 +0000
+++ src/app/docviewer-application.h	2015-09-19 11:41:47 +0000
@@ -37,62 +37,42 @@
 class DocViewerApplication : public QApplication
 {
     Q_OBJECT
-    Q_PROPERTY(bool pickModeEnabled READ pickModeEnabled NOTIFY pickModeEnabledChanged)
     Q_PROPERTY(bool desktopMode READ isDesktopMode CONSTANT)
     Q_PROPERTY(bool fullScreen READ isFullScreen WRITE setFullScreen NOTIFY fullScreenChanged)
     Q_PROPERTY(QString documentFile READ getDocumentFile WRITE setDocumentFile NOTIFY documentFileChanged)
     Q_PROPERTY(QString documentsDir READ getDocumentsDir CONSTANT)
 
 public:
-    enum UiMode{
-        BrowseContentMode,
-        PickContentMode
-    };
-
     explicit DocViewerApplication(int& argc, char** argv);
     virtual ~DocViewerApplication();
 
     bool init();
     int exec();
 
-    void setDefaultUiMode(UiMode mode);
-    UiMode defaultUiMode() const;
-    void setUiMode(UiMode mode);
-    bool pickModeEnabled() const;
     bool isDesktopMode() const;
     bool isFullScreen() const;
     const QString &getDocumentFile() const;
     const QString &getDocumentsDir() const;
 
-    Q_INVOKABLE void returnPickedContent(QList<QString> paths);
-    Q_INVOKABLE void contentPickingCanceled();
     Q_INVOKABLE void parseUri(const QString &arg);
     Q_INVOKABLE void releaseResources();
 
 signals:
-    void pickModeEnabledChanged();
     void fullScreenChanged();
     void documentFileChanged();
-    void browseModeRequested();
 
 private slots:
-    void switchToPickMode();
-    void switchToBrowseMode();
     void setFullScreen(bool fullScreen);
     void setDocumentFile(const QString &documentFile);
 
 private:
-    void registerHub();
     void registerQML();
     void createView();
 
     QQuickView *m_view;
     CommandLineParser* m_cmdLineParser;
     UrlHandler *m_urlHandler;
-    ContentCommunicator *m_contentCommunicator;
 
-    bool m_pickModeEnabled;
-    UiMode m_defaultUiMode;
     QString m_documentFile;
     bool m_documentLoaded;
 };

=== added file 'src/app/qml/common/ContentHubProxy.qml'
--- src/app/qml/common/ContentHubProxy.qml	1970-01-01 00:00:00 +0000
+++ src/app/qml/common/ContentHubProxy.qml	2015-09-19 11:41:47 +0000
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012-2014 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Content 1.1
+import DocumentViewer 1.0
+
+// TODO: Show a dialog asking for the destination (internal storage or SD card)
+
+Item {
+    id: contentHubProxy
+
+    property var activeTransfer
+
+    property alias rejectedDocuments: rejectedDocsModel
+    property alias importedDocuments: importedDocsModel
+
+    property bool multipleSelection: activeTransfer ? (activeTransfer.selectionType !== ContentTransfer.Single) : true
+
+    ListModel { id: rejectedDocsModel }
+    ListModel { id: importedDocsModel }
+
+    ContentTransferHint {
+        activeTransfer: contentHubProxy.activeTransfer
+    }
+
+    Connections {
+        target: ContentHub
+
+        onImportRequested: {
+             activeTransfer = transfer;
+
+            if (activeTransfer.state === ContentTransfer.Charged) {
+                mainView.switchToBrowseMode()
+
+                internal.clearModels()
+
+                for (var i=0; i<activeTransfer.items.length; i++) {
+                    var sourcePath = internal.getPathFromUrl(activeTransfer.items[i].url)
+
+                    if (DocumentViewer.isFileSupported(sourcePath)) {
+                        var documentsLocation = DocumentViewer.getXdgDocumentsLocation()
+                        var destPath = DocumentViewer.buildDestinationPath(documentsLocation, sourcePath);
+
+                        internal.importDocument(sourcePath, destPath)
+
+                    } else {
+                        // Document is not supported, append its entry into the
+                        // rejected documents model, so that we can inform the
+                        // user of what happened.
+                        rejectedDocsModel.append({ path: sourcePath })
+                    }
+                }
+
+                internal.finalizeImport()
+
+                internal.handleNotifications()
+            }
+        }
+
+        onExportRequested: {
+            activeTransfer = transfer;
+            mainView.switchToPickMode()
+        }
+    }
+
+    QtObject {
+        id: internal
+
+        function __openDocument() {
+            if (contentHubProxy.importedDocuments.count > 1) {
+                // If it has been imported more than a document, show
+                // a file picker when user taps the "open" action.
+                PopupUtils.open(
+                            Qt.resolvedUrl("common/PickImportedDialog.qml"),
+                            mainView,
+                            {
+                                parent: mainView,
+                                model: contentHubProxy.importedDocuments
+                            });
+            } else {
+                // It has been imported just a document, open it when
+                // user taps the action button.
+                mainView.openDocument(contentHubProxy.importedDocuments.get(0).path);
+            }
+        }
+
+        function clearModels() {
+            rejectedDocsModel.clear()
+            importedDocsModel.clear()
+        }
+
+        function getPathFromUrl(url) {
+            return url.toString().replace("file://", "")
+        }
+
+        function importDocument(sourcePath, destPath) {
+            DocumentViewer.copy(sourcePath, destPath);
+            importedDocsModel.append({ path: destPath })
+        }
+
+        function finalizeImport() {
+            activeTransfer.finalize()
+        }
+
+        function handleNotifications() {
+            // Check if there's any rejected document in the last transfer.
+            // If so, show an error dialog.
+            if (contentHubProxy.rejectedDocuments.count > 0) {
+                var rejectedDialog = PopupUtils.open(
+                            Qt.resolvedUrl("common/RejectedImportDialog.qml"),
+                            mainView,
+                            {
+                                parent: mainView,
+                                model: contentHubProxy.rejectedDocuments
+                            });
+                rejectedDialog.closed.connect(openDocument)
+            } else {
+                // Open the document, or show a pick dialog if more than one have been imported.
+                __openDocument()
+            }
+        }
+    }
+}

=== modified file 'src/app/qml/common/PickImportedDialog.qml'
--- src/app/qml/common/PickImportedDialog.qml	2015-04-10 17:00:59 +0000
+++ src/app/qml/common/PickImportedDialog.qml	2015-09-19 11:41:47 +0000
@@ -32,13 +32,13 @@
     // We don't use a Flickable, since it already lives in the Dialog itself.
     Repeater {
         id: repeater
-        delegate: ListItem.Standard {
-            text: Utils.getNameOfFile(modelData)
+        ListItem.Standard {
+            text: Utils.getNameOfFile(model.path)
             __foregroundColor: Theme.palette.selected.backgroundText
 
             onClicked: {
                 PopupUtils.close(multipleImportDialog);
-                mainView.openDocument(modelData);
+                mainView.openDocument(model.path);
             }
         }
     }

=== modified file 'src/app/qml/common/RejectedImportDialog.qml'
--- src/app/qml/common/RejectedImportDialog.qml	2015-04-12 15:34:47 +0000
+++ src/app/qml/common/RejectedImportDialog.qml	2015-09-19 11:41:47 +0000
@@ -25,13 +25,13 @@
 
     signal closed
 
-    title: i18n.tr("File not supported", "Files not supported", model.length)
+    title: i18n.tr("File not supported", "Files not supported", repeater.count)
     text: i18n.tr("Following document has not been imported:",
-                  "Following documents have not been imported:", model.length)
+                  "Following documents have not been imported:", repeater.count)
 
     Repeater {
         id: repeater
-        delegate: Label { text: modelData }
+        Label { text: model.path }
     }
 
     Button {

=== removed file 'src/app/qml/common/Toast.qml'
--- src/app/qml/common/Toast.qml	2015-04-07 22:03:03 +0000
+++ src/app/qml/common/Toast.qml	1970-01-01 00:00:00 +0000
@@ -1,82 +0,0 @@
-/*
-  This file is part of quick-memo
-  Copyright (C) 2014, 2015 Stefano Verzegnassi
-
-    This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License 3 as published by
-  the Free Software Foundation.
-
-    This program 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 General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-  along with this program. If not, see http://www.gnu.org/licenses/.
-*/
-
-import QtQuick 2.0
-import Ubuntu.Components 1.1
-
-Rectangle {
-    id: rootItem
-
-    property alias text: label.text
-
-    width: parent.width
-    height: units.gu(8)
-
-    color: "#131313"
-    opacity: 0.85
-    layer.enabled: true
-
-    anchors {
-        horizontalCenter: parent.horizontalCenter
-        bottom: parent.bottom; bottomMargin: - height
-    }
-
-    Label {
-        id: label
-        anchors.centerIn: parent
-
-        font.weight: Font.DemiBold
-        color: "white"
-    }
-
-    MouseArea {
-        anchors.fill: parent
-
-        onClicked: {
-            showAnimation.stop()
-            destroyAnimation.restart()
-        }
-    }
-
-    Rectangle {
-        anchors {
-            bottom: parent.bottom
-            left: parent.left
-            right: parent.right
-        }
-
-        height: units.dp(2)
-        color: UbuntuColors.orange
-    }
-
-    SequentialAnimation {
-        id: showAnimation
-        running: true
-
-        NumberAnimation { target: rootItem; property: "anchors.bottomMargin"; to: 0; duration: 300 }
-        PauseAnimation { duration: 2000 }
-        ScriptAction { script: destroyAnimation.restart() }
-    }
-
-    SequentialAnimation {
-        id: destroyAnimation
-
-        NumberAnimation { target: rootItem; property: "opacity"; to: 0; duration: 500 }
-        ScriptAction { script: rootItem.destroy() }
-    }
-}
-

=== removed file 'src/app/qml/common/ToastWithAction.qml'
--- src/app/qml/common/ToastWithAction.qml	2015-07-14 15:43:11 +0000
+++ src/app/qml/common/ToastWithAction.qml	1970-01-01 00:00:00 +0000
@@ -1,117 +0,0 @@
-/*
-  Copyright (C) 2014, 2015 Stefano Verzegnassi
-
-    This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License 3 as published by
-  the Free Software Foundation.
-
-    This program 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 General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-  along with this program. If not, see http://www.gnu.org/licenses/.
-*/
-
-import QtQuick 2.0
-import Ubuntu.Components 1.1
-import QtQuick.Layouts 1.1
-
-Rectangle {
-    id: rootItem
-
-    property alias text: label.text
-    readonly property alias action: action
-
-    width: parent.width
-    height: units.gu(8)
-
-    color: "#131313"
-    opacity: 0.85
-    layer.enabled: true
-
-    anchors {
-        horizontalCenter: parent.horizontalCenter
-        bottom: parent.bottom; bottomMargin: - height
-    }
-
-    MouseArea {
-        anchors.fill: parent
-
-        onClicked: {
-            showAnimation.stop()
-            destroyAnimation.restart()
-        }
-    }
-
-    RowLayout {
-        anchors {
-            fill: parent
-            margins: units.gu(2)
-        }
-
-        Label {
-            id: label
-            Layout.fillWidth: true
-
-            font.weight: Font.DemiBold
-            color: "white"
-        }
-
-        AbstractButton {
-            Layout.preferredWidth: actionLabel.paintedWidth
-            Layout.fillHeight: true
-
-            onClicked: {
-                action.triggered("[Toast] Action %1 clicked!".arg(action.text))
-            }
-
-            Label {
-                id: actionLabel
-                text: action.text
-
-                font.capitalization: Font.AllUppercase
-                font.weight: Font.DemiBold
-                color: UbuntuColors.orange
-
-                anchors.centerIn: parent
-            }
-        }
-    }
-
-    Rectangle {
-        anchors {
-            bottom: parent.bottom
-            left: parent.left
-            right: parent.right
-        }
-
-        height: units.dp(2)
-        color: UbuntuColors.orange
-    }
-
-    Action {
-        id: action
-
-        text: i18n.tr("Dismiss")
-        onTriggered: destroyAnimation.restart()
-    }
-
-    SequentialAnimation {
-        id: showAnimation
-        running: true
-
-        NumberAnimation { target: rootItem; property: "anchors.bottomMargin"; to: 0; duration: 300 }
-        PauseAnimation { duration: 2000 }
-        ScriptAction { script: destroyAnimation.restart() }
-    }
-
-    SequentialAnimation {
-        id: destroyAnimation
-
-        NumberAnimation { target: rootItem; property: "opacity"; to: 0; duration: 500 }
-        ScriptAction { script: rootItem.destroy() }
-    }
-}
-

=== modified file 'src/app/qml/documentPage/DocumentGridView.qml'
--- src/app/qml/documentPage/DocumentGridView.qml	2015-06-22 17:04:27 +0000
+++ src/app/qml/documentPage/DocumentGridView.qml	2015-09-19 11:41:47 +0000
@@ -37,6 +37,8 @@
     cellWidth: (mainView.width > units.gu(50)) ? units.gu(24)
                                                : (mainView.width - units.gu(2)) * 0.5
 
+    multipleSelection: contentHubProxy.multipleSelection ? contentHubProxy.multipleSelection : false
+
     listDelegate: DocumentGridDelegate {
         id: delegate
         width: cellWidth

=== modified file 'src/app/qml/documentPage/DocumentListView.qml'
--- src/app/qml/documentPage/DocumentListView.qml	2015-06-22 17:04:27 +0000
+++ src/app/qml/documentPage/DocumentListView.qml	2015-09-19 11:41:47 +0000
@@ -48,6 +48,8 @@
         }
     }
 
+    multipleSelection: contentHubProxy.multipleSelection ? contentHubProxy.multipleSelection : false
+
     listDelegate: DocumentListDelegate {
         id: delegate
 

=== modified file 'src/app/qml/documentPage/DocumentPage.qml'
--- src/app/qml/documentPage/DocumentPage.qml	2015-06-22 16:39:07 +0000
+++ src/app/qml/documentPage/DocumentPage.qml	2015-09-19 11:41:47 +0000
@@ -77,10 +77,10 @@
     ]
 
     Connections {
-        target: DOC_VIEWER
+        target: mainView
 
-        onPickModeEnabledChanged: {
-            if (DOC_VIEWER.pickModeEnabled) {
+        onPickModeChanged: {
+            if (mainView.pickMode) {
                 viewLoader.item.startSelection()
             } else {
                 viewLoader.item.cancelSelection()

=== modified file 'src/app/qml/documentPage/DocumentPagePickModeHeader.qml'
--- src/app/qml/documentPage/DocumentPagePickModeHeader.qml	2015-03-03 15:41:11 +0000
+++ src/app/qml/documentPage/DocumentPagePickModeHeader.qml	2015-09-19 11:41:47 +0000
@@ -16,6 +16,7 @@
 
 import QtQuick 2.3
 import Ubuntu.Components 1.1
+import Ubuntu.Content 1.1
 
 PageHeadState {
     id: rootItem
@@ -27,7 +28,12 @@
         text: i18n.tr("Cancel")
         objectName: "cancelButton"
         iconName: "close"
-        onTriggered: DOC_VIEWER.contentPickingCanceled()
+        onTriggered: {
+            if (!contentHubProxy.activeTransfer)
+                return;
+
+            contentHubProxy.activeTransfer.state = ContentTransfer.Aborted;
+        }
     }
 
     actions: [
@@ -45,17 +51,18 @@
             enabled: viewLoader.item.selectedItems.count > 0
             iconName: "ok"
             onTriggered: {
-                if (!enabled)
+                if (!enabled || !contentHubProxy.activeTransfer)
                     return;
 
                 var urlList = []
                 var items = documentPage.view.item.selectedItems;
 
                 for (var i=0; i < items.count; i++) {
-                    urlList.push(items.get(i).model.path);
+                    urlList.push("file://" + items.get(i).model.path);
                 }
 
-                DOC_VIEWER.returnPickedContent(urlList);
+                contentHubProxy.activeTransfer.items = urlList
+                contentHubProxy.activeTransfer.state = ContentTransfer.Charged
             }
         }
     ]

=== modified file 'src/app/qml/ubuntu-docviewer-app.qml'
--- src/app/qml/ubuntu-docviewer-app.qml	2015-06-22 16:45:36 +0000
+++ src/app/qml/ubuntu-docviewer-app.qml	2015-09-19 11:41:47 +0000
@@ -28,7 +28,8 @@
     id: mainView
     objectName: "mainView"
 
-    property bool pickMode: DOC_VIEWER.pickModeEnabled
+    // TODO: Connect with arguments
+    property bool pickMode: false
     readonly property bool isLandscape: Screen.orientation == Qt.LandscapeOrientation ||
                                         Screen.orientation == Qt.InvertedLandscapeOrientation
 
@@ -66,26 +67,12 @@
                         mainView, { parent: mainView });
     }
 
-    function showNotification(args) {
-        var component = Qt.createComponent("common/Toast.qml")
-        var toast = component.createObject(mainView, args);
-
-        return toast;
-    }
-
-    function showNotificationWithAction(args) {
-        var component = Qt.createComponent("common/ToastWithAction.qml")
-        var toast = component.createObject(mainView, args);
-
-        return toast;
-    }
-
     function setFullScreen(fullScreen) {
         DOC_VIEWER.fullScreen = fullScreen;
     }
 
     function toggleFullScreen() {
-        DOC_VIEWER.fullScreen = !APP.fullScreen;
+        DOC_VIEWER.fullScreen = !DOC_VIEWER.fullScreen;
     }
 
     function setHeaderVisibility(visible, toggleFullscreen) {
@@ -108,6 +95,19 @@
         setHeaderVisibility(!header.visible);
     }
 
+    function setPickMode(pickMode) {
+        mainView.pickMode = pickMode
+    }
+
+    function switchToBrowseMode() {
+        setPickMode(false)
+    }
+
+    function switchToPickMode() {
+        setPickMode(true)
+    }
+
+
     // On screen rotation, force updating of header/U8 indicators panel visibility
     onIsLandscapeChanged: setHeaderVisibility(true);
 
@@ -183,6 +183,16 @@
         property bool reverseOrder: false
     }
 
+    // Content Hub support
+    property alias contentHubProxy: contentHubLoader.item
+    Loader {
+        id: contentHubLoader
+
+        asynchronous: true
+        source: Qt.resolvedUrl("common/ContentHubProxy.qml")
+    }
+
+    // Uri Handler support
     Connections {
         target: UriHandler
         onOpened: {
@@ -198,91 +208,13 @@
         onDocumentFileChanged: {
             openDocument(DOC_VIEWER.documentFile);
         }
-
-        onPickModeEnabledChanged: {
-            mainView.pickMode = DOC_VIEWER.pickModeEnabled
-
-            if (mainView.pickMode) {
-                // If a document is loaded, pop() its page.
-                while (pageStack.depth > 1) {
-                    pageStack.pop()
-                }
-            }
-        }
     }
 
-    Connections {
-        target: PICKER_HUB
-
-        onDocumentImported: {
-            // Create two arrays: one for rejected documents, and the other
-            // for imported documents.
-            var importedDocuments = [];
-            var rejectedDocuments = [];
-            var entry;
-
-            // Fill the arrays.
-            for (var i=0; i<documents.length; i++) {
-                entry = documents[i];
-
-                if (entry.rejected) {
-                    rejectedDocuments.push(entry.fileName);
-                    break;
-                }
-
-                importedDocuments.push(entry.fileName);
-            }
-
-            // Prepare import notification
-            var showImportNotification = function() {
-                if (importedDocuments.length > 0) {
-                    var importDialog = showNotificationWithAction({
-                        "text": i18n.tr("Document successfully imported!",
-                                        "Documents successfully imported!",
-                                        importedDocuments.length),
-                        "action.text": i18n.tr("Open")
-                    })
-
-                    if (importedDocuments.length > 1) {
-                        // If it has been imported more than a document, show
-                        // a file picker when user taps the "open" action.
-                        importDialog.action.triggered.connect(function() {
-                            PopupUtils.open(
-                                Qt.resolvedUrl("common/PickImportedDialog.qml"),
-                                mainView,
-                                {
-                                    parent: mainView,
-                                    model: importedDocuments
-                                }
-                            );
-                        });
-                    } else {
-                        // It has been imported just a document, open it when
-                        // user taps the action button.
-                        importDialog.action.triggered.connect(function() {
-                            openDocument(importedDocuments[0]);
-                        });
-                    }
-                }
-            }
-
-            // Check if there's any rejected document in the last transfer.
-            // If so, show an error dialog.
-            if (rejectedDocuments.length > 0) {
-                var rejectedDialog = PopupUtils.open(
-                    Qt.resolvedUrl("common/RejectedImportDialog.qml"),
-                    mainView,
-                    {
-                        parent: mainView,
-                        model: rejectedDocuments
-                    }
-                );
-
-                // Show import notification after the dialog has been closed.
-                rejectedDialog.closed.connect(showImportNotification)
-            } else {
-                // No dialog has been shown. Show the notification.
-                showImportNotification.call();
+    onPickModeChanged: {
+        if (mainView.pickMode) {
+            // If a document is loaded, pop() its page.
+            while (pageStack.depth > 1) {
+                pageStack.pop()
             }
         }
     }

=== modified file 'src/plugin/file-qml-plugin/CMakeLists.txt'
--- src/plugin/file-qml-plugin/CMakeLists.txt	2015-09-09 17:25:56 +0000
+++ src/plugin/file-qml-plugin/CMakeLists.txt	2015-09-19 11:41:47 +0000
@@ -7,6 +7,7 @@
     documentmodel.cpp
     fswatcher.cpp
     docviewerfile.cpp
+    documentviewersingleton.cpp
 )
 
 add_library(fileqmlplugin MODULE

=== modified file 'src/plugin/file-qml-plugin/backend.cpp'
--- src/plugin/file-qml-plugin/backend.cpp	2015-04-29 15:23:32 +0000
+++ src/plugin/file-qml-plugin/backend.cpp	2015-09-19 11:41:47 +0000
@@ -21,6 +21,16 @@
 #include "backend.h"
 #include "documentmodel.h"
 #include "docviewerfile.h"
+#include "documentviewersingleton.h"
+
+static QObject *registerDocumentViewerSingleton (QQmlEngine *engine, QJSEngine *scriptEngine)
+{
+    Q_UNUSED(engine)
+    Q_UNUSED(scriptEngine)
+
+    DocumentViewerSingleton *ch = new DocumentViewerSingleton();
+    return ch;
+}
 
 void BackendPlugin::registerTypes(const char *uri)
 {
@@ -30,6 +40,8 @@
 
     qmlRegisterType<DocumentModel>(uri, 1, 0, "DocumentsModel");
     qmlRegisterType<DocviewerFile>(uri, 1, 0, "File");
+
+    qmlRegisterSingletonType<DocumentViewerSingleton>(uri, 1, 0, "DocumentViewer", registerDocumentViewerSingleton);
 }
 
 void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri)

=== modified file 'src/plugin/file-qml-plugin/documentmodel.cpp'
--- src/plugin/file-qml-plugin/documentmodel.cpp	2015-09-09 17:25:56 +0000
+++ src/plugin/file-qml-plugin/documentmodel.cpp	2015-09-19 11:41:47 +0000
@@ -17,6 +17,7 @@
 
 #include "documentmodel.h"
 #include "fswatcher.h"
+#include "documentviewersingleton.h"
 
 #include <QStandardPaths>
 #include <QDir>
@@ -114,19 +115,7 @@
 
 bool DocumentModel::isFileSupported(const QString &path)
 {
-    QMimeDatabase db;
-    QString mimetype = db.mimeTypeForFile(path).name();
-
-    return (mimetype.startsWith("text/")
-            || mimetype == "application/pdf"
-            || mimetype.startsWith("application/vnd.oasis.opendocument")
-            || mimetype == "application/msword")
-            || mimetype == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
-            || mimetype == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
-            || mimetype == "application/vnd.openxmlformats-officedocument.presentationml.presentation"
-            || mimetype == "application/msword"
-            || mimetype == "application/vnd.ms-excel"
-            || mimetype == "application/vnd.ms-powerpoint";
+    return DocumentViewerSingleton::isFileSupported(path);
 }
 
 QHash<int, QByteArray> DocumentModel::roleNames() const

=== added file 'src/plugin/file-qml-plugin/documentviewersingleton.cpp'
--- src/plugin/file-qml-plugin/documentviewersingleton.cpp	1970-01-01 00:00:00 +0000
+++ src/plugin/file-qml-plugin/documentviewersingleton.cpp	2015-09-19 11:41:47 +0000
@@ -0,0 +1,121 @@
+/*
+  Copyright (C) 2015 Canonical, Ltd.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License 3 as published by
+  the Free Software Foundation.
+
+  This program 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 General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include "documentviewersingleton.h"
+
+#include <QFileInfo>
+#include <QDir>
+#include <QMimeDatabase>
+#include <QStandardPaths>
+
+bool DocumentViewerSingleton::exists(const QString &path)
+{
+    QFileInfo fi(path);
+
+    if (fi.isFile())
+        return fi.exists();
+
+    // else
+    return QDir(path).exists();
+}
+
+bool DocumentViewerSingleton::copy(const QString &source, const QString &destination)
+{
+    return QFile::copy(source, destination);
+}
+
+bool DocumentViewerSingleton::isFileSupported(const QString &path)
+{
+    QMimeDatabase mdb;
+    const QString mimetype = mdb.mimeTypeForFile(path).name();
+
+    return mimetype.startsWith("text/")
+            || mimetype == "application/pdf"
+            || mimetype.startsWith("application/vnd.oasis.opendocument")
+            || mimetype == "application/msword"
+            || mimetype == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+            || mimetype == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+            || mimetype == "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+            || mimetype == "application/vnd.ms-excel"
+            || mimetype == "application/vnd.ms-powerpoint";
+}
+
+QString DocumentViewerSingleton::getXdgDocumentsLocation()
+{
+    return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+}
+
+QString DocumentViewerSingleton::buildDestinationPath(const QString &destinationDir, const QString &sourcePath)
+{
+    QFileInfo fi(sourcePath);
+
+    /*
+      We don't support formats that use a double extension
+      (e.g. tar.gz), so we can safely use completeBaseName() and
+      suffix() functions, in order to properly detect the name of
+      the document even when there's a dot in the middle of the name.
+    */
+    QString suffix = fi.suffix();
+    QString filenameWithoutSuffix = fi.completeBaseName();
+
+    QMimeDatabase mdb;
+    QMimeType mt = mdb.mimeTypeForFile(sourcePath);
+
+    // If the filename doesn't have an extension add one from the
+    // detected mimetype
+    if (suffix.isEmpty())
+        suffix = mt.preferredSuffix();
+
+    QString dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + QDir::separator();
+    QString destination = QString("%1.%2").arg(dir + filenameWithoutSuffix, suffix);
+
+    // If there's already a file of this name, reformat it to
+    // "filename (copy x).png" where x is a number, incremented until we find an
+    // available filename.
+    if (QFile::exists(destination)) {
+        /*
+         TRANSLATORS: This string is used for renaming a copied file,
+         when a file with the same name already exists in user's
+         Documents folder.
+
+         e.g. "Manual_Aquaris_E4.5_ubuntu_EN.pdf" will become
+              "Manual_Aquaris_E4.5_ubuntu_EN (copy 2).pdf"
+
+              where "2" is given by the argument "%1"
+        */
+        QString reformattedSuffix = QString(tr("copy %1"));
+
+        // Check if the file has already a "copy" suffix
+        // If so, remove it since we will update it later.
+        QRegExp rx(" \\(" + reformattedSuffix.arg(QString("\\d+")) + "\\)");
+        int reformattedSuffixPos = filenameWithoutSuffix.lastIndexOf(rx);
+
+        if (reformattedSuffixPos != -1)
+            filenameWithoutSuffix.truncate(reformattedSuffixPos);
+
+        // Add the right "copy" suffix.
+        int append = 1;
+        while (QFile::exists(destination)) {
+            destination = QString("%1 (%2).%3").arg(
+                        dir + filenameWithoutSuffix,
+                        reformattedSuffix.arg(QString::number(append)),
+                        suffix);
+            append++;
+        }
+    }
+
+    return destination;
+}

=== added file 'src/plugin/file-qml-plugin/documentviewersingleton.h'
--- src/plugin/file-qml-plugin/documentviewersingleton.h	1970-01-01 00:00:00 +0000
+++ src/plugin/file-qml-plugin/documentviewersingleton.h	2015-09-19 11:41:47 +0000
@@ -0,0 +1,37 @@
+/*
+  Copyright (C) 2015 Canonical, Ltd.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License 3 as published by
+  the Free Software Foundation.
+
+  This program 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 General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef DOCUMENTVIEWERSINGLETON_H
+#define DOCUMENTVIEWERSINGLETON_H
+
+#include <QObject>
+#include <QThread>
+
+class DocumentViewerSingleton : public QObject
+{
+    Q_OBJECT
+
+public:
+    Q_INVOKABLE static bool exists(const QString &path);
+    Q_INVOKABLE static bool copy(const QString &source, const QString &destination);
+
+    Q_INVOKABLE static bool isFileSupported(const QString &path);
+    Q_INVOKABLE static QString getXdgDocumentsLocation();
+
+    Q_INVOKABLE static QString buildDestinationPath(const QString &destinationDir, const QString &sourcePath);
+};
+
+#endif // DOCUMENTVIEWERSINGLETON_H


Follow ups