1 /***
2 This file is part of snapcast
3 Copyright (C) 2014-2021 Johannes Pohl
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 ***/
18
19 #include "config.hpp"
20 #include "common/aixlog.hpp"
21 #include "common/snap_exception.hpp"
22 #include "common/str_compat.hpp"
23 #include "common/utils/file_utils.hpp"
24 #include <cerrno>
25 #include <fcntl.h>
26 #include <fstream>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29
30 using namespace std;
31
32
33
~Config()34 Config::~Config()
35 {
36 save();
37 }
38
39
init(const std::string & root_directory,const std::string & user,const std::string & group)40 void Config::init(const std::string& root_directory, const std::string& user, const std::string& group)
41 {
42 string dir;
43 if (!root_directory.empty())
44 dir = root_directory;
45 else if (getenv("HOME") == nullptr)
46 dir = "/var/lib/snapserver/";
47 else
48 dir = string(getenv("HOME")) + "/.config/snapserver/";
49
50 if (!dir.empty() && (dir.back() != '/'))
51 dir += "/";
52
53 // if (dir.find("/var/lib/snapserver") == string::npos)
54 // dir += ".config/snapserver/";
55
56 int status = utils::file::mkdirRecursive(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
57 if ((status != 0) && (errno != EEXIST))
58 throw SnapException("failed to create settings directory: \"" + dir + "\": " + cpt::to_string(errno));
59
60 filename_ = dir + "server.json";
61 LOG(NOTICE) << "Settings file: \"" << filename_ << "\"\n";
62
63 int fd;
64 if ((fd = open(filename_.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
65 {
66 if (errno == EACCES)
67 throw std::runtime_error("failed to open file \"" + filename_ + "\", permission denied (error " + cpt::to_string(errno) + ")");
68 else
69 throw std::runtime_error("failed to open file \"" + filename_ + "\", error " + cpt::to_string(errno));
70 }
71 close(fd);
72
73 if (!user.empty() && !group.empty())
74 {
75 try
76 {
77 utils::file::do_chown(dir, user, group);
78 utils::file::do_chown(filename_, user, group);
79 }
80 catch (const std::exception& e)
81 {
82 LOG(ERROR) << "Exception in chown: " << e.what() << "\n";
83 }
84 }
85
86 try
87 {
88 ifstream ifs(filename_, std::ifstream::in);
89 if (ifs.good() && (ifs.peek() != std::ifstream::traits_type::eof()))
90 {
91 json j;
92 ifs >> j;
93 if (j.count("ConfigVersion") != 0u)
94 {
95 json jGroups = j["Groups"];
96 for (auto& jGroup : jGroups)
97 {
98 GroupPtr group = make_shared<Group>();
99 group->fromJson(jGroup);
100 // if (client->id.empty() || getClientInfo(client->id))
101 // continue;
102 groups.push_back(group);
103 }
104 }
105 }
106 }
107 catch (const std::exception& e)
108 {
109 LOG(ERROR) << "Error reading config: " << e.what() << "\n";
110 }
111 }
112
113
save()114 void Config::save()
115 {
116 if (filename_.empty())
117 init();
118 std::ofstream ofs(filename_.c_str(), std::ofstream::out | std::ofstream::trunc);
119 json clients = {{"ConfigVersion", 2}, {"Groups", getGroups()}};
120 // ofs << std::setw(4) << clients;
121 ofs << clients;
122 ofs.close();
123 }
124
125
getClientInfo(const std::string & clientId) const126 ClientInfoPtr Config::getClientInfo(const std::string& clientId) const
127 {
128 if (clientId.empty())
129 return nullptr;
130
131 for (const auto& group : groups)
132 {
133 for (auto client : group->clients)
134 {
135 if (client->id == clientId)
136 return client;
137 }
138 }
139
140 return nullptr;
141 }
142
143
addClientInfo(ClientInfoPtr client)144 GroupPtr Config::addClientInfo(ClientInfoPtr client)
145 {
146 GroupPtr group = getGroupFromClient(client);
147 if (!group)
148 {
149 group = std::make_shared<Group>();
150 group->addClient(client);
151 groups.push_back(group);
152 }
153 return group;
154 }
155
156
addClientInfo(const std::string & clientId)157 GroupPtr Config::addClientInfo(const std::string& clientId)
158 {
159 ClientInfoPtr client = getClientInfo(clientId);
160 if (!client)
161 client = make_shared<ClientInfo>(clientId);
162 return addClientInfo(client);
163 }
164
165
getGroup(const std::string & groupId) const166 GroupPtr Config::getGroup(const std::string& groupId) const
167 {
168 for (auto group : groups)
169 {
170 if (group->id == groupId)
171 return group;
172 }
173
174 return nullptr;
175 }
176
177
getGroupFromClient(const std::string & clientId)178 GroupPtr Config::getGroupFromClient(const std::string& clientId)
179 {
180 for (auto group : groups)
181 {
182 for (const auto& c : group->clients)
183 {
184 if (c->id == clientId)
185 return group;
186 }
187 }
188 return nullptr;
189 }
190
191
getGroupFromClient(ClientInfoPtr client)192 GroupPtr Config::getGroupFromClient(ClientInfoPtr client)
193 {
194 return getGroupFromClient(client->id);
195 }
196
197
getServerStatus(const json & streams) const198 json Config::getServerStatus(const json& streams) const
199 {
200 Host host;
201 host.update();
202 // TODO: Set MAC and IP
203 Snapserver snapserver("Snapserver", VERSION);
204 json serverStatus = {{"server",
205 {{"host", host.toJson()}, // getHostName()},
206 {"snapserver", snapserver.toJson()}}},
207 {"groups", getGroups()},
208 {"streams", streams}};
209
210 return serverStatus;
211 }
212
213
214
getGroups() const215 json Config::getGroups() const
216 {
217 json result = json::array();
218 for (const auto& group : groups)
219 result.push_back(group->toJson());
220 return result;
221 }
222
223
remove(ClientInfoPtr client)224 void Config::remove(ClientInfoPtr client)
225 {
226 auto group = getGroupFromClient(client);
227 if (!group)
228 return;
229 group->removeClient(client);
230 if (group->empty())
231 remove(group);
232 }
233
234
remove(GroupPtr group,bool force)235 void Config::remove(GroupPtr group, bool force)
236 {
237 if (!group)
238 return;
239
240 if (group->empty() || force)
241 groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end());
242 }
243
244 /*
245 GroupPtr Config::removeFromGroup(const std::string& groupId, const std::string& clientId)
246 {
247 GroupPtr group = getGroup(groupId);
248 if (!group || (group->id != groupId))
249 return group;
250
251 auto client = getClientInfo(clientId);
252 if (client)
253 group->clients.erase(std::remove(group->clients.begin(), group->clients.end(), client), group->clients.end());
254
255 addClientInfo(clientId);
256 return group;
257 }
258
259
260 GroupPtr Config::setGroupForClient(const std::string& groupId, const std::string& clientId)
261 {
262 GroupPtr oldGroup = getGroupFromClient(clientId);
263 if (oldGroup && (oldGroup->id == groupId))
264 return oldGroup;
265
266 GroupPtr newGroup = getGroup(groupId);
267 if (!newGroup)
268 return nullptr;
269
270 auto client = getClientInfo(clientId);
271 if (!client)
272 return nullptr;
273
274 if (oldGroup)
275 removeFromGroup(oldGroup->id, clientId);
276
277 newGroup->addClient(client);
278 return newGroup;
279 }
280 */
281