1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2017 Calle Laakkonen
5 
6    Drawpile is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Drawpile is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Drawpile.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "mainwindow.h"
21 #include "localserver.h"
22 #include "remoteserver.h"
23 #include "multiserver.h"
24 #include "database.h"
25 #include "trayicon.h"
26 #include "singleinstance.h"
27 #include "authdialog.h"
28 
29 #include <QApplication>
30 #include <QCommandLineParser>
31 #include <QThread>
32 #include <QMessageBox>
33 #include <QMutex>
34 #include <QFileInfo>
35 #include <QStandardPaths>
36 #include <QDir>
37 #include <QSystemTrayIcon>
38 #include <QSettings>
39 #include <QMenu>
40 
41 namespace server {
42 namespace gui {
43 
startServer()44 bool startServer()
45 {
46 	QString errorMessage;
47 	MultiServer *server=nullptr;
48 
49 	// A database is always used with the GUI server
50 	QDir dbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
51 	dbDir.mkpath(".");
52 	QString dbFile = QFileInfo(dbDir, "guiserver.db").absoluteFilePath();
53 
54 	// The server must be initialized in its own thread
55 	QMutex mutex;
56 	auto threadInitFunc = [dbFile, &errorMessage, &server, &mutex]() {
57 		Database *db = new Database;
58 		if(!db->openFile(dbFile)) {
59 			errorMessage = QApplication::tr("Couldn't open database");
60 			mutex.unlock();
61 			return;
62 		}
63 
64 		server = new MultiServer(db);
65 		db->setParent(server);
66 
67 		mutex.unlock();
68 	};
69 
70 	// Start a new thread for the server
71 	qDebug() << "Starting server thread";
72 	QThread *serverThread = new QThread;
73 	auto initConnetion = serverThread->connect(serverThread, &QThread::started, threadInitFunc);
74 	mutex.lock();
75 	serverThread->start();
76 
77 	// Wait for initialization to complete
78 	mutex.lock();
79 	mutex.unlock();
80 	QObject::disconnect(initConnetion);
81 	qDebug() << "Server thread initialized";
82 
83 	if(!errorMessage.isEmpty()) {
84 		QMessageBox::critical(nullptr, QApplication::tr("Drawpile Server"), errorMessage);
85 		return false;
86 	}
87 
88 	Q_ASSERT(server);
89 
90 	// The local server connector to use
91 	LocalServer *localServer = new LocalServer(server);
92 
93 	QObject::connect(localServer, &LocalServer::serverError, [](const QString &error) {
94 		QMessageBox::warning(nullptr, QApplication::tr("Drawpile Server"), error);
95 	});
96 
97 	// Create the system tray menu
98 	if(QSettings().value("ui/trayicon", true).toBool()) {
99 		TrayIcon::showTrayIcon();
100 	}
101 
102 	QObject::connect(server, &MultiServer::userCountChanged, TrayIcon::instance(), &TrayIcon::setNumber);
103 	QObject::connect(localServer, &LocalServer::serverStateChanged, TrayIcon::instance(), &TrayIcon::setServerOn);
104 
105 	// Open the main window
106 	MainWindow::setDefaultInstanceServer(localServer);
107 	MainWindow::showDefaultInstance();
108 
109 	// Quit when last window is closed, but not if the tray icon is visible
110 	qApp->setQuitOnLastWindowClosed(false);
111 #ifndef Q_OS_MAC
112 	QObject::connect(qApp, &QApplication::lastWindowClosed, []() {
113 		if(!TrayIcon::isTrayIconVisible())
114 			qApp->quit();
115 	});
116 #endif
117 
118 	return true;
119 }
120 
startRemote(const QString & address)121 bool startRemote(const QString &address)
122 {
123 	QUrl url(address);
124 	if(!url.isValid()) {
125 		QMessageBox::critical(nullptr, QApplication::tr("Drawpile Server"), QApplication::tr("Invalid URL"));
126 		return false;
127 	}
128 
129 	AuthDialog::init();
130 
131 	RemoteServer *remote = new RemoteServer(url);
132 
133 	MainWindow *win = new MainWindow(remote);
134 	remote->setParent(win);
135 
136 	win->show();
137 	return true;
138 }
139 
start()140 bool start() {
141 	// Set up command line arguments
142 	QCommandLineParser parser;
143 
144 	parser.setApplicationDescription("Standalone server for Drawpile (graphical mode)");
145 	parser.addHelpOption();
146 
147 	// --gui (this is just for the help text)
148 	QCommandLineOption guiOption(QStringList() << "gui", "Run the graphical version.");
149 	parser.addOption(guiOption);
150 
151 	// remote <address>
152 	QCommandLineOption remoteOption(QStringList() << "remote", "Remote admin mode", "address");
153 	parser.addOption(remoteOption);
154 
155 	// Parse
156 	parser.process(*QCoreApplication::instance());
157 
158 	if(parser.isSet(remoteOption)) {
159 		// Remote admin mode
160 		return startRemote(parser.value(remoteOption));
161 
162 	} else {
163 		// Normal server mode
164 		SingleInstance *guard = new SingleInstance;
165 		if(!guard->tryStart()) {
166 			qWarning("Another instance is already running");
167 			return false;
168 		}
169 		QObject::connect(qApp, &QApplication::aboutToQuit, guard, &SingleInstance::deleteLater);
170 
171 		return startServer();
172 	}
173 }
174 
175 }
176 }
177