2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>
\r
4 * This file is part of CasparCG.
\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
21 #include "../StdAfx.h"
\r
23 #include "AMCPProtocolStrategy.h"
\r
25 #include "../util/AsyncEventServer.h"
\r
26 #include "AMCPCommandsImpl.h"
\r
31 #include <algorithm>
\r
34 #include <boost/algorithm/string/trim.hpp>
\r
35 #include <boost/algorithm/string/split.hpp>
\r
36 #include <boost/lexical_cast.hpp>
\r
38 #if defined(_MSC_VER)
\r
39 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings
\r
42 namespace caspar { namespace protocol { namespace amcp {
\r
44 using IO::ClientInfoPtr;
\r
46 const std::wstring AMCPProtocolStrategy::MessageDelimiter = TEXT("\r\n");
\r
48 inline std::shared_ptr<core::video_channel> GetChannelSafe(unsigned int index, const std::vector<safe_ptr<core::video_channel>>& channels)
\r
50 return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;
\r
53 AMCPProtocolStrategy::AMCPProtocolStrategy(const std::vector<safe_ptr<core::video_channel>>& channels) : channels_(channels) {
\r
54 AMCPCommandQueuePtr pGeneralCommandQueue(new AMCPCommandQueue());
\r
55 if(!pGeneralCommandQueue->Start()) {
\r
56 CASPAR_LOG(error) << "Failed to start the general command-queue";
\r
61 commandQueues_.push_back(pGeneralCommandQueue);
\r
64 std::shared_ptr<core::video_channel> pChannel;
\r
65 unsigned int index = -1;
\r
66 //Create a commandpump for each video_channel
\r
67 while((pChannel = GetChannelSafe(++index, channels_)) != 0) {
\r
68 AMCPCommandQueuePtr pChannelCommandQueue(new AMCPCommandQueue());
\r
69 std::wstring title = TEXT("video_channel ");
\r
71 //HACK: Perform real conversion from int to string
\r
72 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);
\r
75 if(!pChannelCommandQueue->Start()) {
\r
76 std::wstring logString = TEXT("Failed to start command-queue for ");
\r
78 CASPAR_LOG(error) << logString;
\r
83 commandQueues_.push_back(pChannelCommandQueue);
\r
87 AMCPProtocolStrategy::~AMCPProtocolStrategy() {
\r
90 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)
\r
93 std::wstring recvData(pData, charCount);
\r
94 std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;
\r
97 pos = availibleData.find(MessageDelimiter);
\r
98 if(pos != std::wstring::npos)
\r
100 std::wstring message = availibleData.substr(0,pos);
\r
102 //This is where a complete message gets taken care of
\r
103 if(message.length() > 0) {
\r
104 ProcessMessage(message, pClientInfo);
\r
107 std::size_t nextStartPos = pos + MessageDelimiter.length();
\r
108 if(nextStartPos < availibleData.length())
\r
109 availibleData = availibleData.substr(nextStartPos);
\r
111 availibleData.clear();
\r
121 pClientInfo->currentMessage_ = availibleData;
\r
124 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)
\r
126 bool bError = true;
\r
127 MessageParserState state = New;
\r
129 AMCPCommandPtr pCommand;
\r
131 pCommand = InterpretCommandString(message, &state);
\r
133 if(pCommand != 0) {
\r
134 pCommand->SetClientInfo(pClientInfo);
\r
135 if(QueueCommand(pCommand))
\r
138 state = GetChannel;
\r
141 if(bError == true) {
\r
142 std::wstringstream answer;
\r
146 answer << TEXT("400 ERROR\r\n") + message << "\r\n";
\r
149 answer << TEXT("401 ERROR\r\n");
\r
151 case GetParameters:
\r
152 answer << TEXT("402 ERROR\r\n");
\r
155 answer << TEXT("500 FAILED\r\n");
\r
158 pClientInfo->Send(answer.str());
\r
162 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)
\r
164 std::vector<std::wstring> tokens;
\r
165 unsigned int currentToken = 0;
\r
166 std::wstring commandSwitch;
\r
168 AMCPCommandPtr pCommand;
\r
169 MessageParserState state = New;
\r
171 CASPAR_LOG(trace) << message;
\r
173 std::size_t tokensInMessage = TokenizeMessage(message, &tokens);
\r
175 //parse the message one token at the time
\r
176 while(currentToken < tokensInMessage)
\r
181 if(tokens[currentToken][0] == TEXT('/'))
\r
184 state = GetCommand;
\r
188 commandSwitch = tokens[currentToken];
\r
189 state = GetCommand;
\r
194 pCommand = CommandFactory(tokens[currentToken]);
\r
195 if(pCommand == 0) {
\r
196 goto ParseFinnished;
\r
201 if(commandSwitch.size() > 0) {
\r
202 transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);
\r
204 if(commandSwitch == TEXT("/APP"))
\r
205 pCommand->SetScheduling(AddToQueue);
\r
206 else if(commandSwitch == TEXT("/IMMF"))
\r
207 pCommand->SetScheduling(ImmediatelyAndClear);
\r
210 if(pCommand->NeedChannel())
\r
211 state = GetChannel;
\r
213 state = GetParameters;
\r
218 case GetParameters:
\r
220 _ASSERTE(pCommand != 0);
\r
221 int parameterCount=0;
\r
222 while(currentToken<tokensInMessage)
\r
224 pCommand->AddParameter(tokens[currentToken++]);
\r
228 if(parameterCount < pCommand->GetMinimumParameters()) {
\r
229 goto ParseFinnished;
\r
238 // assert(pCommand != 0);
\r
240 std::wstring str = boost::trim_copy(tokens[currentToken]);
\r
241 std::vector<std::wstring> split;
\r
242 boost::split(split, str, boost::is_any_of("-"));
\r
244 int channelIndex = -1;
\r
245 int layerIndex = -1;
\r
248 channelIndex = boost::lexical_cast<int>(split[0]) - 1;
\r
250 if(split.size() > 1)
\r
251 layerIndex = boost::lexical_cast<int>(split[1]);
\r
255 goto ParseFinnished;
\r
258 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);
\r
259 if(pChannel == 0) {
\r
260 goto ParseFinnished;
\r
263 pCommand->SetChannel(pChannel);
\r
264 pCommand->SetChannels(channels_);
\r
265 pCommand->SetChannelIndex(channelIndex);
\r
266 pCommand->SetLayerIntex(layerIndex);
\r
268 state = GetParameters;
\r
273 default: //Done and unexpected
\r
274 goto ParseFinnished;
\r
279 if(state == GetParameters && pCommand->GetMinimumParameters()==0)
\r
282 if(state != Done) {
\r
286 if(pOutState != 0) {
\r
287 *pOutState = state;
\r
293 bool AMCPProtocolStrategy::QueueCommand(AMCPCommandPtr pCommand) {
\r
294 if(pCommand->NeedChannel()) {
\r
295 unsigned int channelIndex = pCommand->GetChannelIndex() + 1;
\r
296 if(commandQueues_.size() > channelIndex) {
\r
297 commandQueues_[channelIndex]->AddCommand(pCommand);
\r
303 commandQueues_[0]->AddCommand(pCommand);
\r
308 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)
\r
310 std::wstring s = str;
\r
311 transform(s.begin(), s.end(), s.begin(), toupper);
\r
313 if (s == TEXT("MIXER")) return std::make_shared<MixerCommand>();
\r
314 else if(s == TEXT("PARAM")) return std::make_shared<ParamCommand>();
\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("STATUS")) return std::make_shared<StatusCommand>();
\r
326 else if(s == TEXT("LOG")) return std::make_shared<LogCommand>();
\r
327 else if(s == TEXT("CG")) return std::make_shared<CGCommand>();
\r
328 else if(s == TEXT("DATA")) return std::make_shared<DataCommand>();
\r
329 else if(s == TEXT("CINF")) return std::make_shared<CinfCommand>();
\r
330 else if(s == TEXT("INFO")) return std::make_shared<InfoCommand>(channels_);
\r
331 else if(s == TEXT("CLS")) return std::make_shared<ClsCommand>();
\r
332 else if(s == TEXT("TLS")) return std::make_shared<TlsCommand>();
\r
333 else if(s == TEXT("VERSION")) return std::make_shared<VersionCommand>();
\r
334 else if(s == TEXT("BYE")) return std::make_shared<ByeCommand>();
\r
335 else if(s == TEXT("SET")) return std::make_shared<SetCommand>();
\r
336 //else if(s == TEXT("MONITOR"))
\r
338 // result = AMCPCommandPtr(new MonitorCommand());
\r
340 //else if(s == TEXT("KILL"))
\r
342 // result = AMCPCommandPtr(new KillCommand());
\r
347 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)
\r
349 //split on whitespace but keep strings within quotationmarks
\r
350 //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string
\r
352 std::wstring currentToken;
\r
355 bool getSpecialCode = false;
\r
357 for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)
\r
361 //insert code-handling here
\r
362 switch(message[charIndex])
\r
365 currentToken += TEXT("\\");
\r
368 currentToken += TEXT("\"");
\r
371 currentToken += TEXT("\n");
\r
376 getSpecialCode = false;
\r
380 if(message[charIndex]==TEXT('\\'))
\r
382 getSpecialCode = true;
\r
386 if(message[charIndex]==' ' && inQuote==false)
\r
388 if(currentToken.size()>0)
\r
390 pTokenVector->push_back(currentToken);
\r
391 currentToken.clear();
\r
396 if(message[charIndex]==TEXT('\"'))
\r
400 if(currentToken.size()>0)
\r
402 pTokenVector->push_back(currentToken);
\r
403 currentToken.clear();
\r
408 currentToken += message[charIndex];
\r
411 if(currentToken.size()>0)
\r
413 pTokenVector->push_back(currentToken);
\r
414 currentToken.clear();
\r
417 return pTokenVector->size();
\r
421 }} //namespace caspar