public inbox for [email protected]  
help / color / mirror / Atom feed
From: Neel Patel <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin4][runtime]: Download feature in runtime
Date: Thu, 30 Jun 2016 15:12:31 +0530
Message-ID: <CACCA4P2eP9URRcHXXiPftxodmV_da57pjSw9PKLgpjEHjrb6MQ@mail.gmail.com> (raw)
List-Unsubscribe:  <mailto:[email protected]?body=unsub%20pgadmin-hackers>

Hi,

Please find attached patch file for initial version of download file in
runtime application.

With this patch, we have implemented two features.

*Feature 1 :- Normal "Download file" from runtime application*

Previously "Download file" was not implemented in runtime application.
With this patch file, we have handled Qt signal for download file properly.

*Feature 2 :-   "download" attribute support for 'a' tag for client side
download*

As per our knowledge, webkit has not implemented the download attribute at
'a' tag.
Currently it shows under development from below link.

https://bugreports.qt.io/browse/QTBUG-47732

We did not found any signal in Qt for download attribute feature but to
implement this feature in runtime application, we added one workaround to
make it work with download CSV file.

When we click on download buttons, we are getting Qt signal
"urlLinkClicked" and in that url we are finding "data:text/csv" from
encoded URL generated from sqleditor. Once we found that tag then we are
decoding the csv data and writing to file.

Is that right approach ? Should we add our own custom mime-type to header ?
Let us know your thoughts on this feature.


Please review it and let us know comments.

Thanks,
Neel Patel


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] download_runtime.patch (12.1K, 3-download_runtime.patch)
  download | inline diff:
diff --git a/runtime/BrowserWindow.cpp b/runtime/BrowserWindow.cpp
index 5419c0e..d33b515 100644
--- a/runtime/BrowserWindow.cpp
+++ b/runtime/BrowserWindow.cpp
@@ -23,7 +23,6 @@
 #include <QInputDialog>
 #include <QLineEdit>
 #endif
-
 // App headers
 #include "BrowserWindow.h"
 #include "ConfigWindow.h"
@@ -42,6 +41,12 @@ BrowserWindow::BrowserWindow(QString url)
     m_widget = NULL;
     m_toolBtnBack = NULL;
     m_toolBtnForward = NULL;
+    downloadStarted = 0;
+    is_download_canceled = 0;
+    m_file = NULL;
+    downloadedFileName = "";
+    defaultFileName = "";
+    progressDlg = NULL;
 
     m_appServerUrl = url;
 
@@ -83,6 +88,11 @@ BrowserWindow::BrowserWindow(QString url)
     // Register the slot on tab index change
     connect(m_tabWidget,SIGNAL(currentChanged(int )),this,SLOT(tabIndexChanged(int )));
 
+    // Listen for the download file request from the web page
+    m_mainWebView->page()->setForwardUnsupportedContent(true);
+    connect(m_mainWebView->page(), SIGNAL(downloadRequested(const QNetworkRequest &)), this, SLOT(download(const QNetworkRequest &)));
+    connect(m_mainWebView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*)));
+
     m_mainWebView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
 
     // Restore the geometry
@@ -199,6 +209,191 @@ int BrowserWindow::findURLTab(const QUrl &name)
     return 0;
 }
 
