MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
mainserver.cpp
Go to the documentation of this file.
1 #include <list>
2 #include <cstdlib>
3 #include <iostream>
4 #include <algorithm>
5 #include <cerrno>
6 #include <memory>
7 using namespace std;
8 
9 #include <math.h>
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include "mythconfig.h"
13 
14 #ifndef USING_MINGW
15 #include <sys/ioctl.h>
16 #endif
17 
18 #include <sys/stat.h>
19 #ifdef __linux__
20 # include <sys/vfs.h>
21 #else // if !__linux__
22 # include <sys/param.h>
23 # ifndef USING_MINGW
24 # include <sys/mount.h>
25 # endif // USING_MINGW
26 #endif // !__linux__
27 
28 #include <QCoreApplication>
29 #include <QDateTime>
30 #include <QFile>
31 #include <QDir>
32 #include <QWaitCondition>
33 #include <QRegExp>
34 #include <QEvent>
35 #include <QUrl>
36 #include <QTcpServer>
37 #include <QTimer>
38 #include <QNetworkInterface>
39 #include <QNetworkProxy>
40 
41 #include "previewgeneratorqueue.h"
42 #include "mythmiscutil.h"
43 #include "mythsystem.h"
44 #include "exitcodes.h"
45 #include "mythcontext.h"
46 #include "mythversion.h"
47 #include "mythdb.h"
48 #include "mainserver.h"
49 #include "server.h"
50 #include "mthread.h"
51 #include "scheduler.h"
52 #include "backendutil.h"
53 #include "programinfo.h"
54 #include "mythtimezone.h"
55 #include "recordinginfo.h"
56 #include "recordingrule.h"
57 #include "scheduledrecording.h"
58 #include "jobqueue.h"
59 #include "autoexpire.h"
60 #include "storagegroup.h"
61 #include "compat.h"
62 #include "ringbuffer.h"
63 #include "remotefile.h"
64 #include "mythsystemevent.h"
65 #include "tv.h"
66 #include "mythcorecontext.h"
67 #include "mythcoreutil.h"
68 #include "mythdirs.h"
69 #include "mythdownloadmanager.h"
70 #include "metadatafactory.h"
71 #include "videoutils.h"
72 #include "mythlogging.h"
73 #include "filesysteminfo.h"
74 
78 #define PRT_TIMEOUT 10
79 
80 #define PRT_STARTUP_THREAD_COUNT 5
81 
82 #define LOC QString("MainServer: ")
83 #define LOC_WARN QString("MainServer, Warning: ")
84 #define LOC_ERR QString("MainServer, Error: ")
85 
86 namespace {
87 
88 int delete_file_immediately(const QString &filename,
89  bool followLinks, bool checkexists)
90 {
91  /* Return 0 for success, non-zero for error. */
92  QFile checkFile(filename);
93  int success1, success2;
94 
95  LOG(VB_FILE, LOG_INFO, QString("About to delete file: %1").arg(filename));
96  success1 = true;
97  success2 = true;
98  if (followLinks)
99  {
100  QFileInfo finfo(filename);
101  if (finfo.isSymLink())
102  {
103  QString linktext = getSymlinkTarget(filename);
104 
105  QFile target(linktext);
106  if (!(success1 = target.remove()))
107  {
108  LOG(VB_GENERAL, LOG_ERR, QString("Error deleting '%1' -> '%2'")
109  .arg(filename).arg(linktext) + ENO);
110  }
111  }
112  }
113  if ((!checkexists || checkFile.exists()) &&
114  !(success2 = checkFile.remove()))
115  {
116  LOG(VB_GENERAL, LOG_ERR, QString("Error deleting '%1': %2")
117  .arg(filename).arg(strerror(errno)));
118  }
119  return success1 && success2 ? 0 : -1;
120 }
121 
122 };
123 
126 
127 class ProcessRequestRunnable : public QRunnable
128 {
129  public:
131  m_parent(parent), m_sock(sock)
132  {
133  m_sock->IncrRef();
134  }
135 
137  {
138  if (m_sock)
139  {
140  m_sock->DecrRef();
141  m_sock = NULL;
142  }
143  }
144 
145  virtual void run(void)
146  {
147  m_parent.ProcessRequest(m_sock);
148  m_sock->DecrRef();
149  m_sock = NULL;
150  }
151 
152  private:
154  MythSocket *m_sock;
155 };
156 
157 class FreeSpaceUpdater : public QRunnable
158 {
159  public:
161  m_parent(parent), m_dorun(true), m_running(true)
162  {
163  m_lastRequest.start();
164  }
166  {
167  QMutexLocker locker(&m_parent.masterFreeSpaceListLock);
168  m_parent.masterFreeSpaceListUpdater = NULL;
169  m_parent.masterFreeSpaceListWait.wakeAll();
170  }
171 
172  virtual void run(void)
173  {
174  while (true)
175  {
176  MythTimer t;
177  t.start();
178  QStringList list;
179  m_parent.BackendQueryDiskSpace(list, true, true);
180  {
181  QMutexLocker locker(&m_parent.masterFreeSpaceListLock);
182  m_parent.masterFreeSpaceList = list;
183  }
184  QMutexLocker locker(&m_lock);
185  int left = kRequeryTimeout - t.elapsed();
186  if (m_lastRequest.elapsed() + left > kExitTimeout)
187  m_dorun = false;
188  if (!m_dorun)
189  {
190  m_running = false;
191  break;
192  }
193  if (left > 50)
194  m_wait.wait(locker.mutex(), left);
195  }
196  }
197 
198  bool KeepRunning(bool dorun)
199  {
200  QMutexLocker locker(&m_lock);
201  if (dorun && m_running)
202  {
203  m_dorun = true;
204  m_lastRequest.restart();
205  }
206  else
207  {
208  m_dorun = false;
209  m_wait.wakeAll();
210  }
211  return m_running;
212  }
213 
215  QMutex m_lock;
216  bool m_dorun;
217  bool m_running;
219  QWaitCondition m_wait;
220  const static int kRequeryTimeout;
221  const static int kExitTimeout;
222 };
223 const int FreeSpaceUpdater::kRequeryTimeout = 15000;
224 const int FreeSpaceUpdater::kExitTimeout = 61000;
225 
226 MainServer::MainServer(bool master, int port,
227  QMap<int, EncoderLink *> *tvList,
228  Scheduler *sched, AutoExpire *expirer) :
229  encoderList(tvList), mythserver(NULL),
230  masterFreeSpaceListUpdater(NULL),
231  masterServerReconnect(NULL),
232  masterServer(NULL), ismaster(master), threadPool("ProcessRequestPool"),
233  masterBackendOverride(false),
234  m_sched(sched), m_expirer(expirer), deferredDeleteTimer(NULL),
235  autoexpireUpdateTimer(NULL), m_exitCode(GENERIC_EXIT_OK),
236  m_stopped(false)
237 {
241 
242  threadPool.setMaxThreadCount(PRT_STARTUP_THREAD_COUNT);
243 
245  gCoreContext->GetNumSetting("MasterBackendOverride", 0);
246 
247  mythserver = new MythServer();
248  mythserver->setProxy(QNetworkProxy::NoProxy);
249 
250  // test to make sure listen addresses are available
251  // no reason to run the backend if the mainserver is not active
252  QHostAddress config_v4(gCoreContext->GetSetting("BackendServerIP"));
253  bool v4IsSet = config_v4.isNull() ? false : true;
254 #if !defined(QT_NO_IPV6)
255  QHostAddress config_v6(gCoreContext->GetSetting("BackendServerIP6"));
256  bool v6IsSet = config_v6.isNull() ? false : true;
257 #endif
258  QList<QHostAddress> listenAddrs = mythserver->DefaultListen();
259 
260  #if !defined(QT_NO_IPV6)
261  if (v6IsSet && !listenAddrs.contains(config_v6))
262  LOG(VB_GENERAL, LOG_WARNING, "Unable to find IPv6 address to bind");
263  #endif
264 
265  if (v4IsSet && !listenAddrs.contains(config_v4))
266  LOG(VB_GENERAL, LOG_WARNING, "Unable to find IPv4 address to bind");
267 
268  if ((v4IsSet && !listenAddrs.contains(config_v4))
269 #if !defined(QT_NO_IPV6)
270  && (v6IsSet && !listenAddrs.contains(config_v6))
271 #endif
272  )
273  {
274  LOG(VB_GENERAL, LOG_ERR, "Unable to find either IPv4 or IPv6 "
275  "address we can bind to, exiting");
276  SetExitCode(GENERIC_EXIT_SOCKET_ERROR, false);
277  return;
278  }
279 
280  if (!mythserver->listen(port))
281  {
282  SetExitCode(GENERIC_EXIT_SOCKET_ERROR, false);
283  return;
284  }
285  connect(mythserver, SIGNAL(NewConnection(int)),
286  this, SLOT(NewConnection(int)));
287 
288  gCoreContext->addListener(this);
289 
290  if (!ismaster)
291  {
292  masterServerReconnect = new QTimer(this);
293  masterServerReconnect->setSingleShot(true);
294  connect(masterServerReconnect, SIGNAL(timeout()),
295  this, SLOT(reconnectTimeout()));
297  }
298 
299  deferredDeleteTimer = new QTimer(this);
300  connect(deferredDeleteTimer, SIGNAL(timeout()),
301  this, SLOT(deferredDeleteSlot()));
302  deferredDeleteTimer->start(30 * 1000);
303 
304  if (sched)
305  sched->SetMainServer(this);
306  if (expirer)
307  expirer->SetMainServer(this);
308 
309  metadatafactory = new MetadataFactory(this);
310 
311  autoexpireUpdateTimer = new QTimer(this);
312  connect(autoexpireUpdateTimer, SIGNAL(timeout()),
313  this, SLOT(autoexpireUpdate()));
314  autoexpireUpdateTimer->setSingleShot(true);
315 
316  AutoExpire::Update(true);
317 
319  masterFreeSpaceList << "TotalDiskSpace";
320  masterFreeSpaceList << "0";
321  masterFreeSpaceList << "-2";
322  masterFreeSpaceList << "-2";
323  masterFreeSpaceList << "0";
324  masterFreeSpaceList << "0";
325  masterFreeSpaceList << "0";
326 
327  masterFreeSpaceListUpdater = (master ? new FreeSpaceUpdater(*this) : NULL);
329  {
331  masterFreeSpaceListUpdater, "FreeSpaceUpdater");
332  }
333 }
334 
336 {
337  if (!m_stopped)
338  Stop();
339 }
340 
342 {
343  m_stopped = true;
344 
346 
347  {
348  QMutexLocker locker(&masterFreeSpaceListLock);
351  }
352 
353  threadPool.Stop();
354 
355  // since Scheduler::SetMainServer() isn't thread-safe
356  // we need to shut down the scheduler thread before we
357  // can call SetMainServer(NULL)
358  if (m_sched)
359  m_sched->Stop();
360 
363 
364  if (mythserver)
365  {
366  mythserver->disconnect();
367  mythserver->deleteLater();
368  mythserver = NULL;
369  }
370 
371  if (m_sched)
372  {
373  m_sched->Wait();
374  m_sched->SetMainServer(NULL);
375  }
376 
377  if (m_expirer)
378  m_expirer->SetMainServer(NULL);
379 
380  {
381  QMutexLocker locker(&masterFreeSpaceListLock);
383  {
385  masterFreeSpaceListWait.wait(locker.mutex());
386  }
387  }
388 
389  // Close all open sockets
390  QWriteLocker locker(&sockListLock);
391 
392  vector<PlaybackSock *>::iterator it = playbackList.begin();
393  for (; it != playbackList.end(); ++it)
394  (*it)->DecrRef();
395  playbackList.clear();
396 
397  vector<FileTransfer *>::iterator ft = fileTransferList.begin();
398  for (; ft != fileTransferList.end(); ++ft)
399  (*ft)->DecrRef();
400  fileTransferList.clear();
401 
402  QSet<MythSocket*>::iterator cs = controlSocketList.begin();
403  for (; cs != controlSocketList.end(); ++cs)
404  (*cs)->DecrRef();
405  controlSocketList.clear();
406 
407  while (!decrRefSocketList.empty())
408  {
409  (*decrRefSocketList.begin())->DecrRef();
410  decrRefSocketList.erase(decrRefSocketList.begin());
411  }
412 }
413 
415 {
416  AutoExpire::Update(false);
417 }
418 
419 void MainServer::NewConnection(int socketDescriptor)
420 {
421  QWriteLocker locker(&sockListLock);
422  controlSocketList.insert(new MythSocket(socketDescriptor, this));
423 }
424 
426 {
427  sockListLock.lockForRead();
428  PlaybackSock *testsock = GetPlaybackBySock(sock);
429  bool expecting_reply = testsock && testsock->isExpectingReply();
430  sockListLock.unlock();
431  if (expecting_reply)
432  {
433  LOG(VB_GENERAL, LOG_INFO, "readyRead ignoring, expecting reply");
434  return;
435  }
436 
438  new ProcessRequestRunnable(*this, sock),
439  "ProcessRequest", PRT_TIMEOUT);
440 
441  QCoreApplication::processEvents();
442 }
443 
445 {
446  if (sock->IsDataAvailable())
447  ProcessRequestWork(sock);
448  else
449  LOG(VB_GENERAL, LOG_INFO, QString("No data on sock %1")
450  .arg(sock->GetSocketDescriptor()));
451 }
452 
454 {
455  QStringList listline;
456  if (!sock->ReadStringList(listline) || listline.empty())
457  {
458  LOG(VB_GENERAL, LOG_INFO, "No data in ProcessRequestWork()");
459  return;
460  }
461 
462  QString line = listline[0];
463 
464  line = line.simplified();
465  QStringList tokens = line.split(' ', QString::SkipEmptyParts);
466  QString command = tokens[0];
467  if (command == "MYTH_PROTO_VERSION")
468  {
469  if (tokens.size() < 2)
470  LOG(VB_GENERAL, LOG_CRIT, "Bad MYTH_PROTO_VERSION command");
471  else
472  HandleVersion(sock, tokens);
473  return;
474  }
475  else if (command == "ANN")
476  {
477  HandleAnnounce(listline, tokens, sock);
478  return;
479  }
480  else if (command == "DONE")
481  {
482  HandleDone(sock);
483  return;
484  }
485 
486  sockListLock.lockForRead();
487  PlaybackSock *pbs = GetPlaybackBySock(sock);
488  if (!pbs)
489  {
490  sockListLock.unlock();
491  LOG(VB_GENERAL, LOG_ERR, "ProcessRequest unknown socket");
492  return;
493  }
494  pbs->IncrRef();
495  sockListLock.unlock();
496 
497  if (command == "QUERY_FILETRANSFER")
498  {
499  if (tokens.size() != 2)
500  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_FILETRANSFER");
501  else
502  HandleFileTransferQuery(listline, tokens, pbs);
503  }
504  else if (command == "QUERY_RECORDINGS")
505  {
506  if (tokens.size() != 2)
507  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDINGS query");
508  else
509  HandleQueryRecordings(tokens[1], pbs);
510  }
511  else if (command == "QUERY_RECORDING")
512  {
513  HandleQueryRecording(tokens, pbs);
514  }
515  else if (command == "GO_TO_SLEEP")
516  {
517  HandleGoToSleep(pbs);
518  }
519  else if (command == "QUERY_FREE_SPACE")
520  {
521  HandleQueryFreeSpace(pbs, false);
522  }
523  else if (command == "QUERY_FREE_SPACE_LIST")
524  {
525  HandleQueryFreeSpace(pbs, true);
526  }
527  else if (command == "QUERY_FREE_SPACE_SUMMARY")
528  {
530  }
531  else if (command == "QUERY_LOAD")
532  {
533  HandleQueryLoad(pbs);
534  }
535  else if (command == "QUERY_UPTIME")
536  {
537  HandleQueryUptime(pbs);
538  }
539  else if (command == "QUERY_HOSTNAME")
540  {
541  HandleQueryHostname(pbs);
542  }
543  else if (command == "QUERY_MEMSTATS")
544  {
545  HandleQueryMemStats(pbs);
546  }
547  else if (command == "QUERY_TIME_ZONE")
548  {
549  HandleQueryTimeZone(pbs);
550  }
551  else if (command == "QUERY_CHECKFILE")
552  {
553  HandleQueryCheckFile(listline, pbs);
554  }
555  else if (command == "QUERY_FILE_EXISTS")
556  {
557  if (listline.size() < 2)
558  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_FILE_EXISTS command");
559  else
560  HandleQueryFileExists(listline, pbs);
561  }
562  else if (command == "QUERY_FILE_HASH")
563  {
564  if (listline.size() < 3)
565  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_FILE_HASH command");
566  else
567  HandleQueryFileHash(listline, pbs);
568  }
569  else if (command == "QUERY_GUIDEDATATHROUGH")
570  {
572  }
573  else if (command == "DELETE_FILE")
574  {
575  if (listline.size() < 3)
576  LOG(VB_GENERAL, LOG_ERR, "Bad DELETE_FILE command");
577  else
578  HandleDeleteFile(listline, pbs);
579  }
580  else if (command == "STOP_RECORDING")
581  {
582  HandleStopRecording(listline, pbs);
583  }
584  else if (command == "CHECK_RECORDING")
585  {
586  HandleCheckRecordingActive(listline, pbs);
587  }
588  else if (command == "DELETE_RECORDING")
589  {
590  if (3 <= tokens.size() && tokens.size() <= 5)
591  {
592  bool force = (tokens.size() >= 4) && (tokens[3] == "FORCE");
593  bool forget = (tokens.size() >= 5) && (tokens[4] == "FORGET");
594  HandleDeleteRecording(tokens[1], tokens[2], pbs, force, forget);
595  }
596  else
597  HandleDeleteRecording(listline, pbs, false);
598  }
599  else if (command == "FORCE_DELETE_RECORDING")
600  {
601  HandleDeleteRecording(listline, pbs, true);
602  }
603  else if (command == "UNDELETE_RECORDING")
604  {
605  HandleUndeleteRecording(listline, pbs);
606  }
607  else if (command == "RESCHEDULE_RECORDINGS")
608  {
609  listline.pop_front();
610  HandleRescheduleRecordings(listline, pbs);
611  }
612  else if (command == "FORGET_RECORDING")
613  {
614  HandleForgetRecording(listline, pbs);
615  }
616  else if (command == "QUERY_GETALLPENDING")
617  {
618  if (tokens.size() == 1)
620  else if (tokens.size() == 2)
621  HandleGetPendingRecordings(pbs, tokens[1]);
622  else
623  HandleGetPendingRecordings(pbs, tokens[1], tokens[2].toInt());
624  }
625  else if (command == "QUERY_GETALLSCHEDULED")
626  {
628  }
629  else if (command == "QUERY_GETCONFLICTING")
630  {
631  HandleGetConflictingRecordings(listline, pbs);
632  }
633  else if (command == "QUERY_GETEXPIRING")
634  {
636  }
637  else if (command == "QUERY_SG_GETFILELIST")
638  {
639  HandleSGGetFileList(listline, pbs);
640  }
641  else if (command == "QUERY_SG_FILEQUERY")
642  {
643  HandleSGFileQuery(listline, pbs);
644  }
645  else if (command == "GET_FREE_RECORDER")
646  {
648  }
649  else if (command == "GET_FREE_RECORDER_COUNT")
650  {
652  }
653  else if (command == "GET_FREE_RECORDER_LIST")
654  {
656  }
657  else if (command == "GET_NEXT_FREE_RECORDER")
658  {
659  HandleGetNextFreeRecorder(listline, pbs);
660  }
661  else if (command == "QUERY_RECORDER")
662  {
663  if (tokens.size() != 2)
664  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDER");
665  else
666  HandleRecorderQuery(listline, tokens, pbs);
667  }
668  else if (command == "QUERY_RECORDING_DEVICE")
669  {
670  // TODO
671  }
672  else if (command == "QUERY_RECORDING_DEVICES")
673  {
674  // TODO
675  }
676  else if (command == "SET_NEXT_LIVETV_DIR")
677  {
678  if (tokens.size() != 3)
679  LOG(VB_GENERAL, LOG_ERR, "Bad SET_NEXT_LIVETV_DIR");
680  else
681  HandleSetNextLiveTVDir(tokens, pbs);
682  }
683  else if (command == "SET_CHANNEL_INFO")
684  {
685  HandleSetChannelInfo(listline, pbs);
686  }
687  else if (command == "QUERY_REMOTEENCODER")
688  {
689  if (tokens.size() != 2)
690  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_REMOTEENCODER");
691  else
692  HandleRemoteEncoder(listline, tokens, pbs);
693  }
694  else if (command == "GET_RECORDER_FROM_NUM")
695  {
696  HandleGetRecorderFromNum(listline, pbs);
697  }
698  else if (command == "GET_RECORDER_NUM")
699  {
700  HandleGetRecorderNum(listline, pbs);
701  }
702  else if (command == "QUERY_GENPIXMAP2")
703  {
704  HandleGenPreviewPixmap(listline, pbs);
705  }
706  else if (command == "QUERY_PIXMAP_LASTMODIFIED")
707  {
708  HandlePixmapLastModified(listline, pbs);
709  }
710  else if (command == "QUERY_PIXMAP_GET_IF_MODIFIED")
711  {
712  HandlePixmapGetIfModified(listline, pbs);
713  }
714  else if (command == "QUERY_ISRECORDING")
715  {
716  HandleIsRecording(listline, pbs);
717  }
718  else if (command == "MESSAGE")
719  {
720  if ((listline.size() >= 2) && (listline[1].startsWith("SET_VERBOSE")))
721  HandleSetVerbose(listline, pbs);
722  else if ((listline.size() >= 2) &&
723  (listline[1].startsWith("SET_LOG_LEVEL")))
724  HandleSetLogLevel(listline, pbs);
725  else
726  HandleMessage(listline, pbs);
727  }
728  else if (command == "FILL_PROGRAM_INFO")
729  {
730  HandleFillProgramInfo(listline, pbs);
731  }
732  else if (command == "LOCK_TUNER")
733  {
734  if (tokens.size() == 1)
735  HandleLockTuner(pbs);
736  else if (tokens.size() == 2)
737  HandleLockTuner(pbs, tokens[1].toInt());
738  else
739  LOG(VB_GENERAL, LOG_ERR, "Bad LOCK_TUNER query");
740  }
741  else if (command == "FREE_TUNER")
742  {
743  if (tokens.size() != 2)
744  LOG(VB_GENERAL, LOG_ERR, "Bad FREE_TUNER query");
745  else
746  HandleFreeTuner(tokens[1].toInt(), pbs);
747  }
748  else if (command == "QUERY_ACTIVE_BACKENDS")
749  {
751  }
752  else if (command == "QUERY_IS_ACTIVE_BACKEND")
753  {
754  if (tokens.size() != 1)
755  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_IS_ACTIVE_BACKEND");
756  else
757  HandleIsActiveBackendQuery(listline, pbs);
758  }
759  else if (command == "QUERY_COMMBREAK")
760  {
761  if (tokens.size() != 3)
762  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_COMMBREAK");
763  else
764  HandleCommBreakQuery(tokens[1], tokens[2], pbs);
765  }
766  else if (command == "QUERY_CUTLIST")
767  {
768  if (tokens.size() != 3)
769  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_CUTLIST");
770  else
771  HandleCutlistQuery(tokens[1], tokens[2], pbs);
772  }
773  else if (command == "QUERY_BOOKMARK")
774  {
775  if (tokens.size() != 3)
776  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_BOOKMARK");
777  else
778  HandleBookmarkQuery(tokens[1], tokens[2], pbs);
779  }
780  else if (command == "SET_BOOKMARK")
781  {
782  if (tokens.size() != 5)
783  LOG(VB_GENERAL, LOG_ERR, "Bad SET_BOOKMARK");
784  else
785  HandleSetBookmark(tokens, pbs);
786  }
787  else if (command == "QUERY_SETTING")
788  {
789  if (tokens.size() != 3)
790  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_SETTING");
791  else
792  HandleSettingQuery(tokens, pbs);
793  }
794  else if (command == "SET_SETTING")
795  {
796  if (tokens.size() != 4)
797  LOG(VB_GENERAL, LOG_ERR, "Bad SET_SETTING");
798  else
799  HandleSetSetting(tokens, pbs);
800  }
801  else if (command == "SCAN_VIDEOS")
802  {
803  HandleScanVideos(pbs);
804  }
805  else if (command == "ALLOW_SHUTDOWN")
806  {
807  if (tokens.size() != 1)
808  LOG(VB_GENERAL, LOG_ERR, "Bad ALLOW_SHUTDOWN");
809  else
810  HandleBlockShutdown(false, pbs);
811  }
812  else if (command == "BLOCK_SHUTDOWN")
813  {
814  if (tokens.size() != 1)
815  LOG(VB_GENERAL, LOG_ERR, "Bad BLOCK_SHUTDOWN");
816  else
817  HandleBlockShutdown(true, pbs);
818  }
819  else if (command == "SHUTDOWN_NOW")
820  {
821  if (tokens.size() != 1)
822  LOG(VB_GENERAL, LOG_ERR, "Bad SHUTDOWN_NOW query");
823  else if (!ismaster)
824  {
825  QString halt_cmd;
826  if (listline.size() >= 2)
827  halt_cmd = listline[1];
828 
829  if (!halt_cmd.isEmpty())
830  {
831  LOG(VB_GENERAL, LOG_NOTICE,
832  "Going down now as of Mainserver request!");
833  myth_system(halt_cmd);
834  }
835  else
836  LOG(VB_GENERAL, LOG_WARNING,
837  "Received an empty SHUTDOWN_NOW query!");
838  }
839  }
840  else if (command == "BACKEND_MESSAGE")
841  {
842  QString message = listline[1];
843  QStringList extra( listline[2] );
844  for (int i = 3; i < listline.size(); i++)
845  extra << listline[i];
846  MythEvent me(message, extra);
847  gCoreContext->dispatch(me);
848  }
849  else if ((command == "DOWNLOAD_FILE") ||
850  (command == "DOWNLOAD_FILE_NOW"))
851  {
852  if (listline.size() != 4)
853  LOG(VB_GENERAL, LOG_ERR, QString("Bad %1 command").arg(command));
854  else
855  HandleDownloadFile(listline, pbs);
856  }
857  else if (command == "REFRESH_BACKEND")
858  {
859  LOG(VB_GENERAL, LOG_INFO ,"Reloading backend settings");
860  HandleBackendRefresh(sock);
861  }
862  else if (command == "OK")
863  {
864  LOG(VB_GENERAL, LOG_ERR, "Got 'OK' out of sequence.");
865  }
866  else if (command == "UNKNOWN_COMMAND")
867  {
868  LOG(VB_GENERAL, LOG_ERR, "Got 'UNKNOWN_COMMAND' out of sequence.");
869  }
870  else
871  {
872  LOG(VB_GENERAL, LOG_ERR, "Unknown command: " + command);
873 
874  MythSocket *pbssock = pbs->getSocket();
875 
876  QStringList strlist;
877  strlist << "UNKNOWN_COMMAND";
878 
879  SendResponse(pbssock, strlist);
880  }
881 
882  pbs->DecrRef();
883 }
884 
885 void MainServer::customEvent(QEvent *e)
886 {
887  QStringList broadcast;
888  QSet<QString> receivers;
889 
890  // delete stale sockets in the UI thread
891  sockListLock.lockForRead();
892  bool decrRefEmpty = decrRefSocketList.empty();
893  sockListLock.unlock();
894  if (!decrRefEmpty)
895  {
896  QWriteLocker locker(&sockListLock);
897  while (!decrRefSocketList.empty())
898  {
899  (*decrRefSocketList.begin())->DecrRef();
900  decrRefSocketList.erase(decrRefSocketList.begin());
901  }
902  }
903 
904  if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
905  {
906  MythEvent *me = (MythEvent *)e;
907 
908  QString message = me->Message();
909  QString error;
910  if ((message == "PREVIEW_SUCCESS" || message == "PREVIEW_QUEUED") &&
911  me->ExtraDataCount() >= 5)
912  {
913  bool ok = true;
914  QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey()
915  QString filename = me->ExtraData(1); // outFileName
916  QString msg = me->ExtraData(2);
917  QString datetime = me->ExtraData(3);
918 
919  if (message == "PREVIEW_QUEUED")
920  {
921  LOG(VB_PLAYBACK, LOG_INFO, QString("Preview Queued: '%1' '%2'")
922  .arg(pginfokey).arg(filename));
923  return;
924  }
925 
926  QFile file(filename);
927  ok = ok && file.open(QIODevice::ReadOnly);
928 
929  if (ok)
930  {
931  QByteArray data = file.readAll();
932  QStringList extra("OK");
933  extra.push_back(pginfokey);
934  extra.push_back(msg);
935  extra.push_back(datetime);
936  extra.push_back(QString::number(data.size()));
937  extra.push_back(
938  QString::number(qChecksum(data.constData(), data.size())));
939  extra.push_back(QString(data.toBase64()));
940 
941  for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++)
942  {
943  QString token = me->ExtraData(i);
944  extra.push_back(token);
945  RequestedBy::iterator it = m_previewRequestedBy.find(token);
946  if (it != m_previewRequestedBy.end())
947  {
948  receivers.insert(*it);
949  m_previewRequestedBy.erase(it);
950  }
951  }
952 
953  if (receivers.empty())
954  {
955  LOG(VB_GENERAL, LOG_ERR, LOC +
956  "PREVIEW_SUCCESS but no receivers.");
957  return;
958  }
959 
960  broadcast.push_back("BACKEND_MESSAGE");
961  broadcast.push_back("GENERATED_PIXMAP");
962  broadcast += extra;
963  }
964  else
965  {
966  message = "PREVIEW_FAILED";
967  error = QString("Failed to read '%1'").arg(filename);
968  LOG(VB_GENERAL, LOG_ERR, LOC + error);
969  }
970  }
971 
972  if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5)
973  {
974  QString pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey()
975  QString msg = me->ExtraData(2);
976 
977  QStringList extra("ERROR");
978  extra.push_back(pginfokey);
979  extra.push_back(msg);
980  for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++)
981  {
982  QString token = me->ExtraData(i);
983  extra.push_back(token);
984  RequestedBy::iterator it = m_previewRequestedBy.find(token);
985  if (it != m_previewRequestedBy.end())
986  {
987  receivers.insert(*it);
988  m_previewRequestedBy.erase(it);
989  }
990  }
991 
992  if (receivers.empty())
993  {
994  LOG(VB_GENERAL, LOG_ERR, LOC +
995  "PREVIEW_FAILED but no receivers.");
996  return;
997  }
998 
999  broadcast.push_back("BACKEND_MESSAGE");
1000  broadcast.push_back("GENERATED_PIXMAP");
1001  broadcast += extra;
1002  }
1003 
1004  if (me->Message().startsWith("AUTO_EXPIRE"))
1005  {
1006  QStringList tokens = me->Message()
1007  .split(" ", QString::SkipEmptyParts);
1008 
1009  if (tokens.size() != 3)
1010  {
1011  LOG(VB_GENERAL, LOG_ERR, "Bad AUTO_EXPIRE message");
1012  return;
1013  }
1014 
1015  QDateTime startts = MythDate::fromString(tokens[2]);
1016  RecordingInfo recInfo(tokens[1].toUInt(), startts);
1017 
1018  if (recInfo.GetChanID())
1019  {
1020  SendMythSystemPlayEvent("REC_EXPIRED", &recInfo);
1021 
1022  // allow re-record if auto expired but not expired live
1023  // or already "deleted" programs
1024  if (recInfo.GetRecordingGroup() != "LiveTV" &&
1025  recInfo.GetRecordingGroup() != "Deleted" &&
1026  (gCoreContext->GetNumSetting("RerecordWatched", 0) ||
1027  !recInfo.IsWatched()))
1028  {
1029  recInfo.ForgetHistory();
1030  }
1031  DoHandleDeleteRecording(recInfo, NULL, false, true, false);
1032  }
1033  else
1034  {
1035  QString msg = QString("Cannot find program info for '%1', "
1036  "while attempting to Auto-Expire.")
1037  .arg(me->Message());
1038  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
1039  }
1040 
1041  return;
1042  }
1043 
1044  if (me->Message().startsWith("QUERY_NEXT_LIVETV_DIR") && m_sched)
1045  {
1046  QStringList tokens = me->Message()
1047  .split(" ", QString::SkipEmptyParts);
1048 
1049  if (tokens.size() != 2)
1050  {
1051  LOG(VB_GENERAL, LOG_ERR,
1052  QString("Bad %1 message").arg(tokens[0]));
1053  return;
1054  }
1055 
1056  m_sched->GetNextLiveTVDir(tokens[1].toInt());
1057  return;
1058  }
1059 
1060  if ((me->Message().startsWith("DELETE_RECORDING")) ||
1061  (me->Message().startsWith("FORCE_DELETE_RECORDING")))
1062  {
1063  QStringList tokens = me->Message()
1064  .split(" ", QString::SkipEmptyParts);
1065 
1066  if (tokens.size() != 3)
1067  {
1068  LOG(VB_GENERAL, LOG_ERR,
1069  QString("Bad %1 message").arg(tokens[0]));
1070  return;
1071  }
1072 
1073  QDateTime startts = MythDate::fromString(tokens[2]);
1074  RecordingInfo recInfo(tokens[1].toUInt(), startts);
1075 
1076  if (recInfo.GetChanID())
1077  {
1078  if (tokens[0] == "FORCE_DELETE_RECORDING")
1079  DoHandleDeleteRecording(recInfo, NULL, true, false, false);
1080  else
1081  DoHandleDeleteRecording(recInfo, NULL, false, false, false);
1082  }
1083  else
1084  {
1085  LOG(VB_GENERAL, LOG_ERR,
1086  QString("Cannot find program info for '%1' while "
1087  "attempting to delete.").arg(me->Message()));
1088  }
1089 
1090  return;
1091  }
1092 
1093  if (me->Message().startsWith("RESCHEDULE_RECORDINGS") && m_sched)
1094  {
1095  QStringList request = me->ExtraDataList();
1096  m_sched->Reschedule(request);
1097  return;
1098  }
1099 
1100  if (me->Message().startsWith("SCHEDULER_ADD_RECORDING") && m_sched)
1101  {
1102  ProgramInfo pi(me->ExtraDataList());
1103  if (!pi.GetChanID())
1104  {
1105  LOG(VB_GENERAL, LOG_ERR, "Bad SCHEDULER_ADD_RECORDING message");
1106  return;
1107  }
1108 
1109  m_sched->AddRecording(pi);
1110  return;
1111  }
1112 
1113  if (me->Message().startsWith("UPDATE_RECORDING_STATUS") && m_sched)
1114  {
1115  QStringList tokens = me->Message()
1116  .split(" ", QString::SkipEmptyParts);
1117 
1118  if (tokens.size() != 6)
1119  {
1120  LOG(VB_GENERAL, LOG_ERR, "Bad UPDATE_RECORDING_STATUS message");
1121  return;
1122  }
1123 
1124  uint cardid = tokens[1].toUInt();
1125  uint chanid = tokens[2].toUInt();
1126  QDateTime startts = MythDate::fromString(tokens[3]);
1127  RecStatusType recstatus = RecStatusType(tokens[4].toInt());
1128  QDateTime recendts = MythDate::fromString(tokens[5]);
1129  m_sched->UpdateRecStatus(cardid, chanid, startts,
1130  recstatus, recendts);
1131  return;
1132  }
1133 
1134  if (me->Message().startsWith("LIVETV_EXITED"))
1135  {
1136  QString chainid = me->ExtraData();
1137  LiveTVChain *chain = GetExistingChain(chainid);
1138  if (chain)
1139  DeleteChain(chain);
1140 
1141  return;
1142  }
1143 
1144  if (me->Message() == "CLEAR_SETTINGS_CACHE")
1146 
1147  if (me->Message().startsWith("RESET_IDLETIME") && m_sched)
1149 
1150  if (me->Message() == "LOCAL_RECONNECT_TO_MASTER")
1152 
1153  if (me->Message() == "LOCAL_SLAVE_BACKEND_ENCODERS_OFFLINE")
1155 
1156  if (me->Message().startsWith("LOCAL_"))
1157  return;
1158 
1159  MythEvent mod_me("");
1160  if (me->Message().startsWith("MASTER_UPDATE_PROG_INFO"))
1161  {
1162  QStringList tokens = me->Message().simplified().split(" ");
1163  uint chanid = 0;
1164  QDateTime recstartts;
1165  if (tokens.size() >= 3)
1166  {
1167  chanid = tokens[1].toUInt();
1168  recstartts = MythDate::fromString(tokens[2]);
1169  }
1170 
1171  ProgramInfo evinfo(chanid, recstartts);
1172  if (evinfo.GetChanID())
1173  {
1174  QDateTime rectime = MythDate::current().addSecs(
1175  -gCoreContext->GetNumSetting("RecordOverTime"));
1176 
1177  if (m_sched && evinfo.GetRecordingEndTime() > rectime)
1178  evinfo.SetRecordingStatus(m_sched->GetRecStatus(evinfo));
1179 
1180  QStringList list;
1181  evinfo.ToStringList(list);
1182  mod_me = MythEvent("RECORDING_LIST_CHANGE UPDATE", list);
1183  me = &mod_me;
1184  }
1185  else
1186  {
1187  return;
1188  }
1189  }
1190 
1191  if (me->Message().startsWith("DOWNLOAD_FILE"))
1192  {
1193  QStringList extraDataList = me->ExtraDataList();
1194  QString localFile = extraDataList[1];
1195  QFile file(localFile);
1196  QStringList tokens = me->Message().simplified().split(" ");
1197  QMutexLocker locker(&m_downloadURLsLock);
1198 
1199  if (!m_downloadURLs.contains(localFile))
1200  return;
1201 
1202  extraDataList[1] = m_downloadURLs[localFile];
1203 
1204  if ((tokens.size() >= 2) && (tokens[1] == "FINISHED"))
1205  m_downloadURLs.remove(localFile);
1206 
1207  mod_me = MythEvent(me->Message(), extraDataList);
1208  me = &mod_me;
1209  }
1210 
1211  if (broadcast.empty())
1212  {
1213  broadcast.push_back("BACKEND_MESSAGE");
1214  broadcast.push_back(me->Message());
1215  broadcast += me->ExtraDataList();
1216  }
1217  }
1218 
1219  if (!broadcast.empty())
1220  {
1221  // Make a local copy of the list, upping the refcount as we go..
1222  vector<PlaybackSock *> localPBSList;
1223  sockListLock.lockForRead();
1224  vector<PlaybackSock *>::iterator it = playbackList.begin();
1225  for (; it != playbackList.end(); ++it)
1226  {
1227  (*it)->IncrRef();
1228  localPBSList.push_back(*it);
1229  }
1230  sockListLock.unlock();
1231 
1232  bool sendGlobal = false;
1233  if (ismaster && broadcast[1].startsWith("GLOBAL_"))
1234  {
1235  broadcast[1].replace("GLOBAL_", "LOCAL_");
1236  MythEvent me(broadcast[1], broadcast[2]);
1237  gCoreContext->dispatch(me);
1238 
1239  sendGlobal = true;
1240  }
1241 
1242  QSet<PlaybackSock*> sentSet;
1243 
1244  bool isSystemEvent = broadcast[1].startsWith("SYSTEM_EVENT ");
1245  QStringList sentSetSystemEvent(gCoreContext->GetHostName());
1246 
1247  vector<PlaybackSock*>::const_iterator iter;
1248  for (iter = localPBSList.begin(); iter != localPBSList.end(); ++iter)
1249  {
1250  PlaybackSock *pbs = *iter;
1251 
1252  if (sentSet.contains(pbs) || pbs->IsDisconnected())
1253  continue;
1254 
1255  if (!receivers.empty() && !receivers.contains(pbs->getHostname()))
1256  continue;
1257 
1258  sentSet.insert(pbs);
1259 
1260  bool reallysendit = false;
1261 
1262  if (broadcast[1] == "CLEAR_SETTINGS_CACHE")
1263  {
1264  if ((ismaster) &&
1265  (pbs->isSlaveBackend() || pbs->wantsEvents()))
1266  reallysendit = true;
1267  }
1268  else if (sendGlobal)
1269  {
1270  if (pbs->isSlaveBackend())
1271  reallysendit = true;
1272  }
1273  else if (pbs->wantsEvents())
1274  {
1275  reallysendit = true;
1276  }
1277 
1278  if (reallysendit)
1279  {
1280  if (isSystemEvent)
1281  {
1282  if (!pbs->wantsSystemEvents())
1283  {
1284  continue;
1285  }
1286  else if (!pbs->wantsOnlySystemEvents())
1287  {
1288  if (sentSetSystemEvent.contains(pbs->getHostname()))
1289  continue;
1290 
1291  sentSetSystemEvent << pbs->getHostname();
1292  }
1293  }
1294  else if (pbs->wantsOnlySystemEvents())
1295  continue;
1296  }
1297 
1298  MythSocket *sock = pbs->getSocket();
1299  if (reallysendit && sock->IsConnected())
1300  sock->WriteStringList(broadcast);
1301  }
1302 
1303  // Done with the pbs list, so decrement all the instances..
1304  for (iter = localPBSList.begin(); iter != localPBSList.end(); ++iter)
1305  {
1306  PlaybackSock *pbs = *iter;
1307  pbs->DecrRef();
1308  }
1309  }
1310 }
1311 
1320 void MainServer::HandleVersion(MythSocket *socket, const QStringList &slist)
1321 {
1322  QStringList retlist;
1323  QString version = slist[1];
1324  if (version != MYTH_PROTO_VERSION)
1325  {
1326  LOG(VB_GENERAL, LOG_CRIT,
1327  "MainServer::HandleVersion - Client speaks protocol version " +
1328  version + " but we speak " + MYTH_PROTO_VERSION + '!');
1329  retlist << "REJECT" << MYTH_PROTO_VERSION;
1330  socket->WriteStringList(retlist);
1331  HandleDone(socket);
1332  return;
1333  }
1334 
1335  if (slist.size() < 3)
1336  {
1337  LOG(VB_GENERAL, LOG_CRIT,
1338  "MainServer::HandleVersion - Client did not pass protocol "
1339  "token. Refusing connection!");
1340  retlist << "REJECT" << MYTH_PROTO_VERSION;
1341  socket->WriteStringList(retlist);
1342  HandleDone(socket);
1343  return;
1344  }
1345 
1346  QString token = slist[2];
1347  if (token != MYTH_PROTO_TOKEN)
1348  {
1349  LOG(VB_GENERAL, LOG_CRIT,
1350  "MainServer::HandleVersion - Client sent incorrect protocol"
1351  " token for protocol version. Refusing connection!");
1352  retlist << "REJECT" << MYTH_PROTO_VERSION;
1353  socket->WriteStringList(retlist);
1354  HandleDone(socket);
1355  return;
1356  }
1357 
1358  retlist << "ACCEPT" << MYTH_PROTO_VERSION;
1359  socket->WriteStringList(retlist);
1360 }
1361 
1374 void MainServer::HandleAnnounce(QStringList &slist, QStringList commands,
1375  MythSocket *socket)
1376 {
1377  QStringList retlist( "OK" );
1378  QStringList errlist( "ERROR" );
1379 
1380  if (commands.size() < 3 || commands.size() > 6)
1381  {
1382  QString info = "";
1383  if (commands.size() == 2)
1384  info = QString(" %1").arg(commands[1]);
1385 
1386  LOG(VB_GENERAL, LOG_ERR, QString("Received malformed ANN%1 query")
1387  .arg(info));
1388 
1389  errlist << "malformed_ann_query";
1390  socket->WriteStringList(errlist);
1391  return;
1392  }
1393 
1394  sockListLock.lockForRead();
1395  vector<PlaybackSock *>::iterator iter = playbackList.begin();
1396  for (; iter != playbackList.end(); ++iter)
1397  {
1398  PlaybackSock *pbs = *iter;
1399  if (pbs->getSocket() == socket)
1400  {
1401  LOG(VB_GENERAL, LOG_WARNING,
1402  QString("Client %1 is trying to announce a socket "
1403  "multiple times.")
1404  .arg(commands[2]));
1405  socket->WriteStringList(retlist);
1406  sockListLock.unlock();
1407  return;
1408  }
1409  }
1410  sockListLock.unlock();
1411 
1412  if (commands[1] == "Playback" || commands[1] == "Monitor")
1413  {
1414  if (commands.size() < 4)
1415  {
1416  LOG(VB_GENERAL, LOG_ERR, QString("Received malformed ANN %1 query")
1417  .arg(commands[1]));
1418 
1419  errlist << "malformed_ann_query";
1420  socket->WriteStringList(errlist);
1421  return;
1422  }
1423  // Monitor connections are same as Playback but they don't
1424  // block shutdowns. See the Scheduler event loop for more.
1425 
1426  PlaybackSockEventsMode eventsMode =
1427  (PlaybackSockEventsMode)commands[3].toInt();
1428  LOG(VB_GENERAL, LOG_INFO, QString("MainServer::ANN %1")
1429  .arg(commands[1]));
1430  LOG(VB_GENERAL, LOG_INFO, QString("adding: %1 as a client (events: %2)")
1431  .arg(commands[2]).arg(eventsMode));
1432  PlaybackSock *pbs = new PlaybackSock(this, socket, commands[2],
1433  eventsMode);
1434  pbs->setBlockShutdown(commands[1] == "Playback");
1435 
1436  sockListLock.lockForWrite();
1437  controlSocketList.remove(socket);
1438  playbackList.push_back(pbs);
1439  sockListLock.unlock();
1440 
1441  if (eventsMode != kPBSEvents_None && commands[2] != "tzcheck")
1443  QString("CLIENT_CONNECTED HOSTNAME %1").arg(commands[2]));
1444  }
1445  else if (commands[1] == "MediaServer")
1446  {
1447  if (commands.size() < 3)
1448  {
1449  LOG(VB_GENERAL, LOG_ERR,
1450  "Received malformed ANN MediaServer query");
1451  errlist << "malformed_ann_query";
1452  socket->WriteStringList(errlist);
1453  return;
1454  }
1455 
1456  PlaybackSock *pbs = new PlaybackSock(this, socket, commands[2],
1458  pbs->setAsMediaServer();
1459  pbs->setBlockShutdown(false);
1460  sockListLock.lockForWrite();
1461  controlSocketList.remove(socket);
1462  playbackList.push_back(pbs);
1463  sockListLock.unlock();
1464 
1466  QString("CLIENT_CONNECTED HOSTNAME %1").arg(commands[2]));
1467  }
1468  else if (commands[1] == "SlaveBackend")
1469  {
1470  if (commands.size() < 4)
1471  {
1472  LOG(VB_GENERAL, LOG_ERR, QString("Received malformed ANN %1 query")
1473  .arg(commands[1]));
1474  errlist << "malformed_ann_query";
1475  socket->WriteStringList(errlist);
1476  return;
1477  }
1478 
1479  LOG(VB_GENERAL, LOG_INFO,
1480  QString("adding: %1 as a slave backend server")
1481  .arg(commands[2]));
1482  PlaybackSock *pbs = new PlaybackSock(this, socket, commands[2],
1483  kPBSEvents_None);
1484  pbs->setAsSlaveBackend();
1485  pbs->setIP(commands[3]);
1486 
1487  if (m_sched)
1488  {
1489  RecordingList slavelist;
1490  QStringList::const_iterator sit = slist.begin()+1;
1491  while (sit != slist.end())
1492  {
1493  RecordingInfo *recinfo = new RecordingInfo(sit, slist.end());
1494  if (!recinfo->GetChanID())
1495  {
1496  delete recinfo;
1497  break;
1498  }
1499  slavelist.push_back(recinfo);
1500  }
1501  m_sched->SlaveConnected(slavelist);
1502  }
1503 
1504  bool wasAsleep = true;
1505  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
1506  for (; iter != encoderList->end(); ++iter)
1507  {
1508  EncoderLink *elink = *iter;
1509  if (elink->GetHostName() == commands[2])
1510  {
1511  if (! (elink->IsWaking() || elink->IsAsleep()))
1512  wasAsleep = false;
1513  elink->SetSocket(pbs);
1514  }
1515  }
1516 
1517  if (!wasAsleep && m_sched)
1518  m_sched->ReschedulePlace("SlaveConnected");
1519 
1520  QString message = QString("LOCAL_SLAVE_BACKEND_ONLINE %2")
1521  .arg(commands[2]);
1522  MythEvent me(message);
1523  gCoreContext->dispatch(me);
1524 
1525  pbs->setBlockShutdown(false);
1526 
1527  sockListLock.lockForWrite();
1528  controlSocketList.remove(socket);
1529  playbackList.push_back(pbs);
1530  sockListLock.unlock();
1531 
1532  autoexpireUpdateTimer->start(1000);
1533 
1535  QString("SLAVE_CONNECTED HOSTNAME %1").arg(commands[2]));
1536  }
1537  else if (commands[1] == "FileTransfer")
1538  {
1539  if (slist.size() < 3)
1540  {
1541  LOG(VB_GENERAL, LOG_ERR, "Received malformed FileTransfer command");
1542  errlist << "malformed_filetransfer_command";
1543  socket->WriteStringList(errlist);
1544  return;
1545  }
1546 
1547  LOG(VB_GENERAL, LOG_INFO, "MainServer::HandleAnnounce FileTransfer");
1548  LOG(VB_GENERAL, LOG_INFO,
1549  QString("adding: %1 as a remote file transfer") .arg(commands[2]));
1550  QStringList::const_iterator it = slist.begin();
1551  QUrl qurl = *(++it);
1552  QString wantgroup = *(++it);
1553  QString filename;
1554  QStringList checkfiles;
1555  for (++it; it != slist.end(); ++it)
1556  checkfiles += *it;
1557 
1558  FileTransfer *ft = NULL;
1559  bool writemode = false;
1560  bool usereadahead = true;
1561  int timeout_ms = 2000;
1562  if (commands.size() > 3)
1563  writemode = commands[3].toInt();
1564 
1565  if (commands.size() > 4)
1566  usereadahead = commands[4].toInt();
1567 
1568  if (commands.size() > 5)
1569  timeout_ms = commands[5].toInt();
1570 
1571  if (writemode)
1572  {
1573  if (wantgroup.isEmpty())
1574  wantgroup = "Default";
1575 
1576  StorageGroup sgroup(wantgroup, gCoreContext->GetHostName(), false);
1577  QString dir = sgroup.FindNextDirMostFree();
1578  if (dir.isEmpty())
1579  {
1580  LOG(VB_GENERAL, LOG_ERR, "Unable to determine directory "
1581  "to write to in FileTransfer write command");
1582  errlist << "filetransfer_directory_not_found";
1583  socket->WriteStringList(errlist);
1584  return;
1585  }
1586 
1587  QString basename = qurl.path();
1588  if (qurl.hasFragment())
1589  basename += "#" + qurl.fragment();
1590 
1591  if (basename.isEmpty())
1592  {
1593  LOG(VB_GENERAL, LOG_ERR,
1594  QString("FileTransfer write filename is empty in url '%1'.")
1595  .arg(qurl.toString()));
1596  errlist << "filetransfer_filename_empty";
1597  socket->WriteStringList(errlist);
1598  return;
1599  }
1600 
1601  if ((basename.contains("/../")) ||
1602  (basename.startsWith("../")))
1603  {
1604  LOG(VB_GENERAL, LOG_ERR,
1605  QString("FileTransfer write filename '%1' does not pass "
1606  "sanity checks.") .arg(basename));
1607  errlist << "filetransfer_filename_dangerous";
1608  socket->WriteStringList(errlist);
1609  return;
1610  }
1611 
1612  filename = dir + "/" + basename;
1613  }
1614  else
1615  filename = LocalFilePath(qurl, wantgroup);
1616 
1617  if (filename.isEmpty())
1618  {
1619  LOG(VB_GENERAL, LOG_ERR, "Empty filename, cowardly aborting!");
1620  errlist << "filetransfer_filename_empty";
1621  socket->WriteStringList(errlist);
1622  return;
1623  }
1624 
1625 
1626  QFileInfo finfo(filename);
1627  if (finfo.isDir())
1628  {
1629  LOG(VB_GENERAL, LOG_ERR,
1630  QString("FileTransfer filename '%1' is actually a directory, "
1631  "cannot transfer.") .arg(filename));
1632  errlist << "filetransfer_filename_is_a_directory";
1633  socket->WriteStringList(errlist);
1634  return;
1635  }
1636 
1637  if (writemode)
1638  {
1639  QString dirPath = finfo.absolutePath();
1640  QDir qdir(dirPath);
1641  if (!qdir.exists())
1642  {
1643  if (!qdir.mkpath(dirPath))
1644  {
1645  LOG(VB_GENERAL, LOG_ERR,
1646  QString("FileTransfer filename '%1' is in a "
1647  "subdirectory which does not exist, and can "
1648  "not be created.") .arg(filename));
1649  errlist << "filetransfer_unable_to_create_subdirectory";
1650  socket->WriteStringList(errlist);
1651  return;
1652  }
1653  }
1654  ft = new FileTransfer(filename, socket, writemode);
1655  }
1656  else
1657  {
1658  ft = new FileTransfer(filename, socket, usereadahead, timeout_ms);
1659  }
1660 
1661  ft->IncrRef();
1662 
1663  sockListLock.lockForWrite();
1664  controlSocketList.remove(socket);
1665  fileTransferList.push_back(ft);
1666  sockListLock.unlock();
1667 
1668  retlist << QString::number(socket->GetSocketDescriptor());
1669  retlist << QString::number(ft->GetFileSize());
1670 
1671  ft->DecrRef();
1672 
1673  if (checkfiles.size())
1674  {
1675  QFileInfo fi(filename);
1676  QDir dir = fi.absoluteDir();
1677  for (it = checkfiles.begin(); it != checkfiles.end(); ++it)
1678  {
1679  if (dir.exists(*it) &&
1680  QFileInfo(dir, *it).size() >= kReadTestSize)
1681  {
1682  retlist<<*it;
1683  }
1684  }
1685  }
1686  }
1687 
1688  socket->WriteStringList(retlist);
1689 }
1690 
1697 {
1698  socket->DisconnectFromHost();
1699 }
1700 
1701 void MainServer::SendResponse(MythSocket *socket, QStringList &commands)
1702 {
1703  // Note: this method assumes that the playback or filetransfer
1704  // handler has already been uprefed and the socket as well.
1705 
1706  // These checks are really just to check if the socket has
1707  // been remotely disconnected while we were working on the
1708  // response.
1709 
1710  bool do_write = false;
1711  if (socket)
1712  {
1713  sockListLock.lockForRead();
1714  do_write = (GetPlaybackBySock(socket) ||
1715  GetFileTransferBySock(socket));
1716  sockListLock.unlock();
1717  }
1718 
1719  if (do_write)
1720  {
1721  socket->WriteStringList(commands);
1722  }
1723  else
1724  {
1725  LOG(VB_GENERAL, LOG_ERR,
1726  "SendResponse: Unable to write to client socket, as it's no "
1727  "longer there");
1728  }
1729 }
1730 
1740 {
1741  MythSocket *pbssock = pbs->getSocket();
1742  QString playbackhost = pbs->getHostname();
1743 
1744  QMap<QString,ProgramInfo*> recMap;
1745  if (m_sched)
1746  recMap = m_sched->GetRecording();
1747 
1748  QMap<QString,uint32_t> inUseMap = ProgramInfo::QueryInUseMap();
1749  QMap<QString,bool> isJobRunning =
1751 
1752  int sort = 0;
1753  // Allow "Play" and "Delete" for backwards compatibility with protocol
1754  // version 56 and below.
1755  if ((type == "Ascending") || (type == "Play"))
1756  sort = 1;
1757  else if ((type == "Descending") || (type == "Delete"))
1758  sort = -1;
1759 
1760  ProgramList destination;
1762  destination, (type == "Recording"),
1763  inUseMap, isJobRunning, recMap, sort);
1764 
1765  QMap<QString,ProgramInfo*>::iterator mit = recMap.begin();
1766  for (; mit != recMap.end(); mit = recMap.erase(mit))
1767  delete *mit;
1768 
1769  QStringList outputlist(QString::number(destination.size()));
1770  QMap<QString, QString> backendIpMap;
1771  QMap<QString, QString> backendPortMap;
1772  QString ip = gCoreContext->GetBackendServerIP();
1773  QString port = gCoreContext->GetSetting("BackendServerPort");
1774 
1775  ProgramList::iterator it = destination.begin();
1776  for (it = destination.begin(); it != destination.end(); ++it)
1777  {
1778  ProgramInfo *proginfo = *it;
1779  PlaybackSock *slave = NULL;
1780 
1781  if (proginfo->GetHostname() != gCoreContext->GetHostName())
1782  slave = GetSlaveByHostname(proginfo->GetHostname());
1783 
1784  if ((proginfo->GetHostname() == gCoreContext->GetHostName()) ||
1785  (!slave && masterBackendOverride))
1786  {
1787  proginfo->SetPathname(gCoreContext->GenMythURL(ip,port,proginfo->GetBasename()));
1788  if (!proginfo->GetFilesize())
1789  {
1790  QString tmpURL = GetPlaybackURL(proginfo);
1791  if (tmpURL.startsWith('/'))
1792  {
1793  QFile checkFile(tmpURL);
1794  if (!tmpURL.isEmpty() && checkFile.exists())
1795  {
1796  proginfo->SetFilesize(checkFile.size());
1797  if (proginfo->GetRecordingEndTime() <
1799  {
1800  proginfo->SaveFilesize(proginfo->GetFilesize());
1801  }
1802  }
1803  }
1804  }
1805  }
1806  else if (!slave)
1807  {
1808  proginfo->SetPathname(GetPlaybackURL(proginfo));
1809  if (proginfo->GetPathname().isEmpty())
1810  {
1811  LOG(VB_GENERAL, LOG_ERR, LOC +
1812  QString("HandleQueryRecordings() "
1813  "Couldn't find backend for:\n\t\t\t%1")
1814  .arg(proginfo->toString(ProgramInfo::kTitleSubtitle)));
1815 
1816  proginfo->SetFilesize(0);
1817  proginfo->SetPathname("file not found");
1818  }
1819  }
1820  else
1821  {
1822  if (!proginfo->GetFilesize())
1823  {
1824  if (!slave->FillProgramInfo(*proginfo, playbackhost))
1825  {
1826  LOG(VB_GENERAL, LOG_ERR,
1827  "MainServer::HandleQueryRecordings()"
1828  "\n\t\t\tCould not fill program info "
1829  "from backend");
1830  }
1831  else
1832  {
1833  if (proginfo->GetRecordingEndTime() <
1835  {
1836  proginfo->SaveFilesize(proginfo->GetFilesize());
1837  }
1838  }
1839  }
1840  else
1841  {
1842  ProgramInfo *p = proginfo;
1843  if (!backendIpMap.contains(p->GetHostname()))
1844  backendIpMap[p->GetHostname()] =
1845  gCoreContext->GetSettingOnHost("BackendServerIp",
1846  p->GetHostname());
1847  if (!backendPortMap.contains(p->GetHostname()))
1848  backendPortMap[p->GetHostname()] =
1849  gCoreContext->GetSettingOnHost("BackendServerPort",
1850  p->GetHostname());
1851  p->SetPathname(gCoreContext->GenMythURL(backendIpMap[p->GetHostname()],
1852  backendPortMap[p->GetHostname()],
1853  p->GetBasename()));
1854  }
1855  }
1856 
1857  if (slave)
1858  slave->DecrRef();
1859 
1860  proginfo->ToStringList(outputlist);
1861  }
1862 
1863  SendResponse(pbssock, outputlist);
1864 }
1865 
1871 void MainServer::HandleQueryRecording(QStringList &slist, PlaybackSock *pbs)
1872 {
1873  if (slist.size() < 3)
1874  {
1875  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDING query");
1876  return;
1877  }
1878 
1879  MythSocket *pbssock = pbs->getSocket();
1880  QString command = slist[1].toUpper();
1881  ProgramInfo *pginfo = NULL;
1882 
1883  if (command == "BASENAME")
1884  {
1885  pginfo = new ProgramInfo(slist[2]);
1886  }
1887  else if (command == "TIMESLOT")
1888  {
1889  if (slist.size() < 4)
1890  {
1891  LOG(VB_GENERAL, LOG_ERR, "Bad QUERY_RECORDING query");
1892  return;
1893  }
1894 
1895  QDateTime recstartts = MythDate::fromString(slist[3]);
1896  pginfo = new ProgramInfo(slist[2].toUInt(), recstartts);
1897  }
1898 
1899  QStringList strlist;
1900 
1901  if (pginfo && pginfo->GetChanID())
1902  {
1903  strlist << "OK";
1904  pginfo->ToStringList(strlist);
1905  }
1906  else
1907  {
1908  strlist << "ERROR";
1909  }
1910 
1911  delete pginfo;
1912 
1913  SendResponse(pbssock, strlist);
1914 }
1915 
1916 void MainServer::HandleFillProgramInfo(QStringList &slist, PlaybackSock *pbs)
1917 {
1918  MythSocket *pbssock = pbs->getSocket();
1919 
1920  QString playbackhost = slist[1];
1921 
1922  QStringList::const_iterator it = slist.begin() + 2;
1923  ProgramInfo pginfo(it, slist.end());
1924 
1925  if (pginfo.HasPathname())
1926  {
1927  QString lpath = GetPlaybackURL(&pginfo);
1928  QString ip = gCoreContext->GetBackendServerIP();
1929  QString port = gCoreContext->GetSetting("BackendServerPort");
1930 
1931  if (playbackhost == gCoreContext->GetHostName())
1932  pginfo.SetPathname(lpath);
1933  else
1934  pginfo.SetPathname(gCoreContext->GenMythURL(ip,port,pginfo.GetBasename()));
1935 
1936  const QFileInfo info(lpath);
1937  pginfo.SetFilesize(info.size());
1938  }
1939 
1940  QStringList strlist;
1941 
1942  pginfo.ToStringList(strlist);
1943 
1944  SendResponse(pbssock, strlist);
1945 }
1946 
1947 
1948 void DeleteThread::run(void)
1949 {
1950  if (m_ms)
1951  m_ms->DoDeleteThread(this);
1952 }
1953 
1955 {
1956  // sleep a little to let frontends reload the recordings list
1957  // after deleting a recording, then we can hammer the DB and filesystem
1958  sleep(3);
1959  usleep(random()%2000);
1960 
1961  deletelock.lock();
1962 
1963  QString logInfo = QString("chanid %1 at %2")
1964  .arg(ds->m_chanid)
1965  .arg(ds->m_recstartts.toString(Qt::ISODate));
1966 
1967  QString name = QString("deleteThread%1%2").arg(getpid()).arg(random());
1968  QFile checkFile(ds->m_filename);
1969 
1971  {
1972  QString msg = QString("ERROR opening database connection for Delete "
1973  "Thread for chanid %1 recorded at %2. Program "
1974  "will NOT be deleted.")
1975  .arg(ds->m_chanid)
1976  .arg(ds->m_recstartts.toString(Qt::ISODate));
1977  LOG(VB_GENERAL, LOG_ERR, msg);
1978 
1979  deletelock.unlock();
1980  return;
1981  }
1982 
1983  ProgramInfo pginfo(ds->m_chanid, ds->m_recstartts);
1984 
1985  if (!pginfo.GetChanID())
1986  {
1987  QString msg = QString("ERROR retrieving program info when trying to "
1988  "delete program for chanid %1 recorded at %2. "
1989  "Recording will NOT be deleted.")
1990  .arg(ds->m_chanid)
1991  .arg(ds->m_recstartts.toString(Qt::ISODate));
1992  LOG(VB_GENERAL, LOG_ERR, msg);
1993 
1994  deletelock.unlock();
1995  return;
1996  }
1997 
1998  // Don't allow deleting files where filesize != 0 and we can't find
1999  // the file, unless forceMetadataDelete has been set. This allows
2000  // deleting failed recordings without fuss, but blocks accidental
2001  // deletion of metadata for files where the filesystem has gone missing.
2002  if ((!checkFile.exists()) && pginfo.GetFilesize() &&
2003  (!ds->m_forceMetadataDelete))
2004  {
2005  LOG(VB_GENERAL, LOG_ERR,
2006  QString("ERROR when trying to delete file: %1. File "
2007  "doesn't exist. Database metadata will not be removed.")
2008  .arg(ds->m_filename));
2009 
2010  pginfo.SaveDeletePendingFlag(false);
2011  deletelock.unlock();
2012  return;
2013  }
2014 
2016 
2017  LiveTVChain *tvchain = GetChainWithRecording(pginfo);
2018  if (tvchain)
2019  tvchain->DeleteProgram(&pginfo);
2020 
2021  bool followLinks = gCoreContext->GetNumSetting("DeletesFollowLinks", 0);
2022  bool slowDeletes = gCoreContext->GetNumSetting("TruncateDeletesSlowly", 0);
2023  int fd = -1;
2024  off_t size = 0;
2025  bool errmsg = false;
2026 
2027  /* Delete recording. */
2028  if (slowDeletes)
2029  {
2030  // Since stat fails after unlinking on some filesystems,
2031  // get the filesize first
2032  const QFileInfo info(ds->m_filename);
2033  size = info.size();
2034  fd = DeleteFile(ds->m_filename, followLinks, ds->m_forceMetadataDelete);
2035 
2036  if ((fd < 0) && checkFile.exists())
2037  errmsg = true;
2038  }
2039  else
2040  {
2041  delete_file_immediately(ds->m_filename, followLinks, false);
2042  sleep(2);
2043  if (checkFile.exists())
2044  errmsg = true;
2045  }
2046 
2047  if (errmsg)
2048  {
2049  LOG(VB_GENERAL, LOG_ERR,
2050  QString("Error deleting file: %1. Keeping metadata in database.")
2051  .arg(ds->m_filename));
2052 
2053  pginfo.SaveDeletePendingFlag(false);
2054  deletelock.unlock();
2055  return;
2056  }
2057 
2058  /* Delete all preview thumbnails. */
2059 
2060  QFileInfo fInfo( ds->m_filename );
2061  QString nameFilter = fInfo.fileName() + "*.png";
2062  // QDir's nameFilter uses spaces or semicolons to separate globs,
2063  // so replace them with the "match any character" wildcard
2064  // since mythrename.pl may have included them in filenames
2065  nameFilter.replace(QRegExp("( |;)"), "?");
2066  QDir dir ( fInfo.path(), nameFilter );
2067 
2068  for (uint nIdx = 0; nIdx < dir.count(); nIdx++)
2069  {
2070  QString sFileName = QString( "%1/%2" )
2071  .arg( fInfo.path() )
2072  .arg( dir[ nIdx ] );
2073 
2074  delete_file_immediately( sFileName, followLinks, true);
2075  }
2076 
2077  DeleteRecordedFiles(ds);
2078 
2079  DoDeleteInDB(ds);
2080 
2081  deletelock.unlock();
2082 
2083  if (slowDeletes && fd >= 0)
2084  TruncateAndClose(&pginfo, fd, ds->m_filename, size);
2085 }
2086 
2088 {
2089  QString logInfo = QString("chanid %1 at %2")
2090  .arg(ds->m_chanid).arg(ds->m_recstartts.toString(Qt::ISODate));
2091 
2092  MSqlQuery update(MSqlQuery::InitCon());
2093  MSqlQuery query(MSqlQuery::InitCon());
2094  query.prepare("SELECT basename, hostname, storagegroup FROM recordedfile "
2095  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
2096  query.bindValue(":CHANID", ds->m_chanid);
2097  query.bindValue(":STARTTIME", ds->m_recstartts);
2098 
2099  if (!query.exec() || !query.isActive())
2100  {
2101  MythDB::DBError("RecordedFiles deletion", query);
2102  LOG(VB_GENERAL, LOG_ERR,
2103  QString("Error querying recordedfiles for %1.") .arg(logInfo));
2104  }
2105 
2106  QString basename;
2107  QString hostname;
2108  QString storagegroup;
2109  bool deleteInDB;
2110  while (query.next())
2111  {
2112  basename = query.value(0).toString();
2113  hostname = query.value(1).toString();
2114  storagegroup = query.value(2).toString();
2115  deleteInDB = false;
2116 
2117  if (basename == ds->m_filename)
2118  deleteInDB = true;
2119  else
2120  {
2121  LOG(VB_FILE, LOG_INFO,
2122  QString("DeleteRecordedFiles(%1), deleting '%2'")
2123  .arg(logInfo).arg(query.value(0).toString()));
2124 
2125  StorageGroup sgroup(storagegroup);
2126  QString localFile = sgroup.FindFile(basename);
2127 
2128  QString url = gCoreContext->GenMythURL(
2129  gCoreContext->GetBackendServerIP(hostname),
2130  gCoreContext->GetSettingOnHost("BackendServerPort", hostname),
2131  basename,
2132  storagegroup);
2133 
2134  if ((((hostname == gCoreContext->GetHostName()) ||
2135  (!localFile.isEmpty())) &&
2136  (HandleDeleteFile(basename, storagegroup))) ||
2137  (((hostname != gCoreContext->GetHostName()) ||
2138  (localFile.isEmpty())) &&
2139  (RemoteFile::DeleteFile(url))))
2140  {
2141  deleteInDB = true;
2142  }
2143  }
2144 
2145  if (deleteInDB)
2146  {
2147  update.prepare("DELETE FROM recordedfile "
2148  "WHERE chanid = :CHANID "
2149  "AND starttime = :STARTTIME "
2150  "AND basename = :BASENAME ;");
2151  update.bindValue(":CHANID", ds->m_chanid);
2152  update.bindValue(":STARTTIME", ds->m_recstartts);
2153  update.bindValue(":BASENAME", basename);
2154  if (!update.exec())
2155  {
2156  MythDB::DBError("RecordedFiles deletion", update);
2157  LOG(VB_GENERAL, LOG_ERR,
2158  QString("Error querying recordedfile (%1) for %2.")
2159  .arg(query.value(1).toString())
2160  .arg(logInfo));
2161  }
2162  }
2163  }
2164 }
2165 
2167 {
2168  QString logInfo = QString("chanid %1 at %2")
2169  .arg(ds->m_chanid).arg(ds->m_recstartts.toString(Qt::ISODate));
2170 
2171  MSqlQuery query(MSqlQuery::InitCon());
2172  query.prepare("DELETE FROM recorded WHERE chanid = :CHANID AND "
2173  "title = :TITLE AND starttime = :STARTTIME;");
2174  query.bindValue(":CHANID", ds->m_chanid);
2175  query.bindValue(":TITLE", ds->m_title);
2176  query.bindValue(":STARTTIME", ds->m_recstartts);
2177 
2178  if (!query.exec() || !query.isActive())
2179  {
2180  MythDB::DBError("Recorded program deletion", query);
2181  LOG(VB_GENERAL, LOG_ERR,
2182  QString("Error deleting recorded entry for %1.") .arg(logInfo));
2183  }
2184 
2185  sleep(1);
2186 
2187  // Notify the frontend so it can requery for Free Space
2188  QString msg = QString("RECORDING_LIST_CHANGE DELETE %1 %2")
2189  .arg(ds->m_chanid).arg(ds->m_recstartts.toString(Qt::ISODate));
2191 
2192  // sleep a little to let frontends reload the recordings list
2193  sleep(3);
2194 
2195  query.prepare("DELETE FROM recordedmarkup "
2196  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
2197  query.bindValue(":CHANID", ds->m_chanid);
2198  query.bindValue(":STARTTIME", ds->m_recstartts);
2199 
2200  if (!query.exec())
2201  {
2202  MythDB::DBError("Recorded program delete recordedmarkup", query);
2203  LOG(VB_GENERAL, LOG_ERR,
2204  QString("Error deleting recordedmarkup for %1.") .arg(logInfo));
2205  }
2206 
2207  query.prepare("DELETE FROM recordedseek "
2208  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
2209  query.bindValue(":CHANID", ds->m_chanid);
2210  query.bindValue(":STARTTIME", ds->m_recstartts);
2211 
2212  if (!query.exec())
2213  {
2214  MythDB::DBError("Recorded program delete recordedseek", query);
2215  LOG(VB_GENERAL, LOG_ERR, QString("Error deleting recordedseek for %1.")
2216  .arg(logInfo));
2217  }
2218 }
2219 
2229 int MainServer::DeleteFile(const QString &filename, bool followLinks,
2230  bool deleteBrokenSymlinks)
2231 {
2232  QFileInfo finfo(filename);
2233  int fd = -1, err = 0;
2234  QString linktext = "";
2235  QByteArray fname = filename.toLocal8Bit();
2236 
2237  LOG(VB_FILE, LOG_INFO, QString("About to unlink/delete file: '%1'")
2238  .arg(fname.constData()));
2239 
2240  QString errmsg = QString("Delete Error '%1'").arg(fname.constData());
2241  if (finfo.isSymLink())
2242  {
2243  linktext = getSymlinkTarget(filename);
2244  QByteArray alink = linktext.toLocal8Bit();
2245  errmsg += QString(" -> '%2'").arg(alink.constData());
2246  }
2247 
2248  if (followLinks && finfo.isSymLink())
2249  {
2250  if (!finfo.exists() && deleteBrokenSymlinks)
2251  err = unlink(fname.constData());
2252  else
2253  {
2254  fd = OpenAndUnlink(linktext);
2255  if (fd >= 0)
2256  err = unlink(fname.constData());
2257  }
2258  }
2259  else if (!finfo.isSymLink())
2260  {
2261  fd = OpenAndUnlink(filename);
2262  }
2263  else // just delete symlinks immediately
2264  {
2265  err = unlink(fname.constData());
2266  if (err == 0)
2267  return -2; // valid result, not an error condition
2268  }
2269 
2270  if (fd < 0)
2271  LOG(VB_GENERAL, LOG_ERR, errmsg + ENO);
2272 
2273  return fd;
2274 }
2275 
2285 int MainServer::OpenAndUnlink(const QString &filename)
2286 {
2287  QByteArray fname = filename.toLocal8Bit();
2288  QString msg = QString("Error deleting '%1'").arg(fname.constData());
2289  int fd = open(fname.constData(), O_WRONLY);
2290 
2291  if (fd == -1)
2292  {
2293  LOG(VB_GENERAL, LOG_ERR, msg + " could not open " + ENO);
2294  return -1;
2295  }
2296 
2297  if (unlink(fname.constData()))
2298  {
2299  LOG(VB_GENERAL, LOG_ERR, msg + " could not unlink " + ENO);
2300  close(fd);
2301  return -1;
2302  }
2303 
2304  return fd;
2305 }
2306 
2316  const QString &filename, off_t fsize)
2317 {
2318  QMutexLocker locker(&truncate_and_close_lock);
2319 
2320  if (pginfo)
2321  {
2322  pginfo->SetPathname(filename);
2323  pginfo->MarkAsInUse(true, kTruncatingDeleteInUseID);
2324  }
2325 
2326  int cards = 5;
2327  {
2328  MSqlQuery query(MSqlQuery::InitCon());
2329  query.prepare("SELECT COUNT(cardid) FROM capturecard;");
2330  if (query.exec() && query.next())
2331  cards = query.value(0).toInt();
2332  }
2333 
2334  // Time between truncation steps in milliseconds
2335  const size_t sleep_time = 500;
2336  const size_t min_tps = 8 * 1024 * 1024;
2337  const size_t calc_tps = (size_t) (cards * 1.2 * (22200000LL / 8));
2338  const size_t tps = max(min_tps, calc_tps);
2339  const size_t increment = (size_t) (tps * (sleep_time * 0.001f));
2340 
2341  LOG(VB_FILE, LOG_INFO,
2342  QString("Truncating '%1' by %2 MB every %3 milliseconds")
2343  .arg(filename)
2344  .arg(increment / (1024.0 * 1024.0), 0, 'f', 2)
2345  .arg(sleep_time));
2346 
2347  GetMythDB()->GetDBManager()->PurgeIdleConnections(false);
2348 
2349  int count = 0;
2350  while (fsize > 0)
2351  {
2352 #if 0
2353  LOG(VB_FILE, LOG_DEBUG, QString("Truncating '%1' to %2 MB")
2354  .arg(filename).arg(fsize / (1024.0 * 1024.0), 0, 'f', 2));
2355 #endif
2356 
2357  int err = ftruncate(fd, fsize);
2358  if (err)
2359  {
2360  LOG(VB_GENERAL, LOG_ERR, QString("Error truncating '%1'")
2361  .arg(filename) + ENO);
2362  if (pginfo)
2363  pginfo->MarkAsInUse(false, kTruncatingDeleteInUseID);
2364  return 0 == close(fd);
2365  }
2366 
2367  fsize -= increment;
2368 
2369  if (pginfo && ((count % 100) == 0))
2370  pginfo->UpdateInUseMark(true);
2371 
2372  count++;
2373 
2374  usleep(sleep_time * 1000);
2375  }
2376 
2377  bool ok = (0 == close(fd));
2378 
2379  if (pginfo)
2380  pginfo->MarkAsInUse(false, kTruncatingDeleteInUseID);
2381 
2382  LOG(VB_FILE, LOG_INFO, QString("Finished truncating '%1'").arg(filename));
2383 
2384  return ok;
2385 }
2386 
2388  PlaybackSock *pbs)
2389 {
2390  MythSocket *pbssock = NULL;
2391  if (pbs)
2392  pbssock = pbs->getSocket();
2393 
2394  QStringList::const_iterator it = slist.begin() + 1;
2395  ProgramInfo pginfo(it, slist.end());
2396 
2397  int result = 0;
2398 
2399  if (ismaster && pginfo.GetHostname() != gCoreContext->GetHostName())
2400  {
2401  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
2402  if (slave)
2403  {
2404  result = slave->CheckRecordingActive(&pginfo);
2405  slave->DecrRef();
2406  }
2407  }
2408  else
2409  {
2410  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
2411  for (; iter != encoderList->end(); ++iter)
2412  {
2413  EncoderLink *elink = *iter;
2414 
2415  if (elink->IsLocal() && elink->MatchesRecording(&pginfo))
2416  result = iter.key();
2417  }
2418  }
2419 
2420  QStringList outputlist( QString::number(result) );
2421  if (pbssock)
2422  SendResponse(pbssock, outputlist);
2423 
2424  return;
2425 }
2426 
2427 void MainServer::HandleStopRecording(QStringList &slist, PlaybackSock *pbs)
2428 {
2429  QStringList::const_iterator it = slist.begin() + 1;
2430  RecordingInfo recinfo(it, slist.end());
2431  if (recinfo.GetChanID())
2432  DoHandleStopRecording(recinfo, pbs);
2433 }
2434 
2436  RecordingInfo &recinfo, PlaybackSock *pbs)
2437 {
2438  MythSocket *pbssock = NULL;
2439  if (pbs)
2440  pbssock = pbs->getSocket();
2441 
2442  // FIXME! We don't know what state the recorder is in at this
2443  // time. Simply set the recstatus to rsUnknown and let the
2444  // scheduler do the best it can with it. The proper long term fix
2445  // is probably to have the recorder return the actual recstatus as
2446  // part of the stop recording response. That's a more involved
2447  // change than I care to make during the 0.25 code freeze.
2448  recinfo.SetRecordingStatus(rsUnknown);
2449 
2450  if (ismaster && recinfo.GetHostname() != gCoreContext->GetHostName())
2451  {
2452  PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname());
2453 
2454  if (slave)
2455  {
2456  int num = slave->StopRecording(&recinfo);
2457 
2458  if (num > 0)
2459  {
2460  (*encoderList)[num]->StopRecording();
2461  if (m_sched)
2462  m_sched->UpdateRecStatus(&recinfo);
2463  }
2464  if (pbssock)
2465  {
2466  QStringList outputlist( "0" );
2467  SendResponse(pbssock, outputlist);
2468  }
2469 
2470  slave->DecrRef();
2471  return;
2472  }
2473  else
2474  {
2475  // If the slave is unreachable, we can assume that the
2476  // recording has stopped and the status should be updated.
2477  // Continue so that the master can try to update the endtime
2478  // of the file is in a shared directory.
2479  if (m_sched)
2480  m_sched->UpdateRecStatus(&recinfo);
2481  }
2482 
2483  }
2484 
2485  int recnum = -1;
2486 
2487  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
2488  for (; iter != encoderList->end(); ++iter)
2489  {
2490  EncoderLink *elink = *iter;
2491 
2492  if (elink->IsLocal() && elink->MatchesRecording(&recinfo))
2493  {
2494  recnum = iter.key();
2495 
2496  elink->StopRecording();
2497 
2498  while (elink->IsBusyRecording() ||
2499  elink->GetState() == kState_ChangingState)
2500  {
2501  usleep(100);
2502  }
2503 
2504  if (ismaster)
2505  {
2506  if (m_sched)
2507  m_sched->UpdateRecStatus(&recinfo);
2508  }
2509  }
2510  }
2511 
2512  if (pbssock)
2513  {
2514  QStringList outputlist( QString::number(recnum) );
2515  SendResponse(pbssock, outputlist);
2516  }
2517 }
2518 
2519 void MainServer::HandleDeleteRecording(QString &chanid, QString &starttime,
2520  PlaybackSock *pbs,
2521  bool forceMetadataDelete,
2522  bool forgetHistory)
2523 {
2524  QDateTime recstartts = MythDate::fromString(starttime);
2525  RecordingInfo recinfo(chanid.toUInt(), recstartts);
2526 
2527  if (!recinfo.GetChanID())
2528  {
2529  MythSocket *pbssock = NULL;
2530  if (pbs)
2531  pbssock = pbs->getSocket();
2532 
2533  QStringList outputlist( QString::number(0) );
2534 
2535  SendResponse(pbssock, outputlist);
2536  return;
2537  }
2538 
2539  DoHandleDeleteRecording(recinfo, pbs, forceMetadataDelete, false, forgetHistory);
2540 }
2541 
2542 void MainServer::HandleDeleteRecording(QStringList &slist, PlaybackSock *pbs,
2543  bool forceMetadataDelete)
2544 {
2545  QStringList::const_iterator it = slist.begin() + 1;
2546  RecordingInfo recinfo(it, slist.end());
2547  if (recinfo.GetChanID())
2548  DoHandleDeleteRecording(recinfo, pbs, forceMetadataDelete, false, false);
2549 }
2550 
2552  RecordingInfo &recinfo, PlaybackSock *pbs,
2553  bool forceMetadataDelete, bool expirer, bool forgetHistory)
2554 {
2555  int resultCode = -1;
2556  MythSocket *pbssock = NULL;
2557  if (pbs)
2558  pbssock = pbs->getSocket();
2559 
2560  bool justexpire = expirer ? false :
2561  ( //gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete") &&
2562  (recinfo.GetRecordingGroup() != "Deleted") &&
2563  (recinfo.GetRecordingGroup() != "LiveTV"));
2564 
2565  QString filename = GetPlaybackURL(&recinfo, false);
2566  if (filename.isEmpty())
2567  {
2568  LOG(VB_GENERAL, LOG_ERR,
2569  QString("ERROR when trying to delete file for %1. Unable "
2570  "to determine filename of recording.")
2571  .arg(recinfo.toString(ProgramInfo::kRecordingKey)));
2572 
2573  if (pbssock)
2574  {
2575  resultCode = -2;
2576  QStringList outputlist(QString::number(resultCode));
2577  SendResponse(pbssock, outputlist);
2578  }
2579 
2580  return;
2581  }
2582 
2583  // Stop the recording if it's still in progress.
2584  DoHandleStopRecording(recinfo, NULL);
2585 
2586  if (justexpire && !forceMetadataDelete &&
2587  recinfo.GetFilesize() > (1024 * 1024) )
2588  {
2589  recinfo.ApplyRecordRecGroupChange("Deleted");
2590  recinfo.SaveAutoExpire(kDeletedAutoExpire, true);
2591  if (forgetHistory)
2592  recinfo.ForgetHistory();
2593  else if (m_sched)
2594  m_sched->RescheduleCheck(recinfo, "DoHandleDelete1");
2595  QStringList outputlist( QString::number(0) );
2596  SendResponse(pbssock, outputlist);
2597  return;
2598  }
2599 
2600  // If this recording was made by a another recorder, and that
2601  // recorder is available, tell it to do the deletion.
2602  if (ismaster && recinfo.GetHostname() != gCoreContext->GetHostName())
2603  {
2604  PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname());
2605 
2606  if (slave)
2607  {
2608  int num = slave->DeleteRecording(&recinfo, forceMetadataDelete);
2609 
2610  if (forgetHistory)
2611  recinfo.ForgetHistory();
2612  else if (m_sched &&
2613  recinfo.GetRecordingGroup() != "Deleted" &&
2614  recinfo.GetRecordingGroup() != "LiveTV")
2615  m_sched->RescheduleCheck(recinfo, "DoHandleDelete2");
2616 
2617  if (pbssock)
2618  {
2619  QStringList outputlist( QString::number(num) );
2620  SendResponse(pbssock, outputlist);
2621  }
2622 
2623  slave->DecrRef();
2624  return;
2625  }
2626  }
2627 
2628  QFile checkFile(filename);
2629  bool fileExists = checkFile.exists();
2630  if (!fileExists)
2631  {
2632  QFile checkFileUTF8(QString::fromUtf8(filename.toLatin1().constData()));
2633  fileExists = checkFileUTF8.exists();
2634  if (fileExists)
2635  filename = QString::fromUtf8(filename.toLatin1().constData());
2636  }
2637 
2638  // Allow deleting of files where the recording failed meaning size == 0
2639  // But do not allow deleting of files that appear to be completely absent.
2640  // The latter condition indicates the filesystem containing the file is
2641  // most likely absent and deleting the file metadata is unsafe.
2642  if (fileExists || !recinfo.GetFilesize() || forceMetadataDelete)
2643  {
2644  recinfo.SaveDeletePendingFlag(true);
2645 
2646  DeleteThread *deleteThread = new DeleteThread(this, filename,
2647  recinfo.GetTitle(), recinfo.GetChanID(),
2648  recinfo.GetRecordingStartTime(), recinfo.GetRecordingEndTime(),
2649  forceMetadataDelete);
2650  deleteThread->start();
2651  }
2652  else
2653  {
2654  QString logInfo = QString("chanid %1")
2655  .arg(recinfo.toString(ProgramInfo::kRecordingKey));
2656 
2657  LOG(VB_GENERAL, LOG_ERR,
2658  QString("ERROR when trying to delete file: %1. File doesn't "
2659  "exist. Database metadata will not be removed.")
2660  .arg(filename));
2661  resultCode = -2;
2662  }
2663 
2664  if (pbssock)
2665  {
2666  QStringList outputlist( QString::number(resultCode) );
2667  SendResponse(pbssock, outputlist);
2668  }
2669 
2670  if (forgetHistory)
2671  recinfo.ForgetHistory();
2672  else if (m_sched &&
2673  recinfo.GetRecordingGroup() != "Deleted" &&
2674  recinfo.GetRecordingGroup() != "LiveTV")
2675  m_sched->RescheduleCheck(recinfo, "DoHandleDelete3");
2676 
2677  // Tell MythTV frontends that the recording list needs to be updated.
2678  if (fileExists || !recinfo.GetFilesize() || forceMetadataDelete)
2679  {
2681  QString("REC_DELETED CHANID %1 STARTTIME %2")
2682  .arg(recinfo.GetChanID())
2683  .arg(recinfo.GetRecordingStartTime(MythDate::ISODate)));
2684 
2685  recinfo.SendDeletedEvent();
2686  }
2687 }
2688 
2690 {
2691  if (slist.size() == 3)
2692  {
2693  RecordingInfo recinfo(
2694  slist[1].toUInt(), MythDate::fromString(slist[2]));
2695  if (recinfo.GetChanID())
2696  DoHandleUndeleteRecording(recinfo, pbs);
2697  }
2698  else if (slist.size() >= (1 + NUMPROGRAMLINES))
2699  {
2700  QStringList::const_iterator it = slist.begin()+1;
2701  RecordingInfo recinfo(it, slist.end());
2702  if (recinfo.GetChanID())
2703  DoHandleUndeleteRecording(recinfo, pbs);
2704  }
2705 }
2706 
2708  RecordingInfo &recinfo, PlaybackSock *pbs)
2709 {
2710  int ret = -1;
2711 
2712  MythSocket *pbssock = NULL;
2713  if (pbs)
2714  pbssock = pbs->getSocket();
2715 
2716 #if 0
2717  if (gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0))
2718  {
2719 #endif
2720  recinfo.ApplyRecordRecGroupChange("Default");
2721  recinfo.UpdateLastDelete(false);
2723  if (m_sched)
2724  m_sched->RescheduleCheck(recinfo, "DoHandleUndelete");
2725  ret = 0;
2726 #if 0
2727  }
2728 #endif
2729 
2730  QStringList outputlist( QString::number(ret) );
2731  SendResponse(pbssock, outputlist);
2732 }
2733 
2734 void MainServer::HandleRescheduleRecordings(const QStringList &request,
2735  PlaybackSock *pbs)
2736 {
2737  QStringList result;
2738  if (m_sched)
2739  {
2740  m_sched->Reschedule(request);
2741  result = QStringList( QString::number(1) );
2742  }
2743  else
2744  result = QStringList( QString::number(0) );
2745 
2746  if (pbs)
2747  {
2748  MythSocket *pbssock = pbs->getSocket();
2749  if (pbssock)
2750  SendResponse(pbssock, result);
2751  }
2752 }
2753 
2754 void MainServer::HandleForgetRecording(QStringList &slist, PlaybackSock *pbs)
2755 {
2756  QStringList::const_iterator it = slist.begin() + 1;
2757  RecordingInfo recinfo(it, slist.end());
2758  if (recinfo.GetChanID())
2759  recinfo.ForgetHistory();
2760 
2761  MythSocket *pbssock = NULL;
2762  if (pbs)
2763  pbssock = pbs->getSocket();
2764  if (pbssock)
2765  {
2766  QStringList outputlist( QString::number(0) );
2767  SendResponse(pbssock, outputlist);
2768  }
2769 }
2770 
2771 /*
2772  * \addtogroup myth_network_protocol
2773  * \par GO_TO_SLEEP
2774  * Commands a slave to go to sleep
2775  */
2777 {
2778  QStringList strlist;
2779 
2780  QString sleepCmd = gCoreContext->GetSetting("SleepCommand");
2781  if (!sleepCmd.isEmpty())
2782  {
2783  strlist << "OK";
2784  SendResponse(pbs->getSocket(), strlist);
2785  LOG(VB_GENERAL, LOG_NOTICE,
2786  "Received GO_TO_SLEEP command from master, running SleepCommand.");
2787  myth_system(sleepCmd);
2788  }
2789  else
2790  {
2791  strlist << "ERROR: SleepCommand is empty";
2792  LOG(VB_GENERAL, LOG_ERR,
2793  "ERROR: in HandleGoToSleep(), but no SleepCommand found!");
2794  SendResponse(pbs->getSocket(), strlist);
2795  }
2796 }
2797 
2808 {
2809  QStringList strlist;
2810 
2811  if (allHosts)
2812  {
2813  QMutexLocker locker(&masterFreeSpaceListLock);
2814  strlist = masterFreeSpaceList;
2817  {
2819  {
2821  masterFreeSpaceListWait.wait(locker.mutex());
2822  }
2825  masterFreeSpaceListUpdater, "FreeSpaceUpdater");
2826  }
2827  }
2828  else
2829  BackendQueryDiskSpace(strlist, allHosts, allHosts);
2830 
2831  SendResponse(pbs->getSocket(), strlist);
2832 }
2833 
2840 {
2841  QStringList strlist;
2842  {
2843  QMutexLocker locker(&masterFreeSpaceListLock);
2844  strlist = masterFreeSpaceList;
2847  {
2849  {
2851  masterFreeSpaceListWait.wait(locker.mutex());
2852  }
2855  masterFreeSpaceListUpdater, "FreeSpaceUpdater");
2856  }
2857  }
2858 
2859  // The TotalKB and UsedKB are the last two numbers encoded in the list
2860  QStringList shortlist;
2861  if (strlist.size() < 4)
2862  {
2863  shortlist << QString("0");
2864  shortlist << QString("0");
2865  }
2866  else
2867  {
2868  unsigned int index = (uint)(strlist.size()) - 2;
2869  shortlist << strlist[index++];
2870  shortlist << strlist[index++];
2871  }
2872 
2873  SendResponse(pbs->getSocket(), shortlist);
2874 }
2875 
2883 {
2884  MythSocket *pbssock = pbs->getSocket();
2885 
2886  QStringList strlist;
2887 
2888  double loads[3];
2889  if (getloadavg(loads,3) == -1)
2890  {
2891  strlist << "ERROR";
2892  strlist << "getloadavg() failed";
2893  }
2894  else
2895  strlist << QString::number(loads[0])
2896  << QString::number(loads[1])
2897  << QString::number(loads[2]);
2898 
2899  SendResponse(pbssock, strlist);
2900 }
2901 
2908 {
2909  MythSocket *pbssock = pbs->getSocket();
2910  QStringList strlist;
2911  time_t uptime;
2912 
2913  if (getUptime(uptime))
2914  strlist << QString::number(uptime);
2915  else
2916  {
2917  strlist << "ERROR";
2918  strlist << "Could not determine uptime.";
2919  }
2920 
2921  SendResponse(pbssock, strlist);
2922 }
2923 
2930 {
2931  MythSocket *pbssock = pbs->getSocket();
2932  QStringList strlist;
2933 
2934  strlist << gCoreContext->GetHostName();
2935 
2936  SendResponse(pbssock, strlist);
2937 }
2938 
2945 {
2946  MythSocket *pbssock = pbs->getSocket();
2947  QStringList strlist;
2948  int totalMB, freeMB, totalVM, freeVM;
2949 
2950  if (getMemStats(totalMB, freeMB, totalVM, freeVM))
2951  strlist << QString::number(totalMB) << QString::number(freeMB)
2952  << QString::number(totalVM) << QString::number(freeVM);
2953  else
2954  {
2955  strlist << "ERROR";
2956  strlist << "Could not determine memory stats.";
2957  }
2958 
2959  SendResponse(pbssock, strlist);
2960 }
2961 
2968 {
2969  MythSocket *pbssock = pbs->getSocket();
2970  QStringList strlist;
2971  strlist << MythTZ::getTimeZoneID()
2972  << QString::number(MythTZ::calc_utc_offset())
2974 
2975  SendResponse(pbssock, strlist);
2976 }
2977 
2982 void MainServer::HandleQueryCheckFile(QStringList &slist, PlaybackSock *pbs)
2983 {
2984  MythSocket *pbssock = pbs->getSocket();
2985  bool checkSlaves = slist[1].toInt();
2986 
2987  QStringList::const_iterator it = slist.begin() + 2;
2988  RecordingInfo recinfo(it, slist.end());
2989 
2990  int exists = 0;
2991 
2992  if (recinfo.HasPathname() && (ismaster) &&
2993  (recinfo.GetHostname() != gCoreContext->GetHostName()) &&
2994  (checkSlaves))
2995  {
2996  PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname());
2997 
2998  if (slave)
2999  {
3000  exists = slave->CheckFile(&recinfo);
3001  slave->DecrRef();
3002 
3003  QStringList outputlist( QString::number(exists) );
3004  if (exists)
3005  outputlist << recinfo.GetPathname();
3006  else
3007  outputlist << "";
3008 
3009  SendResponse(pbssock, outputlist);
3010  return;
3011  }
3012  }
3013 
3014  QString pburl;
3015  if (recinfo.HasPathname())
3016  {
3017  pburl = GetPlaybackURL(&recinfo);
3018  exists = QFileInfo(pburl).exists();
3019  if (!exists)
3020  pburl.clear();
3021  }
3022 
3023  QStringList strlist( QString::number(exists) );
3024  strlist << pburl;
3025  SendResponse(pbssock, strlist);
3026 }
3027 
3028 
3033 void MainServer::HandleQueryFileHash(QStringList &slist, PlaybackSock *pbs)
3034 {
3035  QString storageGroup = "Default";
3036  QString hostname = gCoreContext->GetHostName();
3037  QString filename = "";
3038  QStringList res;
3039 
3040  switch (slist.size()) {
3041  case 4:
3042  if (!slist[3].isEmpty())
3043  hostname = slist[3];
3044  case 3:
3045  if (slist[2].isEmpty())
3046  storageGroup = slist[2];
3047  case 2:
3048  filename = slist[1];
3049  if (filename.isEmpty() ||
3050  filename.contains("/../") ||
3051  filename.startsWith("../"))
3052  {
3053  LOG(VB_GENERAL, LOG_ERR,
3054  QString("ERROR checking for file, filename '%1' "
3055  "fails sanity checks").arg(filename));
3056  res << "";
3057  SendResponse(pbs->getSocket(), res);
3058  return;
3059  }
3060  break;
3061  default:
3062  LOG(VB_GENERAL, LOG_ERR, "ERROR, invalid input count for QUERY_FILE_HASH");
3063  res << "";
3064  SendResponse(pbs->getSocket(), res);
3065  return;
3066  }
3067 
3068  QString hash = "";
3069 
3070  if (hostname == gCoreContext->GetHostName())
3071  {
3072  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
3073  QString fullname = sgroup.FindFile(filename);
3074  hash = FileHash(fullname);
3075  }
3076  else
3077  {
3078  PlaybackSock *slave = GetMediaServerByHostname(hostname);
3079  if (slave)
3080  {
3081  hash = slave->GetFileHash(filename, storageGroup);
3082  slave->DecrRef();
3083  }
3084  else
3085  {
3086  MSqlQuery query(MSqlQuery::InitCon());
3087  query.prepare("SELECT hostname FROM settings "
3088  "WHERE value='BackendServerIP' "
3089  "OR value='BackendServerIP6' "
3090  "AND data=:HOSTNAME;");
3091  query.bindValue(":HOSTNAME", hostname);
3092 
3093  if (query.exec() && query.next())
3094  {
3095  hostname = query.value(0).toString();
3096  slave = GetMediaServerByHostname(hostname);
3097  if (slave)
3098  {
3099  hash = slave->GetFileHash(filename, storageGroup);
3100  slave->DecrRef();
3101  }
3102  }
3103  }
3104  }
3105 
3106  res << hash;
3107  SendResponse(pbs->getSocket(), res);
3108 }
3109 
3114 void MainServer::HandleQueryFileExists(QStringList &slist, PlaybackSock *pbs)
3115 {
3116  QString filename = slist[1];
3117  QString storageGroup = "Default";
3118  QStringList retlist;
3119 
3120  if (slist.size() > 2)
3121  storageGroup = slist[2];
3122 
3123  if ((filename.isEmpty()) ||
3124  (filename.contains("/../")) ||
3125  (filename.startsWith("../")))
3126  {
3127  LOG(VB_GENERAL, LOG_ERR,
3128  QString("ERROR checking for file, filename '%1' "
3129  "fails sanity checks").arg(filename));
3130  retlist << "0";
3131  SendResponse(pbs->getSocket(), retlist);
3132  return;
3133  }
3134 
3135  if (storageGroup.isEmpty())
3136  storageGroup = "Default";
3137 
3138  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
3139 
3140  QString fullname = sgroup.FindFile(filename);
3141 
3142  if (!fullname.isEmpty())
3143  {
3144  retlist << "1";
3145  retlist << fullname;
3146 
3147  struct stat fileinfo;
3148  if (stat(fullname.toLocal8Bit().constData(), &fileinfo) >= 0)
3149  {
3150  retlist << QString::number(fileinfo.st_dev);
3151  retlist << QString::number(fileinfo.st_ino);
3152  retlist << QString::number(fileinfo.st_mode);
3153  retlist << QString::number(fileinfo.st_nlink);
3154  retlist << QString::number(fileinfo.st_uid);
3155  retlist << QString::number(fileinfo.st_gid);
3156  retlist << QString::number(fileinfo.st_rdev);
3157  retlist << QString::number(fileinfo.st_size);
3158 #ifdef USING_MINGW
3159  retlist << "0"; // st_blksize
3160  retlist << "0"; // st_blocks
3161 #else
3162  retlist << QString::number(fileinfo.st_blksize);
3163  retlist << QString::number(fileinfo.st_blocks);
3164 #endif
3165  retlist << QString::number(fileinfo.st_atime);
3166  retlist << QString::number(fileinfo.st_mtime);
3167  retlist << QString::number(fileinfo.st_ctime);
3168  }
3169  }
3170  else
3171  retlist << "0";
3172 
3173  SendResponse(pbs->getSocket(), retlist);
3174 }
3175 
3176 void MainServer::getGuideDataThrough(QDateTime &GuideDataThrough)
3177 {
3178  MSqlQuery query(MSqlQuery::InitCon());
3179  query.prepare("SELECT MAX(endtime) FROM program WHERE manualid = 0;");
3180 
3181  if (query.exec() && query.next())
3182  {
3183  GuideDataThrough = MythDate::fromString(query.value(0).toString());
3184  }
3185 }
3186 
3188 {
3189  QDateTime GuideDataThrough;
3190  MythSocket *pbssock = pbs->getSocket();
3191  QStringList strlist;
3192 
3193  getGuideDataThrough(GuideDataThrough);
3194 
3195  if (GuideDataThrough.isNull())
3196  strlist << QString("0000-00-00 00:00");
3197  else
3198  strlist << QDateTime(GuideDataThrough).toString("yyyy-MM-dd hh:mm");
3199 
3200  SendResponse(pbssock, strlist);
3201 }
3202 
3204  QString tmptable, int recordid)
3205 {
3206  MythSocket *pbssock = pbs->getSocket();
3207 
3208  QStringList strList;
3209 
3210  if (m_sched)
3211  {
3212  if (tmptable.isEmpty())
3213  m_sched->GetAllPending(strList);
3214  else
3215  {
3216  Scheduler *sched = new Scheduler(false, encoderList,
3217  tmptable, m_sched);
3218  sched->FillRecordListFromDB(recordid);
3219  sched->GetAllPending(strList);
3220  delete sched;
3221 
3222  if (recordid > 0)
3223  {
3224  MSqlQuery query(MSqlQuery::InitCon());
3225  query.prepare("SELECT NULL FROM record "
3226  "WHERE recordid = :RECID;");
3227  query.bindValue(":RECID", recordid);
3228 
3229  if (query.exec() && query.size())
3230  {
3231  RecordingRule *record = new RecordingRule();
3232  record->m_recordID = recordid;
3233  if (record->Load() &&
3234  record->m_searchType == kManualSearch)
3235  m_sched->RescheduleMatch(recordid, 0, 0, QDateTime(),
3236  "Speculation");
3237  delete record;
3238  }
3239  query.prepare("DELETE FROM program WHERE manualid = :RECID;");
3240  query.bindValue(":RECID", recordid);
3241  if (!query.exec())
3242  MythDB::DBError("MainServer::HandleGetPendingRecordings "
3243  "- delete", query);
3244  }
3245  }
3246  }
3247  else
3248  {
3249  strList << QString::number(0);
3250  strList << QString::number(0);
3251  }
3252 
3253  SendResponse(pbssock, strList);
3254 }
3255 
3257 {
3258  MythSocket *pbssock = pbs->getSocket();
3259 
3260  QStringList strList;
3261 
3262  if (m_sched)
3263  Scheduler::GetAllScheduled(strList);
3264  else
3265  strList << QString::number(0);
3266 
3267  SendResponse(pbssock, strList);
3268 }
3269 
3271  PlaybackSock *pbs)
3272 {
3273  MythSocket *pbssock = pbs->getSocket();
3274 
3275  QStringList::const_iterator it = slist.begin() + 1;
3276  RecordingInfo recinfo(it, slist.end());
3277 
3278  QStringList strlist;
3279 
3280  if (m_sched && recinfo.GetChanID())
3281  m_sched->getConflicting(&recinfo, strlist);
3282  else
3283  strlist << QString::number(0);
3284 
3285  SendResponse(pbssock, strlist);
3286 }
3287 
3289 {
3290  MythSocket *pbssock = pbs->getSocket();
3291 
3292  QStringList strList;
3293 
3294  if (m_expirer)
3295  m_expirer->GetAllExpiring(strList);
3296  else
3297  strList << QString::number(0);
3298 
3299  SendResponse(pbssock, strList);
3300 }
3301 
3302 void MainServer::HandleSGGetFileList(QStringList &sList,
3303  PlaybackSock *pbs)
3304 {
3305  MythSocket *pbssock = pbs->getSocket();
3306  QStringList strList;
3307 
3308  if ((sList.size() < 4) || (sList.size() > 5))
3309  {
3310  LOG(VB_GENERAL, LOG_ERR,
3311  QString("HandleSGGetFileList: Invalid Request. %1")
3312  .arg(sList.join("[]:[]")));
3313  strList << "EMPTY LIST";
3314  SendResponse(pbssock, strList);
3315  return;
3316  }
3317 
3318  QString host = gCoreContext->GetHostName();
3319  QString wantHost = sList.at(1);
3320  QString groupname = sList.at(2);
3321  QString path = sList.at(3);
3322  bool fileNamesOnly = false;
3323 
3324  if (sList.size() >= 5)
3325  fileNamesOnly = sList.at(4).toInt();
3326 
3327  bool slaveUnreachable = false;
3328 
3329  LOG(VB_FILE, LOG_INFO, QString("HandleSGGetFileList: group = %1 host = %2 "
3330  " path = %3 wanthost = %4")
3331  .arg(groupname).arg(host).arg(path).arg(wantHost));
3332 
3333  if ((host.toLower() == wantHost.toLower()) ||
3334  (gCoreContext->GetSetting("BackendServerIP") == wantHost) ||
3335  (gCoreContext->GetSetting("BackendServerIP6") == wantHost))
3336  {
3337  StorageGroup sg(groupname, host);
3338  LOG(VB_FILE, LOG_INFO, "HandleSGGetFileList: Getting local info");
3339  if (fileNamesOnly)
3340  strList = sg.GetFileList(path);
3341  else
3342  strList = sg.GetFileInfoList(path);
3343  }
3344  else
3345  {
3346  PlaybackSock *slave = GetMediaServerByHostname(wantHost);
3347  if (slave)
3348  {
3349  LOG(VB_FILE, LOG_INFO, "HandleSGGetFileList: Getting remote info");
3350  strList = slave->GetSGFileList(wantHost, groupname, path,
3351  fileNamesOnly);
3352  slave->DecrRef();
3353  slaveUnreachable = false;
3354  }
3355  else
3356  {
3357  LOG(VB_FILE, LOG_INFO,
3358  QString("HandleSGGetFileList: Failed to grab slave socket "
3359  ": %1 :").arg(wantHost));
3360  slaveUnreachable = true;
3361  }
3362 
3363  }
3364 
3365  if (slaveUnreachable)
3366  strList << "SLAVE UNREACHABLE: " << host;
3367 
3368  if (strList.isEmpty() || (strList.at(0) == "0"))
3369  strList << "EMPTY LIST";
3370 
3371  SendResponse(pbssock, strList);
3372 }
3373 
3374 void MainServer::HandleSGFileQuery(QStringList &sList,
3375  PlaybackSock *pbs)
3376 {
3377  MythSocket *pbssock = pbs->getSocket();
3378  QStringList strList;
3379 
3380  if (sList.size() != 4)
3381  {
3382  LOG(VB_GENERAL, LOG_ERR,
3383  QString("HandleSGFileQuery: Invalid Request. %1")
3384  .arg(sList.join("[]:[]")));
3385  strList << "EMPTY LIST";
3386  SendResponse(pbssock, strList);
3387  return;
3388  }
3389 
3390  QString wantHost = sList.at(1);
3391  QString groupname = sList.at(2);
3392  QString filename = sList.at(3);
3393 
3394  bool slaveUnreachable = false;
3395 
3396  LOG(VB_FILE, LOG_INFO, QString("HandleSGFileQuery: %1")
3397  .arg(gCoreContext->GenMythURL(wantHost, 0, filename, groupname)));
3398 
3399  if ((wantHost.toLower() == gCoreContext->GetHostName().toLower()) ||
3400  (wantHost == gCoreContext->GetSetting("BackendServerIP")) ||
3401  (wantHost == gCoreContext->GetSetting("BackendServerIP6")))
3402  {
3403  LOG(VB_FILE, LOG_INFO, "HandleSGFileQuery: Getting local info");
3404  StorageGroup sg(groupname, gCoreContext->GetHostName());
3405  strList = sg.GetFileInfo(filename);
3406  }
3407  else
3408  {
3409  PlaybackSock *slave = GetMediaServerByHostname(wantHost);
3410  if (slave)
3411  {
3412  LOG(VB_FILE, LOG_INFO, "HandleSGFileQuery: Getting remote info");
3413  strList = slave->GetSGFileQuery(wantHost, groupname, filename);
3414  slave->DecrRef();
3415  slaveUnreachable = false;
3416  }
3417  else
3418  {
3419  LOG(VB_FILE, LOG_INFO,
3420  QString("HandleSGFileQuery: Failed to grab slave socket : %1 :")
3421  .arg(wantHost));
3422  slaveUnreachable = true;
3423  }
3424 
3425  }
3426 
3427  if (slaveUnreachable)
3428  strList << "SLAVE UNREACHABLE: " << wantHost;
3429 
3430  if (strList.count() == 0 || (strList.at(0) == "0"))
3431  strList << "EMPTY LIST";
3432 
3433  SendResponse(pbssock, strList);
3434 }
3435 
3437 {
3438  MythSocket *pbssock = pbs->getSocket();
3439  QString pbshost = pbs->getHostname();
3440 
3441  QStringList strlist;
3442  int retval;
3443 
3444  EncoderLink *encoder = NULL;
3445  QString enchost;
3446 
3447  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
3448  for (; iter != encoderList->end(); ++iter)
3449  {
3450  EncoderLink *elink = *iter;
3451 
3452  // we're looking for a specific card but this isn't the one we want
3453  if ((cardid != -1) && (cardid != elink->GetCardID()))
3454  continue;
3455 
3456  if (elink->IsLocal())
3457  enchost = gCoreContext->GetHostName();
3458  else
3459  enchost = elink->GetHostName();
3460 
3461  if ((enchost == pbshost) &&
3462  (elink->IsConnected()) &&
3463  (!elink->IsBusy()) &&
3464  (!elink->IsTunerLocked()))
3465  {
3466  encoder = elink;
3467  break;
3468  }
3469  }
3470 
3471  if (encoder)
3472  {
3473  retval = encoder->LockTuner();
3474 
3475  if (retval != -1)
3476  {
3477  QString msg = QString("Cardid %1 LOCKed for external use on %2.")
3478  .arg(retval).arg(pbshost);
3479  LOG(VB_GENERAL, LOG_INFO, msg);
3480 
3481  MSqlQuery query(MSqlQuery::InitCon());
3482  query.prepare("SELECT videodevice, audiodevice, "
3483  "vbidevice "
3484  "FROM capturecard "
3485  "WHERE cardid = :CARDID ;");
3486  query.bindValue(":CARDID", retval);
3487 
3488  if (query.exec() && query.next())
3489  {
3490  // Success
3491  strlist << QString::number(retval)
3492  << query.value(0).toString()
3493  << query.value(1).toString()
3494  << query.value(2).toString();
3495 
3496  if (m_sched)
3497  m_sched->ReschedulePlace("LockTuner");
3498 
3499  SendResponse(pbssock, strlist);
3500  return;
3501  }
3502  else
3503  LOG(VB_GENERAL, LOG_ERR,
3504  "MainServer::LockTuner(): Could not find "
3505  "card info in database");
3506  }
3507  else
3508  {
3509  // Tuner already locked
3510  strlist << "-2" << "" << "" << "";
3511  SendResponse(pbssock, strlist);
3512  return;
3513  }
3514  }
3515 
3516  strlist << "-1" << "" << "" << "";
3517  SendResponse(pbssock, strlist);
3518 }
3519 
3521 {
3522  MythSocket *pbssock = pbs->getSocket();
3523  QStringList strlist;
3524  EncoderLink *encoder = NULL;
3525 
3526  QMap<int, EncoderLink *>::Iterator iter = encoderList->find(cardid);
3527  if (iter == encoderList->end())
3528  {
3529  LOG(VB_GENERAL, LOG_ERR, "MainServer::HandleFreeTuner() " +
3530  QString("Unknown encoder: %1").arg(cardid));
3531  strlist << "FAILED";
3532  }
3533  else
3534  {
3535  encoder = *iter;
3536  encoder->FreeTuner();
3537 
3538  QString msg = QString("Cardid %1 FREED from external use on %2.")
3539  .arg(cardid).arg(pbs->getHostname());
3540  LOG(VB_GENERAL, LOG_INFO, msg);
3541 
3542  if (m_sched)
3543  m_sched->ReschedulePlace("FreeTuner");
3544 
3545  strlist << "OK";
3546  }
3547 
3548  SendResponse(pbssock, strlist);
3549 }
3550 
3552 {
3553  MythSocket *pbssock = pbs->getSocket();
3554  QString pbshost = pbs->getHostname();
3555 
3556  vector<uint> excluded_cardids;
3557  QStringList strlist;
3558  int retval = -1;
3559 
3560  EncoderLink *encoder = NULL;
3561  QString enchost;
3562  uint bestorder = 0;
3563 
3564  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
3565  for (; iter != encoderList->end(); ++iter)
3566  {
3567  EncoderLink *elink = *iter;
3568 
3569  if (elink->IsLocal())
3570  enchost = gCoreContext->GetHostName();
3571  else
3572  enchost = elink->GetHostName();
3573 
3574  LOG(VB_RECORD, LOG_INFO,
3575  QString("Checking card %1. Best card so far %2")
3576  .arg(iter.key()).arg(retval));
3577 
3578  if (!elink->IsConnected() || elink->IsTunerLocked())
3579  continue;
3580 
3581  vector<InputInfo> inputs = elink->GetFreeInputs(excluded_cardids);
3582 
3583  for (uint i = 0; i < inputs.size(); ++i)
3584  {
3585  if (!encoder || inputs[i].livetvorder < bestorder)
3586  {
3587  retval = iter.key();
3588  encoder = elink;
3589  bestorder = inputs[i].livetvorder;
3590  }
3591  }
3592  }
3593 
3594  LOG(VB_RECORD, LOG_INFO,
3595  QString("Best card is %1").arg(retval));
3596 
3597  strlist << QString::number(retval);
3598 
3599  if (encoder)
3600  {
3601  if (encoder->IsLocal())
3602  {
3603  strlist << gCoreContext->GetBackendServerIP();
3604  strlist << gCoreContext->GetSetting("BackendServerPort");
3605  }
3606  else
3607  {
3608  strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName());
3609  strlist << gCoreContext->GetSettingOnHost("BackendServerPort",
3610  encoder->GetHostName(), "-1");
3611  }
3612  }
3613  else
3614  {
3615  strlist << "nohost";
3616  strlist << "-1";
3617  }
3618 
3619  SendResponse(pbssock, strlist);
3620 }
3621 
3623 {
3624  MythSocket *pbssock = pbs->getSocket();
3625 
3626  vector<uint> excluded_cardids;
3627  QStringList strlist;
3628  int count = 0;
3629 
3630  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
3631  for (; iter != encoderList->end(); ++iter)
3632  {
3633  EncoderLink *elink = *iter;
3634 
3635  if (elink->IsConnected() && !elink->IsTunerLocked() &&
3636  !elink->GetFreeInputs(excluded_cardids).empty())
3637  {
3638  count++;
3639  }
3640  }
3641 
3642  strlist << QString::number(count);
3643 
3644  SendResponse(pbssock, strlist);
3645 }
3646 
3647 static bool comp_livetvorder(const InputInfo &a, const InputInfo &b)
3648 {
3649  return a.livetvorder < b.livetvorder;
3650 }
3651 
3653 {
3654  MythSocket *pbssock = pbs->getSocket();
3655 
3656  vector<uint> excluded_cardids;
3657  vector<InputInfo> allinputs;
3658 
3659  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
3660  for (; iter != encoderList->end(); ++iter)
3661  {
3662  EncoderLink *elink = *iter;
3663 
3664  if (!elink->IsConnected() || elink->IsTunerLocked())
3665  continue;
3666 
3667  vector<InputInfo> inputs = elink->GetFreeInputs(excluded_cardids);
3668  allinputs.insert(allinputs.end(), inputs.begin(), inputs.end());
3669  }
3670 
3671  stable_sort(allinputs.begin(), allinputs.end(), comp_livetvorder);
3672 
3673  QStringList strlist;
3674  QMap<int, bool> cardidused;
3675  for (uint i = 0; i < allinputs.size(); ++i)
3676  {
3677  uint cardid = allinputs[i].cardid;
3678  if (!cardidused[cardid])
3679  {
3680  strlist << QString::number(cardid);
3681  cardidused[cardid] = true;
3682  }
3683  }
3684 
3685  if (strlist.size() == 0)
3686  strlist << "0";
3687 
3688  SendResponse(pbssock, strlist);
3689 }
3690 
3692  PlaybackSock *pbs)
3693 {
3694  MythSocket *pbssock = pbs->getSocket();
3695  QString pbshost = pbs->getHostname();
3696 
3697  QStringList strlist;
3698  int retval = -1;
3699  int currrec = slist[1].toInt();
3700 
3701  EncoderLink *encoder = NULL;
3702  QString enchost;
3703 
3704  LOG(VB_RECORD, LOG_INFO, QString("Getting next free recorder after : %1")
3705  .arg(currrec));
3706 
3707  // find current recorder
3708  QMap<int, EncoderLink *>::Iterator iter, curr = encoderList->find(currrec);
3709 
3710  if (currrec > 0 && curr != encoderList->end())
3711  {
3712  vector<uint> excluded_cardids;
3713  excluded_cardids.push_back(currrec);
3714 
3715  // cycle through all recorders
3716  for (iter = curr;;)
3717  {
3718  EncoderLink *elink;
3719 
3720  // last item? go back
3721  if (++iter == encoderList->end())
3722  {
3723  iter = encoderList->begin();
3724  }
3725 
3726  elink = *iter;
3727 
3728  if (retval == -1 && elink->IsConnected() &&
3729  !elink->IsTunerLocked() &&
3730  !elink->GetFreeInputs(excluded_cardids).empty())
3731  {
3732  encoder = elink;
3733  retval = iter.key();
3734  }
3735 
3736  // cycled right through? no more available recorders
3737  if (iter == curr)
3738  break;
3739  }
3740  }
3741  else
3742  {
3743  HandleGetFreeRecorder(pbs);
3744  return;
3745  }
3746 
3747 
3748  strlist << QString::number(retval);
3749 
3750  if (encoder)
3751  {
3752  if (encoder->IsLocal())
3753  {
3754  strlist << gCoreContext->GetBackendServerIP();
3755  strlist << gCoreContext->GetSetting("BackendServerPort");
3756  }
3757  else
3758  {
3759  strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName());
3760  strlist << gCoreContext->GetSettingOnHost("BackendServerPort",
3761  encoder->GetHostName(), "-1");
3762  }
3763  }
3764  else
3765  {
3766  strlist << "nohost";
3767  strlist << "-1";
3768  }
3769 
3770  SendResponse(pbssock, strlist);
3771 }
3772 
3773 static QString cleanup(const QString &str)
3774 {
3775  if (str == " ")
3776  return "";
3777  return str;
3778 }
3779 
3780 static QString make_safe(const QString &str)
3781 {
3782  if (str.isEmpty())
3783  return " ";
3784  return str;
3785 }
3786 
3787 void MainServer::HandleRecorderQuery(QStringList &slist, QStringList &commands,
3788  PlaybackSock *pbs)
3789 {
3790  MythSocket *pbssock = pbs->getSocket();
3791 
3792  if (commands.size() < 2 || slist.size() < 2)
3793  return;
3794 
3795  int recnum = commands[1].toInt();
3796 
3797  QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recnum);
3798  if (iter == encoderList->end())
3799  {
3800  LOG(VB_GENERAL, LOG_ERR, "MainServer::HandleRecorderQuery() " +
3801  QString("Unknown encoder: %1").arg(recnum));
3802  QStringList retlist( "bad" );
3803  SendResponse(pbssock, retlist);
3804  return;
3805  }
3806 
3807  QString command = slist[1];
3808 
3809  QStringList retlist;
3810 
3811  EncoderLink *enc = *iter;
3812  if (!enc->IsConnected())
3813  {
3814  LOG(VB_GENERAL, LOG_ERR, " MainServer::HandleRecorderQuery() " +
3815  QString("Command %1 for unconnected encoder %2")
3816  .arg(command).arg(recnum));
3817  retlist << "bad";
3818  SendResponse(pbssock, retlist);
3819  return;
3820  }
3821 
3822  if (command == "IS_RECORDING")
3823  {
3824  retlist << QString::number((int)enc->IsReallyRecording());
3825  }
3826  else if (command == "GET_FRAMERATE")
3827  {
3828  retlist << QString::number(enc->GetFramerate());
3829  }
3830  else if (command == "GET_FRAMES_WRITTEN")
3831  {
3832  retlist << QString::number(enc->GetFramesWritten());
3833  }
3834  else if (command == "GET_FILE_POSITION")
3835  {
3836  retlist << QString::number(enc->GetFilePosition());
3837  }
3838  else if (command == "GET_MAX_BITRATE")
3839  {
3840  retlist << QString::number(enc->GetMaxBitrate());
3841  }
3842  else if (command == "GET_CURRENT_RECORDING")
3843  {
3844  ProgramInfo *info = enc->GetRecording();
3845  if (info)
3846  {
3847  info->ToStringList(retlist);
3848  delete info;
3849  }
3850  else
3851  {
3853  dummy.ToStringList(retlist);
3854  }
3855  }
3856  else if (command == "GET_KEYFRAME_POS")
3857  {
3858  long long desired = slist[2].toLongLong();
3859  retlist << QString::number(enc->GetKeyframePosition(desired));
3860  }
3861  else if (command == "FILL_POSITION_MAP")
3862  {
3863  int64_t start = slist[2].toLongLong();
3864  int64_t end = slist[3].toLongLong();
3865  frm_pos_map_t map;
3866 
3867  if (!enc->GetKeyframePositions(start, end, map))
3868  {
3869  retlist << "error";
3870  }
3871  else
3872  {
3873  frm_pos_map_t::const_iterator it = map.begin();
3874  for (; it != map.end(); ++it)
3875  {
3876  retlist += QString::number(it.key());
3877  retlist += QString::number(*it);
3878  }
3879  if (retlist.empty())
3880  retlist << "OK";
3881  }
3882  }
3883  else if (command == "FILL_DURATION_MAP")
3884  {
3885  int64_t start = slist[2].toLongLong();
3886  int64_t end = slist[3].toLongLong();
3887  frm_pos_map_t map;
3888 
3889  if (!enc->GetKeyframeDurations(start, end, map))
3890  {
3891  retlist << "error";
3892  }
3893  else
3894  {
3895  frm_pos_map_t::const_iterator it = map.begin();
3896  for (; it != map.end(); ++it)
3897  {
3898  retlist += QString::number(it.key());
3899  retlist += QString::number(*it);
3900  }
3901  if (retlist.empty())
3902  retlist << "OK";
3903  }
3904  }
3905  else if (command == "GET_RECORDING")
3906  {
3907  ProgramInfo *pginfo = enc->GetRecording();
3908  if (pginfo)
3909  {
3910  pginfo->ToStringList(retlist);
3911  delete pginfo;
3912  }
3913  else
3914  {
3916  dummy.ToStringList(retlist);
3917  }
3918  }
3919  else if (command == "FRONTEND_READY")
3920  {
3921  enc->FrontendReady();
3922  retlist << "OK";
3923  }
3924  else if (command == "CANCEL_NEXT_RECORDING")
3925  {
3926  QString cancel = slist[2];
3927  LOG(VB_GENERAL, LOG_NOTICE,
3928  QString("Received: CANCEL_NEXT_RECORDING %1").arg(cancel));
3929  enc->CancelNextRecording(cancel == "1");
3930  retlist << "OK";
3931  }
3932  else if (command == "SPAWN_LIVETV")
3933  {
3934  QString chainid = slist[2];
3935  LiveTVChain *chain = GetExistingChain(chainid);
3936  if (!chain)
3937  {
3938  chain = new LiveTVChain();
3939  chain->LoadFromExistingChain(chainid);
3940  AddToChains(chain);
3941  }
3942 
3943  chain->SetHostSocket(pbssock);
3944 
3945  enc->SpawnLiveTV(chain, slist[3].toInt(), slist[4]);
3946  retlist << "OK";
3947  }
3948  else if (command == "STOP_LIVETV")
3949  {
3950  QString chainid = enc->GetChainID();
3951  enc->StopLiveTV();
3952 
3953  LiveTVChain *chain = GetExistingChain(chainid);
3954  if (chain)
3955  {
3956  chain->DelHostSocket(pbssock);
3957  if (chain->HostSocketCount() == 0)
3958  {
3959  DeleteChain(chain);
3960  }
3961  }
3962 
3963  retlist << "OK";
3964  }
3965  else if (command == "PAUSE")
3966  {
3967  enc->PauseRecorder();
3968  retlist << "OK";
3969  }
3970  else if (command == "FINISH_RECORDING")
3971  {
3972  enc->FinishRecording();
3973  retlist << "OK";
3974  }
3975  else if (command == "SET_LIVE_RECORDING")
3976  {
3977  int recording = slist[2].toInt();
3978  enc->SetLiveRecording(recording);
3979  retlist << "OK";
3980  }
3981  else if (command == "GET_FREE_INPUTS")
3982  {
3983  vector<uint> excluded_cardids;
3984  for (int i = 2; i < slist.size(); i++)
3985  excluded_cardids.push_back(slist[i].toUInt());
3986 
3987  vector<InputInfo> inputs = enc->GetFreeInputs(excluded_cardids);
3988 
3989  if (inputs.empty())
3990  retlist << "EMPTY_LIST";
3991  else
3992  {
3993  for (uint i = 0; i < inputs.size(); i++)
3994  inputs[i].ToStringList(retlist);
3995  }
3996  }
3997  else if (command == "GET_INPUT")
3998  {
3999  QString ret = enc->GetInput();
4000  ret = (ret.isEmpty()) ? "UNKNOWN" : ret;
4001  retlist << ret;
4002  }
4003  else if (command == "SET_INPUT")
4004  {
4005  QString input = slist[2];
4006  QString ret = enc->SetInput(input);
4007  ret = (ret.isEmpty()) ? "UNKNOWN" : ret;
4008  retlist << ret;
4009  }
4010  else if (command == "TOGGLE_CHANNEL_FAVORITE")
4011  {
4012  QString changroup = slist[2];
4013  enc->ToggleChannelFavorite(changroup);
4014  retlist << "OK";
4015  }
4016  else if (command == "CHANGE_CHANNEL")
4017  {
4018  ChannelChangeDirection direction =
4019  (ChannelChangeDirection) slist[2].toInt();
4020  enc->ChangeChannel(direction);
4021  retlist << "OK";
4022  }
4023  else if (command == "SET_CHANNEL")
4024  {
4025  QString name = slist[2];
4026  enc->SetChannel(name);
4027  retlist << "OK";
4028  }
4029  else if (command == "SET_SIGNAL_MONITORING_RATE")
4030  {
4031  int rate = slist[2].toInt();
4032  int notifyFrontend = slist[3].toInt();
4033  int oldrate = enc->SetSignalMonitoringRate(rate, notifyFrontend);
4034  retlist << QString::number(oldrate);
4035  }
4036  else if (command == "GET_COLOUR")
4037  {
4039  retlist << QString::number(ret);
4040  }
4041  else if (command == "GET_CONTRAST")
4042  {
4044  retlist << QString::number(ret);
4045  }
4046  else if (command == "GET_BRIGHTNESS")
4047  {
4049  retlist << QString::number(ret);
4050  }
4051  else if (command == "GET_HUE")
4052  {
4054  retlist << QString::number(ret);
4055  }
4056  else if (command == "CHANGE_COLOUR")
4057  {
4058  int type = slist[2].toInt();
4059  bool up = slist[3].toInt();
4060  int ret = enc->ChangePictureAttribute(
4062  retlist << QString::number(ret);
4063  }
4064  else if (command == "CHANGE_CONTRAST")
4065  {
4066  int type = slist[2].toInt();
4067  bool up = slist[3].toInt();
4068  int ret = enc->ChangePictureAttribute(
4070  retlist << QString::number(ret);
4071  }
4072  else if (command == "CHANGE_BRIGHTNESS")
4073  {
4074  int type= slist[2].toInt();
4075  bool up = slist[3].toInt();
4076  int ret = enc->ChangePictureAttribute(
4078  retlist << QString::number(ret);
4079  }
4080  else if (command == "CHANGE_HUE")
4081  {
4082  int type= slist[2].toInt();
4083  bool up = slist[3].toInt();
4084  int ret = enc->ChangePictureAttribute(
4086  retlist << QString::number(ret);
4087  }
4088  else if (command == "CHECK_CHANNEL")
4089  {
4090  QString name = slist[2];
4091  retlist << QString::number((int)(enc->CheckChannel(name)));
4092  }
4093  else if (command == "SHOULD_SWITCH_CARD")
4094  {
4095  QString chanid = slist[2];
4096  retlist << QString::number((int)(enc->ShouldSwitchToAnotherCard(chanid)));
4097  }
4098  else if (command == "CHECK_CHANNEL_PREFIX")
4099  {
4100  QString needed_spacer;
4101  QString prefix = slist[2];
4102  uint is_complete_valid_channel_on_rec = 0;
4103  bool is_extra_char_useful = false;
4104 
4105  bool match = enc->CheckChannelPrefix(
4106  prefix, is_complete_valid_channel_on_rec,
4107  is_extra_char_useful, needed_spacer);
4108 
4109  retlist << QString::number((int)match);
4110  retlist << QString::number(is_complete_valid_channel_on_rec);
4111  retlist << QString::number((int)is_extra_char_useful);
4112  retlist << ((needed_spacer.isEmpty()) ? QString("X") : needed_spacer);
4113  }
4114  else if (command == "GET_NEXT_PROGRAM_INFO" && (slist.size() >= 6))
4115  {
4116  QString channelname = slist[2];
4117  uint chanid = slist[3].toUInt();
4118  BrowseDirection direction = (BrowseDirection)slist[4].toInt();
4119  QString starttime = slist[5];
4120 
4121  QString title = "", subtitle = "", desc = "", category = "";
4122  QString endtime = "", callsign = "", iconpath = "";
4123  QString seriesid = "", programid = "";
4124 
4125  enc->GetNextProgram(direction,
4126  title, subtitle, desc, category, starttime,
4127  endtime, callsign, iconpath, channelname, chanid,
4128  seriesid, programid);
4129 
4130  retlist << make_safe(title);
4131  retlist << make_safe(subtitle);
4132  retlist << make_safe(desc);
4133  retlist << make_safe(category);
4134  retlist << make_safe(starttime);
4135  retlist << make_safe(endtime);
4136  retlist << make_safe(callsign);
4137  retlist << make_safe(iconpath);
4138  retlist << make_safe(channelname);
4139  retlist << QString::number(chanid);
4140  retlist << make_safe(seriesid);
4141  retlist << make_safe(programid);
4142  }
4143  else if (command == "GET_CHANNEL_INFO")
4144  {
4145  uint chanid = slist[2].toUInt();
4146  uint sourceid = 0;
4147  QString callsign = "", channum = "", channame = "", xmltv = "";
4148 
4149  enc->GetChannelInfo(chanid, sourceid,
4150  callsign, channum, channame, xmltv);
4151 
4152  retlist << QString::number(chanid);
4153  retlist << QString::number(sourceid);
4154  retlist << make_safe(callsign);
4155  retlist << make_safe(channum);
4156  retlist << make_safe(channame);
4157  retlist << make_safe(xmltv);
4158  }
4159  else
4160  {
4161  LOG(VB_GENERAL, LOG_ERR, QString("Unknown command: %1").arg(command));
4162  retlist << "OK";
4163  }
4164 
4165  SendResponse(pbssock, retlist);
4166 }
4167 
4168 void MainServer::HandleSetNextLiveTVDir(QStringList &commands,
4169  PlaybackSock *pbs)
4170 {
4171  MythSocket *pbssock = pbs->getSocket();
4172 
4173  int recnum = commands[1].toInt();
4174 
4175  QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recnum);
4176  if (iter == encoderList->end())
4177  {
4178  LOG(VB_GENERAL, LOG_ERR, "MainServer::HandleSetNextLiveTVDir() " +
4179  QString("Unknown encoder: %1").arg(recnum));
4180  QStringList retlist( "bad" );
4181  SendResponse(pbssock, retlist);
4182  return;
4183  }
4184 
4185  EncoderLink *enc = *iter;
4186  enc->SetNextLiveTVDir(commands[2]);
4187 
4188  QStringList retlist( "OK" );
4189  SendResponse(pbssock, retlist);
4190 }
4191 
4192 void MainServer::HandleSetChannelInfo(QStringList &slist, PlaybackSock *pbs)
4193 {
4194  bool ok = true;
4195  MythSocket *pbssock = pbs->getSocket();
4196  uint chanid = slist[1].toUInt();
4197  uint sourceid = slist[2].toUInt();
4198  QString oldcnum = cleanup(slist[3]);
4199  QString callsign = cleanup(slist[4]);
4200  QString channum = cleanup(slist[5]);
4201  QString channame = cleanup(slist[6]);
4202  QString xmltv = cleanup(slist[7]);
4203 
4204  QStringList retlist;
4205  if (!chanid || !sourceid)
4206  {
4207  retlist << "0";
4208  SendResponse(pbssock, retlist);
4209  return;
4210  }
4211 
4212  QMap<int, EncoderLink *>::iterator it = encoderList->begin();
4213  for (; it != encoderList->end(); ++it)
4214  {
4215  if (*it)
4216  {
4217  ok &= (*it)->SetChannelInfo(chanid, sourceid, oldcnum,
4218  callsign, channum, channame, xmltv);
4219  }
4220  }
4221 
4222  retlist << ((ok) ? "1" : "0");
4223  SendResponse(pbssock, retlist);
4224 }
4225 
4226 void MainServer::HandleRemoteEncoder(QStringList &slist, QStringList &commands,
4227  PlaybackSock *pbs)
4228 {
4229  MythSocket *pbssock = pbs->getSocket();
4230 
4231  int recnum = commands[1].toInt();
4232  QStringList retlist;
4233 
4234  QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recnum);
4235  if (iter == encoderList->end())
4236  {
4237  LOG(VB_GENERAL, LOG_ERR, "MainServer: " +
4238  QString("HandleRemoteEncoder(cmd %1) ").arg(slist[1]) +
4239  QString("Unknown encoder: %1").arg(recnum));
4240  retlist << QString::number((int) kState_Error);
4241  SendResponse(pbssock, retlist);
4242  return;
4243  }
4244 
4245  EncoderLink *enc = *iter;
4246 
4247  QString command = slist[1];
4248 
4249  if (command == "GET_STATE")
4250  {
4251  retlist << QString::number((int)enc->GetState());
4252  }
4253  else if (command == "GET_SLEEPSTATUS")
4254  {
4255  retlist << QString::number(enc->GetSleepStatus());
4256  }
4257  else if (command == "GET_FLAGS")
4258  {
4259  retlist << QString::number(enc->GetFlags());
4260  }
4261  else if (command == "IS_BUSY")
4262  {
4263  int time_buffer = (slist.size() >= 3) ? slist[2].toInt() : 5;
4264  TunedInputInfo busy_input;
4265  retlist << QString::number((int)enc->IsBusy(&busy_input, time_buffer));
4266  busy_input.ToStringList(retlist);
4267  }
4268  else if (command == "MATCHES_RECORDING" &&
4269  slist.size() >= (2 + NUMPROGRAMLINES))
4270  {
4271  QStringList::const_iterator it = slist.begin() + 2;
4272  ProgramInfo pginfo(it, slist.end());
4273 
4274  retlist << QString::number((int)enc->MatchesRecording(&pginfo));
4275  }
4276  else if (command == "START_RECORDING" &&
4277  slist.size() >= (2 + NUMPROGRAMLINES))
4278  {
4279  QStringList::const_iterator it = slist.begin() + 2;
4280  ProgramInfo pginfo(it, slist.end());
4281 
4282  retlist << QString::number(enc->StartRecording(&pginfo));
4283  }
4284  else if (command == "GET_RECORDING_STATUS")
4285  {
4286  retlist << QString::number((int)enc->GetRecordingStatus());
4287  }
4288  else if (command == "RECORD_PENDING" &&
4289  (slist.size() >= 4 + NUMPROGRAMLINES))
4290  {
4291  int secsleft = slist[2].toInt();
4292  int haslater = slist[3].toInt();
4293  QStringList::const_iterator it = slist.begin() + 4;
4294  ProgramInfo pginfo(it, slist.end());
4295 
4296  enc->RecordPending(&pginfo, secsleft, haslater);
4297 
4298  retlist << "OK";
4299  }
4300  else if (command == "CANCEL_NEXT_RECORDING" &&
4301  (slist.size() >= 3))
4302  {
4303  bool cancel = (bool) slist[2].toInt();
4304  enc->CancelNextRecording(cancel);
4305  retlist << "OK";
4306  }
4307  else if (command == "STOP_RECORDING")
4308  {
4309  enc->StopRecording();
4310  retlist << "OK";
4311  }
4312  else if (command == "GET_MAX_BITRATE")
4313  {
4314  retlist << QString::number(enc->GetMaxBitrate());
4315  }
4316  else if (command == "GET_CURRENT_RECORDING")
4317  {
4318  ProgramInfo *info = enc->GetRecording();
4319  if (info)
4320  {
4321  info->ToStringList(retlist);
4322  delete info;
4323  }
4324  else
4325  {
4327  dummy.ToStringList(retlist);
4328  }
4329  }
4330  else if (command == "GET_FREE_INPUTS")
4331  {
4332  vector<uint> excluded_cardids;
4333  for (int i = 2; i < slist.size(); i++)
4334  excluded_cardids.push_back(slist[i].toUInt());
4335 
4336  vector<InputInfo> inputs = enc->GetFreeInputs(excluded_cardids);
4337 
4338  if (inputs.empty())
4339  retlist << "EMPTY_LIST";
4340  else
4341  {
4342  for (uint i = 0; i < inputs.size(); i++)
4343  inputs[i].ToStringList(retlist);
4344  }
4345  }
4346 
4347  SendResponse(pbssock, retlist);
4348 }
4349 
4350 void MainServer::GetActiveBackends(QStringList &hosts)
4351 {
4352  hosts.clear();
4353  hosts << gCoreContext->GetHostName();
4354 
4355  QString hostname;
4356  QReadLocker rlock(&sockListLock);
4357  vector<PlaybackSock*>::iterator it;
4358  for (it = playbackList.begin(); it != playbackList.end(); ++it)
4359  {
4360  if ((*it)->isMediaServer())
4361  {
4362  hostname = (*it)->getHostname();
4363  if (!hosts.contains(hostname))
4364  hosts << hostname;
4365  }
4366  }
4367 }
4368 
4370 {
4371  QStringList retlist;
4372  GetActiveBackends(retlist);
4373  retlist.push_front(QString::number(retlist.size()));
4374  SendResponse(pbs->getSocket(), retlist);
4375 }
4376 
4378  PlaybackSock *pbs)
4379 {
4380  QStringList retlist;
4381  QString queryhostname = slist[1];
4382 
4383  if (gCoreContext->GetHostName() != queryhostname)
4384  {
4385  PlaybackSock *slave = GetSlaveByHostname(queryhostname);
4386  if (slave != NULL)
4387  {
4388  retlist << "TRUE";
4389  slave->DecrRef();
4390  }
4391  else
4392  retlist << "FALSE";
4393  }
4394  else
4395  retlist << "TRUE";
4396 
4397  SendResponse(pbs->getSocket(), retlist);
4398 }
4399 
4400 int MainServer::GetfsID(QList<FileSystemInfo>::iterator fsInfo)
4401 {
4402  QString fskey = fsInfo->getHostname() + ":" + fsInfo->getPath();
4403  QMutexLocker lock(&fsIDcacheLock);
4404  if (!fsIDcache.contains(fskey))
4405  fsIDcache[fskey] = fsIDcache.count();
4406 
4407  return fsIDcache[fskey];
4408 }
4409 
4411 {
4412  size_t totalKBperMin = 0;
4413 
4414  QMap<int, EncoderLink*>::iterator it = encoderList->begin();
4415  for (; it != encoderList->end(); ++it)
4416  {
4417  EncoderLink *enc = *it;
4418 
4419  if (!enc->IsConnected() || !enc->IsBusy())
4420  continue;
4421 
4422  long long maxBitrate = enc->GetMaxBitrate();
4423  if (maxBitrate<=0)
4424  maxBitrate = 19500000LL;
4425  long long thisKBperMin = (((size_t)maxBitrate)*((size_t)15))>>11;
4426  totalKBperMin += thisKBperMin;
4427  LOG(VB_FILE, LOG_INFO, QString("Cardid %1: max bitrate %2 KB/min")
4428  .arg(enc->GetCardID()).arg(thisKBperMin));
4429  }
4430 
4431  LOG(VB_FILE, LOG_INFO,
4432  QString("Maximal bitrate of busy encoders is %1 KB/min")
4433  .arg(totalKBperMin));
4434 
4435  return totalKBperMin;
4436 }
4437 
4438 void MainServer::BackendQueryDiskSpace(QStringList &strlist, bool consolidated,
4439  bool allHosts)
4440 {
4441  QString allHostList = gCoreContext->GetHostName();
4442  int64_t totalKB = -1, usedKB = -1;
4443  QMap <QString, bool>foundDirs;
4444  QString driveKey;
4445  QString localStr = "1";
4446  struct statfs statbuf;
4447  QStringList groups(StorageGroup::kSpecialGroups);
4448  groups.removeAll("LiveTV");
4449  QString specialGroups = groups.join("', '");
4450  QString sql = QString("SELECT MIN(id),dirname "
4451  "FROM storagegroup "
4452  "WHERE hostname = :HOSTNAME "
4453  "AND groupname NOT IN ( '%1' ) "
4454  "GROUP BY dirname;").arg(specialGroups);
4455  MSqlQuery query(MSqlQuery::InitCon());
4456  query.prepare(sql);
4457  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
4458 
4459  if (query.exec())
4460  {
4461  // If we don't have any dirs of our own, fallback to list of Default
4462  // dirs since that is what StorageGroup::Init() does.
4463  if (!query.size())
4464  {
4465  query.prepare("SELECT MIN(id),dirname "
4466  "FROM storagegroup "
4467  "WHERE groupname = :GROUP "
4468  "GROUP BY dirname;");
4469  query.bindValue(":GROUP", "Default");
4470  if (!query.exec())
4471  MythDB::DBError("BackendQueryDiskSpace", query);
4472  }
4473 
4474  QDir checkDir("");
4475  QString dirID;
4476  QString currentDir;
4477  int bSize;
4478  while (query.next())
4479  {
4480  dirID = query.value(0).toString();
4481  /* The storagegroup.dirname column uses utf8_bin collation, so Qt
4482  * uses QString::fromAscii() for toString(). Explicitly convert the
4483  * value using QString::fromUtf8() to prevent corruption. */
4484  currentDir = QString::fromUtf8(query.value(1)
4485  .toByteArray().constData());
4486  if (currentDir.endsWith("/"))
4487  currentDir.remove(currentDir.length() - 1, 1);
4488 
4489  checkDir.setPath(currentDir);
4490  if (!foundDirs.contains(currentDir))
4491  {
4492  if (checkDir.exists())
4493  {
4494  QByteArray cdir = currentDir.toLatin1();
4495  getDiskSpace(cdir.constData(), totalKB, usedKB);
4496  memset(&statbuf, 0, sizeof(statbuf));
4497  localStr = "1"; // Assume local
4498  bSize = 0;
4499 
4500  if (!statfs(currentDir.toLocal8Bit().constData(), &statbuf))
4501  {
4502 #if CONFIG_DARWIN
4503  char *fstypename = statbuf.f_fstypename;
4504  if ((!strcmp(fstypename, "nfs")) || // NFS|FTP
4505  (!strcmp(fstypename, "afpfs")) || // ApplShr
4506  (!strcmp(fstypename, "smbfs"))) // SMB
4507  localStr = "0";
4508 #elif __linux__
4509  long fstype = statbuf.f_type;
4510  if ((fstype == 0x6969) || // NFS
4511  (fstype == 0x517B) || // SMB
4512  (fstype == (long)0xFF534D42)) // CIFS
4513  localStr = "0";
4514 #endif
4515  bSize = statbuf.f_bsize;
4516  }
4517 
4518  strlist << gCoreContext->GetHostName();
4519  strlist << currentDir;
4520  strlist << localStr;
4521  strlist << "-1"; // Ignore fsID
4522  strlist << dirID;
4523  strlist << QString::number(bSize);
4524  strlist << QString::number(totalKB);
4525  strlist << QString::number(usedKB);
4526 
4527  foundDirs[currentDir] = true;
4528  }
4529  else
4530  foundDirs[currentDir] = false;
4531  }
4532  }
4533  }
4534 
4535  if (allHosts)
4536  {
4537  QMap <QString, bool> backendsCounted;
4538  QString pbsHost;
4539 
4540  list<PlaybackSock *> localPlaybackList;
4541 
4542  sockListLock.lockForRead();
4543 
4544  vector<PlaybackSock *>::iterator pbsit = playbackList.begin();
4545  for (; pbsit != playbackList.end(); ++pbsit)
4546  {
4547  PlaybackSock *pbs = *pbsit;
4548 
4549  if ((pbs->IsDisconnected()) ||
4550  (!pbs->isMediaServer()) ||
4551  (pbs->isLocal()) ||
4552  (backendsCounted.contains(pbs->getHostname())))
4553  continue;
4554 
4555  backendsCounted[pbs->getHostname()] = true;
4556  pbs->IncrRef();
4557  localPlaybackList.push_back(pbs);
4558  allHostList += "," + pbs->getHostname();
4559  }
4560 
4561  sockListLock.unlock();
4562 
4563  for (list<PlaybackSock *>::iterator p = localPlaybackList.begin() ;
4564  p != localPlaybackList.end() ; ++p) {
4565  (*p)->GetDiskSpace(strlist);
4566  (*p)->DecrRef();
4567  }
4568  }
4569 
4570  if (!consolidated)
4571  return;
4572 
4573  FileSystemInfo fsInfo;
4574  QList<FileSystemInfo> fsInfos;
4575 
4576  QStringList::const_iterator it = strlist.begin();
4577  while (it != strlist.end())
4578  {
4579  fsInfo.setHostname(*(it++));
4580  fsInfo.setPath(*(it++));
4581  fsInfo.setLocal((*(it++)).toInt() > 0);
4582  fsInfo.setFSysID(-1);
4583  ++it; // Without this, the strlist gets out of whack
4584  fsInfo.setGroupID((*(it++)).toInt());
4585  fsInfo.setBlockSize((*(it++)).toInt());
4586  fsInfo.setTotalSpace((*(it++)).toLongLong());
4587  fsInfo.setUsedSpace((*(it++)).toLongLong());
4588  fsInfos.push_back(fsInfo);
4589  }
4590  strlist.clear();
4591 
4592  // Consolidate hosts sharing storage
4593  int64_t maxWriteFiveSec = GetCurrentMaxBitrate()/12 /*5 seconds*/;
4594  maxWriteFiveSec = max((int64_t)2048, maxWriteFiveSec); // safety for NFS mounted dirs
4595  QList<FileSystemInfo>::iterator it1, it2;
4596  int bSize = 32;
4597  for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
4598  {
4599  if (it1->getFSysID() == -1)
4600  {
4601  it1->setFSysID(GetfsID(it1));
4602  it1->setPath(
4603  it1->getHostname().section(".", 0, 0) + ":" + it1->getPath());
4604  }
4605 
4606  for (it2 = it1 + 1; it2 != fsInfos.end(); ++it2)
4607  {
4608  // our fuzzy comparison uses the maximum of the two block sizes
4609  // or 32, whichever is greater
4610  bSize = max(32, max(it1->getBlockSize(), it2->getBlockSize()) / 1024);
4611  int64_t diffSize = it1->getTotalSpace() - it2->getTotalSpace();
4612  int64_t diffUsed = it1->getUsedSpace() - it2->getUsedSpace();
4613  if (diffSize < 0)
4614  diffSize = 0 - diffSize;
4615  if (diffUsed < 0)
4616  diffUsed = 0 - diffUsed;
4617 
4618  if (it2->getFSysID() == -1 && (diffSize <= bSize) &&
4619  (diffUsed <= maxWriteFiveSec))
4620  {
4621  if (!it1->getHostname().contains(it2->getHostname()))
4622  it1->setHostname(it1->getHostname() + "," + it2->getHostname());
4623  it1->setPath(it1->getPath() + "," +
4624  it2->getHostname().section(".", 0, 0) + ":" + it2->getPath());
4625  fsInfos.erase(it2);
4626  it2 = it1;
4627  }
4628  }
4629  }
4630 
4631  // Passed the cleaned list back
4632  totalKB = 0;
4633  usedKB = 0;
4634  for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
4635  {
4636  strlist << it1->getHostname();
4637  strlist << it1->getPath();
4638  strlist << QString::number(it1->isLocal());
4639  strlist << QString::number(it1->getFSysID());
4640  strlist << QString::number(it1->getGroupID());
4641  strlist << QString::number(it1->getBlockSize());
4642  strlist << QString::number(it1->getTotalSpace());
4643  strlist << QString::number(it1->getUsedSpace());
4644 
4645  totalKB += it1->getTotalSpace();
4646  usedKB += it1->getUsedSpace();
4647  }
4648 
4649  if (allHosts)
4650  {
4651  strlist << allHostList;
4652  strlist << "TotalDiskSpace";
4653  strlist << "0";
4654  strlist << "-2";
4655  strlist << "-2";
4656  strlist << "0";
4657  strlist << QString::number(totalKB);
4658  strlist << QString::number(usedKB);
4659  }
4660 }
4661 
4662 void MainServer::GetFilesystemInfos(QList<FileSystemInfo> &fsInfos)
4663 {
4664  QStringList strlist;
4665  FileSystemInfo fsInfo;
4666 
4667  fsInfos.clear();
4668 
4669  BackendQueryDiskSpace(strlist, false, true);
4670 
4671  QStringList::const_iterator it = strlist.begin();
4672  while (it != strlist.end())
4673  {
4674  fsInfo.setHostname(*(it++));
4675  fsInfo.setPath(*(it++));
4676  fsInfo.setLocal((*(it++)).toInt() > 0);
4677  fsInfo.setFSysID(-1);
4678  ++it;
4679  fsInfo.setGroupID((*(it++)).toInt());
4680  fsInfo.setBlockSize((*(it++)).toInt());
4681  fsInfo.setTotalSpace((*(it++)).toLongLong());
4682  fsInfo.setUsedSpace((*(it++)).toLongLong());
4683  fsInfo.setWeight(0);
4684  fsInfos.push_back(fsInfo);
4685  }
4686 
4687  LOG(VB_SCHEDULE | VB_FILE, LOG_DEBUG, "Determining unique filesystems");
4688  size_t maxWriteFiveSec = GetCurrentMaxBitrate()/12 /*5 seconds*/;
4689  // safety for NFS mounted dirs
4690  maxWriteFiveSec = max((size_t)2048, maxWriteFiveSec);
4691 
4692  FileSystemInfo::Consolidate(fsInfos, false, maxWriteFiveSec);
4693 
4694  QList<FileSystemInfo>::iterator it1;
4695  if (VERBOSE_LEVEL_CHECK(VB_FILE | VB_SCHEDULE, LOG_INFO))
4696  {
4697  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
4698  "--- GetFilesystemInfos directory list start ---");
4699  for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
4700  {
4701  QString msg = QString("Dir: %1:%2")
4702  .arg(it1->getHostname()).arg(it1->getPath());
4703  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, msg) ;
4704  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" Location: %1")
4705  .arg(it1->isLocal() ? "Local" : "Remote"));
4706  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" fsID : %1")
4707  .arg(it1->getFSysID()));
4708  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" dirID : %1")
4709  .arg(it1->getGroupID()));
4710  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" BlkSize : %1")
4711  .arg(it1->getBlockSize()));
4712  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" TotalKB : %1")
4713  .arg(it1->getTotalSpace()));
4714  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" UsedKB : %1")
4715  .arg(it1->getUsedSpace()));
4716  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, QString(" FreeKB : %1")
4717  .arg(it1->getFreeSpace()));
4718  }
4719  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO,
4720  "--- GetFilesystemInfos directory list end ---");
4721  }
4722 }
4723 
4725 {
4726  if (m_ms)
4727  m_ms->DoTruncateThread(this);
4728 }
4729 
4731 {
4732  if (gCoreContext->GetNumSetting("TruncateDeletesSlowly", 0))
4733  {
4734  TruncateAndClose(NULL, ds->m_fd, ds->m_filename, ds->m_size);
4735  }
4736  else
4737  {
4738  QMutexLocker dl(&deletelock);
4739  close(ds->m_fd);
4740  }
4741 }
4742 
4743 bool MainServer::HandleDeleteFile(QStringList &slist, PlaybackSock *pbs)
4744 {
4745  return HandleDeleteFile(slist[1], slist[2], pbs);
4746 }
4747 
4748 bool MainServer::HandleDeleteFile(QString filename, QString storagegroup,
4749  PlaybackSock *pbs)
4750 {
4751  StorageGroup sgroup(storagegroup, "", false);
4752  QStringList retlist;
4753 
4754  if ((filename.isEmpty()) ||
4755  (filename.contains("/../")) ||
4756  (filename.startsWith("../")))
4757  {
4758  LOG(VB_GENERAL, LOG_ERR, QString("ERROR deleting file, filename '%1' "
4759  "fails sanity checks").arg(filename));
4760  if (pbs)
4761  {
4762  retlist << "0";
4763  SendResponse(pbs->getSocket(), retlist);
4764  }
4765  return false;
4766  }
4767 
4768  QString fullfile = sgroup.FindFile(filename);
4769 
4770  if (fullfile.isEmpty()) {
4771  LOG(VB_GENERAL, LOG_ERR,
4772  QString("Unable to find %1 in HandleDeleteFile()") .arg(filename));
4773  if (pbs)
4774  {
4775  retlist << "0";
4776  SendResponse(pbs->getSocket(), retlist);
4777  }
4778  return false;
4779  }
4780 
4781  QFile checkFile(fullfile);
4782  bool followLinks = gCoreContext->GetNumSetting("DeletesFollowLinks", 0);
4783  int fd = -1;
4784  off_t size = 0;
4785 
4786  // This will open the file and unlink the dir entry. The actual file
4787  // data will be deleted in the truncate thread spawned below.
4788  // Since stat fails after unlinking on some filesystems, get the size first
4789  const QFileInfo info(fullfile);
4790  size = info.size();
4791  fd = DeleteFile(fullfile, followLinks);
4792 
4793  if ((fd < 0) && checkFile.exists())
4794  {
4795  LOG(VB_GENERAL, LOG_ERR, QString("Error deleting file: %1.")
4796  .arg(fullfile));
4797  if (pbs)
4798  {
4799  retlist << "0";
4800  SendResponse(pbs->getSocket(), retlist);
4801  }
4802  return false;
4803  }
4804 
4805  if (pbs)
4806  {
4807  retlist << "1";
4808  SendResponse(pbs->getSocket(), retlist);
4809  }
4810 
4811  // DeleteFile() opened up a file for us to delete
4812  if (fd >= 0)
4813  {
4814  // Thread off the actual file truncate
4815  TruncateThread *truncateThread =
4816  new TruncateThread(this, fullfile, fd, size);
4817  truncateThread->run();
4818  }
4819 
4820  return true;
4821 }
4822 
4823 // Helper function for the guts of HandleCommBreakQuery + HandleCutlistQuery
4824 void MainServer::HandleCutMapQuery(const QString &chanid,
4825  const QString &starttime,
4826  PlaybackSock *pbs, bool commbreak)
4827 {
4828  MythSocket *pbssock = NULL;
4829  if (pbs)
4830  pbssock = pbs->getSocket();
4831 
4832  frm_dir_map_t markMap;
4833  frm_dir_map_t::const_iterator it;
4834  QDateTime recstartdt = MythDate::fromTime_t(starttime.toULongLong());
4835  QStringList retlist;
4836  int rowcnt = 0;
4837 
4838  const ProgramInfo pginfo(chanid.toUInt(), recstartdt);
4839 
4840  if (pginfo.GetChanID())
4841  {
4842  if (commbreak)
4843  pginfo.QueryCommBreakList(markMap);
4844  else
4845  pginfo.QueryCutList(markMap);
4846 
4847  for (it = markMap.begin(); it != markMap.end(); ++it)
4848  {
4849  rowcnt++;
4850  QString intstr = QString("%1").arg(*it);
4851  retlist << intstr;
4852  retlist << QString::number(it.key());
4853  }
4854  }
4855 
4856  if (rowcnt > 0)
4857  retlist.prepend(QString("%1").arg(rowcnt));
4858  else
4859  retlist << "-1";
4860 
4861  if (pbssock)
4862  SendResponse(pbssock, retlist);
4863 
4864  return;
4865 }
4866 
4867 void MainServer::HandleCommBreakQuery(const QString &chanid,
4868  const QString &starttime,
4869  PlaybackSock *pbs)
4870 {
4871 // Commercial break query
4872 // Format: QUERY_COMMBREAK <chanid> <starttime>
4873 // chanid is chanid, starttime is startime of program in
4874 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
4875 // a ProgramInfo structure in a string list.
4876 // Return structure is [number of rows] followed by a triplet of values:
4877 // each triplet : [type] [long portion 1] [long portion 2]
4878 // type is the value in the map, right now 4 = commbreak start, 5= end
4879  return HandleCutMapQuery(chanid, starttime, pbs, true);
4880 }
4881 
4882 void MainServer::HandleCutlistQuery(const QString &chanid,
4883  const QString &starttime,
4884  PlaybackSock *pbs)
4885 {
4886 // Cutlist query
4887 // Format: QUERY_CUTLIST <chanid> <starttime>
4888 // chanid is chanid, starttime is startime of program in
4889 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
4890 // a ProgramInfo structure in a string list.
4891 // Return structure is [number of rows] followed by a triplet of values:
4892 // each triplet : [type] [long portion 1] [long portion 2]
4893 // type is the value in the map, right now 0 = commbreak start, 1 = end
4894  return HandleCutMapQuery(chanid, starttime, pbs, false);
4895 }
4896 
4897 
4898 void MainServer::HandleBookmarkQuery(const QString &chanid,
4899  const QString &starttime,
4900  PlaybackSock *pbs)
4901 // Bookmark query
4902 // Format: QUERY_BOOKMARK <chanid> <starttime>
4903 // chanid is chanid, starttime is startime of program in
4904 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
4905 // a ProgramInfo structure in a string list.
4906 // Return value is a long-long encoded as two separate values
4907 {
4908  MythSocket *pbssock = NULL;
4909  if (pbs)
4910  pbssock = pbs->getSocket();
4911 
4912  QDateTime recstartts = MythDate::fromTime_t(starttime.toULongLong());
4913 
4914  uint64_t bookmark = ProgramInfo::QueryBookmark(
4915  chanid.toUInt(), recstartts);
4916 
4917  QStringList retlist;
4918  retlist << QString::number(bookmark);
4919 
4920  if (pbssock)
4921  SendResponse(pbssock, retlist);
4922 
4923  return;
4924 }
4925 
4926 
4927 void MainServer::HandleSetBookmark(QStringList &tokens,
4928  PlaybackSock *pbs)
4929 {
4930 // Bookmark query
4931 // Format: SET_BOOKMARK <chanid> <starttime> <long part1> <long part2>
4932 // chanid is chanid, starttime is startime of program in
4933 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
4934 // a ProgramInfo structure in a string list. The two longs are the two
4935 // portions of the bookmark value to set.
4936 
4937  MythSocket *pbssock = NULL;
4938  if (pbs)
4939  pbssock = pbs->getSocket();
4940 
4941  QString chanid = tokens[1];
4942  QString starttime = tokens[2];
4943  long long bookmark = tokens[3].toLongLong();
4944 
4945  QDateTime recstartts = MythDate::fromTime_t(starttime.toULongLong());
4946  QStringList retlist;
4947 
4948  ProgramInfo pginfo(chanid.toUInt(), recstartts);
4949 
4950  if (pginfo.GetChanID())
4951  {
4952  pginfo.SaveBookmark(bookmark);
4953  retlist << "OK";
4954  }
4955  else
4956  retlist << "FAILED";
4957 
4958  if (pbssock)
4959  SendResponse(pbssock, retlist);
4960 
4961  return;
4962 }
4963 
4964 void MainServer::HandleSettingQuery(QStringList &tokens, PlaybackSock *pbs)
4965 {
4966 // Format: QUERY_SETTING <hostname> <setting>
4967 // Returns setting value as a string
4968 
4969  MythSocket *pbssock = NULL;
4970  if (pbs)
4971  pbssock = pbs->getSocket();
4972 
4973  QString hostname = tokens[1];
4974  QString setting = tokens[2];
4975  QStringList retlist;
4976 
4977  QString retvalue = gCoreContext->GetSettingOnHost(setting, hostname, "-1");
4978 
4979  retlist << retvalue;
4980  if (pbssock)
4981  SendResponse(pbssock, retlist);
4982 
4983  return;
4984 }
4985 
4986 void MainServer::HandleDownloadFile(const QStringList &command,
4987  PlaybackSock *pbs)
4988 {
4989  bool synchronous = (command[0] == "DOWNLOAD_FILE_NOW");
4990  QString srcURL = command[1];
4991  QString storageGroup = command[2];
4992  QString filename = command[3];
4993  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false);
4994  QString outDir = sgroup.FindNextDirMostFree();
4995  QString outFile;
4996  QStringList retlist;
4997 
4998  MythSocket *pbssock = NULL;
4999  if (pbs)
5000  pbssock = pbs->getSocket();
5001 
5002  if (filename.isEmpty())
5003  {
5004  QFileInfo finfo(srcURL);
5005  filename = finfo.fileName();
5006  }
5007 
5008  if (outDir.isEmpty())
5009  {
5010  LOG(VB_GENERAL, LOG_ERR,
5011  QString("Unable to determine directory "
5012  "to write to in %1 write command").arg(command[0]));
5013  retlist << "downloadfile_directory_not_found";
5014  if (pbssock)
5015  SendResponse(pbssock, retlist);
5016  return;
5017  }
5018 
5019  if ((filename.contains("/../")) ||
5020  (filename.startsWith("../")))
5021  {
5022  LOG(VB_GENERAL, LOG_ERR,
5023  QString("ERROR: %1 write filename '%2' does not pass "
5024  "sanity checks.") .arg(command[0]).arg(filename));
5025  retlist << "downloadfile_filename_dangerous";
5026  if (pbssock)
5027  SendResponse(pbssock, retlist);
5028  return;
5029  }
5030 
5031  outFile = outDir + "/" + filename;
5032 
5033  if (synchronous)
5034  {
5035  if (GetMythDownloadManager()->download(srcURL, outFile))
5036  {
5037  retlist << "OK";
5038  retlist << gCoreContext->GetMasterHostPrefix(storageGroup)
5039  + filename;
5040  }
5041  else
5042  retlist << "ERROR";
5043  }
5044  else
5045  {
5046  QMutexLocker locker(&m_downloadURLsLock);
5047  m_downloadURLs[outFile] =
5048  gCoreContext->GetMasterHostPrefix(storageGroup) +
5050 
5051  GetMythDownloadManager()->queueDownload(srcURL, outFile, this);
5052  retlist << "OK";
5053  retlist << gCoreContext->GetMasterHostPrefix(storageGroup) + filename;
5054  }
5055 
5056  if (pbssock)
5057  SendResponse(pbssock, retlist);
5058 }
5059 
5060 void MainServer::HandleSetSetting(QStringList &tokens,
5061  PlaybackSock *pbs)
5062 {
5063 // Format: SET_SETTING <hostname> <setting> <value>
5064  MythSocket *pbssock = NULL;
5065  if (pbs)
5066  pbssock = pbs->getSocket();
5067 
5068  QString hostname = tokens[1];
5069  QString setting = tokens[2];
5070  QString svalue = tokens[3];
5071  QStringList retlist;
5072 
5073  if (gCoreContext->SaveSettingOnHost(setting, svalue, hostname))
5074  retlist << "OK";
5075  else
5076  retlist << "ERROR";
5077 
5078  if (pbssock)
5079  SendResponse(pbssock, retlist);
5080 
5081  return;
5082 }
5083 
5085 {
5086  MythSocket *pbssock = pbs->getSocket();
5087 
5088  QStringList retlist;
5089 
5090  if (metadatafactory)
5091  {
5092  QStringList hosts;
5093  GetActiveBackends(hosts);
5094  metadatafactory->VideoScan(hosts);
5095  retlist << "OK";
5096  }
5097  else
5098  retlist << "ERROR";
5099 
5100  if (pbssock)
5101  SendResponse(pbssock, retlist);
5102 }
5103 
5104 void MainServer::HandleFileTransferQuery(QStringList &slist,
5105  QStringList &commands,
5106  PlaybackSock *pbs)
5107 {
5108  MythSocket *pbssock = pbs->getSocket();
5109 
5110  int recnum = commands[1].toInt();
5111  QString command = slist[1];
5112 
5113  QStringList retlist;
5114 
5115  sockListLock.lockForRead();
5116  FileTransfer *ft = GetFileTransferByID(recnum);
5117  if (!ft)
5118  {
5119  if (command == "DONE")
5120  {
5121  // if there is an error opening the file, we may not have a
5122  // FileTransfer instance for this connection.
5123  retlist << "OK";
5124  }
5125  else
5126  {
5127  LOG(VB_GENERAL, LOG_ERR, QString("Unknown file transfer socket: %1")
5128  .arg(recnum));
5129  retlist << QString("ERROR: Unknown file transfer socket: %1")
5130  .arg(recnum);
5131  }
5132 
5133  sockListLock.unlock();
5134  SendResponse(pbssock, retlist);
5135  return;
5136  }
5137 
5138  ft->IncrRef();
5139  sockListLock.unlock();
5140 
5141  if (command == "REQUEST_BLOCK")
5142  {
5143  int size = slist[2].toInt();
5144 
5145  retlist << QString::number(ft->RequestBlock(size));
5146  }
5147  else if (command == "WRITE_BLOCK")
5148  {
5149  int size = slist[2].toInt();
5150 
5151  retlist << QString::number(ft->WriteBlock(size));
5152  }
5153  else if (command == "SEEK")
5154  {
5155  long long pos = slist[2].toLongLong();
5156  int whence = slist[3].toInt();
5157  long long curpos = slist[4].toLongLong();
5158 
5159  long long ret = ft->Seek(curpos, pos, whence);
5160  retlist << QString::number(ret);
5161  }
5162  else if (command == "IS_OPEN")
5163  {
5164  bool isopen = ft->isOpen();
5165 
5166  retlist << QString::number(isopen);
5167  }
5168  else if (command == "REOPEN")
5169  {
5170  retlist << QString::number(ft->ReOpen(slist[2]));
5171  }
5172  else if (command == "DONE")
5173  {
5174  ft->Stop();
5175  retlist << "OK";
5176  }
5177  else if (command == "SET_TIMEOUT")
5178  {
5179  bool fast = slist[2].toInt();
5180  ft->SetTimeout(fast);
5181  retlist << "OK";
5182  }
5183  else
5184  {
5185  LOG(VB_GENERAL, LOG_ERR, QString("Unknown command: %1").arg(command));
5186  retlist << "OK";
5187  }
5188 
5189  ft->DecrRef();
5190 
5191  SendResponse(pbssock, retlist);
5192 }
5193 
5194 void MainServer::HandleGetRecorderNum(QStringList &slist, PlaybackSock *pbs)
5195 {
5196  MythSocket *pbssock = pbs->getSocket();
5197 
5198  int retval = -1;
5199 
5200  QStringList::const_iterator it = slist.begin() + 1;
5201  ProgramInfo pginfo(it, slist.end());
5202 
5203  EncoderLink *encoder = NULL;
5204 
5205  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
5206  for (; iter != encoderList->end(); ++iter)
5207  {
5208  EncoderLink *elink = *iter;
5209 
5210  if (elink->IsConnected() && elink->MatchesRecording(&pginfo))
5211  {
5212  retval = iter.key();
5213  encoder = elink;
5214  }
5215  }
5216 
5217  QStringList strlist( QString::number(retval) );
5218 
5219  if (encoder)
5220  {
5221  if (encoder->IsLocal())
5222  {
5223  strlist << gCoreContext->GetBackendServerIP();
5224  strlist << gCoreContext->GetSetting("BackendServerPort");
5225  }
5226  else
5227  {
5228  strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName());
5229  strlist << gCoreContext->GetSettingOnHost("BackendServerPort",
5230  encoder->GetHostName(), "-1");
5231  }
5232  }
5233  else
5234  {
5235  strlist << "nohost";
5236  strlist << "-1";
5237  }
5238 
5239  SendResponse(pbssock, strlist);
5240 }
5241 
5243  PlaybackSock *pbs)
5244 {
5245  MythSocket *pbssock = pbs->getSocket();
5246 
5247  int recordernum = slist[1].toInt();
5248  EncoderLink *encoder = NULL;
5249  QStringList strlist;
5250 
5251  QMap<int, EncoderLink *>::Iterator iter = encoderList->find(recordernum);
5252 
5253  if (iter != encoderList->end())
5254  encoder = (*iter);
5255 
5256  if (encoder && encoder->IsConnected())
5257  {
5258  if (encoder->IsLocal())
5259  {
5260  strlist << gCoreContext->GetBackendServerIP();
5261  strlist << gCoreContext->GetSetting("BackendServerPort");
5262  }
5263  else
5264  {
5265  strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName());
5266  strlist << gCoreContext->GetSettingOnHost("BackendServerPort",
5267  encoder->GetHostName(), "-1");
5268  }
5269  }
5270  else
5271  {
5272  strlist << "nohost";
5273  strlist << "-1";
5274  }
5275 
5276  SendResponse(pbssock, strlist);
5277 }
5278 
5279 void MainServer::HandleMessage(QStringList &slist, PlaybackSock *pbs)
5280 {
5281  if (slist.size() < 2)
5282  return;
5283 
5284  MythSocket *pbssock = pbs->getSocket();
5285 
5286  QString message = slist[1];
5287  QStringList extra_data;
5288  for (uint i = 2; i < (uint) slist.size(); i++)
5289  extra_data.push_back(slist[i]);
5290 
5291  if (extra_data.empty())
5292  {
5293  MythEvent me(message);
5294  gCoreContext->dispatch(me);
5295  }
5296  else
5297  {
5298  MythEvent me(message, extra_data);
5299  gCoreContext->dispatch(me);
5300  }
5301 
5302  QStringList retlist( "OK" );
5303 
5304  SendResponse(pbssock, retlist);
5305 }
5306 
5307 void MainServer::HandleSetVerbose(QStringList &slist, PlaybackSock *pbs)
5308 {
5309  MythSocket *pbssock = pbs->getSocket();
5310  QStringList retlist;
5311 
5312  QString newverbose = slist[1];
5313  int len = newverbose.length();
5314  if (len > 12)
5315  {
5316  verboseArgParse(newverbose.right(len-12));
5317  logPropagateCalc();
5318 
5319  LOG(VB_GENERAL, LOG_NOTICE,
5320  QString("Verbose mask changed, new mask is: %1")
5321  .arg(verboseString));
5322 
5323  retlist << "OK";
5324  }
5325  else
5326  {
5327  LOG(VB_GENERAL, LOG_ERR, QString("Invalid SET_VERBOSE string: '%1'")
5328  .arg(newverbose));
5329  retlist << "Failed";
5330  }
5331 
5332  SendResponse(pbssock, retlist);
5333 }
5334 
5335 void MainServer::HandleSetLogLevel(QStringList &slist, PlaybackSock *pbs)
5336 {
5337  MythSocket *pbssock = pbs->getSocket();
5338  QStringList retlist;
5339  QString newstring = slist[1];
5340  LogLevel_t newlevel = LOG_UNKNOWN;
5341 
5342  int len = newstring.length();
5343  if (len > 14)
5344  {
5345  newlevel = logLevelGet(newstring.right(len-14));
5346  if (newlevel != LOG_UNKNOWN)
5347  {
5348  logLevel = newlevel;
5349  logPropagateCalc();
5350  LOG(VB_GENERAL, LOG_NOTICE,
5351  QString("Log level changed, new level is: %1")
5352  .arg(logLevelGetName(logLevel)));
5353 
5354  retlist << "OK";
5355  }
5356  }
5357 
5358  if (newlevel == LOG_UNKNOWN)
5359  {
5360  LOG(VB_GENERAL, LOG_ERR, QString("Invalid SET_VERBOSE string: '%1'")
5361  .arg(newstring));
5362  retlist << "Failed";
5363  }
5364 
5365  SendResponse(pbssock, retlist);
5366 }
5367 
5368 void MainServer::HandleIsRecording(QStringList &slist, PlaybackSock *pbs)
5369 {
5370  (void)slist;
5371 
5372  MythSocket *pbssock = pbs->getSocket();
5373  int RecordingsInProgress = 0;
5374  int LiveTVRecordingsInProgress = 0;
5375  QStringList retlist;
5376 
5377  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
5378  for (; iter != encoderList->end(); ++iter)
5379  {
5380  EncoderLink *elink = *iter;
5381  if (elink->IsBusyRecording()) {
5382  RecordingsInProgress++;
5383 
5384  ProgramInfo *info = elink->GetRecording();
5385  if (info && info->GetRecordingGroup() == "LiveTV")
5386  LiveTVRecordingsInProgress++;
5387 
5388  delete info;
5389  }
5390  }
5391 
5392  retlist << QString::number(RecordingsInProgress);
5393  retlist << QString::number(LiveTVRecordingsInProgress);
5394 
5395  SendResponse(pbssock, retlist);
5396 }
5397 
5398 void MainServer::HandleGenPreviewPixmap(QStringList &slist, PlaybackSock *pbs)
5399 {
5400  MythSocket *pbssock = pbs->getSocket();
5401 
5402  if (slist.size() < 3)
5403  {
5404  LOG(VB_GENERAL, LOG_ERR, LOC + "Too few params in pixmap request");
5405  QStringList outputlist("ERROR");
5406  outputlist += "TOO_FEW_PARAMS";
5407  SendResponse(pbssock, outputlist);
5408  return;
5409  }
5410 
5411  bool time_fmt_sec = true;
5412  long long time = -1;
5413  QString outputfile;
5414  int width = -1;
5415  int height = -1;
5416  bool has_extra_data = false;
5417 
5418  QString token = slist[1];
5419  if (token.isEmpty())
5420  {
5421  LOG(VB_GENERAL, LOG_ERR, LOC +
5422  "Failed to parse pixmap request. Token absent");
5423  QStringList outputlist("ERROR");
5424  outputlist += "TOKEN_ABSENT";
5425  SendResponse(pbssock, outputlist);
5426  return;
5427  }
5428 
5429  QStringList::const_iterator it = slist.begin() + 2;
5430  QStringList::const_iterator end = slist.end();
5431  ProgramInfo pginfo(it, end);
5432  bool ok = pginfo.HasPathname();
5433  if (!ok)
5434  {
5435  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to parse pixmap request. "
5436  "ProgramInfo missing pathname");
5437  QStringList outputlist("BAD");
5438  outputlist += "NO_PATHNAME";
5439  SendResponse(pbssock, outputlist);
5440  return;
5441  }
5442  if (token.toLower() == "do_not_care")
5443  {
5444  token = QString("%1:%2")
5445  .arg(pginfo.MakeUniqueKey()).arg(random());
5446  }
5447  if (it != slist.end())
5448  (time_fmt_sec = ((*it).toLower() == "s")), ++it;
5449  if (it != slist.end())
5450  (time = (*it).toLongLong()), ++it;
5451  if (it != slist.end())
5452  (outputfile = *it), ++it;
5453  outputfile = (outputfile == "<EMPTY>") ? QString::null : outputfile;
5454  if (it != slist.end())
5455  {
5456  width = (*it).toInt(&ok); ++it;
5457  width = (ok) ? width : -1;
5458  }
5459  if (it != slist.end())
5460  {
5461  height = (*it).toInt(&ok); ++it;
5462  height = (ok) ? height : -1;
5463  has_extra_data = true;
5464  }
5465  QSize outputsize = QSize(width, height);
5466 
5467  if (has_extra_data)
5468  {
5469  LOG(VB_PLAYBACK, LOG_INFO,
5470  QString("HandleGenPreviewPixmap got extra data\n\t\t\t"
5471  "%1%2 %3x%4 '%5'")
5472  .arg(time).arg(time_fmt_sec?"s":"f")
5473  .arg(width).arg(height).arg(outputfile));
5474  }
5475 
5476  pginfo.SetPathname(GetPlaybackURL(&pginfo));
5477 
5478  m_previewRequestedBy[token] = pbs->getHostname();
5479 
5480  if ((ismaster) &&
5481  (pginfo.GetHostname() != gCoreContext->GetHostName()) &&
5482  (!masterBackendOverride || !pginfo.IsLocal()))
5483  {
5484  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
5485 
5486  if (slave)
5487  {
5488  QStringList outputlist;
5489  if (has_extra_data)
5490  {
5491  outputlist = slave->GenPreviewPixmap(
5492  token, &pginfo, time_fmt_sec, time, outputfile, outputsize);
5493  }
5494  else
5495  {
5496  outputlist = slave->GenPreviewPixmap(token, &pginfo);
5497  }
5498 
5499  slave->DecrRef();
5500 
5501  if (outputlist.empty() || outputlist[0] != "OK")
5502  m_previewRequestedBy.remove(token);
5503 
5504  SendResponse(pbssock, outputlist);
5505  return;
5506  }
5507  LOG(VB_GENERAL, LOG_ERR, LOC +
5508  QString("HandleGenPreviewPixmap() "
5509  "Couldn't find backend for:\n\t\t\t%1")
5510  .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)));
5511  }
5512 
5513  if (!pginfo.IsLocal())
5514  {
5515  LOG(VB_GENERAL, LOG_ERR, LOC + "HandleGenPreviewPixmap: Unable to "
5516  "find file locally, unable to make preview image.");
5517  QStringList outputlist( "ERROR" );
5518  outputlist += "FILE_INACCESSIBLE";
5519  SendResponse(pbssock, outputlist);
5520  m_previewRequestedBy.remove(token);
5521  return;
5522  }
5523 
5524  if (has_extra_data)
5525  {
5527  pginfo, outputsize, outputfile, time, time_fmt_sec, token);
5528  }
5529  else
5530  {
5532  }
5533 
5534  QStringList outputlist("OK");
5535  if (!outputfile.isEmpty())
5536  outputlist += outputfile;
5537  SendResponse(pbssock, outputlist);
5538 }
5539 
5541 {
5542  MythSocket *pbssock = pbs->getSocket();
5543 
5544  QStringList::const_iterator it = slist.begin() + 1;
5545  ProgramInfo pginfo(it, slist.end());
5546 
5547  pginfo.SetPathname(GetPlaybackURL(&pginfo));
5548 
5549  QStringList strlist;
5550 
5551  if (ismaster &&
5552  (pginfo.GetHostname() != gCoreContext->GetHostName()) &&
5553  (!masterBackendOverride || !pginfo.IsLocal()))
5554  {
5555  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
5556 
5557  if (slave)
5558  {
5559  QDateTime slavetime = slave->PixmapLastModified(&pginfo);
5560  slave->DecrRef();
5561 
5562  strlist = (slavetime.isValid()) ?
5563  QStringList(QString::number(slavetime.toTime_t())) :
5564  QStringList("BAD");
5565 
5566  SendResponse(pbssock, strlist);
5567  return;
5568  }
5569  else
5570  {
5571  LOG(VB_GENERAL, LOG_ERR, LOC +
5572  QString("HandlePixmapLastModified() "
5573  "Couldn't find backend for:\n\t\t\t%1")
5574  .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)));
5575  }
5576  }
5577 
5578  if (!pginfo.IsLocal())
5579  {
5580  LOG(VB_GENERAL, LOG_ERR,
5581  "MainServer: HandlePixmapLastModified: Unable to "
5582  "find file locally, unable to get last modified date.");
5583  QStringList outputlist( "BAD" );
5584  SendResponse(pbssock, outputlist);
5585  return;
5586  }
5587 
5588  QString filename = pginfo.GetPathname() + ".png";
5589 
5590  QFileInfo finfo(filename);
5591 
5592  if (finfo.exists())
5593  {
5594  QDateTime lastmodified = finfo.lastModified();
5595  strlist = QStringList(QString::number(lastmodified.toTime_t()));
5596  }
5597  else
5598  strlist = QStringList( "BAD" );
5599 
5600  SendResponse(pbssock, strlist);
5601 }
5602 
5604  const QStringList &slist, PlaybackSock *pbs)
5605 {
5606  QStringList strlist;
5607 
5608  MythSocket *pbssock = pbs->getSocket();
5609  if (slist.size() < (3 + NUMPROGRAMLINES))
5610  {
5611  strlist = QStringList("ERROR");
5612  strlist += "1: Parameter list too short";
5613  SendResponse(pbssock, strlist);
5614  return;
5615  }
5616 
5617  QDateTime cachemodified;
5618  if (slist[1].toInt() != -1)
5619  cachemodified = MythDate::fromTime_t(slist[1].toInt());
5620 
5621  int max_file_size = slist[2].toInt();
5622 
5623  QStringList::const_iterator it = slist.begin() + 3;
5624  ProgramInfo pginfo(it, slist.end());
5625 
5626  if (!pginfo.HasPathname())
5627  {
5628  strlist = QStringList("ERROR");
5629  strlist += "2: Invalid ProgramInfo";
5630  SendResponse(pbssock, strlist);
5631  return;
5632  }
5633 
5634  pginfo.SetPathname(GetPlaybackURL(&pginfo) + ".png");
5635  if (pginfo.IsLocal())
5636  {
5637  QFileInfo finfo(pginfo.GetPathname());
5638  if (finfo.exists())
5639  {
5640  size_t fsize = finfo.size();
5641  QDateTime lastmodified = finfo.lastModified();
5642  bool out_of_date = !cachemodified.isValid() ||
5643  (lastmodified > cachemodified);
5644 
5645  if (out_of_date && (fsize > 0) && ((ssize_t)fsize < max_file_size))
5646  {
5647  QByteArray data;
5648  QFile file(pginfo.GetPathname());
5649  bool open_ok = file.open(QIODevice::ReadOnly);
5650  if (open_ok)
5651  data = file.readAll();
5652 
5653  if (data.size())
5654  {
5655  LOG(VB_FILE, LOG_INFO, QString("Read preview file '%1'")
5656  .arg(pginfo.GetPathname()));
5657  strlist += QString::number(lastmodified.toTime_t());
5658  strlist += QString::number(data.size());
5659  strlist += QString::number(qChecksum(data.constData(),
5660  data.size()));
5661  strlist += QString(data.toBase64());
5662  }
5663  else
5664  {
5665  LOG(VB_GENERAL, LOG_ERR,
5666  QString("Failed to read preview file '%1'")
5667  .arg(pginfo.GetPathname()));
5668 
5669  strlist = QStringList("ERROR");
5670  strlist +=
5671  QString("3: Failed to read preview file '%1'%2")
5672  .arg(pginfo.GetPathname())
5673  .arg((open_ok) ? "" : " open failed");
5674  }
5675  }
5676  else if (out_of_date && (max_file_size > 0))
5677  {
5678  if (fsize >= (size_t) max_file_size)
5679  {
5680  strlist = QStringList("WARNING");
5681  strlist += QString("1: Preview file too big %1 > %2")
5682  .arg(fsize).arg(max_file_size);
5683  }
5684  else
5685  {
5686  strlist = QStringList("ERROR");
5687  strlist += "4: Preview file is invalid";
5688  }
5689  }
5690  else
5691  {
5692  strlist += QString::number(lastmodified.toTime_t());
5693  }
5694 
5695  SendResponse(pbssock, strlist);
5696  return;
5697  }
5698  }
5699 
5700  // handle remote ...
5701  if (ismaster && pginfo.GetHostname() != gCoreContext->GetHostName())
5702  {
5703  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
5704  if (!slave)
5705  {
5706  strlist = QStringList("ERROR");
5707  strlist +=
5708  "5: Could not locate mythbackend that made this recording";
5709  SendResponse(pbssock, strlist);
5710  return;
5711  }
5712 
5713  strlist = slave->ForwardRequest(slist);
5714 
5715  slave->DecrRef();
5716 
5717  if (!strlist.empty())
5718  {
5719  SendResponse(pbssock, strlist);
5720  return;
5721  }
5722  }
5723 
5724  strlist = QStringList("WARNING");
5725  strlist += "2: Could not locate requested file";
5726  SendResponse(pbssock, strlist);
5727 }
5728 
5730 {
5731  QStringList retlist( "OK" );
5732  SendResponse(socket, retlist);
5733 }
5734 
5735 void MainServer::HandleBlockShutdown(bool blockShutdown, PlaybackSock *pbs)
5736 {
5737  pbs->setBlockShutdown(blockShutdown);
5738 
5739  MythSocket *socket = pbs->getSocket();
5740  QStringList retlist( "OK" );
5741  SendResponse(socket, retlist);
5742 }
5743 
5745 {
5746  QMutexLocker lock(&deferredDeleteLock);
5747 
5748  if (deferredDeleteList.empty())
5749  return;
5750 
5752  while (dds.ts.secsTo(MythDate::current()) > 30)
5753  {
5754  delete dds.sock;
5755  deferredDeleteList.pop_front();
5756  if (deferredDeleteList.empty())
5757  return;
5758  dds = deferredDeleteList.front();
5759  }
5760 }
5761 
5763 {
5765  dds.sock = sock;
5766  dds.ts = MythDate::current();
5767 
5768  QMutexLocker lock(&deferredDeleteLock);
5769  deferredDeleteList.push_back(dds);
5770 }
5771 
5772 #undef QT_NO_DEBUG
5773 
5775 {
5776  sockListLock.lockForWrite();
5777 
5778  // make sure these are not actually deleted in the callback
5779  socket->IncrRef();
5780  decrRefSocketList.push_back(socket);
5781 
5782  vector<PlaybackSock *>::iterator it = playbackList.begin();
5783  for (; it != playbackList.end(); ++it)
5784  {
5785  PlaybackSock *pbs = (*it);
5786  MythSocket *sock = pbs->getSocket();
5787  if (sock == socket && pbs == masterServer)
5788  {
5789  playbackList.erase(it);
5790  sockListLock.unlock();
5791  masterServer->DecrRef();
5792  masterServer = NULL;
5793  MythEvent me("LOCAL_RECONNECT_TO_MASTER");
5794  gCoreContext->dispatch(me);
5795  return;
5796  }
5797  else if (sock == socket)
5798  {
5799  QList<uint> disconnectedSlaves;
5800  bool needsReschedule = false;
5801 
5802  if (ismaster && pbs->isSlaveBackend())
5803  {
5804  LOG(VB_GENERAL, LOG_ERR,
5805  QString("Slave backend: %1 no longer connected")
5806  .arg(pbs->getHostname()));
5807 
5808  bool isFallingAsleep = true;
5809  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
5810  for (; iter != encoderList->end(); ++iter)
5811  {
5812  EncoderLink *elink = *iter;
5813  if (elink->GetSocket() == pbs)
5814  {
5815  if (!elink->IsFallingAsleep())
5816  isFallingAsleep = false;
5817 
5818  elink->SetSocket(NULL);
5819  if (m_sched)
5820  disconnectedSlaves.push_back(elink->GetCardID());
5821  }
5822  }
5823  if (m_sched && !isFallingAsleep)
5824  needsReschedule = true;
5825 
5826  QString message = QString("LOCAL_SLAVE_BACKEND_OFFLINE %1")
5827  .arg(pbs->getHostname());
5828  MythEvent me(message);
5829  gCoreContext->dispatch(me);
5830 
5831  MythEvent me2("RECORDING_LIST_CHANGE");
5832  gCoreContext->dispatch(me2);
5833 
5835  QString("SLAVE_DISCONNECTED HOSTNAME %1")
5836  .arg(pbs->getHostname()));
5837  }
5838  else if (ismaster && pbs->getHostname() != "tzcheck")
5839  {
5841  QString("CLIENT_DISCONNECTED HOSTNAME %1")
5842  .arg(pbs->getHostname()));
5843  }
5844 
5845  LiveTVChain *chain;
5846  if ((chain = GetExistingChain(sock)))
5847  {
5848  chain->DelHostSocket(sock);
5849  if (chain->HostSocketCount() == 0)
5850  {
5851  QMap<int, EncoderLink *>::iterator it =
5852  encoderList->begin();
5853  for (; it != encoderList->end(); ++it)
5854  {
5855  EncoderLink *enc = *it;
5856  if (enc->IsLocal())
5857  {
5858  while (enc->GetState() == kState_ChangingState)
5859  usleep(500);
5860 
5861  if (enc->IsBusy() &&
5862  enc->GetChainID() == chain->GetID())
5863  {
5864  enc->StopLiveTV();
5865  }
5866  }
5867  }
5868  DeleteChain(chain);
5869  }
5870  }
5871 
5872  pbs->SetDisconnected();
5873  playbackList.erase(it);
5874 
5875  PlaybackSock *testsock = GetPlaybackBySock(socket);
5876  if (testsock)
5877  LOG(VB_GENERAL, LOG_ERR, "Playback sock still exists?");
5878 
5879  pbs->DecrRef();
5880 
5881  sockListLock.unlock();
5882 
5883  // Since we may already be holding the scheduler lock
5884  // delay handling the disconnect until a little later. #9885
5885  SendSlaveDisconnectedEvent(disconnectedSlaves, needsReschedule);
5886 
5887  return;
5888  }
5889  }
5890 
5891  vector<FileTransfer *>::iterator ft = fileTransferList.begin();
5892  for (; ft != fileTransferList.end(); ++ft)
5893  {
5894  MythSocket *sock = (*ft)->getSocket();
5895  if (sock == socket)
5896  {
5897  (*ft)->DecrRef();
5898  fileTransferList.erase(ft);
5899  sockListLock.unlock();
5900  return;
5901  }
5902  }
5903 
5904  QSet<MythSocket*>::iterator cs = controlSocketList.find(socket);
5905  if (cs != controlSocketList.end())
5906  {
5907  (*cs)->DecrRef();
5908  controlSocketList.erase(cs);
5909  sockListLock.unlock();
5910  return;
5911  }
5912 
5913  sockListLock.unlock();
5914 
5915  LOG(VB_GENERAL, LOG_WARNING, LOC +
5916  QString("Unknown socket closing MythSocket(0x%1)")
5917  .arg((intptr_t)socket,0,16));
5918 }
5919 
5921 {
5922  if (!ismaster)
5923  return NULL;
5924 
5925  sockListLock.lockForRead();
5926 
5927  vector<PlaybackSock *>::iterator iter = playbackList.begin();
5928  for (; iter != playbackList.end(); ++iter)
5929  {
5930  PlaybackSock *pbs = *iter;
5931  if (pbs->isSlaveBackend() &&
5932  ((pbs->getHostname().toLower() == hostname.toLower()) ||
5933  (gCoreContext->IsThisHost(hostname, pbs->getHostname()))))
5934  {
5935  sockListLock.unlock();
5936  pbs->IncrRef();
5937  return pbs;
5938  }
5939  }
5940 
5941  sockListLock.unlock();
5942 
5943  return NULL;
5944 }
5945 
5947 {
5948  if (!ismaster)
5949  return NULL;
5950 
5951  QReadLocker rlock(&sockListLock);
5952 
5953  vector<PlaybackSock *>::iterator iter = playbackList.begin();
5954  for (; iter != playbackList.end(); ++iter)
5955  {
5956  PlaybackSock *pbs = *iter;
5957  if (pbs->isMediaServer() &&
5958  ((pbs->getHostname().toLower() == hostname.toLower()) ||
5959  (gCoreContext->IsThisHost(hostname, pbs->getHostname()))))
5960  {
5961  pbs->IncrRef();
5962  return pbs;
5963  }
5964  }
5965 
5966  return NULL;
5967 }
5968 
5971 {
5972  PlaybackSock *retval = NULL;
5973 
5974  vector<PlaybackSock *>::iterator it = playbackList.begin();
5975  for (; it != playbackList.end(); ++it)
5976  {
5977  if (sock == (*it)->getSocket())
5978  {
5979  retval = (*it);
5980  break;
5981  }
5982  }
5983 
5984  return retval;
5985 }
5986 
5989 {
5990  FileTransfer *retval = NULL;
5991 
5992  vector<FileTransfer *>::iterator it = fileTransferList.begin();
5993  for (; it != fileTransferList.end(); ++it)
5994  {
5995  if (id == (*it)->getSocket()->GetSocketDescriptor())
5996  {
5997  retval = (*it);
5998  break;
5999  }
6000  }
6001 
6002  return retval;
6003 }
6004 
6007 {
6008  FileTransfer *retval = NULL;
6009 
6010  vector<FileTransfer *>::iterator it = fileTransferList.begin();
6011  for (; it != fileTransferList.end(); ++it)
6012  {
6013  if (sock == (*it)->getSocket())
6014  {
6015  retval = (*it);
6016  break;
6017  }
6018  }
6019 
6020  return retval;
6021 }
6022 
6024 {
6025  QMutexLocker lock(&liveTVChainsLock);
6026 
6027  vector<LiveTVChain*>::iterator it = liveTVChains.begin();
6028  for (; it != liveTVChains.end(); ++it)
6029  {
6030  if ((*it)->GetID() == id)
6031  return *it;
6032  }
6033 
6034  return NULL;
6035 }
6036 
6038 {
6039  QMutexLocker lock(&liveTVChainsLock);
6040 
6041  vector<LiveTVChain*>::iterator it = liveTVChains.begin();
6042  for (; it != liveTVChains.end(); ++it)
6043  {
6044  if ((*it)->IsHostSocket(sock))
6045  return *it;
6046  }
6047 
6048  return NULL;
6049 }
6050 
6052 {
6053  QMutexLocker lock(&liveTVChainsLock);
6054 
6055  vector<LiveTVChain*>::iterator it = liveTVChains.begin();
6056  for (; it != liveTVChains.end(); ++it)
6057  {
6058  if ((*it)->ProgramIsAt(pginfo) >= 0)
6059  return *it;
6060  }
6061 
6062  return NULL;
6063 }
6064 
6066 {
6067  QMutexLocker lock(&liveTVChainsLock);
6068 
6069  if (chain)
6070  liveTVChains.push_back(chain);
6071 }
6072 
6074 {
6075  QMutexLocker lock(&liveTVChainsLock);
6076 
6077  if (!chain)
6078  return;
6079 
6080  vector<LiveTVChain*> newChains;
6081 
6082  vector<LiveTVChain*>::iterator it = liveTVChains.begin();
6083  for (; it != liveTVChains.end(); ++it)
6084  {
6085  if (*it != chain)
6086  newChains.push_back(*it);
6087  }
6088  liveTVChains = newChains;
6089 
6090  delete chain;
6091 }
6092 
6093 void MainServer::SetExitCode(int exitCode, bool closeApplication)
6094 {
6095  m_exitCode = exitCode;
6096  if (closeApplication)
6098 }
6099 
6100 QString MainServer::LocalFilePath(const QUrl &url, const QString &wantgroup)
6101 {
6102  QString lpath = url.path();
6103 
6104  if (url.hasFragment())
6105  lpath += "#" + url.fragment();
6106 
6107  if (lpath.section('/', -2, -2) == "channels")
6108  {
6109  // This must be an icon request. Check channel.icon to be safe.
6110  QString querytext;
6111 
6112  QString file = lpath.section('/', -1);
6113  lpath = "";
6114 
6115  MSqlQuery query(MSqlQuery::InitCon());
6116  query.prepare("SELECT icon FROM channel WHERE icon LIKE :FILENAME ;");
6117  query.bindValue(":FILENAME", QString("%/") + file);
6118 
6119  if (query.exec() && query.next())
6120  {
6121  lpath = query.value(0).toString();
6122  }
6123  else
6124  {
6125  MythDB::DBError("Icon path", query);
6126  }
6127  }
6128  else
6129  {
6130  lpath = lpath.section('/', -1);
6131 
6132  QString fpath = lpath;
6133  if (fpath.endsWith(".png"))
6134  fpath = fpath.left(fpath.length() - 4);
6135 
6136  ProgramInfo pginfo(fpath);
6137  if (pginfo.GetChanID())
6138  {
6139  QString pburl = GetPlaybackURL(&pginfo);
6140  if (pburl.startsWith("/"))
6141  {
6142  lpath = pburl.section('/', 0, -2) + "/" + lpath;
6143  LOG(VB_FILE, LOG_INFO,
6144  QString("Local file path: %1").arg(lpath));
6145  }
6146  else
6147  {
6148  LOG(VB_GENERAL, LOG_ERR,
6149  QString("ERROR: LocalFilePath unable to find local "
6150  "path for '%1', found '%2' instead.")
6151  .arg(lpath).arg(pburl));
6152  lpath = "";
6153  }
6154  }
6155  else if (!lpath.isEmpty())
6156  {
6157  // For securities sake, make sure filename is really the pathless.
6158  QString opath = lpath;
6159  StorageGroup sgroup;
6160 
6161  if (!wantgroup.isEmpty())
6162  {
6163  sgroup.Init(wantgroup);
6164  lpath = url.toString();
6165  }
6166  else
6167  {
6168  lpath = QFileInfo(lpath).fileName();
6169  }
6170 
6171  QString tmpFile = sgroup.FindFile(lpath);
6172  if (!tmpFile.isEmpty())
6173  {
6174  lpath = tmpFile;
6175  LOG(VB_FILE, LOG_INFO,
6176  QString("LocalFilePath(%1 '%2'), found file through "
6177  "exhaustive search at '%3'")
6178  .arg(url.toString()).arg(opath).arg(lpath));
6179  }
6180  else
6181  {
6182  LOG(VB_GENERAL, LOG_ERR, QString("ERROR: LocalFilePath "
6183  "unable to find local path for '%1'.") .arg(opath));
6184  lpath = "";
6185  }
6186 
6187  }
6188  else
6189  {
6190  lpath = "";
6191  }
6192  }
6193 
6194  return lpath;
6195 }
6196 
6198 {
6199  MythSocket *masterServerSock = new MythSocket(-1, this);
6200 
6201  QString server = gCoreContext->GetSetting("MasterServerIP", "127.0.0.1");
6202  int port = gCoreContext->GetNumSetting("MasterServerPort", 6543);
6203 
6204  LOG(VB_GENERAL, LOG_NOTICE, QString("Connecting to master server: %1:%2")
6205  .arg(server).arg(port));
6206 
6207  if (!masterServerSock->ConnectToHost(server, port))
6208  {
6209  LOG(VB_GENERAL, LOG_NOTICE, "Connection to master server timed out.");
6211  masterServerSock->DecrRef();
6212  return;
6213  }
6214 
6215  LOG(VB_GENERAL, LOG_NOTICE, "Connected successfully");
6216 
6217  QString str = QString("ANN SlaveBackend %1 %2")
6218  .arg(gCoreContext->GetHostName())
6220 
6221  QStringList strlist( str );
6222 
6223  QMap<int, EncoderLink *>::Iterator iter = encoderList->begin();
6224  for (; iter != encoderList->end(); ++iter)
6225  {
6226  EncoderLink *elink = *iter;
6227  elink->CancelNextRecording(true);
6228  ProgramInfo *pinfo = elink->GetRecording();
6229  if (pinfo)
6230  {
6231  pinfo->ToStringList(strlist);
6232  delete pinfo;
6233  }
6234  else
6235  {
6237  dummy.ToStringList(strlist);
6238  }
6239  }
6240 
6241  if (!masterServerSock->SendReceiveStringList(strlist, 1) ||
6242  (strlist[0] == "ERROR"))
6243  {
6244  masterServerSock->DecrRef();
6245  masterServerSock = NULL;
6246  if (strlist.empty())
6247  {
6248  LOG(VB_GENERAL, LOG_ERR, LOC +
6249  "Failed to open master server socket, timeout");
6250  }
6251  else
6252  {
6253  LOG(VB_GENERAL, LOG_ERR, LOC +
6254  "Failed to open master server socket" +
6255  ((strlist.size() >= 2) ?
6256  QString(", error was %1").arg(strlist[1]) :
6257  QString(", remote error")));
6258  }
6260  return;
6261  }
6262 
6263  masterServer = new PlaybackSock(this, masterServerSock, server,
6265  sockListLock.lockForWrite();
6266  playbackList.push_back(masterServer);
6267  sockListLock.unlock();
6268 
6269  autoexpireUpdateTimer->start(1000);
6270 }
6271 
6272 // returns true, if a client (slavebackends are not counted!)
6273 // is connected by checking the lists.
6275 {
6276  bool foundClient = false;
6277 
6278  sockListLock.lockForRead();
6279 
6280  foundClient |= !fileTransferList.empty();
6281 
6282  vector<PlaybackSock *>::iterator it = playbackList.begin();
6283  for (; !foundClient && (it != playbackList.end()); ++it)
6284  {
6285  // we simply ignore slaveBackends!
6286  // and clients that don't want to block shutdown
6287  if (!(*it)->isSlaveBackend() && (*it)->getBlockShutdown())
6288  foundClient = true;
6289  }
6290 
6291  sockListLock.unlock();
6292 
6293  return (foundClient);
6294 }
6295 
6298 {
6299 // TODO FIXME We should issue a MythEvent and have customEvent
6300 // send this with the proper syncronisation and locking.
6301 
6302  QStringList bcast( "SHUTDOWN_NOW" );
6303  bcast << haltcmd;
6304 
6305  sockListLock.lockForRead();
6306 
6307  vector<PlaybackSock *>::iterator it = playbackList.begin();
6308  for (; it != playbackList.end(); ++it)
6309  {
6310  if ((*it)->isSlaveBackend())
6311  (*it)->getSocket()->WriteStringList(bcast);
6312  }
6313 
6314  sockListLock.unlock();
6315 }
6316 
6318 {
6319  if (event.ExtraDataCount() > 0 && m_sched)
6320  {
6321  bool needsReschedule = event.ExtraData(0).toUInt();
6322  for (int i = 1; i < event.ExtraDataCount(); i++)
6323  m_sched->SlaveDisconnected(event.ExtraData(i).toUInt());
6324 
6325  if (needsReschedule)
6326  m_sched->ReschedulePlace("SlaveDisconnected");
6327  }
6328 }
6329 
6331  const QList<uint> &cardids, bool needsReschedule)
6332 {
6333  QStringList extraData;
6334  extraData.push_back(