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<spl::shared_ptr<core::video_channel>>& channels)
\r
53 return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;
\r
56 AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<spl::shared_ptr<core::video_channel>>& channels) : channels_(channels) {
\r
57 AMCPCommandQueuePtr pGeneralCommandQueue(new AMCPCommandQueue());
\r
58 commandQueues_.push_back(pGeneralCommandQueue);
\r
61 std::shared_ptr<core::video_channel> pChannel;
\r
62 unsigned int index = -1;
\r
63 //Create a commandpump for each video_channel
\r
64 while((pChannel = GetChannelSafe(++index, channels_)) != 0) {
\r
65 AMCPCommandQueuePtr pChannelCommandQueue(new AMCPCommandQueue());
\r
66 std::wstring title = TEXT("video_channel ");
\r
68 //HACK: Perform real conversion from int to string
\r
69 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);
\r
72 commandQueues_.push_back(pChannelCommandQueue);
\r
76 AMCPProtocolStrategy::~AMCPProtocolStrategy() {
\r
79 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)
\r
82 std::wstring recvData(pData, charCount);
\r
83 std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;
\r
86 pos = availibleData.find(MessageDelimiter);
\r
87 if(pos != std::wstring::npos)
\r
89 std::wstring message = availibleData.substr(0,pos);
\r
91 //This is where a complete message gets taken care of
\r
92 if(message.length() > 0) {
\r
93 ProcessMessage(message, pClientInfo);
\r
96 std::size_t nextStartPos = pos + MessageDelimiter.length();
\r
97 if(nextStartPos < availibleData.length())
\r
98 availibleData = availibleData.substr(nextStartPos);
\r
100 availibleData.clear();
\r
110 pClientInfo->currentMessage_ = availibleData;
\r
113 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)
\r
115 CASPAR_LOG(info) << L"Received message from " << pClientInfo->print() << ": " << message << L"\\r\\n";
\r
117 bool bError = true;
\r
118 MessageParserState state = New;
\r
120 AMCPCommandPtr pCommand;
\r
122 pCommand = InterpretCommandString(message, &state);
\r
124 if(pCommand != 0) {
\r
125 pCommand->SetClientInfo(pClientInfo);
\r
126 if(QueueCommand(pCommand))
\r
129 state = GetChannel;
\r
132 if(bError == true) {
\r
133 std::wstringstream answer;
\r
137 answer << TEXT("400 ERROR\r\n") + message << "\r\n";
\r
140 answer << TEXT("401 ERROR\r\n");
\r
142 case GetParameters:
\r
143 answer << TEXT("402 ERROR\r\n");
\r
146 answer << TEXT("500 FAILED\r\n");
\r
149 pClientInfo->Send(answer.str());
\r
153 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)
\r
155 std::vector<std::wstring> tokens;
\r
156 unsigned int currentToken = 0;
\r
157 std::wstring commandSwitch;
\r
159 AMCPCommandPtr pCommand;
\r
160 MessageParserState state = New;
\r
162 std::size_t tokensInMessage = TokenizeMessage(message, &tokens);
\r
164 //parse the message one token at the time
\r
165 while(currentToken < tokensInMessage)
\r
170 if(tokens[currentToken][0] == TEXT('/'))
\r
173 state = GetCommand;
\r
177 commandSwitch = tokens[currentToken];
\r
178 state = GetCommand;
\r
183 pCommand = CommandFactory(tokens[currentToken]);
\r
184 if(pCommand == 0) {
\r
185 goto ParseFinnished;
\r
189 pCommand->SetChannels(channels_);
\r
191 if(commandSwitch.size() > 0) {
\r
192 transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);
\r
194 //if(commandSwitch == TEXT("/APP"))
\r
195 // pCommand->SetScheduling(AddToQueue);
\r
196 //else if(commandSwitch == TEXT("/IMMF"))
\r
197 // pCommand->SetScheduling(ImmediatelyAndClear);
\r
200 if(pCommand->NeedChannel())
\r
201 state = GetChannel;
\r
203 state = GetParameters;
\r
208 case GetParameters:
\r
210 _ASSERTE(pCommand != 0);
\r
211 int parameterCount=0;
\r
212 while(currentToken<tokensInMessage)
\r
214 pCommand->AddParameter(tokens[currentToken++]);
\r
218 if(parameterCount < pCommand->GetMinimumParameters()) {
\r
219 goto ParseFinnished;
\r
228 // assert(pCommand != 0);
\r
230 std::wstring str = boost::trim_copy(tokens[currentToken]);
\r
231 std::vector<std::wstring> split;
\r
232 boost::split(split, str, boost::is_any_of("-"));
\r
234 int channelIndex = -1;
\r
235 int layerIndex = -1;
\r
238 channelIndex = boost::lexical_cast<int>(split[0]) - 1;
\r
240 if(split.size() > 1)
\r
241 layerIndex = boost::lexical_cast<int>(split[1]);
\r
245 goto ParseFinnished;
\r
248 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);
\r
249 if(pChannel == 0) {
\r
250 goto ParseFinnished;
\r
253 pCommand->SetChannel(pChannel);
\r
254 pCommand->SetChannels(channels_);
\r
255 pCommand->SetChannelIndex(channelIndex);
\r
256 pCommand->SetLayerIntex(layerIndex);
\r
258 state = GetParameters;
\r
263 default: //Done and unexpected
\r
264 goto ParseFinnished;
\r
269 if(state == GetParameters && pCommand->GetMinimumParameters()==0)
\r
272 if(state != Done) {
\r
276 if(pOutState != 0) {
\r
277 *pOutState = state;
\r
283 bool AMCPProtocolStrategy::QueueCommand(AMCPCommandPtr pCommand) {
\r
284 if(pCommand->NeedChannel()) {
\r
285 unsigned int channelIndex = pCommand->GetChannelIndex() + 1;
\r
286 if(commandQueues_.size() > channelIndex) {
\r
287 commandQueues_[channelIndex]->AddCommand(pCommand);
\r
293 commandQueues_[0]->AddCommand(pCommand);
\r
298 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)
\r
300 std::wstring s = str;
\r
301 transform(s.begin(), s.end(), s.begin(), toupper);
\r
303 if (s == TEXT("MIXER")) return std::make_shared<MixerCommand>();
\r
304 else if(s == TEXT("DIAG")) return std::make_shared<DiagnosticsCommand>();
\r
305 else if(s == TEXT("CHANNEL_GRID")) return std::make_shared<ChannelGridCommand>();
\r
306 else if(s == TEXT("CALL")) return std::make_shared<CallCommand>();
\r
307 else if(s == TEXT("SWAP")) return std::make_shared<SwapCommand>();
\r
308 else if(s == TEXT("LOAD")) return std::make_shared<LoadCommand>();
\r
309 else if(s == TEXT("LOADBG")) return std::make_shared<LoadbgCommand>();
\r
310 else if(s == TEXT("ADD")) return std::make_shared<AddCommand>();
\r
311 else if(s == TEXT("REMOVE")) return std::make_shared<RemoveCommand>();
\r
312 else if(s == TEXT("PAUSE")) return std::make_shared<PauseCommand>();
\r
313 else if(s == TEXT("PLAY")) return std::make_shared<PlayCommand>();
\r
314 else if(s == TEXT("STOP")) return std::make_shared<StopCommand>();
\r
315 else if(s == TEXT("CLEAR")) return std::make_shared<ClearCommand>();
\r
316 else if(s == TEXT("PRINT")) return std::make_shared<PrintCommand>();
\r
317 else if(s == TEXT("LOG")) return std::make_shared<LogCommand>();
\r
318 else if(s == TEXT("CG")) return std::make_shared<CGCommand>();
\r
319 else if(s == TEXT("DATA")) return std::make_shared<DataCommand>();
\r
320 else if(s == TEXT("CINF")) return std::make_shared<CinfCommand>();
\r
321 else if(s == TEXT("INFO")) return std::make_shared<InfoCommand>(channels_);
\r
322 else if(s == TEXT("CLS")) return std::make_shared<ClsCommand>();
\r
323 else if(s == TEXT("TLS")) return std::make_shared<TlsCommand>();
\r
324 else if(s == TEXT("VERSION")) return std::make_shared<VersionCommand>();
\r
325 else if(s == TEXT("BYE")) return std::make_shared<ByeCommand>();
\r
326 else if(s == TEXT("SET")) return std::make_shared<SetCommand>();
\r
327 //else if(s == TEXT("MONITOR"))
\r
329 // result = AMCPCommandPtr(new MonitorCommand());
\r
331 //else if(s == TEXT("KILL"))
\r
333 // result = AMCPCommandPtr(new KillCommand());
\r
338 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
\r
340 //split on whitespace but keep strings within quotationmarks
\r
341 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
\r
343 std::wstring currentToken;
\r
346 bool getSpecialCode = false;
\r
348 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
\r
352 //insert code-handling here
\r
353 switch(message[charIndex])
\r
356 currentToken += TEXT("\\");
\r
359 currentToken += TEXT("\"");
\r
362 currentToken += TEXT("\n");
\r
367 getSpecialCode = false;
\r
371 if(message[charIndex]==TEXT('\\'))
\r
373 getSpecialCode = true;
\r
377 if(message[charIndex]==' ' && inQuote==false)
\r
379 if(currentToken.size()>0)
\r
381 pTokenVector->push_back(currentToken);
\r
382 currentToken.clear();
\r
387 if(message[charIndex]==TEXT('\"'))
\r
391 if(currentToken.size()>0)
\r
393 pTokenVector->push_back(currentToken);
\r
394 currentToken.clear();
\r
399 currentToken += message[charIndex];
\r
402 if(currentToken.size()>0)
\r
404 pTokenVector->push_back(currentToken);
\r
405 currentToken.clear();
\r
408 return pTokenVector->size();
\r
412 }} //namespace caspar