public inbox for [email protected]  
help / color / mirror / Atom feed
From: Neel Patel <[email protected]>
To: Dave Page <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin4][runtime]: Download feature in runtime
Date: Wed, 6 Jul 2016 13:42:43 +0530
Message-ID: <CACCA4P3JOe40WYMGjhpSWYGR=WuvRbbp2gfDKLnU+1rXuW9Www@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxoxE-Le_yovC177qHS+27pZJLbgmJ4=n+TCWakFW5Fe8eA@mail.gmail.com>
References: <CACCA4P2eP9URRcHXXiPftxodmV_da57pjSw9PKLgpjEHjrb6MQ@mail.gmail.com>
	<CA+OCxozY73dR+0OrOrO-urNHaUOJBXjUJB1q31-oMRyM4jXoyg@mail.gmail.com>
	<CACCA4P2bEpUNZhkGQLY1JnMiBChFhauLpqiuAgJQiYcTV6YZ7g@mail.gmail.com>
	<CA+OCxoxE-Le_yovC177qHS+27pZJLbgmJ4=n+TCWakFW5Fe8eA@mail.gmail.com>
List-Unsubscribe:  <mailto:[email protected]?body=unsub%20pgadmin-hackers>

Hi Dave,

I have tried to fix most of the review comments.  I have modified the patch
on top of your changes. Please find attached updated patch file.
Find my comments inline. Can you please review and let us know your
feedback ?

Thanks,
Neel Patel

On Fri, Jul 1, 2016 at 2:39 PM, Dave Page <[email protected]> wrote:

> On Fri, Jul 1, 2016 at 5:43 AM, Neel Patel <[email protected]>
> wrote:
> > Hi Dave,
> >
> > On Thu, Jun 30, 2016 at 7:31 PM, Dave Page <[email protected]> wrote:
> >>
> >> 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.
> >
> >
> > Okay. I will try to reproduce the issue and also i will try to review the
> > code again if i can find something regrading crash.
>

I have tried to reproduce the crash but no luck. I have tried on Linux and
Mac.


>
> Thanks.
>
> >
> >>
> >> > 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).
> >
> >
> > I will fix.
> >>
> >>
> >> 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.
> >
> >
> > We can handle all "data:" download. We will extract the filename and
> > extension from mime type.
> > As i know, Qt provides QUrlQuery class which will be useful to find the
> key
> > value pair. I will test and let you know.
> >
> > e.g. If we have header as below
> >
> >
> "data:text/csv;charset=utf-8;Content-disposition:attachment;filename=download.csv;"
> >
> > From the QurlQuery class we can query "filename" and "data:" and
> accordingly
> > save the data to filename provided.
> >
> > Please suggest.
>
> Sounds good.
>
> >> 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.
>

I found that it is due to different Qt version. You might be using Qt 5.5.
In Qt 5.5, we are getting "download" signal and for Qt < 5.5 we are getting
"urlLinkClicked" signal for client side data download.
We have fixed the issue for all Qt version. Let me know if you can still
able to reproduce the issue.


> >>
> >> 5) It somehow seems to have let me save files with forward slashes in
> >> the name. See attachment.
>

Fixed.

>
> >
> > I think we should not ask for "Save as" dialog. If there is no key found
> of
> > "filename" in encodedURI then we should create the file "download.csv" in
> > user's download directory and save the csv data.
>
> Well we can get the extension from the mimetype in that instance, but
> otherwise I agree with the naming. I do think we need a Save As
> dialogue, as the user should be able to choose the location for the
> file (and rename it). We should also remember the last save location
> for convenience.
>

Fixed.


>
> >> 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.
> >>
>

Fixed.


> >
> > I will try to fix as per above comments and submit the patch again.
> > Let us know for any misunderstanding.
>
> Cool, 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:

  [application/octet-stream] download_runtime_v3.patch (19.1K, 3-download_runtime_v3.patch)
  download | inline diff:
diff --git a/runtime/BrowserWindow.cpp b/runtime/BrowserWindow.cpp
index e875b28..a73ab00 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,14 @@ BrowserWindow::BrowserWindow(QString url)
     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_last_open_folder_path = QDir::currentPath();
+    m_dir = "";
 
     m_appServerUrl = url;
 
