00001 #include <unistd.h>
00002 #include <iostream>
00003 using namespace std;
00004
00005 #include <qapplication.h>
00006 #include <qregexp.h>
00007 #include <qstringlist.h>
00008 #include <qtextstream.h>
00009 #include <qdir.h>
00010 #include <qdeepcopy.h>
00011
00012 #include "libmyth/mythcontext.h"
00013 #include "libmyth/mythdialogs.h"
00014 #include "networkcontrol.h"
00015 #include "programinfo.h"
00016 #include "remoteutil.h"
00017 #include "previewgenerator.h"
00018 #include "compat.h"
00019
00020 #define LOC QString("NetworkControl: ")
00021 #define LOC_ERR QString("NetworkControl Error: ")
00022
00023 const int kNetworkControlDataReadyEvent = 35671;
00024 const int kNetworkControlCloseEvent = 35672;
00025
00033 static bool is_abbrev(QString const& command, QString const& test, unsigned minchars = 1)
00034 {
00035 if (test.length() < minchars)
00036 return command.lower() == test.lower();
00037 else
00038 return test.lower() == command.left(test.length()).lower();
00039 }
00040
00041 NetworkControl::NetworkControl(int port)
00042 : QServerSocket(port, 1),
00043 prompt("# "),
00044 gotAnswer(false), answer(""),
00045 client(NULL), cs(NULL)
00046 {
00047 VERBOSE(VB_IMPORTANT, LOC +
00048 QString("Listening for remote connections on port %1").arg(port));
00049
00050
00051 jumpMap["channelpriorities"] = "Channel Recording Priorities";
00052 jumpMap["livetv"] = "Live TV";
00053 jumpMap["livetvinguide"] = "Live TV In Guide";
00054 jumpMap["mainmenu"] = "Main Menu";
00055 jumpMap["managerecordings"] = "Manage Recordings / Fix Conflicts";
00056 jumpMap["manualrecording"] = "Manual Record Scheduling";
00057 jumpMap["mythgallery"] = "MythGallery";
00058 jumpMap["mythmovietime"] = "MythMovieTime";
00059 jumpMap["mythvideo"] = "MythVideo";
00060 jumpMap["mythweather"] = "MythWeather";
00061 jumpMap["mythgame"] = "MythGame";
00062 jumpMap["mythnews"] = "MythNews";
00063 jumpMap["playdvd"] = "Play DVD";
00064 jumpMap["playmusic"] = "Play music";
00065 jumpMap["programfinder"] = "Program Finder";
00066 jumpMap["programguide"] = "Program Guide";
00067 jumpMap["recordingpriorities"] = "Program Recording Priorities";
00068 jumpMap["ripcd"] = "Rip CD";
00069 jumpMap["ripdvd"] = "Rip DVD";
00070 jumpMap["musicplaylists"] = "Select music playlists";
00071 jumpMap["deleterecordings"] = "TV Recording Deletion";
00072 jumpMap["playbackrecordings"] = "TV Recording Playback";
00073 jumpMap["videobrowser"] = "Video Browser";
00074 jumpMap["videogallery"] = "Video Gallery";
00075 jumpMap["videolistings"] = "Video Listings";
00076 jumpMap["videomanager"] = "Video Manager";
00077 jumpMap["flixbrowse"] = "Netflix Browser";
00078 jumpMap["flixqueue"] = "Netflix Queue";
00079 jumpMap["flixhistory"] = "Netflix History";
00080 jumpMap["zoneminderconsole"] = "ZoneMinder Console";
00081 jumpMap["zoneminderliveview"] = "ZoneMinder Live View";
00082 jumpMap["zoneminderevents"] = "ZoneMinder Events";
00083
00084
00085 jumpMap["channelrecpriority"] = "Channel Recording Priorities";
00086 jumpMap["viewscheduled"] = "Manage Recordings / Fix Conflicts";
00087 jumpMap["manualbox"] = "Manual Record Scheduling";
00088 jumpMap["previousbox"] = "Previously Recorded";
00089 jumpMap["progfinder"] = "Program Finder";
00090 jumpMap["guidegrid"] = "Program Guide";
00091 jumpMap["programrecpriority"] = "Program Recording Priorities";
00092 jumpMap["statusbox"] = "Status Screen";
00093 jumpMap["deletebox"] = "TV Recording Deletion";
00094 jumpMap["playbackbox"] = "TV Recording Playback";
00095
00096 keyMap["up"] = Qt::Key_Up;
00097 keyMap["down"] = Qt::Key_Down;
00098 keyMap["left"] = Qt::Key_Left;
00099 keyMap["right"] = Qt::Key_Right;
00100 keyMap["home"] = Qt::Key_Home;
00101 keyMap["end"] = Qt::Key_End;
00102 keyMap["enter"] = Qt::Key_Enter;
00103 keyMap["return"] = Qt::Key_Return;
00104 keyMap["pageup"] = Qt::Key_Prior;
00105 keyMap["pagedown"] = Qt::Key_Next;
00106 keyMap["escape"] = Qt::Key_Escape;
00107 keyMap["tab"] = Qt::Key_Tab;
00108 keyMap["backtab"] = Qt::Key_Backtab;
00109 keyMap["space"] = Qt::Key_Space;
00110 keyMap["backspace"] = Qt::Key_Backspace;
00111 keyMap["insert"] = Qt::Key_Insert;
00112 keyMap["delete"] = Qt::Key_Delete;
00113 keyMap["plus"] = Qt::Key_Plus;
00114 keyMap["+"] = Qt::Key_Plus;
00115 keyMap["comma"] = Qt::Key_Comma;
00116 keyMap[","] = Qt::Key_Comma;
00117 keyMap["minus"] = Qt::Key_Minus;
00118 keyMap["-"] = Qt::Key_Minus;
00119 keyMap["underscore"] = Qt::Key_Underscore;
00120 keyMap["_"] = Qt::Key_Underscore;
00121 keyMap["period"] = Qt::Key_Period;
00122 keyMap["."] = Qt::Key_Period;
00123 keyMap["numbersign"] = Qt::Key_NumberSign;
00124 keyMap["poundsign"] = Qt::Key_NumberSign;
00125 keyMap["hash"] = Qt::Key_NumberSign;
00126 keyMap["#"] = Qt::Key_NumberSign;
00127 keyMap["bracketleft"] = Qt::Key_BracketLeft;
00128 keyMap["["] = Qt::Key_BracketLeft;
00129 keyMap["bracketright"] = Qt::Key_BracketRight;
00130 keyMap["]"] = Qt::Key_BracketRight;
00131 keyMap["backslash"] = Qt::Key_Backslash;
00132 keyMap["\\"] = Qt::Key_Backslash;
00133 keyMap["dollar"] = Qt::Key_Dollar;
00134 keyMap["$"] = Qt::Key_Dollar;
00135 keyMap["percent"] = Qt::Key_Percent;
00136 keyMap["%"] = Qt::Key_Percent;
00137 keyMap["ampersand"] = Qt::Key_Ampersand;
00138 keyMap["&"] = Qt::Key_Ampersand;
00139 keyMap["parenleft"] = Qt::Key_ParenLeft;
00140 keyMap["("] = Qt::Key_ParenLeft;
00141 keyMap["parenright"] = Qt::Key_ParenRight;
00142 keyMap[")"] = Qt::Key_ParenRight;
00143 keyMap["asterisk"] = Qt::Key_Asterisk;
00144 keyMap["*"] = Qt::Key_Asterisk;
00145 keyMap["question"] = Qt::Key_Question;
00146 keyMap["?"] = Qt::Key_Question;
00147 keyMap["slash"] = Qt::Key_Slash;
00148 keyMap["/"] = Qt::Key_Slash;
00149 keyMap["colon"] = Qt::Key_Colon;
00150 keyMap[":"] = Qt::Key_Colon;
00151 keyMap["semicolon"] = Qt::Key_Semicolon;
00152 keyMap[";"] = Qt::Key_Semicolon;
00153 keyMap["less"] = Qt::Key_Less;
00154 keyMap["<"] = Qt::Key_Less;
00155 keyMap["equal"] = Qt::Key_Equal;
00156 keyMap["="] = Qt::Key_Equal;
00157 keyMap["greater"] = Qt::Key_Greater;
00158 keyMap[">"] = Qt::Key_Greater;
00159 keyMap["bar"] = Qt::Key_Bar;
00160 keyMap["pipe"] = Qt::Key_Bar;
00161 keyMap["|"] = Qt::Key_Bar;
00162 keyMap["f1"] = Qt::Key_F1;
00163 keyMap["f2"] = Qt::Key_F2;
00164 keyMap["f3"] = Qt::Key_F3;
00165 keyMap["f4"] = Qt::Key_F4;
00166 keyMap["f5"] = Qt::Key_F5;
00167 keyMap["f6"] = Qt::Key_F6;
00168 keyMap["f7"] = Qt::Key_F7;
00169 keyMap["f8"] = Qt::Key_F8;
00170 keyMap["f9"] = Qt::Key_F9;
00171 keyMap["f10"] = Qt::Key_F10;
00172 keyMap["f11"] = Qt::Key_F11;
00173 keyMap["f12"] = Qt::Key_F12;
00174 keyMap["f13"] = Qt::Key_F13;
00175 keyMap["f14"] = Qt::Key_F14;
00176 keyMap["f15"] = Qt::Key_F15;
00177 keyMap["f16"] = Qt::Key_F16;
00178 keyMap["f17"] = Qt::Key_F17;
00179 keyMap["f18"] = Qt::Key_F18;
00180 keyMap["f19"] = Qt::Key_F19;
00181 keyMap["f20"] = Qt::Key_F20;
00182 keyMap["f21"] = Qt::Key_F21;
00183 keyMap["f22"] = Qt::Key_F22;
00184 keyMap["f23"] = Qt::Key_F23;
00185 keyMap["f24"] = Qt::Key_F24;
00186
00187 stopCommandThread = false;
00188 pthread_create(&command_thread, NULL, CommandThread, this);
00189
00190 gContext->addListener(this);
00191 }
00192
00193 NetworkControl::~NetworkControl(void)
00194 {
00195 nrLock.lock();
00196 networkControlReplies.push_back(
00197 "mythfrontend shutting down, connection closing...");
00198 nrLock.unlock();
00199
00200 notifyDataAvailable();
00201
00202 stopCommandThread = true;
00203 ncLock.lock();
00204 ncCond.wakeOne();
00205 ncLock.unlock();
00206 pthread_join(command_thread, NULL);
00207 }
00208
00209 void *NetworkControl::CommandThread(void *param)
00210 {
00211 NetworkControl *networkControl = (NetworkControl *)param;
00212 networkControl->RunCommandThread();
00213
00214 return NULL;
00215 }
00216
00217 void NetworkControl::RunCommandThread(void)
00218 {
00219 QString command;
00220
00221 while (!stopCommandThread)
00222 {
00223 ncLock.lock();
00224 while (!networkControlCommands.size()) {
00225 ncCond.wait(&ncLock);
00226 if (stopCommandThread)
00227 {
00228 ncLock.unlock();
00229 return;
00230 }
00231 }
00232 command = networkControlCommands.front();
00233 networkControlCommands.pop_front();
00234 ncLock.unlock();
00235
00236 processNetworkControlCommand(command);
00237 }
00238 }
00239
00240 void NetworkControl::processNetworkControlCommand(QString command)
00241 {
00242 QMutexLocker locker(&clientLock);
00243 QString result = "";
00244 QStringList tokens = QStringList::split(" ", command);
00245
00246 if (is_abbrev("jump", tokens[0]))
00247 result = processJump(tokens);
00248 else if (is_abbrev("key", tokens[0]))
00249 result = processKey(tokens);
00250 else if (is_abbrev("play", tokens[0]))
00251 result = processPlay(tokens);
00252 else if (is_abbrev("query", tokens[0]))
00253 result = processQuery(tokens);
00254 else if (is_abbrev("help", tokens[0]))
00255 result = processHelp(tokens);
00256 else if ((tokens[0].lower() == "exit") || (tokens[0].lower() == "quit"))
00257 QApplication::postEvent(this,
00258 new QCustomEvent(kNetworkControlCloseEvent));
00259 else if (! tokens[0].isEmpty())
00260 result = QString("INVALID command '%1', try 'help' for more info")
00261 .arg(tokens[0]);
00262
00263 if (result != "")
00264 {
00265 nrLock.lock();
00266 networkControlReplies.push_back(QDeepCopy<QString>(result));
00267 nrLock.unlock();
00268
00269 notifyDataAvailable();
00270 }
00271 }
00272
00273 void NetworkControl::newConnection(int socket)
00274 {
00275 QString welcomeStr = "";
00276 bool closedOldConn = false;
00277 QSocket *s = new QSocket(this);
00278 connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
00279 connect(s, SIGNAL(delayedCloseFinished()), this, SLOT(discardClient()));
00280 connect(s, SIGNAL(connectionClosed()), this, SLOT(discardClient()));
00281 s->setSocket(socket);
00282
00283 VERBOSE(VB_IMPORTANT, LOC +
00284 QString("New connection established. (%1)").arg(socket));
00285
00286 QTextStream *os = new QTextStream(s);
00287 os->setEncoding(QTextStream::UnicodeUTF8);
00288
00289 QMutexLocker locker(&clientLock);
00290 if (client)
00291 {
00292 closedOldConn = true;
00293 client->close();
00294 delete client;
00295 delete cs;
00296 }
00297
00298 client = s;
00299 cs = os;
00300
00301 ncLock.lock();
00302 networkControlCommands.clear();
00303 ncLock.unlock();
00304
00305 nrLock.lock();
00306 networkControlReplies.clear();
00307 nrLock.unlock();
00308
00309 welcomeStr = "MythFrontend Network Control\r\n";
00310 if (closedOldConn)
00311 {
00312 welcomeStr +=
00313 "WARNING: mythfrontend was already under network control.\r\n";
00314 welcomeStr +=
00315 " Previous session is being disconnected.\r\n";
00316 }
00317
00318 welcomeStr += "Type 'help' for usage information\r\n"
00319 "---------------------------------";
00320 nrLock.lock();
00321 networkControlReplies.push_back(welcomeStr);
00322 nrLock.unlock();
00323
00324 notifyDataAvailable();
00325 }
00326
00327 void NetworkControl::readClient(void)
00328 {
00329 QSocket *socket = (QSocket *)sender();
00330 if (!socket)
00331 return;
00332
00333 QString lineIn;
00334 QStringList tokens;
00335 while (socket->canReadLine())
00336 {
00337 lineIn = socket->readLine();
00338 lineIn.replace(QRegExp("[^-a-zA-Z0-9\\s\\.:_#/$%&()*+,;<=>?\\[\\]\\|]"), "");
00339 lineIn.replace(QRegExp("[\r\n]"), "");
00340 lineIn.replace(QRegExp("^\\s"), "");
00341
00342 if (lineIn.isEmpty())
00343 continue;
00344
00345 tokens = QStringList::split(" ", lineIn);
00346
00347 ncLock.lock();
00348 networkControlCommands.push_back(QDeepCopy<QString>(lineIn));
00349 ncCond.wakeOne();
00350 ncLock.unlock();
00351 }
00352 }
00353
00354 void NetworkControl::discardClient(void)
00355 {
00356 QSocket *socket = (QSocket *)sender();
00357 if (!socket)
00358 return;
00359
00360 QMutexLocker locker(&clientLock);
00361 if (client == socket)
00362 {
00363 delete cs;
00364 delete client;
00365 client = NULL;
00366 cs = NULL;
00367 }
00368 else
00369 delete socket;
00370 }
00371
00372 QString NetworkControl::processJump(QStringList tokens)
00373 {
00374 QString result = "OK";
00375
00376 if ((tokens.size() < 2) || (!jumpMap.contains(tokens[1])))
00377 return QString("ERROR: See 'help %1' for usage information")
00378 .arg(tokens[0]);
00379
00380 gContext->GetMainWindow()->JumpTo(jumpMap[tokens[1]]);
00381
00382
00383
00384 QTime timer;
00385 timer.start();
00386 while ((timer.elapsed() < 2000) &&
00387 (gContext->getCurrentLocation().lower() != tokens[1]))
00388 usleep(10000);
00389
00390 return result;
00391 }
00392
00393 QString NetworkControl::processKey(QStringList tokens)
00394 {
00395 QString result = "OK";
00396 QKeyEvent *event = NULL;
00397
00398 if (tokens.size() < 2)
00399 return QString("ERROR: See 'help %1' for usage information")
00400 .arg(tokens[0]);
00401
00402 QObject *keyDest = NULL;
00403
00404 if (!gContext)
00405 return QString("ERROR: Application has no gContext!\n");
00406
00407 if (gContext->GetMainWindow())
00408 keyDest = gContext->GetMainWindow();
00409 else
00410 return QString("ERROR: Application has no main window!\n");
00411
00412 if (gContext->GetMainWindow()->currentWidget())
00413 keyDest = gContext->GetMainWindow()->currentWidget()->focusWidget();
00414
00415 unsigned int curToken = 1;
00416 unsigned int tokenLen = 0;
00417 while (curToken < tokens.size())
00418 {
00419 tokenLen = tokens[curToken].length();
00420
00421 if (tokens[curToken] == "sleep")
00422 {
00423 sleep(1);
00424 }
00425 else if (keyMap.contains(tokens[curToken]))
00426 {
00427 int keyCode = keyMap[tokens[curToken]];
00428
00429 event = new QKeyEvent(QEvent::KeyPress, keyCode, 0, NoButton);
00430 QApplication::postEvent(keyDest, event);
00431
00432 event = new QKeyEvent(QEvent::KeyRelease, keyCode, 0, NoButton);
00433 QApplication::postEvent(keyDest, event);
00434 }
00435 else if (((tokenLen == 1) &&
00436 (tokens[curToken][0].isLetterOrNumber())) ||
00437 ((tokenLen >= 1) &&
00438 (tokens[curToken].contains("+"))))
00439 {
00440 QKeySequence a(tokens[curToken]);
00441 int keyCode = a[0];
00442 int ch = (tokens[curToken].ascii())[tokenLen - 1];
00443 int buttons = NoButton;
00444
00445 if (tokenLen > 1)
00446 {
00447 QStringList tokenParts =
00448 QStringList::split("+", tokens[curToken]);
00449
00450 unsigned int partNum = 0;
00451 while (partNum < (tokenParts.size() - 1))
00452 {
00453 if (tokenParts[partNum].upper() == "CTRL")
00454 buttons |= ControlButton;
00455 if (tokenParts[partNum].upper() == "SHIFT")
00456 buttons |= ShiftButton;
00457 if (tokenParts[partNum].upper() == "ALT")
00458 buttons |= AltButton;
00459 if (tokenParts[partNum].upper() == "META")
00460 buttons |= MetaButton;
00461
00462 partNum++;
00463 }
00464 }
00465 else
00466 {
00467 if (tokens[curToken] == tokens[curToken].upper())
00468 buttons = ShiftButton;
00469 }
00470
00471 event = new QKeyEvent(QEvent::KeyPress, keyCode, ch, buttons,
00472 tokens[curToken]);
00473 QApplication::postEvent(keyDest, event);
00474
00475 event = new QKeyEvent(QEvent::KeyRelease, keyCode, ch, buttons,
00476 tokens[curToken]);
00477 QApplication::postEvent(keyDest, event);
00478 }
00479 else
00480 return QString("ERROR: Invalid syntax at '%1', see 'help %2' for "
00481 "usage information")
00482 .arg(tokens[curToken]).arg(tokens[0]);
00483
00484 curToken++;
00485 }
00486
00487 return result;
00488 }
00489
00490 QString NetworkControl::processPlay(QStringList tokens)
00491 {
00492 QString result = "OK";
00493 QString message = "";
00494
00495 if (tokens.size() < 2)
00496 return QString("ERROR: See 'help %1' for usage information")
00497 .arg(tokens[0]);
00498
00499 if ((tokens.size() >= 4) &&
00500 (is_abbrev("program", tokens[1])) &&
00501 (tokens[2].contains(QRegExp("^\\d+$"))) &&
00502 (tokens[3].contains(QRegExp(
00503 "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d$"))))
00504 {
00505 if (gContext->getCurrentLocation() == "Playback")
00506 {
00507 QString message = QString("NETWORK_CONTROL STOP");
00508 MythEvent me(message);
00509 gContext->dispatch(me);
00510
00511 QTime timer;
00512 timer.start();
00513 while ((timer.elapsed() < 10000) &&
00514 (gContext->getCurrentLocation() == "Playback"))
00515 usleep(10000);
00516 }
00517
00518 if (gContext->getCurrentLocation() != "PlaybackBox")
00519 {
00520 gContext->GetMainWindow()->JumpTo(jumpMap["playbackbox"]);
00521
00522 QTime timer;
00523 timer.start();
00524 while ((timer.elapsed() < 10000) &&
00525 (gContext->getCurrentLocation() != "PlaybackBox"))
00526 usleep(10000);
00527 }
00528
00529 if (gContext->getCurrentLocation() == "PlaybackBox")
00530 {
00531 QString action = "PLAY";
00532 if (tokens.size() == 5 && tokens[4] == "resume")
00533 action = "RESUME";
00534
00535 QString message = QString("NETWORK_CONTROL %1 PROGRAM %2 %3")
00536 .arg(action).arg(tokens[2])
00537 .arg(tokens[3].upper());
00538 MythEvent me(message);
00539 gContext->dispatch(me);
00540
00541 result = "";
00542 }
00543 else
00544 {
00545 result = QString("ERROR: Unable to change to PlaybackBox from "
00546 "%1, can not play requested file.")
00547 .arg(gContext->getCurrentLocation());
00548 }
00549 }
00550
00551
00552 else if (gContext->getCurrentLocation().lower() != "playback")
00553 {
00554 return QString("ERROR: You are in %1 mode and this command is only "
00555 "for playback mode")
00556 .arg(gContext->getCurrentLocation());
00557 }
00558 else if (is_abbrev("chanid", tokens[1], 5))
00559 {
00560 if (tokens[2].contains(QRegExp("^\\d+$")))
00561 message = QString("NETWORK_CONTROL CHANID %1").arg(tokens[2]);
00562 else
00563 return QString("ERROR: See 'help %1' for usage information")
00564 .arg(tokens[0]);
00565 }
00566 else if (is_abbrev("channel", tokens[1], 5))
00567 {
00568 if (tokens.size() < 3)
00569 return "ERROR: See 'help play' for usage information";
00570
00571 if (is_abbrev("up", tokens[2]))
00572 message = "NETWORK_CONTROL CHANNEL UP";
00573 else if (is_abbrev("down", tokens[2]))
00574 message = "NETWORK_CONTROL CHANNEL DOWN";
00575 else if (tokens[2].contains(QRegExp("^[-\\.\\d_#]+$")))
00576 message = QString("NETWORK_CONTROL CHANNEL %1").arg(tokens[2]);
00577 else
00578 return QString("ERROR: See 'help %1' for usage information")
00579 .arg(tokens[0]);
00580 }
00581 else if (is_abbrev("seek", tokens[1], 2))
00582 {
00583 if (tokens.size() < 3)
00584 return QString("ERROR: See 'help %1' for usage information")
00585 .arg(tokens[0]);
00586
00587 if (is_abbrev("beginning", tokens[2]))
00588 message = "NETWORK_CONTROL SEEK BEGINNING";
00589 else if (is_abbrev("forward", tokens[2]))
00590 message = "NETWORK_CONTROL SEEK FORWARD";
00591 else if (is_abbrev("rewind", tokens[2]) ||
00592 is_abbrev("backward", tokens[2]))
00593 message = "NETWORK_CONTROL SEEK BACKWARD";
00594 else if (tokens[2].contains(QRegExp("^\\d\\d:\\d\\d:\\d\\d$")))
00595 {
00596 int hours = tokens[2].mid(0, 2).toInt();
00597 int minutes = tokens[2].mid(3, 2).toInt();
00598 int seconds = tokens[2].mid(6, 2).toInt();
00599 message = QString("NETWORK_CONTROL SEEK POSITION %1")
00600 .arg((hours * 3600) + (minutes * 60) + seconds);
00601 }
00602 else
00603 return QString("ERROR: See 'help %1' for usage information")
00604 .arg(tokens[0]);
00605 }
00606 else if (is_abbrev("speed", tokens[1], 2))
00607 {
00608 if (tokens.size() < 3)
00609 return QString("ERROR: See 'help %1' for usage information")
00610 .arg(tokens[0]);
00611
00612 tokens[2] = tokens[2].lower();
00613 if ((tokens[2].contains(QRegExp("^\\-*\\d+x$"))) ||
00614 (tokens[2].contains(QRegExp("^\\-*\\d+\\/\\d+x$"))) ||
00615 (tokens[2].contains(QRegExp("^\\d*\\.\\d+x$"))))
00616 message = QString("NETWORK_CONTROL SPEED %1").arg(tokens[2]);
00617 else if (is_abbrev("normal", tokens[2]))
00618 message = QString("NETWORK_CONTROL SPEED 1x");
00619 else if (is_abbrev("pause", tokens[2]))
00620 message = QString("NETWORK_CONTROL SPEED 0x");
00621 else
00622 return QString("ERROR: See 'help %1' for usage information")
00623 .arg(tokens[0]);
00624 }
00625 else if (is_abbrev("save", tokens[1], 2))
00626 {
00627 if (is_abbrev("screenshot", tokens[2], 2))
00628 return saveScreenshot(tokens);
00629 }
00630 else if (is_abbrev("stop", tokens[1], 2))
00631 message = QString("NETWORK_CONTROL STOP");
00632 else
00633 return QString("ERROR: See 'help %1' for usage information")
00634 .arg(tokens[0]);
00635
00636 if (message != "")
00637 {
00638 MythEvent me(message);
00639 gContext->dispatch(me);
00640 }
00641
00642 return result;
00643 }
00644
00645 QString NetworkControl::processQuery(QStringList tokens)
00646 {
00647 QString result = "OK";
00648
00649 if (tokens.size() < 2)
00650 return QString("ERROR: See 'help %1' for usage information")
00651 .arg(tokens[0]);
00652
00653 if (is_abbrev("location", tokens[1]))
00654 {
00655 QString location = gContext->getCurrentLocation();
00656 result = location;
00657
00658
00659 if (location == "Playback")
00660 {
00661 result += " ";
00662 gotAnswer = false;
00663 QString message = QString("NETWORK_CONTROL QUERY POSITION");
00664 MythEvent me(message);
00665 gContext->dispatch(me);
00666
00667 QTime timer;
00668 timer.start();
00669 while (timer.elapsed() < 2000 && !gotAnswer)
00670 usleep(10000);
00671
00672 if (gotAnswer)
00673 result += answer;
00674 else
00675 result = "ERROR: Timed out waiting for reply from player";
00676 }
00677 }
00678 else if (is_abbrev("liveTV", tokens[1]))
00679 {
00680 if(tokens.size() == 3)
00681 return listSchedule(tokens[2]);
00682 else
00683 return listSchedule();
00684 }
00685 else if(is_abbrev("time", tokens[1]))
00686 return QDateTime::currentDateTime().toString(Qt::ISODate);
00687 else if ((tokens.size() == 4) &&
00688 is_abbrev("recording", tokens[1]) &&
00689 (tokens[2].contains(QRegExp("^\\d+$"))) &&
00690 (tokens[3].contains(QRegExp(
00691 "^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d$"))))
00692 return listRecordings(tokens[2], tokens[3].upper());
00693 else if (is_abbrev("recordings", tokens[1]))
00694 return listRecordings();
00695 else
00696 return QString("ERROR: See 'help %1' for usage information")
00697 .arg(tokens[0]);
00698
00699 return result;
00700 }
00701
00702 QString NetworkControl::processHelp(QStringList tokens)
00703 {
00704 QString command = "";
00705 QString helpText = "";
00706
00707 if (tokens.size() >= 1)
00708 {
00709 if (is_abbrev("help", tokens[0]))
00710 {
00711 if (tokens.size() >= 2)
00712 command = tokens[1];
00713 else
00714 command = "";
00715 }
00716 else
00717 {
00718 command = tokens[0];
00719 }
00720 }
00721
00722 if (is_abbrev("jump", command))
00723 {
00724 QMap<QString, QString>::Iterator it;
00725 helpText +=
00726 "Usage: jump JUMPPOINT\r\n"
00727 "\r\n"
00728 "Where JUMPPOINT is one of the following:\r\n";
00729
00730 for (it = jumpMap.begin(); it != jumpMap.end(); ++it)
00731 {
00732 helpText += it.key().leftJustify(20, ' ', true) + " - " +
00733 it.data() + "\r\n";
00734 }
00735 }
00736 else if (is_abbrev("key", command))
00737 {
00738 helpText +=
00739 "key LETTER - Send the letter key specified\r\n"
00740 "key NUMBER - Send the number key specified\r\n"
00741 "key CODE - Send one of the following key codes\r\n"
00742 "\r\n";
00743
00744 QMap<QString, int>::Iterator it;
00745 bool first = true;
00746 for (it = keyMap.begin(); it != keyMap.end(); ++it)
00747 {
00748 if (first)
00749 first = false;
00750 else
00751 helpText += ", ";
00752
00753 helpText += it.key();
00754 }
00755 helpText += "\r\n";
00756 }
00757 else if (is_abbrev("play", command))
00758 {
00759 helpText +=
00760 "play channel up - Change channel Up\r\n"
00761 "play channel down - Change channel Down\r\n"
00762 "play channel NUMBER - Change to a specific channel number\r\n"
00763 "play chanid NUMBER - Change to a specific channel id (chanid)\r\n"
00764 "play program CHANID yyyy-mm-ddThh:mm:ss\r\n"
00765 " - Play program with chanid & starttime\r\n"
00766 "play program CHANID yyyy-mm-ddThh:mm:ss resume\r\n"
00767 " - Resume program with chanid & starttime\r\n"
00768 "play save screenshot FILENAME\r\n"
00769 " - Save screenshot from current position\r\n"
00770 "play seek beginning - Seek to the beginning of the recording\r\n"
00771 "play seek forward - Skip forward in the video\r\n"
00772 "play seek backward - Skip backwards in the video\r\n"
00773 "play seek HH:MM:SS - Seek to a specific position\r\n"
00774 "play speed pause - Pause playback\r\n"
00775 "play speed normal - Playback at normal speed\r\n"
00776 "play speed 1x - Playback at normal speed\r\n"
00777 "play speed -1x - Playback at normal speed in reverse\r\n"
00778 "play speed 1/16x - Playback at 1/16x speed\r\n"
00779 "play speed 1/8x - Playback at 1/8x speed\r\n"
00780 "play speed 1/4x - Playback at 1/4x speed\r\n"
00781 "play speed 1/2x - Playback at 1/2x speed\r\n"
00782 "play speed 2x - Playback at 2x speed\r\n"
00783 "play speed 4x - Playback at 4x speed\r\n"
00784 "play speed 8x - Playback at 8x speed\r\n"
00785 "play speed 16x - Playback at 16x speed\r\n"
00786 "play stop - Stop playback\r\n";
00787 }
00788 else if (is_abbrev("query", command))
00789 {
00790 helpText +=
00791 "query location - Query current screen or location\r\n"
00792 "query recordings - List currently available recordings\r\n"
00793 "query recording CHANID STARTTIME\r\n"
00794 " - List info about the specified program\r\n"
00795 "query liveTV - List current TV schedule\r\n"
00796 "query liveTV CHANID - Query current program for specified channel\r\n"
00797 "query time - Query current time on server\r\n";
00798 }
00799 else if (command == "exit")
00800 {
00801 helpText +=
00802 "exit - Terminates session\r\n\r\n";
00803 }
00804
00805 if (helpText != "")
00806 return helpText;
00807
00808 if (command != "")
00809 helpText += QString("Unknown command '%1'\r\n\r\n").arg(command);
00810
00811 helpText +=
00812 "Valid Commands:\r\n"
00813 "---------------\r\n"
00814 "jump - Jump to a specified location in Myth\r\n"
00815 "key - Send a keypress to the program\r\n"
00816 "play - Playback related commands\r\n"
00817 "query - Queries\r\n"
00818 "exit - Exit Network Control\r\n"
00819 "\r\n"
00820 "Type 'help COMMANDNAME' for help on any specific command.\r\n";
00821
00822 return helpText;
00823 }
00824
00825 void NetworkControl::notifyDataAvailable(void)
00826 {
00827 QApplication::postEvent(this,
00828 new QCustomEvent(kNetworkControlDataReadyEvent));
00829 }
00830
00831 void NetworkControl::customEvent(QCustomEvent *e)
00832 {
00833 if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
00834 {
00835 MythEvent *me = (MythEvent *)e;
00836 QString message = me->Message();
00837
00838 if (message.left(15) != "NETWORK_CONTROL")
00839 return;
00840
00841 QStringList tokens = QStringList::split(" ", message);
00842 if ((tokens.size() >= 3) &&
00843 (tokens[1] == "ANSWER"))
00844 {
00845 answer = tokens[2];
00846 for (unsigned int i = 3; i < tokens.size(); i++)
00847 answer += QString(" ") + tokens[i];
00848 gotAnswer = true;
00849 }
00850 else if ((tokens.size() >= 3) &&
00851 (tokens[1] == "RESPONSE"))
00852 {
00853 QString response = tokens[2];
00854 for (unsigned int i = 3; i < tokens.size(); i++)
00855 response += QString(" ") + tokens[i];
00856 nrLock.lock();
00857 networkControlReplies.push_back(QDeepCopy<QString>(response));
00858 nrLock.unlock();
00859
00860 notifyDataAvailable();
00861 }
00862 }
00863 else if (e->type() == kNetworkControlDataReadyEvent)
00864 {
00865 QString reply;
00866 int replies;
00867 QRegExp crlfRegEx("\r\n$");
00868 QRegExp crlfcrlfRegEx("\r\n.*\r\n");
00869
00870 nrLock.lock();
00871 replies = networkControlReplies.size();
00872 while (client && cs && replies > 0 &&
00873 client->state() == QSocket::Connected)
00874 {
00875 reply = networkControlReplies.front();
00876 networkControlReplies.pop_front();
00877 *cs << reply;
00878 if (!reply.contains(crlfRegEx) || reply.contains(crlfcrlfRegEx))
00879 *cs << "\r\n" << prompt;
00880 client->flush();
00881
00882 replies = networkControlReplies.size();
00883 }
00884 nrLock.unlock();
00885 }
00886 else if (e->type() == kNetworkControlCloseEvent)
00887 {
00888 if (client && client->state() == QSocket::Connected)
00889 {
00890 clientLock.lock();
00891 client->close();
00892 delete client;
00893 delete cs;
00894 client = NULL;
00895 cs = NULL;
00896 clientLock.unlock();
00897 }
00898 }
00899 }
00900
00901 QString NetworkControl::listSchedule(const QString& chanID) const
00902 {
00903 QString result("");
00904 MSqlQuery query(MSqlQuery::InitCon());
00905 bool appendCRLF = true;
00906 QString queryStr("SELECT chanid, starttime, endtime, title, subtitle "
00907 "FROM program "
00908 "WHERE starttime < :NOW AND endtime > :NOW ");
00909
00910 if(chanID != "")
00911 {
00912 queryStr += " AND chanid = :CHANID";
00913 appendCRLF = false;
00914 }
00915
00916 queryStr += " ORDER BY starttime, endtime, chanid";
00917
00918 query.prepare(queryStr);
00919 query.bindValue(":NOW", QDateTime::currentDateTime());
00920 query.bindValue(":CHANID", chanID);
00921
00922 if (query.exec() && query.isActive() && query.size() > 0)
00923 {
00924 while (query.next())
00925 {
00926 QString title = QString::fromUtf8(query.value(3).toString());
00927 QString subtitle = QString::fromUtf8(query.value(4).toString());
00928
00929 if (subtitle > " ")
00930 title += QString(" -\"%1\"").arg(subtitle);
00931
00932 result +=
00933 QString("%1 %2 %3 %4")
00934 .arg(QString::number(query.value(0).toInt())
00935 .rightJustify(5, ' '))
00936 .arg(query.value(1).toDateTime().toString(Qt::ISODate))
00937 .arg(query.value(2).toDateTime().toString(Qt::ISODate))
00938 .arg(title.local8Bit());
00939
00940 if (appendCRLF)
00941 result += "\r\n";
00942 }
00943 }
00944 else
00945 {
00946 result = "ERROR: Unable to retrieve current schedule list.";
00947 }
00948 return result;
00949 }
00950
00951 QString NetworkControl::listRecordings(QString chanid, QString starttime)
00952 {
00953 QString result = "";
00954 QString episode;
00955 MSqlQuery query(MSqlQuery::InitCon());
00956 QString queryStr;
00957 bool appendCRLF = true;
00958
00959 queryStr = "SELECT chanid, starttime, title, subtitle "
00960 "FROM recorded WHERE deletepending = 0 ";
00961
00962 if ((chanid != "") && (starttime != ""))
00963 {
00964 queryStr += "AND chanid = " + chanid + " "
00965 "AND starttime = '" + starttime + "' ";
00966 appendCRLF = false;
00967 }
00968
00969 queryStr += "ORDER BY starttime, title;";
00970
00971 query.prepare(queryStr);
00972 if (query.exec() && query.isActive() && query.size() > 0)
00973 {
00974 while (query.next())
00975 {
00976 QString title = QString::fromUtf8(query.value(2).toString());
00977 QString subtitle = QString::fromUtf8(query.value(3).toString());
00978
00979 if (subtitle > " ")
00980 episode = QString("%1 -\"%2\"")
00981 .arg(title)
00982 .arg(subtitle);
00983 else
00984 episode = title;
00985
00986 result +=
00987 QString("%1 %2 %3").arg(query.value(0).toInt())
00988 .arg(query.value(1).toDateTime().toString(Qt::ISODate))
00989 .arg(episode);
00990
00991 if (appendCRLF)
00992 result += "\r\n";
00993 }
00994 }
00995 else
00996 result = "ERROR: Unable to retrieve recordings list.";
00997
00998 return result;
00999 }
01000
01001 QString NetworkControl::saveScreenshot(QStringList tokens)
01002 {
01003 QString result = "";
01004 int width = -1;
01005 int height = -1;
01006 long long frameNumber = 150;
01007
01008 QString location = gContext->getCurrentLocation();
01009
01010 if (location != "Playback")
01011 {
01012 return "ERROR: Not in playback mode, unable to save screenshot";
01013 }
01014
01015 gotAnswer = false;
01016 QString message = QString("NETWORK_CONTROL QUERY POSITION");
01017 MythEvent me(message);
01018 gContext->dispatch(me);
01019
01020 QTime timer;
01021 timer.start();
01022 while (timer.elapsed() < 2000 && !gotAnswer)
01023 usleep(10000);
01024
01025 ProgramInfo *pginfo = NULL;
01026 if (gotAnswer)
01027 {
01028 QStringList results = QStringList::split(" ", answer);
01029 pginfo = ProgramInfo::GetProgramFromRecorded(results[5], results[6]);
01030 if (!pginfo)
01031 return "ERROR: Unable to find program info for current program";
01032
01033 QString outFile = QDir::homeDirPath() + "/.mythtv/screenshot.png";
01034
01035 if (tokens.size() >= 4)
01036 outFile = tokens[3];
01037
01038 if (tokens.size() >= 5)
01039 {
01040 QStringList size = QStringList::split("x", tokens[4]);
01041 width = size[0].toInt();
01042 height = size[1].toInt();
01043 }
01044
01045 frameNumber = results[7].toInt();
01046
01047 PreviewGenerator *previewgen = new PreviewGenerator(pginfo);
01048 previewgen->SetPreviewTimeAsFrameNumber(frameNumber);
01049 previewgen->SetOutputFilename(outFile);
01050 previewgen->SetOutputSize(QSize(width, height));
01051 bool ok = previewgen->Run();
01052 previewgen->deleteLater();
01053
01054 delete pginfo;
01055
01056 QString str = "ERROR: Unable to generate screenshot, check logs";
01057 if (ok)
01058 {
01059 str = QString("OK %1x%2")
01060 .arg((width > 0) ? width : 64).arg((height > 0) ? height : 64);
01061 }
01062
01063 return str;
01064 }
01065 else
01066 return "ERROR: Timed out waiting for reply from player";
01067
01068 return "ERROR: Unknown reason";
01069 }
01070
01071
01072