2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG 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.
11 * CasparCG 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.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Nicklas P Andersson
23 #include "../StdAfx.h"
25 #include "AMCPProtocolStrategy.h"
26 #include "AMCPCommandsImpl.h"
27 #include "amcp_shared.h"
28 #include "AMCPCommand.h"
29 #include "AMCPCommandQueue.h"
37 #include <boost/algorithm/string/trim.hpp>
38 #include <boost/algorithm/string/split.hpp>
39 #include <boost/algorithm/string/replace.hpp>
40 #include <boost/lexical_cast.hpp>
43 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
46 namespace caspar { namespace protocol { namespace amcp {
48 using IO::ClientInfoPtr;
50 struct AMCPProtocolStrategy::impl
53 std::vector<channel_context> channels_;
54 std::vector<AMCPCommandQueue::ptr_type> commandQueues_;
55 std::shared_ptr<core::thumbnail_generator> thumb_gen_;
56 boost::promise<bool>& shutdown_server_now_;
59 impl(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen, boost::promise<bool>& shutdown_server_now) : thumb_gen_(thumb_gen), shutdown_server_now_(shutdown_server_now)
61 commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
64 BOOST_FOREACH(const spl::shared_ptr<core::video_channel>& channel, channels)
66 std::wstring lifecycle_key = L"lock" + boost::lexical_cast<std::wstring>(index);
67 channels_.push_back(channel_context(channel, lifecycle_key));
68 auto queue(std::make_shared<AMCPCommandQueue>());
69 commandQueues_.push_back(queue);
91 struct command_interpreter_result
93 command_interpreter_result() : error(no_error) {}
95 std::shared_ptr<caspar::IO::lock_container> lock;
96 std::wstring command_name;
97 AMCPCommand::ptr_type command;
99 AMCPCommandQueue::ptr_type queue;
102 //The paser method expects message to be complete messages with the delimiter stripped away.
103 //Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
104 void Parse(const std::wstring& message, ClientInfoPtr client)
106 CASPAR_LOG(info) << L"Received message from " << client->print() << ": " << message << L"\\r\\n";
108 command_interpreter_result result;
109 if(interpret_command_string(message, result, client))
111 if(result.lock && !result.lock->check_access(client))
112 result.error = access_error;
114 result.queue->AddCommand(result.command);
117 if(result.error != no_error)
119 std::wstringstream answer;
120 boost::to_upper(result.command_name);
125 answer << L"400 ERROR\r\n" << message << "\r\n";
128 answer << L"401 " << result.command_name << " ERROR\r\n";
130 case parameters_error:
131 answer << L"402 " << result.command_name << " ERROR\r\n";
134 answer << L"503 " << result.command_name << " FAILED\r\n";
137 answer << L"500 FAILED\r\n";
140 client->send(answer.str());
145 friend class AMCPCommand;
147 bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
151 std::vector<std::wstring> tokens;
152 parser_state state = New;
154 tokenize(message, &tokens);
156 //parse the message one token at the time
157 auto end = tokens.end();
158 auto it = tokens.begin();
159 while(it != end && result.error == no_error)
164 if((*it)[0] == TEXT('/'))
171 //command_switch = (*it); //we dont care for the switch anymore
178 result.command_name = (*it);
179 result.command = create_command(result.command_name, client);
180 if(result.command) //the command doesn't need a channel
182 result.queue = commandQueues_[0];
183 state = GetParameters;
187 //get channel index from next token
188 int channel_index = -1;
189 int layer_index = -1;
194 if(create_channel_command(result.command_name, client, channels_.at(0), 0, 0)) //check if there is a command like this
195 result.error = channel_error;
197 result.error = command_error;
202 { //parse channel/layer token
205 std::wstring channelid_str = boost::trim_copy(*it);
206 std::vector<std::wstring> split;
207 boost::split(split, channelid_str, boost::is_any_of("-"));
209 channel_index = boost::lexical_cast<int>(split[0]) - 1;
211 layer_index = boost::lexical_cast<int>(split[1]);
215 result.error = channel_error;
220 if(channel_index >= 0 && channel_index < channels_.size())
222 result.command = create_channel_command(result.command_name, client, channels_.at(channel_index), channel_index, layer_index);
225 result.lock = channels_.at(channel_index).lock;
226 result.queue = commandQueues_[channel_index + 1];
230 result.error = command_error;
236 result.error = channel_error;
241 state = GetParameters;
248 int parameterCount=0;
251 result.command->parameters().push_back((*it));
260 if(result.command && result.error == no_error && result.command->parameters().size() < result.command->minimum_parameters()) {
261 result.error = parameters_error;
266 CASPAR_LOG_CURRENT_EXCEPTION();
267 result.error = unknown_error;
270 return result.error == no_error;
273 std::size_t tokenize(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
275 //split on whitespace but keep strings within quotationmarks
276 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
278 std::wstring currentToken;
280 bool inQuote = false;
281 bool getSpecialCode = false;
283 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
287 //insert code-handling here
288 switch(message[charIndex])
291 currentToken += TEXT("\\");
294 currentToken += TEXT("\"");
297 currentToken += TEXT("\n");
302 getSpecialCode = false;
306 if(message[charIndex]==TEXT('\\'))
308 getSpecialCode = true;
312 if(message[charIndex]==' ' && inQuote==false)
314 if(currentToken.size()>0)
316 pTokenVector->push_back(currentToken);
317 currentToken.clear();
322 if(message[charIndex]==TEXT('\"'))
326 if(currentToken.size()>0 || !inQuote)
328 pTokenVector->push_back(currentToken);
329 currentToken.clear();
334 currentToken += message[charIndex];
337 if(currentToken.size()>0)
339 pTokenVector->push_back(currentToken);
340 currentToken.clear();
343 return pTokenVector->size();
346 AMCPCommand::ptr_type create_command(const std::wstring& str, ClientInfoPtr client)
348 std::wstring s = boost::to_upper_copy(str);
349 if(s == TEXT("DIAG")) return std::make_shared<DiagnosticsCommand>(client);
350 else if(s == TEXT("CHANNEL_GRID")) return std::make_shared<ChannelGridCommand>(client, channels_);
351 else if(s == TEXT("DATA")) return std::make_shared<DataCommand>(client);
352 else if(s == TEXT("CINF")) return std::make_shared<CinfCommand>(client);
353 else if(s == TEXT("INFO")) return std::make_shared<InfoCommand>(client, channels_);
354 else if(s == TEXT("CLS")) return std::make_shared<ClsCommand>(client);
355 else if(s == TEXT("TLS")) return std::make_shared<TlsCommand>(client);
356 else if(s == TEXT("VERSION")) return std::make_shared<VersionCommand>(client);
357 else if(s == TEXT("BYE")) return std::make_shared<ByeCommand>(client);
358 else if(s == TEXT("LOCK")) return std::make_shared<LockCommand>(client, channels_);
359 else if(s == TEXT("LOG")) return std::make_shared<LogCommand>(client);
360 else if(s == TEXT("THUMBNAIL")) return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
361 else if(s == TEXT("KILL")) return std::make_shared<KillCommand>(client, shutdown_server_now_);
362 else if(s == TEXT("RESTART")) return std::make_shared<RestartCommand>(client, shutdown_server_now_);
367 AMCPCommand::ptr_type create_channel_command(const std::wstring& str, ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index)
369 std::wstring s = boost::to_upper_copy(str);
371 if (s == TEXT("MIXER")) return std::make_shared<MixerCommand>(client, channel, channel_index, layer_index);
372 else if(s == TEXT("CALL")) return std::make_shared<CallCommand>(client, channel, channel_index, layer_index);
373 else if(s == TEXT("SWAP")) return std::make_shared<SwapCommand>(client, channel, channel_index, layer_index, channels_);
374 else if(s == TEXT("LOAD")) return std::make_shared<LoadCommand>(client, channel, channel_index, layer_index);
375 else if(s == TEXT("LOADBG")) return std::make_shared<LoadbgCommand>(client, channel, channel_index, layer_index, channels_);
376 else if(s == TEXT("ADD")) return std::make_shared<AddCommand>(client, channel, channel_index, layer_index);
377 else if(s == TEXT("REMOVE")) return std::make_shared<RemoveCommand>(client, channel, channel_index, layer_index);
378 else if(s == TEXT("PAUSE")) return std::make_shared<PauseCommand>(client, channel, channel_index, layer_index);
379 else if(s == TEXT("PLAY")) return std::make_shared<PlayCommand>(client, channel, channel_index, layer_index, channels_);
380 else if(s == TEXT("STOP")) return std::make_shared<StopCommand>(client, channel, channel_index, layer_index);
381 else if(s == TEXT("CLEAR")) return std::make_shared<ClearCommand>(client, channel, channel_index, layer_index);
382 else if(s == TEXT("PRINT")) return std::make_shared<PrintCommand>(client, channel, channel_index, layer_index);
383 else if(s == TEXT("CG")) return std::make_shared<CGCommand>(client, channel, channel_index, layer_index);
384 else if(s == TEXT("SET")) return std::make_shared<SetCommand>(client, channel, channel_index, layer_index);
391 AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels, const std::shared_ptr<core::thumbnail_generator>& thumb_gen, boost::promise<bool>& shutdown_server_now) : impl_(spl::make_unique<impl>(channels, thumb_gen, shutdown_server_now)) {}
392 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
393 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
397 }} //namespace caspar