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"
38 #include <boost/algorithm/string/trim.hpp>
39 #include <boost/algorithm/string/split.hpp>
40 #include <boost/algorithm/string/replace.hpp>
41 #include <boost/lexical_cast.hpp>
44 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
47 namespace caspar { namespace protocol { namespace amcp {
49 using IO::ClientInfoPtr;
51 struct AMCPProtocolStrategy::impl
54 std::vector<channel_context> channels_;
55 std::vector<AMCPCommandQueue::ptr_type> commandQueues_;
56 std::shared_ptr<core::thumbnail_generator> thumb_gen_;
57 spl::shared_ptr<core::media_info_repository> media_info_repo_;
58 spl::shared_ptr<core::system_info_provider_repository> system_info_provider_repo_;
59 spl::shared_ptr<core::cg_producer_registry> cg_registry_;
60 std::promise<bool>& shutdown_server_now_;
64 const std::vector<spl::shared_ptr<core::video_channel>>& channels,
65 const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
66 const spl::shared_ptr<core::media_info_repository>& media_info_repo,
67 const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
68 const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
69 std::promise<bool>& shutdown_server_now)
70 : thumb_gen_(thumb_gen)
71 , media_info_repo_(media_info_repo)
72 , system_info_provider_repo_(system_info_provider_repo)
73 , cg_registry_(cg_registry)
74 , shutdown_server_now_(shutdown_server_now)
76 commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
79 for (const auto& channel : channels)
81 std::wstring lifecycle_key = L"lock" + boost::lexical_cast<std::wstring>(index);
82 channels_.push_back(channel_context(channel, lifecycle_key));
83 auto queue(std::make_shared<AMCPCommandQueue>());
84 commandQueues_.push_back(queue);
91 enum class parser_state {
97 enum class error_state {
106 struct command_interpreter_result
108 command_interpreter_result() : error(error_state::no_error) {}
110 std::shared_ptr<caspar::IO::lock_container> lock;
111 std::wstring command_name;
112 AMCPCommand::ptr_type command;
114 AMCPCommandQueue::ptr_type queue;
117 //The paser method expects message to be complete messages with the delimiter stripped away.
118 //Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
119 void Parse(const std::wstring& message, ClientInfoPtr client)
121 CASPAR_LOG(info) << L"Received message from " << client->print() << ": " << message << L"\\r\\n";
123 command_interpreter_result result;
124 if(interpret_command_string(message, result, client))
126 if(result.lock && !result.lock->check_access(client))
127 result.error = error_state::access_error;
129 result.queue->AddCommand(result.command);
132 if (result.error != error_state::no_error)
134 std::wstringstream answer;
135 boost::to_upper(result.command_name);
139 case error_state::command_error:
140 answer << L"400 ERROR\r\n" << message << "\r\n";
142 case error_state::channel_error:
143 answer << L"401 " << result.command_name << " ERROR\r\n";
145 case error_state::parameters_error:
146 answer << L"402 " << result.command_name << " ERROR\r\n";
148 case error_state::access_error:
149 answer << L"503 " << result.command_name << " FAILED\r\n";
152 answer << L"500 FAILED\r\n";
155 client->send(answer.str());
160 friend class AMCPCommand;
162 bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
166 std::vector<std::wstring> tokens;
167 parser_state state = parser_state::New;
169 tokenize(message, &tokens);
171 //parse the message one token at the time
172 auto end = tokens.end();
173 auto it = tokens.begin();
174 while (it != end && result.error == error_state::no_error)
178 case parser_state::New:
180 state = parser_state::GetSwitch;
182 state = parser_state::GetCommand;
185 case parser_state::GetSwitch:
186 //command_switch = (*it); //we dont care for the switch anymore
187 state = parser_state::GetCommand;
191 case parser_state::GetCommand:
193 result.command_name = (*it);
194 result.command = create_command(result.command_name, client);
195 if(result.command) //the command doesn't need a channel
197 result.queue = commandQueues_[0];
198 state = parser_state::GetParameters;
202 //get channel index from next token
203 int channel_index = -1;
204 int layer_index = -1;
209 if(create_channel_command(result.command_name, client, channels_.at(0), 0, 0)) //check if there is a command like this
210 result.error = error_state::channel_error;
212 result.error = error_state::command_error;
217 { //parse channel/layer token
220 std::wstring channelid_str = boost::trim_copy(*it);
221 std::vector<std::wstring> split;
222 boost::split(split, channelid_str, boost::is_any_of("-"));
224 channel_index = boost::lexical_cast<int>(split[0]) - 1;
226 layer_index = boost::lexical_cast<int>(split[1]);
230 result.error = error_state::channel_error;
235 if(channel_index >= 0 && channel_index < channels_.size())
237 result.command = create_channel_command(result.command_name, client, channels_.at(channel_index), channel_index, layer_index);
240 result.lock = channels_.at(channel_index).lock;
241 result.queue = commandQueues_[channel_index + 1];
245 result.error = error_state::command_error;
251 result.error = error_state::channel_error;
256 state = parser_state::GetParameters;
261 case parser_state::GetParameters:
263 int parameterCount=0;
266 result.command->parameters().push_back((*it));
275 if(result.command && result.error == error_state::no_error && result.command->parameters().size() < result.command->minimum_parameters()) {
276 result.error = error_state::parameters_error;
281 CASPAR_LOG_CURRENT_EXCEPTION();
282 result.error = error_state::unknown_error;
285 return result.error == error_state::no_error;
288 std::size_t tokenize(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
290 //split on whitespace but keep strings within quotationmarks
291 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
293 std::wstring currentToken;
295 bool inQuote = false;
296 bool getSpecialCode = false;
298 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
302 //insert code-handling here
303 switch(message[charIndex])
306 currentToken += L"\\";
309 currentToken += L"\"";
312 currentToken += L"\n";
317 getSpecialCode = false;
321 if(message[charIndex]==L'\\')
323 getSpecialCode = true;
327 if(message[charIndex]==L' ' && inQuote==false)
329 if(currentToken.size()>0)
331 pTokenVector->push_back(currentToken);
332 currentToken.clear();
337 if(message[charIndex]==L'\"')
341 if(currentToken.size()>0 || !inQuote)
343 pTokenVector->push_back(currentToken);
344 currentToken.clear();
349 currentToken += message[charIndex];
352 if(currentToken.size()>0)
354 pTokenVector->push_back(currentToken);
355 currentToken.clear();
358 return pTokenVector->size();
361 AMCPCommand::ptr_type create_command(const std::wstring& str, ClientInfoPtr client)
363 std::wstring s = boost::to_upper_copy(str);
364 if ( s == L"DIAG") return std::make_shared<DiagnosticsCommand>(client);
365 else if (s == L"CHANNEL_GRID") return std::make_shared<ChannelGridCommand>(client, channels_);
366 else if (s == L"DATA") return std::make_shared<DataCommand>(client);
367 else if (s == L"CINF") return std::make_shared<CinfCommand>(client, media_info_repo_);
368 else if (s == L"INFO") return std::make_shared<InfoCommand>(client, channels_, system_info_provider_repo_, cg_registry_);
369 else if (s == L"CLS") return std::make_shared<ClsCommand>(client, media_info_repo_);
370 else if (s == L"TLS") return std::make_shared<TlsCommand>(client, cg_registry_);
371 else if (s == L"VERSION") return std::make_shared<VersionCommand>(client, system_info_provider_repo_);
372 else if (s == L"BYE") return std::make_shared<ByeCommand>(client);
373 else if (s == L"LOCK") return std::make_shared<LockCommand>(client, channels_);
374 else if (s == L"LOG") return std::make_shared<LogCommand>(client);
375 else if (s == L"THUMBNAIL") return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
376 else if (s == L"KILL") return std::make_shared<KillCommand>(client, shutdown_server_now_);
377 else if (s == L"RESTART") return std::make_shared<RestartCommand>(client, shutdown_server_now_);
382 AMCPCommand::ptr_type create_channel_command(const std::wstring& str, ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index)
384 std::wstring s = boost::to_upper_copy(str);
386 if ( s == L"MIXER") return std::make_shared<MixerCommand>(client, channel, channel_index, layer_index);
387 else if (s == L"CALL") return std::make_shared<CallCommand>(client, channel, channel_index, layer_index);
388 else if (s == L"SWAP") return std::make_shared<SwapCommand>(client, channel, channel_index, layer_index, channels_);
389 else if (s == L"LOAD") return std::make_shared<LoadCommand>(client, channel, channel_index, layer_index);
390 else if (s == L"LOADBG") return std::make_shared<LoadbgCommand>(client, channel, channel_index, layer_index, channels_);
391 else if (s == L"ADD") return std::make_shared<AddCommand>(client, channel, channel_index, layer_index);
392 else if (s == L"REMOVE") return std::make_shared<RemoveCommand>(client, channel, channel_index, layer_index);
393 else if (s == L"PAUSE") return std::make_shared<PauseCommand>(client, channel, channel_index, layer_index);
394 else if (s == L"PLAY") return std::make_shared<PlayCommand>(client, channel, channel_index, layer_index, channels_);
395 else if (s == L"STOP") return std::make_shared<StopCommand>(client, channel, channel_index, layer_index);
396 else if (s == L"CLEAR") return std::make_shared<ClearCommand>(client, channel, channel_index, layer_index);
397 else if (s == L"PRINT") return std::make_shared<PrintCommand>(client, channel, channel_index, layer_index);
398 else if (s == L"CG") return std::make_shared<CGCommand>(client, channel, channel_index, layer_index, cg_registry_);
399 else if (s == L"SET") return std::make_shared<SetCommand>(client, channel, channel_index, layer_index);
406 AMCPProtocolStrategy::AMCPProtocolStrategy(
407 const std::vector<spl::shared_ptr<core::video_channel>>& channels,
408 const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
409 const spl::shared_ptr<core::media_info_repository>& media_info_repo,
410 const spl::shared_ptr<core::system_info_provider_repository>& system_info_provider_repo,
411 const spl::shared_ptr<core::cg_producer_registry>& cg_registry,
412 std::promise<bool>& shutdown_server_now)
413 : impl_(spl::make_unique<impl>(
417 system_info_provider_repo,
419 shutdown_server_now))
422 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
423 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
427 }} //namespace caspar