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 std::promise<bool>& shutdown_server_now_;
62 const std::vector<spl::shared_ptr<core::video_channel>>& channels,
63 const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
64 const spl::shared_ptr<core::media_info_repository>& media_info_repo,
65 std::promise<bool>& shutdown_server_now)
66 : thumb_gen_(thumb_gen)
67 , media_info_repo_(media_info_repo)
68 , shutdown_server_now_(shutdown_server_now)
70 commandQueues_.push_back(std::make_shared<AMCPCommandQueue>());
73 for (const auto& channel : channels)
75 std::wstring lifecycle_key = L"lock" + boost::lexical_cast<std::wstring>(index);
76 channels_.push_back(channel_context(channel, lifecycle_key));
77 auto queue(std::make_shared<AMCPCommandQueue>());
78 commandQueues_.push_back(queue);
85 enum class parser_state {
91 enum class error_state {
100 struct command_interpreter_result
102 command_interpreter_result() : error(error_state::no_error) {}
104 std::shared_ptr<caspar::IO::lock_container> lock;
105 std::wstring command_name;
106 AMCPCommand::ptr_type command;
108 AMCPCommandQueue::ptr_type queue;
111 //The paser method expects message to be complete messages with the delimiter stripped away.
112 //Thesefore the AMCPProtocolStrategy should be decorated with a delimiter_based_chunking_strategy
113 void Parse(const std::wstring& message, ClientInfoPtr client)
115 CASPAR_LOG(info) << L"Received message from " << client->print() << ": " << message << L"\\r\\n";
117 command_interpreter_result result;
118 if(interpret_command_string(message, result, client))
120 if(result.lock && !result.lock->check_access(client))
121 result.error = error_state::access_error;
123 result.queue->AddCommand(result.command);
126 if (result.error != error_state::no_error)
128 std::wstringstream answer;
129 boost::to_upper(result.command_name);
133 case error_state::command_error:
134 answer << L"400 ERROR\r\n" << message << "\r\n";
136 case error_state::channel_error:
137 answer << L"401 " << result.command_name << " ERROR\r\n";
139 case error_state::parameters_error:
140 answer << L"402 " << result.command_name << " ERROR\r\n";
142 case error_state::access_error:
143 answer << L"503 " << result.command_name << " FAILED\r\n";
146 answer << L"500 FAILED\r\n";
149 client->send(answer.str());
154 friend class AMCPCommand;
156 bool interpret_command_string(const std::wstring& message, command_interpreter_result& result, ClientInfoPtr client)
160 std::vector<std::wstring> tokens;
161 parser_state state = parser_state::New;
163 tokenize(message, &tokens);
165 //parse the message one token at the time
166 auto end = tokens.end();
167 auto it = tokens.begin();
168 while (it != end && result.error == error_state::no_error)
172 case parser_state::New:
174 state = parser_state::GetSwitch;
176 state = parser_state::GetCommand;
179 case parser_state::GetSwitch:
180 //command_switch = (*it); //we dont care for the switch anymore
181 state = parser_state::GetCommand;
185 case parser_state::GetCommand:
187 result.command_name = (*it);
188 result.command = create_command(result.command_name, client);
189 if(result.command) //the command doesn't need a channel
191 result.queue = commandQueues_[0];
192 state = parser_state::GetParameters;
196 //get channel index from next token
197 int channel_index = -1;
198 int layer_index = -1;
203 if(create_channel_command(result.command_name, client, channels_.at(0), 0, 0)) //check if there is a command like this
204 result.error = error_state::channel_error;
206 result.error = error_state::command_error;
211 { //parse channel/layer token
214 std::wstring channelid_str = boost::trim_copy(*it);
215 std::vector<std::wstring> split;
216 boost::split(split, channelid_str, boost::is_any_of("-"));
218 channel_index = boost::lexical_cast<int>(split[0]) - 1;
220 layer_index = boost::lexical_cast<int>(split[1]);
224 result.error = error_state::channel_error;
229 if(channel_index >= 0 && channel_index < channels_.size())
231 result.command = create_channel_command(result.command_name, client, channels_.at(channel_index), channel_index, layer_index);
234 result.lock = channels_.at(channel_index).lock;
235 result.queue = commandQueues_[channel_index + 1];
239 result.error = error_state::command_error;
245 result.error = error_state::channel_error;
250 state = parser_state::GetParameters;
255 case parser_state::GetParameters:
257 int parameterCount=0;
260 result.command->parameters().push_back((*it));
269 if(result.command && result.error == error_state::no_error && result.command->parameters().size() < result.command->minimum_parameters()) {
270 result.error = error_state::parameters_error;
275 CASPAR_LOG_CURRENT_EXCEPTION();
276 result.error = error_state::unknown_error;
279 return result.error == error_state::no_error;
282 std::size_t tokenize(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
284 //split on whitespace but keep strings within quotationmarks
285 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
287 std::wstring currentToken;
289 bool inQuote = false;
290 bool getSpecialCode = false;
292 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
296 //insert code-handling here
297 switch(message[charIndex])
300 currentToken += L"\\";
303 currentToken += L"\"";
306 currentToken += L"\n";
311 getSpecialCode = false;
315 if(message[charIndex]==L'\\')
317 getSpecialCode = true;
321 if(message[charIndex]==L' ' && inQuote==false)
323 if(currentToken.size()>0)
325 pTokenVector->push_back(currentToken);
326 currentToken.clear();
331 if(message[charIndex]==L'\"')
335 if(currentToken.size()>0 || !inQuote)
337 pTokenVector->push_back(currentToken);
338 currentToken.clear();
343 currentToken += message[charIndex];
346 if(currentToken.size()>0)
348 pTokenVector->push_back(currentToken);
349 currentToken.clear();
352 return pTokenVector->size();
355 AMCPCommand::ptr_type create_command(const std::wstring& str, ClientInfoPtr client)
357 std::wstring s = boost::to_upper_copy(str);
358 if(s == L"DIAG") return std::make_shared<DiagnosticsCommand>(client);
359 else if(s == L"CHANNEL_GRID") return std::make_shared<ChannelGridCommand>(client, channels_);
360 else if(s == L"DATA") return std::make_shared<DataCommand>(client);
361 else if(s == L"CINF") return std::make_shared<CinfCommand>(client, media_info_repo_);
362 else if(s == L"INFO") return std::make_shared<InfoCommand>(client, channels_);
363 else if(s == L"CLS") return std::make_shared<ClsCommand>(client, media_info_repo_);
364 else if(s == L"TLS") return std::make_shared<TlsCommand>(client);
365 else if(s == L"VERSION") return std::make_shared<VersionCommand>(client);
366 else if(s == L"BYE") return std::make_shared<ByeCommand>(client);
367 else if(s == L"LOCK") return std::make_shared<LockCommand>(client, channels_);
368 else if(s == L"LOG") return std::make_shared<LogCommand>(client);
369 else if(s == L"THUMBNAIL") return std::make_shared<ThumbnailCommand>(client, thumb_gen_);
370 else if(s == L"KILL") return std::make_shared<KillCommand>(client, shutdown_server_now_);
371 else if(s == L"RESTART") return std::make_shared<RestartCommand>(client, shutdown_server_now_);
376 AMCPCommand::ptr_type create_channel_command(const std::wstring& str, ClientInfoPtr client, const channel_context& channel, unsigned int channel_index, int layer_index)
378 std::wstring s = boost::to_upper_copy(str);
380 if (s == L"MIXER") return std::make_shared<MixerCommand>(client, channel, channel_index, layer_index);
381 else if(s == L"CALL") return std::make_shared<CallCommand>(client, channel, channel_index, layer_index);
382 else if(s == L"SWAP") return std::make_shared<SwapCommand>(client, channel, channel_index, layer_index, channels_);
383 else if(s == L"LOAD") return std::make_shared<LoadCommand>(client, channel, channel_index, layer_index);
384 else if(s == L"LOADBG") return std::make_shared<LoadbgCommand>(client, channel, channel_index, layer_index, channels_);
385 else if(s == L"ADD") return std::make_shared<AddCommand>(client, channel, channel_index, layer_index);
386 else if(s == L"REMOVE") return std::make_shared<RemoveCommand>(client, channel, channel_index, layer_index);
387 else if(s == L"PAUSE") return std::make_shared<PauseCommand>(client, channel, channel_index, layer_index);
388 else if(s == L"PLAY") return std::make_shared<PlayCommand>(client, channel, channel_index, layer_index, channels_);
389 else if(s == L"STOP") return std::make_shared<StopCommand>(client, channel, channel_index, layer_index);
390 else if(s == L"CLEAR") return std::make_shared<ClearCommand>(client, channel, channel_index, layer_index);
391 else if(s == L"PRINT") return std::make_shared<PrintCommand>(client, channel, channel_index, layer_index);
392 else if(s == L"CG") return std::make_shared<CGCommand>(client, channel, channel_index, layer_index);
393 else if(s == L"SET") return std::make_shared<SetCommand>(client, channel, channel_index, layer_index);
400 AMCPProtocolStrategy::AMCPProtocolStrategy(
401 const std::vector<spl::shared_ptr<core::video_channel>>& channels,
402 const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
403 const spl::shared_ptr<core::media_info_repository>& media_info_repo,
404 std::promise<bool>& shutdown_server_now)
405 : impl_(spl::make_unique<impl>(channels, thumb_gen, media_info_repo, shutdown_server_now))
408 AMCPProtocolStrategy::~AMCPProtocolStrategy() {}
409 void AMCPProtocolStrategy::Parse(const std::wstring& msg, IO::ClientInfoPtr pClientInfo) { impl_->Parse(msg, pClientInfo); }
413 }} //namespace caspar