MythTV  0.26-pre
mainserver.cpp
Go to the documentation of this file.
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: */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends