|
MythTV
0.26-pre
|
00001 #include <list> 00002 #include <cstdlib> 00003 #include <iostream> 00004 #include <algorithm> 00005 #include <cerrno> 00006 #include <memory> 00007 using namespace std; 00008 00009 #include <math.h> 00010 #include <unistd.h> 00011 #include <fcntl.h> 00012 #include "mythconfig.h" 00013 00014 #ifndef USING_MINGW 00015 #include <sys/ioctl.h> 00016 #endif 00017 00018 #include <sys/stat.h> 00019 #ifdef __linux__ 00020 # include <sys/vfs.h> 00021 #else // if !__linux__ 00022 # include <sys/param.h> 00023 # ifndef USING_MINGW 00024 # include <sys/mount.h> 00025 # endif // USING_MINGW 00026 #endif // !__linux__ 00027 00028 #include <QCoreApplication> 00029 #include <QDateTime> 00030 #include <QFile> 00031 #include <QDir> 00032 #include <QWaitCondition> 00033 #include <QRegExp> 00034 #include <QEvent> 00035 #include <QUrl> 00036 #include <QTcpServer> 00037 #include <QTimer> 00038 #include <QNetworkInterface> 00039 #include <QNetworkProxy> 00040 00041 #include "previewgeneratorqueue.h" 00042 #include "exitcodes.h" 00043 #include "mythcontext.h" 00044 #include "mythversion.h" 00045 #include "mythdb.h" 00046 #include "mainserver.h" 00047 #include "server.h" 00048 #include "mthread.h" 00049 #include "scheduler.h" 00050 #include "backendutil.h" 00051 #include "programinfo.h" 00052 #include "recordinginfo.h" 00053 #include "recordingrule.h" 00054 #include "scheduledrecording.h" 00055 #include "jobqueue.h" 00056 #include "autoexpire.h" 00057 #include "storagegroup.h" 00058 #include "compat.h" 00059 #include "ringbuffer.h" 00060 #include "remotefile.h" 00061 #include "mythsystemevent.h" 00062 #include "tv.h" 00063 #include "mythcorecontext.h" 00064 #include "mythcoreutil.h" 00065 #include "mythdirs.h" 00066 #include "mythdownloadmanager.h" 00067 #include "metadatafactory.h" 00068 #include "videoutils.h" 00069 #include "mythlogging.h" 00070 #include "filesysteminfo.h" 00071 00075 #define PRT_TIMEOUT 10 00076 00077 #define PRT_STARTUP_THREAD_COUNT 5 00078 00079 #define LOC QString("MainServer: ") 00080 #define LOC_WARN QString("MainServer, Warning: ") 00081 #define LOC_ERR QString("MainServer, Error: ") 00082 00083 namespace { 00084 00085 int delete_file_immediately(const QString &filename, 00086 bool followLinks, bool checkexists) 00087 { 00088 /* Return 0 for success, non-zero for error. */ 00089 QFile checkFile(filename); 00090 int success1, success2; 00091 00092 LOG(VB_FILE, LOG_INFO, QString("About to delete file: %1").arg(filename)); 00093 success1 = true; 00094 success2 = true; 00095 if (followLinks) 00096 { 00097 QFileInfo finfo(filename); 00098 if (finfo.isSymLink()) 00099 { 00100 QString linktext = getSymlinkTarget(filename); 00101 00102 QFile target(linktext); 00103 if (!(success1 = target.remove())) 00104 { 00105 LOG(VB_GENERAL, LOG_ERR, QString("Error deleting '%1' -> '%2'") 00106 .arg(filename).arg(linktext) + ENO); 00107 } 00108 } 00109 } 00110 if ((!checkexists || checkFile.exists()) && 00111 !(success2 = checkFile.remove())) 00112 { 00113 LOG(VB_GENERAL, LOG_ERR, QString("Error deleting '%1': %2") 00114 .arg(filename).arg(strerror(errno))); 00115 } 00116 return success1 && success2 ? 0 : -1; 00117 } 00118 00119 }; 00120 00121 QMutex MainServer::truncate_and_close_lock; 00122 const uint MainServer::kMasterServerReconnectTimeout = 1000; //ms 00123 00124 class ProcessRequestRunnable : public QRunnable 00125 { 00126 public: 00127 ProcessRequestRunnable(MainServer &parent, MythSocket *sock) : 00128 m_parent(parent), m_sock(sock) 00129 { 00130 m_sock->UpRef(); 00131 } 00132 00133 virtual void run(void) 00134 { 00135 m_parent.ProcessRequest(m_sock); 00136 m_sock->DownRef(); 00137 } 00138 00139 MainServer &m_parent; 00140 MythSocket *m_sock; 00141 }; 00142 00143 class FreeSpaceUpdater : public QRunnable 00144 { 00145 public: 00146 FreeSpaceUpdater(MainServer &parent) : 00147 m_parent(parent), m_dorun(true), m_running(true) 00148 { 00149 m_lastRequest.start(); 00150 } 00151 ~FreeSpaceUpdater() 00152 { 00153 QMutexLocker locker(&m_parent.masterFreeSpaceListLock); 00154 m_parent.masterFreeSpaceListUpdater = NULL; 00155 m_parent.masterFreeSpaceListWait.wakeAll(); 00156 } 00157 00158 virtual void run(void) 00159 { 00160 while (true) 00161 { 00162 MythTimer t; 00163 t.start(); 00164 QStringList list; 00165 m_parent.BackendQueryDiskSpace(list, true, true); 00166 { 00167 QMutexLocker locker(&m_parent.masterFreeSpaceListLock); 00168 m_parent.masterFreeSpaceList = list; 00169 } 00170 QMutexLocker locker(&m_lock); 00171 int left = kRequeryTimeout - t.elapsed(); 00172 if (m_lastRequest.elapsed() + left > kExitTimeout) 00173 m_dorun = false; 00174 if (!m_dorun) 00175 { 00176 m_running = false; 00177 break; 00178 } 00179 if (left > 50) 00180 m_wait.wait(locker.mutex(), left); 00181 } 00182 } 00183 00184 bool KeepRunning(bool dorun) 00185 { 00186 QMutexLocker locker(&m_lock); 00187 if (dorun && m_running) 00188 { 00189 m_dorun = true; 00190 m_lastRequest.restart(); 00191 } 00192 else 00193 { 00194 m_dorun = false; 00195 m_wait.wakeAll(); 00196 } 00197 return m_running; 00198 } 00199 00200 MainServer &m_parent; 00201 QMutex m_lock; 00202 bool m_dorun; 00203 bool m_running; 00204 MythTimer m_lastRequest; 00205 QWaitCondition m_wait; 00206 const static int kRequeryTimeout; 00207 const static int kExitTimeout; 00208 }; 00209 const int FreeSpaceUpdater::kRequeryTimeout = 15000; 00210 const int FreeSpaceUpdater::kExitTimeout = 61000; 00211 00212 MainServer::MainServer(bool master, int port, 00213 QMap<int, EncoderLink *> *tvList, 00214 Scheduler *sched, AutoExpire *expirer) : 00215 encoderList(tvList), mythserver(NULL), 00216 masterFreeSpaceListUpdater((master) ? new FreeSpaceUpdater(*this) : NULL), 00217 masterServerReconnect(NULL), 00218 masterServer(NULL), ismaster(master), threadPool("ProcessRequestPool"), 00219 masterBackendOverride(false), 00220 m_sched(sched), m_expirer(expirer), deferredDeleteTimer(NULL), 00221 autoexpireUpdateTimer(NULL), m_exitCode(GENERIC_EXIT_OK), 00222 m_stopped(false) 00223 { 00224 PreviewGeneratorQueue::CreatePreviewGeneratorQueue( 00225 PreviewGenerator::kLocalAndRemote, ~0, 0); 00226 PreviewGeneratorQueue::AddListener(this); 00227 00228 threadPool.setMaxThreadCount(PRT_STARTUP_THREAD_COUNT); 00229 00230 masterBackendOverride = 00231 gCoreContext->GetNumSetting("MasterBackendOverride", 0); 00232 00233 mythserver = new MythServer(); 00234 mythserver->setProxy(QNetworkProxy::NoProxy); 00235 if (!mythserver->listen(port)) 00236 { 00237 SetExitCode(GENERIC_EXIT_SOCKET_ERROR, false); 00238 return; 00239 } 00240 connect(mythserver, SIGNAL(newConnect(MythSocket *)), 00241 this, SLOT(newConnection(MythSocket *))); 00242 00243 gCoreContext->addListener(this); 00244 00245 if (!ismaster) 00246 { 00247 masterServerReconnect = new QTimer(this); 00248 masterServerReconnect->setSingleShot(true); 00249 connect(masterServerReconnect, SIGNAL(timeout()), 00250 this, SLOT(reconnectTimeout())); 00251 masterServerReconnect->start(kMasterServerReconnectTimeout); 00252 } 00253 00254 deferredDeleteTimer = new QTimer(this); 00255 connect(deferredDeleteTimer, SIGNAL(timeout()), 00256 this, SLOT(deferredDeleteSlot())); 00257 deferredDeleteTimer->start(30 * 1000); 00258 00259 if (sched) 00260 sched->SetMainServer(this); 00261 if (expirer) 00262 expirer->SetMainServer(this); 00263 00264 metadatafactory = new MetadataFactory(this); 00265 00266 autoexpireUpdateTimer = new QTimer(this); 00267 connect(autoexpireUpdateTimer, SIGNAL(timeout()), 00268 this, SLOT(autoexpireUpdate())); 00269 autoexpireUpdateTimer->setSingleShot(true); 00270 00271 AutoExpire::Update(true); 00272 00273 masterFreeSpaceList << gCoreContext->GetHostName(); 00274 masterFreeSpaceList << "TotalDiskSpace"; 00275 masterFreeSpaceList << "0"; 00276 masterFreeSpaceList << "-2"; 00277 masterFreeSpaceList << "-2"; 00278 masterFreeSpaceList << "0"; 00279 masterFreeSpaceList << "0"; 00280 masterFreeSpaceList << "0"; 00281 if (masterFreeSpaceListUpdater) 00282 { 00283 MThreadPool::globalInstance()->startReserved( 00284 masterFreeSpaceListUpdater, "FreeSpaceUpdater"); 00285 } 00286 } 00287 00288 MainServer::~MainServer() 00289 { 00290 if (!m_stopped) 00291 Stop(); 00292 } 00293 00294 void MainServer::Stop() 00295 { 00296 m_stopped = true; 00297 00298 gCoreContext->removeListener(this); 00299 00300 { 00301 QMutexLocker locker(&masterFreeSpaceListLock); 00302 if (masterFreeSpaceListUpdater) 00303 masterFreeSpaceListUpdater->KeepRunning(false); 00304 } 00305 00306 threadPool.Stop(); 00307 00308 // since Scheduler::SetMainServer() isn't thread-safe 00309 // we need to shut down the scheduler thread before we 00310 // can call SetMainServer(NULL) 00311 if (m_sched) 00312 m_sched->Stop(); 00313 00314 PreviewGeneratorQueue::RemoveListener(this); 00315 PreviewGeneratorQueue::TeardownPreviewGeneratorQueue(); 00316 00317 if (mythserver) 00318 { 00319 mythserver->disconnect(); 00320 mythserver->deleteLater(); 00321 mythserver = NULL; 00322 } 00323 00324 if (m_sched) 00325 { 00326 m_sched->Wait(); 00327 m_sched->SetMainServer(NULL); 00328 } 00329 00330 if (m_expirer) 00331 m_expirer->SetMainServer(NULL); 00332 00333 { 00334 QMutexLocker locker(&masterFreeSpaceListLock); 00335 while (masterFreeSpaceListUpdater) 00336 { 00337 masterFreeSpaceListUpdater->KeepRunning(false); 00338 masterFreeSpaceListWait.wait(locker.mutex()); 00339 } 00340 } 00341 } 00342 00343 void MainServer::autoexpireUpdate(void) 00344 { 00345 AutoExpire::Update(false); 00346 } 00347 00348 void MainServer::newConnection(MythSocket *socket) 00349 { 00350 socket->setCallbacks(this); 00351 } 00352 00353 void MainServer::readyRead(MythSocket *sock) 00354 { 00355 sockListLock.lockForRead(); 00356 PlaybackSock *testsock = GetPlaybackBySock(sock); 00357 bool expecting_reply = testsock && testsock->isExpectingReply(); 00358 sockListLock.unlock(); 00359 if (expecting_reply) 00360 return; 00361 00362 threadPool.startReserved( 00363 new ProcessRequestRunnable(*this, sock), 00364 "ProcessRequest", PRT_TIMEOUT); 00365 } 00366 00367 void MainServer::ProcessRequest(MythSocket *sock) 00368 { 00369 sock->Lock(); 00370 00371 if (sock->bytesAvailable() > 0) 00372 { 00373 ProcessRequestWork(sock); 00374 } 00375 00376 sock->Unlock(); 00377 } 00378 00379 void MainServer::ProcessRequestWork(MythSocket *sock) 00380 { 00381 QStringList listline; 00382 if (!sock->readStringList(listline)) 00383 return; 00384 00385 QString line = listline[0]; 00386 00387 line = line.simplified(); 00388 QStringList tokens = line.split(' ', QString::SkipEmptyParts); 00389 QString command = tokens[0]; 00390 #if 0 00391 LOG(VB_GENERAL, LOG_DEBUG, "command='" + command + "'"); 00392 #endif 00393 if (command == "MYTH_PROTO_VERSION") 00394 { 00395 if (tokens.size() < 2) 00396 LOG(VB_GENERAL, LOG_CRIT, "Bad MYTH_PROTO_VERSION command"); 00397 else 00398 HandleVersion(sock, tokens); 00399 return; 00400 } 00401 else if (command == "ANN") 00402 { 00403 HandleAnnounce(listline, tokens, sock); 00404 return; 00405 } 00406 else if (command == "DONE") 00407 { 00408 HandleDone(sock); 00409 return; 00410 } 00411 00412 sockListLock.lockForRead(); 00413 PlaybackSock *pbs = GetPlaybackBySock(sock); 00414 if (!pbs) 00415 { 00416 sockListLock.unlock(); 00417 LOG(VB_GENERAL, LOG_ERR, "ProcessRequest unknown socket"); 00418 return; 00419 } 00420 pbs->UpRef(); 00421 sockListLock.unlock(); 00422 00423 if (command == "QUERY_RECORDINGS") 00424 { 00425 if (tokens.size() != 2) 00426 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDINGS query"); 00427 else 00428 HandleQueryRecordings(tokens[1], pbs); 00429 } 00430 else if (command == "QUERY_RECORDING") 00431 { 00432 HandleQueryRecording(tokens, pbs); 00433 } 00434 else if (command == "GO_TO_SLEEP") 00435 { 00436 HandleGoToSleep(pbs); 00437 } 00438 else if (command == "QUERY_FREE_SPACE") 00439 { 00440 HandleQueryFreeSpace(pbs, false); 00441 } 00442 else if (command == "QUERY_FREE_SPACE_LIST") 00443 { 00444 HandleQueryFreeSpace(pbs, true); 00445 } 00446 else if (command == "QUERY_FREE_SPACE_SUMMARY") 00447 { 00448 HandleQueryFreeSpaceSummary(pbs); 00449 } 00450 else if (command == "QUERY_LOAD") 00451 { 00452 HandleQueryLoad(pbs); 00453 } 00454 else if (command == "QUERY_UPTIME") 00455 { 00456 HandleQueryUptime(pbs); 00457 } 00458 else if (command == "QUERY_HOSTNAME") 00459 { 00460 HandleQueryHostname(pbs); 00461 } 00462 else if (command == "QUERY_MEMSTATS") 00463 { 00464 HandleQueryMemStats(pbs); 00465 } 00466 else if (command == "QUERY_TIME_ZONE") 00467 { 00468 HandleQueryTimeZone(pbs); 00469 } 00470 else if (command == "QUERY_CHECKFILE") 00471 { 00472 HandleQueryCheckFile(listline, pbs); 00473 } 00474 else if (command == "QUERY_FILE_EXISTS") 00475 { 00476 if (listline.size() < 2) 00477 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_FILE_EXISTS command"); 00478 else 00479 HandleQueryFileExists(listline, pbs); 00480 } 00481 else if (command == "QUERY_FILE_HASH") 00482 { 00483 if (listline.size() < 3) 00484 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_FILE_HASH command"); 00485 else 00486 HandleQueryFileHash(listline, pbs); 00487 } 00488 else if (command == "QUERY_GUIDEDATATHROUGH") 00489 { 00490 HandleQueryGuideDataThrough(pbs); 00491 } 00492 else if (command == "DELETE_FILE") 00493 { 00494 if (listline.size() < 3) 00495 LOG(VB_GENERAL, LOG_ERR, "Bad DELETE_FILE command"); 00496 else 00497 HandleDeleteFile(listline, pbs); 00498 } 00499 else if (command == "STOP_RECORDING") 00500 { 00501 HandleStopRecording(listline, pbs); 00502 } 00503 else if (command == "CHECK_RECORDING") 00504 { 00505 HandleCheckRecordingActive(listline, pbs); 00506 } 00507 else if (command == "DELETE_RECORDING") 00508 { 00509 if (3 <= tokens.size() && tokens.size() <= 5) 00510 { 00511 bool force = (tokens.size() >= 4) && (tokens[3] == "FORCE"); 00512 bool forget = (tokens.size() >= 5) && (tokens[4] == "FORGET"); 00513 HandleDeleteRecording(tokens[1], tokens[2], pbs, force, forget); 00514 } 00515 else 00516 HandleDeleteRecording(listline, pbs, false); 00517 } 00518 else if (command == "FORCE_DELETE_RECORDING") 00519 { 00520 HandleDeleteRecording(listline, pbs, true); 00521 } 00522 else if (command == "UNDELETE_RECORDING") 00523 { 00524 HandleUndeleteRecording(listline, pbs); 00525 } 00526 else if (command == "RESCHEDULE_RECORDINGS") 00527 { 00528 listline.pop_front(); 00529 HandleRescheduleRecordings(listline, pbs); 00530 } 00531 else if (command == "FORGET_RECORDING") 00532 { 00533 HandleForgetRecording(listline, pbs); 00534 } 00535 else if (command == "QUERY_GETALLPENDING") 00536 { 00537 if (tokens.size() == 1) 00538 HandleGetPendingRecordings(pbs); 00539 else if (tokens.size() == 2) 00540 HandleGetPendingRecordings(pbs, tokens[1]); 00541 else 00542 HandleGetPendingRecordings(pbs, tokens[1], tokens[2].toInt()); 00543 } 00544 else if (command == "QUERY_GETALLSCHEDULED") 00545 { 00546 HandleGetScheduledRecordings(pbs); 00547 } 00548 else if (command == "QUERY_GETCONFLICTING") 00549 { 00550 HandleGetConflictingRecordings(listline, pbs); 00551 } 00552 else if (command == "QUERY_GETEXPIRING") 00553 { 00554 HandleGetExpiringRecordings(pbs); 00555 } 00556 else if (command == "QUERY_SG_GETFILELIST") 00557 { 00558 HandleSGGetFileList(listline, pbs); 00559 } 00560 else if (command == "QUERY_SG_FILEQUERY") 00561 { 00562 HandleSGFileQuery(listline, pbs); 00563 } 00564 else if (command == "GET_FREE_RECORDER") 00565 { 00566 HandleGetFreeRecorder(pbs); 00567 } 00568 else if (command == "GET_FREE_RECORDER_COUNT") 00569 { 00570 HandleGetFreeRecorderCount(pbs); 00571 } 00572 else if (command == "GET_FREE_RECORDER_LIST") 00573 { 00574 HandleGetFreeRecorderList(pbs); 00575 } 00576 else if (command == "GET_NEXT_FREE_RECORDER") 00577 { 00578 HandleGetNextFreeRecorder(listline, pbs); 00579 } 00580 else if (command == "QUERY_RECORDER") 00581 { 00582 if (tokens.size() != 2) 00583 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDER"); 00584 else 00585 HandleRecorderQuery(listline, tokens, pbs); 00586 } 00587 else if (command == "QUERY_RECORDING_DEVICE") 00588 { 00589 // TODO 00590 } 00591 else if (command == "QUERY_RECORDING_DEVICES") 00592 { 00593 // TODO 00594 } 00595 else if (command == "SET_NEXT_LIVETV_DIR") 00596 { 00597 if (tokens.size() != 3) 00598 LOG(VB_GENERAL, LOG_ERR, "Bad SET_NEXT_LIVETV_DIR"); 00599 else 00600 HandleSetNextLiveTVDir(tokens, pbs); 00601 } 00602 else if (command == "SET_CHANNEL_INFO") 00603 { 00604 HandleSetChannelInfo(listline, pbs); 00605 } 00606 else if (command == "QUERY_REMOTEENCODER") 00607 { 00608 if (tokens.size() != 2) 00609 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_REMOTEENCODER"); 00610 else 00611 HandleRemoteEncoder(listline, tokens, pbs); 00612 } 00613 else if (command == "GET_RECORDER_FROM_NUM") 00614 { 00615 HandleGetRecorderFromNum(listline, pbs); 00616 } 00617 else if (command == "GET_RECORDER_NUM") 00618 { 00619 HandleGetRecorderNum(listline, pbs); 00620 } 00621 else if (command == "QUERY_FILETRANSFER") 00622 { 00623 if (tokens.size() != 2) 00624 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_FILETRANSFER"); 00625 else 00626 HandleFileTransferQuery(listline, tokens, pbs); 00627 } 00628 else if (command == "QUERY_GENPIXMAP2") 00629 { 00630 HandleGenPreviewPixmap(listline, pbs); 00631 } 00632 else if (command == "QUERY_PIXMAP_LASTMODIFIED") 00633 { 00634 HandlePixmapLastModified(listline, pbs); 00635 } 00636 else if (command == "QUERY_PIXMAP_GET_IF_MODIFIED") 00637 { 00638 HandlePixmapGetIfModified(listline, pbs); 00639 } 00640 else if (command == "QUERY_ISRECORDING") 00641 { 00642 HandleIsRecording(listline, pbs); 00643 } 00644 else if (command == "MESSAGE") 00645 { 00646 if ((listline.size() >= 2) && (listline[1].left(11) == "SET_VERBOSE")) 00647 HandleSetVerbose(listline, pbs); 00648 else if ((listline.size() >= 2) && 00649 (listline[1].left(13) == "SET_LOG_LEVEL")) 00650 HandleSetLogLevel(listline, pbs); 00651 else 00652 HandleMessage(listline, pbs); 00653 } 00654 else if (command == "FILL_PROGRAM_INFO") 00655 { 00656 HandleFillProgramInfo(listline, pbs); 00657 } 00658 else if (command == "LOCK_TUNER") 00659 { 00660 if (tokens.size() == 1) 00661 HandleLockTuner(pbs); 00662 else if (tokens.size() == 2) 00663 HandleLockTuner(pbs, tokens[1].toInt()); 00664 else 00665 LOG(VB_GENERAL, LOG_ERR, "Bad LOCK_TUNER query"); 00666 } 00667 else if (command == "FREE_TUNER") 00668 { 00669 if (tokens.size() != 2) 00670 LOG(VB_GENERAL, LOG_ERR, "Bad FREE_TUNER query"); 00671 else 00672 HandleFreeTuner(tokens[1].toInt(), pbs); 00673 } 00674 else if (command == "QUERY_ACTIVE_BACKENDS") 00675 { 00676 HandleActiveBackendsQuery(pbs); 00677 } 00678 else if (command == "QUERY_IS_ACTIVE_BACKEND") 00679 { 00680 if (tokens.size() != 1) 00681 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_IS_ACTIVE_BACKEND"); 00682 else 00683 HandleIsActiveBackendQuery(listline, pbs); 00684 } 00685 else if (command == "QUERY_COMMBREAK") 00686 { 00687 if (tokens.size() != 3) 00688 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_COMMBREAK"); 00689 else 00690 HandleCommBreakQuery(tokens[1], tokens[2], pbs); 00691 } 00692 else if (command == "QUERY_CUTLIST") 00693 { 00694 if (tokens.size() != 3) 00695 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_CUTLIST"); 00696 else 00697 HandleCutlistQuery(tokens[1], tokens[2], pbs); 00698 } 00699 else if (command == "QUERY_BOOKMARK") 00700 { 00701 if (tokens.size() != 3) 00702 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_BOOKMARK"); 00703 else 00704 HandleBookmarkQuery(tokens[1], tokens[2], pbs); 00705 } 00706 else if (command == "SET_BOOKMARK") 00707 { 00708 if (tokens.size() != 5) 00709 LOG(VB_GENERAL, LOG_ERR, "Bad SET_BOOKMARK"); 00710 else 00711 HandleSetBookmark(tokens, pbs); 00712 } 00713 else if (command == "QUERY_SETTING") 00714 { 00715 if (tokens.size() != 3) 00716 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_SETTING"); 00717 else 00718 HandleSettingQuery(tokens, pbs); 00719 } 00720 else if (command == "SET_SETTING") 00721 { 00722 if (tokens.size() != 4) 00723 LOG(VB_GENERAL, LOG_ERR, "Bad SET_SETTING"); 00724 else 00725 HandleSetSetting(tokens, pbs); 00726 } 00727 else if (command == "SCAN_VIDEOS") 00728 { 00729 HandleScanVideos(pbs); 00730 } 00731 else if (command == "ALLOW_SHUTDOWN") 00732 { 00733 if (tokens.size() != 1) 00734 LOG(VB_GENERAL, LOG_ERR, "Bad ALLOW_SHUTDOWN"); 00735 else 00736 HandleBlockShutdown(false, pbs); 00737 } 00738 else if (command == "BLOCK_SHUTDOWN") 00739 { 00740 if (tokens.size() != 1) 00741 LOG(VB_GENERAL, LOG_ERR, "Bad BLOCK_SHUTDOWN"); 00742 else 00743 HandleBlockShutdown(true, pbs); 00744 } 00745 else if (command == "SHUTDOWN_NOW") 00746 { 00747 if (tokens.size() != 1) 00748 LOG(VB_GENERAL, LOG_ERR, "Bad SHUTDOWN_NOW query"); 00749 else if (!ismaster) 00750 { 00751 QString halt_cmd; 00752 if (listline.size() >= 2) 00753 halt_cmd = listline[1]; 00754 00755 if (!halt_cmd.isEmpty()) 00756 { 00757 LOG(VB_GENERAL, LOG_NOTICE, 00758 "Going down now as of Mainserver request!"); 00759 myth_system(halt_cmd); 00760 } 00761 else 00762 LOG(VB_GENERAL, LOG_WARNING, 00763 "Received an empty SHUTDOWN_NOW query!"); 00764 } 00765 } 00766 else if (command == "BACKEND_MESSAGE") 00767 { 00768 QString message = listline[1]; 00769 QStringList extra( listline[2] ); 00770 for (int i = 3; i < listline.size(); i++) 00771 extra << listline[i]; 00772 MythEvent me(message, extra); 00773 gCoreContext->dispatch(me); 00774 } 00775 else if ((command == "DOWNLOAD_FILE") || 00776 (command == "DOWNLOAD_FILE_NOW")) 00777 { 00778 if (listline.size() != 4) 00779 LOG(VB_GENERAL, LOG_ERR, QString("Bad %1 command").arg(command)); 00780 else 00781 HandleDownloadFile(listline, pbs); 00782 } 00783 else if (command == "REFRESH_BACKEND") 00784 { 00785 LOG(VB_GENERAL, LOG_INFO ,"Reloading backend settings"); 00786 HandleBackendRefresh(sock); 00787 } 00788 else if (command == "OK") 00789 { 00790 LOG(VB_GENERAL, LOG_ERR, "Got 'OK' out of sequence."); 00791 } 00792 else if (command == "UNKNOWN_COMMAND") 00793 { 00794 LOG(VB_GENERAL, LOG_ERR, "Got 'UNKNOWN_COMMAND' out of sequence."); 00795 } 00796 else 00797 { 00798 LOG(VB_GENERAL, LOG_ERR, "Unknown command: " + command); 00799 00800 MythSocket *pbssock = pbs->getSocket(); 00801 00802 QStringList strlist; 00803 strlist << "UNKNOWN_COMMAND"; 00804 00805 SendResponse(pbssock, strlist); 00806 } 00807 00808 // Decrease refcount.. 00809 pbs->DownRef(); 00810 } 00811 00812 void MainServer::customEvent(QEvent *e) 00813 { 00814 QStringList broadcast; 00815 QSet<QString> receivers; 00816 00817 if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage) 00818 { 00819 MythEvent *me = (MythEvent *)e; 00820 00821 QString message = me->Message(); 00822 QString error; 00823 if ((message == "PREVIEW_SUCCESS" || message == "PREVIEW_QUEUED") && 00824 me->ExtraDataCount() >= 5) 00825 { 00826 bool ok = true; 00827 QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey() 00828 QString filename = me->ExtraData(1); // outFileName 00829 QString msg = me->ExtraData(2); 00830 QString datetime = me->ExtraData(3); 00831 00832 if (message == "PREVIEW_QUEUED") 00833 { 00834 LOG(VB_PLAYBACK, LOG_INFO, QString("Preview Queued: '%1' '%2'") 00835 .arg(pginfokey).arg(filename)); 00836 return; 00837 } 00838 00839 QFile file(filename); 00840 ok = ok && file.open(QIODevice::ReadOnly); 00841 00842 if (ok) 00843 { 00844 QByteArray data = file.readAll(); 00845 QStringList extra("OK"); 00846 extra.push_back(pginfokey); 00847 extra.push_back(msg); 00848 extra.push_back(datetime); 00849 extra.push_back(QString::number(data.size())); 00850 extra.push_back( 00851 QString::number(qChecksum(data.constData(), data.size()))); 00852 extra.push_back(QString(data.toBase64())); 00853 00854 for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++) 00855 { 00856 QString token = me->ExtraData(i); 00857 extra.push_back(token); 00858 RequestedBy::iterator it = m_previewRequestedBy.find(token); 00859 if (it != m_previewRequestedBy.end()) 00860 { 00861 receivers.insert(*it); 00862 m_previewRequestedBy.erase(it); 00863 } 00864 } 00865 00866 if (receivers.empty()) 00867 { 00868 LOG(VB_GENERAL, LOG_ERR, LOC + 00869 "PREVIEW_SUCCESS but no receivers."); 00870 return; 00871 } 00872 00873 broadcast.push_back("BACKEND_MESSAGE"); 00874 broadcast.push_back("GENERATED_PIXMAP"); 00875 broadcast += extra; 00876 } 00877 else 00878 { 00879 message = "PREVIEW_FAILED"; 00880 error = QString("Failed to read '%1'").arg(filename); 00881 LOG(VB_GENERAL, LOG_ERR, LOC + error); 00882 } 00883 } 00884 00885 if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5) 00886 { 00887 QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey() 00888 QString msg = me->ExtraData(2); 00889 00890 QStringList extra("ERROR"); 00891 extra.push_back(pginfokey); 00892 extra.push_back(msg); 00893 for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++) 00894 { 00895 QString token = me->ExtraData(i); 00896 extra.push_back(token); 00897 RequestedBy::iterator it = m_previewRequestedBy.find(token); 00898 if (it != m_previewRequestedBy.end()) 00899 { 00900 receivers.insert(*it); 00901 m_previewRequestedBy.erase(it); 00902 } 00903 } 00904 00905 if (receivers.empty()) 00906 { 00907 LOG(VB_GENERAL, LOG_ERR, LOC + 00908 "PREVIEW_FAILED but no receivers."); 00909 return; 00910 } 00911 00912 broadcast.push_back("BACKEND_MESSAGE"); 00913 broadcast.push_back("GENERATED_PIXMAP"); 00914 broadcast += extra; 00915 } 00916 00917 if (me->Message().left(11) == "AUTO_EXPIRE") 00918 { 00919 QStringList tokens = me->Message() 00920 .split(" ", QString::SkipEmptyParts); 00921 00922 if (tokens.size() != 3) 00923 { 00924 LOG(VB_GENERAL, LOG_ERR, "Bad AUTO_EXPIRE message"); 00925 return; 00926 } 00927 00928 QDateTime startts = QDateTime::fromString(tokens[2], Qt::ISODate); 00929 RecordingInfo recInfo(tokens[1].toUInt(), startts); 00930 00931 if (recInfo.GetChanID()) 00932 { 00933 SendMythSystemPlayEvent("REC_EXPIRED", &recInfo); 00934 00935 // allow re-record if auto expired but not expired live 00936 // or already "deleted" programs 00937 if (recInfo.GetRecordingGroup() != "LiveTV" && 00938 recInfo.GetRecordingGroup() != "Deleted" && 00939 (gCoreContext->GetNumSetting("RerecordWatched", 0) || 00940 !recInfo.IsWatched())) 00941 { 00942 recInfo.ForgetHistory(); 00943 } 00944 DoHandleDeleteRecording(recInfo, NULL, false, true, false); 00945 } 00946 else 00947 { 00948 QString msg = QString("Cannot find program info for '%1', " 00949 "while attempting to Auto-Expire.") 00950 .arg(me->Message()); 00951 LOG(VB_GENERAL, LOG_ERR, LOC + msg); 00952 } 00953 00954 return; 00955 } 00956 00957 if (me->Message().left(21) == "QUERY_NEXT_LIVETV_DIR" && m_sched) 00958 { 00959 QStringList tokens = me->Message() 00960 .split(" ", QString::SkipEmptyParts); 00961 00962 if (tokens.size() != 2) 00963 { 00964 LOG(VB_GENERAL, LOG_ERR, 00965 QString("Bad %1 message").arg(tokens[0])); 00966 return; 00967 } 00968 00969 m_sched->GetNextLiveTVDir(tokens[1].toInt()); 00970 return; 00971 } 00972 00973 if ((me->Message().left(16) == "DELETE_RECORDING") || 00974 (me->Message().left(22) == "FORCE_DELETE_RECORDING")) 00975 { 00976 QStringList tokens = me->Message() 00977 .split(" ", QString::SkipEmptyParts); 00978 00979 if (tokens.size() != 3) 00980 { 00981 LOG(VB_GENERAL, LOG_ERR, 00982 QString("Bad %1 message").arg(tokens[0])); 00983 return; 00984 } 00985 00986 QDateTime startts = QDateTime::fromString(tokens[2], Qt::ISODate); 00987 RecordingInfo recInfo(tokens[1].toUInt(), startts); 00988 00989 if (recInfo.GetChanID()) 00990 { 00991 if (tokens[0] == "FORCE_DELETE_RECORDING") 00992 DoHandleDeleteRecording(recInfo, NULL, true, false, false); 00993 else 00994 DoHandleDeleteRecording(recInfo, NULL, false, false, false); 00995 } 00996 else 00997 { 00998 LOG(VB_GENERAL, LOG_ERR, 00999 QString("Cannot find program info for '%1' while " 01000 "attempting to delete.").arg(me->Message())); 01001 } 01002 01003 return; 01004 } 01005 01006 if (me->Message().left(21) == "RESCHEDULE_RECORDINGS" && m_sched) 01007 { 01008 QStringList request = me->ExtraDataList(); 01009 m_sched->Reschedule(request); 01010 return; 01011 } 01012 01013 if (me->Message().left(23) == "SCHEDULER_ADD_RECORDING" && m_sched) 01014 { 01015 ProgramInfo pi(me->ExtraDataList()); 01016 if (!pi.GetChanID()) 01017 { 01018 LOG(VB_GENERAL, LOG_ERR, "Bad SCHEDULER_ADD_RECORDING message"); 01019 return; 01020 } 01021 01022 m_sched->AddRecording(pi); 01023 return; 01024 } 01025 01026 if (me->Message().left(23) == "UPDATE_RECORDING_STATUS" && m_sched) 01027 { 01028 QStringList tokens = me->Message() 01029 .split(" ", QString::SkipEmptyParts); 01030 01031 if (tokens.size() != 6) 01032 { 01033 LOG(VB_GENERAL, LOG_ERR, "Bad UPDATE_RECORDING_STATUS message"); 01034 return; 01035 } 01036 01037 uint cardid = tokens[1].toUInt(); 01038 uint chanid = tokens[2].toUInt(); 01039 QDateTime startts = QDateTime::fromString(tokens[3], Qt::ISODate); 01040 RecStatusType recstatus = RecStatusType(tokens[4].toInt()); 01041 QDateTime recendts = QDateTime::fromString(tokens[5], Qt::ISODate); 01042 m_sched->UpdateRecStatus(cardid, chanid, startts, 01043 recstatus, recendts); 01044 return; 01045 } 01046 01047 if (me->Message().left(13) == "LIVETV_EXITED") 01048 { 01049 QString chainid = me->ExtraData(); 01050 LiveTVChain *chain = GetExistingChain(chainid); 01051 if (chain) 01052 DeleteChain(chain); 01053 01054 return; 01055 } 01056 01057 if (me->Message() == "CLEAR_SETTINGS_CACHE") 01058 gCoreContext->ClearSettingsCache(); 01059 01060 if (me->Message().left(14) == "RESET_IDLETIME" && m_sched) 01061 m_sched->ResetIdleTime(); 01062 01063 if (me->Message() == "LOCAL_RECONNECT_TO_MASTER") 01064 masterServerReconnect->start(kMasterServerReconnectTimeout); 01065 01066 if (me->Message() == "LOCAL_SLAVE_BACKEND_ENCODERS_OFFLINE") 01067 HandleSlaveDisconnectedEvent(*me); 01068 01069 if (me->Message().left(6) == "LOCAL_") 01070 return; 01071 01072 MythEvent mod_me(""); 01073 if (me->Message().left(23) == "MASTER_UPDATE_PROG_INFO") 01074 { 01075 QStringList tokens = me->Message().simplified().split(" "); 01076 uint chanid = 0; 01077 QDateTime recstartts; 01078 if (tokens.size() >= 3) 01079 { 01080 chanid = tokens[1].toUInt(); 01081 recstartts = QDateTime::fromString(tokens[2], Qt::ISODate); 01082 } 01083 01084 ProgramInfo evinfo(chanid, recstartts); 01085 if (evinfo.GetChanID()) 01086 { 01087 QDateTime rectime = QDateTime::currentDateTime().addSecs( 01088 -gCoreContext->GetNumSetting("RecordOverTime")); 01089 01090 if (m_sched && evinfo.GetRecordingEndTime() > rectime) 01091 evinfo.SetRecordingStatus(m_sched->GetRecStatus(evinfo)); 01092 01093 QStringList list; 01094 evinfo.ToStringList(list); 01095 mod_me = MythEvent("RECORDING_LIST_CHANGE UPDATE", list); 01096 me = &mod_me; 01097 } 01098 else 01099 { 01100 return; 01101 } 01102 } 01103 01104 if (me->Message().left(13) == "DOWNLOAD_FILE") 01105 { 01106 QStringList extraDataList = me->ExtraDataList(); 01107 QString localFile = extraDataList[1]; 01108 QFile file(localFile); 01109 QStringList tokens = me->Message().simplified().split(" "); 01110 QMutexLocker locker(&m_downloadURLsLock); 01111 01112 if (!m_downloadURLs.contains(localFile)) 01113 return; 01114 01115 extraDataList[1] = m_downloadURLs[localFile]; 01116 01117 if ((tokens.size() >= 2) && (tokens[1] == "FINISHED")) 01118 m_downloadURLs.remove(localFile); 01119 01120 mod_me = MythEvent(me->Message(), extraDataList); 01121 me = &mod_me; 01122 } 01123 01124 if (broadcast.empty()) 01125 { 01126 broadcast.push_back("BACKEND_MESSAGE"); 01127 broadcast.push_back(me->Message()); 01128 broadcast += me->ExtraDataList(); 01129 } 01130 } 01131 01132 if (!broadcast.empty()) 01133 { 01134 // Make a local copy of the list, upping the refcount as we go.. 01135 vector<PlaybackSock *> localPBSList; 01136 sockListLock.lockForRead(); 01137 vector<PlaybackSock *>::iterator it = playbackList.begin(); 01138 for (; it != playbackList.end(); ++it) 01139 { 01140 (*it)->UpRef(); 01141 localPBSList.push_back(*it); 01142 } 01143 sockListLock.unlock(); 01144 01145 bool sendGlobal = false; 01146 if (ismaster && broadcast[1].left(7) == "GLOBAL_") 01147 { 01148 broadcast[1].replace("GLOBAL_", "LOCAL_"); 01149 MythEvent me(broadcast[1], broadcast[2]); 01150 gCoreContext->dispatch(me); 01151 01152 sendGlobal = true; 01153 } 01154 01155 QSet<PlaybackSock*> sentSet; 01156 01157 bool isSystemEvent = broadcast[1].startsWith("SYSTEM_EVENT "); 01158 QStringList sentSetSystemEvent(gCoreContext->GetHostName()); 01159 01160 vector<PlaybackSock*>::const_iterator iter; 01161 for (iter = localPBSList.begin(); iter != localPBSList.end(); ++iter) 01162 { 01163 PlaybackSock *pbs = *iter; 01164 01165 if (sentSet.contains(pbs) || pbs->IsDisconnected()) 01166 continue; 01167 01168 if (!receivers.empty() && !receivers.contains(pbs->getHostname())) 01169 continue; 01170 01171 sentSet.insert(pbs); 01172 01173 bool reallysendit = false; 01174 01175 if (broadcast[1] == "CLEAR_SETTINGS_CACHE") 01176 { 01177 if ((ismaster) && 01178 (pbs->isSlaveBackend() || pbs->wantsEvents())) 01179 reallysendit = true; 01180 } 01181 else if (sendGlobal) 01182 { 01183 if (pbs->isSlaveBackend()) 01184 reallysendit = true; 01185 } 01186 else if (pbs->wantsEvents()) 01187 { 01188 reallysendit = true; 01189 } 01190 01191 if (reallysendit) 01192 { 01193 if (isSystemEvent) 01194 { 01195 if (!pbs->wantsSystemEvents()) 01196 { 01197 continue; 01198 } 01199 else if (!pbs->wantsOnlySystemEvents()) 01200 { 01201 if (sentSetSystemEvent.contains(pbs->getHostname())) 01202 continue; 01203 01204 sentSetSystemEvent << pbs->getHostname(); 01205 } 01206 } 01207 else if (pbs->wantsOnlySystemEvents()) 01208 continue; 01209 } 01210 01211 MythSocket *sock = pbs->getSocket(); 01212 if (reallysendit && sock->socket() >= 0) 01213 { 01214 sock->Lock(); 01215 01216 if (sock->socket() >= 0) 01217 sock->writeStringList(broadcast); 01218 01219 sock->Unlock(); 01220 } 01221 } 01222 01223 // Done with the pbs list, so decrement all the instances.. 01224 for (iter = localPBSList.begin(); iter != localPBSList.end(); ++iter) 01225 { 01226 PlaybackSock *pbs = *iter; 01227 pbs->DownRef(); 01228 } 01229 } 01230 } 01231 01240 void MainServer::HandleVersion(MythSocket *socket, const QStringList &slist) 01241 { 01242 QStringList retlist; 01243 QString version = slist[1]; 01244 if (version != MYTH_PROTO_VERSION) 01245 { 01246 LOG(VB_GENERAL, LOG_CRIT, 01247 "MainServer::HandleVersion - Client speaks protocol version " + 01248 version + " but we speak " + MYTH_PROTO_VERSION + '!'); 01249 retlist << "REJECT" << MYTH_PROTO_VERSION; 01250 socket->writeStringList(retlist); 01251 HandleDone(socket); 01252 return; 01253 } 01254 01255 if (slist.size() < 3) 01256 { 01257 LOG(VB_GENERAL, LOG_CRIT, 01258 "MainServer::HandleVersion - Client did not pass protocol " 01259 "token. Refusing connection!"); 01260 retlist << "REJECT" << MYTH_PROTO_VERSION; 01261 socket->writeStringList(retlist); 01262 HandleDone(socket); 01263 return; 01264 } 01265 01266 QString token = slist[2]; 01267 if (token != MYTH_PROTO_TOKEN) 01268 { 01269 LOG(VB_GENERAL, LOG_CRIT, 01270 "MainServer::HandleVersion - Client sent incorrect protocol" 01271 " token for protocol version. Refusing connection!"); 01272 retlist << "REJECT" << MYTH_PROTO_VERSION; 01273 socket->writeStringList(retlist); 01274 HandleDone(socket); 01275 return; 01276 } 01277 01278 retlist << "ACCEPT" << MYTH_PROTO_VERSION; 01279 socket->writeStringList(retlist); 01280 } 01281 01294 void MainServer::HandleAnnounce(QStringList &slist, QStringList commands, 01295 MythSocket *socket) 01296 { 01297 QStringList retlist( "OK" ); 01298 QStringList errlist( "ERROR" ); 01299 01300 if (commands.size() < 3 || commands.size() > 6) 01301 { 01302 QString info = ""; 01303 if (commands.size() == 2) 01304 info = QString(" %1").arg(commands[1]); 01305 01306 LOG(VB_GENERAL, LOG_ERR, QString("Received malformed ANN%1 query") 01307 .arg(info)); 01308 01309 errlist << "malformed_ann_query"; 01310 socket->writeStringList(errlist); 01311 return; 01312 } 01313 01314 sockListLock.lockForRead(); 01315 vector<PlaybackSock *>::iterator iter = playbackList.begin(); 01316 for (; iter != playbackList.end(); ++iter) 01317 { 01318 PlaybackSock *pbs = *iter; 01319 if (pbs->getSocket() == socket) 01320 { 01321 LOG(VB_GENERAL, LOG_WARNING, 01322 QString("Client %1 is trying to announce a socket " 01323 "multiple times.") 01324 .arg(commands[2])); 01325 socket->writeStringList(retlist); 01326 sockListLock.unlock(); 01327 return; 01328 } 01329 } 01330 sockListLock.unlock(); 01331 01332 if (commands[1] == "Playback" || commands[1] == "Monitor") 01333 { 01334 if (commands.size() < 4) 01335 { 01336 LOG(VB_GENERAL, LOG_ERR, QString("Received malformed ANN %1 query") 01337 .arg(commands[1])); 01338 01339 errlist << "malformed_ann_query"; 01340 socket->writeStringList(errlist); 01341 return; 01342 } 01343 // Monitor connections are same as Playback but they don't 01344 // block shutdowns. See the Scheduler event loop for more. 01345 01346 PlaybackSockEventsMode eventsMode = 01347 (PlaybackSockEventsMode)commands[3].toInt(); 01348 LOG(VB_GENERAL, LOG_INFO, QString("MainServer::ANN %1") 01349 .arg(commands[1])); 01350 LOG(VB_GENERAL, LOG_INFO, QString("adding: %1 as a client (events: %2)") 01351 .arg(commands[2]).arg(eventsMode)); 01352 PlaybackSock *pbs = new PlaybackSock(this, socket, commands[2], 01353 eventsMode); 01354 pbs->setBlockShutdown(commands[1] == "Playback"); 01355 01356 sockListLock.lockForWrite(); 01357 playbackList.push_back(pbs); 01358 sockListLock.unlock(); 01359 01360 if (eventsMode != kPBSEvents_None && commands[2] != "tzcheck") 01361 gCoreContext->SendSystemEvent( 01362 QString("CLIENT_CONNECTED HOSTNAME %1").arg(commands[2])); 01363 } 01364 else if (commands[1] == "MediaServer") 01365 { 01366 if (commands.size() < 3) 01367 { 01368 LOG(VB_GENERAL, LOG_ERR, 01369 "Received malformed ANN MediaServer query"); 01370 errlist << "malformed_ann_query"; 01371 socket->writeStringList(errlist); 01372 return; 01373 } 01374 01375 PlaybackSock *pbs = new PlaybackSock(this, socket, commands[2], 01376 kPBSEvents_Normal); 01377 pbs->setAsMediaServer(); 01378 pbs->setBlockShutdown(false); 01379 sockListLock.lockForWrite(); 01380 playbackList.push_back(pbs); 01381 sockListLock.unlock(); 01382 01383 gCoreContext->SendSystemEvent( 01384 QString("CLIENT_CONNECTED HOSTNAME %1").arg(commands[2])); 01385 } 01386 else if (commands[1] == "SlaveBackend") 01387 { 01388 if (commands.size() < 4) 01389 { 01390 LOG(VB_GENERAL, LOG_ERR, QString("Received malformed ANN %1 query") 01391 .arg(commands[1])); 01392 errlist << "malformed_ann_query"; 01393 socket->writeStringList(errlist); 01394 return; 01395 } 01396 01397 LOG(VB_GENERAL, LOG_INFO, 01398 QString("adding: %1 as a slave backend server") 01399 .arg(commands[2])); 01400 PlaybackSock *pbs = new PlaybackSock(this, socket, commands[2], 01401 kPBSEvents_None); 01402 pbs->setAsSlaveBackend(); 01403 pbs->setIP(commands[3]); 01404 01405 if (m_sched) 01406 { 01407 RecordingList slavelist; 01408 QStringList::const_iterator sit = slist.begin()+1; 01409 while (sit != slist.end()) 01410 { 01411 RecordingInfo *recinfo = new RecordingInfo(sit, slist.end()); 01412 if (!recinfo->GetChanID()) 01413 { 01414 delete recinfo; 01415 break; 01416 } 01417 slavelist.push_back(recinfo); 01418 } 01419 m_sched->SlaveConnected(slavelist); 01420 } 01421 01422 bool wasAsleep = true; 01423 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 01424 for (; iter != encoderList->end(); ++iter) 01425 { 01426 EncoderLink *elink = *iter; 01427 if (elink->GetHostName() == commands[2]) 01428 { 01429 if (! (elink->IsWaking() || elink->IsAsleep())) 01430 wasAsleep = false; 01431 elink->SetSocket(pbs); 01432 } 01433 } 01434 01435 if (!wasAsleep && m_sched) 01436 m_sched->ReschedulePlace("SlaveConnected"); 01437 01438 QString message = QString("LOCAL_SLAVE_BACKEND_ONLINE %2") 01439 .arg(commands[2]); 01440 MythEvent me(message); 01441 gCoreContext->dispatch(me); 01442 01443 pbs->setBlockShutdown(false); 01444 01445 sockListLock.lockForWrite(); 01446 playbackList.push_back(pbs); 01447 sockListLock.unlock(); 01448 01449 autoexpireUpdateTimer->start(1000); 01450 01451 gCoreContext->SendSystemEvent( 01452 QString("SLAVE_CONNECTED HOSTNAME %1").arg(commands[2])); 01453 } 01454 else if (commands[1] == "FileTransfer") 01455 { 01456 if (slist.size() < 3) 01457 { 01458 LOG(VB_GENERAL, LOG_ERR, "Received malformed FileTransfer command"); 01459 errlist << "malformed_filetransfer_command"; 01460 socket->writeStringList(errlist); 01461 return; 01462 } 01463 01464 LOG(VB_GENERAL, LOG_INFO, "MainServer::HandleAnnounce FileTransfer"); 01465 LOG(VB_GENERAL, LOG_INFO, 01466 QString("adding: %1 as a remote file transfer") .arg(commands[2])); 01467 QStringList::const_iterator it = slist.begin(); 01468 QUrl qurl = *(++it); 01469 QString wantgroup = *(++it); 01470 QString filename; 01471 QStringList checkfiles; 01472 for (++it; it != slist.end(); ++it) 01473 checkfiles += *it; 01474 01475 FileTransfer *ft = NULL; 01476 bool writemode = false; 01477 bool usereadahead = true; 01478 int timeout_ms = 2000; 01479 if (commands.size() > 3) 01480 writemode = commands[3].toInt(); 01481 01482 if (commands.size() > 4) 01483 usereadahead = commands[4].toInt(); 01484 01485 if (commands.size() > 5) 01486 timeout_ms = commands[5].toInt(); 01487 01488 if (writemode) 01489 { 01490 if (wantgroup.isEmpty()) 01491 wantgroup = "Default"; 01492 01493 StorageGroup sgroup(wantgroup, gCoreContext->GetHostName(), false); 01494 QString dir = sgroup.FindNextDirMostFree(); 01495 if (dir.isEmpty()) 01496 { 01497 LOG(VB_GENERAL, LOG_ERR, "Unable to determine directory " 01498 "to write to in FileTransfer write command"); 01499 errlist << "filetransfer_directory_not_found"; 01500 socket->writeStringList(errlist); 01501 return; 01502 } 01503 01504 QString basename = qurl.path(); 01505 if (qurl.hasFragment()) 01506 basename += "#" + qurl.fragment(); 01507 01508 if (basename.isEmpty()) 01509 { 01510 LOG(VB_GENERAL, LOG_ERR, 01511 QString("FileTransfer write filename is empty in url '%1'.") 01512 .arg(qurl.toString())); 01513 errlist << "filetransfer_filename_empty"; 01514 socket->writeStringList(errlist); 01515 return; 01516 } 01517 01518 if ((basename.contains("/../")) || 01519 (basename.startsWith("../"))) 01520 { 01521 LOG(VB_GENERAL, LOG_ERR, 01522 QString("FileTransfer write filename '%1' does not pass " 01523 "sanity checks.") .arg(basename)); 01524 errlist << "filetransfer_filename_dangerous"; 01525 socket->writeStringList(errlist); 01526 return; 01527 } 01528 01529 filename = dir + "/" + basename; 01530 } 01531 else 01532 filename = LocalFilePath(qurl, wantgroup); 01533 01534 if (filename.isEmpty()) 01535 { 01536 LOG(VB_GENERAL, LOG_ERR, "Empty filename, cowardly aborting!"); 01537 errlist << "filetransfer_filename_empty"; 01538 socket->writeStringList(errlist); 01539 return; 01540 } 01541 01542 01543 QFileInfo finfo(filename); 01544 if (finfo.isDir()) 01545 { 01546 LOG(VB_GENERAL, LOG_ERR, 01547 QString("FileTransfer filename '%1' is actually a directory, " 01548 "cannot transfer.") .arg(filename)); 01549 errlist << "filetransfer_filename_is_a_directory"; 01550 socket->writeStringList(errlist); 01551 return; 01552 } 01553 01554 if (writemode) 01555 { 01556 QString dirPath = finfo.absolutePath(); 01557 QDir qdir(dirPath); 01558 if (!qdir.exists()) 01559 { 01560 if (!qdir.mkpath(dirPath)) 01561 { 01562 LOG(VB_GENERAL, LOG_ERR, 01563 QString("FileTransfer filename '%1' is in a " 01564 "subdirectory which does not exist, and can " 01565 "not be created.") .arg(filename)); 01566 errlist << "filetransfer_unable_to_create_subdirectory"; 01567 socket->writeStringList(errlist); 01568 return; 01569 } 01570 } 01571 ft = new FileTransfer(filename, socket, writemode); 01572 } 01573 else 01574 ft = new FileTransfer(filename, socket, usereadahead, timeout_ms); 01575 01576 sockListLock.lockForWrite(); 01577 fileTransferList.push_back(ft); 01578 sockListLock.unlock(); 01579 01580 retlist << QString::number(socket->socket()); 01581 ft->UpRef(); 01582 retlist << QString::number(ft->GetFileSize()); 01583 ft->DownRef(); 01584 01585 if (checkfiles.size()) 01586 { 01587 QFileInfo fi(filename); 01588 QDir dir = fi.absoluteDir(); 01589 for (it = checkfiles.begin(); it != checkfiles.end(); ++it) 01590 { 01591 if (dir.exists(*it) && 01592 QFileInfo(dir, *it).size() >= kReadTestSize) 01593 { 01594 retlist<<*it; 01595 } 01596 } 01597 } 01598 } 01599 01600 socket->writeStringList(retlist); 01601 } 01602 01608 void MainServer::HandleDone(MythSocket *socket) 01609 { 01610 socket->close(); 01611 } 01612 01613 void MainServer::SendResponse(MythSocket *socket, QStringList &commands) 01614 { 01615 // Note: this method assumes that the playback or filetransfer 01616 // handler has already been uprefed and the socket as well. 01617 01618 // These checks are really just to check if the socket has 01619 // been remotely disconnected while we were working on the 01620 // response. 01621 01622 bool do_write = false; 01623 if (socket) 01624 { 01625 sockListLock.lockForRead(); 01626 do_write = (GetPlaybackBySock(socket) || 01627 GetFileTransferBySock(socket)); 01628 sockListLock.unlock(); 01629 } 01630 01631 if (do_write) 01632 { 01633 socket->writeStringList(commands); 01634 } 01635 else 01636 { 01637 LOG(VB_GENERAL, LOG_ERR, 01638 "SendResponse: Unable to write to client socket, as it's no " 01639 "longer there"); 01640 } 01641 } 01642 01651 void MainServer::HandleQueryRecordings(QString type, PlaybackSock *pbs) 01652 { 01653 MythSocket *pbssock = pbs->getSocket(); 01654 QString playbackhost = pbs->getHostname(); 01655 01656 QMap<QString,ProgramInfo*> recMap; 01657 if (m_sched) 01658 recMap = m_sched->GetRecording(); 01659 01660 QMap<QString,uint32_t> inUseMap = ProgramInfo::QueryInUseMap(); 01661 QMap<QString,bool> isJobRunning = 01662 ProgramInfo::QueryJobsRunning(JOB_COMMFLAG); 01663 01664 int sort = 0; 01665 // Allow "Play" and "Delete" for backwards compatibility with protocol 01666 // version 56 and below. 01667 if ((type == "Ascending") || (type == "Play")) 01668 sort = 1; 01669 else if ((type == "Descending") || (type == "Delete")) 01670 sort = -1; 01671 01672 ProgramList destination; 01673 LoadFromRecorded( 01674 destination, (type == "Recording"), 01675 inUseMap, isJobRunning, recMap, sort); 01676 01677 QMap<QString,ProgramInfo*>::iterator mit = recMap.begin(); 01678 for (; mit != recMap.end(); mit = recMap.erase(mit)) 01679 delete *mit; 01680 01681 QStringList outputlist(QString::number(destination.size())); 01682 QMap<QString, QString> backendIpMap; 01683 QMap<QString, QString> backendPortMap; 01684 QString ip = gCoreContext->GetBackendServerIP(); 01685 QString port = gCoreContext->GetSetting("BackendServerPort"); 01686 01687 ProgramList::iterator it = destination.begin(); 01688 for (it = destination.begin(); it != destination.end(); ++it) 01689 { 01690 ProgramInfo *proginfo = *it; 01691 PlaybackSock *slave = NULL; 01692 01693 if (proginfo->GetHostname() != gCoreContext->GetHostName()) 01694 slave = GetSlaveByHostname(proginfo->GetHostname()); 01695 01696 if ((proginfo->GetHostname() == gCoreContext->GetHostName()) || 01697 (!slave && masterBackendOverride)) 01698 { 01699 proginfo->SetPathname(gCoreContext->GenMythURL(ip,port,proginfo->GetBasename())); 01700 if (!proginfo->GetFilesize()) 01701 { 01702 QString tmpURL = GetPlaybackURL(proginfo); 01703 if (tmpURL.startsWith('/')) 01704 { 01705 QFile checkFile(tmpURL); 01706 if (!tmpURL.isEmpty() && checkFile.exists()) 01707 { 01708 proginfo->SetFilesize(checkFile.size()); 01709 if (proginfo->GetRecordingEndTime() < 01710 QDateTime::currentDateTime()) 01711 { 01712 proginfo->SaveFilesize(proginfo->GetFilesize()); 01713 } 01714 } 01715 } 01716 } 01717 } 01718 else if (!slave) 01719 { 01720 proginfo->SetPathname(GetPlaybackURL(proginfo)); 01721 if (proginfo->GetPathname().isEmpty()) 01722 { 01723 LOG(VB_GENERAL, LOG_ERR, LOC + 01724 QString("HandleQueryRecordings() " 01725 "Couldn't find backend for:\n\t\t\t%1") 01726 .arg(proginfo->toString(ProgramInfo::kTitleSubtitle))); 01727 01728 proginfo->SetFilesize(0); 01729 proginfo->SetPathname("file not found"); 01730 } 01731 } 01732 else 01733 { 01734 if (!proginfo->GetFilesize()) 01735 { 01736 if (!slave->FillProgramInfo(*proginfo, playbackhost)) 01737 { 01738 LOG(VB_GENERAL, LOG_ERR, 01739 "MainServer::HandleQueryRecordings()" 01740 "\n\t\t\tCould not fill program info " 01741 "from backend"); 01742 } 01743 else 01744 { 01745 if (proginfo->GetRecordingEndTime() < 01746 QDateTime::currentDateTime()) 01747 { 01748 proginfo->SaveFilesize(proginfo->GetFilesize()); 01749 } 01750 } 01751 } 01752 else 01753 { 01754 ProgramInfo *p = proginfo; 01755 if (!backendIpMap.contains(p->GetHostname())) 01756 backendIpMap[p->GetHostname()] = 01757 gCoreContext->GetSettingOnHost("BackendServerIp", 01758 p->GetHostname()); 01759 if (!backendPortMap.contains(p->GetHostname())) 01760 backendPortMap[p->GetHostname()] = 01761 gCoreContext->GetSettingOnHost("BackendServerPort", 01762 p->GetHostname()); 01763 p->SetPathname(gCoreContext->GenMythURL(backendIpMap[p->GetHostname()], 01764 backendPortMap[p->GetHostname()], 01765 p->GetBasename())); 01766 } 01767 } 01768 01769 if (slave) 01770 slave->DownRef(); 01771 01772 proginfo->ToStringList(outputlist); 01773 } 01774 01775 SendResponse(pbssock, outputlist); 01776 } 01777 01783 void MainServer::HandleQueryRecording(QStringList &slist, PlaybackSock *pbs) 01784 { 01785 if (slist.size() < 3) 01786 { 01787 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDING query"); 01788 return; 01789 } 01790 01791 MythSocket *pbssock = pbs->getSocket(); 01792 QString command = slist[1].toUpper(); 01793 ProgramInfo *pginfo = NULL; 01794 01795 if (command == "BASENAME") 01796 { 01797 pginfo = new ProgramInfo(slist[2]); 01798 } 01799 else if (command == "TIMESLOT") 01800 { 01801 if (slist.size() < 4) 01802 { 01803 LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDING query"); 01804 return; 01805 } 01806 01807 QDateTime recstartts = myth_dt_from_string(slist[3]); 01808 pginfo = new ProgramInfo(slist[2].toUInt(), recstartts); 01809 } 01810 01811 QStringList strlist; 01812 01813 if (pginfo && pginfo->GetChanID()) 01814 { 01815 strlist << "OK"; 01816 pginfo->ToStringList(strlist); 01817 } 01818 else 01819 { 01820 strlist << "ERROR"; 01821 } 01822 01823 delete pginfo; 01824 01825 SendResponse(pbssock, strlist); 01826 } 01827 01828 void MainServer::HandleFillProgramInfo(QStringList &slist, PlaybackSock *pbs) 01829 { 01830 MythSocket *pbssock = pbs->getSocket(); 01831 01832 QString playbackhost = slist[1]; 01833 01834 QStringList::const_iterator it = slist.begin() + 2; 01835 ProgramInfo pginfo(it, slist.end()); 01836 01837 if (pginfo.HasPathname()) 01838 { 01839 QString lpath = GetPlaybackURL(&pginfo); 01840 QString ip = gCoreContext->GetBackendServerIP(); 01841 QString port = gCoreContext->GetSetting("BackendServerPort"); 01842 01843 if (playbackhost == gCoreContext->GetHostName()) 01844 pginfo.SetPathname(lpath); 01845 else 01846 pginfo.SetPathname(gCoreContext->GenMythURL(ip,port,pginfo.GetBasename())); 01847 01848 const QFileInfo info(lpath); 01849 pginfo.SetFilesize(info.size()); 01850 } 01851 01852 QStringList strlist; 01853 01854 pginfo.ToStringList(strlist); 01855 01856 SendResponse(pbssock, strlist); 01857 } 01858 01859 01860 void DeleteThread::run(void) 01861 { 01862 if (m_ms) 01863 m_ms->DoDeleteThread(this); 01864 } 01865 01866 void MainServer::DoDeleteThread(DeleteStruct *ds) 01867 { 01868 // sleep a little to let frontends reload the recordings list 01869 // after deleting a recording, then we can hammer the DB and filesystem 01870 sleep(3); 01871 usleep(random()%2000); 01872 01873 deletelock.lock(); 01874 01875 QString logInfo = QString("chanid %1 at %2") 01876 .arg(ds->m_chanid) 01877 .arg(ds->m_recstartts.toString()); 01878 01879 QString name = QString("deleteThread%1%2").arg(getpid()).arg(random()); 01880 QFile checkFile(ds->m_filename); 01881 01882 if (!MSqlQuery::testDBConnection()) 01883 { 01884 QString msg = QString("ERROR opening database connection for Delete " 01885 "Thread for chanid %1 recorded at %2. Program " 01886 "will NOT be deleted.") 01887 .arg(ds->m_chanid) 01888 .arg(ds->m_recstartts.toString()); 01889 LOG(VB_GENERAL, LOG_ERR, msg); 01890 01891 deletelock.unlock(); 01892 return; 01893 } 01894 01895 ProgramInfo pginfo(ds->m_chanid, ds->m_recstartts); 01896 01897 if (!pginfo.GetChanID()) 01898 { 01899 QString msg = QString("ERROR retrieving program info when trying to " 01900 "delete program for chanid %1 recorded at %2. " 01901 "Recording will NOT be deleted.") 01902 .arg(ds->m_chanid) 01903 .arg(ds->m_recstartts.toString()); 01904 LOG(VB_GENERAL, LOG_ERR, msg); 01905 01906 deletelock.unlock(); 01907 return; 01908 } 01909 01910 // Don't allow deleting files where filesize != 0 and we can't find 01911 // the file, unless forceMetadataDelete has been set. This allows 01912 // deleting failed recordings without fuss, but blocks accidental 01913 // deletion of metadata for files where the filesystem has gone missing. 01914 if ((!checkFile.exists()) && pginfo.GetFilesize() && 01915 (!ds->m_forceMetadataDelete)) 01916 { 01917 LOG(VB_GENERAL, LOG_ERR, 01918 QString("ERROR when trying to delete file: %1. File " 01919 "doesn't exist. Database metadata will not be removed.") 01920 .arg(ds->m_filename)); 01921 01922 pginfo.SaveDeletePendingFlag(false); 01923 deletelock.unlock(); 01924 return; 01925 } 01926 01927 JobQueue::DeleteAllJobs(ds->m_chanid, ds->m_recstartts); 01928 01929 LiveTVChain *tvchain = GetChainWithRecording(pginfo); 01930 if (tvchain) 01931 tvchain->DeleteProgram(&pginfo); 01932 01933 bool followLinks = gCoreContext->GetNumSetting("DeletesFollowLinks", 0); 01934 bool slowDeletes = gCoreContext->GetNumSetting("TruncateDeletesSlowly", 0); 01935 int fd = -1; 01936 off_t size = 0; 01937 bool errmsg = false; 01938 01939 /* Delete recording. */ 01940 if (slowDeletes) 01941 { 01942 // Since stat fails after unlinking on some filesystems, 01943 // get the filesize first 01944 const QFileInfo info(ds->m_filename); 01945 size = info.size(); 01946 fd = DeleteFile(ds->m_filename, followLinks, ds->m_forceMetadataDelete); 01947 01948 if ((fd < 0) && checkFile.exists()) 01949 errmsg = true; 01950 } 01951 else 01952 { 01953 delete_file_immediately(ds->m_filename, followLinks, false); 01954 sleep(2); 01955 if (checkFile.exists()) 01956 errmsg = true; 01957 } 01958 01959 if (errmsg) 01960 { 01961 LOG(VB_GENERAL, LOG_ERR, 01962 QString("Error deleting file: %1. Keeping metadata in database.") 01963 .arg(ds->m_filename)); 01964 01965 pginfo.SaveDeletePendingFlag(false); 01966 deletelock.unlock(); 01967 return; 01968 } 01969 01970 /* Delete all preview thumbnails. */ 01971 01972 QFileInfo fInfo( ds->m_filename ); 01973 QString nameFilter = fInfo.fileName() + "*.png"; 01974 // QDir's nameFilter uses spaces or semicolons to separate globs, 01975 // so replace them with the "match any character" wildcard 01976 // since mythrename.pl may have included them in filenames 01977 nameFilter.replace(QRegExp("( |;)"), "?"); 01978 QDir dir ( fInfo.path(), nameFilter ); 01979 01980 for (uint nIdx = 0; nIdx < dir.count(); nIdx++) 01981 { 01982 QString sFileName = QString( "%1/%2" ) 01983 .arg( fInfo.path() ) 01984 .arg( dir[ nIdx ] ); 01985 01986 delete_file_immediately( sFileName, followLinks, true); 01987 } 01988 01989 DeleteRecordedFiles(ds); 01990 01991 DoDeleteInDB(ds); 01992 01993 deletelock.unlock(); 01994 01995 if (slowDeletes && fd >= 0) 01996 TruncateAndClose(&pginfo, fd, ds->m_filename, size); 01997 } 01998 01999 void MainServer::DeleteRecordedFiles(DeleteStruct *ds) 02000 { 02001 QString logInfo = QString("chanid %1 at %2") 02002 .arg(ds->m_chanid).arg(ds->m_recstartts.toString()); 02003 02004 MSqlQuery update(MSqlQuery::InitCon()); 02005 MSqlQuery query(MSqlQuery::InitCon()); 02006 query.prepare("SELECT basename, hostname, storagegroup FROM recordedfile " 02007 "WHERE chanid = :CHANID AND starttime = :STARTTIME;"); 02008 query.bindValue(":CHANID", ds->m_chanid); 02009 query.bindValue(":STARTTIME", ds->m_recstartts); 02010 02011 if (!query.exec() || !query.isActive()) 02012 { 02013 MythDB::DBError("RecordedFiles deletion", query); 02014 LOG(VB_GENERAL, LOG_ERR, 02015 QString("Error querying recordedfiles for %1.") .arg(logInfo)); 02016 } 02017 02018 QString basename; 02019 QString hostname; 02020 QString storagegroup; 02021 bool deleteInDB; 02022 while (query.next()) 02023 { 02024 basename = query.value(0).toString(); 02025 hostname = query.value(1).toString(); 02026 storagegroup = query.value(2).toString(); 02027 deleteInDB = false; 02028 02029 if (basename == ds->m_filename) 02030 deleteInDB = true; 02031 else 02032 { 02033 LOG(VB_FILE, LOG_INFO, 02034 QString("DeleteRecordedFiles(%1), deleting '%2'") 02035 .arg(logInfo).arg(query.value(0).toString())); 02036 02037 StorageGroup sgroup(storagegroup); 02038 QString localFile = sgroup.FindFile(basename); 02039 02040 QString url = gCoreContext->GenMythURL( 02041 gCoreContext->GetBackendServerIP(hostname), 02042 gCoreContext->GetSettingOnHost("BackendServerPort", hostname), 02043 basename, 02044 storagegroup); 02045 02046 if ((((hostname == gCoreContext->GetHostName()) || 02047 (!localFile.isEmpty())) && 02048 (HandleDeleteFile(basename, storagegroup))) || 02049 (((hostname != gCoreContext->GetHostName()) || 02050 (localFile.isEmpty())) && 02051 (RemoteFile::DeleteFile(url)))) 02052 { 02053 deleteInDB = true; 02054 } 02055 } 02056 02057 if (deleteInDB) 02058 { 02059 update.prepare("DELETE FROM recordedfile " 02060 "WHERE chanid = :CHANID " 02061 "AND starttime = :STARTTIME " 02062 "AND basename = :BASENAME ;"); 02063 update.bindValue(":CHANID", ds->m_chanid); 02064 update.bindValue(":STARTTIME", ds->m_recstartts); 02065 update.bindValue(":BASENAME", basename); 02066 if (!update.exec()) 02067 { 02068 MythDB::DBError("RecordedFiles deletion", update); 02069 LOG(VB_GENERAL, LOG_ERR, 02070 QString("Error querying recordedfile (%1) for %2.") 02071 .arg(query.value(1).toString()) 02072 .arg(logInfo)); 02073 } 02074 } 02075 } 02076 } 02077 02078 void MainServer::DoDeleteInDB(DeleteStruct *ds) 02079 { 02080 QString logInfo = QString("chanid %1 at %2") 02081 .arg(ds->m_chanid).arg(ds->m_recstartts.toString()); 02082 02083 MSqlQuery query(MSqlQuery::InitCon()); 02084 query.prepare("DELETE FROM recorded WHERE chanid = :CHANID AND " 02085 "title = :TITLE AND starttime = :STARTTIME;"); 02086 query.bindValue(":CHANID", ds->m_chanid); 02087 query.bindValue(":TITLE", ds->m_title); 02088 query.bindValue(":STARTTIME", ds->m_recstartts); 02089 02090 if (!query.exec() || !query.isActive()) 02091 { 02092 MythDB::DBError("Recorded program deletion", query); 02093 LOG(VB_GENERAL, LOG_ERR, 02094 QString("Error deleting recorded entry for %1.") .arg(logInfo)); 02095 } 02096 02097 sleep(1); 02098 02099 // Notify the frontend so it can requery for Free Space 02100 QString msg = QString("RECORDING_LIST_CHANGE DELETE %1 %2") 02101 .arg(ds->m_chanid).arg(ds->m_recstartts.toString(Qt::ISODate)); 02102 gCoreContext->SendEvent(MythEvent(msg)); 02103 02104 // sleep a little to let frontends reload the recordings list 02105 sleep(3); 02106 02107 query.prepare("DELETE FROM recordedmarkup " 02108 "WHERE chanid = :CHANID AND starttime = :STARTTIME;"); 02109 query.bindValue(":CHANID", ds->m_chanid); 02110 query.bindValue(":STARTTIME", ds->m_recstartts); 02111 02112 if (!query.exec()) 02113 { 02114 MythDB::DBError("Recorded program delete recordedmarkup", query); 02115 LOG(VB_GENERAL, LOG_ERR, 02116 QString("Error deleting recordedmarkup for %1.") .arg(logInfo)); 02117 } 02118 02119 query.prepare("DELETE FROM recordedseek " 02120 "WHERE chanid = :CHANID AND starttime = :STARTTIME;"); 02121 query.bindValue(":CHANID", ds->m_chanid); 02122 query.bindValue(":STARTTIME", ds->m_recstartts); 02123 02124 if (!query.exec()) 02125 { 02126 MythDB::DBError("Recorded program delete recordedseek", query); 02127 LOG(VB_GENERAL, LOG_ERR, QString("Error deleting recordedseek for %1.") 02128 .arg(logInfo)); 02129 } 02130 } 02131 02141 int MainServer::DeleteFile(const QString &filename, bool followLinks, 02142 bool deleteBrokenSymlinks) 02143 { 02144 QFileInfo finfo(filename); 02145 int fd = -1, err = 0; 02146 QString linktext = ""; 02147 QByteArray fname = filename.toLocal8Bit(); 02148 02149 LOG(VB_FILE, LOG_INFO, QString("About to unlink/delete file: '%1'") 02150 .arg(fname.constData())); 02151 02152 QString errmsg = QString("Delete Error '%1'").arg(fname.constData()); 02153 if (finfo.isSymLink()) 02154 { 02155 linktext = getSymlinkTarget(filename); 02156 QByteArray alink = linktext.toLocal8Bit(); 02157 errmsg += QString(" -> '%2'").arg(alink.constData()); 02158 } 02159 02160 if (followLinks && finfo.isSymLink()) 02161 { 02162 if (!finfo.exists() && deleteBrokenSymlinks) 02163 err = unlink(fname.constData()); 02164 else 02165 { 02166 fd = OpenAndUnlink(linktext); 02167 if (fd >= 0) 02168 err = unlink(fname.constData()); 02169 } 02170 } 02171 else if (!finfo.isSymLink()) 02172 { 02173 fd = OpenAndUnlink(filename); 02174 } 02175 else // just delete symlinks immediately 02176 { 02177 err = unlink(fname.constData()); 02178 if (err == 0) 02179 return -2; // valid result, not an error condition 02180 } 02181 02182 if (fd < 0) 02183 LOG(VB_GENERAL, LOG_ERR, errmsg + ENO); 02184 02185 return fd; 02186 } 02187 02197 int MainServer::OpenAndUnlink(const QString &filename) 02198 { 02199 QByteArray fname = filename.toLocal8Bit(); 02200 QString msg = QString("Error deleting '%1'").arg(fname.constData()); 02201 int fd = open(fname.constData(), O_WRONLY); 02202 02203 if (fd == -1) 02204 { 02205 LOG(VB_GENERAL, LOG_ERR, msg + " could not open " + ENO); 02206 return -1; 02207 } 02208 02209 if (unlink(fname.constData())) 02210 { 02211 LOG(VB_GENERAL, LOG_ERR, msg + " could not unlink " + ENO); 02212 close(fd); 02213 return -1; 02214 } 02215 02216 return fd; 02217 } 02218 02227 bool MainServer::TruncateAndClose(ProgramInfo *pginfo, int fd, 02228 const QString &filename, off_t fsize) 02229 { 02230 QMutexLocker locker(&truncate_and_close_lock); 02231 02232 if (pginfo) 02233 { 02234 pginfo->SetPathname(filename); 02235 pginfo->MarkAsInUse(true, kTruncatingDeleteInUseID); 02236 } 02237 02238 int cards = 5; 02239 02240 MSqlQuery query(MSqlQuery::InitCon()); 02241 query.prepare("SELECT COUNT(cardid) FROM capturecard;"); 02242 if (query.exec() && query.next()) 02243 cards = query.value(0).toInt(); 02244 02245 // Time between truncation steps in milliseconds 02246 const size_t sleep_time = 500; 02247 const size_t min_tps = 8 * 1024 * 1024; 02248 const size_t calc_tps = (size_t) (cards * 1.2 * (22200000LL / 8)); 02249 const size_t tps = max(min_tps, calc_tps); 02250 const size_t increment = (size_t) (tps * (sleep_time * 0.001f)); 02251 02252 LOG(VB_FILE, LOG_INFO, 02253 QString("Truncating '%1' by %2 MB every %3 milliseconds") 02254 .arg(filename) 02255 .arg(increment / (1024.0 * 1024.0), 0, 'f', 2) 02256 .arg(sleep_time)); 02257 02258 int count = 0; 02259 while (fsize > 0) 02260 { 02261 #if 0 02262 LOG(VB_FILE, LOG_DEBUG, QString("Truncating '%1' to %2 MB") 02263 .arg(filename).arg(fsize / (1024.0 * 1024.0), 0, 'f', 2)); 02264 #endif 02265 02266 int err = ftruncate(fd, fsize); 02267 if (err) 02268 { 02269 LOG(VB_GENERAL, LOG_ERR, QString("Error truncating '%1'") 02270 .arg(filename) + ENO); 02271 if (pginfo) 02272 pginfo->MarkAsInUse(false, kTruncatingDeleteInUseID); 02273 return 0 == close(fd); 02274 } 02275 02276 fsize -= increment; 02277 02278 if (pginfo && ((count % 100) == 0)) 02279 pginfo->UpdateInUseMark(true); 02280 02281 count++; 02282 02283 usleep(sleep_time * 1000); 02284 } 02285 02286 bool ok = (0 == close(fd)); 02287 02288 if (pginfo) 02289 pginfo->MarkAsInUse(false, kTruncatingDeleteInUseID); 02290 02291 LOG(VB_FILE, LOG_INFO, QString("Finished truncating '%1'").arg(filename)); 02292 02293 return ok; 02294 } 02295 02296 void MainServer::HandleCheckRecordingActive(QStringList &slist, 02297 PlaybackSock *pbs) 02298 { 02299 MythSocket *pbssock = NULL; 02300 if (pbs) 02301 pbssock = pbs->getSocket(); 02302 02303 QStringList::const_iterator it = slist.begin() + 1; 02304 ProgramInfo pginfo(it, slist.end()); 02305 02306 int result = 0; 02307 02308 if (ismaster && pginfo.GetHostname() != gCoreContext->GetHostName()) 02309 { 02310 PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname()); 02311 if (slave) 02312 { 02313 result = slave->CheckRecordingActive(&pginfo); 02314 slave->DownRef(); 02315 } 02316 } 02317 else 02318 { 02319 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 02320 for (; iter != encoderList->end(); ++iter) 02321 { 02322 EncoderLink *elink = *iter; 02323 02324 if (elink->IsLocal() && elink->MatchesRecording(&pginfo)) 02325 result = iter.key(); 02326 } 02327 } 02328 02329 QStringList outputlist( QString::number(result) ); 02330 if (pbssock) 02331 SendResponse(pbssock, outputlist); 02332 02333 return; 02334 } 02335 02336 void MainServer::HandleStopRecording(QStringList &slist, PlaybackSock *pbs) 02337 { 02338 QStringList::const_iterator it = slist.begin() + 1; 02339 RecordingInfo recinfo(it, slist.end()); 02340 if (recinfo.GetChanID()) 02341 DoHandleStopRecording(recinfo, pbs); 02342 } 02343 02344 void MainServer::DoHandleStopRecording( 02345 RecordingInfo &recinfo, PlaybackSock *pbs) 02346 { 02347 MythSocket *pbssock = NULL; 02348 if (pbs) 02349 pbssock = pbs->getSocket(); 02350 02351 // FIXME! We don't know what state the recorder is in at this 02352 // time. Simply set the recstatus to rsUnknown and let the 02353 // scheduler do the best it can with it. The proper long term fix 02354 // is probably to have the recorder return the actual recstatus as 02355 // part of the stop recording response. That's a more involved 02356 // change than I care to make during the 0.25 code freeze. 02357 recinfo.SetRecordingStatus(rsUnknown); 02358 02359 if (ismaster && recinfo.GetHostname() != gCoreContext->GetHostName()) 02360 { 02361 PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname()); 02362 02363 if (slave) 02364 { 02365 int num = slave->StopRecording(&recinfo); 02366 02367 if (num > 0) 02368 { 02369 (*encoderList)[num]->StopRecording(); 02370 if (m_sched) 02371 m_sched->UpdateRecStatus(&recinfo); 02372 } 02373 if (pbssock) 02374 { 02375 QStringList outputlist( "0" ); 02376 SendResponse(pbssock, outputlist); 02377 } 02378 02379 slave->DownRef(); 02380 return; 02381 } 02382 else 02383 { 02384 // If the slave is unreachable, we can assume that the 02385 // recording has stopped and the status should be updated. 02386 // Continue so that the master can try to update the endtime 02387 // of the file is in a shared directory. 02388 if (m_sched) 02389 m_sched->UpdateRecStatus(&recinfo); 02390 } 02391 02392 } 02393 02394 int recnum = -1; 02395 02396 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 02397 for (; iter != encoderList->end(); ++iter) 02398 { 02399 EncoderLink *elink = *iter; 02400 02401 if (elink->IsLocal() && elink->MatchesRecording(&recinfo)) 02402 { 02403 recnum = iter.key(); 02404 02405 elink->StopRecording(); 02406 02407 while (elink->IsBusyRecording() || 02408 elink->GetState() == kState_ChangingState) 02409 { 02410 usleep(100); 02411 } 02412 02413 if (ismaster) 02414 { 02415 if (m_sched) 02416 m_sched->UpdateRecStatus(&recinfo); 02417 } 02418 } 02419 } 02420 02421 if (pbssock) 02422 { 02423 QStringList outputlist( QString::number(recnum) ); 02424 SendResponse(pbssock, outputlist); 02425 } 02426 } 02427 02428 void MainServer::HandleDeleteRecording(QString &chanid, QString &starttime, 02429 PlaybackSock *pbs, 02430 bool forceMetadataDelete, 02431 bool forgetHistory) 02432 { 02433 QDateTime recstartts = myth_dt_from_string(starttime); 02434 RecordingInfo recinfo(chanid.toUInt(), recstartts); 02435 02436 if (!recinfo.GetChanID()) 02437 { 02438 MythSocket *pbssock = NULL; 02439 if (pbs) 02440 pbssock = pbs->getSocket(); 02441 02442 QStringList outputlist( QString::number(0) ); 02443 02444 SendResponse(pbssock, outputlist); 02445 return; 02446 } 02447 02448 DoHandleDeleteRecording(recinfo, pbs, forceMetadataDelete, false, forgetHistory); 02449 } 02450 02451 void MainServer::HandleDeleteRecording(QStringList &slist, PlaybackSock *pbs, 02452 bool forceMetadataDelete) 02453 { 02454 QStringList::const_iterator it = slist.begin() + 1; 02455 RecordingInfo recinfo(it, slist.end()); 02456 if (recinfo.GetChanID()) 02457 DoHandleDeleteRecording(recinfo, pbs, forceMetadataDelete, false, false); 02458 } 02459 02460 void MainServer::DoHandleDeleteRecording( 02461 RecordingInfo &recinfo, PlaybackSock *pbs, 02462 bool forceMetadataDelete, bool expirer, bool forgetHistory) 02463 { 02464 int resultCode = -1; 02465 MythSocket *pbssock = NULL; 02466 if (pbs) 02467 pbssock = pbs->getSocket(); 02468 02469 bool justexpire = expirer ? false : 02470 ( //gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete") && 02471 (recinfo.GetRecordingGroup() != "Deleted") && 02472 (recinfo.GetRecordingGroup() != "LiveTV")); 02473 02474 QString filename = GetPlaybackURL(&recinfo, false); 02475 if (filename.isEmpty()) 02476 { 02477 LOG(VB_GENERAL, LOG_ERR, 02478 QString("ERROR when trying to delete file for %1. Unable " 02479 "to determine filename of recording.") 02480 .arg(recinfo.toString(ProgramInfo::kRecordingKey))); 02481 02482 if (pbssock) 02483 { 02484 resultCode = -2; 02485 QStringList outputlist(QString::number(resultCode)); 02486 SendResponse(pbssock, outputlist); 02487 } 02488 02489 return; 02490 } 02491 02492 // Stop the recording if it's still in progress. 02493 DoHandleStopRecording(recinfo, NULL); 02494 02495 if (justexpire && !forceMetadataDelete && 02496 recinfo.GetFilesize() > (1024 * 1024) ) 02497 { 02498 recinfo.ApplyRecordRecGroupChange("Deleted"); 02499 recinfo.SaveAutoExpire(kDeletedAutoExpire, true); 02500 if (forgetHistory) 02501 recinfo.ForgetHistory(); 02502 else if (m_sched) 02503 m_sched->RescheduleCheck(recinfo, "DoHandleDelete1"); 02504 QStringList outputlist( QString::number(0) ); 02505 SendResponse(pbssock, outputlist); 02506 return; 02507 } 02508 02509 // If this recording was made by a another recorder, and that 02510 // recorder is available, tell it to do the deletion. 02511 if (ismaster && recinfo.GetHostname() != gCoreContext->GetHostName()) 02512 { 02513 PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname()); 02514 02515 if (slave) 02516 { 02517 int num = slave->DeleteRecording(&recinfo, forceMetadataDelete); 02518 02519 if (forgetHistory) 02520 recinfo.ForgetHistory(); 02521 else if (m_sched && 02522 recinfo.GetRecordingGroup() != "Deleted" && 02523 recinfo.GetRecordingGroup() != "LiveTV") 02524 m_sched->RescheduleCheck(recinfo, "DoHandleDelete2"); 02525 02526 if (pbssock) 02527 { 02528 QStringList outputlist( QString::number(num) ); 02529 SendResponse(pbssock, outputlist); 02530 } 02531 02532 slave->DownRef(); 02533 return; 02534 } 02535 } 02536 02537 QFile checkFile(filename); 02538 bool fileExists = checkFile.exists(); 02539 if (!fileExists) 02540 { 02541 QFile checkFileUTF8(QString::fromUtf8(filename.toAscii().constData())); 02542 fileExists = checkFileUTF8.exists(); 02543 if (fileExists) 02544 filename = QString::fromUtf8(filename.toAscii().constData()); 02545 } 02546 02547 // Allow deleting of files where the recording failed meaning size == 0 02548 // But do not allow deleting of files that appear to be completely absent. 02549 // The latter condition indicates the filesystem containing the file is 02550 // most likely absent and deleting the file metadata is unsafe. 02551 if (fileExists || !recinfo.GetFilesize() || forceMetadataDelete) 02552 { 02553 recinfo.SaveDeletePendingFlag(true); 02554 02555 DeleteThread *deleteThread = new DeleteThread(this, filename, 02556 recinfo.GetTitle(), recinfo.GetChanID(), 02557 recinfo.GetRecordingStartTime(), recinfo.GetRecordingEndTime(), 02558 forceMetadataDelete); 02559 deleteThread->start(); 02560 } 02561 else 02562 { 02563 QString logInfo = QString("chanid %1") 02564 .arg(recinfo.toString(ProgramInfo::kRecordingKey)); 02565 02566 LOG(VB_GENERAL, LOG_ERR, 02567 QString("ERROR when trying to delete file: %1. File doesn't " 02568 "exist. Database metadata will not be removed.") 02569 .arg(filename)); 02570 resultCode = -2; 02571 } 02572 02573 if (pbssock) 02574 { 02575 QStringList outputlist( QString::number(resultCode) ); 02576 SendResponse(pbssock, outputlist); 02577 } 02578 02579 if (forgetHistory) 02580 recinfo.ForgetHistory(); 02581 else if (m_sched && 02582 recinfo.GetRecordingGroup() != "Deleted" && 02583 recinfo.GetRecordingGroup() != "LiveTV") 02584 m_sched->RescheduleCheck(recinfo, "DoHandleDelete3"); 02585 02586 // Tell MythTV frontends that the recording list needs to be updated. 02587 if (fileExists || !recinfo.GetFilesize() || forceMetadataDelete) 02588 { 02589 gCoreContext->SendSystemEvent( 02590 QString("REC_DELETED CHANID %1 STARTTIME %2") 02591 .arg(recinfo.GetChanID()) 02592 .arg(recinfo.GetRecordingStartTime(ISODate))); 02593 02594 recinfo.SendDeletedEvent(); 02595 } 02596 } 02597 02598 void MainServer::HandleUndeleteRecording(QStringList &slist, PlaybackSock *pbs) 02599 { 02600 if (slist.size() == 3) 02601 { 02602 RecordingInfo recinfo( 02603 slist[1].toUInt(), QDateTime::fromString(slist[2], Qt::ISODate)); 02604 if (recinfo.GetChanID()) 02605 DoHandleUndeleteRecording(recinfo, pbs); 02606 } 02607 else if (slist.size() >= (1 + NUMPROGRAMLINES)) 02608 { 02609 QStringList::const_iterator it = slist.begin()+1; 02610 RecordingInfo recinfo(it, slist.end()); 02611 if (recinfo.GetChanID()) 02612 DoHandleUndeleteRecording(recinfo, pbs); 02613 } 02614 } 02615 02616 void MainServer::DoHandleUndeleteRecording( 02617 RecordingInfo &recinfo, PlaybackSock *pbs) 02618 { 02619 int ret = -1; 02620 02621 MythSocket *pbssock = NULL; 02622 if (pbs) 02623 pbssock = pbs->getSocket(); 02624 02625 #if 0 02626 if (gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0)) 02627 { 02628 #endif 02629 recinfo.ApplyRecordRecGroupChange("Default"); 02630 recinfo.UpdateLastDelete(false); 02631 recinfo.SaveAutoExpire(kDisableAutoExpire); 02632 ret = 0; 02633 #if 0 02634 } 02635 #endif 02636 02637 QStringList outputlist( QString::number(ret) ); 02638 SendResponse(pbssock, outputlist); 02639 } 02640 02641 void MainServer::HandleRescheduleRecordings(const QStringList &request, 02642 PlaybackSock *pbs) 02643 { 02644 QStringList result; 02645 if (m_sched) 02646 { 02647 m_sched->Reschedule(request); 02648 result = QStringList( QString::number(1) ); 02649 } 02650 else 02651 result = QStringList( QString::number(0) ); 02652 02653 if (pbs) 02654 { 02655 MythSocket *pbssock = pbs->getSocket(); 02656 if (pbssock) 02657 SendResponse(pbssock, result); 02658 } 02659 } 02660 02661 void MainServer::HandleForgetRecording(QStringList &slist, PlaybackSock *pbs) 02662 { 02663 QStringList::const_iterator it = slist.begin() + 1; 02664 RecordingInfo recinfo(it, slist.end()); 02665 if (recinfo.GetChanID()) 02666 recinfo.ForgetHistory(); 02667 02668 MythSocket *pbssock = NULL; 02669 if (pbs) 02670 pbssock = pbs->getSocket(); 02671 if (pbssock) 02672 { 02673 QStringList outputlist( QString::number(0) ); 02674 SendResponse(pbssock, outputlist); 02675 } 02676 } 02677 02678 /* 02679 * \addtogroup myth_network_protocol 02680 * \par GO_TO_SLEEP 02681 * Commands a slave to go to sleep 02682 */ 02683 void MainServer::HandleGoToSleep(PlaybackSock *pbs) 02684 { 02685 QStringList strlist; 02686 02687 QString sleepCmd = gCoreContext->GetSetting("SleepCommand"); 02688 if (!sleepCmd.isEmpty()) 02689 { 02690 strlist << "OK"; 02691 SendResponse(pbs->getSocket(), strlist); 02692 LOG(VB_GENERAL, LOG_NOTICE, 02693 "Received GO_TO_SLEEP command from master, running SleepCommand."); 02694 myth_system(sleepCmd); 02695 } 02696 else 02697 { 02698 strlist << "ERROR: SleepCommand is empty"; 02699 LOG(VB_GENERAL, LOG_ERR, 02700 "ERROR: in HandleGoToSleep(), but no SleepCommand found!"); 02701 SendResponse(pbs->getSocket(), strlist); 02702 } 02703 } 02704 02714 void MainServer::HandleQueryFreeSpace(PlaybackSock *pbs, bool allHosts) 02715 { 02716 QStringList strlist; 02717 02718 if (allHosts) 02719 { 02720 QMutexLocker locker(&masterFreeSpaceListLock); 02721 strlist = masterFreeSpaceList; 02722 if (!masterFreeSpaceListUpdater || 02723 !masterFreeSpaceListUpdater->KeepRunning(true)) 02724 { 02725 while (masterFreeSpaceListUpdater) 02726 { 02727 masterFreeSpaceListUpdater->KeepRunning(false); 02728 masterFreeSpaceListWait.wait(locker.mutex()); 02729 } 02730 masterFreeSpaceListUpdater = new FreeSpaceUpdater(*this); 02731 MThreadPool::globalInstance()->startReserved( 02732 masterFreeSpaceListUpdater, "FreeSpaceUpdater"); 02733 } 02734 } 02735 else 02736 BackendQueryDiskSpace(strlist, allHosts, allHosts); 02737 02738 SendResponse(pbs->getSocket(), strlist); 02739 } 02740 02746 void MainServer::HandleQueryFreeSpaceSummary(PlaybackSock *pbs) 02747 { 02748 QStringList strlist; 02749 { 02750 QMutexLocker locker(&masterFreeSpaceListLock); 02751 strlist = masterFreeSpaceList; 02752 if (!masterFreeSpaceListUpdater || 02753 !masterFreeSpaceListUpdater->KeepRunning(true)) 02754 { 02755 while (masterFreeSpaceListUpdater) 02756 { 02757 masterFreeSpaceListUpdater->KeepRunning(false); 02758 masterFreeSpaceListWait.wait(locker.mutex()); 02759 } 02760 masterFreeSpaceListUpdater = new FreeSpaceUpdater(*this); 02761 MThreadPool::globalInstance()->startReserved( 02762 masterFreeSpaceListUpdater, "FreeSpaceUpdater"); 02763 } 02764 } 02765 02766 // The TotalKB and UsedKB are the last two numbers encoded in the list 02767 QStringList shortlist; 02768 if (strlist.size() < 4) 02769 { 02770 shortlist << QString("0"); 02771 shortlist << QString("0"); 02772 } 02773 else 02774 { 02775 unsigned int index = (uint)(strlist.size()) - 2; 02776 shortlist << strlist[index++]; 02777 shortlist << strlist[index++]; 02778 } 02779 02780 SendResponse(pbs->getSocket(), shortlist); 02781 } 02782 02789 void MainServer::HandleQueryLoad(PlaybackSock *pbs) 02790 { 02791 MythSocket *pbssock = pbs->getSocket(); 02792 02793 QStringList strlist; 02794 02795 double loads[3]; 02796 if (getloadavg(loads,3) == -1) 02797 { 02798 strlist << "ERROR"; 02799 strlist << "getloadavg() failed"; 02800 } 02801 else 02802 strlist << QString::number(loads[0]) 02803 << QString::number(loads[1]) 02804 << QString::number(loads[2]); 02805 02806 SendResponse(pbssock, strlist); 02807 } 02808 02814 void MainServer::HandleQueryUptime(PlaybackSock *pbs) 02815 { 02816 MythSocket *pbssock = pbs->getSocket(); 02817 QStringList strlist; 02818 time_t uptime; 02819 02820 if (getUptime(uptime)) 02821 strlist << QString::number(uptime); 02822 else 02823 { 02824 strlist << "ERROR"; 02825 strlist << "Could not determine uptime."; 02826 } 02827 02828 SendResponse(pbssock, strlist); 02829 } 02830 02836 void MainServer::HandleQueryHostname(PlaybackSock *pbs) 02837 { 02838 MythSocket *pbssock = pbs->getSocket(); 02839 QStringList strlist; 02840 02841 strlist << gCoreContext->GetHostName(); 02842 02843 SendResponse(pbssock, strlist); 02844 } 02845 02851 void MainServer::HandleQueryMemStats(PlaybackSock *pbs) 02852 { 02853 MythSocket *pbssock = pbs->getSocket(); 02854 QStringList strlist; 02855 int totalMB, freeMB, totalVM, freeVM; 02856 02857 if (getMemStats(totalMB, freeMB, totalVM, freeVM)) 02858 strlist << QString::number(totalMB) << QString::number(freeMB) 02859 << QString::number(totalVM) << QString::number(freeVM); 02860 else 02861 { 02862 strlist << "ERROR"; 02863 strlist << "Could not determine memory stats."; 02864 } 02865 02866 SendResponse(pbssock, strlist); 02867 } 02868 02874 void MainServer::HandleQueryTimeZone(PlaybackSock *pbs) 02875 { 02876 MythSocket *pbssock = pbs->getSocket(); 02877 QStringList strlist; 02878 strlist << getTimeZoneID() << QString::number(calc_utc_offset()) 02879 << mythCurrentDateTime().toString(Qt::ISODate); 02880 02881 SendResponse(pbssock, strlist); 02882 } 02883 02888 void MainServer::HandleQueryCheckFile(QStringList &slist, PlaybackSock *pbs) 02889 { 02890 MythSocket *pbssock = pbs->getSocket(); 02891 bool checkSlaves = slist[1].toInt(); 02892 02893 QStringList::const_iterator it = slist.begin() + 2; 02894 RecordingInfo recinfo(it, slist.end()); 02895 02896 int exists = 0; 02897 02898 if (recinfo.HasPathname() && (ismaster) && 02899 (recinfo.GetHostname() != gCoreContext->GetHostName()) && 02900 (checkSlaves)) 02901 { 02902 PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname()); 02903 02904 if (slave) 02905 { 02906 exists = slave->CheckFile(&recinfo); 02907 slave->DownRef(); 02908 02909 QStringList outputlist( QString::number(exists) ); 02910 if (exists) 02911 outputlist << recinfo.GetPathname(); 02912 else 02913 outputlist << ""; 02914 02915 SendResponse(pbssock, outputlist); 02916 return; 02917 } 02918 } 02919 02920 QString pburl; 02921 if (recinfo.HasPathname()) 02922 { 02923 pburl = GetPlaybackURL(&recinfo); 02924 exists = QFileInfo(pburl).exists(); 02925 if (!exists) 02926 pburl.clear(); 02927 } 02928 02929 QStringList strlist( QString::number(exists) ); 02930 strlist << pburl; 02931 SendResponse(pbssock, strlist); 02932 } 02933 02934 02939 void MainServer::HandleQueryFileHash(QStringList &slist, PlaybackSock *pbs) 02940 { 02941 QString storageGroup = "Default"; 02942 QString hostname = gCoreContext->GetHostName(); 02943 QString filename = ""; 02944 QStringList res; 02945 02946 switch (slist.size()) { 02947 case 4: 02948 if (!slist[3].isEmpty()) 02949 hostname = slist[3]; 02950 case 3: 02951 if (slist[2].isEmpty()) 02952 storageGroup = slist[2]; 02953 case 2: 02954 filename = slist[1]; 02955 if (filename.isEmpty() || 02956 filename.contains("/../") || 02957 filename.startsWith("../")) 02958 { 02959 LOG(VB_GENERAL, LOG_ERR, 02960 QString("ERROR checking for file, filename '%1' " 02961 "fails sanity checks").arg(filename)); 02962 res << ""; 02963 SendResponse(pbs->getSocket(), res); 02964 return; 02965 } 02966 break; 02967 default: 02968 LOG(VB_GENERAL, LOG_ERR, "ERROR, invalid input count for QUERY_FILE_HASH"); 02969 res << ""; 02970 SendResponse(pbs->getSocket(), res); 02971 return; 02972 } 02973 02974 QString hash = ""; 02975 02976 if (hostname == gCoreContext->GetHostName()) 02977 { 02978 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName()); 02979 QString fullname = sgroup.FindFile(filename); 02980 hash = FileHash(fullname); 02981 } 02982 else 02983 { 02984 PlaybackSock *slave = GetMediaServerByHostname(hostname); 02985 if (slave) 02986 { 02987 hash = slave->GetFileHash(filename, storageGroup); 02988 slave->DownRef(); 02989 } 02990 else 02991 { 02992 MSqlQuery query(MSqlQuery::InitCon()); 02993 query.prepare("SELECT hostname FROM settings " 02994 "WHERE value='BackendServerIP' " 02995 "OR value='BackendServerIP6' " 02996 "AND data=:HOSTNAME;"); 02997 query.bindValue(":HOSTNAME", hostname); 02998 02999 if (query.exec() && query.next()) 03000 { 03001 hostname = query.value(0).toString(); 03002 slave = GetMediaServerByHostname(hostname); 03003 if (slave) 03004 { 03005 hash = slave->GetFileHash(filename, storageGroup); 03006 slave->DownRef(); 03007 } 03008 } 03009 } 03010 } 03011 03012 res << hash; 03013 SendResponse(pbs->getSocket(), res); 03014 } 03015 03020 void MainServer::HandleQueryFileExists(QStringList &slist, PlaybackSock *pbs) 03021 { 03022 QString filename = slist[1]; 03023 QString storageGroup = "Default"; 03024 QStringList retlist; 03025 03026 if (slist.size() > 2) 03027 storageGroup = slist[2]; 03028 03029 if ((filename.isEmpty()) || 03030 (filename.contains("/../")) || 03031 (filename.startsWith("../"))) 03032 { 03033 LOG(VB_GENERAL, LOG_ERR, 03034 QString("ERROR checking for file, filename '%1' " 03035 "fails sanity checks").arg(filename)); 03036 retlist << "0"; 03037 SendResponse(pbs->getSocket(), retlist); 03038 return; 03039 } 03040 03041 if (storageGroup.isEmpty()) 03042 storageGroup = "Default"; 03043 03044 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName()); 03045 03046 QString fullname = sgroup.FindFile(filename); 03047 03048 if (!fullname.isEmpty()) 03049 { 03050 retlist << "1"; 03051 retlist << fullname; 03052 03053 struct stat fileinfo; 03054 if (stat(fullname.toLocal8Bit().constData(), &fileinfo) >= 0) 03055 { 03056 retlist << QString::number(fileinfo.st_dev); 03057 retlist << QString::number(fileinfo.st_ino); 03058 retlist << QString::number(fileinfo.st_mode); 03059 retlist << QString::number(fileinfo.st_nlink); 03060 retlist << QString::number(fileinfo.st_uid); 03061 retlist << QString::number(fileinfo.st_gid); 03062 retlist << QString::number(fileinfo.st_rdev); 03063 retlist << QString::number(fileinfo.st_size); 03064 #ifdef USING_MINGW 03065 retlist << "0"; // st_blksize 03066 retlist << "0"; // st_blocks 03067 #else 03068 retlist << QString::number(fileinfo.st_blksize); 03069 retlist << QString::number(fileinfo.st_blocks); 03070 #endif 03071 retlist << QString::number(fileinfo.st_atime); 03072 retlist << QString::number(fileinfo.st_mtime); 03073 retlist << QString::number(fileinfo.st_ctime); 03074 } 03075 } 03076 else 03077 retlist << "0"; 03078 03079 SendResponse(pbs->getSocket(), retlist); 03080 } 03081 03082 void MainServer::getGuideDataThrough(QDateTime &GuideDataThrough) 03083 { 03084 MSqlQuery query(MSqlQuery::InitCon()); 03085 query.prepare("SELECT MAX(endtime) FROM program WHERE manualid = 0;"); 03086 03087 if (query.exec() && query.next()) 03088 { 03089 GuideDataThrough = QDateTime::fromString( 03090 query.value(0).toString(), Qt::ISODate); 03091 } 03092 } 03093 03094 void MainServer::HandleQueryGuideDataThrough(PlaybackSock *pbs) 03095 { 03096 QDateTime GuideDataThrough; 03097 MythSocket *pbssock = pbs->getSocket(); 03098 QStringList strlist; 03099 03100 getGuideDataThrough(GuideDataThrough); 03101 03102 if (GuideDataThrough.isNull()) 03103 strlist << QString("0000-00-00 00:00"); 03104 else 03105 strlist << QDateTime(GuideDataThrough).toString("yyyy-MM-dd hh:mm"); 03106 03107 SendResponse(pbssock, strlist); 03108 } 03109 03110 void MainServer::HandleGetPendingRecordings(PlaybackSock *pbs, 03111 QString tmptable, int recordid) 03112 { 03113 MythSocket *pbssock = pbs->getSocket(); 03114 03115 QStringList strList; 03116 03117 if (m_sched) 03118 { 03119 if (tmptable.isEmpty()) 03120 m_sched->GetAllPending(strList); 03121 else 03122 { 03123 Scheduler *sched = new Scheduler(false, encoderList, 03124 tmptable, m_sched); 03125 sched->FillRecordListFromDB(recordid); 03126 sched->GetAllPending(strList); 03127 delete sched; 03128 03129 if (recordid > 0) 03130 { 03131 MSqlQuery query(MSqlQuery::InitCon()); 03132 query.prepare("SELECT NULL FROM record " 03133 "WHERE recordid = :RECID;"); 03134 query.bindValue(":RECID", recordid); 03135 03136 if (query.exec() && query.size()) 03137 { 03138 RecordingRule *record = new RecordingRule(); 03139 record->m_recordID = recordid; 03140 if (record->Load() && 03141 record->m_searchType == kManualSearch) 03142 m_sched->RescheduleMatch(recordid, 0, 0, QDateTime(), 03143 "Speculation"); 03144 delete record; 03145 } 03146 query.prepare("DELETE FROM program WHERE manualid = :RECID;"); 03147 query.bindValue(":RECID", recordid); 03148 if (!query.exec()) 03149 MythDB::DBError("MainServer::HandleGetPendingRecordings " 03150 "- delete", query); 03151 } 03152 } 03153 } 03154 else 03155 { 03156 strList << QString::number(0); 03157 strList << QString::number(0); 03158 } 03159 03160 SendResponse(pbssock, strList); 03161 } 03162 03163 void MainServer::HandleGetScheduledRecordings(PlaybackSock *pbs) 03164 { 03165 MythSocket *pbssock = pbs->getSocket(); 03166 03167 QStringList strList; 03168 03169 if (m_sched) 03170 Scheduler::GetAllScheduled(strList); 03171 else 03172 strList << QString::number(0); 03173 03174 SendResponse(pbssock, strList); 03175 } 03176 03177 void MainServer::HandleGetConflictingRecordings(QStringList &slist, 03178 PlaybackSock *pbs) 03179 { 03180 MythSocket *pbssock = pbs->getSocket(); 03181 03182 QStringList::const_iterator it = slist.begin() + 1; 03183 RecordingInfo recinfo(it, slist.end()); 03184 03185 QStringList strlist; 03186 03187 if (m_sched && recinfo.GetChanID()) 03188 m_sched->getConflicting(&recinfo, strlist); 03189 else 03190 strlist << QString::number(0); 03191 03192 SendResponse(pbssock, strlist); 03193 } 03194 03195 void MainServer::HandleGetExpiringRecordings(PlaybackSock *pbs) 03196 { 03197 MythSocket *pbssock = pbs->getSocket(); 03198 03199 QStringList strList; 03200 03201 if (m_expirer) 03202 m_expirer->GetAllExpiring(strList); 03203 else 03204 strList << QString::number(0); 03205 03206 SendResponse(pbssock, strList); 03207 } 03208 03209 void MainServer::HandleSGGetFileList(QStringList &sList, 03210 PlaybackSock *pbs) 03211 { 03212 MythSocket *pbssock = pbs->getSocket(); 03213 QStringList strList; 03214 03215 if ((sList.size() < 4) || (sList.size() > 5)) 03216 { 03217 LOG(VB_GENERAL, LOG_ERR, 03218 QString("HandleSGGetFileList: Invalid Request. %1") 03219 .arg(sList.join("[]:[]"))); 03220 strList << "EMPTY LIST"; 03221 SendResponse(pbssock, strList); 03222 return; 03223 } 03224 03225 QString host = gCoreContext->GetHostName(); 03226 QString wantHost = sList.at(1); 03227 QString groupname = sList.at(2); 03228 QString path = sList.at(3); 03229 bool fileNamesOnly = false; 03230 03231 if (sList.size() >= 5) 03232 fileNamesOnly = sList.at(4).toInt(); 03233 03234 bool slaveUnreachable = false; 03235 03236 LOG(VB_FILE, LOG_INFO, QString("HandleSGGetFileList: group = %1 host = %2 " 03237 " path = %3 wanthost = %4") 03238 .arg(groupname).arg(host).arg(path).arg(wantHost)); 03239 03240 if ((host.toLower() == wantHost.toLower()) || 03241 (gCoreContext->GetSetting("BackendServerIP") == wantHost) || 03242 (gCoreContext->GetSetting("BackendServerIP6") == wantHost)) 03243 { 03244 StorageGroup sg(groupname, host); 03245 LOG(VB_FILE, LOG_INFO, "HandleSGGetFileList: Getting local info"); 03246 if (fileNamesOnly) 03247 strList = sg.GetFileList(path); 03248 else 03249 strList = sg.GetFileInfoList(path); 03250 } 03251 else 03252 { 03253 PlaybackSock *slave = GetMediaServerByHostname(wantHost); 03254 if (slave) 03255 { 03256 LOG(VB_FILE, LOG_INFO, "HandleSGGetFileList: Getting remote info"); 03257 strList = slave->GetSGFileList(wantHost, groupname, path, 03258 fileNamesOnly); 03259 slave->DownRef(); 03260 slaveUnreachable = false; 03261 } 03262 else 03263 { 03264 LOG(VB_FILE, LOG_INFO, 03265 QString("HandleSGGetFileList: Failed to grab slave socket " 03266 ": %1 :").arg(wantHost)); 03267 slaveUnreachable = true; 03268 } 03269 03270 } 03271 03272 if (slaveUnreachable) 03273 strList << "SLAVE UNREACHABLE: " << host; 03274 03275 if (strList.count() == 0 || (strList.at(0) == "0")) 03276 strList << "EMPTY LIST"; 03277 03278 SendResponse(pbssock, strList); 03279 } 03280 03281 void MainServer::HandleSGFileQuery(QStringList &sList, 03282 PlaybackSock *pbs) 03283 { 03284 MythSocket *pbssock = pbs->getSocket(); 03285 QStringList strList; 03286 03287 if (sList.size() != 4) 03288 { 03289 LOG(VB_GENERAL, LOG_ERR, 03290 QString("HandleSGFileQuery: Invalid Request. %1") 03291 .arg(sList.join("[]:[]"))); 03292 strList << "EMPTY LIST"; 03293 SendResponse(pbssock, strList); 03294 return; 03295 } 03296 03297 QString wantHost = sList.at(1); 03298 QString groupname = sList.at(2); 03299 QString filename = sList.at(3); 03300 03301 bool slaveUnreachable = false; 03302 03303 LOG(VB_FILE, LOG_INFO, QString("HandleSGFileQuery: %1") 03304 .arg(gCoreContext->GenMythURL(wantHost, 0, filename, groupname))); 03305 03306 if ((wantHost.toLower() == gCoreContext->GetHostName().toLower()) || 03307 (wantHost == gCoreContext->GetSetting("BackendServerIP")) || 03308 (wantHost == gCoreContext->GetSetting("BackendServerIP6"))) 03309 { 03310 LOG(VB_FILE, LOG_INFO, "HandleSGFileQuery: Getting local info"); 03311 StorageGroup sg(groupname, gCoreContext->GetHostName()); 03312 strList = sg.GetFileInfo(filename); 03313 } 03314 else 03315 { 03316 PlaybackSock *slave = GetMediaServerByHostname(wantHost); 03317 if (slave) 03318 { 03319 LOG(VB_FILE, LOG_INFO, "HandleSGFileQuery: Getting remote info"); 03320 strList = slave->GetSGFileQuery(wantHost, groupname, filename); 03321 slave->DownRef(); 03322 slaveUnreachable = false; 03323 } 03324 else 03325 { 03326 LOG(VB_FILE, LOG_INFO, 03327 QString("HandleSGFileQuery: Failed to grab slave socket : %1 :") 03328 .arg(wantHost)); 03329 slaveUnreachable = true; 03330 } 03331 03332 } 03333 03334 if (slaveUnreachable) 03335 strList << "SLAVE UNREACHABLE: " << wantHost; 03336 03337 if (strList.count() == 0 || (strList.at(0) == "0")) 03338 strList << "EMPTY LIST"; 03339 03340 SendResponse(pbssock, strList); 03341 } 03342 03343 void MainServer::HandleLockTuner(PlaybackSock *pbs, int cardid) 03344 { 03345 MythSocket *pbssock = pbs->getSocket(); 03346 QString pbshost = pbs->getHostname(); 03347 03348 QStringList strlist; 03349 int retval; 03350 03351 EncoderLink *encoder = NULL; 03352 QString enchost; 03353 03354 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 03355 for (; iter != encoderList->end(); ++iter) 03356 { 03357 EncoderLink *elink = *iter; 03358 03359 // we're looking for a specific card but this isn't the one we want 03360 if ((cardid != -1) && (cardid != elink->GetCardID())) 03361 continue; 03362 03363 if (elink->IsLocal()) 03364 enchost = gCoreContext->GetHostName(); 03365 else 03366 enchost = elink->GetHostName(); 03367 03368 if ((enchost == pbshost) && 03369 (elink->IsConnected()) && 03370 (!elink->IsBusy()) && 03371 (!elink->IsTunerLocked())) 03372 { 03373 encoder = elink; 03374 break; 03375 } 03376 } 03377 03378 if (encoder) 03379 { 03380 retval = encoder->LockTuner(); 03381 03382 if (retval != -1) 03383 { 03384 QString msg = QString("Cardid %1 LOCKed for external use on %2.") 03385 .arg(retval).arg(pbshost); 03386 LOG(VB_GENERAL, LOG_INFO, msg); 03387 03388 MSqlQuery query(MSqlQuery::InitCon()); 03389 query.prepare("SELECT videodevice, audiodevice, " 03390 "vbidevice " 03391 "FROM capturecard " 03392 "WHERE cardid = :CARDID ;"); 03393 query.bindValue(":CARDID", retval); 03394 03395 if (query.exec() && query.next()) 03396 { 03397 // Success 03398 strlist << QString::number(retval) 03399 << query.value(0).toString() 03400 << query.value(1).toString() 03401 << query.value(2).toString(); 03402 03403 if (m_sched) 03404 m_sched->ReschedulePlace("LockTuner"); 03405 03406 SendResponse(pbssock, strlist); 03407 return; 03408 } 03409 else 03410 LOG(VB_GENERAL, LOG_ERR, 03411 "MainServer::LockTuner(): Could not find " 03412 "card info in database"); 03413 } 03414 else 03415 { 03416 // Tuner already locked 03417 strlist << "-2" << "" << "" << ""; 03418 SendResponse(pbssock, strlist); 03419 return; 03420 } 03421 } 03422 03423 strlist << "-1" << "" << "" << ""; 03424 SendResponse(pbssock, strlist); 03425 } 03426 03427 void MainServer::HandleFreeTuner(int cardid, PlaybackSock *pbs) 03428 { 03429 MythSocket *pbssock = pbs->getSocket(); 03430 QStringList strlist; 03431 EncoderLink *encoder = NULL; 03432 03433 QMap<int, EncoderLink *>::Iterator iter = encoderList->find(cardid); 03434 if (iter == encoderList->end()) 03435 { 03436 LOG(VB_GENERAL, LOG_ERR, "MainServer::HandleFreeTuner() " + 03437 QString("Unknown encoder: %1").arg(cardid)); 03438 strlist << "FAILED"; 03439 } 03440 else 03441 { 03442 encoder = *iter; 03443 encoder->FreeTuner(); 03444 03445 QString msg = QString("Cardid %1 FREED from external use on %2.") 03446 .arg(cardid).arg(pbs->getHostname()); 03447 LOG(VB_GENERAL, LOG_INFO, msg); 03448 03449 if (m_sched) 03450 m_sched->ReschedulePlace("FreeTuner"); 03451 03452 strlist << "OK"; 03453 } 03454 03455 SendResponse(pbssock, strlist); 03456 } 03457 03458 void MainServer::HandleGetFreeRecorder(PlaybackSock *pbs) 03459 { 03460 MythSocket *pbssock = pbs->getSocket(); 03461 QString pbshost = pbs->getHostname(); 03462 03463 vector<uint> excluded_cardids; 03464 QStringList strlist; 03465 int retval = -1; 03466 03467 EncoderLink *encoder = NULL; 03468 QString enchost; 03469 uint bestorder = 0; 03470 03471 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 03472 for (; iter != encoderList->end(); ++iter) 03473 { 03474 EncoderLink *elink = *iter; 03475 03476 if (elink->IsLocal()) 03477 enchost = gCoreContext->GetHostName(); 03478 else 03479 enchost = elink->GetHostName(); 03480 03481 LOG(VB_RECORD, LOG_INFO, 03482 QString("Checking card %1. Best card so far %2") 03483 .arg(iter.key()).arg(retval)); 03484 03485 if (!elink->IsConnected() || elink->IsTunerLocked()) 03486 continue; 03487 03488 vector<InputInfo> inputs = elink->GetFreeInputs(excluded_cardids); 03489 03490 for (uint i = 0; i < inputs.size(); ++i) 03491 { 03492 if (!encoder || inputs[i].livetvorder < bestorder) 03493 { 03494 retval = iter.key(); 03495 encoder = elink; 03496 bestorder = inputs[i].livetvorder; 03497 } 03498 } 03499 } 03500 03501 LOG(VB_RECORD, LOG_INFO, 03502 QString("Best card is %1").arg(retval)); 03503 03504 strlist << QString::number(retval); 03505 03506 if (encoder) 03507 { 03508 if (encoder->IsLocal()) 03509 { 03510 strlist << gCoreContext->GetBackendServerIP(); 03511 strlist << gCoreContext->GetSetting("BackendServerPort"); 03512 } 03513 else 03514 { 03515 strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName()); 03516 strlist << gCoreContext->GetSettingOnHost("BackendServerPort", 03517 encoder->GetHostName(), "-1"); 03518 } 03519 } 03520 else 03521 { 03522 strlist << "nohost"; 03523 strlist << "-1"; 03524 } 03525 03526 SendResponse(pbssock, strlist); 03527 } 03528 03529 void MainServer::HandleGetFreeRecorderCount(PlaybackSock *pbs) 03530 { 03531 MythSocket *pbssock = pbs->getSocket(); 03532 03533 vector<uint> excluded_cardids; 03534 QStringList strlist; 03535 int count = 0; 03536 03537 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 03538 for (; iter != encoderList->end(); ++iter) 03539 { 03540 EncoderLink *elink = *iter; 03541 03542 if (elink->IsConnected() && !elink->IsTunerLocked() && 03543 !elink->GetFreeInputs(excluded_cardids).empty()) 03544 { 03545 count++; 03546 } 03547 } 03548 03549 strlist << QString::number(count); 03550 03551 SendResponse(pbssock, strlist); 03552 } 03553 03554 static bool comp_livetvorder(const InputInfo &a, const InputInfo &b) 03555 { 03556 return a.livetvorder < b.livetvorder; 03557 } 03558 03559 void MainServer::HandleGetFreeRecorderList(PlaybackSock *pbs) 03560 { 03561 MythSocket *pbssock = pbs->getSocket(); 03562 03563 vector<uint> excluded_cardids; 03564 vector<InputInfo> allinputs; 03565 03566 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 03567 for (; iter != encoderList->end(); ++iter) 03568 { 03569 EncoderLink *elink = *iter; 03570 03571 if (!elink->IsConnected() || elink->IsTunerLocked()) 03572 continue; 03573 03574 vector<InputInfo> inputs = elink->GetFreeInputs(excluded_cardids); 03575 allinputs.insert(allinputs.end(), inputs.begin(), inputs.end()); 03576 } 03577 03578 stable_sort(allinputs.begin(), allinputs.end(), comp_livetvorder); 03579 03580 QStringList strlist; 03581 QMap<int, bool> cardidused; 03582 for (uint i = 0; i < allinputs.size(); ++i) 03583 { 03584 uint cardid = allinputs[i].cardid; 03585 if (!cardidused[cardid]) 03586 { 03587 strlist << QString::number(cardid); 03588 cardidused[cardid] = true; 03589 } 03590 } 03591 03592 if (strlist.size() == 0) 03593 strlist << "0"; 03594 03595 SendResponse(pbssock, strlist); 03596 } 03597 03598 void MainServer::HandleGetNextFreeRecorder(QStringList &slist, 03599 PlaybackSock *pbs) 03600 { 03601 MythSocket *pbssock = pbs->getSocket(); 03602 QString pbshost = pbs->getHostname(); 03603 03604 QStringList strlist; 03605 int retval = -1; 03606 int currrec = slist[1].toInt(); 03607 03608 EncoderLink *encoder = NULL; 03609 QString enchost; 03610 03611 LOG(VB_RECORD, LOG_INFO, QString("Getting next free recorder after : %1") 03612 .arg(currrec)); 03613 03614 // find current recorder 03615 QMap<int, EncoderLink *>::Iterator iter, curr = encoderList->find(currrec); 03616 03617 if (currrec > 0 && curr != encoderList->end()) 03618 { 03619 vector<uint> excluded_cardids; 03620 excluded_cardids.push_back(currrec); 03621 03622 // cycle through all recorders 03623 for (iter = curr;;) 03624 { 03625 EncoderLink *elink; 03626 03627 // last item? go back 03628 if (++iter == encoderList->end()) 03629 { 03630 iter = encoderList->begin(); 03631 } 03632 03633 elink = *iter; 03634 03635 if (retval == -1 && elink->IsConnected() && 03636 !elink->IsTunerLocked() && 03637 !elink->GetFreeInputs(excluded_cardids).empty()) 03638 { 03639 encoder = elink; 03640 retval = iter.key(); 03641 } 03642 03643 // cycled right through? no more available recorders 03644 if (iter == curr) 03645 break; 03646 } 03647 } 03648 else 03649 { 03650 HandleGetFreeRecorder(pbs); 03651 return; 03652 } 03653 03654 03655 strlist << QString::number(retval); 03656 03657 if (encoder) 03658 { 03659 if (encoder->IsLocal()) 03660 { 03661 strlist << gCoreContext->GetBackendServerIP(); 03662 strlist << gCoreContext->GetSetting("BackendServerPort"); 03663 } 03664 else 03665 { 03666 strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName()); 03667 strlist << gCoreContext->GetSettingOnHost("BackendServerPort", 03668 encoder->GetHostName(), "-1"); 03669 } 03670 } 03671 else 03672 { 03673 strlist << "nohost"; 03674 strlist << "-1"; 03675 } 03676 03677 SendResponse(pbssock, strlist); 03678 } 03679 03680 static QString cleanup(const QString &str) 03681 { 03682 if (str == " ") 03683 return ""; 03684 return str; 03685 } 03686 03687 static QString make_safe(const QString &str) 03688 { 03689 if (str.isEmpty()) 03690 return " "; 03691 return str; 03692 } 03693 03694 void MainServer::HandleRecorderQuery(QStringList &slist, QStringList &commands, 03695 PlaybackSock *pbs) 03696 { 03697 MythSocket *pbssock = pbs->getSocket(); 03698 03699 if (commands.size() < 2 || slist.size() < 2) 03700 return; 03701 03702 int recnum = commands[1].toInt(); 03703 03704 QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recnum); 03705 if (iter == encoderList->end()) 03706 { 03707 LOG(VB_GENERAL, LOG_ERR, "MainServer::HandleRecorderQuery() " + 03708 QString("Unknown encoder: %1").arg(recnum)); 03709 QStringList retlist( "bad" ); 03710 SendResponse(pbssock, retlist); 03711 return; 03712 } 03713 03714 QString command = slist[1]; 03715 03716 QStringList retlist; 03717 03718 EncoderLink *enc = *iter; 03719 if (!enc->IsConnected()) 03720 { 03721 LOG(VB_GENERAL, LOG_ERR, " MainServer::HandleRecorderQuery() " + 03722 QString("Command %1 for unconnected encoder %2") 03723 .arg(command).arg(recnum)); 03724 retlist << "bad"; 03725 SendResponse(pbssock, retlist); 03726 return; 03727 } 03728 03729 if (command == "IS_RECORDING") 03730 { 03731 retlist << QString::number((int)enc->IsReallyRecording()); 03732 } 03733 else if (command == "GET_FRAMERATE") 03734 { 03735 retlist << QString::number(enc->GetFramerate()); 03736 } 03737 else if (command == "GET_FRAMES_WRITTEN") 03738 { 03739 retlist << QString::number(enc->GetFramesWritten()); 03740 } 03741 else if (command == "GET_FILE_POSITION") 03742 { 03743 retlist << QString::number(enc->GetFilePosition()); 03744 } 03745 else if (command == "GET_MAX_BITRATE") 03746 { 03747 retlist << QString::number(enc->GetMaxBitrate()); 03748 } 03749 else if (command == "GET_CURRENT_RECORDING") 03750 { 03751 ProgramInfo *info = enc->GetRecording(); 03752 if (info) 03753 { 03754 info->ToStringList(retlist); 03755 delete info; 03756 } 03757 else 03758 { 03759 ProgramInfo dummy; 03760 dummy.ToStringList(retlist); 03761 } 03762 } 03763 else if (command == "GET_KEYFRAME_POS") 03764 { 03765 long long desired = slist[2].toLongLong(); 03766 retlist << QString::number(enc->GetKeyframePosition(desired)); 03767 } 03768 else if (command == "FILL_POSITION_MAP") 03769 { 03770 long long start = slist[2].toLongLong(); 03771 long long end = slist[3].toLongLong(); 03772 frm_pos_map_t map; 03773 03774 if (!enc->GetKeyframePositions(start, end, map)) 03775 { 03776 retlist << "error"; 03777 } 03778 else 03779 { 03780 frm_pos_map_t::const_iterator it = map.begin(); 03781 for (; it != map.end(); ++it) 03782 { 03783 retlist += QString::number(it.key()); 03784 retlist += QString::number(*it); 03785 } 03786 if (retlist.empty()) 03787 retlist << "ok"; 03788 } 03789 } 03790 else if (command == "GET_RECORDING") 03791 { 03792 ProgramInfo *pginfo = enc->GetRecording(); 03793 if (pginfo) 03794 { 03795 pginfo->ToStringList(retlist); 03796 delete pginfo; 03797 } 03798 else 03799 { 03800 ProgramInfo dummy; 03801 dummy.ToStringList(retlist); 03802 } 03803 } 03804 else if (command == "FRONTEND_READY") 03805 { 03806 enc->FrontendReady(); 03807 retlist << "ok"; 03808 } 03809 else if (command == "CANCEL_NEXT_RECORDING") 03810 { 03811 QString cancel = slist[2]; 03812 LOG(VB_GENERAL, LOG_NOTICE, 03813 QString("Received: CANCEL_NEXT_RECORDING %1").arg(cancel)); 03814 enc->CancelNextRecording(cancel == "1"); 03815 retlist << "ok"; 03816 } 03817 else if (command == "SPAWN_LIVETV") 03818 { 03819 QString chainid = slist[2]; 03820 LiveTVChain *chain = GetExistingChain(chainid); 03821 if (!chain) 03822 { 03823 chain = new LiveTVChain(); 03824 chain->LoadFromExistingChain(chainid); 03825 AddToChains(chain); 03826 } 03827 03828 chain->SetHostSocket(pbssock); 03829 03830 enc->SpawnLiveTV(chain, slist[3].toInt(), slist[4]); 03831 retlist << "ok"; 03832 } 03833 else if (command == "STOP_LIVETV") 03834 { 03835 QString chainid = enc->GetChainID(); 03836 enc->StopLiveTV(); 03837 03838 LiveTVChain *chain = GetExistingChain(chainid); 03839 if (chain) 03840 { 03841 chain->DelHostSocket(pbssock); 03842 if (chain->HostSocketCount() == 0) 03843 { 03844 DeleteChain(chain); 03845 } 03846 } 03847 03848 retlist << "ok"; 03849 } 03850 else if (command == "PAUSE") 03851 { 03852 enc->PauseRecorder(); 03853 retlist << "ok"; 03854 } 03855 else if (command == "FINISH_RECORDING") 03856 { 03857 enc->FinishRecording(); 03858 retlist << "ok"; 03859 } 03860 else if (command == "SET_LIVE_RECORDING") 03861 { 03862 int recording = slist[2].toInt(); 03863 enc->SetLiveRecording(recording); 03864 retlist << "ok"; 03865 } 03866 else if (command == "GET_FREE_INPUTS") 03867 { 03868 vector<uint> excluded_cardids; 03869 for (int i = 2; i < slist.size(); i++) 03870 excluded_cardids.push_back(slist[i].toUInt()); 03871 03872 vector<InputInfo> inputs = enc->GetFreeInputs(excluded_cardids); 03873 03874 if (inputs.empty()) 03875 retlist << "EMPTY_LIST"; 03876 else 03877 { 03878 for (uint i = 0; i < inputs.size(); i++) 03879 inputs[i].ToStringList(retlist); 03880 } 03881 } 03882 else if (command == "GET_INPUT") 03883 { 03884 QString ret = enc->GetInput(); 03885 ret = (ret.isEmpty()) ? "UNKNOWN" : ret; 03886 retlist << ret; 03887 } 03888 else if (command == "SET_INPUT") 03889 { 03890 QString input = slist[2]; 03891 QString ret = enc->SetInput(input); 03892 ret = (ret.isEmpty()) ? "UNKNOWN" : ret; 03893 retlist << ret; 03894 } 03895 else if (command == "TOGGLE_CHANNEL_FAVORITE") 03896 { 03897 QString changroup = slist[2]; 03898 enc->ToggleChannelFavorite(changroup); 03899 retlist << "ok"; 03900 } 03901 else if (command == "CHANGE_CHANNEL") 03902 { 03903 ChannelChangeDirection direction = 03904 (ChannelChangeDirection) slist[2].toInt(); 03905 enc->ChangeChannel(direction); 03906 retlist << "ok"; 03907 } 03908 else if (command == "SET_CHANNEL") 03909 { 03910 QString name = slist[2]; 03911 enc->SetChannel(name); 03912 retlist << "ok"; 03913 } 03914 else if (command == "SET_SIGNAL_MONITORING_RATE") 03915 { 03916 int rate = slist[2].toInt(); 03917 int notifyFrontend = slist[3].toInt(); 03918 int oldrate = enc->SetSignalMonitoringRate(rate, notifyFrontend); 03919 retlist << QString::number(oldrate); 03920 } 03921 else if (command == "GET_COLOUR") 03922 { 03923 int ret = enc->GetPictureAttribute(kPictureAttribute_Colour); 03924 retlist << QString::number(ret); 03925 } 03926 else if (command == "GET_CONTRAST") 03927 { 03928 int ret = enc->GetPictureAttribute(kPictureAttribute_Contrast); 03929 retlist << QString::number(ret); 03930 } 03931 else if (command == "GET_BRIGHTNESS") 03932 { 03933 int ret = enc->GetPictureAttribute(kPictureAttribute_Brightness); 03934 retlist << QString::number(ret); 03935 } 03936 else if (command == "GET_HUE") 03937 { 03938 int ret = enc->GetPictureAttribute(kPictureAttribute_Hue); 03939 retlist << QString::number(ret); 03940 } 03941 else if (command == "CHANGE_COLOUR") 03942 { 03943 int type = slist[2].toInt(); 03944 bool up = slist[3].toInt(); 03945 int ret = enc->ChangePictureAttribute( 03946 (PictureAdjustType) type, kPictureAttribute_Colour, up); 03947 retlist << QString::number(ret); 03948 } 03949 else if (command == "CHANGE_CONTRAST") 03950 { 03951 int type = slist[2].toInt(); 03952 bool up = slist[3].toInt(); 03953 int ret = enc->ChangePictureAttribute( 03954 (PictureAdjustType) type, kPictureAttribute_Contrast, up); 03955 retlist << QString::number(ret); 03956 } 03957 else if (command == "CHANGE_BRIGHTNESS") 03958 { 03959 int type= slist[2].toInt(); 03960 bool up = slist[3].toInt(); 03961 int ret = enc->ChangePictureAttribute( 03962 (PictureAdjustType) type, kPictureAttribute_Brightness, up); 03963 retlist << QString::number(ret); 03964 } 03965 else if (command == "CHANGE_HUE") 03966 { 03967 int type= slist[2].toInt(); 03968 bool up = slist[3].toInt(); 03969 int ret = enc->ChangePictureAttribute( 03970 (PictureAdjustType) type, kPictureAttribute_Hue, up); 03971 retlist << QString::number(ret); 03972 } 03973 else if (command == "CHECK_CHANNEL") 03974 { 03975 QString name = slist[2]; 03976 retlist << QString::number((int)(enc->CheckChannel(name))); 03977 } 03978 else if (command == "SHOULD_SWITCH_CARD") 03979 { 03980 QString chanid = slist[2]; 03981 retlist << QString::number((int)(enc->ShouldSwitchToAnotherCard(chanid))); 03982 } 03983 else if (command == "CHECK_CHANNEL_PREFIX") 03984 { 03985 QString needed_spacer; 03986 QString prefix = slist[2]; 03987 uint is_complete_valid_channel_on_rec = 0; 03988 bool is_extra_char_useful = false; 03989 03990 bool match = enc->CheckChannelPrefix( 03991 prefix, is_complete_valid_channel_on_rec, 03992 is_extra_char_useful, needed_spacer); 03993 03994 retlist << QString::number((int)match); 03995 retlist << QString::number(is_complete_valid_channel_on_rec); 03996 retlist << QString::number((int)is_extra_char_useful); 03997 retlist << ((needed_spacer.isEmpty()) ? QString("X") : needed_spacer); 03998 } 03999 else if (command == "GET_NEXT_PROGRAM_INFO" && (slist.size() >= 6)) 04000 { 04001 QString channelname = slist[2]; 04002 uint chanid = slist[3].toUInt(); 04003 BrowseDirection direction = (BrowseDirection)slist[4].toInt(); 04004 QString starttime = slist[5]; 04005 04006 QString title = "", subtitle = "", desc = "", category = ""; 04007 QString endtime = "", callsign = "", iconpath = ""; 04008 QString seriesid = "", programid = ""; 04009 04010 enc->GetNextProgram(direction, 04011 title, subtitle, desc, category, starttime, 04012 endtime, callsign, iconpath, channelname, chanid, 04013 seriesid, programid); 04014 04015 retlist << make_safe(title); 04016 retlist << make_safe(subtitle); 04017 retlist << make_safe(desc); 04018 retlist << make_safe(category); 04019 retlist << make_safe(starttime); 04020 retlist << make_safe(endtime); 04021 retlist << make_safe(callsign); 04022 retlist << make_safe(iconpath); 04023 retlist << make_safe(channelname); 04024 retlist << QString::number(chanid); 04025 retlist << make_safe(seriesid); 04026 retlist << make_safe(programid); 04027 } 04028 else if (command == "GET_CHANNEL_INFO") 04029 { 04030 uint chanid = slist[2].toUInt(); 04031 uint sourceid = 0; 04032 QString callsign = "", channum = "", channame = "", xmltv = ""; 04033 04034 enc->GetChannelInfo(chanid, sourceid, 04035 callsign, channum, channame, xmltv); 04036 04037 retlist << QString::number(chanid); 04038 retlist << QString::number(sourceid); 04039 retlist << make_safe(callsign); 04040 retlist << make_safe(channum); 04041 retlist << make_safe(channame); 04042 retlist << make_safe(xmltv); 04043 } 04044 else 04045 { 04046 LOG(VB_GENERAL, LOG_ERR, QString("Unknown command: %1").arg(command)); 04047 retlist << "ok"; 04048 } 04049 04050 SendResponse(pbssock, retlist); 04051 } 04052 04053 void MainServer::HandleSetNextLiveTVDir(QStringList &commands, 04054 PlaybackSock *pbs) 04055 { 04056 MythSocket *pbssock = pbs->getSocket(); 04057 04058 int recnum = commands[1].toInt(); 04059 04060 QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recnum); 04061 if (iter == encoderList->end()) 04062 { 04063 LOG(VB_GENERAL, LOG_ERR, "MainServer::HandleSetNextLiveTVDir() " + 04064 QString("Unknown encoder: %1").arg(recnum)); 04065 QStringList retlist( "bad" ); 04066 SendResponse(pbssock, retlist); 04067 return; 04068 } 04069 04070 EncoderLink *enc = *iter; 04071 enc->SetNextLiveTVDir(commands[2]); 04072 04073 QStringList retlist( "OK" ); 04074 SendResponse(pbssock, retlist); 04075 } 04076 04077 void MainServer::HandleSetChannelInfo(QStringList &slist, PlaybackSock *pbs) 04078 { 04079 bool ok = true; 04080 MythSocket *pbssock = pbs->getSocket(); 04081 uint chanid = slist[1].toUInt(); 04082 uint sourceid = slist[2].toUInt(); 04083 QString oldcnum = cleanup(slist[3]); 04084 QString callsign = cleanup(slist[4]); 04085 QString channum = cleanup(slist[5]); 04086 QString channame = cleanup(slist[6]); 04087 QString xmltv = cleanup(slist[7]); 04088 04089 QStringList retlist; 04090 if (!chanid || !sourceid) 04091 { 04092 retlist << "0"; 04093 SendResponse(pbssock, retlist); 04094 return; 04095 } 04096 04097 QMap<int, EncoderLink *>::iterator it = encoderList->begin(); 04098 for (; it != encoderList->end(); ++it) 04099 { 04100 if (*it) 04101 { 04102 ok &= (*it)->SetChannelInfo(chanid, sourceid, oldcnum, 04103 callsign, channum, channame, xmltv); 04104 } 04105 } 04106 04107 retlist << ((ok) ? "1" : "0"); 04108 SendResponse(pbssock, retlist); 04109 } 04110 04111 void MainServer::HandleRemoteEncoder(QStringList &slist, QStringList &commands, 04112 PlaybackSock *pbs) 04113 { 04114 MythSocket *pbssock = pbs->getSocket(); 04115 04116 int recnum = commands[1].toInt(); 04117 QStringList retlist; 04118 04119 QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recnum); 04120 if (iter == encoderList->end()) 04121 { 04122 LOG(VB_GENERAL, LOG_ERR, "MainServer: " + 04123 QString("HandleRemoteEncoder(cmd %1) ").arg(slist[1]) + 04124 QString("Unknown encoder: %1").arg(recnum)); 04125 retlist << QString::number((int) kState_Error); 04126 SendResponse(pbssock, retlist); 04127 return; 04128 } 04129 04130 EncoderLink *enc = *iter; 04131 04132 QString command = slist[1]; 04133 04134 if (command == "GET_STATE") 04135 { 04136 retlist << QString::number((int)enc->GetState()); 04137 } 04138 else if (command == "GET_SLEEPSTATUS") 04139 { 04140 retlist << QString::number(enc->GetSleepStatus()); 04141 } 04142 else if (command == "GET_FLAGS") 04143 { 04144 retlist << QString::number(enc->GetFlags()); 04145 } 04146 else if (command == "IS_BUSY") 04147 { 04148 int time_buffer = (slist.size() >= 3) ? slist[2].toInt() : 5; 04149 TunedInputInfo busy_input; 04150 retlist << QString::number((int)enc->IsBusy(&busy_input, time_buffer)); 04151 busy_input.ToStringList(retlist); 04152 } 04153 else if (command == "MATCHES_RECORDING" && 04154 slist.size() >= (2 + NUMPROGRAMLINES)) 04155 { 04156 QStringList::const_iterator it = slist.begin() + 2; 04157 ProgramInfo pginfo(it, slist.end()); 04158 04159 retlist << QString::number((int)enc->MatchesRecording(&pginfo)); 04160 } 04161 else if (command == "START_RECORDING" && 04162 slist.size() >= (2 + NUMPROGRAMLINES)) 04163 { 04164 QStringList::const_iterator it = slist.begin() + 2; 04165 ProgramInfo pginfo(it, slist.end()); 04166 04167 retlist << QString::number(enc->StartRecording(&pginfo)); 04168 } 04169 else if (command == "GET_RECORDING_STATUS") 04170 { 04171 retlist << QString::number((int)enc->GetRecordingStatus()); 04172 } 04173 else if (command == "RECORD_PENDING" && 04174 (slist.size() >= 4 + NUMPROGRAMLINES)) 04175 { 04176 int secsleft = slist[2].toInt(); 04177 int haslater = slist[3].toInt(); 04178 QStringList::const_iterator it = slist.begin() + 4; 04179 ProgramInfo pginfo(it, slist.end()); 04180 04181 enc->RecordPending(&pginfo, secsleft, haslater); 04182 04183 retlist << "OK"; 04184 } 04185 else if (command == "CANCEL_NEXT_RECORDING" && 04186 (slist.size() >= 3)) 04187 { 04188 bool cancel = (bool) slist[2].toInt(); 04189 enc->CancelNextRecording(cancel); 04190 retlist << "OK"; 04191 } 04192 else if (command == "STOP_RECORDING") 04193 { 04194 enc->StopRecording(); 04195 retlist << "OK"; 04196 } 04197 else if (command == "GET_MAX_BITRATE") 04198 { 04199 retlist << QString::number(enc->GetMaxBitrate()); 04200 } 04201 else if (command == "GET_CURRENT_RECORDING") 04202 { 04203 ProgramInfo *info = enc->GetRecording(); 04204 if (info) 04205 { 04206 info->ToStringList(retlist); 04207 delete info; 04208 } 04209 else 04210 { 04211 ProgramInfo dummy; 04212 dummy.ToStringList(retlist); 04213 } 04214 } 04215 else if (command == "GET_FREE_INPUTS") 04216 { 04217 vector<uint> excluded_cardids; 04218 for (int i = 2; i < slist.size(); i++) 04219 excluded_cardids.push_back(slist[i].toUInt()); 04220 04221 vector<InputInfo> inputs = enc->GetFreeInputs(excluded_cardids); 04222 04223 if (inputs.empty()) 04224 retlist << "EMPTY_LIST"; 04225 else 04226 { 04227 for (uint i = 0; i < inputs.size(); i++) 04228 inputs[i].ToStringList(retlist); 04229 } 04230 } 04231 04232 SendResponse(pbssock, retlist); 04233 } 04234 04235 void MainServer::GetActiveBackends(QStringList &hosts) 04236 { 04237 hosts.clear(); 04238 hosts << gCoreContext->GetHostName(); 04239 04240 QString hostname; 04241 QReadLocker rlock(&sockListLock); 04242 vector<PlaybackSock*>::iterator it; 04243 for (it = playbackList.begin(); it != playbackList.end(); ++it) 04244 { 04245 if ((*it)->isMediaServer()) 04246 { 04247 hostname = (*it)->getHostname(); 04248 if (!hosts.contains(hostname)) 04249 hosts << hostname; 04250 } 04251 } 04252 } 04253 04254 void MainServer::HandleActiveBackendsQuery(PlaybackSock *pbs) 04255 { 04256 QStringList retlist; 04257 GetActiveBackends(retlist); 04258 retlist.push_front(QString::number(retlist.size())); 04259 SendResponse(pbs->getSocket(), retlist); 04260 } 04261 04262 void MainServer::HandleIsActiveBackendQuery(QStringList &slist, 04263 PlaybackSock *pbs) 04264 { 04265 QStringList retlist; 04266 QString queryhostname = slist[1]; 04267 04268 if (gCoreContext->GetHostName() != queryhostname) 04269 { 04270 PlaybackSock *slave = GetSlaveByHostname(queryhostname); 04271 if (slave != NULL) 04272 { 04273 retlist << "TRUE"; 04274 slave->DownRef(); 04275 } 04276 else 04277 retlist << "FALSE"; 04278 } 04279 else 04280 retlist << "TRUE"; 04281 04282 SendResponse(pbs->getSocket(), retlist); 04283 } 04284 04285 int MainServer::GetfsID(QList<FileSystemInfo>::iterator fsInfo) 04286 { 04287 QString fskey = fsInfo->getHostname() + ":" + fsInfo->getPath(); 04288 QMutexLocker lock(&fsIDcacheLock); 04289 if (!fsIDcache.contains(fskey)) 04290 fsIDcache[fskey] = fsIDcache.count(); 04291 04292 return fsIDcache[fskey]; 04293 } 04294 04295 size_t MainServer::GetCurrentMaxBitrate(void) 04296 { 04297 size_t totalKBperMin = 0; 04298 04299 QMap<int, EncoderLink*>::iterator it = encoderList->begin(); 04300 for (; it != encoderList->end(); ++it) 04301 { 04302 EncoderLink *enc = *it; 04303 04304 if (!enc->IsConnected() || !enc->IsBusy()) 04305 continue; 04306 04307 long long maxBitrate = enc->GetMaxBitrate(); 04308 if (maxBitrate<=0) 04309 maxBitrate = 19500000LL; 04310 long long thisKBperMin = (((size_t)maxBitrate)*((size_t)15))>>11; 04311 totalKBperMin += thisKBperMin; 04312 LOG(VB_FILE, LOG_INFO, QString("Cardid %1: max bitrate %2 KB/min") 04313 .arg(enc->GetCardID()).arg(thisKBperMin)); 04314 } 04315 04316 LOG(VB_FILE, LOG_INFO, 04317 QString("Maximal bitrate of busy encoders is %1 KB/min") 04318 .arg(totalKBperMin)); 04319 04320 return totalKBperMin; 04321 } 04322 04323 void MainServer::BackendQueryDiskSpace(QStringList &strlist, bool consolidated, 04324 bool allHosts) 04325 { 04326 QString allHostList = gCoreContext->GetHostName(); 04327 int64_t totalKB = -1, usedKB = -1; 04328 QMap <QString, bool>foundDirs; 04329 QString driveKey; 04330 QString localStr = "1"; 04331 struct statfs statbuf; 04332 QStringList groups(StorageGroup::kSpecialGroups); 04333 groups.removeAll("LiveTV"); 04334 QString specialGroups = groups.join("', '"); 04335 QString sql = QString("SELECT MIN(id),dirname " 04336 "FROM storagegroup " 04337 "WHERE hostname = :HOSTNAME " 04338 "AND groupname NOT IN ( '%1' ) " 04339 "GROUP BY dirname;").arg(specialGroups); 04340 MSqlQuery query(MSqlQuery::InitCon()); 04341 query.prepare(sql); 04342 query.bindValue(":HOSTNAME", gCoreContext->GetHostName()); 04343 04344 if (query.exec()) 04345 { 04346 // If we don't have any dirs of our own, fallback to list of Default 04347 // dirs since that is what StorageGroup::Init() does. 04348 if (!query.size()) 04349 { 04350 query.prepare("SELECT MIN(id),dirname " 04351 "FROM storagegroup " 04352 "WHERE groupname = :GROUP " 04353 "GROUP BY dirname;"); 04354 query.bindValue(":GROUP", "Default"); 04355 if (!query.exec()) 04356 MythDB::DBError("BackendQueryDiskSpace", query); 04357 } 04358 04359 QDir checkDir(""); 04360 QString dirID; 04361 QString currentDir; 04362 int bSize; 04363 while (query.next()) 04364 { 04365 dirID = query.value(0).toString(); 04366 /* The storagegroup.dirname column uses utf8_bin collation, so Qt 04367 * uses QString::fromAscii() for toString(). Explicitly convert the 04368 * value using QString::fromUtf8() to prevent corruption. */ 04369 currentDir = QString::fromUtf8(query.value(1) 04370 .toByteArray().constData()); 04371 if (currentDir.right(1) == "/") 04372 currentDir.remove(currentDir.length() - 1, 1); 04373 04374 checkDir.setPath(currentDir); 04375 if (!foundDirs.contains(currentDir)) 04376 { 04377 if (checkDir.exists()) 04378 { 04379 QByteArray cdir = currentDir.toAscii(); 04380 getDiskSpace(cdir.constData(), totalKB, usedKB); 04381 memset(&statbuf, 0, sizeof(statbuf)); 04382 localStr = "1"; // Assume local 04383 bSize = 0; 04384 04385 if (!statfs(currentDir.toLocal8Bit().constData(), &statbuf)) 04386 { 04387 #if CONFIG_DARWIN 04388 char *fstypename = statbuf.f_fstypename; 04389 if ((!strcmp(fstypename, "nfs")) || // NFS|FTP 04390 (!strcmp(fstypename, "afpfs")) || // ApplShr 04391 (!strcmp(fstypename, "smbfs"))) // SMB 04392 localStr = "0"; 04393 #elif __linux__ 04394 long fstype = statbuf.f_type; 04395 if ((fstype == 0x6969) || // NFS 04396 (fstype == 0x517B) || // SMB 04397 (fstype == (long)0xFF534D42)) // CIFS 04398 localStr = "0"; 04399 #endif 04400 bSize = statbuf.f_bsize; 04401 } 04402 04403 strlist << gCoreContext->GetHostName(); 04404 strlist << currentDir; 04405 strlist << localStr; 04406 strlist << "-1"; // Ignore fsID 04407 strlist << dirID; 04408 strlist << QString::number(bSize); 04409 strlist << QString::number(totalKB); 04410 strlist << QString::number(usedKB); 04411 04412 foundDirs[currentDir] = true; 04413 } 04414 else 04415 foundDirs[currentDir] = false; 04416 } 04417 } 04418 } 04419 04420 if (allHosts) 04421 { 04422 QMap <QString, bool> backendsCounted; 04423 QString pbsHost; 04424 04425 list<PlaybackSock *> localPlaybackList; 04426 04427 sockListLock.lockForRead(); 04428 04429 vector<PlaybackSock *>::iterator pbsit = playbackList.begin(); 04430 for (; pbsit != playbackList.end(); ++pbsit) 04431 { 04432 PlaybackSock *pbs = *pbsit; 04433 04434 if ((pbs->IsDisconnected()) || 04435 (!pbs->isMediaServer()) || 04436 (pbs->isLocal()) || 04437 (backendsCounted.contains(pbs->getHostname()))) 04438 continue; 04439 04440 backendsCounted[pbs->getHostname()] = true; 04441 pbs->UpRef(); 04442 localPlaybackList.push_back(pbs); 04443 allHostList += "," + pbs->getHostname(); 04444 } 04445 04446 sockListLock.unlock(); 04447 04448 for (list<PlaybackSock *>::iterator p = localPlaybackList.begin() ; 04449 p != localPlaybackList.end() ; ++p) { 04450 (*p)->GetDiskSpace(strlist); 04451 (*p)->DownRef(); 04452 } 04453 } 04454 04455 if (!consolidated) 04456 return; 04457 04458 FileSystemInfo fsInfo; 04459 QList<FileSystemInfo> fsInfos; 04460 04461 QStringList::const_iterator it = strlist.begin(); 04462 while (it != strlist.end()) 04463 { 04464 fsInfo.setHostname(*(it++)); 04465 fsInfo.setPath(*(it++)); 04466 fsInfo.setLocal((*(it++)).toInt() > 0); 04467 fsInfo.setFSysID(-1); 04468 ++it; // Without this, the strlist gets out of whack 04469 fsInfo.setGroupID((*(it++)).toInt()); 04470 fsInfo.setBlockSize((*(it++)).toInt()); 04471 fsInfo.setTotalSpace((*(it++)).toLongLong()); 04472 fsInfo.setUsedSpace((*(it++)).toLongLong()); 04473 fsInfos.push_back(fsInfo); 04474 } 04475 strlist.clear(); 04476 04477 // Consolidate hosts sharing storage 04478 int64_t maxWriteFiveSec = GetCurrentMaxBitrate()/12 /*5 seconds*/; 04479 maxWriteFiveSec = max((int64_t)2048, maxWriteFiveSec); // safety for NFS mounted dirs 04480 QList<FileSystemInfo>::iterator it1, it2; 04481 int bSize = 32; 04482 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1) 04483 { 04484 if (it1->getFSysID() == -1) 04485 { 04486 it1->setFSysID(GetfsID(it1)); 04487 it1->setPath( 04488 it1->getHostname().section(".", 0, 0) + ":" + it1->getPath()); 04489 } 04490 04491 for (it2 = it1 + 1; it2 != fsInfos.end(); ++it2) 04492 { 04493 // our fuzzy comparison uses the maximum of the two block sizes 04494 // or 32, whichever is greater 04495 bSize = max(32, max(it1->getBlockSize(), it2->getBlockSize()) / 1024); 04496 int64_t diffSize = it1->getTotalSpace() - it2->getTotalSpace(); 04497 int64_t diffUsed = it1->getUsedSpace() - it2->getUsedSpace(); 04498 if (diffSize < 0) 04499 diffSize = 0 - diffSize; 04500 if (diffUsed < 0) 04501 diffUsed = 0 - diffUsed; 04502 04503 if (it2->getFSysID() == -1 && (diffSize <= bSize) && 04504 (diffUsed <= maxWriteFiveSec)) 04505 { 04506 if (!it1->getHostname().contains(it2->getHostname())) 04507 it1->setHostname(it1->getHostname() + "," + it2->getHostname()); 04508 it1->setPath(it1->getPath() + "," + 04509 it2->getHostname().section(".", 0, 0) + ":" + it2->getPath()); 04510 fsInfos.erase(it2); 04511 it2 = it1; 04512 } 04513 } 04514 } 04515 04516 // Passed the cleaned list back 04517 totalKB = 0; 04518 usedKB = 0; 04519 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1) 04520 { 04521 strlist << it1->getHostname(); 04522 strlist << it1->getPath(); 04523 strlist << QString::number(it1->isLocal()); 04524 strlist << QString::number(it1->getFSysID()); 04525 strlist << QString::number(it1->getGroupID()); 04526 strlist << QString::number(it1->getBlockSize()); 04527 strlist << QString::number(it1->getTotalSpace()); 04528 strlist << QString::number(it1->getUsedSpace()); 04529 04530 totalKB += it1->getTotalSpace(); 04531 usedKB += it1->getUsedSpace(); 04532 } 04533 04534 if (allHosts) 04535 { 04536 strlist << allHostList; 04537 strlist << "TotalDiskSpace"; 04538 strlist << "0"; 04539 strlist << "-2"; 04540 strlist << "-2"; 04541 strlist << "0"; 04542 strlist << QString::number(totalKB); 04543 strlist << QString::number(usedKB); 04544 } 04545 } 04546 04547 void MainServer::GetFilesystemInfos(QList<FileSystemInfo> &fsInfos) 04548 { 04549 QStringList strlist; 04550 FileSystemInfo fsInfo; 04551 04552 fsInfos.clear(); 04553 04554 BackendQueryDiskSpace(strlist, false, true); 04555 04556 QStringList::const_iterator it = strlist.begin(); 04557 while (it != strlist.end()) 04558 { 04559 fsInfo.setHostname(*(it++)); 04560 fsInfo.setPath(*(it++)); 04561 fsInfo.setLocal((*(it++)).toInt() > 0); 04562 fsInfo.setFSysID(-1); 04563 ++it; 04564 fsInfo.setGroupID((*(it++)).toInt()); 04565 fsInfo.setBlockSize((*(it++)).toInt()); 04566 fsInfo.setTotalSpace((*(it++)).toLongLong()); 04567 fsInfo.setUsedSpace((*(it++)).toLongLong()); 04568 fsInfo.setWeight(0); 04569 fsInfos.push_back(fsInfo); 04570 } 04571 04572 LOG(VB_SCHEDULE | VB_FILE, LOG_DEBUG, "Determining unique filesystems"); 04573 size_t maxWriteFiveSec = GetCurrentMaxBitrate()/12 /*5 seconds*/; 04574 // safety for NFS mounted dirs 04575 maxWriteFiveSec = max((size_t)2048, maxWriteFiveSec); 04576 04577 FileSystemInfo::Consolidate(fsInfos, false, maxWriteFiveSec); 04578 04579 QList<FileSystemInfo>::iterator it1; 04580 if (VERBOSE_LEVEL_CHECK(VB_FILE | VB_SCHEDULE, LOG_INFO)) 04581 { 04582 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, 04583 "--- GetFilesystemInfos directory list start ---"); 04584 for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1) 04585 { 04586 QString msg = QString("Dir: %1:%2") 04587 .arg(it1->getHostname()).arg(it1->getPath()); 04588 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg) ; 04589 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" Location: %1") 04590 .arg(it1->isLocal() ? "Local" : "Remote")); 04591 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" fsID : %1") 04592 .arg(it1->getFSysID())); 04593 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" dirID : %1") 04594 .arg(it1->getGroupID())); 04595 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" BlkSize : %1") 04596 .arg(it1->getBlockSize())); 04597 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" TotalKB : %1") 04598 .arg(it1->getTotalSpace())); 04599 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" UsedKB : %1") 04600 .arg(it1->getUsedSpace())); 04601 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" FreeKB : %1") 04602 .arg(it1->getFreeSpace())); 04603 } 04604 LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, 04605 "--- GetFilesystemInfos directory list end ---"); 04606 } 04607 } 04608 04609 void TruncateThread::run(void) 04610 { 04611 if (m_ms) 04612 m_ms->DoTruncateThread(this); 04613 } 04614 04615 void MainServer::DoTruncateThread(DeleteStruct *ds) 04616 { 04617 if (gCoreContext->GetNumSetting("TruncateDeletesSlowly", 0)) 04618 { 04619 TruncateAndClose(NULL, ds->m_fd, ds->m_filename, ds->m_size); 04620 } 04621 else 04622 { 04623 QMutexLocker dl(&deletelock); 04624 close(ds->m_fd); 04625 } 04626 } 04627 04628 bool MainServer::HandleDeleteFile(QStringList &slist, PlaybackSock *pbs) 04629 { 04630 return HandleDeleteFile(slist[1], slist[2], pbs); 04631 } 04632 04633 bool MainServer::HandleDeleteFile(QString filename, QString storagegroup, 04634 PlaybackSock *pbs) 04635 { 04636 StorageGroup sgroup(storagegroup, "", false); 04637 QStringList retlist; 04638 04639 if ((filename.isEmpty()) || 04640 (filename.contains("/../")) || 04641 (filename.startsWith("../"))) 04642 { 04643 LOG(VB_GENERAL, LOG_ERR, QString("ERROR deleting file, filename '%1' " 04644 "fails sanity checks").arg(filename)); 04645 if (pbs) 04646 { 04647 retlist << "0"; 04648 SendResponse(pbs->getSocket(), retlist); 04649 } 04650 return false; 04651 } 04652 04653 QString fullfile = sgroup.FindFile(filename); 04654 04655 if (fullfile.isEmpty()) { 04656 LOG(VB_GENERAL, LOG_ERR, 04657 QString("Unable to find %1 in HandleDeleteFile()") .arg(filename)); 04658 if (pbs) 04659 { 04660 retlist << "0"; 04661 SendResponse(pbs->getSocket(), retlist); 04662 } 04663 return false; 04664 } 04665 04666 QFile checkFile(fullfile); 04667 bool followLinks = gCoreContext->GetNumSetting("DeletesFollowLinks", 0); 04668 int fd = -1; 04669 off_t size = 0; 04670 04671 // This will open the file and unlink the dir entry. The actual file 04672 // data will be deleted in the truncate thread spawned below. 04673 // Since stat fails after unlinking on some filesystems, get the size first 04674 const QFileInfo info(fullfile); 04675 size = info.size(); 04676 fd = DeleteFile(fullfile, followLinks); 04677 04678 if ((fd < 0) && checkFile.exists()) 04679 { 04680 LOG(VB_GENERAL, LOG_ERR, QString("Error deleting file: %1.") 04681 .arg(fullfile)); 04682 if (pbs) 04683 { 04684 retlist << "0"; 04685 SendResponse(pbs->getSocket(), retlist); 04686 } 04687 return false; 04688 } 04689 04690 if (pbs) 04691 { 04692 retlist << "1"; 04693 SendResponse(pbs->getSocket(), retlist); 04694 } 04695 04696 // DeleteFile() opened up a file for us to delete 04697 if (fd >= 0) 04698 { 04699 // Thread off the actual file truncate 04700 TruncateThread *truncateThread = 04701 new TruncateThread(this, fullfile, fd, size); 04702 truncateThread->run(); 04703 } 04704 04705 return true; 04706 } 04707 04708 // Helper function for the guts of HandleCommBreakQuery + HandleCutlistQuery 04709 void MainServer::HandleCutMapQuery(const QString &chanid, 04710 const QString &starttime, 04711 PlaybackSock *pbs, bool commbreak) 04712 { 04713 MythSocket *pbssock = NULL; 04714 if (pbs) 04715 pbssock = pbs->getSocket(); 04716 04717 frm_dir_map_t markMap; 04718 frm_dir_map_t::const_iterator it; 04719 QDateTime recstartdt; 04720 recstartdt.setTime_t(starttime.toULongLong()); 04721 QStringList retlist; 04722 int rowcnt = 0; 04723 04724 const ProgramInfo pginfo(chanid.toUInt(), recstartdt); 04725 04726 if (pginfo.GetChanID()) 04727 { 04728 if (commbreak) 04729 pginfo.QueryCommBreakList(markMap); 04730 else 04731 pginfo.QueryCutList(markMap); 04732 04733 for (it = markMap.begin(); it != markMap.end(); ++it) 04734 { 04735 rowcnt++; 04736 QString intstr = QString("%1").arg(*it); 04737 retlist << intstr; 04738 retlist << QString::number(it.key()); 04739 } 04740 } 04741 04742 if (rowcnt > 0) 04743 retlist.prepend(QString("%1").arg(rowcnt)); 04744 else 04745 retlist << "-1"; 04746 04747 if (pbssock) 04748 SendResponse(pbssock, retlist); 04749 04750 return; 04751 } 04752 04753 void MainServer::HandleCommBreakQuery(const QString &chanid, 04754 const QString &starttime, 04755 PlaybackSock *pbs) 04756 { 04757 // Commercial break query 04758 // Format: QUERY_COMMBREAK <chanid> <starttime> 04759 // chanid is chanid, starttime is startime of program in 04760 // # of seconds since Jan 1, 1970, in UTC time. Same format as in 04761 // a ProgramInfo structure in a string list. 04762 // Return structure is [number of rows] followed by a triplet of values: 04763 // each triplet : [type] [long portion 1] [long portion 2] 04764 // type is the value in the map, right now 4 = commbreak start, 5= end 04765 return HandleCutMapQuery(chanid, starttime, pbs, true); 04766 } 04767 04768 void MainServer::HandleCutlistQuery(const QString &chanid, 04769 const QString &starttime, 04770 PlaybackSock *pbs) 04771 { 04772 // Cutlist query 04773 // Format: QUERY_CUTLIST <chanid> <starttime> 04774 // chanid is chanid, starttime is startime of program in 04775 // # of seconds since Jan 1, 1970, in UTC time. Same format as in 04776 // a ProgramInfo structure in a string list. 04777 // Return structure is [number of rows] followed by a triplet of values: 04778 // each triplet : [type] [long portion 1] [long portion 2] 04779 // type is the value in the map, right now 0 = commbreak start, 1 = end 04780 return HandleCutMapQuery(chanid, starttime, pbs, false); 04781 } 04782 04783 04784 void MainServer::HandleBookmarkQuery(const QString &chanid, 04785 const QString &starttime, 04786 PlaybackSock *pbs) 04787 // Bookmark query 04788 // Format: QUERY_BOOKMARK <chanid> <starttime> 04789 // chanid is chanid, starttime is startime of program in 04790 // # of seconds since Jan 1, 1970, in UTC time. Same format as in 04791 // a ProgramInfo structure in a string list. 04792 // Return value is a long-long encoded as two separate values 04793 { 04794 MythSocket *pbssock = NULL; 04795 if (pbs) 04796 pbssock = pbs->getSocket(); 04797 04798 QDateTime recstartts; 04799 recstartts.setTime_t(starttime.toULongLong()); 04800 04801 uint64_t bookmark = ProgramInfo::QueryBookmark( 04802 chanid.toUInt(), recstartts); 04803 04804 QStringList retlist; 04805 retlist << QString::number(bookmark); 04806 04807 if (pbssock) 04808 SendResponse(pbssock, retlist); 04809 04810 return; 04811 } 04812 04813 04814 void MainServer::HandleSetBookmark(QStringList &tokens, 04815 PlaybackSock *pbs) 04816 { 04817 // Bookmark query 04818 // Format: SET_BOOKMARK <chanid> <starttime> <long part1> <long part2> 04819 // chanid is chanid, starttime is startime of program in 04820 // # of seconds since Jan 1, 1970, in UTC time. Same format as in 04821 // a ProgramInfo structure in a string list. The two longs are the two 04822 // portions of the bookmark value to set. 04823 04824 MythSocket *pbssock = NULL; 04825 if (pbs) 04826 pbssock = pbs->getSocket(); 04827 04828 QString chanid = tokens[1]; 04829 QString starttime = tokens[2]; 04830 long long bookmark = tokens[3].toLongLong(); 04831 04832 QDateTime recstartts; 04833 recstartts.setTime_t(starttime.toULongLong()); 04834 QStringList retlist; 04835 04836 ProgramInfo pginfo(chanid.toUInt(), recstartts); 04837 04838 if (pginfo.GetChanID()) 04839 { 04840 pginfo.SaveBookmark(bookmark); 04841 retlist << "OK"; 04842 } 04843 else 04844 retlist << "FAILED"; 04845 04846 if (pbssock) 04847 SendResponse(pbssock, retlist); 04848 04849 return; 04850 } 04851 04852 void MainServer::HandleSettingQuery(QStringList &tokens, PlaybackSock *pbs) 04853 { 04854 // Format: QUERY_SETTING <hostname> <setting> 04855 // Returns setting value as a string 04856 04857 MythSocket *pbssock = NULL; 04858 if (pbs) 04859 pbssock = pbs->getSocket(); 04860 04861 QString hostname = tokens[1]; 04862 QString setting = tokens[2]; 04863 QStringList retlist; 04864 04865 QString retvalue = gCoreContext->GetSettingOnHost(setting, hostname, "-1"); 04866 04867 retlist << retvalue; 04868 if (pbssock) 04869 SendResponse(pbssock, retlist); 04870 04871 return; 04872 } 04873 04874 void MainServer::HandleDownloadFile(const QStringList &command, 04875 PlaybackSock *pbs) 04876 { 04877 bool synchronous = (command[0] == "DOWNLOAD_FILE_NOW"); 04878 QString srcURL = command[1]; 04879 QString storageGroup = command[2]; 04880 QString filename = command[3]; 04881 StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false); 04882 QString outDir = sgroup.FindNextDirMostFree(); 04883 QString outFile; 04884 QStringList retlist; 04885 04886 MythSocket *pbssock = NULL; 04887 if (pbs) 04888 pbssock = pbs->getSocket(); 04889 04890 if (filename.isEmpty()) 04891 { 04892 QFileInfo finfo(srcURL); 04893 filename = finfo.fileName(); 04894 } 04895 04896 if (outDir.isEmpty()) 04897 { 04898 LOG(VB_GENERAL, LOG_ERR, 04899 QString("Unable to determine directory " 04900 "to write to in %1 write command").arg(command[0])); 04901 retlist << "downloadfile_directory_not_found"; 04902 if (pbssock) 04903 SendResponse(pbssock, retlist); 04904 return; 04905 } 04906 04907 if ((filename.contains("/../")) || 04908 (filename.startsWith("../"))) 04909 { 04910 LOG(VB_GENERAL, LOG_ERR, 04911 QString("ERROR: %1 write filename '%2' does not pass " 04912 "sanity checks.") .arg(command[0]).arg(filename)); 04913 retlist << "downloadfile_filename_dangerous"; 04914 if (pbssock) 04915 SendResponse(pbssock, retlist); 04916 return; 04917 } 04918 04919 outFile = outDir + "/" + filename; 04920 04921 if (synchronous) 04922 { 04923 if (GetMythDownloadManager()->download(srcURL, outFile)) 04924 { 04925 retlist << "OK"; 04926 retlist << gCoreContext->GetMasterHostPrefix(storageGroup) 04927 + filename; 04928 } 04929 else 04930 retlist << "ERROR"; 04931 } 04932 else 04933 { 04934 QMutexLocker locker(&m_downloadURLsLock); 04935 m_downloadURLs[outFile] = 04936 gCoreContext->GetMasterHostPrefix(storageGroup) + 04937 StorageGroup::GetRelativePathname(outFile); 04938 04939 GetMythDownloadManager()->queueDownload(srcURL, outFile, this); 04940 retlist << "OK"; 04941 retlist << gCoreContext->GetMasterHostPrefix(storageGroup) + filename; 04942 } 04943 04944 if (pbssock) 04945 SendResponse(pbssock, retlist); 04946 } 04947 04948 void MainServer::HandleSetSetting(QStringList &tokens, 04949 PlaybackSock *pbs) 04950 { 04951 // Format: SET_SETTING <hostname> <setting> <value> 04952 MythSocket *pbssock = NULL; 04953 if (pbs) 04954 pbssock = pbs->getSocket(); 04955 04956 QString hostname = tokens[1]; 04957 QString setting = tokens[2]; 04958 QString svalue = tokens[3]; 04959 QStringList retlist; 04960 04961 if (gCoreContext->SaveSettingOnHost(setting, svalue, hostname)) 04962 retlist << "OK"; 04963 else 04964 retlist << "ERROR"; 04965 04966 if (pbssock) 04967 SendResponse(pbssock, retlist); 04968 04969 return; 04970 } 04971 04972 void MainServer::HandleScanVideos(PlaybackSock *pbs) 04973 { 04974 MythSocket *pbssock = pbs->getSocket(); 04975 04976 QStringList retlist; 04977 04978 if (metadatafactory) 04979 { 04980 QStringList hosts; 04981 GetActiveBackends(hosts); 04982 metadatafactory->VideoScan(hosts); 04983 retlist << "OK"; 04984 } 04985 else 04986 retlist << "ERROR"; 04987 04988 if (pbssock) 04989 SendResponse(pbssock, retlist); 04990 } 04991 04992 void MainServer::HandleFileTransferQuery(QStringList &slist, 04993 QStringList &commands, 04994 PlaybackSock *pbs) 04995 { 04996 MythSocket *pbssock = pbs->getSocket(); 04997 04998 int recnum = commands[1].toInt(); 04999 QString command = slist[1]; 05000 05001 QStringList retlist; 05002 05003 sockListLock.lockForRead(); 05004 FileTransfer *ft = GetFileTransferByID(recnum); 05005 if (!ft) 05006 { 05007 if (command == "DONE") 05008 { 05009 // if there is an error opening the file, we may not have a 05010 // FileTransfer instance for this connection. 05011 retlist << "ok"; 05012 } 05013 else 05014 { 05015 LOG(VB_GENERAL, LOG_ERR, QString("Unknown file transfer socket: %1") 05016 .arg(recnum)); 05017 retlist << QString("ERROR: Unknown file transfer socket: %1") 05018 .arg(recnum); 05019 } 05020 05021 sockListLock.unlock(); 05022 SendResponse(pbssock, retlist); 05023 return; 05024 } 05025 05026 ft->UpRef(); 05027 sockListLock.unlock(); 05028 05029 if (command == "IS_OPEN") 05030 { 05031 bool isopen = ft->isOpen(); 05032 05033 retlist << QString::number(isopen); 05034 } 05035 else if (command == "REOPEN") 05036 { 05037 retlist << QString::number(ft->ReOpen(slist[2])); 05038 } 05039 else if (command == "DONE") 05040 { 05041 ft->Stop(); 05042 retlist << "ok"; 05043 } 05044 else if (command == "REQUEST_BLOCK") 05045 { 05046 int size = slist[2].toInt(); 05047 05048 retlist << QString::number(ft->RequestBlock(size)); 05049 } 05050 else if (command == "WRITE_BLOCK") 05051 { 05052 int size = slist[2].toInt(); 05053 05054 retlist << QString::number(ft->WriteBlock(size)); 05055 } 05056 else if (command == "SEEK") 05057 { 05058 long long pos = slist[2].toLongLong(); 05059 int whence = slist[3].toInt(); 05060 long long curpos = slist[4].toLongLong(); 05061 05062 long long ret = ft->Seek(curpos, pos, whence); 05063 retlist << QString::number(ret); 05064 } 05065 else if (command == "SET_TIMEOUT") 05066 { 05067 bool fast = slist[2].toInt(); 05068 ft->SetTimeout(fast); 05069 retlist << "ok"; 05070 } 05071 else 05072 { 05073 LOG(VB_GENERAL, LOG_ERR, QString("Unknown command: %1").arg(command)); 05074 retlist << "ok"; 05075 } 05076 05077 ft->DownRef(); 05078 05079 SendResponse(pbssock, retlist); 05080 } 05081 05082 void MainServer::HandleGetRecorderNum(QStringList &slist, PlaybackSock *pbs) 05083 { 05084 MythSocket *pbssock = pbs->getSocket(); 05085 05086 int retval = -1; 05087 05088 QStringList::const_iterator it = slist.begin() + 1; 05089 ProgramInfo pginfo(it, slist.end()); 05090 05091 EncoderLink *encoder = NULL; 05092 05093 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 05094 for (; iter != encoderList->end(); ++iter) 05095 { 05096 EncoderLink *elink = *iter; 05097 05098 if (elink->IsConnected() && elink->MatchesRecording(&pginfo)) 05099 { 05100 retval = iter.key(); 05101 encoder = elink; 05102 } 05103 } 05104 05105 QStringList strlist( QString::number(retval) ); 05106 05107 if (encoder) 05108 { 05109 if (encoder->IsLocal()) 05110 { 05111 strlist << gCoreContext->GetBackendServerIP(); 05112 strlist << gCoreContext->GetSetting("BackendServerPort"); 05113 } 05114 else 05115 { 05116 strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName()); 05117 strlist << gCoreContext->GetSettingOnHost("BackendServerPort", 05118 encoder->GetHostName(), "-1"); 05119 } 05120 } 05121 else 05122 { 05123 strlist << "nohost"; 05124 strlist << "-1"; 05125 } 05126 05127 SendResponse(pbssock, strlist); 05128 } 05129 05130 void MainServer::HandleGetRecorderFromNum(QStringList &slist, 05131 PlaybackSock *pbs) 05132 { 05133 MythSocket *pbssock = pbs->getSocket(); 05134 05135 int recordernum = slist[1].toInt(); 05136 EncoderLink *encoder = NULL; 05137 QStringList strlist; 05138 05139 QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recordernum); 05140 05141 if (iter != encoderList->end()) 05142 encoder = (*iter); 05143 05144 if (encoder && encoder->IsConnected()) 05145 { 05146 if (encoder->IsLocal()) 05147 { 05148 strlist << gCoreContext->GetBackendServerIP(); 05149 strlist << gCoreContext->GetSetting("BackendServerPort"); 05150 } 05151 else 05152 { 05153 strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName()); 05154 strlist << gCoreContext->GetSettingOnHost("BackendServerPort", 05155 encoder->GetHostName(), "-1"); 05156 } 05157 } 05158 else 05159 { 05160 strlist << "nohost"; 05161 strlist << "-1"; 05162 } 05163 05164 SendResponse(pbssock, strlist); 05165 } 05166 05167 void MainServer::HandleMessage(QStringList &slist, PlaybackSock *pbs) 05168 { 05169 if (slist.size() < 2) 05170 return; 05171 05172 MythSocket *pbssock = pbs->getSocket(); 05173 05174 QString message = slist[1]; 05175 QStringList extra_data; 05176 for (uint i = 2; i < (uint) slist.size(); i++) 05177 extra_data.push_back(slist[i]); 05178 05179 if (extra_data.empty()) 05180 { 05181 MythEvent me(message); 05182 gCoreContext->dispatch(me); 05183 } 05184 else 05185 { 05186 MythEvent me(message, extra_data); 05187 gCoreContext->dispatch(me); 05188 } 05189 05190 QStringList retlist( "OK" ); 05191 05192 SendResponse(pbssock, retlist); 05193 } 05194 05195 void MainServer::HandleSetVerbose(QStringList &slist, PlaybackSock *pbs) 05196 { 05197 MythSocket *pbssock = pbs->getSocket(); 05198 QStringList retlist; 05199 05200 QString newverbose = slist[1]; 05201 int len = newverbose.length(); 05202 if (len > 12) 05203 { 05204 verboseArgParse(newverbose.right(len-12)); 05205 logPropagateCalc(); 05206 05207 LOG(VB_GENERAL, LOG_NOTICE, 05208 QString("Verbose mask changed, new mask is: %1") 05209 .arg(verboseString)); 05210 05211 retlist << "OK"; 05212 } 05213 else 05214 { 05215 LOG(VB_GENERAL, LOG_ERR, QString("Invalid SET_VERBOSE string: '%1'") 05216 .arg(newverbose)); 05217 retlist << "Failed"; 05218 } 05219 05220 SendResponse(pbssock, retlist); 05221 } 05222 05223 void MainServer::HandleSetLogLevel(QStringList &slist, PlaybackSock *pbs) 05224 { 05225 MythSocket *pbssock = pbs->getSocket(); 05226 QStringList retlist; 05227 QString newstring = slist[1]; 05228 LogLevel_t newlevel = LOG_UNKNOWN; 05229 05230 int len = newstring.length(); 05231 if (len > 14) 05232 { 05233 newlevel = logLevelGet(newstring.right(len-14)); 05234 if (newlevel != LOG_UNKNOWN) 05235 { 05236 logLevel = newlevel; 05237 logPropagateCalc(); 05238 LOG(VB_GENERAL, LOG_NOTICE, 05239 QString("Log level changed, new level is: %1") 05240 .arg(logLevelGetName(logLevel))); 05241 05242 retlist << "OK"; 05243 } 05244 } 05245 05246 if (newlevel == LOG_UNKNOWN) 05247 { 05248 LOG(VB_GENERAL, LOG_ERR, QString("Invalid SET_VERBOSE string: '%1'") 05249 .arg(newstring)); 05250 retlist << "Failed"; 05251 } 05252 05253 SendResponse(pbssock, retlist); 05254 } 05255 05256 void MainServer::HandleIsRecording(QStringList &slist, PlaybackSock *pbs) 05257 { 05258 (void)slist; 05259 05260 MythSocket *pbssock = pbs->getSocket(); 05261 int RecordingsInProgress = 0; 05262 int LiveTVRecordingsInProgress = 0; 05263 QStringList retlist; 05264 05265 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 05266 for (; iter != encoderList->end(); ++iter) 05267 { 05268 EncoderLink *elink = *iter; 05269 if (elink->IsBusyRecording()) { 05270 RecordingsInProgress++; 05271 05272 ProgramInfo *info = elink->GetRecording(); 05273 if (info && info->GetRecordingGroup() == "LiveTV") 05274 LiveTVRecordingsInProgress++; 05275 05276 delete info; 05277 } 05278 } 05279 05280 retlist << QString::number(RecordingsInProgress); 05281 retlist << QString::number(LiveTVRecordingsInProgress); 05282 05283 SendResponse(pbssock, retlist); 05284 } 05285 05286 void MainServer::HandleGenPreviewPixmap(QStringList &slist, PlaybackSock *pbs) 05287 { 05288 MythSocket *pbssock = pbs->getSocket(); 05289 05290 if (slist.size() < 3) 05291 { 05292 LOG(VB_GENERAL, LOG_ERR, LOC + "Too few params in pixmap request"); 05293 QStringList outputlist("ERROR"); 05294 outputlist += "TOO_FEW_PARAMS"; 05295 SendResponse(pbssock, outputlist); 05296 return; 05297 } 05298 05299 bool time_fmt_sec = true; 05300 long long time = -1; 05301 QString outputfile; 05302 int width = -1; 05303 int height = -1; 05304 bool has_extra_data = false; 05305 05306 QString token = slist[1]; 05307 if (token.isEmpty()) 05308 { 05309 LOG(VB_GENERAL, LOG_ERR, LOC + 05310 "Failed to parse pixmap request. Token absent"); 05311 QStringList outputlist("ERROR"); 05312 outputlist += "TOKEN_ABSENT"; 05313 SendResponse(pbssock, outputlist); 05314 return; 05315 } 05316 05317 QStringList::const_iterator it = slist.begin() + 2; 05318 QStringList::const_iterator end = slist.end(); 05319 ProgramInfo pginfo(it, end); 05320 bool ok = pginfo.HasPathname(); 05321 if (!ok) 05322 { 05323 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to parse pixmap request. " 05324 "ProgramInfo missing pathname"); 05325 QStringList outputlist("BAD"); 05326 outputlist += "NO_PATHNAME"; 05327 SendResponse(pbssock, outputlist); 05328 return; 05329 } 05330 if (token.toLower() == "do_not_care") 05331 { 05332 token = QString("%1:%2") 05333 .arg(pginfo.MakeUniqueKey()).arg(random()); 05334 } 05335 if (it != slist.end()) 05336 (time_fmt_sec = ((*it).toLower() == "s")), ++it; 05337 if (it != slist.end()) 05338 (time = (*it).toLongLong()), ++it; 05339 if (it != slist.end()) 05340 (outputfile = *it), ++it; 05341 outputfile = (outputfile == "<EMPTY>") ? QString::null : outputfile; 05342 if (it != slist.end()) 05343 { 05344 width = (*it).toInt(&ok); ++it; 05345 width = (ok) ? width : -1; 05346 } 05347 if (it != slist.end()) 05348 { 05349 height = (*it).toInt(&ok); ++it; 05350 height = (ok) ? height : -1; 05351 has_extra_data = true; 05352 } 05353 QSize outputsize = QSize(width, height); 05354 05355 if (has_extra_data) 05356 { 05357 LOG(VB_PLAYBACK, LOG_INFO, 05358 QString("HandleGenPreviewPixmap got extra data\n\t\t\t" 05359 "%1%2 %3x%4 '%5'") 05360 .arg(time).arg(time_fmt_sec?"s":"f") 05361 .arg(width).arg(height).arg(outputfile)); 05362 } 05363 05364 pginfo.SetPathname(GetPlaybackURL(&pginfo)); 05365 05366 m_previewRequestedBy[token] = pbs->getHostname(); 05367 05368 if ((ismaster) && 05369 (pginfo.GetHostname() != gCoreContext->GetHostName()) && 05370 (!masterBackendOverride || !pginfo.IsLocal())) 05371 { 05372 PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname()); 05373 05374 if (slave) 05375 { 05376 QStringList outputlist; 05377 if (has_extra_data) 05378 { 05379 outputlist = slave->GenPreviewPixmap( 05380 token, &pginfo, time_fmt_sec, time, outputfile, outputsize); 05381 } 05382 else 05383 { 05384 outputlist = slave->GenPreviewPixmap(token, &pginfo); 05385 } 05386 05387 slave->DownRef(); 05388 05389 if (outputlist.empty() || outputlist[0] != "OK") 05390 m_previewRequestedBy.remove(token); 05391 05392 SendResponse(pbssock, outputlist); 05393 return; 05394 } 05395 LOG(VB_GENERAL, LOG_ERR, LOC + 05396 QString("HandleGenPreviewPixmap() " 05397 "Couldn't find backend for:\n\t\t\t%1") 05398 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle))); 05399 } 05400 05401 if (!pginfo.IsLocal()) 05402 { 05403 LOG(VB_GENERAL, LOG_ERR, LOC + "HandleGenPreviewPixmap: Unable to " 05404 "find file locally, unable to make preview image."); 05405 QStringList outputlist( "ERROR" ); 05406 outputlist += "FILE_INACCESSIBLE"; 05407 SendResponse(pbssock, outputlist); 05408 m_previewRequestedBy.remove(token); 05409 return; 05410 } 05411 05412 if (has_extra_data) 05413 { 05414 PreviewGeneratorQueue::GetPreviewImage( 05415 pginfo, outputsize, outputfile, time, time_fmt_sec, token); 05416 } 05417 else 05418 { 05419 PreviewGeneratorQueue::GetPreviewImage(pginfo, token); 05420 } 05421 05422 QStringList outputlist("OK"); 05423 if (!outputfile.isEmpty()) 05424 outputlist += outputfile; 05425 SendResponse(pbssock, outputlist); 05426 } 05427 05428 void MainServer::HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs) 05429 { 05430 MythSocket *pbssock = pbs->getSocket(); 05431 05432 QStringList::const_iterator it = slist.begin() + 1; 05433 ProgramInfo pginfo(it, slist.end()); 05434 05435 pginfo.SetPathname(GetPlaybackURL(&pginfo)); 05436 05437 QStringList strlist; 05438 05439 if (ismaster && 05440 (pginfo.GetHostname() != gCoreContext->GetHostName()) && 05441 (!masterBackendOverride || !pginfo.IsLocal())) 05442 { 05443 PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname()); 05444 05445 if (slave) 05446 { 05447 QDateTime slavetime = slave->PixmapLastModified(&pginfo); 05448 slave->DownRef(); 05449 05450 strlist = (slavetime.isValid()) ? 05451 QStringList(QString::number(slavetime.toTime_t())) : 05452 QStringList("BAD"); 05453 05454 SendResponse(pbssock, strlist); 05455 return; 05456 } 05457 else 05458 { 05459 LOG(VB_GENERAL, LOG_ERR, LOC + 05460 QString("HandlePixmapLastModified() " 05461 "Couldn't find backend for:\n\t\t\t%1") 05462 .arg(pginfo.toString(ProgramInfo::kTitleSubtitle))); 05463 } 05464 } 05465 05466 if (!pginfo.IsLocal()) 05467 { 05468 LOG(VB_GENERAL, LOG_ERR, 05469 "MainServer: HandlePixmapLastModified: Unable to " 05470 "find file locally, unable to get last modified date."); 05471 QStringList outputlist( "BAD" ); 05472 SendResponse(pbssock, outputlist); 05473 return; 05474 } 05475 05476 QString filename = pginfo.GetPathname() + ".png"; 05477 05478 QFileInfo finfo(filename); 05479 05480 if (finfo.exists()) 05481 { 05482 QDateTime lastmodified = finfo.lastModified(); 05483 strlist = QStringList(QString::number(lastmodified.toTime_t())); 05484 } 05485 else 05486 strlist = QStringList( "BAD" ); 05487 05488 SendResponse(pbssock, strlist); 05489 } 05490 05491 void MainServer::HandlePixmapGetIfModified( 05492 const QStringList &slist, PlaybackSock *pbs) 05493 { 05494 QStringList strlist; 05495 05496 MythSocket *pbssock = pbs->getSocket(); 05497 if (slist.size() < (3 + NUMPROGRAMLINES)) 05498 { 05499 strlist = QStringList("ERROR"); 05500 strlist += "1: Parameter list too short"; 05501 SendResponse(pbssock, strlist); 05502 return; 05503 } 05504 05505 QDateTime cachemodified; 05506 if (slist[1].toInt() != -1) 05507 cachemodified.setTime_t(slist[1].toInt()); 05508 05509 int max_file_size = slist[2].toInt(); 05510 05511 QStringList::const_iterator it = slist.begin() + 3; 05512 ProgramInfo pginfo(it, slist.end()); 05513 05514 if (!pginfo.HasPathname()) 05515 { 05516 strlist = QStringList("ERROR"); 05517 strlist += "2: Invalid ProgramInfo"; 05518 SendResponse(pbssock, strlist); 05519 return; 05520 } 05521 05522 pginfo.SetPathname(GetPlaybackURL(&pginfo) + ".png"); 05523 if (pginfo.IsLocal()) 05524 { 05525 QFileInfo finfo(pginfo.GetPathname()); 05526 if (finfo.exists()) 05527 { 05528 size_t fsize = finfo.size(); 05529 QDateTime lastmodified = finfo.lastModified(); 05530 bool out_of_date = !cachemodified.isValid() || 05531 (lastmodified > cachemodified); 05532 05533 if (out_of_date && (fsize > 0) && ((ssize_t)fsize < max_file_size)) 05534 { 05535 QByteArray data; 05536 QFile file(pginfo.GetPathname()); 05537 bool open_ok = file.open(QIODevice::ReadOnly); 05538 if (open_ok) 05539 data = file.readAll(); 05540 05541 if (data.size()) 05542 { 05543 LOG(VB_FILE, LOG_INFO, QString("Read preview file '%1'") 05544 .arg(pginfo.GetPathname())); 05545 strlist += QString::number(lastmodified.toTime_t()); 05546 strlist += QString::number(data.size()); 05547 strlist += QString::number(qChecksum(data.constData(), 05548 data.size())); 05549 strlist += QString(data.toBase64()); 05550 } 05551 else 05552 { 05553 LOG(VB_GENERAL, LOG_ERR, 05554 QString("Failed to read preview file '%1'") 05555 .arg(pginfo.GetPathname())); 05556 05557 strlist = QStringList("ERROR"); 05558 strlist += 05559 QString("3: Failed to read preview file '%1'%2") 05560 .arg(pginfo.GetPathname()) 05561 .arg((open_ok) ? "" : " open failed"); 05562 } 05563 } 05564 else if (out_of_date && (max_file_size > 0)) 05565 { 05566 if (fsize >= (size_t) max_file_size) 05567 { 05568 strlist = QStringList("WARNING"); 05569 strlist += QString("1: Preview file too big %1 > %2") 05570 .arg(fsize).arg(max_file_size); 05571 } 05572 else 05573 { 05574 strlist = QStringList("ERROR"); 05575 strlist += "4: Preview file is invalid"; 05576 } 05577 } 05578 else 05579 { 05580 strlist += QString::number(lastmodified.toTime_t()); 05581 } 05582 05583 SendResponse(pbssock, strlist); 05584 return; 05585 } 05586 } 05587 05588 // handle remote ... 05589 if (ismaster && pginfo.GetHostname() != gCoreContext->GetHostName()) 05590 { 05591 PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname()); 05592 if (!slave) 05593 { 05594 strlist = QStringList("ERROR"); 05595 strlist += 05596 "5: Could not locate mythbackend that made this recording"; 05597 SendResponse(pbssock, strlist); 05598 return; 05599 } 05600 05601 strlist = slave->ForwardRequest(slist); 05602 05603 slave->DownRef(); slave = NULL; 05604 05605 if (!strlist.empty()) 05606 { 05607 SendResponse(pbssock, strlist); 05608 return; 05609 } 05610 } 05611 05612 strlist = QStringList("WARNING"); 05613 strlist += "2: Could not locate requested file"; 05614 SendResponse(pbssock, strlist); 05615 } 05616 05617 void MainServer::HandleBackendRefresh(MythSocket *socket) 05618 { 05619 QStringList retlist( "OK" ); 05620 SendResponse(socket, retlist); 05621 } 05622 05623 void MainServer::HandleBlockShutdown(bool blockShutdown, PlaybackSock *pbs) 05624 { 05625 pbs->setBlockShutdown(blockShutdown); 05626 05627 MythSocket *socket = pbs->getSocket(); 05628 QStringList retlist( "OK" ); 05629 SendResponse(socket, retlist); 05630 } 05631 05632 void MainServer::deferredDeleteSlot(void) 05633 { 05634 QMutexLocker lock(&deferredDeleteLock); 05635 05636 if (deferredDeleteList.empty()) 05637 return; 05638 05639 DeferredDeleteStruct dds = deferredDeleteList.front(); 05640 while (dds.ts.secsTo(QDateTime::currentDateTime()) > 30) 05641 { 05642 delete dds.sock; 05643 deferredDeleteList.pop_front(); 05644 if (deferredDeleteList.empty()) 05645 return; 05646 dds = deferredDeleteList.front(); 05647 } 05648 } 05649 05650 void MainServer::DeletePBS(PlaybackSock *sock) 05651 { 05652 DeferredDeleteStruct dds; 05653 dds.sock = sock; 05654 dds.ts = QDateTime::currentDateTime(); 05655 05656 QMutexLocker lock(&deferredDeleteLock); 05657 deferredDeleteList.push_back(dds); 05658 } 05659 05660 void MainServer::connectionClosed(MythSocket *socket) 05661 { 05662 sockListLock.lockForWrite(); 05663 05664 vector<PlaybackSock *>::iterator it = playbackList.begin(); 05665 for (; it != playbackList.end(); ++it) 05666 { 05667 PlaybackSock *pbs = (*it); 05668 MythSocket *sock = pbs->getSocket(); 05669 if (sock == socket && pbs == masterServer) 05670 { 05671 playbackList.erase(it); 05672 sockListLock.unlock(); 05673 masterServer->DownRef(); 05674 masterServer = NULL; 05675 MythEvent me("LOCAL_RECONNECT_TO_MASTER"); 05676 gCoreContext->dispatch(me); 05677 return; 05678 } 05679 else if (sock == socket) 05680 { 05681 QList<uint> disconnectedSlaves; 05682 bool needsReschedule = false; 05683 05684 if (ismaster && pbs->isSlaveBackend()) 05685 { 05686 LOG(VB_GENERAL, LOG_ERR, 05687 QString("Slave backend: %1 no longer connected") 05688 .arg(pbs->getHostname())); 05689 05690 bool isFallingAsleep = true; 05691 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 05692 for (; iter != encoderList->end(); ++iter) 05693 { 05694 EncoderLink *elink = *iter; 05695 if (elink->GetSocket() == pbs) 05696 { 05697 if (!elink->IsFallingAsleep()) 05698 isFallingAsleep = false; 05699 05700 elink->SetSocket(NULL); 05701 if (m_sched) 05702 disconnectedSlaves.push_back(elink->GetCardID()); 05703 } 05704 } 05705 if (m_sched && !isFallingAsleep) 05706 needsReschedule = true; 05707 05708 QString message = QString("LOCAL_SLAVE_BACKEND_OFFLINE %1") 05709 .arg(pbs->getHostname()); 05710 MythEvent me(message); 05711 gCoreContext->dispatch(me); 05712 05713 MythEvent me2("RECORDING_LIST_CHANGE"); 05714 gCoreContext->dispatch(me2); 05715 05716 gCoreContext->SendSystemEvent( 05717 QString("SLAVE_DISCONNECTED HOSTNAME %1") 05718 .arg(pbs->getHostname())); 05719 } 05720 else if (ismaster && pbs->getHostname() != "tzcheck") 05721 { 05722 gCoreContext->SendSystemEvent( 05723 QString("CLIENT_DISCONNECTED HOSTNAME %1") 05724 .arg(pbs->getHostname())); 05725 } 05726 05727 LiveTVChain *chain; 05728 if ((chain = GetExistingChain(sock))) 05729 { 05730 chain->DelHostSocket(sock); 05731 if (chain->HostSocketCount() == 0) 05732 { 05733 QMap<int, EncoderLink *>::iterator it = 05734 encoderList->begin(); 05735 for (; it != encoderList->end(); ++it) 05736 { 05737 EncoderLink *enc = *it; 05738 if (enc->IsLocal()) 05739 { 05740 while (enc->GetState() == kState_ChangingState) 05741 usleep(500); 05742 05743 if (enc->IsBusy() && 05744 enc->GetChainID() == chain->GetID()) 05745 { 05746 enc->StopLiveTV(); 05747 } 05748 } 05749 } 05750 DeleteChain(chain); 05751 } 05752 } 05753 05754 pbs->SetDisconnected(); 05755 playbackList.erase(it); 05756 05757 PlaybackSock *testsock = GetPlaybackBySock(socket); 05758 if (testsock) 05759 LOG(VB_GENERAL, LOG_ERR, "Playback sock still exists?"); 05760 05761 sockListLock.unlock(); 05762 05763 // Since we may already be holding the scheduler lock 05764 // delay handling the disconnect until a little later. #9885 05765 SendSlaveDisconnectedEvent(disconnectedSlaves, needsReschedule); 05766 05767 pbs->DownRef(); 05768 return; 05769 } 05770 } 05771 05772 vector<FileTransfer *>::iterator ft = fileTransferList.begin(); 05773 for (; ft != fileTransferList.end(); ++ft) 05774 { 05775 MythSocket *sock = (*ft)->getSocket(); 05776 if (sock == socket) 05777 { 05778 (*ft)->DownRef(); 05779 fileTransferList.erase(ft); 05780 sockListLock.unlock(); 05781 return; 05782 } 05783 } 05784 05785 sockListLock.unlock(); 05786 05787 LOG(VB_GENERAL, LOG_WARNING, LOC + 05788 QString("Unknown socket closing MythSocket(0x%1)") 05789 .arg((uint64_t)socket,0,16)); 05790 } 05791 05792 PlaybackSock *MainServer::GetSlaveByHostname(const QString &hostname) 05793 { 05794 if (!ismaster) 05795 return NULL; 05796 05797 sockListLock.lockForRead(); 05798 05799 vector<PlaybackSock *>::iterator iter = playbackList.begin(); 05800 for (; iter != playbackList.end(); ++iter) 05801 { 05802 PlaybackSock *pbs = *iter; 05803 if (pbs->isSlaveBackend() && 05804 ((pbs->getHostname().toLower() == hostname.toLower()) || 05805 (gCoreContext->IsThisHost(hostname, pbs->getHostname())))) 05806 { 05807 sockListLock.unlock(); 05808 pbs->UpRef(); 05809 return pbs; 05810 } 05811 } 05812 05813 sockListLock.unlock(); 05814 05815 return NULL; 05816 } 05817 05818 PlaybackSock *MainServer::GetMediaServerByHostname(const QString &hostname) 05819 { 05820 if (!ismaster) 05821 return NULL; 05822 05823 QReadLocker rlock(&sockListLock); 05824 05825 vector<PlaybackSock *>::iterator iter = playbackList.begin(); 05826 for (; iter != playbackList.end(); ++iter) 05827 { 05828 PlaybackSock *pbs = *iter; 05829 if (pbs->isMediaServer() && 05830 ((pbs->getHostname().toLower() == hostname.toLower()) || 05831 (gCoreContext->IsThisHost(hostname, pbs->getHostname())))) 05832 { 05833 pbs->UpRef(); 05834 return pbs; 05835 } 05836 } 05837 05838 return NULL; 05839 } 05840 05842 PlaybackSock *MainServer::GetPlaybackBySock(MythSocket *sock) 05843 { 05844 PlaybackSock *retval = NULL; 05845 05846 vector<PlaybackSock *>::iterator it = playbackList.begin(); 05847 for (; it != playbackList.end(); ++it) 05848 { 05849 if (sock == (*it)->getSocket()) 05850 { 05851 retval = (*it); 05852 break; 05853 } 05854 } 05855 05856 return retval; 05857 } 05858 05860 FileTransfer *MainServer::GetFileTransferByID(int id) 05861 { 05862 FileTransfer *retval = NULL; 05863 05864 vector<FileTransfer *>::iterator it = fileTransferList.begin(); 05865 for (; it != fileTransferList.end(); ++it) 05866 { 05867 if (id == (*it)->getSocket()->socket()) 05868 { 05869 retval = (*it); 05870 break; 05871 } 05872 } 05873 05874 return retval; 05875 } 05876 05878 FileTransfer *MainServer::GetFileTransferBySock(MythSocket *sock) 05879 { 05880 FileTransfer *retval = NULL; 05881 05882 vector<FileTransfer *>::iterator it = fileTransferList.begin(); 05883 for (; it != fileTransferList.end(); ++it) 05884 { 05885 if (sock == (*it)->getSocket()) 05886 { 05887 retval = (*it); 05888 break; 05889 } 05890 } 05891 05892 return retval; 05893 } 05894 05895 LiveTVChain *MainServer::GetExistingChain(const QString &id) 05896 { 05897 QMutexLocker lock(&liveTVChainsLock); 05898 05899 vector<LiveTVChain*>::iterator it = liveTVChains.begin(); 05900 for (; it != liveTVChains.end(); ++it) 05901 { 05902 if ((*it)->GetID() == id) 05903 return *it; 05904 } 05905 05906 return NULL; 05907 } 05908 05909 LiveTVChain *MainServer::GetExistingChain(const MythSocket *sock) 05910 { 05911 QMutexLocker lock(&liveTVChainsLock); 05912 05913 vector<LiveTVChain*>::iterator it = liveTVChains.begin(); 05914 for (; it != liveTVChains.end(); ++it) 05915 { 05916 if ((*it)->IsHostSocket(sock)) 05917 return *it; 05918 } 05919 05920 return NULL; 05921 } 05922 05923 LiveTVChain *MainServer::GetChainWithRecording(const ProgramInfo &pginfo) 05924 { 05925 QMutexLocker lock(&liveTVChainsLock); 05926 05927 vector<LiveTVChain*>::iterator it = liveTVChains.begin(); 05928 for (; it != liveTVChains.end(); ++it) 05929 { 05930 if ((*it)->ProgramIsAt(pginfo) >= 0) 05931 return *it; 05932 } 05933 05934 return NULL; 05935 } 05936 05937 void MainServer::AddToChains(LiveTVChain *chain) 05938 { 05939 QMutexLocker lock(&liveTVChainsLock); 05940 05941 if (chain) 05942 liveTVChains.push_back(chain); 05943 } 05944 05945 void MainServer::DeleteChain(LiveTVChain *chain) 05946 { 05947 QMutexLocker lock(&liveTVChainsLock); 05948 05949 if (!chain) 05950 return; 05951 05952 vector<LiveTVChain*> newChains; 05953 05954 vector<LiveTVChain*>::iterator it = liveTVChains.begin(); 05955 for (; it != liveTVChains.end(); ++it) 05956 { 05957 if (*it != chain) 05958 newChains.push_back(*it); 05959 } 05960 liveTVChains = newChains; 05961 05962 delete chain; 05963 } 05964 05965 void MainServer::SetExitCode(int exitCode, bool closeApplication) 05966 { 05967 m_exitCode = exitCode; 05968 if (closeApplication) 05969 QCoreApplication::exit(m_exitCode); 05970 } 05971 05972 QString MainServer::LocalFilePath(const QUrl &url, const QString &wantgroup) 05973 { 05974 QString lpath = url.path(); 05975 05976 if (url.hasFragment()) 05977 lpath += "#" + url.fragment(); 05978 05979 if (lpath.section('/', -2, -2) == "channels") 05980 { 05981 // This must be an icon request. Check channel.icon to be safe. 05982 QString querytext; 05983 05984 QString file = lpath.section('/', -1); 05985 lpath = ""; 05986 05987 MSqlQuery query(MSqlQuery::InitCon()); 05988 query.prepare("SELECT icon FROM channel WHERE icon LIKE :FILENAME ;"); 05989 query.bindValue(":FILENAME", QString("%/") + file); 05990 05991 if (query.exec() && query.next()) 05992 { 05993 lpath = query.value(0).toString(); 05994 } 05995 else 05996 { 05997 MythDB::DBError("Icon path", query); 05998 } 05999 } 06000 else 06001 { 06002 lpath = lpath.section('/', -1); 06003 06004 QString fpath = lpath; 06005 if (fpath.right(4) == ".png") 06006 fpath = fpath.left(fpath.length() - 4); 06007 06008 ProgramInfo pginfo(fpath); 06009 if (pginfo.GetChanID()) 06010 { 06011 QString pburl = GetPlaybackURL(&pginfo); 06012 if (pburl.left(1) == "/") 06013 { 06014 lpath = pburl.section('/', 0, -2) + "/" + lpath; 06015 LOG(VB_FILE, LOG_INFO, 06016 QString("Local file path: %1").arg(lpath)); 06017 } 06018 else 06019 { 06020 LOG(VB_GENERAL, LOG_ERR, 06021 QString("ERROR: LocalFilePath unable to find local " 06022 "path for '%1', found '%2' instead.") 06023 .arg(lpath).arg(pburl)); 06024 lpath = ""; 06025 } 06026 } 06027 else if (!lpath.isEmpty()) 06028 { 06029 // For securities sake, make sure filename is really the pathless. 06030 QString opath = lpath; 06031 StorageGroup sgroup; 06032 06033 if (!wantgroup.isEmpty()) 06034 { 06035 sgroup.Init(wantgroup); 06036 lpath = url.toString(); 06037 } 06038 else 06039 { 06040 lpath = QFileInfo(lpath).fileName(); 06041 } 06042 06043 QString tmpFile = sgroup.FindFile(lpath); 06044 if (!tmpFile.isEmpty()) 06045 { 06046 lpath = tmpFile; 06047 LOG(VB_FILE, LOG_INFO, 06048 QString("LocalFilePath(%1 '%2'), found file through " 06049 "exhaustive search at '%3'") 06050 .arg(url.toString()).arg(opath).arg(lpath)); 06051 } 06052 else 06053 { 06054 LOG(VB_GENERAL, LOG_ERR, QString("ERROR: LocalFilePath " 06055 "unable to find local path for '%1'.") .arg(opath)); 06056 lpath = ""; 06057 } 06058 06059 } 06060 else 06061 { 06062 lpath = ""; 06063 } 06064 } 06065 06066 return lpath; 06067 } 06068 06069 void MainServer::reconnectTimeout(void) 06070 { 06071 MythSocket *masterServerSock = new MythSocket(); 06072 06073 QString server = gCoreContext->GetSetting("MasterServerIP", "127.0.0.1"); 06074 int port = gCoreContext->GetNumSetting("MasterServerPort", 6543); 06075 06076 LOG(VB_GENERAL, LOG_NOTICE, QString("Connecting to master server: %1:%2") 06077 .arg(server).arg(port)); 06078 06079 if (!masterServerSock->connect(server, port)) 06080 { 06081 LOG(VB_GENERAL, LOG_NOTICE, "Connection to master server timed out."); 06082 masterServerReconnect->start(kMasterServerReconnectTimeout); 06083 masterServerSock->DownRef(); 06084 return; 06085 } 06086 06087 if (masterServerSock->state() != MythSocket::Connected) 06088 { 06089 LOG(VB_GENERAL, LOG_ERR, "Could not connect to master server."); 06090 masterServerReconnect->start(kMasterServerReconnectTimeout); 06091 masterServerSock->DownRef(); 06092 return; 06093 } 06094 06095 LOG(VB_GENERAL, LOG_NOTICE, "Connected successfully"); 06096 06097 QString str = QString("ANN SlaveBackend %1 %2") 06098 .arg(gCoreContext->GetHostName()) 06099 .arg(gCoreContext->GetBackendServerIP()); 06100 06101 masterServerSock->Lock(); 06102 06103 QStringList strlist( str ); 06104 06105 QMap<int, EncoderLink *>::Iterator iter = encoderList->begin(); 06106 for (; iter != encoderList->end(); ++iter) 06107 { 06108 EncoderLink *elink = *iter; 06109 elink->CancelNextRecording(true); 06110 ProgramInfo *pinfo = elink->GetRecording(); 06111 if (pinfo) 06112 { 06113 pinfo->ToStringList(strlist); 06114 delete pinfo; 06115 } 06116 else 06117 { 06118 ProgramInfo dummy; 06119 dummy.ToStringList(strlist); 06120 } 06121 } 06122 06123 if (!masterServerSock->writeStringList(strlist) || 06124 !masterServerSock->readStringList(strlist) || 06125 strlist.empty() || strlist[0] == "ERROR") 06126 { 06127 masterServerSock->Unlock(); // DownRef will delete socket... 06128 masterServerSock->DownRef(); 06129 masterServerSock = NULL; 06130 if (strlist.empty()) 06131 { 06132 LOG(VB_GENERAL, LOG_ERR, LOC + 06133 "Failed to open master server socket, timeout"); 06134 } 06135 else 06136 { 06137 LOG(VB_GENERAL, LOG_ERR, LOC + 06138 "Failed to open master server socket" + 06139 ((strlist.size() >= 2) ? 06140 QString(", error was %1").arg(strlist[1]) : 06141 QString(", remote error"))); 06142 } 06143 masterServerReconnect->start(kMasterServerReconnectTimeout); 06144 return; 06145 } 06146 06147 masterServerSock->setCallbacks(this); 06148 06149 masterServer = new PlaybackSock(this, masterServerSock, server, 06150 kPBSEvents_Normal); 06151 sockListLock.lockForWrite(); 06152 playbackList.push_back(masterServer); 06153 sockListLock.unlock(); 06154 06155 masterServerSock->Unlock(); 06156 06157 autoexpireUpdateTimer->start(1000); 06158 } 06159 06160 // returns true, if a client (slavebackends are not counted!) 06161 // is connected by checking the lists. 06162 bool MainServer::isClientConnected() 06163 { 06164 bool foundClient = false; 06165 06166 sockListLock.lockForRead(); 06167 06168 foundClient |= !fileTransferList.empty(); 06169 06170 vector<PlaybackSock *>::iterator it = playbackList.begin(); 06171 for (; !foundClient && (it != playbackList.end()); ++it) 06172 { 06173 // we simply ignore slaveBackends! 06174 // and clients that don't want to block shutdown 06175 if (!(*it)->isSlaveBackend() && (*it)->getBlockShutdown()) 06176 foundClient = true; 06177 } 06178 06179 sockListLock.unlock(); 06180 06181 return (foundClient); 06182 } 06183 06185 void MainServer::ShutSlaveBackendsDown(QString &haltcmd) 06186 { 06187 // TODO FIXME We should issue a MythEvent and have customEvent 06188 // send this with the proper syncronisation and locking. 06189 06190 QStringList bcast( "SHUTDOWN_NOW" ); 06191 bcast << haltcmd; 06192 06193 sockListLock.lockForRead(); 06194 06195 vector<PlaybackSock *>::iterator it = playbackList.begin(); 06196 for (; it != playbackList.end(); ++it) 06197 { 06198 if ((*it)->isSlaveBackend()) 06199 (*it)->getSocket()->writeStringList(bcast); 06200 } 06201 06202 sockListLock.unlock(); 06203 } 06204 06205 void MainServer::HandleSlaveDisconnectedEvent(const MythEvent &event) 06206 { 06207 if (event.ExtraDataCount() > 0 && m_sched) 06208 { 06209 bool needsReschedule = event.ExtraData(0).toUInt(); 06210 for (int i = 1; i < event.ExtraDataCount(); i++) 06211 m_sched->SlaveDisconnected(event.ExtraData(i).toUInt()); 06212 06213 if (needsReschedule) 06214 m_sched->ReschedulePlace("SlaveDisconnected"); 06215 } 06216 } 06217 06218 void MainServer::SendSlaveDisconnectedEvent( 06219 const QList<uint> &cardids, bool needsReschedule) 06220 { 06221 QStringList extraData; 06222 extraData.push_back( 06223 QString::number(static_cast<uint>(needsReschedule))); 06224 06225 QList<uint>::const_iterator it; 06226 for (it = cardids.begin(); it != cardids.end(); ++it) 06227 extraData.push_back(QString::number(*it)); 06228 06229 MythEvent me("LOCAL_SLAVE_BACKEND_ENCODERS_OFFLINE", extraData); 06230 gCoreContext->dispatch(me); 06231 } 06232 06233 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1.7.6.1