1 /** @file localserver.cpp Starting and stopping local servers.
2 *
3 * @authors Copyright © 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * LGPL: http://www.gnu.org/licenses/lgpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14 * General Public License for more details. You should have received a copy of
15 * the GNU Lesser General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "de/shell/LocalServer"
20 #include "de/shell/Link"
21 #include "de/shell/DoomsdayInfo"
22 #include <de/CommandLine>
23 #include <QCoreApplication>
24 #include <QDir>
25
26 #if !defined (DENG_MOBILE)
27
28 namespace de { namespace shell {
29
30 static String const ERROR_LOG_NAME = "doomsday-errors.out";
31
DENG2_PIMPL_NOREF(LocalServer)32 DENG2_PIMPL_NOREF(LocalServer)
33 {
34 Link * link = nullptr;
35 NativePath appPath; // where to find the server
36 duint16 port = 0;
37 String name;
38 NativePath userDir;
39 QProcess * proc = nullptr; // not deleted until stopped
40
41 ~Impl()
42 {
43 if (proc && proc->state() == QProcess::NotRunning)
44 {
45 delete proc;
46 }
47 }
48 };
49
LocalServer()50 LocalServer::LocalServer() : d(new Impl)
51 {}
52
setName(String const & name)53 void LocalServer::setName(String const &name)
54 {
55 d->name = name;
56 d->name.replace("\"", "\\\""); // for use on command line
57 }
58
setApplicationPath(NativePath const & path)59 void LocalServer::setApplicationPath(NativePath const &path)
60 {
61 d->appPath = path;
62 }
63
start(duint16 port,String const & gameMode,QStringList additionalOptions,NativePath const & runtimePath)64 void LocalServer::start(duint16 port,
65 String const &gameMode,
66 QStringList additionalOptions,
67 NativePath const &runtimePath)
68 {
69 d->port = port;
70
71 d->userDir = runtimePath;
72
73 if (d->userDir.isEmpty())
74 {
75 // Default runtime location.
76 d->userDir = DoomsdayInfo::defaultServerRuntimeFolder();
77 }
78
79 // Get rid of a previous error log in this location.
80 QDir(d->userDir).remove(ERROR_LOG_NAME);
81
82 DENG2_ASSERT(d->link == 0);
83
84 CommandLine cmd;
85 NativePath bin;
86
87 #ifdef MACOSX
88 // First locate the server executable.
89 if (!d->appPath.isEmpty())
90 {
91 bin = d->appPath / "Doomsday.app/Contents/MacOS/doomsday-server";
92 if (!bin.exists())
93 {
94 bin = d->appPath / "Contents/MacOS/doomsday-server";
95 }
96 }
97 if (!bin.exists())
98 {
99 bin = NativePath(qApp->applicationDirPath()) / "../MacOS/doomsday-server";
100 }
101 if (!bin.exists())
102 {
103 // Yet another possibility: Doomsday Shell.app -> Doomsday.app
104 // App folder randomization means this is only useful in developer builds, though.
105 bin = NativePath(qApp->applicationDirPath()) /
106 "../../../Doomsday.app/Contents/MacOS/doomsday-server";
107 }
108 if (!bin.exists())
109 {
110 throw NotFoundError("LocalServer::start", "Could not find Doomsday.app");
111 }
112 cmd.append(bin);
113
114 #elif WIN32
115 if (!d->appPath.isEmpty())
116 {
117 bin = d->appPath / "doomsday-server.exe";
118 }
119 if (!bin.exists())
120 {
121 bin = NativePath(qApp->applicationDirPath()) / "doomsday-server.exe";
122 }
123 cmd.append(bin);
124 cmd.append("-basedir");
125 cmd.append(bin.fileNamePath() / "..");
126
127 #else // UNIX
128 if (!d->appPath.isEmpty())
129 {
130 bin = d->appPath / "doomsday-server";
131 }
132 if (!bin.exists())
133 {
134 bin = NativePath(qApp->applicationDirPath()) / "doomsday-server";
135 }
136 if (!bin.exists()) bin = "doomsday-server"; // Perhaps it's on the path?
137 cmd.append(bin);
138 #endif
139
140 cmd.append("-userdir");
141 cmd.append(d->userDir);
142 cmd.append("-errors");
143 cmd.append(ERROR_LOG_NAME);
144 cmd.append("-game");
145 cmd.append(gameMode);
146 cmd.append("-cmd");
147 cmd.append("net-ip-port " + String::number(port));
148
149 if (!d->name.isEmpty())
150 {
151 cmd.append("-cmd");
152 cmd.append("server-name \"" + d->name + "\"");
153 }
154
155 foreach (String opt, additionalOptions) cmd.append(opt);
156
157 LOG_NET_NOTE("Starting local server on port %i using game mode '%s'")
158 << port << gameMode;
159
160 d->proc = cmd.executeProcess();
161 }
162
stop()163 void LocalServer::stop()
164 {
165 if (isRunning())
166 {
167 LOG_NET_NOTE("Stopping local server on port %i") << d->port;
168 d->proc->kill();
169 }
170 }
171
port() const172 duint16 LocalServer::port() const
173 {
174 return d->port;
175 }
176
isRunning() const177 bool LocalServer::isRunning() const
178 {
179 if (!d->proc) return false;
180 return (d->proc->state() != QProcess::NotRunning);
181 }
182
openLink()183 Link *LocalServer::openLink()
184 {
185 if (!isRunning()) return nullptr;
186 return new Link(String("localhost:%1").arg(d->port), 30);
187 }
188
errorLogPath() const189 NativePath LocalServer::errorLogPath() const
190 {
191 return d->userDir / ERROR_LOG_NAME;
192 }
193
194 }} // namespace de::shell
195
196 #endif
197