]> git.sesse.net Git - casparcg/blob - protocol/amcp/AMCPProtocolStrategy.cpp
git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches...
[casparcg] / protocol / amcp / AMCPProtocolStrategy.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\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
10 *\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
15 \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
18 *\r
19 */\r
20  \r
21 #include "../StdAfx.h"\r
22 \r
23 #include "AMCPProtocolStrategy.h"\r
24 \r
25 #include "../util/AsyncEventServer.h"\r
26 #include "AMCPCommandsImpl.h"\r
27 \r
28 #include <stdio.h>\r
29 #include <crtdbg.h>\r
30 #include <string.h>\r
31 #include <algorithm>\r
32 #include <cctype>\r
33 \r
34 #include <boost/algorithm/string/trim.hpp>\r
35 #include <boost/algorithm/string/split.hpp>\r
36 #include <boost/lexical_cast.hpp>\r
37 \r
38 #if defined(_MSC_VER)\r
39 #pragma warning (push, 1) // TODO: Legacy code, just disable warnings\r
40 #endif\r
41 \r
42 namespace caspar { namespace protocol { namespace amcp {\r
43 \r
44 using IO::ClientInfoPtr;\r
45 \r
46 const std::wstring AMCPProtocolStrategy::MessageDelimiter = TEXT("\r\n");\r
47 \r
48 inline std::shared_ptr<core::video_channel> GetChannelSafe(unsigned int index, const std::vector<safe_ptr<core::video_channel>>& channels)\r
49 {\r
50         return index < channels.size() ? std::shared_ptr<core::video_channel>(channels[index]) : nullptr;\r
51 }\r
52 \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
57 \r
58                 //TODO: THROW!\r
59         }\r
60         else\r
61                 commandQueues_.push_back(pGeneralCommandQueue);\r
62 \r
63 \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
70 \r
71                 //HACK: Perform real conversion from int to string\r
72                 TCHAR num = TEXT('1')+static_cast<TCHAR>(index);\r
73                 title += num;\r
74 \r
75                 if(!pChannelCommandQueue->Start()) {\r
76                         std::wstring logString = TEXT("Failed to start command-queue for ");\r
77                         logString += title;\r
78                         CASPAR_LOG(error) << logString;\r
79 \r
80                         //TODO: THROW!\r
81                 }\r
82                 else\r
83                         commandQueues_.push_back(pChannelCommandQueue);\r
84         }\r
85 }\r
86 \r
87 AMCPProtocolStrategy::~AMCPProtocolStrategy() {\r
88 }\r
89 \r
90 void AMCPProtocolStrategy::Parse(const TCHAR* pData, int charCount, ClientInfoPtr pClientInfo)\r
91 {\r
92         size_t pos;\r
93         std::wstring recvData(pData, charCount);\r
94         std::wstring availibleData = (pClientInfo != nullptr ? pClientInfo->currentMessage_ : L"") + recvData;\r
95 \r
96         while(true) {\r
97                 pos = availibleData.find(MessageDelimiter);\r
98                 if(pos != std::wstring::npos)\r
99                 {\r
100                         std::wstring message = availibleData.substr(0,pos);\r
101 \r
102                         //This is where a complete message gets taken care of\r
103                         if(message.length() > 0) {\r
104                                 ProcessMessage(message, pClientInfo);\r
105                         }\r
106 \r
107                         std::size_t nextStartPos = pos + MessageDelimiter.length();\r
108                         if(nextStartPos < availibleData.length())\r
109                                 availibleData = availibleData.substr(nextStartPos);\r
110                         else {\r
111                                 availibleData.clear();\r
112                                 break;\r
113                         }\r
114                 }\r
115                 else\r
116                 {\r
117                         break;\r
118                 }\r
119         }\r
120         if(pClientInfo)\r
121                 pClientInfo->currentMessage_ = availibleData;\r
122 }\r
123 \r
124 void AMCPProtocolStrategy::ProcessMessage(const std::wstring& message, ClientInfoPtr& pClientInfo)\r
125 {\r
126         bool bError = true;\r
127         MessageParserState state = New;\r
128 \r
129         AMCPCommandPtr pCommand;\r
130 \r
131         pCommand = InterpretCommandString(message, &state);\r
132 \r
133         if(pCommand != 0) {\r
134                 pCommand->SetClientInfo(pClientInfo);   \r
135                 if(QueueCommand(pCommand))\r
136                         bError = false;\r
137                 else\r
138                         state = GetChannel;\r
139         }\r
140 \r
141         if(bError == true) {\r
142                 std::wstringstream answer;\r
143                 switch(state)\r
144                 {\r
145                 case GetCommand:\r
146                         answer << TEXT("400 ERROR\r\n") + message << "\r\n";\r
147                         break;\r
148                 case GetChannel:\r
149                         answer << TEXT("401 ERROR\r\n");\r
150                         break;\r
151                 case GetParameters:\r
152                         answer << TEXT("402 ERROR\r\n");\r
153                         break;\r
154                 default:\r
155                         answer << TEXT("500 FAILED\r\n");\r
156                         break;\r
157                 }\r
158                 pClientInfo->Send(answer.str());\r
159         }\r
160 }\r
161 \r
162 AMCPCommandPtr AMCPProtocolStrategy::InterpretCommandString(const std::wstring& message, MessageParserState* pOutState)\r
163 {\r
164         std::vector<std::wstring> tokens;\r
165         unsigned int currentToken = 0;\r
166         std::wstring commandSwitch;\r
167 \r
168         AMCPCommandPtr pCommand;\r
169         MessageParserState state = New;\r
170 \r
171         CASPAR_LOG(info) << message;\r
172 \r
173         std::size_t tokensInMessage = TokenizeMessage(message, &tokens);\r
174 \r
175         //parse the message one token at the time\r
176         while(currentToken < tokensInMessage)\r
177         {\r
178                 switch(state)\r
179                 {\r
180                 case New:\r
181                         if(tokens[currentToken][0] == TEXT('/'))\r
182                                 state = GetSwitch;\r
183                         else\r
184                                 state = GetCommand;\r
185                         break;\r
186 \r
187                 case GetSwitch:\r
188                         commandSwitch = tokens[currentToken];\r
189                         state = GetCommand;\r
190                         ++currentToken;\r
191                         break;\r
192 \r
193                 case GetCommand:\r
194                         pCommand = CommandFactory(tokens[currentToken]);\r
195                         if(pCommand == 0) {\r
196                                 goto ParseFinnished;\r
197                         }\r
198                         else\r
199                         {\r
200                                 //Set scheduling\r
201                                 if(commandSwitch.size() > 0) {\r
202                                         transform(commandSwitch.begin(), commandSwitch.end(), commandSwitch.begin(), toupper);\r
203 \r
204                                         if(commandSwitch == TEXT("/APP"))\r
205                                                 pCommand->SetScheduling(AddToQueue);\r
206                                         else if(commandSwitch  == TEXT("/IMMF"))\r
207                                                 pCommand->SetScheduling(ImmediatelyAndClear);\r
208                                 }\r
209 \r
210                                 if(pCommand->NeedChannel())\r
211                                         state = GetChannel;\r
212                                 else\r
213                                         state = GetParameters;\r
214                         }\r
215                         ++currentToken;\r
216                         break;\r
217 \r
218                 case GetParameters:\r
219                         {\r
220                                 _ASSERTE(pCommand != 0);\r
221                                 int parameterCount=0;\r
222                                 while(currentToken<tokensInMessage)\r
223                                 {\r
224                                         pCommand->AddParameter(tokens[currentToken++]);\r
225                                         ++parameterCount;\r
226                                 }\r
227 \r
228                                 if(parameterCount < pCommand->GetMinimumParameters()) {\r
229                                         goto ParseFinnished;\r
230                                 }\r
231 \r
232                                 state = Done;\r
233                                 break;\r
234                         }\r
235 \r
236                 case GetChannel:\r
237                         {\r
238 //                              assert(pCommand != 0);\r
239 \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
243                                         \r
244                                 int channelIndex = -1;\r
245                                 int layerIndex = -1;\r
246                                 try\r
247                                 {\r
248                                         channelIndex = boost::lexical_cast<int>(split[0]) - 1;\r
249 \r
250                                         if(split.size() > 1)\r
251                                                 layerIndex = boost::lexical_cast<int>(split[1]);\r
252                                 }\r
253                                 catch(...)\r
254                                 {\r
255                                         goto ParseFinnished;\r
256                                 }\r
257 \r
258                                 std::shared_ptr<core::video_channel> pChannel = GetChannelSafe(channelIndex, channels_);\r
259                                 if(pChannel == 0) {\r
260                                         goto ParseFinnished;\r
261                                 }\r
262 \r
263                                 pCommand->SetChannel(pChannel);\r
264                                 pCommand->SetChannels(channels_);\r
265                                 pCommand->SetChannelIndex(channelIndex);\r
266                                 pCommand->SetLayerIntex(layerIndex);\r
267 \r
268                                 state = GetParameters;\r
269                                 ++currentToken;\r
270                                 break;\r
271                         }\r
272 \r
273                 default:        //Done and unexpected\r
274                         goto ParseFinnished;\r
275                 }\r
276         }\r
277 \r
278 ParseFinnished:\r
279         if(state == GetParameters && pCommand->GetMinimumParameters()==0)\r
280                 state = Done;\r
281 \r
282         if(state != Done) {\r
283                 pCommand.reset();\r
284         }\r
285 \r
286         if(pOutState != 0) {\r
287                 *pOutState = state;\r
288         }\r
289 \r
290         return pCommand;\r
291 }\r
292 \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
298                 }\r
299                 else\r
300                         return false;\r
301         }\r
302         else {\r
303                 commandQueues_[0]->AddCommand(pCommand);\r
304         }\r
305         return true;\r
306 }\r
307 \r
308 AMCPCommandPtr AMCPProtocolStrategy::CommandFactory(const std::wstring& str)\r
309 {\r
310         std::wstring s = str;\r
311         transform(s.begin(), s.end(), s.begin(), toupper);\r
312         \r
313         if         (s == TEXT("MIXER"))         return std::make_shared<MixerCommand>();\r
314         else if(s == TEXT("DIAG"))              return std::make_shared<DiagnosticsCommand>();\r
315         else if(s == TEXT("PARAM"))             return std::make_shared<ParamCommand>();\r
316         else if(s == TEXT("SWAP"))              return std::make_shared<SwapCommand>();\r
317         else if(s == TEXT("LOAD"))              return std::make_shared<LoadCommand>();\r
318         else if(s == TEXT("LOADBG"))    return std::make_shared<LoadbgCommand>();\r
319         else if(s == TEXT("ADD"))               return std::make_shared<AddCommand>();\r
320         else if(s == TEXT("REMOVE"))    return std::make_shared<RemoveCommand>();\r
321         else if(s == TEXT("PAUSE"))             return std::make_shared<PauseCommand>();\r
322         else if(s == TEXT("PLAY"))              return std::make_shared<PlayCommand>();\r
323         else if(s == TEXT("STOP"))              return std::make_shared<StopCommand>();\r
324         else if(s == TEXT("CLEAR"))             return std::make_shared<ClearCommand>();\r
325         else if(s == TEXT("PRINT"))             return std::make_shared<PrintCommand>();\r
326         else if(s == TEXT("STATUS"))    return std::make_shared<StatusCommand>();\r
327         else if(s == TEXT("LOG"))               return std::make_shared<LogCommand>();\r
328         else if(s == TEXT("CG"))                return std::make_shared<CGCommand>();\r
329         else if(s == TEXT("DATA"))              return std::make_shared<DataCommand>();\r
330         else if(s == TEXT("CINF"))              return std::make_shared<CinfCommand>();\r
331         else if(s == TEXT("INFO"))              return std::make_shared<InfoCommand>(channels_);\r
332         else if(s == TEXT("CLS"))               return std::make_shared<ClsCommand>();\r
333         else if(s == TEXT("TLS"))               return std::make_shared<TlsCommand>();\r
334         else if(s == TEXT("VERSION"))   return std::make_shared<VersionCommand>();\r
335         else if(s == TEXT("BYE"))               return std::make_shared<ByeCommand>();\r
336         else if(s == TEXT("SET"))               return std::make_shared<SetCommand>();\r
337         //else if(s == TEXT("MONITOR"))\r
338         //{\r
339         //      result = AMCPCommandPtr(new MonitorCommand());\r
340         //}\r
341         //else if(s == TEXT("KILL"))\r
342         //{\r
343         //      result = AMCPCommandPtr(new KillCommand());\r
344         //}\r
345         return nullptr;\r
346 }\r
347 \r
348 std::size_t AMCPProtocolStrategy::TokenizeMessage(const std::wstring& message, std::vector<std::wstring>* pTokenVector)\r
349 {\r
350         //split on whitespace but keep strings within quotationmarks\r
351         //treat \ as the start of an escape-sequence: the following char will indicate what to actually put in the string\r
352 \r
353         std::wstring currentToken;\r
354 \r
355         char inQuote = 0;\r
356         bool getSpecialCode = false;\r
357 \r
358         for(unsigned int charIndex=0; charIndex<message.size(); ++charIndex)\r
359         {\r
360                 if(getSpecialCode)\r
361                 {\r
362                         //insert code-handling here\r
363                         switch(message[charIndex])\r
364                         {\r
365                         case TEXT('\\'):\r
366                                 currentToken += TEXT("\\");\r
367                                 break;\r
368                         case TEXT('\"'):\r
369                                 currentToken += TEXT("\"");\r
370                                 break;\r
371                         case TEXT('n'):\r
372                                 currentToken += TEXT("\n");\r
373                                 break;\r
374                         default:\r
375                                 break;\r
376                         };\r
377                         getSpecialCode = false;\r
378                         continue;\r
379                 }\r
380 \r
381                 if(message[charIndex]==TEXT('\\'))\r
382                 {\r
383                         getSpecialCode = true;\r
384                         continue;\r
385                 }\r
386 \r
387                 if(message[charIndex]==' ' && inQuote==false)\r
388                 {\r
389                         if(currentToken.size()>0)\r
390                         {\r
391                                 pTokenVector->push_back(currentToken);\r
392                                 currentToken.clear();\r
393                         }\r
394                         continue;\r
395                 }\r
396 \r
397                 if(message[charIndex]==TEXT('\"'))\r
398                 {\r
399                         inQuote ^= 1;\r
400 \r
401                         if(currentToken.size()>0)\r
402                         {\r
403                                 pTokenVector->push_back(currentToken);\r
404                                 currentToken.clear();\r
405                         }\r
406                         continue;\r
407                 }\r
408 \r
409                 currentToken += message[charIndex];\r
410         }\r
411 \r
412         if(currentToken.size()>0)\r
413         {\r
414                 pTokenVector->push_back(currentToken);\r
415                 currentToken.clear();\r
416         }\r
417 \r
418         return pTokenVector->size();\r
419 }\r
420 \r
421 }       //namespace amcp\r
422 }}      //namespace caspar