@@ -83,6 +90,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 +211,281 @@ 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)
+{
+    // Check that request contains the data download at client side
+    QUrl name;
+    if (checkClientDownload(name, request))
+        return;
+
+    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();
+
+    QFileDialog save_dialog(this);
+    save_dialog.setAcceptMode(QFileDialog::AcceptSave);
+    save_dialog.setWindowTitle(tr("Save file"));
+    save_dialog.setDirectory(m_last_open_folder_path);
+    save_dialog.selectFile(m_defaultFilename);
+
+    QObject::connect(&save_dialog, SIGNAL(directoryEntered(const QString &)), this, SLOT(current_dir_path(const QString &)));
+    m_dir = m_last_open_folder_path;
+    QString fileName = "";
+    QString f_name = "";
+
+    if (save_dialog.exec() == QDialog::Accepted) {
+        fileName = save_dialog.selectedFiles().first();
+        f_name = fileName.replace(m_dir, "");
+        // remove the first character from fiename
+        f_name.remove(0,1);
+        m_defaultFilename = f_name;
+    }
+    else
+        return;
+
+    fileName = m_dir + fileName;
+    // clear the last folder open path
+    m_dir.clear();
+
+#ifdef __APPLE__
+    // Check that user has given valid file name or not - forward slash not allowed in file name
+    // In Mac OSX, forward slash is converted to colon(:) by Qt so we need to check for colon.
+    if (f_name.indexOf(":") != -1)
+    {
+        QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
+        return;
+    }
+#else
+    // Check that user has given valid file name or not - forward slash not allowed in file name
+    if (f_name.indexOf("/") != -1)
+    {
+        QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
+        return;
+    }
+#endif
+
+    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();
+    QFileDialog save_dialog(this);
+    save_dialog.setAcceptMode(QFileDialog::AcceptSave);
+    save_dialog.setWindowTitle(tr("Save file"));
+    save_dialog.setDirectory(m_last_open_folder_path);
+    save_dialog.selectFile(m_defaultFilename);
+
+    QObject::connect(&save_dialog, SIGNAL(directoryEntered(const QString &)), this, SLOT(current_dir_path(const QString &)));
+    m_dir = m_last_open_folder_path;
+    QString fileName = "";
+    QString f_name = "";
+
+    if (save_dialog.exec() == QDialog::Accepted) {
+        fileName = save_dialog.selectedFiles().first();
+        f_name = fileName.replace(m_dir, "");
+        // remove the first character from fiename
+        f_name.remove(0,1);
+        m_defaultFilename = f_name;
+    }
+    else
+        return;
+
+    fileName = m_dir + fileName;
+    // clear the last folder open path
+    m_dir.clear();
+
+#ifdef __APPLE__
+    // Check that user has given valid file name or not - forward slash not allowed in file name
+    // In Mac OSX, forward slash is converted to colon(:) by Qt so we need to check for colon.
+    if (f_name.indexOf(":") != -1)
+    {
+        QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
+        return;
+    }
+#else
+    // Check that user has given valid file name or not - forward slash not allowed in file name
+    if (f_name.indexOf("/") != -1)
+    {
+        QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
+        return;
+    }
+#endif
+
+    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)
 {
@@ -340,9 +627,142 @@ void BrowserWindow::tabTitleChanged(const QString &str)
     }
 }
 