+// This slot will be called when user right click the download link and select "Save Link..."
+void BrowserWindow::download(const QNetworkRequest &request)
+{
+    if (downloadStarted)
+    {
+        //Inform the user that one download already started
+        QMessageBox::information(this, tr("Download warning"), tr("File download already in progress - %1").arg(defaultFileName));
+        return;
+    }
+
+    defaultFileName = QFileInfo(request.url().toString()).fileName();
+    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), defaultFileName);
+    if (fileName.isEmpty())
+        return;
+    else
+    {
+        downloadedFileName = fileName;
+
+        QNetworkRequest newRequest = request;
+        newRequest.setAttribute(QNetworkRequest::User, fileName);
+
+        QObject *obj_web_page = QObject::sender();
+        if (obj_web_page != NULL)
+        {
+            QWebPage *sender_web_page = dynamic_cast<QWebPage*>(obj_web_page);
+            if (sender_web_page != NULL)
+            {
+                QNetworkAccessManager *networkManager = sender_web_page->networkAccessManager();
+                QNetworkReply *reply = networkManager->get(newRequest);
+                if (reply != NULL)
+                {
+                    downloadStarted = 1;
+                    is_download_canceled = 0;
+                    // Connect the signal for downloadprogress and downloadFinished for file download
+                    connect( reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadFileProgress(qint64, qint64)) );
+                    connect( reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
+                }
+            }
+        }
+    }
+}
+
+//This slot will called in chunk and give the progress for the file download
+void BrowserWindow::downloadFileProgress(qint64 readData, qint64 totalData)
+{
+    QNetworkReply *reply = ((QNetworkReply*)sender());
+    QNetworkRequest request = reply->request();
+    QVariant v = request.attribute(QNetworkRequest::User);
+
+    // Is download is canceled by the user then no action is taken, just return
+    if (is_download_canceled)
+        return;
+
+    if(reply != NULL && reply->error() != QNetworkReply::NoError)
+    {
+        qDebug() << "Network error occured during downloding " << defaultFileName << " file";
+        return;
+    }
+
+    // Download is not yet started so open the file first time.
+    if (!m_file)
+    {
+        m_file = new QFile(downloadedFileName);
+        if (!m_file->open(QIODevice::WriteOnly))
+        {
+            qDebug() << "Error opening file: " << downloadedFileName;
+            downloadedFileName.clear();
+            defaultFileName.clear();
+            downloadStarted = 0;
+            return;
+        }
+
+        // Start downaloding progress bar
+        progressDlg = new QProgressDialog (tr("Downloading file...%1 ").arg(defaultFileName), "Cancel", readData, totalData, this);
+        progressDlg->setWindowTitle("Download progress..");
+        progressDlg->setMinimumWidth(450);
+        progressDlg->setMinimumHeight(80);
+        progressDlg->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint);
+        QObject::connect(progressDlg, SIGNAL(canceled()), this, SLOT(progressCanceled()));
+        progressDlg->show();
+    }
+
+    if (m_file)
+    {
+        // Write the data to file
+        m_file->write(reply->read(readData));
+        progressDlg->setValue(readData);
+        // As read data and totalData difference is zero means downloading is finished
+        if ((totalData - readData) == 0)
+        {
+            if (progressDlg)
+            {
+                delete progressDlg;
+                progressDlg = NULL;
+            }
+
+            // Downloading complted so we need to display the message
+            // Inform user that downloading is completed
+            QMessageBox::information(this, tr("Download completed"), tr("%1 file downloaded successfully").arg(defaultFileName));
+            downloadedFileName.clear();
+            defaultFileName.clear();
+            downloadStarted = 0;
+            is_download_canceled = 0;
+            if (m_file)
+            {
+                delete m_file;
+                m_file = NULL;
+            }
+        }
+    }
+}
+
+//This slot will called when user cancel the downloading file which is in progress.
+void BrowserWindow::progressCanceled()
+{
+    is_download_canceled = 1;
+
+    if (progressDlg)
+    {
+        delete progressDlg;
+        progressDlg = NULL;
+    }
+
+    if (m_file)
+    {
+        delete m_file;
+        m_file = NULL;
+    }
+
+    downloadedFileName.clear();
+    defaultFileName.clear();
+    downloadStarted = 0;
+}
+
+// This slot will called when file downloading is finished
+void BrowserWindow::downloadFinished()
+{
+    if (progressDlg)
+    {
+        delete progressDlg;
+        progressDlg = NULL;
+    }
+
+    // Inform user that downloading is completed
+    if (downloadStarted)
+        QMessageBox::information(this, tr("Download completed"), tr("%1 file downloaded successfully").arg(defaultFileName));
+
+    downloadedFileName.clear();
+    defaultFileName.clear();
+    downloadStarted = 0;
+    is_download_canceled = 0;
+    if (m_file)
+    {
+        delete m_file;
+        m_file = NULL;
+    }
+}
+
+// This slot will be called when user directly click on any download file
+void BrowserWindow::unsupportedContent(QNetworkReply * reply)
+{
+    if (downloadStarted)
+    {
+        //Inform the user that one download already started
+        QMessageBox::information(this, tr("Download warning"), tr("File download already in progress - %1").arg(defaultFileName));
+        return;
+    }
+
+    defaultFileName = QFileInfo(reply->url().toString()).fileName();
+    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), defaultFileName);
+    if (fileName.isEmpty())
+        return;
+    else
+    {
+        downloadedFileName = fileName;
+        if (reply != NULL)
+        {
+            downloadStarted = 1;
+            is_download_canceled = 0;
+            connect( reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadFileProgress(qint64, qint64)));
+            connect( reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
+        }
+    }
+}
+
 // Slot: When the tab index change, hide/show the toolbutton displayed on tab
 void BrowserWindow::tabIndexChanged(int index)
 {
@@ -343,6 +538,40 @@ void BrowserWindow::tabTitleChanged(const QString &str)
 // Slot: Link is open from pgAdmin mainwindow
 void BrowserWindow::urlLinkClicked(const QUrl &name)
 {
+    QString csv_data = QString::fromUtf8(name.toEncoded());
+
+    // Find the "data:text/csv" tag, get the decoded data from QUrl class and write to csv file.
+    if (csv_data.contains(QRegExp("^data:text\/csv")))
+    {
+        // Ask user where to save the csv file in filesystem.
+        QString filename = QFileDialog::getSaveFileName(this, tr("Save csv file"), QDir::currentPath(), tr("Files (*.csv)") );
+        if(!filename.isEmpty())
+        {
+            // Decode the encoded uri data
+            QString csvData = QUrl::fromPercentEncoding(name.toEncoded());
+            QStringList csvStrList = csvData.split(";");
+            QString extractString = "";
+            if (csvStrList.size() >= 3)
+                extractString = csvStrList.at(2);
+            QFile csvfile(filename);
+            if (!csvfile.open(QIODevice::WriteOnly | QIODevice::Text))
+            {
+                QMessageBox::information(this, tr("Save csv file"), tr("Error while opening file %1").arg(filename));
+                return;
+            }
+            // Write the csv data to file
+	    qint64 data_return = csvfile.write(extractString.toUtf8().constData());
+            if (data_return == -1)
+            {
+                QMessageBox::information(this, tr("Save csv file"), tr("Error while writing data to file %1").arg(filename));
+                csvfile.close();
+                return;
+            }
+            csvfile.close();
+        }
+        return;
+    }
+
     // First check is there any tab opened with same URL then open it again.
     int tabFound = findURLTab(name);
 
@@ -353,6 +582,11 @@ void BrowserWindow::urlLinkClicked(const QUrl &name)
         m_addNewGridLayout->setContentsMargins(0, 0, 0, 0);
         m_addNewWebView = new WebViewWindow(m_addNewTab);
 
+	// Listen for the download request from the web page
+	m_addNewWebView->page()->setForwardUnsupportedContent(true);
+        connect(m_addNewWebView->page(), SIGNAL(downloadRequested(const QNetworkRequest &)), this, SLOT(download(const QNetworkRequest &)));
+        connect(m_addNewWebView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*)));
+
         m_widget = new QWidget(m_addNewTab);
         m_toolBtnBack = new QToolButton(m_widget);
         m_toolBtnBack->setFixedHeight(PGA_BTN_SIZE);
