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: Fri, 8 Jul 2016 11:47:23 +0530
Message-ID: <CACCA4P3aToRjTteJK_asEmU-Bb2C_ASBTP=-Bx7DfBFfETx98g@mail.gmail.com> (raw)
In-Reply-To: <CA+OCxoyTTFbx1-p5-yZh_y1z5zzQm5TrMXFgq4qPM4PTendMAg@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>
	<CACCA4P3JOe40WYMGjhpSWYGR=WuvRbbp2gfDKLnU+1rXuW9Www@mail.gmail.com>
	<CA+OCxoyTTFbx1-p5-yZh_y1z5zzQm5TrMXFgq4qPM4PTendMAg@mail.gmail.com>
List-Unsubscribe:  <mailto:[email protected]?body=unsub%20pgadmin-hackers>

Hi Dave,

Please find attached patch file for the fix of crash and comment text.
Downloading cancel request was not handled properly and due to that
application was getting crashed.

Do review it and let us know for comments.

Thanks,
Neel Patel

On Thu, Jul 7, 2016 at 2:13 PM, Dave Page <[email protected]> wrote:

> Hi
>
> On Wed, Jul 6, 2016 at 9:12 AM, Neel Patel <[email protected]>
> wrote:
> > 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
> > ?
>
> That's definitely getting there;
>
> - Please make sure you follow the code style requirements, e.g.
> //<space>Comment text
>
> - In your comments, typically you should refer to the user as "the
> user" not just "user". e.g.
>   // Check that *the* user has given *a* valid file name or not
>
>   The same applies to other cases where you miss the article
> (https://en.wikipedia.org/wiki/Article_(grammar)):
>   // Check that *the* request contains the data download at client side
>
> NOTE: This isn't a criticism of you in particular - most of the team
> do this, I assume because it's more like the way you'd phrase things
> in Hindi. I just find myself correcting such mistakes regularly, and
> it's good for us all to continue to improve in general.
>
> - I was able to reproduce the crash again:
>   1) Open a tab, and go to the PostgreSQL download page on
> enterprisedb.com (linked from the pg.org site)
>   2) Start to download the 9.6b2 Win64 installer
>   3) Cancel the download
>   4) Click the link to download if your download didn't automatically start
>   5) Overwrite the existing file
>
> This results in:
>   a) The progress bar flashes up and down weirdly on the second download
>   b) The app crashes when the download completes:
>
> The program has unexpectedly finished.
>
> /Users/dpage/git/pgadmin4/build-pgAdmin4-Desktop_Qt_5_5_1_clang_64bit2-Debug/pgAdmin4.app/Contents/MacOS/pgAdmin4
> crashed
>
> See the attached backtrace.
>
> Thanks!
>
> > 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
> >
> >
>
>
>
> --
> 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_v4.patch (19.4K, 3-download_runtime_v4.patch)
  download | inline diff:
diff --git a/runtime/BrowserWindow.cpp b/runtime/BrowserWindow.cpp
index e875b28..c507b61 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,15 @@ 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_reply = NULL;
 
     m_appServerUrl = url;
 
@@ -83,6 +91,11 @@ BrowserWindow::BrowserWindow(QString url)
     // Register the slot on tab index change
     connect(m_tabWidget,SIGNAL(currentChanged(int )),this,SLOT(tabIndexChanged(int )));
 
+    // Listen for 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 +212,297 @@ int BrowserWindow::findURLTab(const QUrl &name)
     return 0;
 }
 
+// Below slot will be called when user right click on download link and select "Save Link..." option from context menu
+void BrowserWindow::download(const QNetworkRequest &request)
+{
+    // Check that request contains data for download at client side
+    QUrl name;
+    if (checkClientDownload(name, request))
+        return;
+
+    if (m_downloadStarted)
+    {
+        // Inform 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();
+
+    // Open the dialog to save file
+    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);
+
+    // Register the slot for directory travesing when file dialog is opened and save the last open directory
+    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 open directory path
+    m_dir.clear();
+
+#ifdef __APPLE__
+    // Check that user has given valid file name or not - forward slash is 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 is 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
+                    connect( reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadFileProgress(qint64, qint64)) );
+                    connect( reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
+                }
+            }
+        }
+    }
+}
+
+// Below slot will be called when file download is in progress
+void BrowserWindow::downloadFileProgress(qint64 readData, qint64 totalData)
+{
+    QNetworkReply *reply = ((QNetworkReply*)sender());
+    QNetworkRequest request = reply->request();
+    QVariant v = request.attribute(QNetworkRequest::User);
+
+    // When download is canceled by user then no need to write data to file
+    if (m_downloadCancelled)
+        return;
+
+    if(reply != NULL && reply->error() != QNetworkReply::NoError)
+    {
+        qDebug() << "Network error occurred whilst downloading: " << m_defaultFilename;
+        return;
+    }
+
+    // Download is started so open the file
+    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;
+        }
+
+        // Create progress bar dialog
+        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);
+        // Register slot for file download cancel request
+        QObject::connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(progressCanceled()));
+        m_reply = reply;
+        // Show downloading progress bar
+        m_progressDialog->show();
+    }
+
+    if (m_file)
+    {
+        // Write 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)
+        {
+            // As downloading is finished so remove progress bar dialog
+            if (m_progressDialog)
+            {
+                delete m_progressDialog;
+                m_progressDialog = NULL;
+            }
+
+            m_downloadStarted = 0;
+            m_downloadFilename.clear();
+            m_defaultFilename.clear();
+            m_downloadCancelled = 0;
+            if (m_file)
+            {
+                m_file->close();
+                delete m_file;
+                m_file = NULL;
+            }
+
+            if (m_reply)
+              m_reply = NULL;
+        }
+    }
+}
+
+// Below slot will be 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)
+    {
+        m_file->close();
+        // Remove the file from file system as downloading is canceled by user
+        m_file->remove();
+        delete m_file;
+        m_file = NULL;
+    }
+
+    if (m_reply)
+    {
+        m_reply->abort();
+        m_reply = NULL;
+    }
+
+    m_downloadFilename.clear();
+    m_defaultFilename.clear();
+    m_downloadStarted = 0;
+}
+
+// Below slot will called when file download is finished
+void BrowserWindow::downloadFinished()
+{
+    if (m_progressDialog)
+    {
+        delete m_progressDialog;
+        m_progressDialog = NULL;
+    }
+
+    m_downloadFilename.clear();
+    m_defaultFilename.clear();
+    m_downloadStarted = 0;
+    m_downloadCancelled = 0;
+    if (m_file)
+    {
+        m_file->close();
+        delete m_file;
+        m_file = NULL;
+    }
+
+    if (m_reply)
+        m_reply = NULL;
+}
+
+// Below slot will be called when user directly click on any download link
+void BrowserWindow::unsupportedContent(QNetworkReply * reply)
+{
+    if (m_downloadStarted)
+    {
+        // Inform user that download is 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 last open folder path
+    m_dir.clear();
+
+#ifdef __APPLE__
+    // Check that user has given valid file name or not - forward slash is 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 is 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 +644,143 @@ void BrowserWindow::tabTitleChanged(const QString &str)
     }
 }
 
+// Below function will be 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());
+
+    // Extract the filename and value(data) from encoded URL
+    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;
+
+    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 first character from fiename
+            f_name.remove(0,1);
+        }
+
+        // clear last open folder path
+        m_dir.clear();
+
+        return_val = true;
+
+#ifdef __APPLE__
+        // Check that user has given valid file name or not - forward slash is 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 is 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 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 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;
+    m_last_open_folder_path = 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 +791,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..7200ff3 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,20 @@ 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;
+    QNetworkReply *m_reply;
 
     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: <CACCA4P3aToRjTteJK_asEmU-Bb2C_ASBTP=-Bx7DfBFfETx98g@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