2013-06-16 14:17:46 +01:00
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2017-01-04 13:33:32 +00:00
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
2013-06-16 14:17:46 +01:00
// This software is released under the PostgreSQL Licence
//
// pgAdmin4.cpp - Main application entry point
//
//////////////////////////////////////////////////////////////////////////
2013-06-21 23:21:11 +01:00
#include "pgAdmin4.h"
2013-06-16 14:17:46 +01:00
// Must be before QT
#include <Python.h>
#if QT_VERSION >= 0x050000
#include <QtWidgets>
2017-04-03 12:58:08 +01:00
#include <QNetworkProxyFactory>
2017-03-26 21:02:15 -04:00
#include <QNetworkRequest>
#include <QNetworkReply>
2013-06-16 14:17:46 +01:00
#else
#include <QApplication>
2013-06-21 23:32:32 +01:00
#include <QDebug>
2016-01-18 14:33:28 +00:00
#include <QtNetwork>
#include <QLineEdit>
#include <QInputDialog>
2016-09-05 16:45:01 +01:00
#include <QSplashScreen>
2017-03-06 14:53:49 +00:00
#include <QUuid>
2017-03-29 12:17:16 -04:00
#include <QNetworkProxyFactory>
#include <QSslConfiguration>
2013-06-16 14:17:46 +01:00
#endif
2013-06-21 23:21:11 +01:00
// App headers
2013-06-16 14:17:46 +01:00
#include "BrowserWindow.h"
2016-01-19 10:26:01 +00:00
#include "ConfigWindow.h"
2013-06-21 23:21:11 +01:00
#include "Server.h"
2013-06-16 14:17:46 +01:00
2016-02-23 09:10:49 +00:00
#include <QTime>
2017-03-29 12:17:16 -04:00
// Implement support for system proxies for Qt 4.x on Linux
#if defined (Q_OS_LINUX) && QT_VERSION < 0x050000
#include "qnetworkproxy.h"
#include <QtCore/QByteArray>
#include <QtCore/QUrl>
#ifndef QT_NO_NETWORKPROXY
QT_BEGIN_NAMESPACE
static bool ignoreProxyFor ( const QNetworkProxyQuery & query )
{
const QByteArray noProxy = qgetenv ( "no_proxy" ). trimmed ();
if ( noProxy . isEmpty ())
return false ;
const QList < QByteArray > noProxyTokens = noProxy . split ( ',' );
foreach ( const QByteArray & rawToken , noProxyTokens ) {
QByteArray token = rawToken . trimmed ();
QString peerHostName = query . peerHostName ();
// Since we use suffix matching, "*" is our 'default' behaviour
if ( token . startsWith ( "*" ))
token = token . mid ( 1 );
// Harmonize trailing dot notation
if ( token . endsWith ( '.' ) && ! peerHostName . endsWith ( '.' ))
token = token . left ( token . length () - 1 );
// We prepend a dot to both values, so that when we do a suffix match,
// we don't match "donotmatch.com" with "match.com"
if ( ! token . startsWith ( '.' ))
token . prepend ( '.' );
if ( ! peerHostName . startsWith ( '.' ))
peerHostName . prepend ( '.' );
if ( peerHostName . endsWith ( QString :: fromLatin1 ( token )))
return true ;
}
return false ;
}
static QList < QNetworkProxy > pgAdminSystemProxyForQuery ( const QNetworkProxyQuery & query )
{
QList < QNetworkProxy > proxyList ;
if ( ignoreProxyFor ( query ))
return proxyList << QNetworkProxy :: NoProxy ;
// No need to care about casing here, QUrl lowercases values already
const QString queryProtocol = query . protocolTag ();
QByteArray proxy_env ;
if ( queryProtocol == QLatin1String ( "http" ))
proxy_env = qgetenv ( "http_proxy" );
else if ( queryProtocol == QLatin1String ( "https" ))
proxy_env = qgetenv ( "https_proxy" );
else if ( queryProtocol == QLatin1String ( "ftp" ))
proxy_env = qgetenv ( "ftp_proxy" );
else
proxy_env = qgetenv ( "all_proxy" );
// Fallback to http_proxy is no protocol specific proxy was found
if ( proxy_env . isEmpty ())
proxy_env = qgetenv ( "http_proxy" );
if ( ! proxy_env . isEmpty ())
{
QUrl url = QUrl ( QString :: fromLocal8Bit ( proxy_env ));
if ( url . scheme () == QLatin1String ( "socks5" ))
{
QNetworkProxy proxy ( QNetworkProxy :: Socks5Proxy , url . host (),
url . port () ? url . port () : 1080 , url . userName (), url . password ());
proxyList << proxy ;
} else if ( url . scheme () == QLatin1String ( "socks5h" ))
{
QNetworkProxy proxy ( QNetworkProxy :: Socks5Proxy , url . host (),
url . port () ? url . port () : 1080 , url . userName (), url . password ());
proxy . setCapabilities ( QNetworkProxy :: HostNameLookupCapability );
proxyList << proxy ;
} else if (( url . scheme () == QLatin1String ( "http" ) || url . scheme () == QLatin1String ( "https" ) || url . scheme (). isEmpty ())
&& query . queryType () != QNetworkProxyQuery :: UdpSocket
&& query . queryType () != QNetworkProxyQuery :: TcpServer )
{
QNetworkProxy proxy ( QNetworkProxy :: HttpProxy , url . host (),
url . port () ? url . port () : 8080 , url . userName (), url . password ());
proxyList << proxy ;
}
}
if ( proxyList . isEmpty ())
proxyList << QNetworkProxy :: NoProxy ;
return proxyList ;
}
class pgAdminSystemConfigurationProxyFactory : public QNetworkProxyFactory
{
public :
pgAdminSystemConfigurationProxyFactory () : QNetworkProxyFactory () {}
virtual QList < QNetworkProxy > queryProxy ( const QNetworkProxyQuery & query )
{
QList < QNetworkProxy > proxies = pgAdminSystemProxyForQuery ( query );
// Make sure NoProxy is in the list, so that QTcpServer can work:
// it searches for the first proxy that can has the ListeningCapability capability
// if none have (as is the case with HTTP proxies), it fails to bind.
// NoProxy allows it to fallback to the 'no proxy' case and bind.
proxies . append ( QNetworkProxy :: NoProxy );
return proxies ;
}
};
QT_END_NAMESPACE
#endif // QT_NO_NETWORKINTERFACE
#endif
2016-02-23 09:10:49 +00:00
void delay ( int milliseconds )
{
QTime endTime = QTime :: currentTime (). addMSecs ( milliseconds );
while ( QTime :: currentTime () < endTime )
{
QCoreApplication :: processEvents ( QEventLoop :: AllEvents , 100 );
}
}
2013-06-16 14:17:46 +01:00
int main ( int argc , char * argv [])
{
2017-08-09 16:11:44 +05:30
/*
* Before starting main application, need to set 'QT_X11_NO_MITSHM=1'
* to make the runtime work with IBM PPC machine.
*/
#if defined (Q_OS_LINUX)
QByteArray val ( "1" );
qputenv ( "QT_X11_NO_MITSHM" , val );
#endif
2013-06-16 14:17:46 +01:00
// Create the QT application
QApplication app ( argc , argv );
2013-10-04 10:08:18 -04:00
// Setup the settings management
2016-12-02 11:24:32 +09:00
QCoreApplication :: setOrganizationName ( "pgadmin" );
2013-10-04 10:08:18 -04:00
QCoreApplication :: setOrganizationDomain ( "pgadmin.org" );
2016-12-02 11:24:32 +09:00
QCoreApplication :: setApplicationName ( PGA_APP_NAME . toLower (). replace ( " " , "" ));
2013-10-04 10:08:18 -04:00
2017-06-16 11:26:31 +01:00
#if QT_VERSION >= 0x050000
2017-06-16 09:57:19 +01:00
// Set high DPI pixmap to display icons clear on Qt widget.
QApplication :: setAttribute ( Qt :: AA_UseHighDpiPixmaps );
2017-06-16 11:26:31 +01:00
#endif
2017-06-16 09:57:19 +01:00
2017-11-15 16:33:24 +00:00
#ifdef _WIN32
// Set registry "HKEY_CLASSES_ROOT\.css\Content Type" to value "text/css" to avoid rendering issue in windows OS.
QString infoMsgStr ( "" );
QSettings css_keys ( "HKEY_CLASSES_ROOT" , QSettings :: NativeFormat );
// If key already exists then check for existing value and it differs then only change it.
if ( css_keys . childGroups (). contains ( ".css" , Qt :: CaseInsensitive ))
{
QSettings set ( "HKEY_CLASSES_ROOT \\ .css" , QSettings :: NativeFormat );
if ( set . value ( "Content Type" ). toString () != "text/css" )
{
set . setValue ( "Content Type" , "text/css" );
// If error while setting registry then it should be issue with permissions.
if ( set . status () == QSettings :: NoError )
infoMsgStr = "pgAdmin 4 application has reset the registry key 'HKEY_CLASSES_ROOT \\ css \\ Content Type' to 'text/css' to fix a system misconfiguration that can lead to rendering problems." ;
else
infoMsgStr = "Failed to reset the registry key 'HKEY_CLASSES_ROOT \\ css \\ Content Type' to 'text/css'. Try to run with Administrator privileges." ;
}
}
#endif
2017-05-30 13:17:07 +01:00
/* In windows and linux, it is required to set application level proxy
* becuase socket bind logic to find free port gives socket creation error
* when system proxy is configured. We are also setting
* "setUseSystemConfiguration"=true to use the system proxy which will
* override this application level proxy. As this bug is fixed in Qt 5.9 so
* need to set application proxy for Qt version < 5.9.
*/
#ifndef PGADMIN4_USE_WEBENGINE
#if defined (Q_OS_WIN) && QT_VERSION <= 0x050800
// Give dummy URL required to find proxy server configured in windows.
QNetworkProxyQuery proxyQuery ( QUrl ( "https://www.pgadmin.org" ));
QNetworkProxy l_proxy ;
QList < QNetworkProxy > listOfProxies = QNetworkProxyFactory :: systemProxyForQuery ( proxyQuery );
if ( listOfProxies . size ())
{
l_proxy = listOfProxies [ 0 ];
// If host name is not empty means proxy server is configured.
if ( ! l_proxy . hostName (). isEmpty ()) {
QNetworkProxy :: setApplicationProxy ( QNetworkProxy ());
}
}
#endif
#endif
#ifndef PGADMIN4_USE_WEBENGINE
#if defined (Q_OS_LINUX) && QT_VERSION <= 0x050800
QByteArray proxy_env ;
proxy_env = qgetenv ( "http_proxy" );
// If http_proxy environment is defined in linux then proxy server is configured.
if ( ! proxy_env . isEmpty ()) {
QNetworkProxy :: setApplicationProxy ( QNetworkProxy ());
}
#endif
#endif
2016-09-05 16:45:01 +01:00
// Display the spash screen
QSplashScreen * splash = new QSplashScreen ();
splash -> setPixmap ( QPixmap ( ":/splash.png" ));
splash -> show ();
app . processEvents ( QEventLoop :: AllEvents );
2016-01-18 14:33:28 +00:00
quint16 port = 0L ;
2013-10-04 18:16:31 +01:00
// Find an unused port number. Essentially, we're just reserving one
2014-12-16 12:53:09 +00:00
// here that Flask will use when we start up the server.
2016-01-18 14:33:28 +00:00
// In order to use the socket, we need to free this socket ASAP.
// Hence - putting this code in a code block so the scope of the socket
// variable vanishes to make that socket available.
{
2017-03-29 11:33:55 -04:00
#if QT_VERSION >= 0x050000
2017-03-26 21:02:15 -04:00
QTcpSocket socket ;
2017-07-11 11:48:13 +01:00
#if QT_VERSION >= 0x050900
socket . setProxy ( QNetworkProxy :: NoProxy );
#endif
2017-03-26 21:02:15 -04:00
socket . bind ( 0 , QTcpSocket :: ShareAddress );
2017-03-29 11:33:55 -04:00
#else
QUdpSocket socket ;
socket . bind ( 0 , QUdpSocket :: ShareAddress );
#endif
2016-01-18 14:33:28 +00:00
port = socket . localPort ();
}
2013-10-04 17:12:10 +01:00
2017-03-06 14:53:49 +00:00
// Generate a random key to authenticate the client to the server
QString key = QUuid :: createUuid (). toString ();
key = key . mid ( 1 , key . length () - 2 );
2017-03-29 12:17:16 -04:00
#if defined (Q_OS_LINUX) && QT_VERSION < 0x050000
QNetworkProxyFactory :: setApplicationProxyFactory ( new pgAdminSystemConfigurationProxyFactory );
QSslConfiguration sslCfg = QSslConfiguration :: defaultConfiguration ();
QList < QSslCertificate > ca_list = sslCfg . caCertificates ();
QList < QSslCertificate > ca_new = QSslCertificate :: fromData ( "CaCertificates" );
ca_list += ca_new ;
sslCfg . setCaCertificates ( ca_list );
sslCfg . setProtocol ( QSsl :: AnyProtocol );
QSslConfiguration :: setDefaultConfiguration ( sslCfg );
#else
QNetworkProxyFactory :: setUseSystemConfiguration ( true );
#endif
2013-06-21 23:21:11 +01:00
// Fire up the webserver
2016-11-02 13:42:08 +05:30
Server * server ;
2013-06-21 23:21:11 +01:00
2016-11-02 13:42:08 +05:30
bool done = false ;
while ( done != true )
2013-06-21 23:21:11 +01:00
{
2017-03-06 14:53:49 +00:00
server = new Server ( port , key );
2013-06-21 23:21:11 +01:00
2016-11-02 13:42:08 +05:30
if ( ! server -> Init ())
{
2017-02-27 10:49:47 +00:00
splash -> finish ( NULL );
2016-11-02 13:42:08 +05:30
qDebug () << server -> getError ();
2013-06-21 23:21:11 +01:00
2016-11-02 13:42:08 +05:30
QString error = QString ( QWidget :: tr ( "An error occurred initialising the application server: \n\n %1" )). arg ( server -> getError ());
QMessageBox :: critical ( NULL , QString ( QWidget :: tr ( "Fatal Error" )), error );
2013-06-16 14:17:46 +01:00
2016-11-02 13:42:08 +05:30
exit ( 1 );
}
2014-12-16 12:53:09 +00:00
2016-11-02 13:42:08 +05:30
server -> start ();
2014-12-16 12:53:09 +00:00
2016-11-02 13:42:08 +05:30
// Any errors?
if ( server -> isFinished () || server -> getError (). length () > 0 )
{
splash -> finish ( NULL );
2014-12-16 12:53:09 +00:00
2016-11-02 13:42:08 +05:30
qDebug () << server -> getError ();
2016-01-19 10:26:01 +00:00
2016-11-02 13:42:08 +05:30
QString error = QString ( QWidget :: tr ( "An error occurred initialising the application server: \n\n %1" )). arg ( server -> getError ());
QMessageBox :: critical ( NULL , QString ( QWidget :: tr ( "Fatal Error" )), error );
2014-12-16 12:53:09 +00:00
2016-11-02 13:42:08 +05:30
// Allow the user to tweak the Python Path if needed
QSettings settings ;
bool ok ;
ConfigWindow * dlg = new ConfigWindow ();
dlg -> setWindowTitle ( QWidget :: tr ( "Configuration" ));
dlg -> setPythonPath ( settings . value ( "PythonPath" ). toString ());
dlg -> setApplicationPath ( settings . value ( "ApplicationPath" ). toString ());
dlg -> setModal ( true );
ok = dlg -> exec ();
QString pythonpath = dlg -> getPythonPath ();
QString applicationpath = dlg -> getApplicationPath ();
if ( ok )
{
settings . setValue ( "PythonPath" , pythonpath );
settings . setValue ( "ApplicationPath" , applicationpath );
settings . sync ();
}
else
{
exit ( 1 );
}
delete server ;
2016-01-19 10:26:01 +00:00
}
2016-11-02 13:42:08 +05:30
else
done = true ;
2014-12-16 12:53:09 +00:00
}
2016-11-02 13:42:08 +05:30
2013-10-04 18:16:31 +01:00
// Generate the app server URL
2017-06-11 13:56:49 +01:00
QString appServerUrl = QString ( "http://127.0.0.1:%1/?key=%2" ). arg ( port ). arg ( key );
2013-10-04 18:16:31 +01:00
2017-07-17 15:17:58 +01:00
// Read the server connection timeout from the registry or set the default timeout.
QSettings settings ;
int timeout = settings . value ( "ConnectionTimeout" , 30 ). toInt ();
2013-10-04 18:16:31 +01:00
// Now the server should be up, we'll attempt to connect and get a response.
2017-02-27 10:49:47 +00:00
// We'll retry in a loop a few time before aborting if necessary.
2017-07-17 15:17:58 +01:00
QTime endTime = QTime :: currentTime (). addSecs ( timeout );
bool alive = false ;
while ( QTime :: currentTime () <= endTime )
2013-10-04 18:16:31 +01:00
{
2017-07-17 15:17:58 +01:00
alive = PingServer ( QUrl ( appServerUrl ));
2013-10-04 18:16:31 +01:00
if ( alive )
{
break ;
}
2017-07-17 15:17:58 +01:00
delay ( 200 );
}
2013-10-04 18:16:31 +01:00
2017-07-17 15:17:58 +01:00
// Attempt to connect one more time in case of a long network timeout while looping
if ( ! alive && ! PingServer ( QUrl ( appServerUrl )))
{
splash -> finish ( NULL );
QString error ( QWidget :: tr ( "The application server could not be contacted." ));
QMessageBox :: critical ( NULL , QString ( QWidget :: tr ( "Fatal Error" )), error );
2017-06-11 14:38:22 +01:00
2017-07-17 15:17:58 +01:00
exit ( 1 );
2013-10-04 18:16:31 +01:00
}
2013-06-16 14:17:46 +01:00
// Create & show the main window
2013-10-04 18:16:31 +01:00
BrowserWindow browserWindow ( appServerUrl );
2016-01-22 14:23:47 +00:00
browserWindow . setWindowTitle ( PGA_APP_NAME );
2016-01-25 17:21:00 +00:00
browserWindow . setWindowIcon ( QIcon ( ":/pgAdmin4.ico" ));
2017-11-15 16:33:24 +00:00
#ifdef _WIN32
browserWindow . setRegistryMessage ( infoMsgStr );
#endif
2013-06-16 14:17:46 +01:00
browserWindow . show ();
// Go!
2016-09-05 16:45:01 +01:00
splash -> finish ( NULL );
2017-06-16 09:57:19 +01:00
// Set global application stylesheet.
QFile file ( ":/qss/pgadmin4.qss" );
if ( file . open ( QFile :: ReadOnly ))
{
QString StyleSheet = QLatin1String ( file . readAll ());
qApp -> setStyleSheet ( StyleSheet );
file . close ();
}
2013-06-16 14:17:46 +01:00
return app . exec ();
}
2014-03-09 14:39:12 +00:00
// Ping the application server to see if it's alive
2013-10-04 18:16:31 +01:00
bool PingServer ( QUrl url )
{
QNetworkAccessManager manager ;
QEventLoop loop ;
QNetworkReply * reply ;
QVariant redirectUrl ;
2017-07-17 15:19:30 +01:00
url . setPath ( "/misc/ping" );
2013-10-04 18:16:31 +01:00
do
{
reply = manager . get ( QNetworkRequest ( url ));
QObject :: connect ( reply , SIGNAL ( finished ()), & loop , SLOT ( quit ()));
loop . exec ();
redirectUrl = reply -> attribute ( QNetworkRequest :: RedirectionTargetAttribute );
url = redirectUrl . toUrl ();
if ( ! redirectUrl . isNull ())
delete reply ;
} while ( ! redirectUrl . isNull ());
if ( reply -> error () != QNetworkReply :: NoError )
{
return false ;
}
QString response = reply -> readAll ();
if ( response != "PING" )
{
qDebug () << "Failed to connect, server response: " << response ;
return false ;
}
return true ;
}
2014-03-09 14:39:12 +00:00