public inbox for [email protected]
help / color / mirror / Atom feedFrom: Dave Page <[email protected]>
To: Neel Patel <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4][runtime]: Download feature in runtime
Date: Thu, 30 Jun 2016 15:01:07 +0100
Message-ID: <CA+OCxozY73dR+0OrOrO-urNHaUOJBXjUJB1q31-oMRyM4jXoyg@mail.gmail.com> (raw)
In-Reply-To: <CACCA4P2eP9URRcHXXiPftxodmV_da57pjSw9PKLgpjEHjrb6MQ@mail.gmail.com>
References: <CACCA4P2eP9URRcHXXiPftxodmV_da57pjSw9PKLgpjEHjrb6MQ@mail.gmail.com>
List-Unsubscribe: <mailto:[email protected]?body=unsub%20pgadmin-hackers>
Hi
On Thu, Jun 30, 2016 at 10:42 AM, Neel Patel
<[email protected]> wrote:
> Hi,
>
> Please find attached patch file for initial version of download file in
> runtime application.
I've attached an update with some improved messages, and setting the
progress dialogue to be modal (seeing as we cannot have multiple
downloads, and it's easy to lose the dialogue).
> 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.
This seems to work fine. I did get one crash (after I cancelled a
download, then tried it again), but I couldn't reproduce that.
> 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.
This doesn't work so well, for a number of reasons:
1) QT Creator is complaining that your regexp contains an invalid
escape sequence (line 546).
2) The default file name seems to be the entire data blob. I would
suggest making the file name "download.csv" if we don't know anything
better. The "csv" part should be taken from the mime type (see below)
3) Should we handle all "data:" downloads in this way? Taking the file
type and default extension from the mimetype offered.
4) When I change the filename the data is properly saved, but then I
get a confirmation message that still has the full data blob as the
filename.
5) It somehow seems to have let me save files with forward slashes in
the name. See attachment.
6) I get all sorts of weird redrawing on the screen when downloading a
data blob. I suspect it's because the filename (which is still the
entire data blob) is shown on the progress dialogue.
Thanks.
--
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake
EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers
Attachments:
[image/png] Screen Shot 2016-06-30 at 14.59.04.png (128.1K, 2-Screen%20Shot%202016-06-30%20at%2014.59.04.png)
download | view image
[application/octet-stream] download_runtime-dave.patch (12.5K, 3-download_runtime-dave.patch)
download | inline diff:
Index: runtime/BrowserWindow.h
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- runtime/BrowserWindow.h (revision d79524ff60a4645af798ce1e926210320de26a99)
+++ runtime/BrowserWindow.h (revision )
@@ -54,6 +54,11 @@
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 @@
bool m_initialLoad;
int m_loadAttempt;
+ QString m_downloadFilename;
+ int m_downloadStarted;
+ int m_downloadCancelled;
+ QFile *m_file;
+ QProgressDialog *m_progressDialog;
+ QString m_defaultFilename;
void createActions();
void pause(int seconds = 1);
Index: runtime/BrowserWindow.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- runtime/BrowserWindow.cpp (revision d79524ff60a4645af798ce1e926210320de26a99)
+++ runtime/BrowserWindow.cpp (revision )
@@ -23,7 +23,6 @@
#include <QInputDialog>
#include <QLineEdit>
#endif
-
// App headers
#include "BrowserWindow.h"
#include "ConfigWindow.h"
@@ -42,6 +41,12 @@
m_widget = NULL;
m_toolBtnBack = NULL;
m_toolBtnForward = NULL;
+ m_downloadStarted = 0;
+ m_downloadCancelled = 0;
+ m_file = NULL;
+ m_downloadFilename = "";
+ m_defaultFilename = "";
+ m_progressDialog = NULL;
m_appServerUrl = url;
@@ -83,6 +88,11 @@
// 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,193 @@
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 (m_downloadStarted)
+ {
+ // Inform the user that a download is already started
+ QMessageBox::information(this, tr("Download warning"), tr("File download already in progress: %1").arg(m_defaultFilename));
+ return;
+ }
+
+ m_defaultFilename = QFileInfo(request.url().toString()).fileName();
+ QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), m_defaultFilename);
+ if (fileName.isEmpty())
+ return;
+ else
+ {
+ m_downloadFilename = 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)
+ {
+ m_downloadStarted = 1;
+ m_downloadCancelled = 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 (m_downloadCancelled)
+ return;
+
+ if(reply != NULL && reply->error() != QNetworkReply::NoError)
+ {
+ qDebug() << "Network error occurred whilst downloading: " << m_defaultFilename;
+ return;
+ }
+
+ // Download is not yet started so open the file first time.
+ if (!m_file)
+ {
+ m_file = new QFile(m_downloadFilename);
+ if (!m_file->open(QIODevice::WriteOnly))
+ {
+ qDebug() << "Error opening file: " << m_downloadFilename;
+ m_downloadFilename.clear();
+ m_defaultFilename.clear();
+ m_downloadStarted = 0;
+ return;
+ }
+
+ // Start downloading progress bar
+ m_progressDialog = new QProgressDialog (tr("Downloading file: %1 ").arg(m_defaultFilename), "Cancel", readData, totalData, this);
+ m_progressDialog->setWindowModality(Qt::WindowModal);
+ m_progressDialog->setWindowTitle("Download progress");
+ m_progressDialog->setMinimumWidth(450);
+ m_progressDialog->setMinimumHeight(80);
+ m_progressDialog->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint);
+ QObject::connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(progressCanceled()));
+ m_progressDialog->show();
+ }
+
+ if (m_file)
+ {
+ // Write the data to file
+ m_file->write(reply->read(readData));
+ m_progressDialog->setValue(readData);
+
+ // As read data and totalData difference is zero means downloading is finished
+ if ((totalData - readData) == 0)
+ {
+ if (m_progressDialog)
+ {
+ delete m_progressDialog;
+ m_progressDialog = NULL;
+ }
+
+ // Downloading complted so we need to display the message
+ // Inform user that downloading is completed
+ QMessageBox::information(this, tr("Download completed"), tr("Successfully downloaded %1").arg(m_defaultFilename));
+ m_downloadFilename.clear();
+ m_defaultFilename.clear();
+ m_downloadStarted = 0;
+ m_downloadCancelled = 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()
+{
+ m_downloadCancelled = 1;
+
+ if (m_progressDialog)
+ {
+ delete m_progressDialog;
+ m_progressDialog = NULL;
+ }
+
+ if (m_file)
+ {
+ delete m_file;
+ m_file = NULL;
+ }
+
+ m_downloadFilename.clear();
+ m_defaultFilename.clear();
+ m_downloadStarted = 0;
+}
+
+// This slot will called when file downloading is finished
+void BrowserWindow::downloadFinished()
+{
+ if (m_progressDialog)
+ {
+ delete m_progressDialog;
+ m_progressDialog = NULL;
+ }
+
+ // Inform user that downloading is completed
+ if (m_downloadStarted)
+ QMessageBox::information(this, tr("Download completed"), tr("Successfully downloaded %1").arg(m_defaultFilename));
+
+ m_downloadFilename.clear();
+ m_defaultFilename.clear();
+ m_downloadStarted = 0;
+ m_downloadCancelled = 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 (m_downloadStarted)
+ {
+ //Inform the user that one download already started
+ QMessageBox::information(this, tr("Download warning"), tr("File download already in progress: %1").arg(m_defaultFilename));
+ return;
+ }
+
+ m_defaultFilename = QFileInfo(reply->url().toString()).fileName();
+ QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), m_defaultFilename);
+ if (fileName.isEmpty())
+ return;
+ else
+ {
+ m_downloadFilename = fileName;
+ if (reply != NULL)
+ {
+ m_downloadStarted = 1;
+ m_downloadCancelled = 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 +540,40 @@
// 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);
@@ -352,6 +583,11 @@
m_addNewGridLayout = new QGridLayout(m_addNewTab);
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);
Index: web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js (revision d79524ff60a4645af798ce1e926210320de26a99)
+++ web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js (revision )
@@ -2572,7 +2572,7 @@
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,
@@ -2585,7 +2585,7 @@
}).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], [email protected]
Subject: Re: [pgAdmin4][runtime]: Download feature in runtime
In-Reply-To: <CA+OCxozY73dR+0OrOrO-urNHaUOJBXjUJB1q31-oMRyM4jXoyg@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