1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <cmajor/build/ServerConfig.hpp>
  7 #include <cmajor/symbols/GlobalFlags.hpp>
  8 #include <sngcm/ast/Project.hpp>
  9 #include <sngxml/dom/Parser.hpp>
 10 #include <sngxml/dom/Document.hpp>
 11 #include <sngxml/dom/Element.hpp>
 12 #include <sngxml/xpath/XPathEvaluate.hpp>
 13 #include <soulng/util/Path.hpp>
 14 #include <soulng/util/Log.hpp>
 15 #include <soulng/util/Unicode.hpp>
 16 #include <boost/filesystem.hpp>
 17 #include <boost/lexical_cast.hpp>
 18 #include <iostream>
 19 
 20 namespace cmajor { namespace build {
 21 
 22 using namespace sngcm::ast;
 23 using namespace sngxml::dom;
 24 using namespace sngxml::xpath;
 25 using namespace soulng::unicode;
 26 using namespace cmajor::symbols;
 27 
 28 std::string GetDefaultToolChainForCurrentPlatform()
 29 {
 30 #ifdef _WIN32
 31 
 32 #else
 33     return "gcc";
 34 #endif
 35 }
 36 
 37 int GetDefaultLocalPort()
 38 {
 39     return 52360;
 40 }
 41 
 42 ServerInfo::ServerInfo(const std::string& serverName_const std::string& host_int port_const std::string& defaultToolChain_) :
 43     serverName(serverName_)host(host_)port(port_)defaultToolChain(defaultToolChain_)
 44 {
 45 }
 46 
 47 void ServerInfo::SetDefaultToolChain(const std::string& defaultToolChain_)
 48 {
 49     defaultToolChain = defaultToolChain_;
 50 }
 51 
 52 void ServerInfo::Print(CodeFormatter& formatter)
 53 {
 54     std::string toolChainStr;
 55     std::string hostStr;
 56     if (!defaultToolChain.empty())
 57     {
 58         toolChainStr = ", default tool chain '" + defaultToolChain + "'";
 59     }
 60     if (!host.empty())
 61     {
 62         hostStr = ", host " + host;
 63     }
 64     formatter.WriteLine("server '" + serverName + "'" + hostStr + ", port " + std::to_string(port) + toolChainStr);
 65 }
 66 
 67 void ServerConfig::Init()
 68 {
 69     instance.reset(new ServerConfig());
 70 }
 71 
 72 void ServerConfig::Done()
 73 {
 74     instance.reset();
 75 }
 76 
 77 std::string CmajorConfigDir()
 78 {
 79     return Path::Combine(CmajorRootDir()"config");
 80 }
 81 
 82 std::string CmajorServerConfigFilePath()
 83 {
 84     return Path::Combine(CmajorConfigDir()"server-config.xml");
 85 }
 86 
 87 std::unique_ptr<ServerConfig> ServerConfig::instance;
 88 
 89 void ServerConfig::Read()
 90 {
 91     std::string serverConfigFilePath = CmajorServerConfigFilePath();
 92     try
 93     {
 94         if (boost::filesystem::exists(serverConfigFilePath))
 95         {
 96             serverInfos.clear();
 97             serverMap.clear();
 98             hostPortMap.clear();
 99             std::unique_ptr<sngxml::dom::Document> configDoc = ReadDocument(serverConfigFilePath);
100             std::unique_ptr<sngxml::xpath::XPathObject> servers = Evaluate(U"/servers/server"configDoc.get());
101             if (servers)
102             {
103                 if (servers->Type() == sngxml::xpath::XPathObjectType::nodeSet)
104                 {
105                     sngxml::xpath::XPathNodeSet* nodeSet = static_cast<sngxml::xpath::XPathNodeSet*>(servers.get());
106                     int n = nodeSet->Length();
107                     for (int i = 0; i < n; ++i)
108                     {
109                         sngxml::dom::Node* node = (*nodeSet)[i];
110                         if (node->GetNodeType() == sngxml::dom::NodeType::elementNode)
111                         {
112                             sngxml::dom::Element* element = static_cast<sngxml::dom::Element*>(node);
113                             std::string serverName = ToUtf8(element->GetAttribute(U"name"));
114                             std::string serverHost = ToUtf8(element->GetAttribute(U"host"));
115                             std::string serverPort = ToUtf8(element->GetAttribute(U"port"));
116                             int port = boost::lexical_cast<int>(serverPort);
117                             std::string defaultToolChain = ToUtf8(element->GetAttribute(U"defaultToolChain"));
118                             Add(serverNameserverHostportdefaultToolChaintruefalsefalse);
119                         }
120                     }
121                 }
122             }
123         }
124     }
125     catch (const std::exception& ex;)
126     {
127         LogMessage(-1"server config: reading server configuration from '" + serverConfigFilePath + "' failed: " + ex.what());
128     }
129 }
130 
131 void ServerConfig::Write()
132 {
133     std::string serverConfigFilePath = CmajorServerConfigFilePath();
134     try
135     {
136         sngxml::dom::Document serverConfigDoc;
137         sngxml::dom::Element* serversElement = new sngxml::dom::Element(U"servers");
138         serverConfigDoc.AppendChild(std::unique_ptr<sngxml::dom::Node>(serversElement));
139         for (const std::std::unique_ptr<ServerInfo>&serverInfo : serverInfos)
140         {
141             sngxml::dom::Element* serverElement = new sngxml::dom::Element(U"server");
142             serverElement->SetAttribute(U"name"ToUtf32(serverInfo->ServerName()));
143             if (!serverInfo->Host().empty())
144             {
145                 serverElement->SetAttribute(U"host"ToUtf32(serverInfo->Host()));
146             }
147             serverElement->SetAttribute(U"port"ToUtf32(std::to_string(serverInfo->Port())));
148             serverElement->SetAttribute(U"defaultToolChain"ToUtf32(serverInfo->DefaultToolChain()));
149             serversElement->AppendChild(std::unique_ptr<sngxml::dom::Node>(serverElement));
150         }
151         std::ofstream serverConfigFile(serverConfigFilePath);
152         CodeFormatter formatter(serverConfigFile);
153         serverConfigDoc.Write(formatter);
154     }
155     catch (const std::exception& ex;)
156     {
157         LogMessage(-1"server config: writing server configuration to '" + serverConfigFilePath + "' failed: " + ex.what());
158     }
159 }
160 
161 void ServerConfig::Add(const std::string& serverNameconst std::string& hostNameint portconst std::string& defaultToolChainbool forcebool readbool write)
162 {
163     if (read)
164     {
165         Read();
166     }
167     std::string host = hostName;
168     if (host == "localhost" || host == "127.0.0.1")
169     {
170         host.clear();
171     }
172     if (GetGlobalFlag(GlobalFlags::verbose))
173     {
174         std::string toolChainStr;
175         if (!defaultToolChain.empty())
176         {
177             toolChainStr = ", default tool chain '" + defaultToolChain + "'";
178         }
179         std::string hostStr;
180         if (!host.empty())
181         {
182             hostStr = ", host " + host;
183         }
184     }
185     ServerInfo* serverInfo = GetServerInfo(serverNamefalsefalse);
186     if (serverInfo != nullptr)
187     {
188         if (!force)
189         {
190             throw std::runtime_error("server config: server '" + serverName + "' already exists, use --force to add anyway");
191         }
192         auto it = hostPortMap.find(std::make_pair(hostport));
193         if (it != hostPortMap.cend())
194         {
195             std::string prevServerName = it->second;
196             if (prevServerName != serverName)
197             {
198                 std::string hostPortStr;
199                 if (!host.empty())
200                 {
201                     hostPortStr = "host " + host + ", port " + std::to_string(port);
202                 }
203                 else
204                 {
205                     hostPortStr = "port " + std::to_string(port);
206                 }
207                 LogMessage(-1"server config: warning: " + hostPortStr + " already in use for server '" + prevServerName + "'");
208             }
209         }
210         serverInfo->SetHost(host);
211         serverInfo->SetPort(port);
212         serverInfo->SetDefaultToolChain(defaultToolChain);
213         hostPortMap[std::make_pair(hostport)] = serverName;
214     }
215     else
216     {
217         auto it = hostPortMap.find(std::make_pair(hostport));
218         if (it != hostPortMap.cend())
219         {
220             std::string prevServerName = it->second;
221             if (prevServerName != serverName)
222             {
223                 std::string hostPortStr;
224                 if (!host.empty())
225                 {
226                     hostPortStr.append("host ").append(host).append(", port ").append(std::to_string(port));
227                 }
228                 else
229                 {
230                     hostPortStr.append("port ").append(std::to_string(port));
231                 }
232                 throw std::runtime_error("server config: error: " + hostPortStr + " already in use for server '" + prevServerName + "'");
233             }
234         }
235         std::unique_ptr<ServerInfo> newServerInfo(new ServerInfo(serverNamehostportdefaultToolChain));
236         serverMap[serverName] = newServerInfo.get();
237         serverInfos.push_back(std::move(newServerInfo));
238         hostPortMap[std::make_pair(hostport)] = serverName;
239     }
240     if (write)
241     {
242         Write();
243     }
244 }
245 
246 void ServerConfig::Remove(const std::string& serverName)
247 {
248     bool found = false;
249     int n = serverInfos.size();
250     for (int i = 0; i < n; ++i)
251     {
252         if (serverInfos[i]->ServerName() == serverName)
253         {
254             serverInfos.erase(serverInfos.begin() + i);
255             found = true;
256             break;
257         }
258     }
259     if (found)
260     {
261         Write();
262     }
263     else
264     {
265         throw std::runtime_error("server '" + serverName + "' not found");
266     }
267 }
268 
269 void ServerConfig::Show()
270 {
271     Read();
272     CodeFormatter formatter(std::cout);
273     for (const auto& p : serverMap)
274     {
275         ServerInfo* serverInfo = p.second;
276         serverInfo->Print(formatter);
277     }
278 }
279 
280 ServerConfig::ServerConfig()
281 {
282     Read();
283     ServerInfo* local = GetServerInfo("local"falsefalse);
284     if (!local)
285     {
286         Add("local"std::string()GetDefaultLocalPort()GetDefaultToolChainForCurrentPlatform()truefalsetrue);
287     }
288 }
289 
290 ServerInfo* ServerConfig::GetServerInfo(const std::string& serverNamebool failIfNotExistbool read)
291 {
292     if (read)
293     {
294         Read();
295     }
296     auto it = serverMap.find(serverName);
297     if (it != serverMap.cend())
298     {
299         return it->second;
300     }
301     else
302     {
303         if (failIfNotExist)
304         {
305             throw std::runtime_error("server config: error: server name '" + serverName + "' not found from configuration file '" + CmajorServerConfigFilePath() + "'");
306         }
307         return nullptr;
308     }
309 }
310 
311 void ServerInit()
312 {
313     ServerConfig::Init();
314 }
315 
316 void ServerDone()
317 {
318     ServerConfig::Done();
319 }
320 
321 } } // namespace cmajor::build