diff --git a/runtime/BrowserWindow.h b/runtime/BrowserWindow.h
index 43f90fe..d0e4150 100644
--- a/runtime/BrowserWindow.h
+++ b/runtime/BrowserWindow.h
@@ -54,6 +54,11 @@ public slots:
     void tabIndexChanged(int index);
     void goBackPage();
     void goForwardPage();
+    void download(const QNetworkRequest &request);
+    void unsupportedContent(QNetworkReply * reply);
+    void downloadFinished();
+    void downloadFileProgress(qint64 , qint64 );
+    void progressCanceled();
 
 private:
     QString m_appServerUrl;
@@ -79,6 +84,12 @@ private:
 
     bool m_initialLoad;
     int m_loadAttempt;
+    QString downloadedFileName;
+    int downloadStarted;
+    int is_download_canceled;
+    QFile *m_file;
+    QProgressDialog *progressDlg;
+    QString defaultFileName;
 
     void createActions();
     void pause(int seconds = 1);
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index c2282a1..535b543 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -2571,7 +2571,7 @@ define(
                 keys = _.pluck(self.columns, 'name');
 
             // Fetch the items from fullCollection and convert it as csv format
-            var csv = labels.join(',') + '\n';
+            var csv = keys.join(',') + '\n';
             csv += coll.map(function(item) {
                 return _.map(keys, function(key) {
                   var cell = csv_col [key].cell,
@@ -2584,7 +2584,7 @@ define(
             }).join('\n');
 
             // Download the file.
-            var encodedUri = encodeURI('data:text/csv;charset=utf-8,' + csv),
+            var encodedUri = encodeURI('data:text/csv;charset=utf-8;' + csv),
                     link = document.createElement('a');
             link.setAttribute('href', encodedUri);
 


view thread (8+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [pgAdmin4][runtime]: Download feature in runtime
  In-Reply-To: <CACCA4P2eP9URRcHXXiPftxodmV_da57pjSw9PKLgpjEHjrb6MQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox