Properly support file downloads in the desktop runtime. Fixes #1405. Fixes #1342

This commit is contained in:
Neel Patel 2016-07-13 12:26:24 +01:00 committed by Dave Page
parent 1ada5031cd
commit d22ea8bf61
3 changed files with 462 additions and 3 deletions

View File

@ -23,7 +23,6 @@
#include <QInputDialog> #include <QInputDialog>
#include <QLineEdit> #include <QLineEdit>
#endif #endif
// App headers // App headers
#include "BrowserWindow.h" #include "BrowserWindow.h"
#include "ConfigWindow.h" #include "ConfigWindow.h"
@ -42,6 +41,15 @@ BrowserWindow::BrowserWindow(QString url)
m_widget = NULL; m_widget = NULL;
m_toolBtnBack = NULL; m_toolBtnBack = NULL;
m_toolBtnForward = 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; m_appServerUrl = url;
@ -83,6 +91,11 @@ BrowserWindow::BrowserWindow(QString url)
// Register the slot on tab index change // Register the slot on tab index change
connect(m_tabWidget,SIGNAL(currentChanged(int )),this,SLOT(tabIndexChanged(int ))); 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); m_mainWebView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// Restore the geometry // Restore the geometry
@ -199,6 +212,297 @@ int BrowserWindow::findURLTab(const QUrl &name)
return 0; 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(tr("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 // Slot: When the tab index change, hide/show the toolbutton displayed on tab
void BrowserWindow::tabIndexChanged(int index) 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 // Slot: Link is open from pgAdmin mainwindow
void BrowserWindow::urlLinkClicked(const QUrl &name) 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. // First check is there any tab opened with same URL then open it again.
int tabFound = findURLTab(name); int tabFound = findURLTab(name);
@ -353,6 +791,11 @@ void BrowserWindow::urlLinkClicked(const QUrl &name)
m_addNewGridLayout->setContentsMargins(0, 0, 0, 0); m_addNewGridLayout->setContentsMargins(0, 0, 0, 0);
m_addNewWebView = new WebViewWindow(m_addNewTab); 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_widget = new QWidget(m_addNewTab);
m_toolBtnBack = new QToolButton(m_widget); m_toolBtnBack = new QToolButton(m_widget);
m_toolBtnBack->setFixedHeight(PGA_BTN_SIZE); m_toolBtnBack->setFixedHeight(PGA_BTN_SIZE);

View File

@ -54,6 +54,12 @@ public slots:
void tabIndexChanged(int index); void tabIndexChanged(int index);
void goBackPage(); void goBackPage();
void goForwardPage(); 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: private:
QString m_appServerUrl; QString m_appServerUrl;
@ -79,10 +85,20 @@ private:
bool m_initialLoad; bool m_initialLoad;
int m_loadAttempt; 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 createActions();
void pause(int seconds = 1); void pause(int seconds = 1);
int findURLTab(const QUrl &name); int findURLTab(const QUrl &name);
bool checkClientDownload(const QUrl &name, const QNetworkRequest &request);
}; };
#endif // BROWSERWINDOW_H #endif // BROWSERWINDOW_H

View File

@ -2601,7 +2601,7 @@ define(
keys = _.pluck(self.columns, 'name'); keys = _.pluck(self.columns, 'name');
// Fetch the items from fullCollection and convert it as csv format // 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) { csv += coll.map(function(item) {
return _.map(keys, function(key) { return _.map(keys, function(key) {
var cell = csv_col [key].cell, var cell = csv_col [key].cell,
@ -2614,7 +2614,7 @@ define(
}).join('\n'); }).join('\n');
// Download the file. // 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 = document.createElement('a');
link.setAttribute('href', encodedUri); link.setAttribute('href', encodedUri);