+// This function will used to download the data set in encoded URL so data will be downloaded at client side.
+bool BrowserWindow::checkClientDownload(const QUrl &name, const QNetworkRequest &request)
+{
+    QString mime_type = "";
+    QString file_name = "";
+    QString write_data = "";
+    QString csv_data = "";
+    bool return_val = false;
+
+    /*
+    In Qt version 5.5, "download" signal is emitted when 'download' attribute is set on 'a' tag.
+    In "download" signal emission, name will be empty and data will be in request object.
+    Earlier version ( < 5.5 ), "urlLinkClicked" signal is emitted so name will contain the object data.
+    */
+    if (name.isEmpty())
+        csv_data = QFileInfo(request.url().toString()).fileName();
+    else
+        csv_data = QString::fromUtf8(name.toEncoded());
+
+    QUrlQuery downloadData(csv_data);
+    QStringList keyValueData = csv_data.split("&");
+    file_name = downloadData.queryItemValue("filename");
+    write_data = downloadData.queryItemValue("value");
+
+    int key_value_length = keyValueData.size();
+    int i_count = 0;
+
+    // we have key value data so we need to tokeniza it and find the values
+    while (i_count < key_value_length)
+    {
+        //Extract the extension after "data:" word found from encoded url.
+        QString start_match_string = "data:";
+        int s_offset = keyValueData.at(i_count).indexOf(start_match_string);
+        if (s_offset != -1)
+        {
+            int format_offset = keyValueData.at(i_count).indexOf("/");
+            mime_type = keyValueData.at(i_count).mid((format_offset+1));
+            break;
+        }
+
+        int split_offset = keyValueData.at(i_count).indexOf("=");
+        if (split_offset == -1)
+        {
+            mime_type = keyValueData.at(i_count);
+            break;
+        }
+
+        i_count += 1;
+    }
+
+    // write data to file
+    if (!write_data.isEmpty())
+    {
+        QString filename = "";
+        QString f_name = "";
+        QFileDialog saveAsdialog(this);
+        saveAsdialog.setAcceptMode(QFileDialog::AcceptSave);
+        saveAsdialog.selectNameFilter(tr("Files (*.%1)").arg(mime_type));
+        saveAsdialog.setWindowTitle(tr("Save %1 file").arg(mime_type));
+        saveAsdialog.setDirectory(m_last_open_folder_path);
+        saveAsdialog.selectFile(file_name);
+        saveAsdialog.setDefaultSuffix(mime_type);
+
+        QObject::connect(&saveAsdialog, SIGNAL(directoryEntered(const QString &)), this, SLOT(current_dir_path(const QString &)));
+        m_dir = m_last_open_folder_path;
+
+        if (saveAsdialog.exec() == QDialog::Accepted) {
+            filename = saveAsdialog.selectedFiles().at(0);
+            QString filename = saveAsdialog.selectedFiles().first();
+            f_name = filename.replace(m_dir, "");
+            // remove the first character from fiename
+            f_name.remove(0,1);
+        }
+
+        // clear the last folder open path
+        m_dir.clear();
+
+        return_val = true;
+
+#ifdef __APPLE__
+        // Check that user has given valid file name or not - forward slash not allowed in file name
+        // In Mac OSX, forward slash is converted to colon(:) by Qt so we need to check for colon.
+        if (f_name.indexOf(":") != -1)
+        {
+            QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
+            return return_val;
+        }
+#else
+        // Check that user has given valid file name or not - forward slash not allowed in file name
+        if (f_name.indexOf("/") != -1)
+        {
+            QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
+            return return_val;
+        }
+#endif
+        if(!filename.isEmpty())
+        {
+            // save the last open folder path
+            m_last_open_folder_path = QFileInfo(filename).path();
+            // Decode the encoded uri data
+            QString csvData = QUrl::fromPercentEncoding(write_data.toUtf8());
+
+            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 return_val;
+            }
+            // Write the csv data to file
+            qint64 data_return = csvfile.write(csvData.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 return_val;
+            }
+            csvfile.close();
+        }
+    }
+
+    return return_val;
+}
+
+void BrowserWindow::current_dir_path(const QString &dir)
+{
+    m_dir = dir;
+}
+
 // Slot: Link is open from pgAdmin mainwindow
 void BrowserWindow::urlLinkClicked(const QUrl &name)
 {
+    // Check that request contains the data download at client side
+    QNetworkRequest request;
+    if (checkClientDownload(name, request))
+        return;
+
     // First check is there any tab opened with same URL then open it again.
     int tabFound = findURLTab(name);
 
@@ -353,6 +773,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..7635549 100644
--- a/runtime/BrowserWindow.h
+++ b/runtime/BrowserWindow.h
@@ -54,6 +54,12 @@ 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();
+    void current_dir_path(const QString &dir);
 
 private:
     QString m_appServerUrl;
@@ -79,10 +85,19 @@ private:
 
     bool m_initialLoad;
     int m_loadAttempt;
+    QString m_downloadFilename;
+    int m_downloadStarted;
+    int m_downloadCancelled;
+    QFile *m_file;
+    QProgressDialog *m_progressDialog;
+    QString m_defaultFilename;
+    QString m_last_open_folder_path;
+    QString m_dir;
 
     void createActions();
     void pause(int seconds = 1);
     int  findURLTab(const QUrl &name);
+    bool checkClientDownload(const QUrl &name, const QNetworkRequest &request);
 };
 
 #endif // BROWSERWINDOW_H
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index 295caf4..2df2b5f 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -2587,7 +2587,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,
@@ -2600,7 +2600,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&filename=download.csv&value=' + 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: <CACCA4P3JOe40WYMGjhpSWYGR=WuvRbbp2gfDKLnU+1rXuW9Www@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