2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG (www.casparcg.com).
\r
6 * CasparCG is free software: you can redistribute it and/or modify
\r
7 * it under the terms of the GNU General Public License as published by
\r
8 * the Free Software Foundation, either version 3 of the License, or
\r
9 * (at your option) any later version.
\r
11 * CasparCG is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU General Public License for more details.
\r
16 * You should have received a copy of the GNU General Public License
\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
\r
19 * Author: Nicklas P Andersson
\r
23 #include "../StdAfx.h"
\r
25 #include "AMCPProtocolStrategy.h"
\r
27 #include "../util/AsyncEventServer.h"
\r
28 #include "AMCPCommandsImpl.h"
\r
33 #include <algorithm>
\r
36 #include <boost/algorithm/string/trim.hpp>
\r
37 #include <boost/algorithm/string/split.hpp>
\r
38 #include <boost/algorithm/string/replace.hpp>
\r
39 #include <boost/lexical_cast.hpp>
\r
41 #if defined(_MSC_VER)
\r
42 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
\r
45 namespace caspar { namespace protocol { namespace amcp {
\r
47 using IO::ClientInfoPtr;
\r
49 const std::wstring AMCPProtocolStrategy::MessageDelimiter = TEXT("\r\n");
\r
51 inline std::shared_ptr<core::video_channel> GetChannelSafe(unsigned int index, const std::vector<safe_ptr<core::video_channel>>& channels)
\r
53 return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;
\r
56 AMCPProtocolStrategy::AMCPProtocolStrategy(
\r
57 const std::vector<safe_ptr<core::video_channel>>& channels,
\r
58 const std::shared_ptr<core::thumbnail_generator>& thumb_gen,
\r
59 boost::promise<bool>& shutdown_server_now)
\r
60 : channels_(channels)
\r
61 , thumb_gen_(thumb_gen)
\r
62 , shutdown_server_now_(shutdown_server_now)
\r
64 AMCPCommandQueuePtr pGeneralCommandQueue(new AMCPCommandQueue());
\r
65 commandQueues_.push_back(pGeneralCommandQueue);
\r
68 std::shared_ptr<core::video_channel> pChannel;
\r
69 unsigned int index = -1;
\r
70 //Create a commandpump for each video_channel
\r
71 while((pChannel = GetChannelSafe(++index, channels_)) != 0) {
\r
72 AMCPCommandQueuePtr pChannelCommandQueue(new AMCPCommandQueue());
\r
73 std::wstring title = TEXT("video_channel ");
\r
75 //HACK: Perform real conversion from int to string
\r
76 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);
\r
79 commandQueues_.push_back(pChannelCommandQueue);
\r
83 AMCPProtocolStrategy::~AMCPProtocolStrategy() {
\r
86 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)
\r
89 std::wstring recvData(pData, charCount);
\r
90 std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;
\r
93 pos = availibleData.find(MessageDelimiter);
\r
94 if(pos != std::wstring::npos)
\r
96 std::wstring message = availibleData.substr(0,pos);
\r
98 //This is where a complete message gets taken care of
\r
99 if(message.length() > 0) {
\r
100 ProcessMessage(message, pClientInfo);
\r
103 std::size_t nextStartPos = pos + MessageDelimiter.length();
\r
104 if(nextStartPos < availibleData.length())
\r
105 availibleData = availibleData.substr(nextStartPos);
\r
107 availibleData.clear();
\r
117 pClientInfo->currentMessage_ = availibleData;
\r
120 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)
\r
122 CASPAR_LOG(info) << L"Received message from " << pClientInfo->print() << ": " << message << L"\\r\\n";
\r
124 bool bError = true;
\r
125 MessageParserState state = New;
\r
127 AMCPCommandPtr pCommand;
\r
129 pCommand = InterpretCommandString(message, &state);
\r
131 if(pCommand != 0) {
\r
132 pCommand->SetClientInfo(pClientInfo);
\r
133 if(QueueCommand(pCommand))
\r
136 state = GetChannel;
\r
139 if(bError == true) {
\r
140 std::wstringstream answer;
\r
144 answer << TEXT("400 ERROR\r\n") + message << "\r\n";
\r
147 answer << TEXT("401 ERROR\r\n");
\r
149 case GetParameters:
\r
150 answer << TEXT("402 ERROR\r\n");
\r
153 answer << TEXT("500 FAILED\r\n");
\r
156 pClientInfo->Send(answer.str());
\r
160 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)
\r
162 std::vector<std::wstring> tokens;
\r
163 unsigned int currentToken = 0;
\r
164 std::wstring commandSwitch;
\r
166 AMCPCommandPtr pCommand;
\r
167 MessageParserState state = New;
\r
169 std::size_t tokensInMessage = TokenizeMessage(message, &tokens);
\r
171 //parse the message one token at the time
\r
172 while(currentToken < tokensInMessage)
\r
177 if(tokens[currentToken][0] == TEXT('/'))
\r
180 state = GetCommand;
\r
184 commandSwitch = tokens[currentToken];
\r
185 state = GetCommand;
\r
190 pCommand = CommandFactory(tokens[currentToken]);
\r
191 if(pCommand == 0) {
\r
192 goto ParseFinnished;
\r
196 pCommand->SetChannels(channels_);
\r
197 pCommand->SetThumbGenerator(thumb_gen_);
\r
198 pCommand->SetShutdownServerNow(shutdown_server_now_);
\r
200 if(commandSwitch.size() > 0) {
\r
201 transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);
\r
203 if(commandSwitch == TEXT("/APP"))
\r
204 pCommand->SetScheduling(AddToQueue);
\r
205 else if(commandSwitch == TEXT("/IMMF"))
\r
206 pCommand->SetScheduling(ImmediatelyAndClear);
\r
209 if(pCommand->NeedChannel())
\r
210 state = GetChannel;
\r
212 state = GetParameters;
\r
217 case GetParameters:
\r
219 _ASSERTE(pCommand != 0);
\r
220 int parameterCount=0;
\r
221 while(currentToken<tokensInMessage)
\r
223 pCommand->AddParameter(tokens[currentToken++]);
\r
227 if(parameterCount < pCommand->GetMinimumParameters()) {
\r
228 goto ParseFinnished;
\r
237 // assert(pCommand != 0);
\r
239 std::wstring str = boost::trim_copy(tokens[currentToken]);
\r
240 std::vector<std::wstring> split;
\r
241 boost::split(split, str, boost::is_any_of("-"));
\r
243 int channelIndex = -1;
\r
244 int layerIndex = -1;
\r
247 channelIndex = boost::lexical_cast<int>(split[0]) - 1;
\r
249 if(split.size() > 1)
\r
250 layerIndex = boost::lexical_cast<int>(split[1]);
\r
254 goto ParseFinnished;
\r
257 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);
\r
258 if(pChannel == 0) {
\r
259 goto ParseFinnished;
\r
262 pCommand->SetChannel(pChannel);
\r
263 pCommand->SetChannelIndex(channelIndex);
\r
264 pCommand->SetLayerIntex(layerIndex);
\r
266 state = GetParameters;
\r
271 default: //Done and unexpected
\r
272 goto ParseFinnished;
\r
277 if(state == GetParameters && pCommand->GetMinimumParameters()==0)
\r
280 if(state != Done) {
\r
284 if(pOutState != 0) {
\r
285 *pOutState = state;
\r
291 bool AMCPProtocolStrategy::QueueCommand(AMCPCommandPtr pCommand) {
\r
292 if(pCommand->NeedChannel()) {
\r
293 unsigned int channelIndex = pCommand->GetChannelIndex() + 1;
\r
294 if(commandQueues_.size() > channelIndex) {
\r
295 commandQueues_[channelIndex]->AddCommand(pCommand);
\r
301 commandQueues_[0]->AddCommand(pCommand);
\r
306 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)
\r
308 std::wstring s = str;
\r
309 transform(s.begin(), s.end(), s.begin(), toupper);
\r
311 if (s == TEXT("MIXER")) return std::make_shared<MixerCommand>();
\r
312 else if(s == TEXT("DIAG")) return std::make_shared<DiagnosticsCommand>();
\r
313 else if(s == TEXT("CHANNEL_GRID")) return std::make_shared<ChannelGridCommand>();
\r
314 else if(s == TEXT("CALL")) return std::make_shared<CallCommand>();
\r
315 else if(s == TEXT("SWAP")) return std::make_shared<SwapCommand>();
\r
316 else if(s == TEXT("LOAD")) return std::make_shared<LoadCommand>();
\r
317 else if(s == TEXT("LOADBG")) return std::make_shared<LoadbgCommand>();
\r
318 else if(s == TEXT("ADD")) return std::make_shared<AddCommand>();
\r
319 else if(s == TEXT("REMOVE")) return std::make_shared<RemoveCommand>();
\r
320 else if(s == TEXT("PAUSE")) return std::make_shared<PauseCommand>();
\r
321 else if(s == TEXT("PLAY")) return std::make_shared<PlayCommand>();
\r
322 else if(s == TEXT("STOP")) return std::make_shared<StopCommand>();
\r
323 else if(s == TEXT("CLEAR")) return std::make_shared<ClearCommand>();
\r
324 else if(s == TEXT("PRINT")) return std::make_shared<PrintCommand>();
\r
325 else if(s == TEXT("LOG")) return std::make_shared<LogCommand>();
\r
326 else if(s == TEXT("CG")) return std::make_shared<CGCommand>();
\r
327 else if(s == TEXT("DATA")) return std::make_shared<DataCommand>();
\r
328 else if(s == TEXT("CINF")) return std::make_shared<CinfCommand>();
\r
329 else if(s == TEXT("INFO")) return std::make_shared<InfoCommand>(channels_);
\r
330 else if(s == TEXT("CLS")) return std::make_shared<ClsCommand>();
\r
331 else if(s == TEXT("TLS")) return std::make_shared<TlsCommand>();
\r
332 else if(s == TEXT("VERSION")) return std::make_shared<VersionCommand>();
\r
333 else if(s == TEXT("BYE")) return std::make_shared<ByeCommand>();
\r
334 else if(s == TEXT("SET")) return std::make_shared<SetCommand>();
\r
335 else if(s == TEXT("THUMBNAIL")) return std::make_shared<ThumbnailCommand>();
\r
336 //else if(s == TEXT("MONITOR"))
\r
338 // result = AMCPCommandPtr(new MonitorCommand());
\r
340 else if(s == TEXT("KILL")) return std::make_shared<KillCommand>();
\r
344 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
\r
346 //split on whitespace but keep strings within quotationmarks
\r
347 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
\r
349 std::wstring currentToken;
\r
352 bool getSpecialCode = false;
\r
354 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
\r
358 //insert code-handling here
\r
359 switch(message[charIndex])
\r
362 currentToken += TEXT("\\");
\r
365 currentToken += TEXT("\"");
\r
368 currentToken += TEXT("\n");
\r
373 getSpecialCode = false;
\r
377 if(message[charIndex]==TEXT('\\'))
\r
379 getSpecialCode = true;
\r
383 if(message[charIndex]==' ' && inQuote==false)
\r
385 if(currentToken.size()>0)
\r
387 pTokenVector->push_back(currentToken);
\r
388 currentToken.clear();
\r
393 if(message[charIndex]==TEXT('\"'))
\r
397 if(currentToken.size()>0)
\r
399 pTokenVector->push_back(currentToken);
\r
400 currentToken.clear();
\r
405 currentToken += message[charIndex];
\r
408 if(currentToken.size()>0)
\r
410 pTokenVector->push_back(currentToken);
\r
411 currentToken.clear();
\r
414 return pTokenVector->size();
\r
418 }} //namespace caspar