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