MythTV  master
mainserver.cpp
Go to the documentation of this file.
1 // C++
2 #include <algorithm>
3 #include <cerrno>
4 #include <chrono> // for milliseconds
5 #include <cmath>
6 #include <cstdlib>
7 #include <fcntl.h>
8 #include <iostream>
9 #include <list>
10 #include <memory>
11 #include <thread> // for sleep_for
12 
13 #include "libmythbase/mythconfig.h"
14 
15 #ifndef _WIN32
16 #include <sys/ioctl.h>
17 #endif
18 #if CONFIG_SYSTEMD_NOTIFY
19 #include <systemd/sd-daemon.h>
20 #endif
21 
22 #include <sys/stat.h>
23 #ifdef __linux__
24 # include <sys/vfs.h>
25 #else // if !__linux__
26 # include <sys/param.h>
27 # ifndef _WIN32
28 # include <sys/mount.h>
29 # endif // _WIN32
30 #endif // !__linux__
31 
32 // Qt
33 #include <QtGlobal>
34 #include <QCoreApplication>
35 #include <QDateTime>
36 #include <QFile>
37 #include <QDir>
38 #include <QWaitCondition>
39 #include <QWriteLocker>
40 #include <QProcess>
41 #include <QRegularExpression>
42 #include <QEvent>
43 #include <QTcpServer>
44 #include <QTimer>
45 #include <QNetworkInterface>
46 #include <QNetworkProxy>
47 #include <QHostAddress>
48 
49 // MythTV
50 #include "libmyth/mythcontext.h"
51 #include "libmythbase/compat.h"
53 #include "libmythbase/mthread.h"
56 #include "libmythbase/mythdb.h"
57 #include "libmythbase/mythdirs.h"
61 #include "libmythbase/mythrandom.h"
64 #include "libmythbase/mythversion.h"
66 #include "libmythbase/remotefile.h"
67 #include "libmythbase/serverpool.h"
71 #include "libmythmetadata/metaio.h"
75 #include "libmythtv/cardutil.h"
77 #include "libmythtv/jobqueue.h"
83 #include "libmythtv/tv.h"
84 #include "libmythtv/tv_rec.h"
85 
86 // mythbackend headers
87 #include "autoexpire.h"
88 #include "backendcontext.h"
89 #include "mainserver.h"
90 #include "scheduler.h"
91 
95 static constexpr std::chrono::milliseconds PRT_TIMEOUT { 10ms };
97 static constexpr int PRT_STARTUP_THREAD_COUNT { 5 };
98 
99 #define LOC QString("MainServer: ")
100 #define LOC_WARN QString("MainServer, Warning: ")
101 #define LOC_ERR QString("MainServer, Error: ")
102 
103 namespace {
104 
105 bool delete_file_immediately(const QString &filename,
106  bool followLinks, bool checkexists)
107 {
108  /* Return true for success, false for error. */
109  QFile checkFile(filename);
110  bool success1 = true;
111  bool success2 = true;
112 
113  LOG(VB_FILE, LOG_INFO, LOC +
114  QString("About to delete file: %1").arg(filename));
115  if (followLinks)
116  {
117  QFileInfo finfo(filename);
118  if (finfo.isSymLink())
119  {
120  QString linktext = getSymlinkTarget(filename);
121 
122  QFile target(linktext);
123  success1 = target.remove();
124  if (!success1)
125  {
126  LOG(VB_GENERAL, LOG_ERR, LOC +
127  QString("Error deleting '%1' -> '%2'")
128  .arg(filename, linktext) + ENO);
129  }
130  }
131  }
132  if (!checkexists || checkFile.exists())
133  {
134  success2 = checkFile.remove();
135  if (!success2)
136  {
137  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error deleting '%1': %2")
138  .arg(filename, strerror(errno)));
139  }
140  }
141  return success1 && success2;
142 }
143 
144 };
145 
147 const std::chrono::milliseconds MainServer::kMasterServerReconnectTimeout { 1s };
148 
149 class BEProcessRequestRunnable : public QRunnable
150 {
151  public:
153  m_parent(parent), m_sock(sock)
154  {
155  m_sock->IncrRef();
156  }
157 
159  {
160  if (m_sock)
161  {
162  m_sock->DecrRef();
163  m_sock = nullptr;
164  }
165  }
166 
167  void run(void) override // QRunnable
168  {
170  m_sock->DecrRef();
171  m_sock = nullptr;
172  }
173 
174  private:
177 };
178 
179 class FreeSpaceUpdater : public QRunnable
180 {
181  public:
182  explicit FreeSpaceUpdater(MainServer &parent) :
183  m_parent(parent)
184  {
186  }
187  ~FreeSpaceUpdater() override
188  {
189  QMutexLocker locker(&m_parent.m_masterFreeSpaceListLock);
192  }
193 
194  void run(void) override // QRunnable
195  {
196  while (true)
197  {
198  MythTimer t;
199  t.start();
200  QStringList list;
201  m_parent.BackendQueryDiskSpace(list, true, true);
202  {
203  QMutexLocker locker(&m_parent.m_masterFreeSpaceListLock);
205  }
206  QMutexLocker locker(&m_lock);
207  std::chrono::milliseconds left = kRequeryTimeout - t.elapsed();
208  if (m_lastRequest.elapsed() + left > kExitTimeout)
209  m_dorun = false;
210  if (!m_dorun)
211  {
212  m_running = false;
213  break;
214  }
215  if (left > 50ms)
216  m_wait.wait(locker.mutex(), left.count());
217  }
218  }
219 
220  bool KeepRunning(bool dorun)
221  {
222  QMutexLocker locker(&m_lock);
223  if (dorun && m_running)
224  {
225  m_dorun = true;
227  }
228  else
229  {
230  m_dorun = false;
231  m_wait.wakeAll();
232  }
233  return m_running;
234  }
235 
237  QMutex m_lock;
238  bool m_dorun { true };
239  bool m_running { true };
241  QWaitCondition m_wait;
242  static constexpr std::chrono::milliseconds kRequeryTimeout { 15s };
243  static constexpr std::chrono::milliseconds kExitTimeout { 61s };
244 };
245 
246 MainServer::MainServer(bool master, int port,
247  QMap<int, EncoderLink *> *_tvList,
248  Scheduler *sched, AutoExpire *_expirer) :
249  m_encoderList(_tvList),
250  m_ismaster(master), m_threadPool("ProcessRequestPool"),
251  m_sched(sched), m_expirer(_expirer)
252 {
256 
258 
260  gCoreContext->GetBoolSetting("MasterBackendOverride", false);
261 
262  m_mythserver = new MythServer();
263  m_mythserver->setProxy(QNetworkProxy::NoProxy);
264 
265  QList<QHostAddress> listenAddrs = MythServer::DefaultListen();
266  if (!gCoreContext->GetBoolSetting("ListenOnAllIps",true))
267  {
268  // test to make sure listen addresses are available
269  // no reason to run the backend if the mainserver is not active
270  QHostAddress config_v4(gCoreContext->resolveSettingAddress(
271  "BackendServerIP",
272  QString(),
274  bool v4IsSet = !config_v4.isNull();
275  QHostAddress config_v6(gCoreContext->resolveSettingAddress(
276  "BackendServerIP6",
277  QString(),
279  bool v6IsSet = !config_v6.isNull();
280 
281  if (v6IsSet && !listenAddrs.contains(config_v6))
282  LOG(VB_GENERAL, LOG_WARNING, LOC +
283  "Unable to find IPv6 address to bind");
284 
285  if (v4IsSet && !listenAddrs.contains(config_v4))
286  LOG(VB_GENERAL, LOG_WARNING, LOC +
287  "Unable to find IPv4 address to bind");
288 
289  if ((v4IsSet && !listenAddrs.contains(config_v4))
290  && (v6IsSet && !listenAddrs.contains(config_v6))
291  )
292  {
293  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to find either IPv4 or IPv6 "
294  "address we can bind to, exiting");
296  return;
297  }
298  }
299  if (!m_mythserver->listen(port))
300  {
302  return;
303  }
306 
307  gCoreContext->addListener(this);
308 
309  if (!m_ismaster)
310  {
311  m_masterServerReconnect = new QTimer(this);
312  m_masterServerReconnect->setSingleShot(true);
316  }
317 
318  m_deferredDeleteTimer = new QTimer(this);
321  m_deferredDeleteTimer->start(30s);
322 
323  if (sched)
324  {
325  // Make sure we have a good, fsinfo cache before setting
326  // mainServer in the scheduler.
327  QList<FileSystemInfo> m_fsInfos;
328  GetFilesystemInfos(m_fsInfos, false);
329  sched->SetMainServer(this);
330  }
331  if (gExpirer)
332  gExpirer->SetMainServer(this);
333 
335 
336  m_autoexpireUpdateTimer = new QTimer(this);
339  m_autoexpireUpdateTimer->setSingleShot(true);
340 
341  AutoExpire::Update(true);
342 
344  m_masterFreeSpaceList << "TotalDiskSpace";
345  m_masterFreeSpaceList << "0";
346  m_masterFreeSpaceList << "-2";
347  m_masterFreeSpaceList << "-2";
348  m_masterFreeSpaceList << "0";
349  m_masterFreeSpaceList << "0";
350  m_masterFreeSpaceList << "0";
351 
352  m_masterFreeSpaceListUpdater = (master ? new FreeSpaceUpdater(*this) : nullptr);
354  {
356  m_masterFreeSpaceListUpdater, "FreeSpaceUpdater");
357  }
358 }
359 
361 {
362  if (!m_stopped)
363  Stop();
364 }
365 
367 {
368  m_stopped = true;
369 
371 
372  {
373  QMutexLocker locker(&m_masterFreeSpaceListLock);
376  }
377 
378  m_threadPool.Stop();
379 
380  // since Scheduler::SetMainServer() isn't thread-safe
381  // we need to shut down the scheduler thread before we
382  // can call SetMainServer(nullptr)
383  if (m_sched)
384  m_sched->Stop();
385 
388 
389  if (m_mythserver)
390  {
391  m_mythserver->disconnect();
392  m_mythserver->deleteLater();
393  m_mythserver = nullptr;
394  }
395 
396  if (m_sched)
397  {
398  m_sched->Wait();
399  m_sched->SetMainServer(nullptr);
400  }
401 
402  if (m_expirer)
403  m_expirer->SetMainServer(nullptr);
404 
405  {
406  QMutexLocker locker(&m_masterFreeSpaceListLock);
408  {
410  m_masterFreeSpaceListWait.wait(locker.mutex());
411  }
412  }
413 
414  // Close all open sockets
415  QWriteLocker locker(&m_sockListLock);
416 
417  for (auto & pbs : m_playbackList)
418  pbs->DecrRef();
419  m_playbackList.clear();
420 
421  for (auto & ft : m_fileTransferList)
422  ft->DecrRef();
423  m_fileTransferList.clear();
424 
425  for (auto *cs : std::as_const(m_controlSocketList))
426  cs->DecrRef();
427  m_controlSocketList.clear();
428 
429  while (!m_decrRefSocketList.empty())
430  {
431  (*m_decrRefSocketList.begin())->DecrRef();
433  }
434 }
435 
437 {
438  AutoExpire::Update(false);
439 }
440 
441 void MainServer::NewConnection(qintptr socketDescriptor)
442 {
443  QWriteLocker locker(&m_sockListLock);
444  auto *ms = new MythSocket(socketDescriptor, this);
445  if (ms->IsConnected())
446  m_controlSocketList.insert(ms);
447  else
448  ms-> DecrRef();
449 }
450 
452 {
454  new BEProcessRequestRunnable(*this, sock),
455  "ProcessRequest", PRT_TIMEOUT);
456 
457  QCoreApplication::processEvents();
458 }
459 
461 {
462  if (sock->IsDataAvailable())
463  ProcessRequestWork(sock);
464  else
465  LOG(VB_GENERAL, LOG_INFO, LOC + QString("No data on sock %1")
466  .arg(sock->GetSocketDescriptor()));
467 }
468 
470 {
471  m_sockListLock.lockForRead();
473  if (pbs)
474  pbs->IncrRef();
475 
476  bool bIsControl = (pbs) ? false : m_controlSocketList.contains(sock);
477  m_sockListLock.unlock();
478 
479  QStringList listline;
480  if (pbs)
481  {
482  if (!pbs->ReadStringList(listline) || listline.empty())
483  {
484  pbs->DecrRef();
485  LOG(VB_GENERAL, LOG_INFO, "No data in ProcessRequestWork()");
486  return;
487  }
488  pbs->DecrRef();
489  }
490  else if (!bIsControl)
491  {
492  // The socket has been disconnected
493  return;
494  }
495  else if (!sock->ReadStringList(listline) || listline.empty())
496  {
497  LOG(VB_GENERAL, LOG_INFO, LOC + "No data in ProcessRequestWork()");
498  return;
499  }
500 
501  QString line = listline[0];
502 
503  line = line.simplified();
504  QStringList tokens = line.split(' ', Qt::SkipEmptyParts);
505  QString command = tokens[0];
506 
507  if (command == "MYTH_PROTO_VERSION")
508  {
509  if (tokens.size() < 2)
510  SendErrorResponse(sock, "Bad MYTH_PROTO_VERSION command");
511  else
512  HandleVersion(sock, tokens);
513  return;
514  }
515  if (command == "ANN")
516  {
517  HandleAnnounce(listline, tokens, sock);
518  return;
519  }
520  if (command == "DONE")
521  {
522  HandleDone(sock);
523  return;
524  }
525 
526  m_sockListLock.lockForRead();
527  pbs = GetPlaybackBySock(sock);
528  if (!pbs)
529  {
530  m_sockListLock.unlock();
531  LOG(VB_GENERAL, LOG_ERR, LOC + "ProcessRequest unknown socket");
532  return;
533  }
534  pbs->IncrRef();
535  m_sockListLock.unlock();
536 
537  if (command == "QUERY_FILETRANSFER")
538  {
539  if (tokens.size() != 2)
540  SendErrorResponse(pbs, "Bad QUERY_FILETRANSFER");
541  else
542  HandleFileTransferQuery(listline, tokens, pbs);
543  }
544  else if (command == "QUERY_RECORDINGS")
545  {
546  if (tokens.size() != 2)
547  SendErrorResponse(pbs, "Bad QUERY_RECORDINGS query");
548  else
549  HandleQueryRecordings(tokens[1], pbs);
550  }
551  else if (command == "QUERY_RECORDING")
552  {
553  HandleQueryRecording(tokens, pbs);
554  }
555  else if (command == "GO_TO_SLEEP")
556  {
558  }
559  else if (command == "QUERY_FREE_SPACE")
560  {
561  HandleQueryFreeSpace(pbs, false);
562  }
563  else if (command == "QUERY_FREE_SPACE_LIST")
564  {
565  HandleQueryFreeSpace(pbs, true);
566  }
567  else if (command == "QUERY_FREE_SPACE_SUMMARY")
568  {
570  }
571  else if (command == "QUERY_LOAD")
572  {
574  }
575  else if (command == "QUERY_UPTIME")
576  {
578  }
579  else if (command == "QUERY_HOSTNAME")
580  {
582  }
583  else if (command == "QUERY_MEMSTATS")
584  {
586  }
587  else if (command == "QUERY_TIME_ZONE")
588  {
590  }
591  else if (command == "QUERY_CHECKFILE")
592  {
593  HandleQueryCheckFile(listline, pbs);
594  }
595  else if (command == "QUERY_FILE_EXISTS")
596  {
597  if (listline.size() < 2)
598  SendErrorResponse(pbs, "Bad QUERY_FILE_EXISTS command");
599  else
600  HandleQueryFileExists(listline, pbs);
601  }
602  else if (command == "QUERY_FINDFILE")
603  {
604  if (listline.size() < 4)
605  SendErrorResponse(pbs, "Bad QUERY_FINDFILE command");
606  else
607  HandleQueryFindFile(listline, pbs);
608  }
609  else if (command == "QUERY_FILE_HASH")
610  {
611  if (listline.size() < 3)
612  SendErrorResponse(pbs, "Bad QUERY_FILE_HASH command");
613  else
614  HandleQueryFileHash(listline, pbs);
615  }
616  else if (command == "QUERY_GUIDEDATATHROUGH")
617  {
619  }
620  else if (command == "DELETE_FILE")
621  {
622  if (listline.size() < 3)
623  SendErrorResponse(pbs, "Bad DELETE_FILE command");
624  else
625  HandleDeleteFile(listline, pbs);
626  }
627  else if (command == "MOVE_FILE")
628  {
629  if (listline.size() < 4)
630  SendErrorResponse(pbs, "Bad MOVE_FILE command");
631  else
632  HandleMoveFile(pbs, listline[1], listline[2], listline[3]);
633  }
634  else if (command == "STOP_RECORDING")
635  {
636  HandleStopRecording(listline, pbs);
637  }
638  else if (command == "CHECK_RECORDING")
639  {
640  HandleCheckRecordingActive(listline, pbs);
641  }
642  else if (command == "DELETE_RECORDING")
643  {
644  if (3 <= tokens.size() && tokens.size() <= 5)
645  {
646  bool force = (tokens.size() >= 4) && (tokens[3] == "FORCE");
647  bool forget = (tokens.size() >= 5) && (tokens[4] == "FORGET");
648  HandleDeleteRecording(tokens[1], tokens[2], pbs, force, forget);
649  }
650  else
651  HandleDeleteRecording(listline, pbs, false);
652  }
653  else if (command == "FORCE_DELETE_RECORDING")
654  {
655  HandleDeleteRecording(listline, pbs, true);
656  }
657  else if (command == "UNDELETE_RECORDING")
658  {
659  HandleUndeleteRecording(listline, pbs);
660  }
661  else if (command == "ADD_CHILD_INPUT")
662  {
663  QStringList reslist;
664  if (m_ismaster)
665  {
666  LOG(VB_GENERAL, LOG_ERR, LOC +
667  "ADD_CHILD_INPUT command received in master context");
668  reslist << QString("ERROR: Called in master context");
669  }
670  else if (tokens.size() != 2)
671  reslist << "ERROR: Bad ADD_CHILD_INPUT request";
672  else if (HandleAddChildInput(tokens[1].toUInt()))
673  reslist << "OK";
674  else
675  reslist << QString("ERROR: Failed to add child input");
676  SendResponse(pbs->getSocket(), reslist);
677  }
678  else if (command == "RESCHEDULE_RECORDINGS")
679  {
680  listline.pop_front();
681  HandleRescheduleRecordings(listline, pbs);
682  }
683  else if (command == "FORGET_RECORDING")
684  {
685  HandleForgetRecording(listline, pbs);
686  }
687  else if (command == "QUERY_GETALLPENDING")
688  {
689  if (tokens.size() == 1)
691  else if (tokens.size() == 2)
692  HandleGetPendingRecordings(pbs, tokens[1]);
693  else
694  HandleGetPendingRecordings(pbs, tokens[1], tokens[2].toInt());
695  }
696  else if (command == "QUERY_GETALLSCHEDULED")
697  {
699  }
700  else if (command == "QUERY_GETCONFLICTING")
701  {
703  }
704  else if (command == "QUERY_GETEXPIRING")
705  {
707  }
708  else if (command == "QUERY_SG_GETFILELIST")
709  {
710  HandleSGGetFileList(listline, pbs);
711  }
712  else if (command == "QUERY_SG_FILEQUERY")
713  {
714  HandleSGFileQuery(listline, pbs);
715  }
716  else if (command == "GET_FREE_INPUT_INFO")
717  {
718  if (tokens.size() != 2)
719  SendErrorResponse(pbs, "Bad GET_FREE_INPUT_INFO");
720  else
721  HandleGetFreeInputInfo(pbs, tokens[1].toUInt());
722  }
723  else if (command == "QUERY_RECORDER")
724  {
725  if (tokens.size() != 2)
726  SendErrorResponse(pbs, "Bad QUERY_RECORDER");
727  else
728  HandleRecorderQuery(listline, tokens, pbs);
729  }
730  else if ((command == "QUERY_RECORDING_DEVICE") ||
731  (command == "QUERY_RECORDING_DEVICES"))
732  {
733  // TODO
734  }
735  else if (command == "SET_NEXT_LIVETV_DIR")
736  {
737  if (tokens.size() != 3)
738  SendErrorResponse(pbs, "Bad SET_NEXT_LIVETV_DIR");
739  else
740  HandleSetNextLiveTVDir(tokens, pbs);
741  }
742  else if (command == "SET_CHANNEL_INFO")
743  {
744  HandleSetChannelInfo(listline, pbs);
745  }
746  else if (command == "QUERY_REMOTEENCODER")
747  {
748  if (tokens.size() != 2)
749  SendErrorResponse(pbs, "Bad QUERY_REMOTEENCODER");
750  else
751  HandleRemoteEncoder(listline, tokens, pbs);
752  }
753  else if (command == "GET_RECORDER_FROM_NUM")
754  {
755  HandleGetRecorderFromNum(listline, pbs);
756  }
757  else if (command == "GET_RECORDER_NUM")
758  {
759  HandleGetRecorderNum(listline, pbs);
760  }
761  else if (command == "QUERY_GENPIXMAP2")
762  {
763  HandleGenPreviewPixmap(listline, pbs);
764  }
765  else if (command == "QUERY_PIXMAP_LASTMODIFIED")
766  {
767  HandlePixmapLastModified(listline, pbs);
768  }
769  else if (command == "QUERY_PIXMAP_GET_IF_MODIFIED")
770  {
771  HandlePixmapGetIfModified(listline, pbs);
772  }
773  else if (command == "QUERY_ISRECORDING")
774  {
775  HandleIsRecording(listline, pbs);
776  }
777  else if (command == "MESSAGE")
778  {
779  if ((listline.size() >= 2) && (listline[1].startsWith("SET_VERBOSE")))
780  HandleSetVerbose(listline, pbs);
781  else if ((listline.size() >= 2) &&
782  (listline[1].startsWith("SET_LOG_LEVEL")))
783  HandleSetLogLevel(listline, pbs);
784  else
785  HandleMessage(listline, pbs);
786  }
787  else if (command == "FILL_PROGRAM_INFO")
788  {
789  HandleFillProgramInfo(listline, pbs);
790  }
791  else if (command == "LOCK_TUNER")
792  {
793  if (tokens.size() == 1)
795  else if (tokens.size() == 2)
796  HandleLockTuner(pbs, tokens[1].toInt());
797  else
798  SendErrorResponse(pbs, "Bad LOCK_TUNER query");
799  }
800  else if (command == "FREE_TUNER")
801  {
802  if (tokens.size() != 2)
803  SendErrorResponse(pbs, "Bad FREE_TUNER query");
804  else
805  HandleFreeTuner(tokens[1].toInt(), pbs);
806  }
807  else if (command == "QUERY_ACTIVE_BACKENDS")
808  {
810  }
811  else if (command == "QUERY_IS_ACTIVE_BACKEND")
812  {
813  if (tokens.size() != 1)
814  SendErrorResponse(pbs, "Bad QUERY_IS_ACTIVE_BACKEND");
815  else
816  HandleIsActiveBackendQuery(listline, pbs);
817  }
818  else if (command == "QUERY_COMMBREAK")
819  {
820  if (tokens.size() != 3)
821  SendErrorResponse(pbs, "Bad QUERY_COMMBREAK");
822  else
823  HandleCommBreakQuery(tokens[1], tokens[2], pbs);
824  }
825  else if (command == "QUERY_CUTLIST")
826  {
827  if (tokens.size() != 3)
828  SendErrorResponse(pbs, "Bad QUERY_CUTLIST");
829  else
830  HandleCutlistQuery(tokens[1], tokens[2], pbs);
831  }
832  else if (command == "QUERY_BOOKMARK")
833  {
834  if (tokens.size() != 3)
835  SendErrorResponse(pbs, "Bad QUERY_BOOKMARK");
836  else
837  HandleBookmarkQuery(tokens[1], tokens[2], pbs);
838  }
839  else if (command == "SET_BOOKMARK")
840  {
841  if (tokens.size() != 4)
842  SendErrorResponse(pbs, "Bad SET_BOOKMARK");
843  else
844  HandleSetBookmark(tokens, pbs);
845  }
846  else if (command == "QUERY_SETTING")
847  {
848  if (tokens.size() != 3)
849  SendErrorResponse(pbs, "Bad QUERY_SETTING");
850  else
851  HandleSettingQuery(tokens, pbs);
852  }
853  else if (command == "SET_SETTING")
854  {
855  if (tokens.size() != 4)
856  SendErrorResponse(pbs, "Bad SET_SETTING");
857  else
858  HandleSetSetting(tokens, pbs);
859  }
860  else if (command == "SCAN_VIDEOS")
861  {
863  }
864  else if (command == "SCAN_MUSIC")
865  {
866  HandleScanMusic(tokens, pbs);
867  }
868  else if (command == "MUSIC_TAG_UPDATE_VOLATILE")
869  {
870  if (listline.size() != 6)
871  SendErrorResponse(pbs, "Bad MUSIC_TAG_UPDATE_VOLATILE");
872  else
874  }
875  else if (command == "MUSIC_CALC_TRACK_LENGTH")
876  {
877  if (listline.size() != 3)
878  SendErrorResponse(pbs, "Bad MUSIC_CALC_TRACK_LENGTH");
879  else
880  HandleMusicCalcTrackLen(listline, pbs);
881  }
882  else if (command == "MUSIC_TAG_UPDATE_METADATA")
883  {
884  if (listline.size() != 3)
885  SendErrorResponse(pbs, "Bad MUSIC_TAG_UPDATE_METADATA");
886  else
888  }
889  else if (command == "MUSIC_FIND_ALBUMART")
890  {
891  if (listline.size() != 4)
892  SendErrorResponse(pbs, "Bad MUSIC_FIND_ALBUMART");
893  else
894  HandleMusicFindAlbumArt(listline, pbs);
895  }
896  else if (command == "MUSIC_TAG_GETIMAGE")
897  {
898  if (listline.size() < 4)
899  SendErrorResponse(pbs, "Bad MUSIC_TAG_GETIMAGE");
900  else
901  HandleMusicTagGetImage(listline, pbs);
902  }
903  else if (command == "MUSIC_TAG_ADDIMAGE")
904  {
905  if (listline.size() < 5)
906  SendErrorResponse(pbs, "Bad MUSIC_TAG_ADDIMAGE");
907  else
908  HandleMusicTagAddImage(listline, pbs);
909  }
910  else if (command == "MUSIC_TAG_REMOVEIMAGE")
911  {
912  if (listline.size() < 4)
913  SendErrorResponse(pbs, "Bad MUSIC_TAG_REMOVEIMAGE");
914  else
915  HandleMusicTagRemoveImage(listline, pbs);
916  }
917  else if (command == "MUSIC_TAG_CHANGEIMAGE")
918  {
919  if (listline.size() < 5)
920  SendErrorResponse(pbs, "Bad MUSIC_TAG_CHANGEIMAGE");
921  else
922  HandleMusicTagChangeImage(listline, pbs);
923  }
924  else if (command == "MUSIC_LYRICS_FIND")
925  {
926  if (listline.size() < 3)
927  SendErrorResponse(pbs, "Bad MUSIC_LYRICS_FIND");
928  else
929  HandleMusicFindLyrics(listline, pbs);
930  }
931  else if (command == "MUSIC_LYRICS_GETGRABBERS")
932  {
933  HandleMusicGetLyricGrabbers(listline, pbs);
934  }
935  else if (command == "MUSIC_LYRICS_SAVE")
936  {
937  if (listline.size() < 3)
938  SendErrorResponse(pbs, "Bad MUSIC_LYRICS_SAVE");
939  else
940  HandleMusicSaveLyrics(listline, pbs);
941  }
942  else if (command == "IMAGE_SCAN")
943  {
944  // Expects command
945  QStringList reply = (listline.size() == 2)
947  : QStringList("ERROR") << "Bad: " << listline;
948 
949  SendResponse(pbs->getSocket(), reply);
950  }
951  else if (command == "IMAGE_COPY")
952  {
953  // Expects at least 1 comma-delimited image definition
954  QStringList reply = (listline.size() >= 2)
955  ? ImageManagerBe::getInstance()->HandleDbCreate(listline.mid(1))
956  : QStringList("ERROR") << "Bad: " << listline;
957 
958  SendResponse(pbs->getSocket(), reply);
959  }
960  else if (command == "IMAGE_MOVE")
961  {
962  // Expects comma-delimited dir/file ids, path to replace, new path
963  QStringList reply = (listline.size() == 4)
965  HandleDbMove(listline[1], listline[2], listline[3])
966  : QStringList("ERROR") << "Bad: " << listline;
967 
968  SendResponse(pbs->getSocket(), reply);
969  }
970  else if (command == "IMAGE_DELETE")
971  {
972  // Expects comma-delimited dir/file ids
973  QStringList reply = (listline.size() == 2)
974  ? ImageManagerBe::getInstance()->HandleDelete(listline[1])
975  : QStringList("ERROR") << "Bad: " << listline;
976 
977  SendResponse(pbs->getSocket(), reply);
978  }
979  else if (command == "IMAGE_HIDE")
980  {
981  // Expects hide flag, comma-delimited file/dir ids
982  QStringList reply = (listline.size() == 3)
984  HandleHide(listline[1].toInt() != 0, listline[2])
985  : QStringList("ERROR") << "Bad: " << listline;
986 
987  SendResponse(pbs->getSocket(), reply);
988  }
989  else if (command == "IMAGE_TRANSFORM")
990  {
991  // Expects transformation, write file flag,
992  QStringList reply = (listline.size() == 3)
994  HandleTransform(listline[1].toInt(), listline[2])
995  : QStringList("ERROR") << "Bad: " << listline;
996 
997  SendResponse(pbs->getSocket(), reply);
998  }
999  else if (command == "IMAGE_RENAME")
1000  {
1001  // Expects file/dir id, new basename
1002  QStringList reply = (listline.size() == 3)
1003  ? ImageManagerBe::getInstance()->HandleRename(listline[1], listline[2])
1004  : QStringList("ERROR") << "Bad: " << listline;
1005 
1006  SendResponse(pbs->getSocket(), reply);
1007  }
1008  else if (command == "IMAGE_CREATE_DIRS")
1009  {
1010  // Expects destination path, rescan flag, list of dir names
1011  QStringList reply = (listline.size() >= 4)
1013  HandleDirs(listline[1], listline[2].toInt() != 0, listline.mid(3))
1014  : QStringList("ERROR") << "Bad: " << listline;
1015 
1016  SendResponse(pbs->getSocket(), reply);
1017  }
1018  else if (command == "IMAGE_COVER")
1019  {
1020  // Expects dir id, cover id. Cover id of 0 resets dir to use its own
1021  QStringList reply = (listline.size() == 3)
1023  HandleCover(listline[1].toInt(), listline[2].toInt())
1024  : QStringList("ERROR") << "Bad: " << listline;
1025 
1026  SendResponse(pbs->getSocket(), reply);
1027  }
1028  else if (command == "IMAGE_IGNORE")
1029  {
1030  // Expects list of exclusion patterns
1031  QStringList reply = (listline.size() == 2)
1032  ? ImageManagerBe::getInstance()->HandleIgnore(listline[1])
1033  : QStringList("ERROR") << "Bad: " << listline;
1034 
1035  SendResponse(pbs->getSocket(), reply);
1036  }
1037  else if (command == "ALLOW_SHUTDOWN")
1038  {
1039  if (tokens.size() != 1)
1040  SendErrorResponse(pbs, "Bad ALLOW_SHUTDOWN");
1041  else
1042  HandleBlockShutdown(false, pbs);
1043  }
1044  else if (command == "BLOCK_SHUTDOWN")
1045  {
1046  if (tokens.size() != 1)
1047  SendErrorResponse(pbs, "Bad BLOCK_SHUTDOWN");
1048  else
1049  HandleBlockShutdown(true, pbs);
1050  }
1051  else if (command == "SHUTDOWN_NOW")
1052  {
1053  if (tokens.size() != 1)
1054  SendErrorResponse(pbs, "Bad SHUTDOWN_NOW query");
1055  else if (!m_ismaster)
1056  {
1057  QString halt_cmd;
1058  if (listline.size() >= 2)
1059  halt_cmd = listline[1];
1060 
1061  if (!halt_cmd.isEmpty())
1062  {
1063  LOG(VB_GENERAL, LOG_NOTICE, LOC +
1064  "Going down now as of Mainserver request!");
1065  myth_system(halt_cmd);
1066  }
1067  else
1068  SendErrorResponse(pbs, "Received an empty SHUTDOWN_NOW query!");
1069  }
1070  }
1071  else if (command == "BACKEND_MESSAGE")
1072  {
1073  QString message = listline[1];
1074  QStringList extra( listline[2] );
1075  for (int i = 3; i < listline.size(); i++)
1076  extra << listline[i];
1077  MythEvent me(message, extra);
1078  gCoreContext->dispatch(me);
1079  }
1080  else if ((command == "DOWNLOAD_FILE") ||
1081  (command == "DOWNLOAD_FILE_NOW"))
1082  {
1083  if (listline.size() != 4)
1084  SendErrorResponse(pbs, QString("Bad %1 command").arg(command));
1085  else
1086  HandleDownloadFile(listline, pbs);
1087  }
1088  else if (command == "REFRESH_BACKEND")
1089  {
1090  LOG(VB_GENERAL, LOG_INFO , LOC + "Reloading backend settings");
1091  HandleBackendRefresh(sock);
1092  }
1093  else if (command == "OK")
1094  {
1095  LOG(VB_GENERAL, LOG_ERR, LOC + "Got 'OK' out of sequence.");
1096  }
1097  else if (command == "UNKNOWN_COMMAND")
1098  {
1099  LOG(VB_GENERAL, LOG_ERR, LOC + "Got 'UNKNOWN_COMMAND' out of sequence.");
1100  }
1101  else
1102  {
1103  LOG(VB_GENERAL, LOG_ERR, LOC + "Unknown command: " + command);
1104 
1105  MythSocket *pbssock = pbs->getSocket();
1106 
1107  QStringList strlist;
1108  strlist << "UNKNOWN_COMMAND";
1109 
1110  SendResponse(pbssock, strlist);
1111  }
1112 
1113  pbs->DecrRef();
1114 }
1115 
1117 {
1118  if (!e)
1119  return;
1120 
1121  QStringList broadcast;
1122  QSet<QString> receivers;
1123 
1124  // delete stale sockets in the UI thread
1125  m_sockListLock.lockForRead();
1126  bool decrRefEmpty = m_decrRefSocketList.empty();
1127  m_sockListLock.unlock();
1128  if (!decrRefEmpty)
1129  {
1130  QWriteLocker locker(&m_sockListLock);
1131  while (!m_decrRefSocketList.empty())
1132  {
1133  (*m_decrRefSocketList.begin())->DecrRef();
1135  }
1136  }
1137 
1138  if (e->type() == MythEvent::kMythEventMessage)
1139  {
1140  auto *me = dynamic_cast<MythEvent *>(e);
1141  if (me == nullptr)
1142  return;
1143 
1144  QString message = me->Message();
1145  QString error;
1146  if ((message == "PREVIEW_SUCCESS" || message == "PREVIEW_QUEUED") &&
1147  me->ExtraDataCount() >= 5)
1148  {
1149  bool ok = true;
1150  uint recordingID = me->ExtraData(0).toUInt(); // pginfo->GetRecordingID()
1151  const QString& filename = me->ExtraData(1); // outFileName
1152  const QString& msg = me->ExtraData(2);
1153  const QString& datetime = me->ExtraData(3);
1154 
1155  if (message == "PREVIEW_QUEUED")
1156  {
1157  LOG(VB_PLAYBACK, LOG_INFO, LOC +
1158  QString("Preview Queued: '%1' '%2'")
1159  .arg(recordingID).arg(filename));
1160  return;
1161  }
1162 
1163  QFile file(filename);
1164  ok = ok && file.open(QIODevice::ReadOnly);
1165 
1166  if (ok)
1167  {
1168  QByteArray data = file.readAll();
1169  QStringList extra("OK");
1170  extra.push_back(QString::number(recordingID));
1171  extra.push_back(msg);
1172  extra.push_back(datetime);
1173  extra.push_back(QString::number(data.size()));
1174 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1175  quint16 checksum = qChecksum(data.constData(), data.size());
1176 #else
1177  quint16 checksum = qChecksum(data);
1178 #endif
1179  extra.push_back(QString::number(checksum));
1180  extra.push_back(QString(data.toBase64()));
1181 
1182  for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++)
1183  {
1184  const QString& token = me->ExtraData(i);
1185  extra.push_back(token);
1186  RequestedBy::iterator it = m_previewRequestedBy.find(token);
1187  if (it != m_previewRequestedBy.end())
1188  {
1189  receivers.insert(*it);
1190  m_previewRequestedBy.erase(it);
1191  }
1192  }
1193 
1194  if (receivers.empty())
1195  {
1196  LOG(VB_GENERAL, LOG_ERR, LOC +
1197  "PREVIEW_SUCCESS but no receivers.");
1198  return;
1199  }
1200 
1201  broadcast.push_back("BACKEND_MESSAGE");
1202  broadcast.push_back("GENERATED_PIXMAP");
1203  broadcast += extra;
1204  }
1205  else
1206  {
1207  message = "PREVIEW_FAILED";
1208  error = QString("Failed to read '%1'").arg(filename);
1209  LOG(VB_GENERAL, LOG_ERR, LOC + error);
1210  }
1211  }
1212 
1213  if (message == "PREVIEW_FAILED" && me->ExtraDataCount() >= 5)
1214  {
1215  const QString& pginfokey = me->ExtraData(0); // pginfo->MakeUniqueKey()
1216  const QString& msg = me->ExtraData(2);
1217 
1218  QStringList extra("ERROR");
1219  extra.push_back(pginfokey);
1220  extra.push_back(msg);
1221  for (uint i = 4 ; i < (uint) me->ExtraDataCount(); i++)
1222  {
1223  const QString& token = me->ExtraData(i);
1224  extra.push_back(token);
1225  RequestedBy::iterator it = m_previewRequestedBy.find(token);
1226  if (it != m_previewRequestedBy.end())
1227  {
1228  receivers.insert(*it);
1229  m_previewRequestedBy.erase(it);
1230  }
1231  }
1232 
1233  if (receivers.empty())
1234  {
1235  LOG(VB_GENERAL, LOG_ERR, LOC +
1236  "PREVIEW_FAILED but no receivers.");
1237  return;
1238  }
1239 
1240  broadcast.push_back("BACKEND_MESSAGE");
1241  broadcast.push_back("GENERATED_PIXMAP");
1242  broadcast += extra;
1243  }
1244 
1245  if (me->Message().startsWith("AUTO_EXPIRE"))
1246  {
1247  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1248  if (tokens.size() != 3)
1249  {
1250  LOG(VB_GENERAL, LOG_ERR, LOC + "Bad AUTO_EXPIRE message");
1251  return;
1252  }
1253 
1254  QDateTime startts = MythDate::fromString(tokens[2]);
1255  RecordingInfo recInfo(tokens[1].toUInt(), startts);
1256 
1257  if (recInfo.GetChanID())
1258  {
1259  SendMythSystemPlayEvent("REC_EXPIRED", &recInfo);
1260 
1261  // allow re-record if auto expired but not expired live
1262  // or already "deleted" programs
1263  if (recInfo.GetRecordingGroup() != "LiveTV" &&
1264  recInfo.GetRecordingGroup() != "Deleted" &&
1265  (gCoreContext->GetBoolSetting("RerecordWatched", false) ||
1266  !recInfo.IsWatched()))
1267  {
1268  recInfo.ForgetHistory();
1269  }
1270  DoHandleDeleteRecording(recInfo, nullptr, false, true, false);
1271  }
1272  else
1273  {
1274  QString msg = QString("Cannot find program info for '%1', "
1275  "while attempting to Auto-Expire.")
1276  .arg(me->Message());
1277  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
1278  }
1279 
1280  return;
1281  }
1282 
1283  if (me->Message().startsWith("QUERY_NEXT_LIVETV_DIR") && m_sched)
1284  {
1285  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1286  if (tokens.size() != 2)
1287  {
1288  LOG(VB_GENERAL, LOG_ERR, LOC +
1289  QString("Bad %1 message").arg(tokens[0]));
1290  return;
1291  }
1292 
1293  m_sched->GetNextLiveTVDir(tokens[1].toInt());
1294  return;
1295  }
1296 
1297  if (me->Message().startsWith("STOP_RECORDING"))
1298  {
1299  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1300  if (tokens.size() < 3 || tokens.size() > 3)
1301  {
1302  LOG(VB_GENERAL, LOG_ERR, LOC +
1303  QString("Bad STOP_RECORDING message: %1")
1304  .arg(me->Message()));
1305  return;
1306  }
1307 
1308  QDateTime startts = MythDate::fromString(tokens[2]);
1309  RecordingInfo recInfo(tokens[1].toUInt(), startts);
1310 
1311  if (recInfo.GetChanID())
1312  {
1313  DoHandleStopRecording(recInfo, nullptr);
1314  }
1315  else
1316  {
1317  LOG(VB_GENERAL, LOG_ERR, LOC +
1318  QString("Cannot find program info for '%1' while "
1319  "attempting to stop recording.").arg(me->Message()));
1320  }
1321 
1322  return;
1323  }
1324 
1325  if ((me->Message().startsWith("DELETE_RECORDING")) ||
1326  (me->Message().startsWith("FORCE_DELETE_RECORDING")))
1327  {
1328  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1329  if (tokens.size() < 3 || tokens.size() > 5)
1330  {
1331  LOG(VB_GENERAL, LOG_ERR, LOC +
1332  QString("Bad %1 message").arg(tokens[0]));
1333  return;
1334  }
1335 
1336  bool force = (tokens.size() >= 4) && (tokens[3] == "FORCE");
1337  bool forget = (tokens.size() >= 5) && (tokens[4] == "FORGET");
1338 
1339  QDateTime startts = MythDate::fromString(tokens[2]);
1340  RecordingInfo recInfo(tokens[1].toUInt(), startts);
1341 
1342  if (recInfo.GetChanID())
1343  {
1344  if (tokens[0] == "FORCE_DELETE_RECORDING")
1345  DoHandleDeleteRecording(recInfo, nullptr, true, false, forget);
1346  else
1347  DoHandleDeleteRecording(recInfo, nullptr, force, false, forget);
1348  }
1349  else
1350  {
1351  LOG(VB_GENERAL, LOG_ERR, LOC +
1352  QString("Cannot find program info for '%1' while "
1353  "attempting to delete.").arg(me->Message()));
1354  }
1355 
1356  return;
1357  }
1358 
1359  if (me->Message().startsWith("UNDELETE_RECORDING"))
1360  {
1361  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1362  if (tokens.size() < 3 || tokens.size() > 3)
1363  {
1364  LOG(VB_GENERAL, LOG_ERR, LOC +
1365  QString("Bad UNDELETE_RECORDING message: %1")
1366  .arg(me->Message()));
1367  return;
1368  }
1369 
1370  QDateTime startts = MythDate::fromString(tokens[2]);
1371  RecordingInfo recInfo(tokens[1].toUInt(), startts);
1372 
1373  if (recInfo.GetChanID())
1374  {
1375  DoHandleUndeleteRecording(recInfo, nullptr);
1376  }
1377  else
1378  {
1379  LOG(VB_GENERAL, LOG_ERR, LOC +
1380  QString("Cannot find program info for '%1' while "
1381  "attempting to undelete.").arg(me->Message()));
1382  }
1383 
1384  return;
1385  }
1386 
1387  if (me->Message().startsWith("ADD_CHILD_INPUT"))
1388  {
1389  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1390  if (!m_ismaster)
1391  {
1392  LOG(VB_GENERAL, LOG_ERR, LOC +
1393  "ADD_CHILD_INPUT event received in slave context");
1394  }
1395  else if (tokens.size() != 2)
1396  {
1397  LOG(VB_GENERAL, LOG_ERR, LOC + "Bad ADD_CHILD_INPUT message");
1398  }
1399  else
1400  {
1401  HandleAddChildInput(tokens[1].toUInt());
1402  }
1403  return;
1404  }
1405 
1406  if (me->Message().startsWith("RESCHEDULE_RECORDINGS") && m_sched)
1407  {
1408  const QStringList& request = me->ExtraDataList();
1409  m_sched->Reschedule(request);
1410  return;
1411  }
1412 
1413  if (me->Message().startsWith("SCHEDULER_ADD_RECORDING") && m_sched)
1414  {
1415  ProgramInfo pi(me->ExtraDataList());
1416  if (!pi.GetChanID())
1417  {
1418  LOG(VB_GENERAL, LOG_ERR, LOC +
1419  "Bad SCHEDULER_ADD_RECORDING message");
1420  return;
1421  }
1422 
1423  m_sched->AddRecording(pi);
1424  return;
1425  }
1426 
1427  if (me->Message().startsWith("UPDATE_RECORDING_STATUS") && m_sched)
1428  {
1429  QStringList tokens = me->Message().split(" ", Qt::SkipEmptyParts);
1430  if (tokens.size() != 6)
1431  {
1432  LOG(VB_GENERAL, LOG_ERR, LOC +
1433  "Bad UPDATE_RECORDING_STATUS message");
1434  return;
1435  }
1436 
1437  uint cardid = tokens[1].toUInt();
1438  uint chanid = tokens[2].toUInt();
1439  QDateTime startts = MythDate::fromString(tokens[3]);
1440  auto recstatus = RecStatus::Type(tokens[4].toInt());
1441  QDateTime recendts = MythDate::fromString(tokens[5]);
1442  m_sched->UpdateRecStatus(cardid, chanid, startts,
1443  recstatus, recendts);
1444 
1446  return;
1447  }
1448 
1449  if (me->Message().startsWith("LIVETV_EXITED"))
1450  {
1451  const QString& chainid = me->ExtraData();
1452  LiveTVChain *chain = GetExistingChain(chainid);
1453  if (chain)
1454  DeleteChain(chain);
1455 
1456  return;
1457  }
1458 
1459  if (me->Message() == "CLEAR_SETTINGS_CACHE")
1461 
1462  if (me->Message().startsWith("RESET_IDLETIME") && m_sched)
1464 
1465  if (me->Message() == "LOCAL_RECONNECT_TO_MASTER")
1467 
1468  if (me->Message() == "LOCAL_SLAVE_BACKEND_ENCODERS_OFFLINE")
1470 
1471  if (me->Message().startsWith("LOCAL_"))
1472  return;
1473 
1474  if (me->Message() == "CREATE_THUMBNAILS")
1475  ImageManagerBe::getInstance()->HandleCreateThumbnails(me->ExtraDataList());
1476 
1477  if (me->Message() == "IMAGE_GET_METADATA")
1478  ImageManagerBe::getInstance()->HandleGetMetadata(me->ExtraData());
1479 
1480  std::unique_ptr<MythEvent> mod_me {nullptr};
1481  if (me->Message().startsWith("MASTER_UPDATE_REC_INFO"))
1482  {
1483  QStringList tokens = me->Message().simplified().split(" ");
1484  uint recordedid = 0;
1485  if (tokens.size() >= 2)
1486  recordedid = tokens[1].toUInt();
1487  if (recordedid == 0)
1488  return;
1489 
1490  ProgramInfo evinfo(recordedid);
1491  if (evinfo.GetChanID())
1492  {
1493  QDateTime rectime = MythDate::current().addSecs(
1494  -gCoreContext->GetNumSetting("RecordOverTime"));
1495 
1496  if (m_sched && evinfo.GetRecordingEndTime() > rectime)
1497  evinfo.SetRecordingStatus(m_sched->GetRecStatus(evinfo));
1498 
1499  QStringList list;
1500  evinfo.ToStringList(list);
1501  mod_me = std::make_unique<MythEvent>("RECORDING_LIST_CHANGE UPDATE", list);
1502  }
1503  else
1504  {
1505  return;
1506  }
1507  }
1508 
1509  if (me->Message().startsWith("DOWNLOAD_FILE"))
1510  {
1511  QStringList extraDataList = me->ExtraDataList();
1512  QString localFile = extraDataList[1];
1513  QFile file(localFile);
1514  QStringList tokens = me->Message().simplified().split(" ");
1515  QMutexLocker locker(&m_downloadURLsLock);
1516 
1517  if (!m_downloadURLs.contains(localFile))
1518  return;
1519 
1520  extraDataList[1] = m_downloadURLs[localFile];
1521 
1522  if ((tokens.size() >= 2) && (tokens[1] == "FINISHED"))
1523  m_downloadURLs.remove(localFile);
1524 
1525  mod_me = std::make_unique<MythEvent>(me->Message(), extraDataList);
1526  }
1527 
1528  if (broadcast.empty())
1529  {
1530  broadcast.push_back("BACKEND_MESSAGE");
1531  if (mod_me != nullptr)
1532  {
1533  broadcast.push_back(mod_me->Message());
1534  broadcast += mod_me->ExtraDataList();
1535  }
1536  else
1537  {
1538  broadcast.push_back(me->Message());
1539  broadcast += me->ExtraDataList();
1540  }
1541  }
1542  }
1543 
1544  if (!broadcast.empty())
1545  {
1546  // Make a local copy of the list, upping the refcount as we go..
1547  std::vector<PlaybackSock *> localPBSList;
1548  m_sockListLock.lockForRead();
1549  for (auto & pbs : m_playbackList)
1550  {
1551  pbs->IncrRef();
1552  localPBSList.push_back(pbs);
1553  }
1554  m_sockListLock.unlock();
1555 
1556  bool sendGlobal = false;
1557  if (m_ismaster && broadcast[1].startsWith("GLOBAL_"))
1558  {
1559  broadcast[1].replace("GLOBAL_", "LOCAL_");
1560  MythEvent me(broadcast[1], broadcast[2]);
1561  gCoreContext->dispatch(me);
1562 
1563  sendGlobal = true;
1564  }
1565 
1566  QSet<PlaybackSock*> sentSet;
1567 
1568  bool isSystemEvent = broadcast[1].startsWith("SYSTEM_EVENT ");
1569  QStringList sentSetSystemEvent(gCoreContext->GetHostName());
1570 
1571  std::vector<PlaybackSock*>::const_iterator iter;
1572  for (iter = localPBSList.begin(); iter != localPBSList.end(); ++iter)
1573  {
1574  PlaybackSock *pbs = *iter;
1575 
1576  if (sentSet.contains(pbs) || pbs->IsDisconnected())
1577  continue;
1578 
1579  if (!receivers.empty() && !receivers.contains(pbs->getHostname()))
1580  continue;
1581 
1582  sentSet.insert(pbs);
1583 
1584  bool reallysendit = false;
1585 
1586  if (broadcast[1] == "CLEAR_SETTINGS_CACHE")
1587  {
1588  if ((m_ismaster) &&
1589  (pbs->isSlaveBackend() || pbs->wantsEvents()))
1590  reallysendit = true;
1591  }
1592  else if (sendGlobal)
1593  {
1594  if (pbs->isSlaveBackend())
1595  reallysendit = true;
1596  }
1597  else if (pbs->wantsEvents())
1598  {
1599  reallysendit = true;
1600  }
1601 
1602  if (reallysendit)
1603  {
1604  if (isSystemEvent)
1605  {
1606  if (!pbs->wantsSystemEvents())
1607  {
1608  continue;
1609  }
1610  if (!pbs->wantsOnlySystemEvents())
1611  {
1612  if (sentSetSystemEvent.contains(pbs->getHostname()))
1613  continue;
1614 
1615  sentSetSystemEvent << pbs->getHostname();
1616  }
1617  }
1618  else if (pbs->wantsOnlySystemEvents())
1619  continue;
1620  }
1621 
1622  MythSocket *sock = pbs->getSocket();
1623  if (reallysendit && sock->IsConnected())
1624  sock->WriteStringList(broadcast);
1625  }
1626 
1627  // Done with the pbs list, so decrement all the instances..
1628  for (iter = localPBSList.begin(); iter != localPBSList.end(); ++iter)
1629  {
1630  PlaybackSock *pbs = *iter;
1631  pbs->DecrRef();
1632  }
1633  }
1634 }
1635 
1644 void MainServer::HandleVersion(MythSocket *socket, const QStringList &slist)
1645 {
1646  QStringList retlist;
1647  const QString& version = slist[1];
1648  if (version != MYTH_PROTO_VERSION)
1649  {
1650  LOG(VB_GENERAL, LOG_CRIT, LOC +
1651  "MainServer::HandleVersion - Client speaks protocol version " +
1652  version + " but we speak " + MYTH_PROTO_VERSION + '!');
1653  retlist << "REJECT" << MYTH_PROTO_VERSION;
1654  socket->WriteStringList(retlist);
1655  HandleDone(socket);
1656  return;
1657  }
1658 
1659  if (slist.size() < 3)
1660  {
1661  LOG(VB_GENERAL, LOG_CRIT, LOC +
1662  "MainServer::HandleVersion - Client did not pass protocol "
1663  "token. Refusing connection!");
1664  retlist << "REJECT" << MYTH_PROTO_VERSION;
1665  socket->WriteStringList(retlist);
1666  HandleDone(socket);
1667  return;
1668  }
1669 
1670  const QString& token = slist[2];
1671  if (token != QString::fromUtf8(MYTH_PROTO_TOKEN))
1672  {
1673  LOG(VB_GENERAL, LOG_CRIT, LOC +
1674  QString("MainServer::HandleVersion - Client sent incorrect "
1675  "protocol token \"%1\" for protocol version. Refusing "
1676  "connection!").arg(token));
1677  retlist << "REJECT" << MYTH_PROTO_VERSION;
1678  socket->WriteStringList(retlist);
1679  HandleDone(socket);
1680  return;
1681  }
1682 
1683  retlist << "ACCEPT" << MYTH_PROTO_VERSION;
1684  socket->WriteStringList(retlist);
1685 }
1686 
1709 void MainServer::HandleAnnounce(QStringList &slist, QStringList commands,
1710  MythSocket *socket)
1711 {
1712  QStringList retlist( "OK" );
1713  QStringList errlist( "ERROR" );
1714 
1715  if (commands.size() < 3 || commands.size() > 6)
1716  {
1717  QString info = "";
1718  if (commands.size() == 2)
1719  info = QString(" %1").arg(commands[1]);
1720 
1721  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Received malformed ANN%1 query")
1722  .arg(info));
1723 
1724  errlist << "malformed_ann_query";
1725  socket->WriteStringList(errlist);
1726  return;
1727  }
1728 
1729  m_sockListLock.lockForRead();
1730  for (auto *pbs : m_playbackList)
1731  {
1732  if (pbs->getSocket() == socket)
1733  {
1734  LOG(VB_GENERAL, LOG_WARNING, LOC +
1735  QString("Client %1 is trying to announce a socket "
1736  "multiple times.")
1737  .arg(commands[2]));
1738  socket->WriteStringList(retlist);
1739  m_sockListLock.unlock();
1740  return;
1741  }
1742  }
1743  m_sockListLock.unlock();
1744 
1745  if (commands[1] == "Playback" || commands[1] == "Monitor" ||
1746  commands[1] == "Frontend")
1747  {
1748  if (commands.size() < 4)
1749  {
1750  LOG(VB_GENERAL, LOG_ERR, LOC +
1751  QString("Received malformed ANN %1 query")
1752  .arg(commands[1]));
1753 
1754  errlist << "malformed_ann_query";
1755  socket->WriteStringList(errlist);
1756  return;
1757  }
1758 
1759  // Monitor connections are same as Playback but they don't
1760  // block shutdowns. See the Scheduler event loop for more.
1761 
1762  auto eventsMode = (PlaybackSockEventsMode)commands[3].toInt();
1763 
1764  QWriteLocker lock(&m_sockListLock);
1765  if (!m_controlSocketList.remove(socket))
1766  return; // socket was disconnected
1767  auto *pbs = new PlaybackSock(this, socket, commands[2], eventsMode);
1768  m_playbackList.push_back(pbs);
1769  lock.unlock();
1770 
1771  LOG(VB_GENERAL, LOG_INFO, LOC + QString("MainServer::ANN %1")
1772  .arg(commands[1]));
1773  LOG(VB_GENERAL, LOG_INFO, LOC +
1774  QString("adding: %1(%2) as a client (events: %3)")
1775  .arg(commands[2])
1776  .arg(quintptr(socket),0,16)
1777  .arg(eventsMode));
1778  pbs->setBlockShutdown((commands[1] == "Playback") ||
1779  (commands[1] == "Frontend"));
1780 
1781  if (commands[1] == "Frontend")
1782  {
1783  pbs->SetAsFrontend();
1784  auto *frontend = new Frontend();
1785  frontend->m_name = commands[2];
1786  // On a combined mbe/fe the frontend will connect using the localhost
1787  // address, we need the external IP which happily will be the same as
1788  // the backend's external IP
1789  if (frontend->m_name == gCoreContext->GetMasterHostName())
1790  frontend->m_ip = QHostAddress(gCoreContext->GetBackendServerIP());
1791  else
1792  frontend->m_ip = socket->GetPeerAddress();
1793  if (gBackendContext)
1795  else
1796  delete frontend;
1797  }
1798 
1799  }
1800  else if (commands[1] == "MediaServer")
1801  {
1802  if (commands.size() < 3)
1803  {
1804  LOG(VB_GENERAL, LOG_ERR, LOC +
1805  "Received malformed ANN MediaServer query");
1806  errlist << "malformed_ann_query";
1807  socket->WriteStringList(errlist);
1808  return;
1809  }
1810 
1811  QWriteLocker lock(&m_sockListLock);
1812  if (!m_controlSocketList.remove(socket))
1813  return; // socket was disconnected
1814  auto *pbs = new PlaybackSock(this, socket, commands[2],
1816  pbs->setAsMediaServer();
1817  pbs->setBlockShutdown(false);
1818  m_playbackList.push_back(pbs);
1819  lock.unlock();
1820 
1822  QString("CLIENT_CONNECTED HOSTNAME %1").arg(commands[2]));
1823  }
1824  else if (commands[1] == "SlaveBackend")
1825  {
1826  if (commands.size() < 4)
1827  {
1828  LOG(VB_GENERAL, LOG_ERR, LOC +
1829  QString("Received malformed ANN %1 query")
1830  .arg(commands[1]));
1831  errlist << "malformed_ann_query";
1832  socket->WriteStringList(errlist);
1833  return;
1834  }
1835 
1836  QWriteLocker lock(&m_sockListLock);
1837  if (!m_controlSocketList.remove(socket))
1838  return; // socket was disconnected
1839  auto *pbs = new PlaybackSock(this, socket, commands[2],
1840  kPBSEvents_None);
1841  m_playbackList.push_back(pbs);
1842  lock.unlock();
1843 
1844  LOG(VB_GENERAL, LOG_INFO, LOC +
1845  QString("adding: %1 as a slave backend server")
1846  .arg(commands[2]));
1847  pbs->setAsSlaveBackend();
1848  pbs->setIP(commands[3]);
1849 
1850  if (m_sched)
1851  {
1852  RecordingList slavelist;
1853  QStringList::const_iterator sit = slist.cbegin()+1;
1854  while (sit != slist.cend())
1855  {
1856  auto *recinfo = new RecordingInfo(sit, slist.cend());
1857  if (!recinfo->GetChanID())
1858  {
1859  delete recinfo;
1860  break;
1861  }
1862  slavelist.push_back(recinfo);
1863  }
1864  m_sched->SlaveConnected(slavelist);
1865  }
1866 
1867  bool wasAsleep = true;
1868  TVRec::s_inputsLock.lockForRead();
1869  for (auto * elink : std::as_const(*m_encoderList))
1870  {
1871  if (elink->GetHostName() == commands[2])
1872  {
1873  if (! (elink->IsWaking() || elink->IsAsleep()))
1874  wasAsleep = false;
1875  elink->SetSocket(pbs);
1876  }
1877  }
1878  TVRec::s_inputsLock.unlock();
1879 
1880  if (!wasAsleep && m_sched)
1881  m_sched->ReschedulePlace("SlaveConnected");
1882 
1883  QString message = QString("LOCAL_SLAVE_BACKEND_ONLINE %2")
1884  .arg(commands[2]);
1885  MythEvent me(message);
1886  gCoreContext->dispatch(me);
1887 
1888  pbs->setBlockShutdown(false);
1889 
1890  m_autoexpireUpdateTimer->start(1s);
1891 
1893  QString("SLAVE_CONNECTED HOSTNAME %1").arg(commands[2]));
1894  }
1895  else if (commands[1] == "FileTransfer")
1896  {
1897  if (slist.size() < 3)
1898  {
1899  LOG(VB_GENERAL, LOG_ERR, LOC +
1900  "Received malformed FileTransfer command");
1901  errlist << "malformed_filetransfer_command";
1902  socket->WriteStringList(errlist);
1903  return;
1904  }
1905 
1906  LOG(VB_NETWORK, LOG_INFO, LOC +
1907  "MainServer::HandleAnnounce FileTransfer");
1908  LOG(VB_NETWORK, LOG_INFO, LOC +
1909  QString("adding: %1 as a remote file transfer") .arg(commands[2]));
1910  QStringList::const_iterator it = slist.cbegin();
1911  QString path = *(++it);
1912  QString wantgroup = *(++it);
1913  QString filename;
1914  QStringList checkfiles;
1915 
1916  for (++it; it != slist.cend(); ++it)
1917  checkfiles += *it;
1918 
1919  BEFileTransfer *ft = nullptr;
1920  bool writemode = false;
1921  bool usereadahead = true;
1922  std::chrono::milliseconds timeout_ms = 2s;
1923  if (commands.size() > 3)
1924  writemode = (commands[3].toInt() != 0);
1925 
1926  if (commands.size() > 4)
1927  usereadahead = (commands[4].toInt() != 0);
1928 
1929  if (commands.size() > 5)
1930  timeout_ms = std::chrono::milliseconds(commands[5].toInt());
1931 
1932  if (writemode)
1933  {
1934  if (wantgroup.isEmpty())
1935  wantgroup = "Default";
1936 
1937  StorageGroup sgroup(wantgroup, gCoreContext->GetHostName(), false);
1938  QString dir = sgroup.FindNextDirMostFree();
1939  if (dir.isEmpty())
1940  {
1941  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to determine directory "
1942  "to write to in FileTransfer write command");
1943  errlist << "filetransfer_directory_not_found";
1944  socket->WriteStringList(errlist);
1945  return;
1946  }
1947 
1948  if (path.isEmpty())
1949  {
1950  LOG(VB_GENERAL, LOG_ERR, LOC +
1951  QString("FileTransfer write filename is empty in path '%1'.")
1952  .arg(path));
1953  errlist << "filetransfer_filename_empty";
1954  socket->WriteStringList(errlist);
1955  return;
1956  }
1957 
1958  if ((path.contains("/../")) ||
1959  (path.startsWith("../")))
1960  {
1961  LOG(VB_GENERAL, LOG_ERR, LOC +
1962  QString("FileTransfer write filename '%1' does not pass "
1963  "sanity checks.") .arg(path));
1964  errlist << "filetransfer_filename_dangerous";
1965  socket->WriteStringList(errlist);
1966  return;
1967  }
1968 
1969  filename = dir + "/" + path;
1970  }
1971  else
1972  filename = LocalFilePath(path, wantgroup);
1973 
1974  if (filename.isEmpty())
1975  {
1976  LOG(VB_GENERAL, LOG_ERR, LOC + "Empty filename, cowardly aborting!");
1977  errlist << "filetransfer_filename_empty";
1978  socket->WriteStringList(errlist);
1979  return;
1980  }
1981 
1982 
1983  QFileInfo finfo(filename);
1984  if (finfo.isDir())
1985  {
1986  LOG(VB_GENERAL, LOG_ERR, LOC +
1987  QString("FileTransfer filename '%1' is actually a directory, "
1988  "cannot transfer.") .arg(filename));
1989  errlist << "filetransfer_filename_is_a_directory";
1990  socket->WriteStringList(errlist);
1991  return;
1992  }
1993 
1994  if (writemode)
1995  {
1996  QString dirPath = finfo.absolutePath();
1997  QDir qdir(dirPath);
1998  if (!qdir.exists())
1999  {
2000  if (!qdir.mkpath(dirPath))
2001  {
2002  LOG(VB_GENERAL, LOG_ERR, LOC +
2003  QString("FileTransfer filename '%1' is in a "
2004  "subdirectory which does not exist, and can "
2005  "not be created.") .arg(filename));
2006  errlist << "filetransfer_unable_to_create_subdirectory";
2007  socket->WriteStringList(errlist);
2008  return;
2009  }
2010  }
2011  QWriteLocker lock(&m_sockListLock);
2012  if (!m_controlSocketList.remove(socket))
2013  return; // socket was disconnected
2014  ft = new BEFileTransfer(filename, socket, writemode);
2015  }
2016  else
2017  {
2018  QWriteLocker lock(&m_sockListLock);
2019  if (!m_controlSocketList.remove(socket))
2020  return; // socket was disconnected
2021  ft = new BEFileTransfer(filename, socket, usereadahead, timeout_ms);
2022  }
2023 
2024  if (!ft->isOpen())
2025  {
2026  LOG(VB_GENERAL, LOG_ERR, LOC +
2027  QString("Can't open %1").arg(filename));
2028  errlist << "filetransfer_unable_to_open_file";
2029  socket->WriteStringList(errlist);
2030  socket->IncrRef(); // BEFileTransfer took ownership of the socket, take it back
2031  ft->DecrRef();
2032  return;
2033  }
2034  ft->IncrRef();
2035  LOG(VB_GENERAL, LOG_INFO, LOC +
2036  QString("adding: %1(%2) as a file transfer")
2037  .arg(commands[2])
2038  .arg(quintptr(socket),0,16));
2039  m_sockListLock.lockForWrite();
2040  m_fileTransferList.push_back(ft);
2041  m_sockListLock.unlock();
2042 
2043  retlist << QString::number(socket->GetSocketDescriptor());
2044  retlist << QString::number(ft->GetFileSize());
2045 
2046  ft->DecrRef();
2047 
2048  if (!checkfiles.empty())
2049  {
2050  QFileInfo fi(filename);
2051  QDir dir = fi.absoluteDir();
2052  for (const auto & file : std::as_const(checkfiles))
2053  {
2054  if (dir.exists(file) &&
2055  ((file).endsWith(".srt") ||
2056  QFileInfo(dir, file).size() >= kReadTestSize))
2057  {
2058  retlist<<file;
2059  }
2060  }
2061  }
2062  }
2063 
2064  socket->WriteStringList(retlist);
2066 }
2067 
2074 {
2075  socket->DisconnectFromHost();
2077 }
2078 
2080 {
2081  SendErrorResponse(pbs->getSocket(), error);
2082 }
2083 
2085 {
2086  LOG(VB_GENERAL, LOG_ERR, LOC + error);
2087 
2088  QStringList strList("ERROR");
2089  strList << error;
2090 
2091  SendResponse(sock, strList);
2092 }
2093 
2094 void MainServer::SendResponse(MythSocket *socket, QStringList &commands)
2095 {
2096  // Note: this method assumes that the playback or filetransfer
2097  // handler has already been uprefed and the socket as well.
2098 
2099  // These checks are really just to check if the socket has
2100  // been remotely disconnected while we were working on the
2101  // response.
2102 
2103  bool do_write = false;
2104  if (socket)
2105  {
2106  m_sockListLock.lockForRead();
2107  do_write = (GetPlaybackBySock(socket) ||
2108  GetFileTransferBySock(socket));
2109  m_sockListLock.unlock();
2110  }
2111 
2112  if (do_write)
2113  {
2114  socket->WriteStringList(commands);
2115  }
2116  else
2117  {
2118  LOG(VB_GENERAL, LOG_ERR, LOC +
2119  "SendResponse: Unable to write to client socket, as it's no "
2120  "longer there");
2121  }
2122 }
2123 
2133 {
2134  MythSocket *pbssock = pbs->getSocket();
2135  QString playbackhost = pbs->getHostname();
2136 
2137  QMap<QString,ProgramInfo*> recMap;
2138  if (m_sched)
2139  recMap = m_sched->GetRecording();
2140 
2141  QMap<QString,uint32_t> inUseMap = ProgramInfo::QueryInUseMap();
2142  QMap<QString,bool> isJobRunning =
2144 
2145  int sort = 0;
2146  // Allow "Play" and "Delete" for backwards compatibility with protocol
2147  // version 56 and below.
2148  if ((type == "Ascending") || (type == "Play"))
2149  sort = 1;
2150  else if ((type == "Descending") || (type == "Delete"))
2151  sort = -1;
2152 
2153  ProgramList destination;
2155  destination, (type == "Recording"),
2156  inUseMap, isJobRunning, recMap, sort);
2157 
2158  QMap<QString,ProgramInfo*>::iterator mit = recMap.begin();
2159  for (; mit != recMap.end(); mit = recMap.erase(mit))
2160  delete *mit;
2161 
2162  QStringList outputlist(QString::number(destination.size()));
2163  QMap<QString, int> backendPortMap;
2164  int port = gCoreContext->GetBackendServerPort();
2165  QString host = gCoreContext->GetHostName();
2166 
2167  for (auto* proginfo : destination)
2168  {
2169  PlaybackSock *slave = nullptr;
2170 
2171  if (proginfo->GetHostname() != gCoreContext->GetHostName())
2172  slave = GetSlaveByHostname(proginfo->GetHostname());
2173 
2174  if ((proginfo->GetHostname() == gCoreContext->GetHostName()) ||
2175  (!slave && m_masterBackendOverride))
2176  {
2177  proginfo->SetPathname(MythCoreContext::GenMythURL(host,port,
2178  proginfo->GetBasename()));
2179  if (!proginfo->GetFilesize())
2180  {
2181  QString tmpURL = GetPlaybackURL(proginfo);
2182  if (tmpURL.startsWith('/'))
2183  {
2184  QFile checkFile(tmpURL);
2185  if (!tmpURL.isEmpty() && checkFile.exists())
2186  {
2187  proginfo->SetFilesize(checkFile.size());
2188  if (proginfo->GetRecordingEndTime() <
2190  {
2191  proginfo->SaveFilesize(proginfo->GetFilesize());
2192  }
2193  }
2194  }
2195  }
2196  }
2197  else if (!slave)
2198  {
2199  proginfo->SetPathname(GetPlaybackURL(proginfo));
2200  if (proginfo->GetPathname().isEmpty())
2201  {
2202  LOG(VB_GENERAL, LOG_ERR, LOC +
2203  QString("HandleQueryRecordings() "
2204  "Couldn't find backend for:\n\t\t\t%1")
2205  .arg(proginfo->toString(ProgramInfo::kTitleSubtitle)));
2206 
2207  proginfo->SetFilesize(0);
2208  proginfo->SetPathname("file not found");
2209  }
2210  }
2211  else
2212  {
2213  if (!proginfo->GetFilesize())
2214  {
2215  if (!slave->FillProgramInfo(*proginfo, playbackhost))
2216  {
2217  LOG(VB_GENERAL, LOG_ERR, LOC +
2218  "MainServer::HandleQueryRecordings()"
2219  "\n\t\t\tCould not fill program info "
2220  "from backend");
2221  }
2222  else
2223  {
2224  if (proginfo->GetRecordingEndTime() <
2226  {
2227  proginfo->SaveFilesize(proginfo->GetFilesize());
2228  }
2229  }
2230  }
2231  else
2232  {
2233  ProgramInfo *p = proginfo;
2234  QString hostname = p->GetHostname();
2235 
2236  if (!backendPortMap.contains(hostname))
2237  backendPortMap[hostname] = gCoreContext->GetBackendServerPort(hostname);
2238 
2239  p->SetPathname(MythCoreContext::GenMythURL(hostname,
2240  backendPortMap[hostname],
2241  p->GetBasename()));
2242  }
2243  }
2244 
2245  if (slave)
2246  slave->DecrRef();
2247 
2248  proginfo->ToStringList(outputlist);
2249  }
2250 
2251  SendResponse(pbssock, outputlist);
2252 }
2253 
2260 {
2261  if (slist.size() < 3)
2262  {
2263  LOG(VB_GENERAL, LOG_ERR, LOC + "Bad QUERY_RECORDING query");
2264  return;
2265  }
2266 
2267  MythSocket *pbssock = pbs->getSocket();
2268  QString command = slist[1].toUpper();
2269  ProgramInfo *pginfo = nullptr;
2270 
2271  if (command == "BASENAME")
2272  {
2273  pginfo = new ProgramInfo(slist[2]);
2274  }
2275  else if (command == "TIMESLOT")
2276  {
2277  if (slist.size() < 4)
2278  {
2279  LOG(VB_GENERAL, LOG_ERR, LOC + "Bad QUERY_RECORDING query");
2280  return;
2281  }
2282 
2283  QDateTime recstartts = MythDate::fromString(slist[3]);
2284  pginfo = new ProgramInfo(slist[2].toUInt(), recstartts);
2285  }
2286 
2287  QStringList strlist;
2288 
2289  if (pginfo && pginfo->GetChanID())
2290  {
2291  strlist << "OK";
2292  pginfo->ToStringList(strlist);
2293  }
2294  else
2295  {
2296  strlist << "ERROR";
2297  }
2298 
2299  delete pginfo;
2300 
2301  SendResponse(pbssock, strlist);
2302 }
2303 
2305 {
2306  MythSocket *pbssock = pbs->getSocket();
2307 
2308  QString playbackhost = slist[1];
2309 
2310  QStringList::const_iterator it = slist.cbegin() + 2;
2311  ProgramInfo pginfo(it, slist.cend());
2312 
2313  if (pginfo.HasPathname())
2314  {
2315  QString lpath = GetPlaybackURL(&pginfo);
2316  int port = gCoreContext->GetBackendServerPort();
2317  QString host = gCoreContext->GetHostName();
2318 
2319  if (playbackhost == gCoreContext->GetHostName())
2320  pginfo.SetPathname(lpath);
2321  else
2322  pginfo.SetPathname(MythCoreContext::GenMythURL(host,port,
2323  pginfo.GetBasename()));
2324 
2325  const QFileInfo info(lpath);
2326  pginfo.SetFilesize(info.size());
2327  }
2328 
2329  QStringList strlist;
2330 
2331  pginfo.ToStringList(strlist);
2332 
2333  SendResponse(pbssock, strlist);
2334 }
2335 
2336 
2337 void DeleteThread::run(void)
2338 {
2339  if (m_ms)
2340  m_ms->DoDeleteThread(this);
2341 }
2342 
2344 {
2345  // sleep a little to let frontends reload the recordings list
2346  // after deleting a recording, then we can hammer the DB and filesystem
2347  std::this_thread::sleep_for(3s + std::chrono::microseconds(MythRandom(0, 2000)));
2348 
2349  m_deletelock.lock();
2350 
2351 #if 0
2352  QString logInfo = QString("recording id %1 (chanid %2 at %3)")
2353  .arg(ds->m_recordedid)
2354  .arg(ds->m_chanid)
2355  .arg(ds->m_recstartts.toString(Qt::ISODate));
2356 
2357  QString name = QString("deleteThread%1%2").arg(getpid()).arg(MythRandom());
2358 #endif
2359  QFile checkFile(ds->m_filename);
2360 
2362  {
2363  QString msg = QString("ERROR opening database connection for Delete "
2364  "Thread for chanid %1 recorded at %2. Program "
2365  "will NOT be deleted.")
2366  .arg(ds->m_chanid)
2367  .arg(ds->m_recstartts.toString(Qt::ISODate));
2368  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
2369 
2370  m_deletelock.unlock();
2371  return;
2372  }
2373 
2374  ProgramInfo pginfo(ds->m_chanid, ds->m_recstartts);
2375 
2376  if (!pginfo.GetChanID())
2377  {
2378  QString msg = QString("ERROR retrieving program info when trying to "
2379  "delete program for chanid %1 recorded at %2. "
2380  "Recording will NOT be deleted.")
2381  .arg(ds->m_chanid)
2382  .arg(ds->m_recstartts.toString(Qt::ISODate));
2383  LOG(VB_GENERAL, LOG_ERR, LOC + msg);
2384 
2385  m_deletelock.unlock();
2386  return;
2387  }
2388 
2389  // Don't allow deleting files where filesize != 0 and we can't find
2390  // the file, unless forceMetadataDelete has been set. This allows
2391  // deleting failed recordings without fuss, but blocks accidental
2392  // deletion of metadata for files where the filesystem has gone missing.
2393  if ((!checkFile.exists()) && pginfo.GetFilesize() &&
2394  (!ds->m_forceMetadataDelete))
2395  {
2396  LOG(VB_GENERAL, LOG_ERR, LOC +
2397  QString("ERROR when trying to delete file: %1. File "
2398  "doesn't exist. Database metadata will not be removed.")
2399  .arg(ds->m_filename));
2400 
2401  pginfo.SaveDeletePendingFlag(false);
2402  m_deletelock.unlock();
2403  return;
2404  }
2405 
2407 
2408  LiveTVChain *tvchain = GetChainWithRecording(pginfo);
2409  if (tvchain)
2410  tvchain->DeleteProgram(&pginfo);
2411 
2412  bool followLinks = gCoreContext->GetBoolSetting("DeletesFollowLinks", false);
2413  bool slowDeletes = gCoreContext->GetBoolSetting("TruncateDeletesSlowly", false);
2414  int fd = -1;
2415  off_t size = 0;
2416  bool errmsg = false;
2417 
2418  //-----------------------------------------------------------------------
2419  // TODO Move the following into DeleteRecordedFiles
2420  //-----------------------------------------------------------------------
2421 
2422  // Delete recording.
2423  if (slowDeletes)
2424  {
2425  // Since stat fails after unlinking on some filesystems,
2426  // get the filesize first
2427  const QFileInfo info(ds->m_filename);
2428  size = info.size();
2429  fd = DeleteFile(ds->m_filename, followLinks, ds->m_forceMetadataDelete);
2430 
2431  if ((fd < 0) && checkFile.exists())
2432  errmsg = true;
2433  }
2434  else
2435  {
2436  delete_file_immediately(ds->m_filename, followLinks, false);
2437  std::this_thread::sleep_for(2s);
2438  if (checkFile.exists())
2439  errmsg = true;
2440  }
2441 
2442  if (errmsg)
2443  {
2444  LOG(VB_GENERAL, LOG_ERR, LOC +
2445  QString("Error deleting file: %1. Keeping metadata in database.")
2446  .arg(ds->m_filename));
2447 
2448  pginfo.SaveDeletePendingFlag(false);
2449  m_deletelock.unlock();
2450  return;
2451  }
2452 
2453  // Delete all related files, though not the recording itself
2454  // i.e. preview thumbnails, srt subtitles, orphaned transcode temporary
2455  // files
2456  //
2457  // TODO: Delete everything with this basename to catch stray
2458  // .tmp and .old files, and future proof it
2459  QFileInfo fInfo( ds->m_filename );
2460  QStringList nameFilters;
2461  nameFilters.push_back(fInfo.fileName() + "*.png");
2462  nameFilters.push_back(fInfo.fileName() + "*.jpg");
2463  nameFilters.push_back(fInfo.fileName() + ".tmp");
2464  nameFilters.push_back(fInfo.fileName() + ".old");
2465  nameFilters.push_back(fInfo.fileName() + ".map");
2466  nameFilters.push_back(fInfo.fileName() + ".tmp.map");
2467  nameFilters.push_back(fInfo.baseName() + ".srt"); // e.g. 1234_20150213165800.srt
2468 
2469  QDir dir (fInfo.path());
2470  QFileInfoList miscFiles = dir.entryInfoList(nameFilters);
2471 
2472  for (const auto & file : std::as_const(miscFiles))
2473  {
2474  QString sFileName = file.absoluteFilePath();
2475  delete_file_immediately( sFileName, followLinks, true);
2476  }
2477  // -----------------------------------------------------------------------
2478 
2479  // TODO Have DeleteRecordedFiles do the deletion of all associated files
2480  DeleteRecordedFiles(ds);
2481 
2482  DoDeleteInDB(ds);
2483 
2484  m_deletelock.unlock();
2485 
2486  if (slowDeletes && fd >= 0)
2487  TruncateAndClose(&pginfo, fd, ds->m_filename, size);
2488 }
2489 
2491 {
2492  QString logInfo = QString("recording id %1 filename %2")
2493  .arg(ds->m_recordedid).arg(ds->m_filename);
2494 
2495  LOG(VB_GENERAL, LOG_NOTICE, "DeleteRecordedFiles - " + logInfo);
2496 
2497  MSqlQuery update(MSqlQuery::InitCon());
2498  MSqlQuery query(MSqlQuery::InitCon());
2499  query.prepare("SELECT basename, hostname, storagegroup FROM recordedfile "
2500  "WHERE recordedid = :RECORDEDID;");
2501  query.bindValue(":RECORDEDID", ds->m_recordedid);
2502 
2503  if (!query.exec() || !query.size())
2504  {
2505  MythDB::DBError("RecordedFiles deletion", query);
2506  LOG(VB_GENERAL, LOG_ERR, LOC +
2507  QString("Error querying recordedfiles for %1.") .arg(logInfo));
2508  }
2509 
2510  while (query.next())
2511  {
2512  QString basename = query.value(0).toString();
2513  //QString hostname = query.value(1).toString();
2514  //QString storagegroup = query.value(2).toString();
2515  bool deleteInDB = false;
2516 
2517  if (basename == QFileInfo(ds->m_filename).fileName())
2518  deleteInDB = true;
2519  else
2520  {
2521 // LOG(VB_FILE, LOG_INFO, LOC +
2522 // QString("DeleteRecordedFiles(%1), deleting '%2'")
2523 // .arg(logInfo).arg(query.value(0).toString()));
2524 //
2525 // StorageGroup sgroup(storagegroup);
2526 // QString localFile = sgroup.FindFile(basename);
2527 //
2528 // QString url = gCoreContext->GenMythURL(hostname,
2529 // gCoreContext->GetBackendServerPort(hostname),
2530 // basename,
2531 // storagegroup);
2532 //
2533 // if ((((hostname == gCoreContext->GetHostName()) ||
2534 // (!localFile.isEmpty())) &&
2535 // (HandleDeleteFile(basename, storagegroup))) ||
2536 // (((hostname != gCoreContext->GetHostName()) ||
2537 // (localFile.isEmpty())) &&
2538 // (RemoteFile::DeleteFile(url))))
2539 // {
2540 // deleteInDB = true;
2541 // }
2542  }
2543 
2544  if (deleteInDB)
2545  {
2546  update.prepare("DELETE FROM recordedfile "
2547  "WHERE recordedid = :RECORDEDID "
2548  "AND basename = :BASENAME ;");
2549  update.bindValue(":RECORDEDID", ds->m_recordedid);
2550  update.bindValue(":BASENAME", basename);
2551  if (!update.exec())
2552  {
2553  MythDB::DBError("RecordedFiles deletion", update);
2554  LOG(VB_GENERAL, LOG_ERR, LOC +
2555  QString("Error querying recordedfile (%1) for %2.")
2556  .arg(query.value(1).toString(), logInfo));
2557  }
2558  }
2559  }
2560 }
2561 
2563 {
2564  QString logInfo = QString("recording id %1 (chanid %2 at %3)")
2565  .arg(ds->m_recordedid)
2566  .arg(ds->m_chanid).arg(ds->m_recstartts.toString(Qt::ISODate));
2567 
2568  LOG(VB_GENERAL, LOG_NOTICE, "DoDeleteINDB - " + logInfo);
2569 
2570  MSqlQuery query(MSqlQuery::InitCon());
2571  query.prepare("DELETE FROM recorded WHERE recordedid = :RECORDEDID AND "
2572  "title = :TITLE;");
2573  query.bindValue(":RECORDEDID", ds->m_recordedid);
2574  query.bindValue(":TITLE", ds->m_title);
2575 
2576  if (!query.exec() || !query.size())
2577  {
2578  MythDB::DBError("Recorded program deletion", query);
2579  LOG(VB_GENERAL, LOG_ERR, LOC +
2580  QString("Error deleting recorded entry for %1.") .arg(logInfo));
2581  }
2582 
2583  std::this_thread::sleep_for(1s);
2584 
2585  // Notify the frontend so it can requery for Free Space
2586  QString msg = QString("RECORDING_LIST_CHANGE DELETE %1")
2587  .arg(ds->m_recordedid);
2589 
2590  // sleep a little to let frontends reload the recordings list
2591  std::this_thread::sleep_for(3s);
2592 
2593  query.prepare("DELETE FROM recordedmarkup "
2594  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
2595  query.bindValue(":CHANID", ds->m_chanid);
2596  query.bindValue(":STARTTIME", ds->m_recstartts);
2597 
2598  if (!query.exec())
2599  {
2600  MythDB::DBError("Recorded program delete recordedmarkup", query);
2601  LOG(VB_GENERAL, LOG_ERR, LOC +
2602  QString("Error deleting recordedmarkup for %1.") .arg(logInfo));
2603  }
2604 
2605  query.prepare("DELETE FROM recordedseek "
2606  "WHERE chanid = :CHANID AND starttime = :STARTTIME;");
2607  query.bindValue(":CHANID", ds->m_chanid);
2608  query.bindValue(":STARTTIME", ds->m_recstartts);
2609 
2610  if (!query.exec())
2611  {
2612  MythDB::DBError("Recorded program delete recordedseek", query);
2613  LOG(VB_GENERAL, LOG_ERR, LOC +
2614  QString("Error deleting recordedseek for %1.")
2615  .arg(logInfo));
2616  }
2617 }
2618 
2628 int MainServer::DeleteFile(const QString &filename, bool followLinks,
2629  bool deleteBrokenSymlinks)
2630 {
2631  QFileInfo finfo(filename);
2632  int fd = -1;
2633  QString linktext = "";
2634  QByteArray fname = filename.toLocal8Bit();
2635 
2636  LOG(VB_FILE, LOG_INFO, LOC +
2637  QString("About to unlink/delete file: '%1'")
2638  .arg(fname.constData()));
2639 
2640  QString errmsg = QString("Delete Error '%1'").arg(fname.constData());
2641  if (finfo.isSymLink())
2642  {
2643  linktext = getSymlinkTarget(filename);
2644  QByteArray alink = linktext.toLocal8Bit();
2645  errmsg += QString(" -> '%2'").arg(alink.constData());
2646  }
2647 
2648  if (followLinks && finfo.isSymLink())
2649  {
2650  if (!finfo.exists() && deleteBrokenSymlinks)
2651  unlink(fname.constData());
2652  else
2653  {
2654  fd = OpenAndUnlink(linktext);
2655  if (fd >= 0)
2656  unlink(fname.constData());
2657  }
2658  }
2659  else if (!finfo.isSymLink())
2660  {
2661  fd = OpenAndUnlink(filename);
2662  }
2663  else // just delete symlinks immediately
2664  {
2665  int err = unlink(fname.constData());
2666  if (err == 0)
2667  return -2; // valid result, not an error condition
2668  }
2669 
2670  if (fd < 0 && errno != EISDIR)
2671  LOG(VB_GENERAL, LOG_ERR, LOC + errmsg + ENO);
2672 
2673  return fd;
2674 }
2675 
2686 {
2687  QByteArray fname = filename.toLocal8Bit();
2688  QString msg = QString("Error deleting '%1'").arg(fname.constData());
2689  int fd = open(fname.constData(), O_WRONLY);
2690 
2691  if (fd == -1)
2692  {
2693  if (errno == EISDIR)
2694  {
2695  QDir dir(filename);
2696  if(MythRemoveDirectory(dir))
2697  {
2698  LOG(VB_GENERAL, LOG_ERR, msg + " could not delete directory " + ENO);
2699  return -1;
2700  }
2701  }
2702  else
2703  {
2704  LOG(VB_GENERAL, LOG_ERR, msg + " could not open " + ENO);
2705  return -1;
2706  }
2707  }
2708  else if (unlink(fname.constData()))
2709  {
2710  LOG(VB_GENERAL, LOG_ERR, LOC + msg + " could not unlink " + ENO);
2711  close(fd);
2712  return -1;
2713  }
2714 
2715  return fd;
2716 }
2717 
2727  const QString &filename, off_t fsize)
2728 {
2729  QMutexLocker locker(&s_truncate_and_close_lock);
2730 
2731  if (pginfo)
2732  {
2733  pginfo->SetPathname(filename);
2734  pginfo->MarkAsInUse(true, kTruncatingDeleteInUseID);
2735  }
2736 
2737  int cards = 5;
2738  {
2739  MSqlQuery query(MSqlQuery::InitCon());
2740  query.prepare("SELECT COUNT(cardid) FROM capturecard;");
2741  if (query.exec() && query.next())
2742  cards = query.value(0).toInt();
2743  }
2744 
2745  // Time between truncation steps in milliseconds
2746  constexpr std::chrono::milliseconds sleep_time = 500ms;
2747  const size_t min_tps = 8LL * 1024 * 1024;
2748  const auto calc_tps = (size_t) (cards * 1.2 * (22200000LL / 8.0));
2749  const size_t tps = std::max(min_tps, calc_tps);
2750  const auto increment = (size_t) (tps * (sleep_time.count() * 0.001F));
2751 
2752  LOG(VB_FILE, LOG_INFO, LOC +
2753  QString("Truncating '%1' by %2 MB every %3 milliseconds")
2754  .arg(filename)
2755  .arg(increment / (1024.0 * 1024.0), 0, 'f', 2)
2756  .arg(sleep_time.count()));
2757 
2758  GetMythDB()->GetDBManager()->PurgeIdleConnections(false);
2759 
2760  int count = 0;
2761  while (fsize > 0)
2762  {
2763 #if 0
2764  LOG(VB_FILE, LOG_DEBUG, LOC + QString("Truncating '%1' to %2 MB")
2765  .arg(filename).arg(fsize / (1024.0 * 1024.0), 0, 'f', 2));
2766 #endif
2767 
2768  int err = ftruncate(fd, fsize);
2769  if (err)
2770  {
2771  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error truncating '%1'")
2772  .arg(filename) + ENO);
2773  if (pginfo)
2774  pginfo->MarkAsInUse(false, kTruncatingDeleteInUseID);
2775  return 0 == close(fd);
2776  }
2777 
2778  fsize -= increment;
2779 
2780  if (pginfo && ((count % 100) == 0))
2781  pginfo->UpdateInUseMark(true);
2782 
2783  count++;
2784 
2785  std::this_thread::sleep_for(sleep_time);
2786  }
2787 
2788  bool ok = (0 == close(fd));
2789 
2790  if (pginfo)
2791  pginfo->MarkAsInUse(false, kTruncatingDeleteInUseID);
2792 
2793  LOG(VB_FILE, LOG_INFO, LOC +
2794  QString("Finished truncating '%1'").arg(filename));
2795 
2796  return ok;
2797 }
2798 
2800  PlaybackSock *pbs)
2801 {
2802  MythSocket *pbssock = nullptr;
2803  if (pbs)
2804  pbssock = pbs->getSocket();
2805 
2806  QStringList::const_iterator it = slist.cbegin() + 1;
2807  ProgramInfo pginfo(it, slist.cend());
2808 
2809  int result = 0;
2810 
2811  if (m_ismaster && pginfo.GetHostname() != gCoreContext->GetHostName())
2812  {
2813  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
2814  if (slave)
2815  {
2816  result = slave->CheckRecordingActive(&pginfo);
2817  slave->DecrRef();
2818  }
2819  }
2820  else
2821  {
2822  TVRec::s_inputsLock.lockForRead();
2823  for (auto iter = m_encoderList->constBegin(); iter != m_encoderList->constEnd(); ++iter)
2824  {
2825  EncoderLink *elink = *iter;
2826 
2827  if (elink->IsLocal() && elink->MatchesRecording(&pginfo))
2828  result = iter.key();
2829  }
2830  TVRec::s_inputsLock.unlock();
2831  }
2832 
2833  QStringList outputlist( QString::number(result) );
2834  if (pbssock)
2835  SendResponse(pbssock, outputlist);
2836 }
2837 
2839 {
2840  QStringList::const_iterator it = slist.cbegin() + 1;
2841  RecordingInfo recinfo(it, slist.cend());
2842  if (recinfo.GetChanID())
2843  {
2844  if (m_ismaster)
2845  {
2846  // Stop recording may have been called for the same program on
2847  // different channel in the guide, we need to find the actual channel
2848  // that the recording is occurring on. This only needs doing once
2849  // on the master backend, as the correct chanid will then be sent
2850  // to the slave
2851  ProgramList schedList;
2852  bool hasConflicts = false;
2853  LoadFromScheduler(schedList, hasConflicts);
2854  for (auto *pInfo : schedList)
2855  {
2856  if ((pInfo->GetRecordingStatus() == RecStatus::Tuning ||
2857  pInfo->GetRecordingStatus() == RecStatus::Failing ||
2858  pInfo->GetRecordingStatus() == RecStatus::Recording)
2859  && recinfo.IsSameProgram(*pInfo))
2860  recinfo.SetChanID(pInfo->GetChanID());
2861  }
2862  }
2863  DoHandleStopRecording(recinfo, pbs);
2864  }
2865 }
2866 
2868  RecordingInfo &recinfo, PlaybackSock *pbs)
2869 {
2870  MythSocket *pbssock = nullptr;
2871  if (pbs)
2872  pbssock = pbs->getSocket();
2873 
2874  // FIXME! We don't know what state the recorder is in at this
2875  // time. Simply set the recstatus to RecStatus::Unknown and let the
2876  // scheduler do the best it can with it. The proper long term fix
2877  // is probably to have the recorder return the actual recstatus as
2878  // part of the stop recording response. That's a more involved
2879  // change than I care to make during the 0.25 code freeze.
2881 
2882  if (m_ismaster && recinfo.GetHostname() != gCoreContext->GetHostName())
2883  {
2884  PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname());
2885 
2886  if (slave)
2887  {
2888  int num = slave->StopRecording(&recinfo);
2889 
2890  if (num > 0)
2891  {
2892  TVRec::s_inputsLock.lockForRead();
2893  if (m_encoderList->contains(num))
2894  {
2895  (*m_encoderList)[num]->StopRecording();
2896  }
2897  TVRec::s_inputsLock.unlock();
2898  if (m_sched)
2899  m_sched->UpdateRecStatus(&recinfo);
2900  }
2901  if (pbssock)
2902  {
2903  QStringList outputlist( "0" );
2904  SendResponse(pbssock, outputlist);
2905  }
2906 
2907  slave->DecrRef();
2908  return;
2909  }
2910 
2911  // If the slave is unreachable, we can assume that the
2912  // recording has stopped and the status should be updated.
2913  // Continue so that the master can try to update the endtime
2914  // of the file is in a shared directory.
2915  if (m_sched)
2916  m_sched->UpdateRecStatus(&recinfo);
2917  }
2918 
2919  int recnum = -1;
2920 
2921  TVRec::s_inputsLock.lockForRead();
2922  for (auto iter = m_encoderList->constBegin(); iter != m_encoderList->constEnd(); ++iter)
2923  {
2924  EncoderLink *elink = *iter;
2925 
2926  if (elink->IsLocal() && elink->MatchesRecording(&recinfo))
2927  {
2928  recnum = iter.key();
2929 
2930  elink->StopRecording();
2931 
2932  while (elink->IsBusyRecording() ||
2933  elink->GetState() == kState_ChangingState)
2934  {
2935  std::this_thread::sleep_for(100us);
2936  }
2937 
2938  if (m_ismaster)
2939  {
2940  if (m_sched)
2941  m_sched->UpdateRecStatus(&recinfo);
2942  }
2943 
2944  break;
2945  }
2946  }
2947  TVRec::s_inputsLock.unlock();
2948 
2949  if (pbssock)
2950  {
2951  QStringList outputlist( QString::number(recnum) );
2952  SendResponse(pbssock, outputlist);
2953  }
2954 }
2955 
2956 void MainServer::HandleDeleteRecording(QString &chanid, QString &starttime,
2957  PlaybackSock *pbs,
2958  bool forceMetadataDelete,
2959  bool forgetHistory)
2960 {
2961  QDateTime recstartts = MythDate::fromString(starttime);
2962  RecordingInfo recinfo(chanid.toUInt(), recstartts);
2963 
2964  if (!recinfo.GetRecordingID())
2965  {
2966  qDebug() << "HandleDeleteRecording(chanid, starttime) Empty Recording ID";
2967  }
2968 
2969  if (!recinfo.GetChanID()) // !recinfo.GetRecordingID()
2970  {
2971  MythSocket *pbssock = nullptr;
2972  if (pbs)
2973  pbssock = pbs->getSocket();
2974 
2975  QStringList outputlist( QString::number(0) );
2976 
2977  SendResponse(pbssock, outputlist);
2978  return;
2979  }
2980 
2981  DoHandleDeleteRecording(recinfo, pbs, forceMetadataDelete, false, forgetHistory);
2982 }
2983 
2985  bool forceMetadataDelete)
2986 {
2987  QStringList::const_iterator it = slist.cbegin() + 1;
2988  RecordingInfo recinfo(it, slist.cend());
2989 
2990  if (!recinfo.GetRecordingID())
2991  {
2992  qDebug() << "HandleDeleteRecording(QStringList) Empty Recording ID";
2993  }
2994 
2995  if (recinfo.GetChanID()) // !recinfo.GetRecordingID()
2996  DoHandleDeleteRecording(recinfo, pbs, forceMetadataDelete, false, false);
2997 }
2998 
3000  RecordingInfo &recinfo, PlaybackSock *pbs,
3001  bool forceMetadataDelete, bool lexpirer, bool forgetHistory)
3002 {
3003  int resultCode = -1;
3004  MythSocket *pbssock = nullptr;
3005  if (pbs)
3006  pbssock = pbs->getSocket();
3007 
3008  bool justexpire = lexpirer ? false :
3009  ( //gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete") &&
3010  (recinfo.GetRecordingGroup() != "Deleted") &&
3011  (recinfo.GetRecordingGroup() != "LiveTV"));
3012 
3013  QString filename = GetPlaybackURL(&recinfo, false);
3014  if (filename.isEmpty())
3015  {
3016  LOG(VB_GENERAL, LOG_ERR, LOC +
3017  QString("ERROR when trying to delete file for %1. Unable "
3018  "to determine filename of recording.")
3019  .arg(recinfo.toString(ProgramInfo::kRecordingKey)));
3020 
3021  if (pbssock)
3022  {
3023  resultCode = -2;
3024  QStringList outputlist(QString::number(resultCode));
3025  SendResponse(pbssock, outputlist);
3026  }
3027 
3028  return;
3029  }
3030 
3031  // Stop the recording if it's still in progress.
3032  DoHandleStopRecording(recinfo, nullptr);
3033 
3034  if (justexpire && !forceMetadataDelete &&
3035  recinfo.GetFilesize() > (1LL * 1024 * 1024) )
3036  {
3037  recinfo.ApplyRecordRecGroupChange("Deleted");
3038  recinfo.SaveAutoExpire(kDeletedAutoExpire, true);
3039  if (forgetHistory)
3040  recinfo.ForgetHistory();
3041  else if (m_sched)
3042  m_sched->RescheduleCheck(recinfo, "DoHandleDelete1");
3043  QStringList outputlist( QString::number(0) );
3044  SendResponse(pbssock, outputlist);
3045  return;
3046  }
3047 
3048  // If this recording was made by a another recorder, and that
3049  // recorder is available, tell it to do the deletion.
3050  if (m_ismaster && recinfo.GetHostname() != gCoreContext->GetHostName())
3051  {
3052  PlaybackSock *slave = GetSlaveByHostname(recinfo.GetHostname());
3053 
3054  if (slave)
3055  {
3056  int num = slave->DeleteRecording(&recinfo, forceMetadataDelete);
3057 
3058  if (forgetHistory)
3059  recinfo.ForgetHistory();
3060  else if (m_sched &&
3061  recinfo.GetRecordingGroup() != "Deleted" &&
3062  recinfo.GetRecordingGroup() != "LiveTV")
3063  m_sched->RescheduleCheck(recinfo, "DoHandleDelete2");
3064 
3065  if (pbssock)
3066  {
3067  QStringList outputlist( QString::number(num) );
3068  SendResponse(pbssock, outputlist);
3069  }
3070 
3071  slave->DecrRef();
3072  return;
3073  }
3074  }
3075 
3076  QFile checkFile(filename);
3077  bool fileExists = checkFile.exists();
3078  if (!fileExists)
3079  {
3080  QFile checkFileUTF8(QString::fromUtf8(filename.toLatin1().constData()));
3081  fileExists = checkFileUTF8.exists();
3082  if (fileExists)
3083  filename = QString::fromUtf8(filename.toLatin1().constData());
3084  }
3085 
3086  // Allow deleting of files where the recording failed meaning size == 0
3087  // But do not allow deleting of files that appear to be completely absent.
3088  // The latter condition indicates the filesystem containing the file is
3089  // most likely absent and deleting the file metadata is unsafe.
3090  if (fileExists || !recinfo.GetFilesize() || forceMetadataDelete)
3091  {
3092  recinfo.SaveDeletePendingFlag(true);
3093 
3094  if (!recinfo.GetRecordingID())
3095  {
3096  qDebug() << "DoHandleDeleteRecording() Empty Recording ID";
3097  }
3098 
3099  auto *deleteThread = new DeleteThread(this, filename,
3100  recinfo.GetTitle(), recinfo.GetChanID(),
3101  recinfo.GetRecordingStartTime(), recinfo.GetRecordingEndTime(),
3102  recinfo.GetRecordingID(),
3103  forceMetadataDelete);
3104  deleteThread->start();
3105  }
3106  else
3107  {
3108 #if 0
3109  QString logInfo = QString("chanid %1")
3110  .arg(recinfo.toString(ProgramInfo::kRecordingKey));
3111 #endif
3112 
3113  LOG(VB_GENERAL, LOG_ERR, LOC +
3114  QString("ERROR when trying to delete file: %1. File doesn't "
3115  "exist. Database metadata will not be removed.")
3116  .arg(filename));
3117  resultCode = -2;
3118  }
3119 
3120  if (pbssock)
3121  {
3122  QStringList outputlist( QString::number(resultCode) );
3123  SendResponse(pbssock, outputlist);
3124  }
3125 
3126  if (forgetHistory)
3127  recinfo.ForgetHistory();
3128  else if (m_sched &&
3129  recinfo.GetRecordingGroup() != "Deleted" &&
3130  recinfo.GetRecordingGroup() != "LiveTV")
3131  m_sched->RescheduleCheck(recinfo, "DoHandleDelete3");
3132 
3133  // Tell MythTV frontends that the recording list needs to be updated.
3134  if (fileExists || !recinfo.GetFilesize() || forceMetadataDelete)
3135  {
3137  QString("REC_DELETED CHANID %1 STARTTIME %2")
3138  .arg(recinfo.GetChanID())
3139  .arg(recinfo.GetRecordingStartTime(MythDate::ISODate)));
3140 
3141  recinfo.SendDeletedEvent();
3142  }
3143 }
3144 
3146 {
3147  if (slist.size() == 3)
3148  {
3149  RecordingInfo recinfo(
3150  slist[1].toUInt(), MythDate::fromString(slist[2]));
3151  if (recinfo.GetChanID())
3152  DoHandleUndeleteRecording(recinfo, pbs);
3153  }
3154  else if (slist.size() >= (1 + NUMPROGRAMLINES))
3155  {
3156  QStringList::const_iterator it = slist.cbegin()+1;
3157  RecordingInfo recinfo(it, slist.cend());
3158  if (recinfo.GetChanID())
3159  DoHandleUndeleteRecording(recinfo, pbs);
3160  }
3161 }
3162 
3164  RecordingInfo &recinfo, PlaybackSock *pbs)
3165 {
3166  int ret = -1;
3167 
3168  MythSocket *pbssock = nullptr;
3169  if (pbs)
3170  pbssock = pbs->getSocket();
3171 
3172 #if 0
3173  if (gCoreContext->GetNumSetting("AutoExpireInsteadOfDelete", 0))
3174 #endif
3175  {
3176  recinfo.ApplyRecordRecGroupChange("Default");
3177  recinfo.UpdateLastDelete(false);
3179  if (m_sched)
3180  m_sched->RescheduleCheck(recinfo, "DoHandleUndelete");
3181  ret = 0;
3182  }
3183 
3184  QStringList outputlist( QString::number(ret) );
3185  SendResponse(pbssock, outputlist);
3186 }
3187 
3214 void MainServer::HandleRescheduleRecordings(const QStringList &request,
3215  PlaybackSock *pbs)
3216 {
3217  QStringList result;
3218  if (m_sched)
3219  {
3220  m_sched->Reschedule(request);
3221  result = QStringList(QString::number(1));
3222  }
3223  else
3224  result = QStringList(QString::number(0));
3225 
3226  if (pbs)
3227  {
3228  MythSocket *pbssock = pbs->getSocket();
3229  if (pbssock)
3230  SendResponse(pbssock, result);
3231  }
3232 }
3233 
3235 {
3236  // If we're already trying to add a child input, ignore this
3237  // attempt. The scheduler will keep asking until it gets added.
3238  // This makes the whole operation asynchronous and allows the
3239  // scheduler to continue servicing other recordings.
3240  if (!m_addChildInputLock.tryLock())
3241  {
3242  LOG(VB_GENERAL, LOG_INFO, LOC + "HandleAddChildInput: Already locked");
3243  return false;
3244  }
3245 
3246  LOG(VB_GENERAL, LOG_INFO, LOC +
3247  QString("HandleAddChildInput: Handling input %1").arg(inputid));
3248 
3249  TVRec::s_inputsLock.lockForWrite();
3250 
3251  if (m_ismaster)
3252  {
3253  // First, add the new input to the database.
3254  uint childid = CardUtil::AddChildInput(inputid);
3255  if (!childid)
3256  {
3257  LOG(VB_GENERAL, LOG_ERR, LOC +
3258  QString("HandleAddChildInput: "
3259  "Failed to add child to input %1").arg(inputid));
3260  TVRec::s_inputsLock.unlock();
3261  m_addChildInputLock.unlock();
3262  return false;
3263  }
3264 
3265  LOG(VB_GENERAL, LOG_INFO, LOC +
3266  QString("HandleAddChildInput: Added child input %1").arg(childid));
3267 
3268  // Next, create the master TVRec and/or EncoderLink.
3269  QString localhostname = gCoreContext->GetHostName();
3270  QString hostname = CardUtil::GetHostname(childid);
3271 
3272  if (hostname == localhostname)
3273  {
3274  auto *tv = new TVRec(childid);
3275  if (!tv || !tv->Init())
3276  {
3277  LOG(VB_GENERAL, LOG_ERR, LOC +
3278  QString("HandleAddChildInput: "
3279  "Failed to initialize input %1").arg(childid));
3280  delete tv;
3281  CardUtil::DeleteInput(childid);
3282  TVRec::s_inputsLock.unlock();
3283  m_addChildInputLock.unlock();
3284  return false;
3285  }
3286 
3287  auto *enc = new EncoderLink(childid, tv);
3288  (*m_encoderList)[childid] = enc;
3289  }
3290  else
3291  {
3292  EncoderLink *enc = (*m_encoderList)[inputid];
3293  if (!enc->AddChildInput(childid))
3294  {
3295  LOG(VB_GENERAL, LOG_ERR, LOC +
3296  QString("HandleAddChildInput: "
3297  "Failed to add remote input %1").arg(childid));
3298  CardUtil::DeleteInput(childid);
3299  TVRec::s_inputsLock.unlock();
3300  m_addChildInputLock.unlock();
3301  return false;
3302  }
3303 
3304  PlaybackSock *pbs = enc->GetSocket();
3305  enc = new EncoderLink(childid, nullptr, hostname);
3306  enc->SetSocket(pbs);
3307  (*m_encoderList)[childid] = enc;
3308  }
3309 
3310  // Finally, add the new input to the Scheduler.
3311  m_sched->AddChildInput(inputid, childid);
3312  }
3313  else
3314  {
3315  // Create the slave TVRec and EncoderLink.
3316  auto *tv = new TVRec(inputid);
3317  if (!tv || !tv->Init())
3318  {
3319  LOG(VB_GENERAL, LOG_ERR, LOC +
3320  QString("HandleAddChildInput: "
3321  "Failed to initialize input %1").arg(inputid));
3322  delete tv;
3323  TVRec::s_inputsLock.unlock();
3324  m_addChildInputLock.unlock();
3325  return false;
3326  }
3327 
3328  auto *enc = new EncoderLink(inputid, tv);
3329  (*m_encoderList)[inputid] = enc;
3330  }
3331 
3332  TVRec::s_inputsLock.unlock();
3333  m_addChildInputLock.unlock();
3334 
3335  LOG(VB_GENERAL, LOG_INFO, LOC +
3336  QString("HandleAddChildInput: "
3337  "Successfully handled input %1").arg(inputid));
3338 
3339  return true;
3340 }
3341 
3343 {
3344  QStringList::const_iterator it = slist.cbegin() + 1;
3345  RecordingInfo recinfo(it, slist.cend());
3346  if (recinfo.GetChanID())
3347  recinfo.ForgetHistory();
3348 
3349  MythSocket *pbssock = nullptr;
3350  if (pbs)
3351  pbssock = pbs->getSocket();
3352  if (pbssock)
3353  {
3354  QStringList outputlist( QString::number(0) );
3355  SendResponse(pbssock, outputlist);
3356  }
3357 }
3358 
3365 {
3366  QStringList strlist;
3367 
3368  QString sleepCmd = gCoreContext->GetSetting("SleepCommand");
3369  if (!sleepCmd.isEmpty())
3370  {
3371  strlist << "OK";
3372  SendResponse(pbs->getSocket(), strlist);
3373  LOG(VB_GENERAL, LOG_NOTICE, LOC +
3374  "Received GO_TO_SLEEP command from master, running SleepCommand.");
3375  myth_system(sleepCmd);
3376  }
3377  else
3378  {
3379  strlist << "ERROR: SleepCommand is empty";
3380  LOG(VB_GENERAL, LOG_ERR, LOC +
3381  "ERROR: in HandleGoToSleep(), but no SleepCommand found!");
3382  SendResponse(pbs->getSocket(), strlist);
3383  }
3384 }
3385 
3396 {
3397  QStringList strlist;
3398 
3399  if (allHosts)
3400  {
3401  QMutexLocker locker(&m_masterFreeSpaceListLock);
3402  strlist = m_masterFreeSpaceList;
3405  {
3407  {
3409  m_masterFreeSpaceListWait.wait(locker.mutex());
3410  }
3413  m_masterFreeSpaceListUpdater, "FreeSpaceUpdater");
3414  }
3415  }
3416  else
3417  BackendQueryDiskSpace(strlist, allHosts, allHosts);
3418 
3419  SendResponse(pbs->getSocket(), strlist);
3420 }
3421 
3428 {
3429  QStringList strlist;
3430  {
3431  QMutexLocker locker(&m_masterFreeSpaceListLock);
3432  strlist = m_masterFreeSpaceList;
3435  {
3437  {
3439  m_masterFreeSpaceListWait.wait(locker.mutex());
3440  }
3443  m_masterFreeSpaceListUpdater, "FreeSpaceUpdater");
3444  }
3445  }
3446 
3447  // The TotalKB and UsedKB are the last two numbers encoded in the list
3448  QStringList shortlist;
3449  if (strlist.size() < 4)
3450  {
3451  shortlist << QString("0");
3452  shortlist << QString("0");
3453  }
3454  else
3455  {
3456  unsigned int index = (uint)(strlist.size()) - 2;
3457  shortlist << strlist[index++];
3458  shortlist << strlist[index++];
3459  }
3460 
3461  SendResponse(pbs->getSocket(), shortlist);
3462 }
3463 
3471 {
3472  MythSocket *pbssock = pbs->getSocket();
3473 
3474  QStringList strlist;
3475 
3476 #if defined(_WIN32) || defined(Q_OS_ANDROID)
3477  strlist << "0" << "0" << "0";
3478 #else
3479  loadArray loads = getLoadAvgs();
3480  if (loads[0] == -1)
3481  {
3482  strlist << "ERROR";
3483  strlist << "getloadavg() failed";
3484  }
3485  else
3486  {
3487  strlist << QString::number(loads[0])
3488  << QString::number(loads[1])
3489  << QString::number(loads[2]);
3490  }
3491 #endif
3492 
3493  SendResponse(pbssock, strlist);
3494 }
3495 
3502 {
3503  MythSocket *pbssock = pbs->getSocket();
3504  QStringList strlist;
3505  std::chrono::seconds uptime = 0s;
3506 
3507  if (getUptime(uptime))
3508  strlist << QString::number(uptime.count());
3509  else
3510  {
3511  strlist << "ERROR";
3512  strlist << "Could not determine uptime.";
3513  }
3514 
3515  SendResponse(pbssock, strlist);
3516 }
3517 
3524 {
3525  MythSocket *pbssock = pbs->getSocket();
3526  QStringList strlist;
3527 
3528  strlist << gCoreContext->GetHostName();
3529 
3530  SendResponse(pbssock, strlist);
3531 }
3532 
3539 {
3540  MythSocket *pbssock = pbs->getSocket();
3541  QStringList strlist;
3542  int totalMB = 0;
3543  int freeMB = 0;
3544  int totalVM = 0;
3545  int freeVM = 0;
3546 
3547  if (getMemStats(totalMB, freeMB, totalVM, freeVM))
3548  {
3549  strlist << QString::number(totalMB) << QString::number(freeMB)
3550  << QString::number(totalVM) << QString::number(freeVM);
3551  }
3552  else
3553  {
3554  strlist << "ERROR";
3555  strlist << "Could not determine memory stats.";
3556  }
3557 
3558  SendResponse(pbssock, strlist);
3559 }
3560 
3567 {
3568  MythSocket *pbssock = pbs->getSocket();
3569  QStringList strlist;
3570  strlist << MythTZ::getTimeZoneID()
3571  << QString::number(MythTZ::calc_utc_offset())
3573 
3574  SendResponse(pbssock, strlist);
3575 }
3576 
3582 {
3583  MythSocket *pbssock = pbs->getSocket();
3584  bool checkSlaves = slist[1].toInt() != 0;
3585 
3586  QStringList::const_iterator it = slist.cbegin() + 2;
3587  RecordingInfo recinfo(it, slist.cend());
3588 
3589  bool exists = false;
3590 
3591  if (recinfo.HasPathname() && (m_ismaster) &&
3592  (recinfo.GetHostname() != gCoreContext->GetHostName()) &&
3593  (checkSlaves))
3594  {
3596 
3597  if (slave)
3598  {
3599  exists = slave->CheckFile(&recinfo);
3600  slave->DecrRef();
3601 
3602  QStringList outputlist( QString::number(static_cast<int>(exists)) );
3603  if (exists)
3604  outputlist << recinfo.GetPathname();
3605  else
3606  outputlist << "";
3607 
3608  SendResponse(pbssock, outputlist);
3609  return;
3610  }
3611  }
3612 
3613  QString pburl;
3614  if (recinfo.HasPathname())
3615  {
3616  pburl = GetPlaybackURL(&recinfo);
3617  exists = QFileInfo::exists(pburl);
3618  if (!exists)
3619  pburl.clear();
3620  }
3621 
3622  QStringList strlist( QString::number(static_cast<int>(exists)) );
3623  strlist << pburl;
3624  SendResponse(pbssock, strlist);
3625 }
3626 
3627 
3633 {
3634  QString storageGroup = "Default";
3635  QString hostname = gCoreContext->GetHostName();
3636  QString filename = "";
3637  QStringList res;
3638 
3639  switch (slist.size()) {
3640  case 4:
3641  if (!slist[3].isEmpty())
3642  hostname = slist[3];
3643  [[fallthrough]];
3644  case 3:
3645  if (slist[2].isEmpty())
3646  storageGroup = slist[2];
3647  [[fallthrough]];
3648  case 2:
3649  filename = slist[1];
3650  if (filename.isEmpty() ||
3651  filename.contains("/../") ||
3652  filename.startsWith("../"))
3653  {
3654  LOG(VB_GENERAL, LOG_ERR, LOC +
3655  QString("ERROR checking for file, filename '%1' "
3656  "fails sanity checks").arg(filename));
3657  res << "";
3658  SendResponse(pbs->getSocket(), res);
3659  return;
3660  }
3661  break;
3662  default:
3663  LOG(VB_GENERAL, LOG_ERR, LOC +
3664  "ERROR, invalid input count for QUERY_FILE_HASH");
3665  res << "";
3666  SendResponse(pbs->getSocket(), res);
3667  return;
3668  }
3669 
3670  QString hash = "";
3671 
3673  {
3674  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
3675  QString fullname = sgroup.FindFile(filename);
3676  hash = FileHash(fullname);
3677  }
3678  else
3679  {
3681  if (slave)
3682  {
3683  hash = slave->GetFileHash(filename, storageGroup);
3684  slave->DecrRef();
3685  }
3686  // I deleted the incorrect SQL select that was supposed to get
3687  // host name from ip address. Since it cannot work and has
3688  // been there 6 years I assume it is not important.
3689  }
3690 
3691  res << hash;
3692  SendResponse(pbs->getSocket(), res);
3693 }
3694 
3700 {
3701  QString filename = slist[1];
3702  QString storageGroup = "Default";
3703  QStringList retlist;
3704 
3705  if (slist.size() > 2)
3706  storageGroup = slist[2];
3707 
3708  if ((filename.isEmpty()) ||
3709  (filename.contains("/../")) ||
3710  (filename.startsWith("../")))
3711  {
3712  LOG(VB_GENERAL, LOG_ERR, LOC +
3713  QString("ERROR checking for file, filename '%1' "
3714  "fails sanity checks").arg(filename));
3715  retlist << "0";
3716  SendResponse(pbs->getSocket(), retlist);
3717  return;
3718  }
3719 
3720  if (storageGroup.isEmpty())
3721  storageGroup = "Default";
3722 
3723  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
3724 
3725  QString fullname = sgroup.FindFile(filename);
3726 
3727  if (!fullname.isEmpty())
3728  {
3729  retlist << "1";
3730  retlist << fullname;
3731 
3732  struct stat fileinfo {};
3733  if (stat(fullname.toLocal8Bit().constData(), &fileinfo) >= 0)
3734  {
3735  retlist << QString::number(fileinfo.st_dev);
3736  retlist << QString::number(fileinfo.st_ino);
3737  retlist << QString::number(fileinfo.st_mode);
3738  retlist << QString::number(fileinfo.st_nlink);
3739  retlist << QString::number(fileinfo.st_uid);
3740  retlist << QString::number(fileinfo.st_gid);
3741  retlist << QString::number(fileinfo.st_rdev);
3742  retlist << QString::number(fileinfo.st_size);
3743 #ifdef _WIN32
3744  retlist << "0"; // st_blksize
3745  retlist << "0"; // st_blocks
3746 #else
3747  retlist << QString::number(fileinfo.st_blksize);
3748  retlist << QString::number(fileinfo.st_blocks);
3749 #endif
3750  retlist << QString::number(fileinfo.st_atime);
3751  retlist << QString::number(fileinfo.st_mtime);
3752  retlist << QString::number(fileinfo.st_ctime);
3753  }
3754  }
3755  else
3756  retlist << "0";
3757 
3758  SendResponse(pbs->getSocket(), retlist);
3759 }
3760 
3761 void MainServer::getGuideDataThrough(QDateTime &GuideDataThrough)
3762 {
3763  MSqlQuery query(MSqlQuery::InitCon());
3764  query.prepare("SELECT MAX(endtime) FROM program WHERE manualid = 0;");
3765 
3766  if (query.exec() && query.next())
3767  {
3768  GuideDataThrough = MythDate::fromString(query.value(0).toString());
3769  }
3770 }
3771 
3773 {
3774  QDateTime GuideDataThrough;
3775  MythSocket *pbssock = pbs->getSocket();
3776  QStringList strlist;
3777 
3778  getGuideDataThrough(GuideDataThrough);
3779 
3780  if (GuideDataThrough.isNull())
3781  strlist << QString("0000-00-00 00:00");
3782  else
3783  strlist << GuideDataThrough.toString("yyyy-MM-dd hh:mm");
3784 
3785  SendResponse(pbssock, strlist);
3786 }
3787 
3789  const QString& tmptable, int recordid)
3790 {
3791  MythSocket *pbssock = pbs->getSocket();
3792 
3793  QStringList strList;
3794 
3795  if (m_sched)
3796  {
3797  if (tmptable.isEmpty())
3798  m_sched->GetAllPending(strList);
3799  else
3800  {
3801  auto *sched = new Scheduler(false, m_encoderList, tmptable, m_sched);
3802  sched->FillRecordListFromDB(recordid);
3803  sched->GetAllPending(strList);
3804  delete sched;
3805 
3806  if (recordid > 0)
3807  {
3808  MSqlQuery query(MSqlQuery::InitCon());
3809  query.prepare("SELECT NULL FROM record "
3810  "WHERE recordid = :RECID;");
3811  query.bindValue(":RECID", recordid);
3812 
3813  if (query.exec() && query.size())
3814  {
3815  auto *record = new RecordingRule();
3816  record->m_recordID = recordid;
3817  if (record->Load() &&
3818  record->m_searchType == kManualSearch)
3819  m_sched->RescheduleMatch(recordid, 0, 0, QDateTime(),
3820  "Speculation");
3821  delete record;
3822  }
3823  query.prepare("DELETE FROM program WHERE manualid = :RECID;");
3824  query.bindValue(":RECID", recordid);
3825  if (!query.exec())
3826  MythDB::DBError("MainServer::HandleGetPendingRecordings "
3827  "- delete", query);
3828  }
3829  }
3830  }
3831  else
3832  {
3833  strList << QString::number(0);
3834  strList << QString::number(0);
3835  }
3836 
3837  SendResponse(pbssock, strList);
3838 }
3839 
3841 {
3842  MythSocket *pbssock = pbs->getSocket();
3843 
3844  QStringList strList;
3845 
3846  if (m_sched)
3847  Scheduler::GetAllScheduled(strList);
3848  else
3849  strList << QString::number(0);
3850 
3851  SendResponse(pbssock, strList);
3852 }
3853 
3855  PlaybackSock *pbs)
3856 {
3857  MythSocket *pbssock = pbs->getSocket();
3858 
3859  QStringList::const_iterator it = slist.cbegin() + 1;
3860  RecordingInfo recinfo(it, slist.cend());
3861 
3862  QStringList strlist;
3863 
3864  if (m_sched && recinfo.GetChanID())
3865  m_sched->getConflicting(&recinfo, strlist);
3866  else
3867  strlist << QString::number(0);
3868 
3869  SendResponse(pbssock, strlist);
3870 }
3871 
3873 {
3874  MythSocket *pbssock = pbs->getSocket();
3875 
3876  QStringList strList;
3877 
3878  if (m_expirer)
3879  m_expirer->GetAllExpiring(strList);
3880  else
3881  strList << QString::number(0);
3882 
3883  SendResponse(pbssock, strList);
3884 }
3885 
3886 void MainServer::HandleSGGetFileList(QStringList &sList,
3887  PlaybackSock *pbs)
3888 {
3889  MythSocket *pbssock = pbs->getSocket();
3890  QStringList strList;
3891 
3892  if ((sList.size() < 4) || (sList.size() > 5))
3893  {
3894  LOG(VB_GENERAL, LOG_ERR, LOC +
3895  QString("HandleSGGetFileList: Invalid Request. %1")
3896  .arg(sList.join("[]:[]")));
3897  strList << "EMPTY LIST";
3898  SendResponse(pbssock, strList);
3899  return;
3900  }
3901 
3902  QString host = gCoreContext->GetHostName();
3903  const QString& wantHost = sList.at(1);
3904  QHostAddress wantHostaddr(wantHost);
3905  const QString& groupname = sList.at(2);
3906  const QString& path = sList.at(3);
3907  bool fileNamesOnly = false;
3908 
3909  if (sList.size() >= 5)
3910  fileNamesOnly = (sList.at(4).toInt() != 0);
3911 
3912  bool slaveUnreachable = false;
3913 
3914  LOG(VB_FILE, LOG_INFO, LOC +
3915  QString("HandleSGGetFileList: group = %1 host = %2 "
3916  " path = %3 wanthost = %4")
3917  .arg(groupname, host, path, wantHost));
3918 
3919  QString addr = gCoreContext->GetBackendServerIP();
3920 
3921  if ((host.toLower() == wantHost.toLower()) ||
3922  (!addr.isEmpty() && addr == wantHostaddr.toString()))
3923  {
3924  StorageGroup sg(groupname, host);
3925  LOG(VB_FILE, LOG_INFO, LOC + "HandleSGGetFileList: Getting local info");
3926  if (fileNamesOnly)
3927  strList = sg.GetFileList(path);
3928  else
3929  strList = sg.GetFileInfoList(path);
3930  }
3931  else
3932  {
3933  PlaybackSock *slave = GetMediaServerByHostname(wantHost);
3934  if (slave)
3935  {
3936  LOG(VB_FILE, LOG_INFO, LOC +
3937  "HandleSGGetFileList: Getting remote info");
3938  strList = slave->GetSGFileList(wantHost, groupname, path,
3939  fileNamesOnly);
3940  slave->DecrRef();
3941  slaveUnreachable = false;
3942  }
3943  else
3944  {
3945  LOG(VB_FILE, LOG_INFO, LOC +
3946  QString("HandleSGGetFileList: Failed to grab slave socket "
3947  ": %1 :").arg(wantHost));
3948  slaveUnreachable = true;
3949  }
3950 
3951  }
3952 
3953  if (slaveUnreachable)
3954  strList << "SLAVE UNREACHABLE: " << host;
3955 
3956  if (strList.isEmpty() || (strList.at(0) == "0"))
3957  strList << "EMPTY LIST";
3958 
3959  SendResponse(pbssock, strList);
3960 }
3961 
3963 {
3964 //format: QUERY_FINDFILE <host> <storagegroup> <filename> <useregex (optional)> <allowfallback (optional)>
3965 
3966  QString hostname = slist[1];
3967  QString storageGroup = slist[2];
3968  QString filename = slist[3];
3969  bool allowFallback = true;
3970  bool useRegex = false;
3971  QStringList fileList;
3972 
3973  if (!QHostAddress(hostname).isNull())
3974  {
3975  LOG(VB_GENERAL, LOG_ERR, QString("Mainserver: QUERY_FINDFILE called "
3976  "with IP (%1) instead of hostname. "
3977  "This is invalid.").arg(hostname));
3978  }
3979 
3980  if (hostname.isEmpty())
3982 
3983  if (storageGroup.isEmpty())
3984  storageGroup = "Default";
3985 
3986  if (filename.isEmpty() || filename.contains("/../") ||
3987  filename.startsWith("../"))
3988  {
3989  LOG(VB_GENERAL, LOG_ERR, LOC +
3990  QString("ERROR QueryFindFile, filename '%1' "
3991  "fails sanity checks").arg(filename));
3992  fileList << "ERROR: Bad/Missing Filename";
3993  SendResponse(pbs->getSocket(), fileList);
3994  return;
3995  }
3996 
3997  if (slist.size() >= 5)
3998  useRegex = (slist[4].toInt() > 0);
3999 
4000  if (slist.size() >= 6)
4001  allowFallback = (slist[5].toInt() > 0);
4002 
4003  LOG(VB_FILE, LOG_INFO, LOC +
4004  QString("Looking for file '%1' on host '%2' in group '%3' (useregex: %4, allowfallback: %5")
4005  .arg(filename, hostname, storageGroup).arg(useRegex).arg(allowFallback));
4006 
4007  // first check the given host
4009  {
4010  LOG(VB_FILE, LOG_INFO, LOC + QString("Checking local host '%1' for file").arg(gCoreContext->GetHostName()));
4011 
4012  // check the local storage group
4013  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false);
4014 
4015  if (useRegex)
4016  {
4017  QFileInfo fi(filename);
4018  QStringList files = sgroup.GetFileList('/' + fi.path());
4019 
4020  LOG(VB_FILE, LOG_INFO, LOC + QString("Looking in dir '%1' for '%2'")
4021  .arg(fi.path(), fi.fileName()));
4022 
4023  for (int x = 0; x < files.size(); x++)
4024  {
4025  LOG(VB_FILE, LOG_INFO, LOC + QString("Found '%1 - %2'").arg(x).arg(files[x]));
4026  }
4027 
4028  QStringList filteredFiles = files.filter(QRegularExpression(fi.fileName()));
4029  for (const QString& file : std::as_const(filteredFiles))
4030  {
4033  fi.path() + '/' + file,
4034  storageGroup);
4035  }
4036  }
4037  else
4038  {
4039  if (!sgroup.FindFile(filename).isEmpty())
4040  {
4043  filename, storageGroup);
4044  }
4045  }
4046  }
4047  else
4048  {
4049  LOG(VB_FILE, LOG_INFO, LOC + QString("Checking remote host '%1' for file").arg(hostname));
4050 
4051  // check the given slave hostname
4053  if (slave)
4054  {
4055  QStringList slaveFiles = slave->GetFindFile(hostname, filename, storageGroup, useRegex);
4056 
4057  if (!slaveFiles.isEmpty() && slaveFiles[0] != "NOT FOUND" && !slaveFiles[0].startsWith("ERROR: "))
4058  fileList += slaveFiles;
4059 
4060  slave->DecrRef();
4061  }
4062  else
4063  {
4064  LOG(VB_FILE, LOG_INFO, LOC + QString("Slave '%1' was unreachable").arg(hostname));
4065  fileList << QString("ERROR: SLAVE UNREACHABLE: %1").arg(hostname);
4066  SendResponse(pbs->getSocket(), fileList);
4067  return;
4068  }
4069  }
4070 
4071  // if we still haven't found it and this is the master and fallback is enabled
4072  // check all other slaves that have a directory in the storagegroup
4073  if (m_ismaster && fileList.isEmpty() && allowFallback)
4074  {
4075  // get a list of hosts
4076  MSqlQuery query(MSqlQuery::InitCon());
4077 
4078  QString sql = "SELECT DISTINCT hostname "
4079  "FROM storagegroup "
4080  "WHERE groupname = :GROUP "
4081  "AND hostname != :HOSTNAME";
4082  query.prepare(sql);
4083  query.bindValue(":GROUP", storageGroup);
4084  query.bindValue(":HOSTNAME", hostname);
4085 
4086  if (!query.exec() || !query.isActive())
4087  {
4088  MythDB::DBError(LOC + "FindFile() get host list", query);
4089  fileList << "ERROR: failed to get host list";
4090  SendResponse(pbs->getSocket(), fileList);
4091  return;
4092  }
4093 
4094  while(query.next())
4095  {
4096  hostname = query.value(0).toString();
4097 
4099  {
4100  StorageGroup sgroup(storageGroup, hostname);
4101 
4102  if (useRegex)
4103  {
4104  QFileInfo fi(filename);
4105  QStringList files = sgroup.GetFileList('/' + fi.path());
4106 
4107  LOG(VB_FILE, LOG_INFO, LOC + QString("Looking in dir '%1' for '%2'")
4108  .arg(fi.path(), fi.fileName()));
4109 
4110  for (int x = 0; x < files.size(); x++)
4111  {
4112  LOG(VB_FILE, LOG_INFO, LOC + QString("Found '%1 - %2'").arg(x).arg(files[x]));
4113  }
4114 
4115  QStringList filteredFiles = files.filter(QRegularExpression(fi.fileName()));
4116 
4117  for (const QString& file : std::as_const(filteredFiles))
4118  {
4121  fi.path() + '/' + file,
4122  storageGroup);
4123  }
4124  }
4125  else
4126  {
4127  QString fname = sgroup.FindFile(filename);
4128  if (!fname.isEmpty())
4129  {
4132  filename, storageGroup);
4133  }
4134  }
4135  }
4136  else
4137  {
4138  // check the slave host
4140  if (slave)
4141  {
4142  QStringList slaveFiles = slave->GetFindFile(hostname, filename, storageGroup, useRegex);
4143  if (!slaveFiles.isEmpty() && slaveFiles[0] != "NOT FOUND" && !slaveFiles[0].startsWith("ERROR: "))
4144  fileList += slaveFiles;
4145 
4146  slave->DecrRef();
4147  }
4148  }
4149 
4150  if (!fileList.isEmpty())
4151  break;
4152  }
4153  }
4154 
4155  if (fileList.isEmpty())
4156  {
4157  fileList << "NOT FOUND";
4158  LOG(VB_FILE, LOG_INFO, LOC + QString("File was not found"));
4159  }
4160  else
4161  {
4162  for (int x = 0; x < fileList.size(); x++)
4163  {
4164  LOG(VB_FILE, LOG_INFO, LOC + QString("File %1 was found at: '%2'").arg(x).arg(fileList[0]));
4165  }
4166  }
4167 
4168  SendResponse(pbs->getSocket(), fileList);
4169 }
4170 
4171 void MainServer::HandleSGFileQuery(QStringList &sList,
4172  PlaybackSock *pbs)
4173 {
4174 //format: QUERY_SG_FILEQUERY <host> <storagegroup> <filename> <allowfallback (optional)>
4175 
4176  MythSocket *pbssock = pbs->getSocket();
4177  QStringList strList;
4178 
4179  if (sList.size() < 4)
4180  {
4181  LOG(VB_GENERAL, LOG_ERR, LOC +
4182  QString("HandleSGFileQuery: Invalid Request. %1")
4183  .arg(sList.join("[]:[]")));
4184  strList << "EMPTY LIST";
4185  SendResponse(pbssock, strList);
4186  return;
4187  }
4188 
4189  QString host = gCoreContext->GetHostName();
4190  const QString& wantHost = sList.at(1);
4191  QHostAddress wantHostaddr(wantHost);
4192  const QString& groupname = sList.at(2);
4193  const QString& filename = sList.at(3);
4194 
4195  bool allowFallback = true;
4196  if (sList.size() >= 5)
4197  allowFallback = (sList.at(4).toInt() > 0);
4198  LOG(VB_FILE, LOG_ERR, QString("HandleSGFileQuery - allowFallback: %1").arg(allowFallback));
4199 
4200  bool slaveUnreachable = false;
4201 
4202  LOG(VB_FILE, LOG_INFO, LOC + QString("HandleSGFileQuery: %1")
4203  .arg(gCoreContext->GenMythURL(wantHost, 0, filename, groupname)));
4204 
4205  QString addr = gCoreContext->GetBackendServerIP();
4206 
4207  if ((host.toLower() == wantHost.toLower()) ||
4208  (!addr.isEmpty() && addr == wantHostaddr.toString()))
4209  {
4210  LOG(VB_FILE, LOG_INFO, LOC + "HandleSGFileQuery: Getting local info");
4211  StorageGroup sg(groupname, gCoreContext->GetHostName(), allowFallback);
4212  strList = sg.GetFileInfo(filename);
4213  }
4214  else
4215  {
4216  PlaybackSock *slave = GetMediaServerByHostname(wantHost);
4217  if (slave)
4218  {
4219  LOG(VB_FILE, LOG_INFO, LOC +
4220  "HandleSGFileQuery: Getting remote info");
4221  strList = slave->GetSGFileQuery(wantHost, groupname, filename);
4222  slave->DecrRef();
4223  slaveUnreachable = false;
4224  }
4225  else
4226  {
4227  LOG(VB_FILE, LOG_INFO, LOC +
4228  QString("HandleSGFileQuery: Failed to grab slave socket : %1 :")
4229  .arg(wantHost));
4230  slaveUnreachable = true;
4231  }
4232 
4233  }
4234 
4235  if (slaveUnreachable)
4236  strList << "SLAVE UNREACHABLE: " << wantHost;
4237 
4238  if (strList.count() == 0 || (strList.at(0) == "0"))
4239  strList << "EMPTY LIST";
4240 
4241  SendResponse(pbssock, strList);
4242 }
4243 
4245 {
4246  MythSocket *pbssock = pbs->getSocket();
4247  QString pbshost = pbs->getHostname();
4248 
4249  QStringList strlist;
4250 
4251  EncoderLink *encoder = nullptr;
4252  QString enchost;
4253 
4254  TVRec::s_inputsLock.lockForRead();
4255  for (auto * elink : std::as_const(*m_encoderList))
4256  {
4257  // we're looking for a specific card but this isn't the one we want
4258  if ((cardid != -1) && (cardid != elink->GetInputID()))
4259  continue;
4260 
4261  if (elink->IsLocal())
4262  enchost = gCoreContext->GetHostName();
4263  else
4264  enchost = elink->GetHostName();
4265 
4266  if ((enchost == pbshost) &&
4267  (elink->IsConnected()) &&
4268  (!elink->IsBusy()) &&
4269  (!elink->IsTunerLocked()))
4270  {
4271  encoder = elink;
4272  break;
4273  }
4274  }
4275  TVRec::s_inputsLock.unlock();
4276 
4277  if (encoder)
4278  {
4279  int retval = encoder->LockTuner();
4280 
4281  if (retval != -1)
4282  {
4283  QString msg = QString("Cardid %1 LOCKed for external use on %2.")
4284  .arg(retval).arg(pbshost);
4285  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
4286 
4287  MSqlQuery query(MSqlQuery::InitCon());
4288  query.prepare("SELECT videodevice, audiodevice, "
4289  "vbidevice "
4290  "FROM capturecard "
4291  "WHERE cardid = :CARDID ;");
4292  query.bindValue(":CARDID", retval);
4293 
4294  if (query.exec() && query.next())
4295  {
4296  // Success
4297  strlist << QString::number(retval)
4298  << query.value(0).toString()
4299  << query.value(1).toString()
4300  << query.value(2).toString();
4301 
4302  if (m_sched)
4303  m_sched->ReschedulePlace("LockTuner");
4304 
4305  SendResponse(pbssock, strlist);
4306  return;
4307  }
4308  LOG(VB_GENERAL, LOG_ERR, LOC +
4309  "MainServer::LockTuner(): Could not find "
4310  "card info in database");
4311  }
4312  else
4313  {
4314  // Tuner already locked
4315  strlist << "-2" << "" << "" << "";
4316  SendResponse(pbssock, strlist);
4317  return;
4318  }
4319  }
4320 
4321  strlist << "-1" << "" << "" << "";
4322  SendResponse(pbssock, strlist);
4323 }
4324 
4326 {
4327  MythSocket *pbssock = pbs->getSocket();
4328  QStringList strlist;
4329  EncoderLink *encoder = nullptr;
4330 
4331  TVRec::s_inputsLock.lockForRead();
4332  auto iter = m_encoderList->constFind(cardid);
4333  if (iter == m_encoderList->constEnd())
4334  {
4335  LOG(VB_GENERAL, LOG_ERR, LOC + "MainServer::HandleFreeTuner() " +
4336  QString("Unknown encoder: %1").arg(cardid));
4337  strlist << "FAILED";
4338  }
4339  else
4340  {
4341  encoder = *iter;
4342  encoder->FreeTuner();
4343 
4344  QString msg = QString("Cardid %1 FREED from external use on %2.")
4345  .arg(cardid).arg(pbs->getHostname());
4346  LOG(VB_GENERAL, LOG_INFO, LOC + msg);
4347 
4348  if (m_sched)
4349  m_sched->ReschedulePlace("FreeTuner");
4350 
4351  strlist << "OK";
4352  }
4353  TVRec::s_inputsLock.unlock();
4354 
4355  SendResponse(pbssock, strlist);
4356 }
4357 
4358 static bool comp_livetvorder(const InputInfo &a, const InputInfo &b)
4359 {
4360  if (a.m_liveTvOrder != b.m_liveTvOrder)
4361  return a.m_liveTvOrder < b.m_liveTvOrder;
4362  return a.m_inputId < b.m_inputId;
4363 }
4364 
4366  uint excluded_input)
4367 {
4368  LOG(VB_CHANNEL, LOG_INFO,
4369  LOC + QString("Excluding input %1")
4370  .arg(excluded_input));
4371 
4372  MythSocket *pbssock = pbs->getSocket();
4373  std::vector<InputInfo> busyinputs;
4374  std::vector<InputInfo> freeinputs;
4375  QMap<uint, QSet<uint> > groupids;
4376 
4377  // Loop over each encoder and divide the inputs into busy and free
4378  // lists.
4379  TVRec::s_inputsLock.lockForRead();
4380  for (auto * elink : std::as_const(*m_encoderList))
4381  {
4382  InputInfo info;
4383  info.m_inputId = elink->GetInputID();
4384 
4385  if (!elink->IsConnected() || elink->IsTunerLocked())
4386  {
4387  LOG(VB_CHANNEL, LOG_INFO,
4388  LOC + QString("Input %1 is locked or not connected")
4389  .arg(info.m_inputId));
4390  continue;
4391  }
4392 
4393  std::vector<uint> infogroups;
4394  CardUtil::GetInputInfo(info, &infogroups);
4395  for (uint group : infogroups)
4396  groupids[info.m_inputId].insert(group);
4397 
4398  InputInfo busyinfo;
4399  if (info.m_inputId != excluded_input && elink->IsBusy(&busyinfo))
4400  {
4401  LOG(VB_CHANNEL, LOG_DEBUG,
4402  LOC + QString("Input %1 is busy on %2/%3")
4403  .arg(info.m_inputId).arg(busyinfo.m_chanId).arg(busyinfo.m_mplexId));
4404  info.m_chanId = busyinfo.m_chanId;
4405  info.m_mplexId = busyinfo.m_mplexId;
4406  busyinputs.push_back(info);
4407  }
4408  else if (info.m_liveTvOrder)
4409  {
4410  LOG(VB_CHANNEL, LOG_DEBUG,
4411  LOC + QString("Input %1 is free")
4412  .arg(info.m_inputId));
4413  freeinputs.push_back(info);
4414  }
4415  }
4416  TVRec::s_inputsLock.unlock();
4417 
4418  // Loop over each busy input and restrict or delete any free
4419  // inputs that are in the same group.
4420  for (auto & busyinfo : busyinputs)
4421  {
4422  auto freeiter = freeinputs.begin();
4423  while (freeiter != freeinputs.end())
4424  {
4425  InputInfo &freeinfo = *freeiter;
4426 
4427  if ((groupids[busyinfo.m_inputId] & groupids[freeinfo.m_inputId])
4428  .isEmpty())
4429  {
4430  ++freeiter;
4431  continue;
4432  }
4433 
4434  if (busyinfo.m_sourceId == freeinfo.m_sourceId)
4435  {
4436  LOG(VB_CHANNEL, LOG_DEBUG,
4437  LOC + QString("Input %1 is limited to %2/%3 by input %4")
4438  .arg(freeinfo.m_inputId).arg(busyinfo.m_chanId)
4439  .arg(busyinfo.m_mplexId).arg(busyinfo.m_inputId));
4440  freeinfo.m_chanId = busyinfo.m_chanId;
4441  freeinfo.m_mplexId = busyinfo.m_mplexId;
4442  ++freeiter;
4443  continue;
4444  }
4445 
4446  LOG(VB_CHANNEL, LOG_DEBUG,
4447  LOC + QString("Input %1 is unavailable by input %2")
4448  .arg(freeinfo.m_inputId).arg(busyinfo.m_inputId));
4449  freeiter = freeinputs.erase(freeiter);
4450  }
4451  }
4452 
4453  // Return the results in livetvorder.
4454  stable_sort(freeinputs.begin(), freeinputs.end(), comp_livetvorder);
4455  QStringList strlist;
4456  for (auto & input : freeinputs)
4457  {
4458  LOG(VB_CHANNEL, LOG_INFO,
4459  LOC + QString("Input %1 is available on %2/%3")
4460  .arg(input.m_inputId).arg(input.m_chanId)
4461  .arg(input.m_mplexId));
4462  input.ToStringList(strlist);
4463  }
4464 
4465  if (strlist.empty())
4466  strlist << "OK";
4467 
4468  SendResponse(pbssock, strlist);
4469 }
4470 
4471 static QString cleanup(const QString &str)
4472 {
4473  if (str == " ")
4474  return "";
4475  return str;
4476 }
4477 
4478 static QString make_safe(const QString &str)
4479 {
4480  if (str.isEmpty())
4481  return " ";
4482  return str;
4483 }
4484 
4485 void MainServer::HandleRecorderQuery(QStringList &slist, QStringList &commands,
4486  PlaybackSock *pbs)
4487 {
4488  MythSocket *pbssock = pbs->getSocket();
4489 
4490  if (commands.size() < 2 || slist.size() < 2)
4491  return;
4492 
4493  int recnum = commands[1].toInt();
4494 
4495  TVRec::s_inputsLock.lockForRead();
4496  auto iter = m_encoderList->constFind(recnum);
4497  if (iter == m_encoderList->constEnd())
4498  {
4499  TVRec::s_inputsLock.unlock();
4500  LOG(VB_GENERAL, LOG_ERR, LOC + "MainServer::HandleRecorderQuery() " +
4501  QString("Unknown encoder: %1").arg(recnum));
4502  QStringList retlist( "bad" );
4503  SendResponse(pbssock, retlist);
4504  return;
4505  }
4506  TVRec::s_inputsLock.unlock();
4507 
4508  QString command = slist[1];
4509 
4510  QStringList retlist;
4511 
4512  EncoderLink *enc = *iter;
4513  if (!enc->IsConnected())
4514  {
4515  LOG(VB_GENERAL, LOG_ERR, LOC + " MainServer::HandleRecorderQuery() " +
4516  QString("Command %1 for unconnected encoder %2")
4517  .arg(command).arg(recnum));
4518  retlist << "bad";
4519  SendResponse(pbssock, retlist);
4520  return;
4521  }
4522 
4523  if (command == "IS_RECORDING")
4524  {
4525  retlist << QString::number((int)enc->IsReallyRecording());
4526  }
4527  else if (command == "GET_FRAMERATE")
4528  {
4529  retlist << QString::number(enc->GetFramerate());
4530  }
4531  else if (command == "GET_FRAMES_WRITTEN")
4532  {
4533  retlist << QString::number(enc->GetFramesWritten());
4534  }
4535  else if (command == "GET_FILE_POSITION")
4536  {
4537  retlist << QString::number(enc->GetFilePosition());
4538  }
4539  else if (command == "GET_MAX_BITRATE")
4540  {
4541  retlist << QString::number(enc->GetMaxBitrate());
4542  }
4543  else if (command == "GET_CURRENT_RECORDING")
4544  {
4545  ProgramInfo *info = enc->GetRecording();
4546  if (info)
4547  {
4548  info->ToStringList(retlist);
4549  delete info;
4550  }
4551  else
4552  {
4553  ProgramInfo dummy;
4554  dummy.SetInputID(enc->GetInputID());
4555  dummy.ToStringList(retlist);
4556  }
4557  }
4558  else if (command == "GET_KEYFRAME_POS")
4559  {
4560  long long desired = slist[2].toLongLong();
4561  retlist << QString::number(enc->GetKeyframePosition(desired));
4562  }
4563  else if (command == "FILL_POSITION_MAP")
4564  {
4565  int64_t start = slist[2].toLongLong();
4566  int64_t end = slist[3].toLongLong();
4567  frm_pos_map_t map;
4568 
4569  if (!enc->GetKeyframePositions(start, end, map))
4570  {
4571  retlist << "error";
4572  }
4573  else
4574  {
4575  for (auto it = map.cbegin(); it != map.cend(); ++it)
4576  {
4577  retlist += QString::number(it.key());
4578  retlist += QString::number(*it);
4579  }
4580  if (retlist.empty())
4581  retlist << "OK";
4582  }
4583  }
4584  else if (command == "FILL_DURATION_MAP")
4585  {
4586  int64_t start = slist[2].toLongLong();
4587  int64_t end = slist[3].toLongLong();
4588  frm_pos_map_t map;
4589 
4590  if (!enc->GetKeyframeDurations(start, end, map))
4591  {
4592  retlist << "error";
4593  }
4594  else
4595  {
4596  for (auto it = map.cbegin(); it != map.cend(); ++it)
4597  {
4598  retlist += QString::number(it.key());
4599  retlist += QString::number(*it);
4600  }
4601  if (retlist.empty())
4602  retlist << "OK";
4603  }
4604  }
4605  else if (command == "GET_RECORDING")
4606  {
4607  ProgramInfo *pginfo = enc->GetRecording();
4608  if (pginfo)
4609  {
4610  pginfo->ToStringList(retlist);
4611  delete pginfo;
4612  }
4613  else
4614  {
4615  ProgramInfo dummy;
4616  dummy.SetInputID(enc->GetInputID());
4617  dummy.ToStringList(retlist);
4618  }
4619  }
4620  else if (command == "FRONTEND_READY")
4621  {
4622  enc->FrontendReady();
4623  retlist << "OK";
4624  }
4625  else if (command == "CANCEL_NEXT_RECORDING")
4626  {
4627  QString cancel = slist[2];
4628  LOG(VB_GENERAL, LOG_NOTICE, LOC +
4629  QString("Received: CANCEL_NEXT_RECORDING %1").arg(cancel));
4630  enc->CancelNextRecording(cancel == "1");
4631  retlist << "OK";
4632  }
4633  else if (command == "SPAWN_LIVETV")
4634  {
4635  QString chainid = slist[2];
4636  LiveTVChain *chain = GetExistingChain(chainid);
4637  if (!chain)
4638  {
4639  chain = new LiveTVChain();
4640  chain->LoadFromExistingChain(chainid);
4641  AddToChains(chain);
4642  }
4643 
4644  chain->SetHostSocket(pbssock);
4645 
4646  enc->SpawnLiveTV(chain, slist[3].toInt() != 0, slist[4]);
4647  retlist << "OK";
4648  }
4649  else if (command == "STOP_LIVETV")
4650  {
4651  QString chainid = enc->GetChainID();
4652  enc->StopLiveTV();
4653 
4654  LiveTVChain *chain = GetExistingChain(chainid);
4655  if (chain)
4656  {
4657  chain->DelHostSocket(pbssock);
4658  if (chain->HostSocketCount() == 0)
4659  {
4660  DeleteChain(chain);
4661  }
4662  }
4663 
4664  retlist << "OK";
4665  }
4666  else if (command == "PAUSE")
4667  {
4668  enc->PauseRecorder();
4669  retlist << "OK";
4670  }
4671  else if (command == "FINISH_RECORDING")
4672  {
4673  enc->FinishRecording();
4674  retlist << "OK";
4675  }
4676  else if (command == "SET_LIVE_RECORDING")
4677  {
4678  int recording = slist[2].toInt();
4679  enc->SetLiveRecording(recording);
4680  retlist << "OK";
4681  }
4682  else if (command == "GET_INPUT")
4683  {
4684  QString ret = enc->GetInput();
4685  ret = (ret.isEmpty()) ? "UNKNOWN" : ret;
4686  retlist << ret;
4687  }
4688  else if (command == "SET_INPUT")
4689  {
4690  QString input = slist[2];
4691  QString ret = enc->SetInput(input);
4692  ret = (ret.isEmpty()) ? "UNKNOWN" : ret;
4693  retlist << ret;
4694  }
4695  else if (command == "TOGGLE_CHANNEL_FAVORITE")
4696  {
4697  QString changroup = slist[2];
4698  enc->ToggleChannelFavorite(changroup);
4699  retlist << "OK";
4700  }
4701  else if (command == "CHANGE_CHANNEL")
4702  {
4703  auto direction = (ChannelChangeDirection) slist[2].toInt();
4704  enc->ChangeChannel(direction);
4705  retlist << "OK";
4706  }
4707  else if (command == "SET_CHANNEL")
4708  {
4709  QString name = slist[2];
4710  enc->SetChannel(name);
4711  retlist << "OK";
4712  }
4713  else if (command == "SET_SIGNAL_MONITORING_RATE")
4714  {
4715  auto rate = std::chrono::milliseconds(slist[2].toInt());
4716  int notifyFrontend = slist[3].toInt();
4717  auto oldrate = enc->SetSignalMonitoringRate(rate, notifyFrontend);
4718  retlist << QString::number(oldrate.count());
4719  }
4720  else if (command == "GET_COLOUR")
4721  {
4723  retlist << QString::number(ret);
4724  }
4725  else if (command == "GET_CONTRAST")
4726  {
4728  retlist << QString::number(ret);
4729  }
4730  else if (command == "GET_BRIGHTNESS")
4731  {
4733  retlist << QString::number(ret);
4734  }
4735  else if (command == "GET_HUE")
4736  {
4738  retlist << QString::number(ret);
4739  }
4740  else if (command == "CHANGE_COLOUR")
4741  {
4742  int type = slist[2].toInt();
4743  bool up = slist[3].toInt() != 0;
4744  int ret = enc->ChangePictureAttribute(
4746  retlist << QString::number(ret);
4747  }
4748  else if (command == "CHANGE_CONTRAST")
4749  {
4750  int type = slist[2].toInt();
4751  bool up = slist[3].toInt() != 0;
4752  int ret = enc->ChangePictureAttribute(
4754  retlist << QString::number(ret);
4755  }
4756  else if (command == "CHANGE_BRIGHTNESS")
4757  {
4758  int type= slist[2].toInt();
4759  bool up = slist[3].toInt() != 0;
4760  int ret = enc->ChangePictureAttribute(
4762  retlist << QString::number(ret);
4763  }
4764  else if (command == "CHANGE_HUE")
4765  {
4766  int type= slist[2].toInt();
4767  bool up = slist[3].toInt() != 0;
4768  int ret = enc->ChangePictureAttribute(
4770  retlist << QString::number(ret);
4771  }
4772  else if (command == "CHECK_CHANNEL")
4773  {
4774  QString name = slist[2];
4775  retlist << QString::number((int)(enc->CheckChannel(name)));
4776  }
4777  else if (command == "SHOULD_SWITCH_CARD")
4778  {
4779  QString chanid = slist[2];
4780  retlist << QString::number((int)(enc->ShouldSwitchToAnotherInput(chanid)));
4781  }
4782  else if (command == "CHECK_CHANNEL_PREFIX")
4783  {
4784  QString needed_spacer;
4785  QString prefix = slist[2];
4786  uint complete_valid_channel_on_rec = 0;
4787  bool is_extra_char_useful = false;
4788 
4789  bool match = enc->CheckChannelPrefix(
4790  prefix, complete_valid_channel_on_rec,
4791  is_extra_char_useful, needed_spacer);
4792 
4793  retlist << QString::number((int)match);
4794  retlist << QString::number(complete_valid_channel_on_rec);
4795  retlist << QString::number((int)is_extra_char_useful);
4796  retlist << ((needed_spacer.isEmpty()) ? QString("X") : needed_spacer);
4797  }
4798  else if (command == "GET_NEXT_PROGRAM_INFO" && (slist.size() >= 6))
4799  {
4800  QString channelname = slist[2];
4801  uint chanid = slist[3].toUInt();
4802  auto direction = (BrowseDirection)slist[4].toInt();
4803  QString starttime = slist[5];
4804 
4805  QString title = "";
4806  QString subtitle = "";
4807  QString desc = "";
4808  QString category = "";
4809  QString endtime = "";
4810  QString callsign = "";
4811  QString iconpath = "";
4812  QString seriesid = "";
4813  QString programid = "";
4814 
4815  enc->GetNextProgram(direction,
4816  title, subtitle, desc, category, starttime,
4817  endtime, callsign, iconpath, channelname, chanid,
4818  seriesid, programid);
4819 
4820  retlist << make_safe(title);
4821  retlist << make_safe(subtitle);
4822  retlist << make_safe(desc);
4823  retlist << make_safe(category);
4824  retlist << make_safe(starttime);
4825  retlist << make_safe(endtime);
4826  retlist << make_safe(callsign);
4827  retlist << make_safe(iconpath);
4828  retlist << make_safe(channelname);
4829  retlist << QString::number(chanid);
4830  retlist << make_safe(seriesid);
4831  retlist << make_safe(programid);
4832  }
4833  else if (command == "GET_CHANNEL_INFO")
4834  {
4835  uint chanid = slist[2].toUInt();
4836  uint sourceid = 0;
4837  QString callsign = "";
4838  QString channum = "";
4839  QString channame = "";
4840  QString xmltv = "";
4841 
4842  enc->GetChannelInfo(chanid, sourceid,
4843  callsign, channum, channame, xmltv);
4844 
4845  retlist << QString::number(chanid);
4846  retlist << QString::number(sourceid);
4847  retlist << make_safe(callsign);
4848  retlist << make_safe(channum);
4849  retlist << make_safe(channame);
4850  retlist << make_safe(xmltv);
4851  }
4852  else
4853  {
4854  LOG(VB_GENERAL, LOG_ERR, LOC +
4855  QString("Unknown command: %1").arg(command));
4856  retlist << "OK";
4857  }
4858 
4859  SendResponse(pbssock, retlist);
4860 }
4861 
4862 void MainServer::HandleSetNextLiveTVDir(QStringList &commands,
4863  PlaybackSock *pbs)
4864 {
4865  MythSocket *pbssock = pbs->getSocket();
4866 
4867  int recnum = commands[1].toInt();
4868 
4869  TVRec::s_inputsLock.lockForRead();
4870  auto iter = m_encoderList->constFind(recnum);
4871  if (iter == m_encoderList->constEnd())
4872  {
4873  TVRec::s_inputsLock.unlock();
4874  LOG(VB_GENERAL, LOG_ERR, LOC + "MainServer::HandleSetNextLiveTVDir() " +
4875  QString("Unknown encoder: %1").arg(recnum));
4876  QStringList retlist( "bad" );
4877  SendResponse(pbssock, retlist);
4878  return;
4879  }
4880  TVRec::s_inputsLock.unlock();
4881 
4882  EncoderLink *enc = *iter;
4883  enc->SetNextLiveTVDir(commands[2]);
4884 
4885  QStringList retlist( "OK" );
4886  SendResponse(pbssock, retlist);
4887 }
4888 
4890 {
4891  bool ok = true;
4892  MythSocket *pbssock = pbs->getSocket();
4893  uint chanid = slist[1].toUInt();
4894  uint sourceid = slist[2].toUInt();
4895  QString oldcnum = cleanup(slist[3]);
4896  QString callsign = cleanup(slist[4]);
4897  QString channum = cleanup(slist[5]);
4898  QString channame = cleanup(slist[6]);
4899  QString xmltv = cleanup(slist[7]);
4900 
4901  QStringList retlist;
4902  if (!chanid || !sourceid)
4903  {
4904  retlist << "0";
4905  SendResponse(pbssock, retlist);
4906  return;
4907  }
4908 
4909  TVRec::s_inputsLock.lockForRead();
4910  for (auto * encoder : std::as_const(*m_encoderList))
4911  {
4912  if (encoder)
4913  {
4914  ok &= encoder->SetChannelInfo(chanid, sourceid, oldcnum,
4915  callsign, channum, channame, xmltv);
4916  }
4917  }
4918  TVRec::s_inputsLock.unlock();
4919 
4920  retlist << ((ok) ? "1" : "0");
4921  SendResponse(pbssock, retlist);
4922 }
4923 
4924 void MainServer::HandleRemoteEncoder(QStringList &slist, QStringList &commands,
4925  PlaybackSock *pbs)
4926 {
4927  MythSocket *pbssock = pbs->getSocket();
4928 
4929  int recnum = commands[1].toInt();
4930  QStringList retlist;
4931 
4932  TVRec::s_inputsLock.lockForRead();
4933  auto iter = m_encoderList->constFind(recnum);
4934  if (iter == m_encoderList->constEnd())
4935  {
4936  TVRec::s_inputsLock.unlock();
4937  LOG(VB_GENERAL, LOG_ERR, LOC +
4938  QString("HandleRemoteEncoder(cmd %1) ").arg(slist[1]) +
4939  QString("Unknown encoder: %1").arg(recnum));
4940  retlist << QString::number((int) kState_Error);
4941  SendResponse(pbssock, retlist);
4942  return;
4943  }
4944  TVRec::s_inputsLock.unlock();
4945 
4946  EncoderLink *enc = *iter;
4947 
4948  QString command = slist[1];
4949 
4950  if (command == "GET_STATE")
4951  {
4952  retlist << QString::number((int)enc->GetState());
4953  }
4954  else if (command == "GET_SLEEPSTATUS")
4955  {
4956  retlist << QString::number(enc->GetSleepStatus());
4957  }
4958  else if (command == "GET_FLAGS")
4959  {
4960  retlist << QString::number(enc->GetFlags());
4961  }
4962  else if (command == "IS_BUSY")
4963  {
4964  std::chrono::seconds time_buffer = 5s;
4965  if (slist.size() >= 3)
4966  time_buffer = std::chrono::seconds(slist[2].toInt());
4967  InputInfo busy_input;
4968  retlist << QString::number((int)enc->IsBusy(&busy_input, time_buffer));
4969  busy_input.ToStringList(retlist);
4970  }
4971  else if (command == "MATCHES_RECORDING" &&
4972  slist.size() >= (2 + NUMPROGRAMLINES))
4973  {
4974  QStringList::const_iterator it = slist.cbegin() + 2;
4975  ProgramInfo pginfo(it, slist.cend());
4976 
4977  retlist << QString::number((int)enc->MatchesRecording(&pginfo));
4978  }
4979  else if (command == "START_RECORDING" &&
4980  slist.size() >= (2 + NUMPROGRAMLINES))
4981  {
4982  QStringList::const_iterator it = slist.cbegin() + 2;
4983  ProgramInfo pginfo(it, slist.cend());
4984 
4985  retlist << QString::number(enc->StartRecording(&pginfo));
4986  retlist << QString::number(pginfo.GetRecordingID());
4987  retlist << QString::number(pginfo.GetRecordingStartTime().toSecsSinceEpoch());
4988  }
4989  else if (command == "GET_RECORDING_STATUS")
4990  {
4991  retlist << QString::number((int)enc->GetRecordingStatus());
4992  }
4993  else if (command == "RECORD_PENDING" &&
4994  (slist.size() >= 4 + NUMPROGRAMLINES))
4995  {
4996  auto secsleft = std::chrono::seconds(slist[2].toInt());
4997  int haslater = slist[3].toInt();
4998  QStringList::const_iterator it = slist.cbegin() + 4;
4999  ProgramInfo pginfo(it, slist.cend());
5000 
5001  enc->RecordPending(&pginfo, secsleft, haslater != 0);
5002 
5003  retlist << "OK";
5004  }
5005  else if (command == "CANCEL_NEXT_RECORDING" &&
5006  (slist.size() >= 3))
5007  {
5008  bool cancel = (bool) slist[2].toInt();
5009  enc->CancelNextRecording(cancel);
5010  retlist << "OK";
5011  }
5012  else if (command == "STOP_RECORDING")
5013  {
5014  enc->StopRecording();
5015  retlist << "OK";
5016  }
5017  else if (command == "GET_MAX_BITRATE")
5018  {
5019  retlist << QString::number(enc->GetMaxBitrate());
5020  }
5021  else if (command == "GET_CURRENT_RECORDING")
5022  {
5023  ProgramInfo *info = enc->GetRecording();
5024  if (info)
5025  {
5026  info->ToStringList(retlist);
5027  delete info;
5028  }
5029  else
5030  {
5031  ProgramInfo dummy;
5032  dummy.SetInputID(enc->GetInputID());
5033  dummy.ToStringList(retlist);
5034  }
5035  }
5036 
5037  SendResponse(pbssock, retlist);
5038 }
5039 
5040 void MainServer::GetActiveBackends(QStringList &hosts)
5041 {
5042  hosts.clear();
5043  hosts << gCoreContext->GetHostName();
5044 
5045  QString hostname;
5046  QReadLocker rlock(&m_sockListLock);
5047  for (auto & pbs : m_playbackList)
5048  {
5049  if (pbs->isMediaServer())
5050  {
5051  hostname = pbs->getHostname();
5052  if (!hosts.contains(hostname))
5053  hosts << hostname;
5054  }
5055  }
5056 }
5057 
5059 {
5060  QStringList retlist;
5061  GetActiveBackends(retlist);
5062  retlist.push_front(QString::number(retlist.size()));
5063  SendResponse(pbs->getSocket(), retlist);
5064 }
5065 
5066 void MainServer::HandleIsActiveBackendQuery(const QStringList &slist,
5067  PlaybackSock *pbs)
5068 {
5069  QStringList retlist;
5070  const QString& queryhostname = slist[1];
5071 
5072  if (gCoreContext->GetHostName() != queryhostname)
5073  {
5074  PlaybackSock *slave = GetSlaveByHostname(queryhostname);
5075  if (slave != nullptr)
5076  {
5077  retlist << "TRUE";
5078  slave->DecrRef();
5079  }
5080  else
5081  retlist << "FALSE";
5082  }
5083  else
5084  retlist << "TRUE";
5085 
5086  SendResponse(pbs->getSocket(), retlist);
5087 }
5088 
5089 int MainServer::GetfsID(const QList<FileSystemInfo>::iterator& fsInfo)
5090 {
5091  QString fskey = fsInfo->getHostname() + ":" + fsInfo->getPath();
5092  QMutexLocker lock(&m_fsIDcacheLock);
5093  if (!m_fsIDcache.contains(fskey))
5094  m_fsIDcache[fskey] = m_fsIDcache.count();
5095 
5096  return m_fsIDcache[fskey];
5097 }
5098 
5100 {
5101  size_t totalKBperMin = 0;
5102 
5103  TVRec::s_inputsLock.lockForRead();
5104  for (auto * enc : std::as_const(*m_encoderList))
5105  {
5106  if (!enc->IsConnected() || !enc->IsBusy())
5107  continue;
5108 
5109  long long maxBitrate = enc->GetMaxBitrate();
5110  if (maxBitrate<=0)
5111  maxBitrate = 19500000LL;
5112  long long thisKBperMin = (((size_t)maxBitrate)*((size_t)15))>>11;
5113  totalKBperMin += thisKBperMin;
5114  LOG(VB_FILE, LOG_INFO, LOC + QString("Cardid %1: max bitrate %2 KB/min")
5115  .arg(enc->GetInputID()).arg(thisKBperMin));
5116  }
5117  TVRec::s_inputsLock.unlock();
5118 
5119  LOG(VB_FILE, LOG_INFO, LOC +
5120  QString("Maximal bitrate of busy encoders is %1 KB/min")
5121  .arg(totalKBperMin));
5122 
5123  return totalKBperMin;
5124 }
5125 
5126 void MainServer::BackendQueryDiskSpace(QStringList &strlist, bool consolidated,
5127  bool allHosts)
5128 {
5129  QString allHostList = gCoreContext->GetHostName();
5130  int64_t totalKB = -1;
5131  int64_t usedKB = -1;
5132  QMap <QString, bool>foundDirs;
5133  QString localStr = "1";
5134  struct statfs statbuf {};
5135  QStringList groups(StorageGroup::kSpecialGroups);
5136  groups.removeAll("LiveTV");
5137  QString specialGroups = groups.join("', '");
5138  QString sql = QString("SELECT MIN(id),dirname "
5139  "FROM storagegroup "
5140  "WHERE hostname = :HOSTNAME "
5141  "AND groupname NOT IN ( '%1' ) "
5142  "GROUP BY dirname;").arg(specialGroups);
5143  MSqlQuery query(MSqlQuery::InitCon());
5144  query.prepare(sql);
5145  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
5146 
5147  if (query.exec())
5148  {
5149  // If we don't have any dirs of our own, fallback to list of Default
5150  // dirs since that is what StorageGroup::Init() does.
5151  if (!query.size())
5152  {
5153  query.prepare("SELECT MIN(id),dirname "
5154  "FROM storagegroup "
5155  "WHERE groupname = :GROUP "
5156  "GROUP BY dirname;");
5157  query.bindValue(":GROUP", "Default");
5158  if (!query.exec())
5159  MythDB::DBError("BackendQueryDiskSpace", query);
5160  }
5161 
5162  QDir checkDir("");
5163  QString dirID;
5164  QString currentDir;
5165  while (query.next())
5166  {
5167  dirID = query.value(0).toString();
5168  /* The storagegroup.dirname column uses utf8_bin collation, so Qt
5169  * uses QString::fromAscii() for toString(). Explicitly convert the
5170  * value using QString::fromUtf8() to prevent corruption. */
5171  currentDir = QString::fromUtf8(query.value(1)
5172  .toByteArray().constData());
5173  if (currentDir.endsWith("/"))
5174  currentDir.remove(currentDir.length() - 1, 1);
5175 
5176  checkDir.setPath(currentDir);
5177  if (!foundDirs.contains(currentDir))
5178  {
5179  if (checkDir.exists())
5180  {
5181  QByteArray cdir = currentDir.toLatin1();
5182  getDiskSpace(cdir.constData(), totalKB, usedKB);
5183  memset(&statbuf, 0, sizeof(statbuf));
5184  localStr = "1"; // Assume local
5185  int bSize = 0;
5186 
5187  if (statfs(currentDir.toLocal8Bit().constData(), &statbuf) == 0)
5188  {
5189 #ifdef Q_OS_DARWIN
5190  char *fstypename = statbuf.f_fstypename;
5191  if ((!strcmp(fstypename, "nfs")) || // NFS|FTP
5192  (!strcmp(fstypename, "afpfs")) || // ApplShr
5193  (!strcmp(fstypename, "smbfs"))) // SMB
5194  localStr = "0";
5195 #elif defined(__linux__)
5196  long fstype = statbuf.f_type;
5197  if ((fstype == 0x6969) || // NFS
5198  (fstype == 0x517B) || // SMB
5199  (fstype == (long)0xFF534D42)) // CIFS
5200  localStr = "0";
5201 #endif
5202  bSize = statbuf.f_bsize;
5203  }
5204 
5205  strlist << gCoreContext->GetHostName();
5206  strlist << currentDir;
5207  strlist << localStr;
5208  strlist << "-1"; // Ignore fsID
5209  strlist << dirID;
5210  strlist << QString::number(bSize);
5211  strlist << QString::number(totalKB);
5212  strlist << QString::number(usedKB);
5213 
5214  foundDirs[currentDir] = true;
5215  }
5216  else
5217  foundDirs[currentDir] = false;
5218  }
5219  }
5220  }
5221 
5222  if (allHosts)
5223  {
5224  QMap <QString, bool> backendsCounted;
5225  std::list<PlaybackSock *> localPlaybackList;
5226 
5227  m_sockListLock.lockForRead();
5228 
5229  for (auto *pbs : m_playbackList)
5230  {
5231  if ((pbs->IsDisconnected()) ||
5232  (!pbs->isMediaServer()) ||
5233  (pbs->isLocal()) ||
5234  (backendsCounted.contains(pbs->getHostname())))
5235  continue;
5236 
5237  backendsCounted[pbs->getHostname()] = true;
5238  pbs->IncrRef();
5239  localPlaybackList.push_back(pbs);
5240  allHostList += "," + pbs->getHostname();
5241  }
5242 
5243  m_sockListLock.unlock();
5244 
5245  for (auto & pbs : localPlaybackList) {
5246  pbs->GetDiskSpace(strlist);
5247  pbs->DecrRef();
5248  }
5249  }
5250 
5251  if (!consolidated)
5252  return;
5253 
5254  QList<FileSystemInfo> fsInfos;
5255  QStringList::const_iterator it = strlist.cbegin();
5256  while (it != strlist.cend())
5257  {
5258  FileSystemInfo fsInfo;
5259 
5260  fsInfo.setHostname(*(it++));
5261  fsInfo.setPath(*(it++));
5262  fsInfo.setLocal((*(it++)).toInt() > 0);
5263  fsInfo.setFSysID(-1);
5264  ++it; // Without this, the strlist gets out of whack
5265  fsInfo.setGroupID((*(it++)).toInt());
5266  fsInfo.setBlockSize((*(it++)).toInt());
5267  fsInfo.setTotalSpace((*(it++)).toLongLong());
5268  fsInfo.setUsedSpace((*(it++)).toLongLong());
5269  fsInfos.push_back(fsInfo);
5270  }
5271  strlist.clear();
5272 
5273  // Consolidate hosts sharing storage
5274  int64_t maxWriteFiveSec = GetCurrentMaxBitrate()/12 /*5 seconds*/;
5275  maxWriteFiveSec = std::max((int64_t)2048, maxWriteFiveSec); // safety for NFS mounted dirs
5276 
5277  for (auto it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
5278  {
5279  if (it1->getFSysID() == -1)
5280  {
5281  it1->setFSysID(GetfsID(it1));
5282  it1->setPath(
5283  it1->getHostname().section(".", 0, 0) + ":" + it1->getPath());
5284  }
5285 
5286  for (auto it2 = it1 + 1; it2 != fsInfos.end(); )
5287  {
5288  // our fuzzy comparison uses the maximum of the two block sizes
5289  // or 32, whichever is greater
5290  int bSize = std::max(32, std::max(it1->getBlockSize(), it2->getBlockSize()) / 1024);
5291  int64_t diffSize = it1->getTotalSpace() - it2->getTotalSpace();
5292  int64_t diffUsed = it1->getUsedSpace() - it2->getUsedSpace();
5293  if (diffSize < 0)
5294  diffSize = 0 - diffSize;
5295  if (diffUsed < 0)
5296  diffUsed = 0 - diffUsed;
5297 
5298  if (it2->getFSysID() == -1 && (diffSize <= bSize) &&
5299  (diffUsed <= maxWriteFiveSec))
5300  {
5301  if (!it1->getHostname().contains(it2->getHostname()))
5302  it1->setHostname(it1->getHostname() + "," + it2->getHostname());
5303  it1->setPath(it1->getPath() + "," +
5304  it2->getHostname().section(".", 0, 0) + ":" + it2->getPath());
5305  it2 = fsInfos.erase(it2);
5306  }
5307  else
5308  {
5309  it2++;
5310  }
5311  }
5312  }
5313 
5314  // Passed the cleaned list back
5315  totalKB = 0;
5316  usedKB = 0;
5317  for (const auto & fsInfo : std::as_const(fsInfos))
5318  {
5319  strlist << fsInfo.getHostname();
5320  strlist << fsInfo.getPath();
5321  strlist << QString::number(static_cast<int>(fsInfo.isLocal()));
5322  strlist << QString::number(fsInfo.getFSysID());
5323  strlist << QString::number(fsInfo.getGroupID());
5324  strlist << QString::number(fsInfo.getBlockSize());
5325  strlist << QString::number(fsInfo.getTotalSpace());
5326  strlist << QString::number(fsInfo.getUsedSpace());
5327 
5328  totalKB += fsInfo.getTotalSpace();
5329  usedKB += fsInfo.getUsedSpace();
5330  }
5331 
5332  if (allHosts)
5333  {
5334  strlist << allHostList;
5335  strlist << "TotalDiskSpace";
5336  strlist << "0";
5337  strlist << "-2";
5338  strlist << "-2";
5339  strlist << "0";
5340  strlist << QString::number(totalKB);
5341  strlist << QString::number(usedKB);
5342  }
5343 }
5344 
5345 void MainServer::GetFilesystemInfos(QList<FileSystemInfo> &fsInfos,
5346  bool useCache)
5347 {
5348  // Return cached information if requested.
5349  if (useCache)
5350  {
5351  QMutexLocker locker(&m_fsInfosCacheLock);
5352  fsInfos = m_fsInfosCache;
5353  return;
5354  }
5355 
5356  QStringList strlist;
5357  FileSystemInfo fsInfo;
5358 
5359  fsInfos.clear();
5360 
5361  BackendQueryDiskSpace(strlist, false, true);
5362 
5363  QStringList::const_iterator it = strlist.cbegin();
5364  while (it != strlist.cend())
5365  {
5366  fsInfo.setHostname(*(it++));
5367  fsInfo.setPath(*(it++));
5368  fsInfo.setLocal((*(it++)).toInt() > 0);
5369  fsInfo.setFSysID(-1);
5370  ++it;
5371  fsInfo.setGroupID((*(it++)).toInt());
5372  fsInfo.setBlockSize((*(it++)).toInt());
5373  fsInfo.setTotalSpace((*(it++)).toLongLong());
5374  fsInfo.setUsedSpace((*(it++)).toLongLong());
5375  fsInfo.setWeight(0);
5376  fsInfos.push_back(fsInfo);
5377  }
5378 
5379  LOG(VB_SCHEDULE | VB_FILE, LOG_DEBUG, LOC +
5380  "Determining unique filesystems");
5381  size_t maxWriteFiveSec = GetCurrentMaxBitrate()/12 /*5 seconds*/;
5382  // safety for NFS mounted dirs
5383  maxWriteFiveSec = std::max((size_t)2048, maxWriteFiveSec);
5384 
5385  FileSystemInfo::Consolidate(fsInfos, false, maxWriteFiveSec);
5386 
5387  QList<FileSystemInfo>::iterator it1;
5388  if (VERBOSE_LEVEL_CHECK(VB_FILE | VB_SCHEDULE, LOG_INFO))
5389  {
5390  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5391  "--- GetFilesystemInfos directory list start ---");
5392  for (it1 = fsInfos.begin(); it1 != fsInfos.end(); ++it1)
5393  {
5394  QString msg =
5395  QString("Dir: %1:%2")
5396  .arg(it1->getHostname(), it1->getPath());
5397  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC + msg) ;
5398  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5399  QString(" Location: %1")
5400  .arg(it1->isLocal() ? "Local" : "Remote"));
5401  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5402  QString(" fsID : %1")
5403  .arg(it1->getFSysID()));
5404  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5405  QString(" dirID : %1")
5406  .arg(it1->getGroupID()));
5407  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5408  QString(" BlkSize : %1")
5409  .arg(it1->getBlockSize()));
5410  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5411  QString(" TotalKB : %1")
5412  .arg(it1->getTotalSpace()));
5413  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5414  QString(" UsedKB : %1")
5415  .arg(it1->getUsedSpace()));
5416  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5417  QString(" FreeKB : %1")
5418  .arg(it1->getFreeSpace()));
5419  }
5420  LOG(VB_FILE | VB_SCHEDULE, LOG_INFO, LOC +
5421  "--- GetFilesystemInfos directory list end ---");
5422  }
5423 
5424  // Save these results to the cache.
5425  QMutexLocker locker(&m_fsInfosCacheLock);
5426  m_fsInfosCache = fsInfos;
5427 }
5428 
5429 void MainServer::HandleMoveFile(PlaybackSock *pbs, const QString &storagegroup,
5430  const QString &src, const QString &dst)
5431 {
5432  StorageGroup sgroup(storagegroup, "", false);
5433  QStringList retlist;
5434 
5435  if (src.isEmpty() || dst.isEmpty()
5436  || src.contains("..") || dst.contains(".."))
5437  {
5438  LOG(VB_GENERAL, LOG_ERR, LOC +
5439  QString("HandleMoveFile: ERROR moving file '%1' -> '%2', "
5440  "a path fails sanity checks").arg(src, dst));
5441  retlist << "0" << "Invalid path";
5442  SendResponse(pbs->getSocket(), retlist);
5443  return;
5444  }
5445 
5446  QString srcAbs = sgroup.FindFile(src);
5447  if (srcAbs.isEmpty())
5448  {
5449  LOG(VB_GENERAL, LOG_ERR, LOC +
5450  QString("HandleMoveFile: Unable to find %1").arg(src));
5451  retlist << "0" << "Source file not found";
5452  SendResponse(pbs->getSocket(), retlist);
5453  return;
5454  }
5455 
5456  // Path of files must be unique within SG. Rename will permit <sgdir1>/<dst>
5457  // even when <sgdir2>/<dst> already exists.
5458  // Directory paths do not have to be unique.
5459  QString dstAbs = sgroup.FindFile(dst);
5460  if (!dstAbs.isEmpty() && QFileInfo(dstAbs).isFile())
5461  {
5462  LOG(VB_GENERAL, LOG_ERR, LOC +
5463  QString("HandleMoveFile: Destination exists at %1").arg(dstAbs));
5464  retlist << "0" << "Destination file exists";
5465  SendResponse(pbs->getSocket(), retlist);
5466  return;
5467  }
5468 
5469  // Files never move filesystems, so use current SG dir
5470  int sgPathSize = srcAbs.size() - src.size();
5471  dstAbs = srcAbs.mid(0, sgPathSize) + dst;
5472 
5473  // Renaming on same filesystem should always be fast but is liable to delays
5474  // for unknowable reasons so we delegate to a separate thread for safety.
5475  auto *renamer = new RenameThread(*this, *pbs, srcAbs, dstAbs);
5476  MThreadPool::globalInstance()->start(renamer, "Rename");
5477 }
5478 
5480 
5482 {
5483  // Only permit one rename to run at any time
5484  QMutexLocker lock(&s_renamelock);
5485  LOG(VB_FILE, LOG_INFO, QString("MainServer::RenameThread: Renaming %1 -> %2")
5486  .arg(m_src, m_dst));
5487 
5488  QStringList retlist;
5489  QFileInfo fi(m_dst);
5490 
5491  if (QDir().mkpath(fi.path()) && QFile::rename(m_src, m_dst))
5492  {
5493  retlist << "1";
5494  }
5495  else
5496  {
5497  retlist << "0" << "Rename failed";
5498  LOG(VB_FILE, LOG_ERR, "MainServer::DoRenameThread: Rename failed");
5499  }
5500  m_ms.SendResponse(m_pbs.getSocket(), retlist);
5501 }
5502 
5504 {
5505  if (m_ms)
5506  m_ms->DoTruncateThread(this);
5507 }
5508 
5510 {
5511  if (gCoreContext->GetBoolSetting("TruncateDeletesSlowly", false))
5512  {
5513  TruncateAndClose(nullptr, ds->m_fd, ds->m_filename, ds->m_size);
5514  }
5515  else
5516  {
5517  QMutexLocker dl(&m_deletelock);
5518  close(ds->m_fd);
5519  }
5520 }
5521 
5522 bool MainServer::HandleDeleteFile(const QStringList &slist, PlaybackSock *pbs)
5523 {
5524  return HandleDeleteFile(slist[1], slist[2], pbs);
5525 }
5526 
5527 bool MainServer::HandleDeleteFile(const QString& filename, const QString& storagegroup,
5528  PlaybackSock *pbs)
5529 {
5530  StorageGroup sgroup(storagegroup, "", false);
5531  QStringList retlist;
5532 
5533  if ((filename.isEmpty()) ||
5534  (filename.contains("/../")) ||
5535  (filename.startsWith("../")))
5536  {
5537  LOG(VB_GENERAL, LOG_ERR, LOC +
5538  QString("ERROR deleting file, filename '%1' "
5539  "fails sanity checks").arg(filename));
5540  if (pbs)
5541  {
5542  retlist << "0";
5543  SendResponse(pbs->getSocket(), retlist);
5544  }
5545  return false;
5546  }
5547 
5548  QString fullfile = sgroup.FindFile(filename);
5549 
5550  if (fullfile.isEmpty()) {
5551  LOG(VB_GENERAL, LOG_ERR, LOC +
5552  QString("Unable to find %1 in HandleDeleteFile()") .arg(filename));
5553  if (pbs)
5554  {
5555  retlist << "0";
5556  SendResponse(pbs->getSocket(), retlist);
5557  }
5558  return false;
5559  }
5560 
5561  QFile checkFile(fullfile);
5562  bool followLinks = gCoreContext->GetBoolSetting("DeletesFollowLinks", false);
5563  off_t size = 0;
5564 
5565  // This will open the file and unlink the dir entry. The actual file
5566  // data will be deleted in the truncate thread spawned below.
5567  // Since stat fails after unlinking on some filesystems, get the size first
5568  const QFileInfo info(fullfile);
5569  size = info.size();
5570  int fd = DeleteFile(fullfile, followLinks);
5571 
5572  if ((fd < 0) && checkFile.exists())
5573  {
5574  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error deleting file: %1.")
5575  .arg(fullfile));
5576  if (pbs)
5577  {
5578  retlist << "0";
5579  SendResponse(pbs->getSocket(), retlist);
5580  }
5581  return false;
5582  }
5583 
5584  if (pbs)
5585  {
5586  retlist << "1";
5587  SendResponse(pbs->getSocket(), retlist);
5588  }
5589 
5590  // DeleteFile() opened up a file for us to delete
5591  if (fd >= 0)
5592  {
5593  // Thread off the actual file truncate
5594  auto *truncateThread = new TruncateThread(this, fullfile, fd, size);
5595  truncateThread->run();
5596  }
5597 
5598  // The truncateThread should be deleted by QRunnable after it
5599  // finished executing.
5600  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
5601  return true;
5602 }
5603 
5604 // Helper function for the guts of HandleCommBreakQuery + HandleCutlistQuery
5605 void MainServer::HandleCutMapQuery(const QString &chanid,
5606  const QString &starttime,
5607  PlaybackSock *pbs, bool commbreak)
5608 {
5609  MythSocket *pbssock = nullptr;
5610  if (pbs)
5611  pbssock = pbs->getSocket();
5612 
5613  frm_dir_map_t markMap;
5614  frm_dir_map_t::const_iterator it;
5615  QDateTime recstartdt = MythDate::fromSecsSinceEpoch(starttime.toLongLong());
5616  QStringList retlist;
5617  int rowcnt = 0;
5618 
5619  const ProgramInfo pginfo(chanid.toUInt(), recstartdt);
5620 
5621  if (pginfo.GetChanID())
5622  {
5623  if (commbreak)
5624  pginfo.QueryCommBreakList(markMap);
5625  else
5626  pginfo.QueryCutList(markMap);
5627 
5628  for (it = markMap.cbegin(); it != markMap.cend(); ++it)
5629  {
5630  rowcnt++;
5631  QString intstr = QString("%1").arg(*it);
5632  retlist << intstr;
5633  retlist << QString::number(it.key());
5634  }
5635  }
5636 
5637  if (rowcnt > 0)
5638  retlist.prepend(QString("%1").arg(rowcnt));
5639  else
5640  retlist << "-1";
5641 
5642  if (pbssock)
5643  SendResponse(pbssock, retlist);
5644 }
5645 
5646 void MainServer::HandleCommBreakQuery(const QString &chanid,
5647  const QString &starttime,
5648  PlaybackSock *pbs)
5649 {
5650 // Commercial break query
5651 // Format: QUERY_COMMBREAK <chanid> <starttime>
5652 // chanid is chanid, starttime is startime of program in
5653 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
5654 // a ProgramInfo structure in a string list.
5655 // Return structure is [number of rows] followed by a triplet of values:
5656 // each triplet : [type] [long portion 1] [long portion 2]
5657 // type is the value in the map, right now 4 = commbreak start, 5= end
5658  HandleCutMapQuery(chanid, starttime, pbs, true);
5659 }
5660 
5661 void MainServer::HandleCutlistQuery(const QString &chanid,
5662  const QString &starttime,
5663  PlaybackSock *pbs)
5664 {
5665 // Cutlist query
5666 // Format: QUERY_CUTLIST <chanid> <starttime>
5667 // chanid is chanid, starttime is startime of program in
5668 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
5669 // a ProgramInfo structure in a string list.
5670 // Return structure is [number of rows] followed by a triplet of values:
5671 // each triplet : [type] [long portion 1] [long portion 2]
5672 // type is the value in the map, right now 0 = commbreak start, 1 = end
5673  HandleCutMapQuery(chanid, starttime, pbs, false);
5674 }
5675 
5676 
5677 void MainServer::HandleBookmarkQuery(const QString &chanid,
5678  const QString &starttime,
5679  PlaybackSock *pbs)
5680 // Bookmark query
5681 // Format: QUERY_BOOKMARK <chanid> <starttime>
5682 // chanid is chanid, starttime is startime of program in
5683 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
5684 // a ProgramInfo structure in a string list.
5685 // Return value is a long-long encoded as two separate values
5686 {
5687  MythSocket *pbssock = nullptr;
5688  if (pbs)
5689  pbssock = pbs->getSocket();
5690 
5691  QDateTime recstartts = MythDate::fromSecsSinceEpoch(starttime.toLongLong());
5692  uint64_t bookmark = ProgramInfo::QueryBookmark(
5693  chanid.toUInt(), recstartts);
5694 
5695  QStringList retlist;
5696  retlist << QString::number(bookmark);
5697 
5698  if (pbssock)
5699  SendResponse(pbssock, retlist);
5700 }
5701 
5702 
5703 void MainServer::HandleSetBookmark(QStringList &tokens,
5704  PlaybackSock *pbs)
5705 {
5706 // Bookmark query
5707 // Format: SET_BOOKMARK <chanid> <starttime> <position>
5708 // chanid is chanid, starttime is startime of program in
5709 // # of seconds since Jan 1, 1970, in UTC time. Same format as in
5710 // a ProgramInfo structure in a string list. The two longs are the two
5711 // portions of the bookmark value to set.
5712 
5713  MythSocket *pbssock = nullptr;
5714  if (pbs)
5715  pbssock = pbs->getSocket();
5716 
5717  QString chanid = tokens[1];
5718  QString starttime = tokens[2];
5719  long long bookmark = tokens[3].toLongLong();
5720 
5721  QDateTime recstartts = MythDate::fromSecsSinceEpoch(starttime.toLongLong());
5722  QStringList retlist;
5723 
5724  ProgramInfo pginfo(chanid.toUInt(), recstartts);
5725 
5726  if (pginfo.GetChanID())
5727  {
5728  pginfo.SaveBookmark(bookmark);
5729  retlist << "OK";
5730  }
5731  else
5732  retlist << "FAILED";
5733 
5734  if (pbssock)
5735  SendResponse(pbssock, retlist);
5736 }
5737 
5738 void MainServer::HandleSettingQuery(const QStringList &tokens, PlaybackSock *pbs)
5739 {
5740 // Format: QUERY_SETTING <hostname> <setting>
5741 // Returns setting value as a string
5742 
5743  MythSocket *pbssock = nullptr;
5744  if (pbs)
5745  pbssock = pbs->getSocket();
5746 
5747  const QString& hostname = tokens[1];
5748  const QString& setting = tokens[2];
5749  QStringList retlist;
5750 
5751  QString retvalue = gCoreContext->GetSettingOnHost(setting, hostname, "-1");
5752 
5753  retlist << retvalue;
5754  if (pbssock)
5755  SendResponse(pbssock, retlist);
5756 }
5757 
5758 void MainServer::HandleDownloadFile(const QStringList &command,
5759  PlaybackSock *pbs)
5760 {
5761  bool synchronous = (command[0] == "DOWNLOAD_FILE_NOW");
5762  const QString& srcURL = command[1];
5763  const QString& storageGroup = command[2];
5764  QString filename = command[3];
5765  StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false);
5766  QString outDir = sgroup.FindNextDirMostFree();
5767  QString outFile;
5768  QStringList retlist;
5769 
5770  MythSocket *pbssock = nullptr;
5771  if (pbs)
5772  pbssock = pbs->getSocket();
5773 
5774  if (filename.isEmpty())
5775  {
5776  QFileInfo finfo(srcURL);
5777  filename = finfo.fileName();
5778  }
5779 
5780  if (outDir.isEmpty())
5781  {
5782  LOG(VB_GENERAL, LOG_ERR, LOC +
5783  QString("Unable to determine directory "
5784  "to write to in %1 write command").arg(command[0]));
5785  retlist << "downloadfile_directory_not_found";
5786  if (pbssock)
5787  SendResponse(pbssock, retlist);
5788  return;
5789  }
5790 
5791  if ((filename.contains("/../")) ||
5792  (filename.startsWith("../")))
5793  {
5794  LOG(VB_GENERAL, LOG_ERR, LOC +
5795  QString("ERROR: %1 write filename '%2' does not pass "
5796  "sanity checks.") .arg(command[0], filename));
5797  retlist << "downloadfile_filename_dangerous";
5798  if (pbssock)
5799  SendResponse(pbssock, retlist);
5800  return;
5801  }
5802 
5803  outFile = outDir + "/" + filename;
5804 
5805  if (synchronous)
5806  {
5807  if (GetMythDownloadManager()->download(srcURL, outFile))
5808  {
5809  retlist << "OK";
5810  retlist << gCoreContext->GetMasterHostPrefix(storageGroup)
5811  + filename;
5812  }
5813  else
5814  retlist << "ERROR";
5815  }
5816  else
5817  {
5818  QMutexLocker locker(&m_downloadURLsLock);
5819  m_downloadURLs[outFile] =
5820  gCoreContext->GetMasterHostPrefix(storageGroup) +
5822 
5823  GetMythDownloadManager()->queueDownload(srcURL, outFile, this);
5824  retlist << "OK";
5825  retlist << gCoreContext->GetMasterHostPrefix(storageGroup) + filename;
5826  }
5827 
5828  if (pbssock)
5829  SendResponse(pbssock, retlist);
5830 }
5831 
5832 void MainServer::HandleSetSetting(const QStringList &tokens,
5833  PlaybackSock *pbs)
5834 {
5835 // Format: SET_SETTING <hostname> <setting> <value>
5836  MythSocket *pbssock = nullptr;
5837  if (pbs)
5838  pbssock = pbs->getSocket();
5839 
5840  const QString& hostname = tokens[1];
5841  const QString& setting = tokens[2];
5842  const QString& svalue = tokens[3];
5843  QStringList retlist;
5844 
5845  if (gCoreContext->SaveSettingOnHost(setting, svalue, hostname))
5846  retlist << "OK";
5847  else
5848  retlist << "ERROR";
5849 
5850  if (pbssock)
5851  SendResponse(pbssock, retlist);
5852 }
5853 
5855 {
5856  MythSocket *pbssock = pbs->getSocket();
5857 
5858  QStringList retlist;
5859 
5860  if (m_metadatafactory)
5861  {
5862  QStringList hosts;
5863  GetActiveBackends(hosts);
5864  m_metadatafactory->VideoScan(hosts);
5865  retlist << "OK";
5866  }
5867  else
5868  retlist << "ERROR";
5869 
5870  if (pbssock)
5871  SendResponse(pbssock, retlist);
5872 }
5873 
5874 void MainServer::HandleScanMusic(const QStringList &slist, PlaybackSock *pbs)
5875 {
5876  MythSocket *pbssock = pbs->getSocket();
5877 
5878  QStringList strlist;
5879 
5880  if (m_ismaster)
5881  {
5882  // get a list of hosts with a directory defined for the 'Music' storage group
5883  MSqlQuery query(MSqlQuery::InitCon());
5884  QString sql = "SELECT DISTINCT hostname "
5885  "FROM storagegroup "
5886  "WHERE groupname = 'Music'";
5887  if (!query.exec(sql) || !query.isActive())
5888  MythDB::DBError("MainServer::HandleScanMusic get host list", query);
5889  else
5890  {
5891  while(query.next())
5892  {
5893  QString hostname = query.value(0).toString();
5894 
5895  if (hostname == gCoreContext->GetHostName())
5896  {
5897  // this is the master BE with a music storage group directory defined so run the file scanner
5898  LOG(VB_GENERAL, LOG_INFO, LOC +
5899  QString("HandleScanMusic: running filescanner on master BE '%1'").arg(hostname));
5900  QScopedPointer<MythSystem> cmd(MythSystem::Create(GetAppBinDir() + "mythutil --scanmusic",
5904  }
5905  else
5906  {
5907  // found a slave BE so ask it to run the file scanner
5909  if (slave)
5910  {
5911  LOG(VB_GENERAL, LOG_INFO, LOC +
5912  QString("HandleScanMusic: asking slave '%1' to run file scanner").arg(hostname));
5913  slave->ForwardRequest(slist);
5914  slave->DecrRef();
5915  }
5916  else
5917  {
5918  LOG(VB_GENERAL, LOG_INFO, LOC +
5919  QString("HandleScanMusic: Failed to grab slave socket on '%1'").arg(hostname));
5920  }
5921  }
5922  }
5923  }
5924  }
5925  else
5926  {
5927  // must be a slave with a music storage group directory defined so run the file scanner
5928  LOG(VB_GENERAL, LOG_INFO, LOC +
5929  QString("HandleScanMusic: running filescanner on slave BE '%1'")
5930  .arg(gCoreContext->GetHostName()));
5931  QScopedPointer<MythSystem> cmd(MythSystem::Create(GetAppBinDir() + "mythutil --scanmusic",
5935  }
5936 
5937  strlist << "OK";
5938 
5939  if (pbssock)
5940  SendResponse(pbssock, strlist);
5941 }
5942 
5944 {
5945 // format: MUSIC_TAG_UPDATE_VOLATILE <hostname> <songid> <rating> <playcount> <lastplayed>
5946 
5947  QStringList strlist;
5948 
5949  MythSocket *pbssock = pbs->getSocket();
5950 
5951  const QString& hostname = slist[1];
5952 
5954  {
5955  // forward the request to the slave BE
5957  if (slave)
5958  {
5959  LOG(VB_GENERAL, LOG_INFO, LOC +
5960  QString("HandleMusicTagUpdateVolatile: asking slave '%1' to update the metadata").arg(hostname));
5961  strlist = slave->ForwardRequest(slist);
5962  slave->DecrRef();
5963 
5964  if (pbssock)
5965  SendResponse(pbssock, strlist);
5966 
5967  return;
5968  }
5969 
5970  LOG(VB_GENERAL, LOG_INFO, LOC +
5971  QString("HandleMusicTagUpdateVolatile: Failed to grab slave socket on '%1'").arg(hostname));
5972 
5973  strlist << "ERROR: slave not found";
5974 
5975  if (pbssock)
5976  SendResponse(pbssock, strlist);
5977 
5978  return;
5979  }
5980 
5981  // run mythutil to update the metadata
5982  QStringList paramList;
5983  paramList.append(QString("--songid='%1'").arg(slist[2]));
5984  paramList.append(QString("--rating='%1'").arg(slist[3]));
5985  paramList.append(QString("--playcount='%1'").arg(slist[4]));
5986  paramList.append(QString("--lastplayed='%1'").arg(slist[5]));
5987 
5988  QString command = GetAppBinDir() + "mythutil --updatemeta " + paramList.join(" ");
5989 
5990  LOG(VB_GENERAL, LOG_INFO, LOC +
5991  QString("HandleMusicTagUpdateVolatile: running %1'").arg(command));
5992  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
5996 
5997  strlist << "OK";
5998 
5999  if (pbssock)
6000  SendResponse(pbssock, strlist);
6001 }
6002 
6003 void MainServer::HandleMusicCalcTrackLen(const QStringList &slist, PlaybackSock *pbs)
6004 {
6005 // format: MUSIC_CALC_TRACK_LENGTH <hostname> <songid>
6006 
6007  QStringList strlist;
6008 
6009  MythSocket *pbssock = pbs->getSocket();
6010 
6011  const QString& hostname = slist[1];
6012 
6014  {
6015  // forward the request to the slave BE
6017  if (slave)
6018  {
6019  LOG(VB_GENERAL, LOG_INFO, LOC +
6020  QString("HandleMusicCalcTrackLen: asking slave '%1' to update the track length").arg(hostname));
6021  strlist = slave->ForwardRequest(slist);
6022  slave->DecrRef();
6023 
6024  if (pbssock)
6025  SendResponse(pbssock, strlist);
6026 
6027  return;
6028  }
6029 
6030  LOG(VB_GENERAL, LOG_INFO, LOC +
6031  QString("HandleMusicCalcTrackLen: Failed to grab slave socket on '%1'").arg(hostname));
6032 
6033  strlist << "ERROR: slave not found";
6034 
6035  if (pbssock)
6036  SendResponse(pbssock, strlist);
6037 
6038  return;
6039  }
6040 
6041  // run mythutil to calc the tracks length
6042  QStringList paramList;
6043  paramList.append(QString("--songid='%1'").arg(slist[2]));
6044 
6045  QString command = GetAppBinDir() + "mythutil --calctracklen " + paramList.join(" ");
6046 
6047  LOG(VB_GENERAL, LOG_INFO, LOC +
6048  QString("HandleMusicCalcTrackLen: running %1'").arg(command));
6049  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
6053 
6054  strlist << "OK";
6055 
6056  if (pbssock)
6057  SendResponse(pbssock, strlist);
6058 }
6059 
6061 {
6062 // format: MUSIC_TAG_UPDATE_METADATA <hostname> <songid>
6063 // this assumes the new metadata has already been saved to the database for this track
6064 
6065  QStringList strlist;
6066 
6067  MythSocket *pbssock = pbs->getSocket();
6068 
6069  const QString& hostname = slist[1];
6070 
6072  {
6073  // forward the request to the slave BE
6075  if (slave)
6076  {
6077  LOG(VB_GENERAL, LOG_INFO, LOC +
6078  QString("HandleMusicTagUpdateMetadata: asking slave '%1' "
6079  "to update the metadata").arg(hostname));
6080  strlist = slave->ForwardRequest(slist);
6081  slave->DecrRef();
6082 
6083  if (pbssock)
6084  SendResponse(pbssock, strlist);
6085 
6086  return;
6087  }
6088 
6089  LOG(VB_GENERAL, LOG_INFO, LOC +
6090  QString("HandleMusicTagUpdateMetadata: Failed to grab "
6091  "slave socket on '%1'").arg(hostname));
6092 
6093  strlist << "ERROR: slave not found";
6094 
6095  if (pbssock)
6096  SendResponse(pbssock, strlist);
6097 
6098  return;
6099  }
6100 
6101  // load the new metadata from the database
6102  int songID = slist[2].toInt();
6103 
6104  MusicMetadata *mdata = MusicMetadata::createFromID(songID);
6105 
6106  if (!mdata)
6107  {
6108  LOG(VB_GENERAL, LOG_ERR, LOC +
6109  QString("HandleMusicTagUpdateMetadata: "
6110  "Cannot find metadata for trackid: %1")
6111  .arg(songID));
6112 
6113  strlist << "ERROR: track not found";
6114 
6115  if (pbssock)
6116  SendResponse(pbssock, strlist);
6117 
6118  return;
6119  }
6120 
6121  MetaIO *tagger = mdata->getTagger();
6122  if (tagger)
6123  {
6124  if (!tagger->write(mdata->getLocalFilename(), mdata))
6125  {
6126  LOG(VB_GENERAL, LOG_ERR, LOC +
6127  QString("HandleMusicTagUpdateMetadata: "
6128  "Failed to write to tag for trackid: %1")
6129  .arg(songID));
6130 
6131  strlist << "ERROR: write to tag failed";
6132 
6133  if (pbssock)
6134  SendResponse(pbssock, strlist);
6135 
6136  return;
6137  }
6138  }
6139 
6140  strlist << "OK";
6141 
6142  if (pbssock)
6143  SendResponse(pbssock, strlist);
6144 }
6145 
6146 
6147 void MainServer::HandleMusicFindAlbumArt(const QStringList &slist, PlaybackSock *pbs)
6148 {
6149 // format: MUSIC_FIND_ALBUMART <hostname> <songid> <update_database>
6150 
6151  QStringList strlist;
6152 
6153  MythSocket *pbssock = pbs->getSocket();
6154 
6155  const QString& hostname = slist[1];
6156 
6158  {
6159  // forward the request to the slave BE
6161  if (slave)
6162  {
6163  LOG(VB_GENERAL, LOG_INFO, LOC +
6164  QString("HandleMusicFindAlbumArt: asking slave '%1' "
6165  "to update the albumart").arg(hostname));
6166  strlist = slave->ForwardRequest(slist);
6167  slave->DecrRef();
6168 
6169  if (pbssock)
6170  SendResponse(pbssock, strlist);
6171 
6172  return;
6173  }
6174 
6175  LOG(VB_GENERAL, LOG_INFO, LOC +
6176  QString("HandleMusicFindAlbumArt: Failed to grab "
6177  "slave socket on '%1'").arg(hostname));
6178 
6179  strlist << "ERROR: slave not found";
6180 
6181  if (pbssock)
6182  SendResponse(pbssock, strlist);
6183 
6184  return;
6185  }
6186 
6187  // find the track in the database
6188  int songID = slist[2].toInt();
6189  bool updateDatabase = (slist[3].toInt() == 1);
6190 
6191  MusicMetadata *mdata = MusicMetadata::createFromID(songID);
6192 
6193  if (!mdata)
6194  {
6195  LOG(VB_GENERAL, LOG_ERR, LOC +
6196  QString("HandleMusicFindAlbumArt: "
6197  "Cannot find metadata for trackid: %1").arg(songID));
6198 
6199  strlist << "ERROR: track not found";
6200 
6201  if (pbssock)
6202  SendResponse(pbssock, strlist);
6203 
6204  return;
6205  }
6206 
6207  // find any directory images
6208  QFileInfo fi(mdata->getLocalFilename());
6209  QDir dir = fi.absoluteDir();
6210 
6211  QString nameFilter = gCoreContext->GetSetting("AlbumArtFilter",
6212  "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
6213  dir.setNameFilters(nameFilter.split(";"));
6214 
6215  QStringList files = dir.entryList();
6216 
6217  // create an empty image list
6218  auto *images = new AlbumArtImages(mdata, false);
6219 
6220  fi.setFile(mdata->Filename(false));
6221  QString startDir = fi.path();
6222 
6223  for (const QString& file : std::as_const(files))
6224  {
6225  fi.setFile(file);
6226  auto *image = new AlbumArtImage();
6227  image->m_filename = startDir + '/' + fi.fileName();
6228  image->m_hostname = gCoreContext->GetHostName();
6229  image->m_embedded = false;
6230  image->m_imageType = AlbumArtImages::guessImageType(image->m_filename);
6231  image->m_description = "";
6232  images->addImage(image);
6233  delete image;
6234  }
6235 
6236  // find any embedded albumart in the tracks tag
6237  MetaIO *tagger = mdata->getTagger();
6238  if (tagger)
6239  {
6240  if (tagger->supportsEmbeddedImages())
6241  {
6242  AlbumArtList artList = tagger->getAlbumArtList(mdata->getLocalFilename());
6243 
6244  for (int x = 0; x < artList.count(); x++)
6245  {
6246  AlbumArtImage *image = artList.at(x);
6247  image->m_filename = QString("%1-%2").arg(mdata->ID()).arg(image->m_filename);
6248  images->addImage(image);
6249  }
6250  }
6251 
6252  delete tagger;
6253  }
6254  else
6255  {
6256  LOG(VB_GENERAL, LOG_ERR, LOC +
6257  QString("HandleMusicFindAlbumArt: "
6258  "Failed to find a tagger for trackid: %1").arg(songID));
6259  }
6260 
6261  // finally save the result to the database
6262  if (updateDatabase)
6263  images->dumpToDatabase();
6264 
6265  strlist << "OK";
6266  strlist.append(QString("%1").arg(images->getImageCount()));
6267 
6268  for (uint x = 0; x < images->getImageCount(); x++)
6269  {
6270  AlbumArtImage *image = images->getImageAt(x);
6271  strlist.append(QString("%1").arg(image->m_id));
6272  strlist.append(QString("%1").arg((int)image->m_imageType));
6273  strlist.append(QString("%1").arg(static_cast<int>(image->m_embedded)));
6274  strlist.append(image->m_description);
6275  strlist.append(image->m_filename);
6276  strlist.append(image->m_hostname);
6277 
6278  // if this is an embedded image update the cached image
6279  if (image->m_embedded)
6280  {
6281  QStringList paramList;
6282  paramList.append(QString("--songid='%1'").arg(mdata->ID()));
6283  paramList.append(QString("--imagetype='%1'").arg(image->m_imageType));
6284 
6285  QString command = GetAppBinDir() + "mythutil --extractimage " + paramList.join(" ");
6286  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
6290  }
6291  }
6292 
6293  delete images;
6294 
6295  if (pbssock)
6296  SendResponse(pbssock, strlist);
6297 }
6298 
6299 void MainServer::HandleMusicTagGetImage(const QStringList &slist, PlaybackSock *pbs)
6300 {
6301 // format: MUSIC_TAG_GETIMAGE <hostname> <songid> <imagetype>
6302 
6303  QStringList strlist;
6304 
6305  MythSocket *pbssock = pbs->getSocket();
6306 
6307  const QString& hostname = slist[1];
6308  const QString& songid = slist[2];
6309  const QString& imagetype = slist[3];
6310 
6312  {
6313  // forward the request to the slave BE
6315  if (slave)
6316  {
6317  LOG(VB_GENERAL, LOG_INFO, LOC +
6318  QString("HandleMusicTagGetImage: asking slave '%1' to "
6319  "extract the image").arg(hostname));
6320  strlist = slave->ForwardRequest(slist);
6321  slave->DecrRef();
6322 
6323  if (pbssock)
6324  SendResponse(pbssock, strlist);
6325 
6326  return;
6327  }
6328 
6329  LOG(VB_GENERAL, LOG_INFO, LOC +
6330  QString("HandleMusicTagGetImage: Failed to grab slave "
6331  "socket on '%1'").arg(hostname));
6332  }
6333  else
6334  {
6335  QStringList paramList;
6336  paramList.append(QString("--songid='%1'").arg(songid));
6337  paramList.append(QString("--imagetype='%1'").arg(imagetype));
6338 
6339  QString command = GetAppBinDir() + "mythutil --extractimage " + paramList.join(" ");
6340 
6341  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
6345  }
6346 
6347  strlist << "OK";
6348 
6349  if (pbssock)
6350  SendResponse(pbssock, strlist);
6351 }
6352 
6354 {
6355 // format: MUSIC_TAG_CHANGEIMAGE <hostname> <songid> <oldtype> <newtype>
6356 
6357  QStringList strlist;
6358 
6359  MythSocket *pbssock = pbs->getSocket();
6360 
6361  const QString& hostname = slist[1];
6362 
6364  {
6365  // forward the request to the slave BE
6367  if (slave)
6368  {
6369  LOG(VB_GENERAL, LOG_INFO, LOC +
6370  QString("HandleMusicTagChangeImage: asking slave '%1' "
6371  "to update the metadata").arg(hostname));
6372  strlist = slave->ForwardRequest(slist);
6373  slave->DecrRef();
6374 
6375  if (pbssock)
6376  SendResponse(pbssock, strlist);
6377 
6378  return;
6379  }
6380 
6381  LOG(VB_GENERAL, LOG_INFO, LOC +
6382  QString("HandleMusicTagChangeImage: Failed to grab "
6383  "slave socket on '%1'").arg(hostname));
6384 
6385  strlist << "ERROR: slave not found";
6386 
6387  if (pbssock)
6388  SendResponse(pbssock, strlist);
6389 
6390  return;
6391  }
6392 
6393  int songID = slist[2].toInt();
6394  auto oldType = (ImageType)slist[3].toInt();
6395  auto newType = (ImageType)slist[4].toInt();
6396 
6397  // load the metadata from the database
6398  MusicMetadata *mdata = MusicMetadata::createFromID(songID);
6399 
6400  if (!mdata)
6401  {
6402  LOG(VB_GENERAL, LOG_ERR, LOC +
6403  QString("HandleMusicTagChangeImage: "
6404  "Cannot find metadata for trackid: %1")
6405  .arg(songID));
6406 
6407  strlist << "ERROR: track not found";
6408 
6409  if (pbssock)
6410  SendResponse(pbssock, strlist);
6411 
6412  return;
6413  }
6414 
6415  mdata->setFilename(mdata->getLocalFilename());
6416 
6417  AlbumArtImages *albumArt = mdata->getAlbumArtImages();
6418  AlbumArtImage *image = albumArt->getImage(oldType);
6419  if (image)
6420  {
6421  AlbumArtImage oldImage = *image;
6422 
6423  image->m_imageType = newType;
6424 
6425  if (image->m_imageType == oldImage.m_imageType)
6426  {
6427  // nothing to change
6428  strlist << "OK";
6429 
6430  if (pbssock)
6431  SendResponse(pbssock, strlist);
6432 
6433  delete mdata;
6434 
6435  return;
6436  }
6437 
6438  // rename any cached image to match the new type
6439  if (image->m_embedded)
6440  {
6441  // change the image type in the tag if it supports it
6442  MetaIO *tagger = mdata->getTagger();
6443 
6444  if (tagger && tagger->supportsEmbeddedImages())
6445  {
6446  if (!tagger->changeImageType(mdata->getLocalFilename(), &oldImage, image->m_imageType))
6447  {
6448  LOG(VB_GENERAL, LOG_ERR, "HandleMusicTagChangeImage: failed to change image type");
6449 
6450  strlist << "ERROR: failed to change image type";
6451 
6452  if (pbssock)
6453  SendResponse(pbssock, strlist);
6454 
6455  delete mdata;
6456  delete tagger;
6457  return;
6458  }
6459  }
6460 
6461  delete tagger;
6462 
6463  // update the new cached image filename
6464  StorageGroup artGroup("MusicArt", gCoreContext->GetHostName(), false);
6465  oldImage.m_filename = artGroup.FindFile("AlbumArt/" + image->m_filename);
6466 
6467  QFileInfo fi(oldImage.m_filename);
6468  image->m_filename = fi.path() + QString("/%1-%2.jpg")
6469  .arg(mdata->ID())
6471 
6472  // remove any old cached file with the same name as the new one
6473  if (QFile::exists(image->m_filename))
6474  QFile::remove(image->m_filename);
6475 
6476  // rename the old cached file to the new one
6477  if (image->m_filename != oldImage.m_filename && QFile::exists(oldImage.m_filename))
6478  QFile::rename(oldImage.m_filename, image->m_filename);
6479  else
6480  {
6481  // extract the image from the tag and cache it
6482  QStringList paramList;
6483  paramList.append(QString("--songid='%1'").arg(mdata->ID()));
6484  paramList.append(QString("--imagetype='%1'").arg(image->m_imageType));
6485 
6486  QString command = GetAppBinDir() + "mythutil --extractimage " + paramList.join(" ");
6487 
6488  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
6492  }
6493  }
6494  else
6495  {
6496  QFileInfo fi(oldImage.m_filename);
6497 
6498  // get the new images filename
6499  image->m_filename = fi.absolutePath() + QString("/%1.jpg")
6501 
6502  if (image->m_filename != oldImage.m_filename && QFile::exists(oldImage.m_filename))
6503  {
6504  // remove any old cached file with the same name as the new one
6505  QFile::remove(image->m_filename);
6506  // rename the old cached file to the new one
6507  QFile::rename(oldImage.m_filename, image->m_filename);
6508  }
6509  }
6510  }
6511 
6512  delete mdata;
6513 
6514  strlist << "OK";
6515 
6516  if (pbssock)
6517  SendResponse(pbssock, strlist);
6518 }
6519 
6520 void MainServer::HandleMusicTagAddImage(const QStringList& slist, PlaybackSock* pbs)
6521 {
6522 // format: MUSIC_TAG_ADDIMAGE <hostname> <songid> <filename> <imagetype>
6523 
6524  QStringList strlist;
6525 
6526  MythSocket *pbssock = pbs->getSocket();
6527 
6528  const QString& hostname = slist[1];
6529 
6531  {
6532  // forward the request to the slave BE
6534  if (slave)
6535  {
6536  LOG(VB_GENERAL, LOG_INFO, LOC +
6537  QString("HandleMusicTagAddImage: asking slave '%1' "
6538  "to add the image").arg(hostname));
6539  strlist = slave->ForwardRequest(slist);
6540  slave->DecrRef();
6541 
6542  if (pbssock)
6543  SendResponse(pbssock, strlist);
6544 
6545  return;
6546  }
6547 
6548  LOG(VB_GENERAL, LOG_INFO, LOC +
6549  QString("HandleMusicTagAddImage: Failed to grab "
6550  "slave socket on '%1'").arg(hostname));
6551 
6552  strlist << "ERROR: slave not found";
6553 
6554  if (pbssock)
6555  SendResponse(pbssock, strlist);
6556 
6557  return;
6558  }
6559 
6560  // load the metadata from the database
6561  int songID = slist[2].toInt();
6562  const QString& filename = slist[3];
6563  auto imageType = (ImageType) slist[4].toInt();
6564 
6565  MusicMetadata *mdata = MusicMetadata::createFromID(songID);
6566 
6567  if (!mdata)
6568  {
6569  LOG(VB_GENERAL, LOG_ERR, LOC +
6570  QString("HandleMusicTagAddImage: Cannot find metadata for trackid: %1")
6571  .arg(songID));
6572 
6573  strlist << "ERROR: track not found";
6574 
6575  if (pbssock)
6576  SendResponse(pbssock, strlist);
6577 
6578  return;
6579  }
6580 
6581  MetaIO *tagger = mdata->getTagger();
6582 
6583  if (!tagger)
6584  {
6585  LOG(VB_GENERAL, LOG_ERR, LOC +
6586  "HandleMusicTagAddImage: failed to find a tagger for track");
6587 
6588  strlist << "ERROR: tagger not found";
6589 
6590  if (pbssock)
6591  SendResponse(pbssock, strlist);
6592 
6593  delete mdata;
6594  return;
6595  }
6596 
6597  if (!tagger->supportsEmbeddedImages())
6598  {
6599  LOG(VB_GENERAL, LOG_ERR, LOC +
6600  "HandleMusicTagAddImage: asked to write album art to the tag "
6601  "but the tagger doesn't support it!");
6602 
6603  strlist << "ERROR: embedded images not supported by tag";
6604 
6605  if (pbssock)
6606  SendResponse(pbssock, strlist);
6607 
6608  delete tagger;
6609  delete mdata;
6610  return;
6611  }
6612 
6613  // is the image in the 'MusicArt' storage group
6614  bool isDirectoryImage = false;
6615  StorageGroup storageGroup("MusicArt", gCoreContext->GetHostName(), false);
6616  QString imageFilename = storageGroup.FindFile("AlbumArt/" + filename);
6617  if (imageFilename.isEmpty())
6618  {
6619  // not found there so look in the tracks directory
6620  QFileInfo fi(mdata->getLocalFilename());
6621  imageFilename = fi.absolutePath() + '/' + filename;
6622  isDirectoryImage = true;
6623  }
6624 
6625  if (!QFile::exists(imageFilename))
6626  {
6627  LOG(VB_GENERAL, LOG_ERR, LOC +
6628  QString("HandleMusicTagAddImage: cannot find image file %1").arg(filename));
6629 
6630  strlist << "ERROR: failed to find image file";
6631 
6632  if (pbssock)
6633  SendResponse(pbssock, strlist);
6634 
6635  delete tagger;
6636  delete mdata;
6637  return;
6638  }
6639 
6640  AlbumArtImage image;
6641  image.m_filename = imageFilename;
6642  image.m_imageType = imageType;
6643 
6644  if (!tagger->writeAlbumArt(mdata->getLocalFilename(), &image))
6645  {
6646  LOG(VB_GENERAL, LOG_ERR, LOC + "HandleMusicTagAddImage: failed to write album art to tag");
6647 
6648  strlist << "ERROR: failed to write album art to tag";
6649 
6650  if (pbssock)
6651  SendResponse(pbssock, strlist);
6652 
6653  if (!isDirectoryImage)
6654  QFile::remove(imageFilename);
6655 
6656  delete tagger;
6657  delete mdata;
6658  return;
6659  }
6660 
6661  // only remove the image if we temporarily saved one to the 'AlbumArt' storage group
6662  if (!isDirectoryImage)
6663  QFile::remove(imageFilename);
6664 
6665  delete tagger;
6666  delete mdata;
6667 
6668  strlist << "OK";
6669 
6670  if (pbssock)
6671  SendResponse(pbssock, strlist);
6672 }
6673 
6674 
6676 {
6677 // format: MUSIC_TAG_REMOVEIMAGE <hostname> <songid> <imageid>
6678 
6679  QStringList strlist;
6680 
6681  MythSocket *pbssock = pbs->getSocket();
6682 
6683  const QString& hostname = slist[1];
6684 
6686  {
6687  // forward the request to the slave BE
6689  if (slave)
6690  {
6691  LOG(VB_GENERAL, LOG_INFO, LOC +
6692  QString("HandleMusicTagRemoveImage: asking slave '%1' "
6693  "to remove the image").arg(hostname));
6694  strlist = slave->ForwardRequest(slist);
6695  slave->DecrRef();
6696 
6697  if (pbssock)
6698  SendResponse(pbssock, strlist);
6699 
6700  return;
6701  }
6702 
6703  LOG(VB_GENERAL, LOG_INFO, LOC +
6704  QString("HandleMusicTagRemoveImage: Failed to grab "
6705  "slave socket on '%1'").arg(hostname));
6706 
6707  strlist << "ERROR: slave not found";
6708 
6709  if (pbssock)
6710  SendResponse(pbssock, strlist);
6711 
6712  return;
6713  }
6714 
6715  int songID = slist[2].toInt();
6716  int imageID = slist[3].toInt();
6717 
6718  // load the metadata from the database
6719  MusicMetadata *mdata = MusicMetadata::createFromID(songID);
6720 
6721  if (!mdata)
6722  {
6723  LOG(VB_GENERAL, LOG_ERR, LOC +
6724  QString("HandleMusicTagRemoveImage: Cannot find metadata for trackid: %1")
6725  .arg(songID));
6726 
6727  strlist << "ERROR: track not found";
6728 
6729  if (pbssock)
6730  SendResponse(pbssock, strlist);
6731 
6732  return;
6733  }
6734 
6735  MetaIO *tagger = mdata->getTagger();
6736 
6737  if (!tagger)
6738  {
6739  LOG(VB_GENERAL, LOG_ERR, LOC +
6740  "HandleMusicTagRemoveImage: failed to find a tagger for track");
6741 
6742  strlist << "ERROR: tagger not found";
6743 
6744  if (pbssock)
6745  SendResponse(pbssock, strlist);
6746 
6747  delete mdata;
6748  return;
6749  }
6750 
6751  if (!tagger->supportsEmbeddedImages())
6752  {
6753  LOG(VB_GENERAL, LOG_ERR, LOC + "HandleMusicTagRemoveImage: asked to remove album art "
6754  "from the tag but the tagger doesn't support it!");
6755 
6756  strlist << "ERROR: embedded images not supported by tag";
6757 
6758  if (pbssock)
6759  SendResponse(pbssock, strlist);
6760 
6761  delete mdata;
6762  delete tagger;
6763  return;
6764  }
6765 
6766  AlbumArtImage *image = mdata->getAlbumArtImages()->getImageByID(imageID);
6767  if (!image)
6768  {
6769  LOG(VB_GENERAL, LOG_ERR, LOC +
6770  QString("HandleMusicTagRemoveImage: Cannot find image for imageid: %1")
6771  .arg(imageID));
6772 
6773  strlist << "ERROR: image not found";
6774 
6775  if (pbssock)
6776  SendResponse(pbssock, strlist);
6777 
6778  delete mdata;
6779  delete tagger;
6780  return;
6781  }
6782 
6783  if (!tagger->removeAlbumArt(mdata->getLocalFilename(), image))
6784  {
6785  LOG(VB_GENERAL, LOG_ERR, LOC + "HandleMusicTagRemoveImage: failed to remove album art from tag");
6786 
6787  strlist << "ERROR: failed to remove album art from tag";
6788 
6789  if (pbssock)
6790  SendResponse(pbssock, strlist);
6791 
6792  return;
6793  }
6794 
6795  strlist << "OK";
6796 
6797  if (pbssock)
6798  SendResponse(pbssock, strlist);
6799 }
6800 
6801 void MainServer::HandleMusicFindLyrics(const QStringList &slist, PlaybackSock *pbs)
6802 {
6803 // format: MUSIC_LYRICS_FIND <hostname> <songid> <grabbername> <artist (optional)> <album (optional)> <title (optional)>
6804 // if artist is present then album and title must also be included (only used for radio and cd tracks)
6805 
6806  QStringList strlist;
6807 
6808  MythSocket *pbssock = pbs->getSocket();
6809 
6810  const QString& hostname = slist[1];
6811  const QString& songid = slist[2];
6812  const QString& grabberName = slist[3];
6813  QString artist = "";
6814  QString album = "";
6815  QString title = "";
6816 
6817  if (slist.size() == 7)
6818  {
6819  artist = slist[4];
6820  album = slist[5];
6821  title = slist[6];
6822  }
6823 
6825  {
6826  // forward the request to the slave BE
6828  if (slave)
6829  {
6830  LOG(VB_GENERAL, LOG_INFO, LOC +
6831  QString("HandleMusicFindLyrics: asking slave '%1' to "
6832  "find lyrics").arg(hostname));
6833  strlist = slave->ForwardRequest(slist);
6834  slave->DecrRef();
6835 
6836  if (pbssock)
6837  SendResponse(pbssock, strlist);
6838 
6839  return;
6840  }
6841 
6842  LOG(VB_GENERAL, LOG_INFO, LOC +
6843  QString("HandleMusicFindLyrics: Failed to grab slave "
6844  "socket on '%1'").arg(hostname));
6845  }
6846  else
6847  {
6848  QStringList paramList;
6849  paramList.append(QString("--songid='%1'").arg(songid));
6850  paramList.append(QString("--grabber='%1'").arg(grabberName));
6851 
6852  if (!artist.isEmpty())
6853  paramList.append(QString("--artist=\"%1\"").arg(artist));
6854 
6855  if (!album.isEmpty())
6856  paramList.append(QString("--album=\"%1\"").arg(album));
6857 
6858  if (!title.isEmpty())
6859  paramList.append(QString("--title=\"%1\"").arg(title));
6860 
6861  QString command = GetAppBinDir() + "mythutil --findlyrics " + paramList.join(" ");
6862 
6863  QScopedPointer<MythSystem> cmd(MythSystem::Create(command,
6867  }
6868 
6869  strlist << "OK";
6870 
6871  if (pbssock)
6872  SendResponse(pbssock, strlist);
6873 }
6874 
6893 void MainServer::HandleMusicGetLyricGrabbers(const QStringList &/*slist*/, PlaybackSock *pbs)
6894 {
6895  QStringList strlist;
6896 
6897  MythSocket *pbssock = pbs->getSocket();
6898 
6899  QString scriptDir = GetShareDir() + "metadata/Music/lyrics";
6900  QDir d(scriptDir);
6901 
6902  if (!d.exists())
6903  {
6904  LOG(VB_GENERAL, LOG_ERR, QString("Cannot find lyric scripts directory: %1").arg(scriptDir));
6905  strlist << QString("ERROR: Cannot find lyric scripts directory: %1").arg(scriptDir);
6906 
6907  if (pbssock)
6908  SendResponse(pbssock, strlist);
6909 
6910  return;
6911  }
6912 
6913  d.setFilter(QDir::Files | QDir::NoDotAndDotDot);
6914  d.setNameFilters(QStringList("*.py"));
6915  QFileInfoList list = d.entryInfoList();
6916  if (list.isEmpty())
6917  {
6918  LOG(VB_GENERAL, LOG_ERR, QString("Cannot find any lyric scripts in: %1").arg(scriptDir));
6919  strlist << QString("ERROR: Cannot find any lyric scripts in: %1").arg(scriptDir);
6920 
6921  if (pbssock)
6922  SendResponse(pbssock, strlist);
6923 
6924  return;
6925  }
6926 
6927  QStringList scripts;
6928  for (const auto & fi : std::as_const(list))
6929  {
6930  LOG(VB_FILE, LOG_NOTICE, QString("Found lyric script at: %1").arg(fi.filePath()));
6931  scripts.append(fi.filePath());
6932  }
6933 
6934  QStringList grabbers;
6935 
6936  // query the grabbers to get their name
6937  for (int x = 0; x < scripts.count(); x++)
6938  {
6939  QStringList args { scripts.at(x), "-v" };
6940  QProcess p;
6941  p.start(PYTHON_EXE, args);
6942  p.waitForFinished(-1);
6943  QString result = p.readAllStandardOutput();
6944 
6945  QDomDocument domDoc;
6946  QString errorMsg;
6947  int errorLine = 0;
6948  int errorColumn = 0;
6949 
6950  if (!domDoc.setContent(result, false, &errorMsg, &errorLine, &errorColumn))
6951  {
6952  LOG(VB_GENERAL, LOG_ERR,
6953  QString("FindLyrics: Could not parse version from %1").arg(scripts.at(x)) +
6954  QString("\n\t\t\tError at line: %1 column: %2 msg: %3").arg(errorLine).arg(errorColumn).arg(errorMsg));
6955  continue;
6956  }
6957 
6958  QDomNodeList itemList = domDoc.elementsByTagName("grabber");
6959  QDomNode itemNode = itemList.item(0);
6960 
6961  grabbers.append(itemNode.namedItem(QString("name")).toElement().text());
6962  }
6963 
6964  grabbers.sort();
6965 
6966  strlist << "OK";
6967 
6968  for (int x = 0; x < grabbers.count(); x++)
6969  strlist.append(grabbers.at(x));
6970 
6971  if (pbssock)
6972  SendResponse(pbssock, strlist);
6973 }
6974 
6975 void MainServer::HandleMusicSaveLyrics(const QStringList& slist, PlaybackSock* pbs)
6976 {
6977 // format: MUSIC_LYRICS_SAVE <hostname> <songid>
6978 // followed by the lyrics lines
6979 
6980  QStringList strlist;
6981 
6982  MythSocket *pbssock = pbs->getSocket();
6983 
6984  const QString& hostname = slist[1];
6985  int songID = slist[2].toInt();
6986 
6988  {
6989  // forward the request to the slave BE
6991  if (slave)
6992  {
6993  LOG(VB_GENERAL, LOG_INFO, LOC +
6994  QString("HandleMusicSaveLyrics: asking slave '%1' to "
6995  "save the lyrics").arg(hostname));
6996  strlist = slave->ForwardRequest(slist);
6997  slave->DecrRef();
6998 
6999  if (pbssock)
7000  SendResponse(pbssock, strlist);
7001 
7002  return;
7003  }
7004 
7005  LOG(VB_GENERAL, LOG_INFO, LOC +
7006  QString("HandleMusicSaveLyrics: Failed to grab slave "
7007  "socket on '%1'").arg(hostname));
7008  }
7009  else
7010  {
7011  MusicMetadata *mdata = MusicMetadata::createFromID(songID);
7012  if (!mdata)
7013  {
7014  LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
7015  strlist << QString("ERROR: Cannot find metadata for trackid: %1").arg(songID);
7016 
7017  if (pbssock)
7018  SendResponse(pbssock, strlist);
7019 
7020  return;
7021  }
7022 
7023  QString lyricsFile = GetConfDir() + QString("/MythMusic/Lyrics/%1.txt").arg(songID);
7024 
7025  // remove any existing lyrics for this songID
7026  if (QFile::exists(lyricsFile))
7027  QFile::remove(lyricsFile);
7028 
7029  // save the new lyrics
7030  QFile file(QLatin1String(qPrintable(lyricsFile)));
7031 
7032  if (file.open(QIODevice::WriteOnly))
7033  {
7034  QTextStream stream(&file);
7035  for (int x = 3; x < slist.count(); x++)
7036  stream << slist.at(x);
7037  file.close();
7038  }
7039  }
7040 
7041  strlist << "OK";
7042 
7043  if (pbssock)
7044  SendResponse(pbssock, strlist);
7045 }
7046 
7047 void MainServer::HandleFileTransferQuery(QStringList &slist,
7048  QStringList &commands,
7049  PlaybackSock *pbs)
7050 {
7051  MythSocket *pbssock = pbs->getSocket();
7052 
7053  int recnum = commands[1].toInt();
7054  QString command = slist[1];
7055 
7056  QStringList retlist;
7057 
7058  m_sockListLock.lockForRead();
7059  BEFileTransfer *ft = GetFileTransferByID(recnum);
7060  if (!ft)
7061  {
7062  if (command == "DONE")
7063  {
7064  // if there is an error opening the file, we may not have a
7065  // BEFileTransfer instance for this connection.
7066  retlist << "OK";
7067  }
7068  else
7069  {
7070  LOG(VB_GENERAL, LOG_ERR, LOC +
7071  QString("Unknown file transfer socket: %1").arg(recnum));
7072  retlist << QString("ERROR: Unknown file transfer socket: %1")
7073  .arg(recnum);
7074  }
7075 
7076  m_sockListLock.unlock();
7077  SendResponse(pbssock, retlist);
7078  return;
7079  }
7080 
7081  ft->IncrRef();
7082  m_sockListLock.unlock();
7083 
7084  if (command == "REQUEST_BLOCK")
7085  {
7086  int size = slist[2].toInt();
7087 
7088  retlist << QString::number(ft->RequestBlock(size));
7089  }
7090  else if (command == "WRITE_BLOCK")
7091  {
7092  int size = slist[2].toInt();
7093 
7094  retlist << QString::number(ft->WriteBlock(size));
7095  }
7096  else if (command == "SEEK")
7097  {
7098  long long pos = slist[2].toLongLong();
7099  int whence = slist[3].toInt();
7100  long long curpos = slist[4].toLongLong();
7101 
7102  long long ret = ft->Seek(curpos, pos, whence);
7103  retlist << QString::number(ret);
7104  }
7105  else if (command == "IS_OPEN")
7106  {
7107  bool isopen = ft->isOpen();
7108 
7109  retlist << QString::number(static_cast<int>(isopen));
7110  }
7111  else if (command == "REOPEN")
7112  {
7113  retlist << QString::number(static_cast<int>(ft->ReOpen(slist[2])));
7114  }
7115  else if (command == "DONE")
7116  {
7117  ft->Stop();
7118  retlist << "OK";
7119  }
7120  else if (command == "SET_TIMEOUT")
7121  {
7122  bool fast = slist[2].toInt() != 0;
7123  ft->SetTimeout(fast);
7124  retlist << "OK";
7125  }
7126  else if (command == "REQUEST_SIZE")
7127  {
7128  // return size and if the file is not opened for writing
7129  retlist << QString::number(ft->GetFileSize());
7130  retlist << QString::number(static_cast<int>(!gCoreContext->IsRegisteredFileForWrite(ft->GetFileName())));
7131  }
7132  else
7133  {
7134  LOG(VB_GENERAL, LOG_ERR, LOC +
7135  QString("Unknown command: %1").arg(command));
7136  retlist << "ERROR" << "invalid_call";
7137  }
7138 
7139  ft->DecrRef();
7140 
7141  SendResponse(pbssock, retlist);
7142 }
7143 
7145 {
7146  MythSocket *pbssock = pbs->getSocket();
7147 
7148  int retval = -1;
7149 
7150  QStringList::const_iterator it = slist.cbegin() + 1;
7151  ProgramInfo pginfo(it, slist.cend());
7152 
7153  EncoderLink *encoder = nullptr;
7154 
7155  TVRec::s_inputsLock.lockForRead();
7156  for (auto iter = m_encoderList->constBegin(); iter != m_encoderList->constEnd(); ++iter)
7157  {
7158  EncoderLink *elink = *iter;
7159 
7160  if (elink->IsConnected() && elink->MatchesRecording(&pginfo))
7161  {
7162  retval = iter.key();
7163  encoder = elink;
7164  }
7165  }
7166  TVRec::s_inputsLock.unlock();
7167 
7168  QStringList strlist( QString::number(retval) );
7169 
7170  if (encoder)
7171  {
7172  if (encoder->IsLocal())
7173  {
7174  strlist << gCoreContext->GetBackendServerIP();
7175  strlist << QString::number(gCoreContext->GetBackendServerPort());
7176  }
7177  else
7178  {
7179  strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName());
7180  strlist << QString::number(gCoreContext->GetBackendServerPort(encoder->GetHostName()));
7181  }
7182  }
7183  else
7184  {
7185  strlist << "nohost";
7186  strlist << "-1";
7187  }
7188 
7189  SendResponse(pbssock, strlist);
7190 }
7191 
7193  PlaybackSock *pbs)
7194 {
7195  MythSocket *pbssock = pbs->getSocket();
7196 
7197  int recordernum = slist[1].toInt();
7198  EncoderLink *encoder = nullptr;
7199  QStringList strlist;
7200 
7201  TVRec::s_inputsLock.lockForRead();
7202  auto iter = m_encoderList->constFind(recordernum);
7203  if (iter != m_encoderList->constEnd())
7204  encoder = (*iter);
7205  TVRec::s_inputsLock.unlock();
7206 
7207  if (encoder && encoder->IsConnected())
7208  {
7209  if (encoder->IsLocal())
7210  {
7211  strlist << gCoreContext->GetBackendServerIP();
7212  strlist << QString::number(gCoreContext->GetBackendServerPort());
7213  }
7214  else
7215  {
7216  strlist << gCoreContext->GetBackendServerIP(encoder->GetHostName());
7217  strlist << QString::number(gCoreContext->GetBackendServerPort(encoder->GetHostName()));
7218  }
7219  }
7220  else
7221  {
7222  strlist << "nohost";
7223  strlist << "-1";
7224  }
7225 
7226  SendResponse(pbssock, strlist);
7227 }
7228 
7229 void MainServer::HandleMessage(QStringList &slist, PlaybackSock *pbs)
7230 {
7231  if (slist.size() < 2)
7232  return;
7233 
7234  MythSocket *pbssock = pbs->getSocket();
7235 
7236  QString message = slist[1];
7237  QStringList extra_data;
7238  for (uint i = 2; i < (uint) slist.size(); i++)
7239  extra_data.push_back(slist[i]);
7240 
7241  if (extra_data.empty())
7242  {
7243  MythEvent me(message);
7244  gCoreContext->dispatch(me);
7245  }
7246  else
7247  {
7248  MythEvent me(message, extra_data);
7249  gCoreContext->dispatch(me);
7250  }
7251 
7252  QStringList retlist( "OK" );
7253 
7254  SendResponse(pbssock, retlist);
7255 }
7256 
7257 void MainServer::HandleSetVerbose(const QStringList &slist, PlaybackSock *pbs)
7258 {
7259  MythSocket *pbssock = pbs->getSocket();
7260  QStringList retlist;
7261 
7262  const QString& newverbose = slist[1];
7263  int len = newverbose.length();
7264  if (len > 12)
7265  {
7266  verboseArgParse(newverbose.right(len-12));
7267  logPropagateCalc();
7268 
7269  LOG(VB_GENERAL, LOG_NOTICE, LOC +
7270  QString("Verbose mask changed, new mask is: %1").arg(verboseString));
7271 
7272  retlist << "OK";
7273  }
7274  else
7275  {
7276  LOG(VB_GENERAL, LOG_ERR, LOC +
7277  QString("Invalid SET_VERBOSE string: '%1'").arg(newverbose));
7278  retlist << "Failed";
7279  }
7280 
7281  SendResponse(pbssock, retlist);
7282 }
7283 
7284 void MainServer::HandleSetLogLevel(const QStringList &slist, PlaybackSock *pbs)
7285 {
7286  MythSocket *pbssock = pbs->getSocket();
7287  QStringList retlist;
7288  const QString& newstring = slist[1];
7289  LogLevel_t newlevel = LOG_UNKNOWN;
7290 
7291  int len = newstring.length();
7292  if (len > 14)
7293  {
7294  newlevel = logLevelGet(newstring.right(len-14));
7295  if (newlevel != LOG_UNKNOWN)
7296  {
7297  logLevel = newlevel;
7298  logPropagateCalc();
7299  LOG(VB_GENERAL, LOG_NOTICE, LOC +
7300  QString("Log level changed, new level is: %1")
7301  .arg(logLevelGetName(logLevel)));
7302 
7303  retlist << "OK";
7304  }
7305  }
7306 
7307  if (newlevel == LOG_UNKNOWN)
7308  {
7309  LOG(VB_GENERAL, LOG_ERR, LOC +
7310  QString("Invalid SET_VERBOSE string: '%1'").arg(newstring));
7311  retlist << "Failed";
7312  }
7313 
7314  SendResponse(pbssock, retlist);
7315 }
7316 
7317 void MainServer::HandleIsRecording([[maybe_unused]] const QStringList &slist,
7318  PlaybackSock *pbs)
7319 {
7320  MythSocket *pbssock = pbs->getSocket();
7321  int RecordingsInProgress = 0;
7322  int LiveTVRecordingsInProgress = 0;
7323  QStringList retlist;
7324 
7325  TVRec::s_inputsLock.lockForRead();
7326  for (auto * elink : std::as_const(*m_encoderList))
7327  {
7328  if (elink->IsBusyRecording()) {
7329  RecordingsInProgress++;
7330 
7331  ProgramInfo *info = elink->GetRecording();
7332  if (info && info->GetRecordingGroup() == "LiveTV")
7333  LiveTVRecordingsInProgress++;
7334 
7335  delete info;
7336  }
7337  }
7338  TVRec::s_inputsLock.unlock();
7339 
7340  retlist << QString::number(RecordingsInProgress);
7341  retlist << QString::number(LiveTVRecordingsInProgress);
7342 
7343  SendResponse(pbssock, retlist);
7344 }
7345 
7347 {
7348  MythSocket *pbssock = pbs->getSocket();
7349 
7350  if (slist.size() < 3)
7351  {
7352  LOG(VB_GENERAL, LOG_ERR, LOC + "Too few params in pixmap request");
7353  QStringList outputlist("ERROR");
7354  outputlist += "TOO_FEW_PARAMS";
7355  SendResponse(pbssock, outputlist);
7356  return;
7357  }
7358 
7359  bool time_fmt_sec = true;
7360  std::chrono::seconds time = std::chrono::seconds::max();
7361  long long frame = -1;
7362  QString outputfile;
7363  int width = -1;
7364  int height = -1;
7365  bool has_extra_data = false;
7366 
7367  QString token = slist[1];
7368  if (token.isEmpty())
7369  {
7370  LOG(VB_GENERAL, LOG_ERR, LOC +
7371  "Failed to parse pixmap request. Token absent");
7372  QStringList outputlist("ERROR");
7373  outputlist += "TOKEN_ABSENT";
7374  SendResponse(pbssock, outputlist);
7375  return;
7376  }
7377 
7378  QStringList::const_iterator it = slist.cbegin() + 2;
7379  QStringList::const_iterator end = slist.cend();
7380  ProgramInfo pginfo(it, end);
7381  bool ok = pginfo.HasPathname();
7382  if (!ok)
7383  {
7384  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to parse pixmap request. "
7385  "ProgramInfo missing pathname");
7386  QStringList outputlist("BAD");
7387  outputlist += "NO_PATHNAME";
7388  SendResponse(pbssock, outputlist);
7389  return;
7390  }
7391  if (token.toLower() == "do_not_care")
7392  {
7393  token = QString("%1:%2")
7394  .arg(pginfo.MakeUniqueKey()).arg(MythRandom());
7395  }
7396  if (it != slist.cend())
7397  (time_fmt_sec = ((*it).toLower() == "s")), ++it;
7398  if (it != slist.cend())
7399  {
7400  if (time_fmt_sec)
7401  time = std::chrono::seconds((*it).toLongLong()), ++it;
7402  else
7403  frame = (*it).toLongLong(), ++it;
7404  }
7405  if (it != slist.cend())
7406  (outputfile = *it), ++it;
7407  outputfile = (outputfile == "<EMPTY>") ? QString() : outputfile;
7408  if (it != slist.cend())
7409  {
7410  width = (*it).toInt(&ok); ++it;
7411  width = (ok) ? width : -1;
7412  }
7413  if (it != slist.cend())
7414  {
7415  height = (*it).toInt(&ok); ++it;
7416  height = (ok) ? height : -1;
7417  has_extra_data = true;
7418  }
7419  QSize outputsize = QSize(width, height);
7420 
7421  if (has_extra_data)
7422  {
7423  auto pos_text = (time != std::chrono::seconds::max())
7424  ? QString::number(time.count()) + "s"
7425  : QString::number(frame) + "f";
7426  LOG(VB_PLAYBACK, LOG_INFO, LOC +
7427  QString("HandleGenPreviewPixmap got extra data\n\t\t\t"
7428  "%1 %2x%3 '%4'")
7429  .arg(pos_text)
7430  .arg(width).arg(height).arg(outputfile));
7431  }
7432 
7433  pginfo.SetPathname(GetPlaybackURL(&pginfo));
7434 
7435  m_previewRequestedBy[token] = pbs->getHostname();
7436 
7437  if ((m_ismaster) &&
7438  (pginfo.GetHostname() != gCoreContext->GetHostName()) &&
7439  (!m_masterBackendOverride || !pginfo.IsLocal()))
7440  {
7441  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
7442 
7443  if (slave)
7444  {
7445  QStringList outputlist;
7446  if (has_extra_data)
7447  {
7448  if (time != std::chrono::seconds::max())
7449  {
7450  outputlist = slave->GenPreviewPixmap(
7451  token, &pginfo, time, -1, outputfile, outputsize);
7452  }
7453  else
7454  {
7455  outputlist = slave->GenPreviewPixmap(
7456  token, &pginfo, std::chrono::seconds::max(), frame, outputfile, outputsize);
7457  }
7458  }
7459  else
7460  {
7461  outputlist = slave->GenPreviewPixmap(token, &pginfo);
7462  }
7463 
7464  slave->DecrRef();
7465 
7466  if (outputlist.empty() || outputlist[0] != "OK")
7467  m_previewRequestedBy.remove(token);
7468 
7469  SendResponse(pbssock, outputlist);
7470  return;
7471  }
7472  LOG(VB_GENERAL, LOG_ERR, LOC +
7473  QString("HandleGenPreviewPixmap() "
7474  "Couldn't find backend for:\n\t\t\t%1")
7475  .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)));
7476  }
7477 
7478  if (!pginfo.IsLocal())
7479  {
7480  LOG(VB_GENERAL, LOG_ERR, LOC + "HandleGenPreviewPixmap: Unable to "
7481  "find file locally, unable to make preview image.");
7482  QStringList outputlist( "ERROR" );
7483  outputlist += "FILE_INACCESSIBLE";
7484  SendResponse(pbssock, outputlist);
7485  m_previewRequestedBy.remove(token);
7486  return;
7487  }
7488 
7489  if (has_extra_data)
7490  {
7491  if (time != std::chrono::seconds::max()) {
7493  pginfo, outputsize, outputfile, time, -1, token);
7494  } else {
7496  pginfo, outputsize, outputfile, -1s, frame, token);
7497 }
7498  }
7499  else
7500  {
7502  }
7503 
7504  QStringList outputlist("OK");
7505  if (!outputfile.isEmpty())
7506  outputlist += outputfile;
7507  SendResponse(pbssock, outputlist);
7508 }
7509 
7511 {
7512  MythSocket *pbssock = pbs->getSocket();
7513 
7514  QStringList::const_iterator it = slist.cbegin() + 1;
7515  ProgramInfo pginfo(it, slist.cend());
7516 
7517  pginfo.SetPathname(GetPlaybackURL(&pginfo));
7518 
7519  QStringList strlist;
7520 
7521  if (m_ismaster &&
7522  (pginfo.GetHostname() != gCoreContext->GetHostName()) &&
7523  (!m_masterBackendOverride || !pginfo.IsLocal()))
7524  {
7525  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
7526 
7527  if (slave)
7528  {
7529  QDateTime slavetime = slave->PixmapLastModified(&pginfo);
7530  slave->DecrRef();
7531 
7532  strlist = (slavetime.isValid()) ?
7533  QStringList(QString::number(slavetime.toSecsSinceEpoch())) :
7534  QStringList("BAD");
7535 
7536  SendResponse(pbssock, strlist);
7537  return;
7538  }
7539 
7540  LOG(VB_GENERAL, LOG_ERR, LOC +
7541  QString("HandlePixmapLastModified() "
7542  "Couldn't find backend for:\n\t\t\t%1")
7543  .arg(pginfo.toString(ProgramInfo::kTitleSubtitle)));
7544  }
7545 
7546  if (!pginfo.IsLocal())
7547  {
7548  LOG(VB_GENERAL, LOG_ERR, LOC +
7549  "MainServer: HandlePixmapLastModified: Unable to "
7550  "find file locally, unable to get last modified date.");
7551  QStringList outputlist( "BAD" );
7552  SendResponse(pbssock, outputlist);
7553  return;
7554  }
7555 
7556  QString filename = pginfo.GetPathname() + ".png";
7557 
7558  QFileInfo finfo(filename);
7559 
7560  if (finfo.exists())
7561  {
7562  QDateTime lastmodified = finfo.lastModified();
7563  if (lastmodified.isValid())
7564  strlist = QStringList(QString::number(lastmodified.toSecsSinceEpoch()));
7565  else
7566  strlist = QStringList(QString::number(UINT_MAX));
7567  }
7568  else
7569  strlist = QStringList( "BAD" );
7570 
7571  SendResponse(pbssock, strlist);
7572 }
7573 
7575  const QStringList &slist, PlaybackSock *pbs)
7576 {
7577  QStringList strlist;
7578 
7579  MythSocket *pbssock = pbs->getSocket();
7580  if (slist.size() < (3 + NUMPROGRAMLINES))
7581  {
7582  strlist = QStringList("ERROR");
7583  strlist += "1: Parameter list too short";
7584  SendResponse(pbssock, strlist);
7585  return;
7586  }
7587 
7588  QDateTime cachemodified;
7589  if (!slist[1].isEmpty() && (slist[1].toInt() != -1))
7590  {
7591  cachemodified = MythDate::fromSecsSinceEpoch(slist[1].toLongLong());
7592  }
7593 
7594  int max_file_size = slist[2].toInt();
7595 
7596  QStringList::const_iterator it = slist.begin() + 3;
7597  ProgramInfo pginfo(it, slist.end());
7598 
7599  if (!pginfo.HasPathname())
7600  {
7601  strlist = QStringList("ERROR");
7602  strlist += "2: Invalid ProgramInfo";
7603  SendResponse(pbssock, strlist);
7604  return;
7605  }
7606 
7607  pginfo.SetPathname(GetPlaybackURL(&pginfo) + ".png");
7608  if (pginfo.IsLocal())
7609  {
7610  QFileInfo finfo(pginfo.GetPathname());
7611  if (finfo.exists())
7612  {
7613  size_t fsize = finfo.size();
7614  QDateTime lastmodified = finfo.lastModified();
7615  bool out_of_date = !cachemodified.isValid() ||
7616  (lastmodified > cachemodified);
7617 
7618  if (out_of_date && (fsize > 0) && ((ssize_t)fsize < max_file_size))
7619  {
7620  QByteArray data;
7621  QFile file(pginfo.GetPathname());
7622  bool open_ok = file.open(QIODevice::ReadOnly);
7623  if (open_ok)
7624  data = file.readAll();
7625 
7626  if (!data.isEmpty())
7627  {
7628  LOG(VB_FILE, LOG_INFO, LOC +
7629  QString("Read preview file '%1'")
7630  .arg(pginfo.GetPathname()));
7631  if (lastmodified.isValid())
7632  strlist += QString::number(lastmodified.toSecsSinceEpoch());
7633  else
7634  strlist += QString::number(UINT_MAX);
7635  strlist += QString::number(data.size());
7636 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
7637  quint16 checksum = qChecksum(data.constData(), data.size());
7638 #else
7639  quint16 checksum = qChecksum(data);
7640 #endif
7641  strlist += QString::number(checksum);
7642  strlist += QString(data.toBase64());
7643  }
7644  else
7645  {
7646  LOG(VB_GENERAL, LOG_ERR, LOC +
7647  QString("Failed to read preview file '%1'")
7648  .arg(pginfo.GetPathname()));
7649 
7650  strlist = QStringList("ERROR");
7651  strlist +=
7652  QString("3: Failed to read preview file '%1'%2")
7653  .arg(pginfo.GetPathname(),
7654  (open_ok) ? "" : " open failed");
7655  }
7656  }
7657  else if (out_of_date && (max_file_size > 0))
7658  {
7659  if (fsize >= (size_t) max_file_size)
7660  {
7661  strlist = QStringList("WARNING");
7662  strlist += QString("1: Preview file too big %1 > %2")
7663  .arg(fsize).arg(max_file_size);
7664  }
7665  else
7666  {
7667  strlist = QStringList("ERROR");
7668  strlist += "4: Preview file is invalid";
7669  }
7670  }
7671  else
7672  {
7673  if (lastmodified.isValid())
7674  strlist += QString::number(lastmodified.toSecsSinceEpoch());
7675  else
7676  strlist += QString::number(UINT_MAX);
7677  }
7678 
7679  SendResponse(pbssock, strlist);
7680  return;
7681  }
7682  }
7683 
7684  // handle remote ...
7685  if (m_ismaster && pginfo.GetHostname() != gCoreContext->GetHostName())
7686  {
7687  PlaybackSock *slave = GetSlaveByHostname(pginfo.GetHostname());
7688  if (!slave)
7689  {
7690  strlist = QStringList("ERROR");
7691  strlist +=
7692  "5: Could not locate mythbackend that made this recording";
7693  SendResponse(pbssock, strlist);
7694  return;
7695  }
7696 
7697  strlist = slave->ForwardRequest(slist);
7698 
7699  slave->DecrRef();
7700 
7701  if (!strlist.empty())
7702  {
7703  SendResponse(pbssock, strlist);
7704  return;
7705  }
7706  }
7707 
7708  strlist = QStringList("WARNING");
7709  strlist += "2: Could not locate requested file";
7710  SendResponse(pbssock, strlist);
7711 }
7712 
7714 {
7715  QStringList retlist( "OK" );
7716  SendResponse(socket, retlist);
7717 }
7718 
7720 {
7721  pbs->setBlockShutdown(blockShutdown);
7722 
7723  MythSocket *socket = pbs->getSocket();
7724  QStringList retlist( "OK" );
7725  SendResponse(socket, retlist);
7726 }
7727 
7729 {
7730  QMutexLocker lock(&m_deferredDeleteLock);
7731 
7732  if (m_deferredDeleteList.empty())
7733  return;
7734 
7736  while (MythDate::secsInPast(dds.ts) > 30s)
7737  {
7738  dds.sock->DecrRef();
7739  m_deferredDeleteList.pop_front();
7740  if (m_deferredDeleteList.empty())
7741  return;
7742  dds = m_deferredDeleteList.front();
7743  }
7744 }
7745 
7747 {
7749  dds.sock = sock;
7750  dds.ts = MythDate::current();
7751 
7752  QMutexLocker lock(&m_deferredDeleteLock);
7753  m_deferredDeleteList.push_back(dds);
7754 }
7755 
7756 #undef QT_NO_DEBUG
7757 
7759 {
7760  // we're in the middle of stopping, prevent deadlock
7761  if (m_stopped)
7762  return;
7763 
7764  m_sockListLock.lockForWrite();
7765 
7766  // make sure these are not actually deleted in the callback
7767  socket->IncrRef();
7768  m_decrRefSocketList.push_back(socket);
7769  QList<uint> disconnectedSlaves;
7770 
7771  for (auto it = m_playbackList.begin(); it != m_playbackList.end(); ++it)
7772  {
7773  PlaybackSock *pbs = (*it);
7774  MythSocket *sock = pbs->getSocket();
7775  if (sock == socket && pbs == m_masterServer)
7776  {
7777  m_playbackList.erase(it);
7778  m_sockListLock.unlock();
7780  m_masterServer = nullptr;
7781  MythEvent me("LOCAL_RECONNECT_TO_MASTER");
7782  gCoreContext->dispatch(me);
7783  return;
7784  }
7785  if (sock == socket)
7786  {
7787  disconnectedSlaves.clear();
7788  bool needsReschedule = false;
7789 
7790  if (m_ismaster && pbs->isSlaveBackend())
7791  {
7792  LOG(VB_GENERAL, LOG_ERR, LOC +
7793  QString("Slave backend: %1 no longer connected")
7794  .arg(pbs->getHostname()));
7795 
7796  bool isFallingAsleep = true;
7797  TVRec::s_inputsLock.lockForRead();
7798  for (auto * elink : std::as_const(*m_encoderList))
7799  {
7800  if (elink->GetSocket() == pbs)
7801  {
7802  if (!elink->IsFallingAsleep())
7803  isFallingAsleep = false;
7804 
7805  elink->SetSocket(nullptr);
7806  if (m_sched)
7807  disconnectedSlaves.push_back(elink->GetInputID());
7808  }
7809  }
7810  TVRec::s_inputsLock.unlock();
7811  if (m_sched && !isFallingAsleep)
7812  needsReschedule = true;
7813 
7814  QString message = QString("LOCAL_SLAVE_BACKEND_OFFLINE %1")
7815  .arg(pbs->getHostname());
7816  MythEvent me(message);
7817  gCoreContext->dispatch(me);
7818 
7819  MythEvent me2("RECORDING_LIST_CHANGE");
7820  gCoreContext->dispatch(me2);
7821 
7823  QString("SLAVE_DISCONNECTED HOSTNAME %1")
7824  .arg(pbs->getHostname()));
7825  }
7826  else if (m_ismaster && pbs->IsFrontend())
7827  {
7828  if (gBackendContext)
7829  gBackendContext->SetFrontendDisconnected(pbs->getHostname());
7830  }
7831 
7832  LiveTVChain *chain = GetExistingChain(sock);
7833  if (chain != nullptr)
7834  {
7835  chain->DelHostSocket(sock);
7836  if (chain->HostSocketCount() == 0)
7837  {
7838  TVRec::s_inputsLock.lockForRead();
7839  for (auto * enc : std::as_const(*m_encoderList))
7840  {
7841  if (enc->IsLocal())
7842  {
7843  while (enc->GetState() == kState_ChangingState)
7844  std::this_thread::sleep_for(500us);
7845 
7846  if (enc->IsBusy() &&
7847  enc->GetChainID() == chain->GetID())
7848  {
7849  enc->StopLiveTV();
7850  }
7851  }
7852  }
7853  TVRec::s_inputsLock.unlock();
7854  DeleteChain(chain);
7855  }
7856  }
7857 
7858  LOG(VB_GENERAL, LOG_INFO, QString("%1 sock(%2) '%3' disconnected")
7859  .arg(pbs->getBlockShutdown() ? "Playback" : "Monitor")
7860  .arg(quintptr(socket),0,16)
7861  .arg(pbs->getHostname()) );
7862  pbs->SetDisconnected();
7863  m_playbackList.erase(it);
7864 
7865  PlaybackSock *testsock = GetPlaybackBySock(socket);
7866  if (testsock)
7867  LOG(VB_GENERAL, LOG_ERR, LOC + "Playback sock still exists?");
7868 
7869  pbs->DecrRef();
7870 
7871  m_sockListLock.unlock();
7872 
7873  // Since we may already be holding the scheduler lock
7874  // delay handling the disconnect until a little later. #9885
7875  if (!disconnectedSlaves.isEmpty())
7876  SendSlaveDisconnectedEvent(disconnectedSlaves, needsReschedule);
7877  else
7878  {
7879  // During idle periods customEvent() might never be called,
7880  // leading to an increasing number of closed sockets in
7881  // decrRefSocketList. Sending an event here makes sure that
7882  // customEvent() is called and that the closed sockets are
7883  // deleted.
7884  MythEvent me("LOCAL_CONNECTION_CLOSED");
7885  gCoreContext->dispatch(me);
7886  }
7887 
7889  return;
7890  }
7891  }
7892 
7893  for (auto ft = m_fileTransferList.begin(); ft != m_fileTransferList.end(); ++ft)
7894  {
7895  MythSocket *sock = (*ft)->getSocket();
7896  if (sock == socket)
7897  {
7898  LOG(VB_GENERAL, LOG_INFO, QString("BEFileTransfer sock(%1) disconnected")
7899  .arg(quintptr(socket),0,16) );
7900  (*ft)->DecrRef();
7901  m_fileTransferList.erase(ft);
7902  m_sockListLock.unlock();
7904  return;
7905  }
7906  }
7907 
7908  QSet<MythSocket*>::iterator cs = m_controlSocketList.find(socket);
7909  if (cs != m_controlSocketList.end())
7910  {
7911  LOG(VB_GENERAL, LOG_INFO, QString("Control sock(%1) disconnected")
7912  .arg(quintptr(socket),0,16) );
7913  (*cs)->DecrRef();
7914  m_controlSocketList.erase(cs);
7915  m_sockListLock.unlock();
7917  return;
7918  }
7919 
7920  m_sockListLock.unlock();
7921 
7922  LOG(VB_GENERAL, LOG_WARNING, LOC +
7923  QString("Unknown socket closing MythSocket(0x%1)")
7924  .arg((intptr_t)socket,0,16));
7926 }
7927 
7929 {
7930  if (!m_ismaster)
7931  return nullptr;
7932 
7933  m_sockListLock.lockForRead();
7934 
7935  for (auto *pbs : m_playbackList)
7936  {
7937  if (pbs->isSlaveBackend() &&
7938  gCoreContext->IsThisHost(hostname, pbs->getHostname()))
7939  {
7940  m_sockListLock.unlock();
7941  pbs->IncrRef();
7942  return pbs;
7943  }
7944  }
7945 
7946  m_sockListLock.unlock();
7947 
7948  return nullptr;
7949 }
7950 
7952 {
7953  if (!m_ismaster)
7954  return nullptr;
7955 
7956  QReadLocker rlock(&m_sockListLock);
7957 
7958  for (auto *pbs : m_playbackList)
7959  {
7960  if (pbs->isMediaServer() &&
7961  gCoreContext->IsThisHost(hostname, pbs->getHostname()))
7962  {
7963  pbs->IncrRef();
7964  return pbs;
7965  }
7966  }
7967 
7968  return nullptr;
7969 }
7970 
7973 {
7974  auto it = std::find_if(m_playbackList.cbegin(), m_playbackList.cend(),
7975  [sock](auto & pbs)
7976  { return sock == pbs->getSocket(); });
7977  return (it != m_playbackList.cend()) ? *it : nullptr;
7978 }
7979 
7982 {
7983  for (auto & ft : m_fileTransferList)
7984  if (id == ft->getSocket()->GetSocketDescriptor())
7985  return ft;
7986  return nullptr;
7987 }
7988 
7991 {
7992  for (auto & ft : m_fileTransferList)
7993  if (sock == ft->getSocket())
7994  return ft;
7995  return nullptr;
7996 }
7997 
7999 {
8000  QMutexLocker lock(&m_liveTVChainsLock);
8001 
8002  for (auto & chain : m_liveTVChains)
8003  if (chain->GetID() == id)
8004  return chain;
8005  return nullptr;
8006 }
8007 
8009 {
8010  QMutexLocker lock(&m_liveTVChainsLock);
8011 
8012  for (auto & chain : m_liveTVChains)
8013  if (chain->IsHostSocket(sock))
8014  return chain;
8015  return nullptr;
8016 }
8017 
8019 {
8020  QMutexLocker lock(&m_liveTVChainsLock);
8021 
8022  for (auto & chain : m_liveTVChains)
8023  if (chain->ProgramIsAt(pginfo) >= 0)
8024  return chain;
8025  return nullptr;
8026 }
8027 
8029 {
8030  QMutexLocker lock(&m_liveTVChainsLock);
8031 
8032  if (chain)
8033  m_liveTVChains.push_back(chain);
8034 }
8035 
8037 {
8038  QMutexLocker lock(&m_liveTVChainsLock);
8039 
8040  if (!chain)
8041  return;
8042 
8043  std::vector<LiveTVChain*> newChains;
8044 
8045  for (auto & entry : m_liveTVChains)
8046  {
8047  if (entry != chain)
8048  newChains.push_back(entry);
8049  }
8050  m_liveTVChains = newChains;
8051 
8052  chain->DecrRef();
8053 }
8054 
8055 void MainServer::SetExitCode(int exitCode, bool closeApplication)
8056 {
8057  m_exitCode = exitCode;
8058  if (closeApplication)
8059  QCoreApplication::exit(m_exitCode);
8060 }
8061 
8062 QString MainServer::LocalFilePath(const QString &path, const QString &wantgroup)
8063 {
8064  QString lpath = QString(path);
8065 
8066  if (lpath.section('/', -2, -2) == "channels")
8067  {
8068  // This must be an icon request. Check channel.icon to be safe.
8069  QString file = lpath.section('/', -1);
8070  lpath = "";
8071 
8072  MSqlQuery query(MSqlQuery::InitCon());
8073  query.prepare("SELECT icon FROM channel "
8074  "WHERE deleted IS NULL AND icon LIKE :FILENAME ;");
8075  query.bindValue(":FILENAME", QString("%/") + file);
8076 
8077  if (query.exec() && query.next())
8078  {
8079  lpath = query.value(0).toString();
8080  }
8081  else
8082  {
8083  MythDB::DBError("Icon path", query);
8084  }
8085  }
8086  else
8087  {
8088  lpath = lpath.section('/', -1);
8089 
8090  QString fpath = lpath;
8091  if (fpath.endsWith(".png"))
8092  fpath = fpath.left(fpath.length() - 4);
8093 
8094  ProgramInfo pginfo(fpath);
8095  if (pginfo.GetChanID())
8096  {
8097  QString pburl = GetPlaybackURL(&pginfo);
8098  if (pburl.startsWith("/"))
8099  {
8100  lpath = pburl.section('/', 0, -2) + "/" + lpath;
8101  LOG(VB_FILE, LOG_INFO, LOC +
8102  QString("Local file path: %1").arg(lpath));
8103  }
8104  else
8105  {
8106  LOG(VB_GENERAL, LOG_ERR, LOC +
8107  QString("ERROR: LocalFilePath unable to find local "
8108  "path for '%1', found '%2' instead.")
8109  .arg(lpath, pburl));
8110  lpath = "";
8111  }
8112  }
8113  else if (!lpath.isEmpty())
8114  {
8115  // For securities sake, make sure filename is really the pathless.
8116  QString opath = lpath;
8117  StorageGroup sgroup;
8118 
8119  if (!wantgroup.isEmpty())
8120  {
8121  sgroup.Init(wantgroup);
8122  lpath = QString(path);
8123  }
8124  else
8125  {
8126  lpath = QFileInfo(lpath).fileName();
8127  }
8128 
8129  QString tmpFile = sgroup.FindFile(lpath);
8130  if (!tmpFile.isEmpty())
8131  {
8132  lpath = tmpFile;
8133  LOG(VB_FILE, LOG_INFO, LOC +
8134  QString("LocalFilePath(%1 '%2'), found file through "
8135  "exhaustive search at '%3'")
8136  .arg(path, opath, lpath));
8137  }
8138  else
8139  {
8140  LOG(VB_GENERAL, LOG_ERR, LOC + QString("ERROR: LocalFilePath "
8141  "unable to find local path for '%1'.") .arg(path));
8142  lpath = "";
8143  }
8144 
8145  }
8146  else
8147  {
8148  lpath = "";
8149  }
8150  }
8151 
8152  return lpath;
8153 }
8154 
8156 {
8157  auto *masterServerSock = new MythSocket(-1, this);
8158 
8159  QString server = gCoreContext->GetMasterServerIP();
8161 
8162  LOG(VB_GENERAL, LOG_NOTICE, LOC +
8163  QString("Connecting to master server: %1:%2")
8164  .arg(server).arg(port));
8165 
8166  if (!masterServerSock->ConnectToHost(server, port))
8167  {
8168  LOG(VB_GENERAL, LOG_NOTICE, LOC +
8169  "Connection to master server timed out.");
8171  masterServerSock->DecrRef();
8172  return;
8173  }
8174 
8175  LOG(VB_GENERAL, LOG_NOTICE, LOC + "Connected successfully");
8176 
8177  QString str = QString("ANN SlaveBackend %1 %2")
8178  .arg(gCoreContext->GetHostName(),
8180 
8181  QStringList strlist( str );
8182 
8183  TVRec::s_inputsLock.lockForRead();
8184  for (auto * elink : std::as_const(*m_encoderList))
8185  {
8186  elink->CancelNextRecording(true);
8187  ProgramInfo *pinfo = elink->GetRecording();
8188  if (pinfo)
8189  {
8190  pinfo->ToStringList(strlist);
8191  delete pinfo;
8192  }
8193  else
8194  {
8195  ProgramInfo dummy;
8196  dummy.SetInputID(elink->GetInputID());
8197  dummy.ToStringList(strlist);
8198  }
8199  }
8200  TVRec::s_inputsLock.unlock();
8201 
8202  // Calling SendReceiveStringList() with callbacks enabled is asking for
8203  // trouble, our reply might be swallowed by readyRead
8204  masterServerSock->SetReadyReadCallbackEnabled(false);
8205  if (!masterServerSock->SendReceiveStringList(strlist, 1) ||
8206  (strlist[0] == "ERROR"))
8207  {
8208  masterServerSock->DecrRef();
8209  masterServerSock = nullptr;
8210  if (strlist.empty())
8211  {
8212  LOG(VB_GENERAL, LOG_ERR, LOC +
8213  "Failed to open master server socket, timeout");
8214  }
8215  else
8216  {
8217  LOG(VB_GENERAL, LOG_ERR, LOC +
8218  "Failed to open master server socket" +
8219  ((strlist.size() >= 2) ?
8220  QString(", error was %1").arg(strlist[1]) :
8221  QString(", remote error")));
8222  }
8224  return;
8225  }
8226  masterServerSock->SetReadyReadCallbackEnabled(true);
8227 
8228  m_masterServer = new PlaybackSock(this, masterServerSock, server,
8230  m_sockListLock.lockForWrite();
8231  m_playbackList.push_back(m_masterServer);
8232  m_sockListLock.unlock();
8233 
8234  m_autoexpireUpdateTimer->start(1s);
8235 }
8236 
8237 // returns true, if a client (slavebackends are not counted!)
8238 // is connected by checking the lists.
8239 bool MainServer::isClientConnected(bool onlyBlockingClients)
8240 {
8241  bool foundClient = false;
8242 
8243  m_sockListLock.lockForRead();
8244 
8245  foundClient |= !m_fileTransferList.empty();
8246 
8247  for (auto it = m_playbackList.begin();
8248  !foundClient && (it != m_playbackList.end()); ++it)
8249  {
8250  // Ignore slave backends
8251  if ((*it)->isSlaveBackend())
8252  continue;
8253 
8254  // If we are only interested in blocking clients then ignore
8255  // non-blocking ones
8256  if (onlyBlockingClients && !(*it)->getBlockShutdown())
8257  continue;
8258 
8259  foundClient = true;
8260  }
8261 
8262  m_sockListLock.unlock();
8263 
8264  return (foundClient);
8265 }
8266 
8268 void MainServer::ShutSlaveBackendsDown(const QString &haltcmd)
8269 {
8270 // TODO FIXME We should issue a MythEvent and have customEvent
8271 // send this with the proper syncronisation and locking.
8272 
8273  QStringList bcast( "SHUTDOWN_NOW" );
8274  bcast << haltcmd;
8275 
8276  m_sockListLock.lockForRead();
8277 
8278  for (auto & pbs : m_playbackList)
8279  {
8280  if (pbs->isSlaveBackend())
8281  pbs->getSocket()->WriteStringList(bcast);
8282  }
8283 
8284  m_sockListLock.unlock();
8285 }
8286 
8288 {
8289  if (event.ExtraDataCount() > 0 && m_sched)
8290  {
8291  bool needsReschedule = event.ExtraData(0).toUInt() != 0U;
8292  for (int i = 1; i < event.ExtraDataCount(); i++)
8293  m_sched->SlaveDisconnected(event.ExtraData(i).toUInt());
8294 
8295  if (needsReschedule)
8296  m_sched->ReschedulePlace("SlaveDisconnected");
8297  }
8298 }
8299 
8301  const QList<uint> &offlineEncoderIDs, bool needsReschedule)
8302 {
8303  QStringList extraData;
8304  extraData.push_back(
8305  QString::number(static_cast<uint>(needsReschedule)));
8306 
8307  QList<uint>::const_iterator it;
8308  for (it = offlineEncoderIDs.begin(); it != offlineEncoderIDs.end(); ++it)
8309  extraData.push_back(QString::number(*it));
8310 
8311  MythEvent me("LOCAL_SLAVE_BACKEND_ENCODERS_OFFLINE", extraData);
8312  gCoreContext->dispatch(me);
8313 }
8314 
8316 {
8317 #if CONFIG_SYSTEMD_NOTIFY
8318  QStringList status2;
8319 
8320  if (m_ismaster)
8321  status2 << QString("Master backend.");
8322  else
8323  status2 << QString("Slave backend.");
8324 
8325 #if 0
8326  // Count connections
8327  {
8328  int playback = 0, frontend = 0, monitor = 0, slave = 0, media = 0;
8329  QReadLocker rlock(&m_sockListLock);
8330 
8331  for (auto iter = m_playbackList.begin(); iter != m_playbackList.end(); ++iter)
8332  {
8333  PlaybackSock *pbs = *iter;
8334  if (pbs->IsDisconnected())
8335  continue;
8336  if (pbs->isSlaveBackend())
8337  slave += 1;
8338  else if (pbs->isMediaServer())
8339  media += 1;
8340  else if (pbs->IsFrontend())
8341  frontend += 1;
8342  else if (pbs->getBlockShutdown())
8343  playback += 1;
8344  else
8345  monitor += 1;
8346  }
8347  status2 << QString("Connections: Pl %1, Fr %2, Mo %3, Sl %4, MS %5, FT %6, Co %7")
8348  .arg(playback).arg(frontend).arg(monitor).arg(slave).arg(media)
8349  .arg(m_fileTransferList.size()).arg(m_controlSocketList.size());
8350  }
8351 #endif
8352 
8353  // Count active recordings
8354  {
8355  int active = 0;
8356  TVRec::s_inputsLock.lockForRead();
8357  for (auto * elink : std::as_const(*m_encoderList))
8358  {
8359  if (not elink->IsLocal())
8360  continue;
8361  switch (elink->GetState())
8362  {
8363  case kState_WatchingLiveTV:
8364  case kState_RecordingOnly:
8366  active += 1;
8367  break;
8368  default:
8369  break;
8370  }
8371  }
8372  TVRec::s_inputsLock.unlock();
8373 
8374  // Count scheduled recordings
8375  int scheduled = 0;
8376  if (m_sched) {
8377  RecList recordings;
8378 
8379  m_sched->GetAllPending(recordings);
8380  for (auto & recording : recordings)
8381  {
8382  if ((recording->GetRecordingStatus() <= RecStatus::WillRecord) &&
8383  (recording->GetRecordingStartTime() >= MythDate::current()))
8384  {
8385  scheduled++;
8386  }
8387  }
8388  while (!recordings.empty())
8389  {
8390  ProgramInfo *pginfo = recordings.back();
8391  delete pginfo;
8392  recordings.pop_back();
8393  }
8394  }
8395  status2 <<
8396  QString("Recordings: active %1, scheduled %2")
8397  .arg(active).arg(scheduled);
8398  }
8399 
8400  // Systemd only allows a single line for status
8401  QString status("STATUS=" + status2.join(' '));
8402  (void)sd_notify(0, qPrintable(status));
8403 #endif
8404 }
8405 
8406 /* vim: set expandtab tabstop=4 shiftwidth=4: */
MainServer::m_controlSocketList
QSet< MythSocket * > m_controlSocketList
Definition: mainserver.h:321
MainServer::SendErrorResponse
void SendErrorResponse(MythSocket *sock, const QString &error)
Definition: mainserver.cpp:2084
filesysteminfo.h
FileSystemInfo::setUsedSpace
void setUsedSpace(int64_t size)
Definition: filesysteminfo.h:54
MythCoreContext::resolveSettingAddress
QString resolveSettingAddress(const QString &name, const QString &host=QString(), ResolveType type=ResolveAny, bool keepscope=false)
Retrieve IP setting "name" for "host".
Definition: mythcorecontext.cpp:1163
Scheduler
Definition: scheduler.h:45
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:215
RenameThread::s_renamelock
static QMutex s_renamelock
Definition: mainserver.h:108
MainServer::AddToChains
void AddToChains(LiveTVChain *chain)
Definition: mainserver.cpp:8028
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
FreeSpaceUpdater::m_lastRequest
MythTimer m_lastRequest
Definition: mainserver.cpp:240
RecStatus::Type
Type
Definition: recordingstatus.h:15
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
build_compdb.args
args
Definition: build_compdb.py:11
FreeSpaceUpdater::FreeSpaceUpdater
FreeSpaceUpdater(MainServer &parent)
Definition: mainserver.cpp:182
MetaIO::changeImageType
virtual bool changeImageType([[maybe_unused]] const QString &filename, [[maybe_unused]] const AlbumArtImage *albumart, [[maybe_unused]] ImageType newType)
Definition: metaio.h:86
MythTimer::elapsed
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
MainServer::kMasterServerReconnectTimeout
static const std::chrono::milliseconds kMasterServerReconnectTimeout
Definition: mainserver.h:371
bool
bool
Definition: pxsup2dast.c:30
ProgramInfo::MakeUniqueKey
QString MakeUniqueKey(void) const
Creates a unique string that can be used to identify an existing recording.
Definition: programinfo.h:339
ImageHandler::HandleRename
QStringList HandleRename(const QString &id, const QString &newBase) const
Change name of an image/dir.
Definition: imagemanager.cpp:1209
kPBSEvents_None
@ kPBSEvents_None
Definition: playbacksock.h:21
MainServer::HandleMusicTagChangeImage
void HandleMusicTagChangeImage(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6353
Scheduler::AddRecording
void AddRecording(const RecordingInfo &pi)
Definition: scheduler.cpp:1868
MainServer::m_mythserver
MythServer * m_mythserver
Definition: mainserver.h:315
ProgramInfo::UpdateLastDelete
void UpdateLastDelete(bool setTime) const
Set or unset the record.last_delete field.
Definition: programinfo.cpp:3419
tv.h
MSqlQuery::size
int size(void) const
Definition: mythdbcon.h:214
MusicMetadata::Filename
QString Filename(bool find=true)
Definition: musicmetadata.cpp:962
DeleteStruct::m_filename
QString m_filename
Definition: mainserver.h:65
AlbumArtImage::m_imageType
ImageType m_imageType
Definition: musicmetadata.h:51
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
Scheduler::GetNextLiveTVDir
void GetNextLiveTVDir(uint cardid)
Definition: scheduler.cpp:5090
MythCoreContext::GetMasterHostName
QString GetMasterHostName(void)
Definition: mythcorecontext.cpp:807
MainServer::GetFileTransferBySock
BEFileTransfer * GetFileTransferBySock(MythSocket *socket)
Warning you must hold a sockListLock lock before calling this.
Definition: mainserver.cpp:7990
InputInfo::m_chanId
uint m_chanId
chanid restriction if applicable
Definition: inputinfo.h:51
ProgramInfo::GetFilesize
virtual uint64_t GetFilesize(void) const
Definition: programinfo.cpp:6425
MainServer::DeleteRecordedFiles
static void DeleteRecordedFiles(DeleteStruct *ds)
Definition: mainserver.cpp:2490
MainServer::LocalFilePath
static QString LocalFilePath(const QString &path, const QString &wantgroup)
Definition: mainserver.cpp:8062
MythEvent::kMythEventMessage
static const Type kMythEventMessage
Definition: mythevent.h:79
backendcontext.h
RenameThread::run
void run(void) override
Definition: mainserver.cpp:5481
LoadFromScheduler
bool LoadFromScheduler(AutoDeleteDeque< TYPE * > &destination, bool &hasConflicts, const QString &altTable="", int recordid=-1)
Definition: programinfo.h:912
BackendContext::SetFrontendDisconnected
void SetFrontendDisconnected(const QString &name)
Definition: backendcontext.cpp:73
error
static void error(const char *str,...)
Definition: vbi.cpp:36
ReferenceCounter::DecrRef
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: referencecounter.cpp:125
MainServer::m_fsIDcacheLock
QMutex m_fsIDcacheLock
Definition: mainserver.h:357
ProgramInfo::SaveBookmark
void SaveBookmark(uint64_t frame)
Clears any existing bookmark in DB and if frame is greater than 0 sets a new bookmark.
Definition: programinfo.cpp:2669
ProgramInfo::SetRecordingStatus
void SetRecordingStatus(RecStatus::Type status)
Definition: programinfo.h:580
MainServer::HandleQueryTimeZone
void HandleQueryTimeZone(PlaybackSock *pbs)
Definition: mainserver.cpp:3566
ProgramInfo::GetHostname
QString GetHostname(void) const
Definition: programinfo.h:421
kMSDontBlockInputDevs
@ kMSDontBlockInputDevs
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:36
MainServer::ProcessRequestWork
void ProcessRequestWork(MythSocket *sock)
Definition: mainserver.cpp:469
ChannelChangeDirection
ChannelChangeDirection
ChannelChangeDirection is an enumeration of possible channel changing directions.
Definition: tv.h:28
MainServer::HandleMusicTagRemoveImage
void HandleMusicTagRemoveImage(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6675
mythdb.h
MainServer::GetFilesystemInfos
void GetFilesystemInfos(QList< FileSystemInfo > &fsInfos, bool useCache=true)
Definition: mainserver.cpp:5345
false
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:89
MainServer::m_encoderList
QMap< int, EncoderLink * > * m_encoderList
Definition: mainserver.h:313
mythrandom.h
FreeSpaceUpdater::run
void run(void) override
Definition: mainserver.cpp:194
MainServer::m_fileTransferList
std::vector< BEFileTransfer * > m_fileTransferList
Definition: mainserver.h:320
getDiskSpace
int64_t getDiskSpace(const QString &file_on_disk, int64_t &total, int64_t &used)
Definition: mythcoreutil.cpp:33
DeleteStruct::m_title
QString m_title
Definition: mainserver.h:66
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
MainServer::HandleMusicCalcTrackLen
void HandleMusicCalcTrackLen(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6003
logPropagateCalc
void logPropagateCalc(void)
Generate the logPropagateArgs global with the latest logging level, mask, etc to propagate to all of ...
Definition: logging.cpp:572
AutoExpire::Update
static void Update(int encoder, int fsID, bool immediately)
This is used to update the global AutoExpire instance "expirer".
Definition: autoexpire.cpp:1038
MainServer::HandleBackendRefresh
void HandleBackendRefresh(MythSocket *socket)
Definition: mainserver.cpp:7713
Scheduler::SlaveConnected
void SlaveConnected(const RecordingList &slavelist)
Definition: scheduler.cpp:831
ImageHandler::HandleGetMetadata
QStringList HandleGetMetadata(const QString &id) const
Read meta data for an image.
Definition: imagemanager.cpp:1178
MainServer::GetExistingChain
LiveTVChain * GetExistingChain(const QString &id)
Definition: mainserver.cpp:7998
PictureAdjustType
PictureAdjustType
Definition: tv.h:120
MusicMetadata::getAlbumArtImages
AlbumArtImages * getAlbumArtImages(void)
Definition: musicmetadata.cpp:1364
MainServer::HandleQueryMemStats
void HandleQueryMemStats(PlaybackSock *pbs)
Definition: mainserver.cpp:3538
MainServer::m_previewRequestedBy
RequestedBy m_previewRequestedBy
Definition: mainserver.h:367
MythTZ::getTimeZoneID
QString getTimeZoneID(void)
Returns the zoneinfo time zone ID or as much time zone information as possible.
Definition: mythtimezone.cpp:32
Scheduler::ReschedulePlace
void ReschedulePlace(const QString &why)
Definition: scheduler.h:65
FileSystemInfo::setGroupID
void setGroupID(int id)
Definition: filesysteminfo.h:51
MusicMetadata::createFromID
static MusicMetadata * createFromID(int trackid)
Definition: musicmetadata.cpp:255
MetaIO::write
virtual bool write(const QString &filename, MusicMetadata *mdata)=0
Writes all metadata back to a file.
MainServer::m_addChildInputLock
QMutex m_addChildInputLock
Definition: mainserver.h:341
RecStatus::Tuning
@ Tuning
Definition: recordingstatus.h:21
MainServer::HandleSGFileQuery
void HandleSGFileQuery(QStringList &sList, PlaybackSock *pbs)
Definition: mainserver.cpp:4171
MainServer::m_deferredDeleteLock
QMutex m_deferredDeleteLock
Definition: mainserver.h:349
logLevel
LogLevel_t logLevel
Definition: logging.cpp:85
Scheduler::GetAllScheduled
static void GetAllScheduled(QStringList &strList, SchedSortColumn sortBy=kSortTitle, bool ascending=true)
Returns all scheduled programs serialized into a QStringList.
Definition: scheduler.cpp:1843
MainServer::m_autoexpireUpdateTimer
QTimer * m_autoexpireUpdateTimer
Definition: mainserver.h:353
AutoDeleteDeque::cbegin
const_iterator cbegin(void) const
Definition: autodeletedeque.h:54
RecordingInfo
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:35
kPBSEvents_Normal
@ kPBSEvents_Normal
Definition: playbacksock.h:22
RecordingInfo::GetFilesize
uint64_t GetFilesize(void) const override
Definition: recordinginfo.cpp:1755
gExpirer
AutoExpire * gExpirer
Definition: backendcontext.cpp:8
RecordingInfo::ForgetHistory
void ForgetHistory(void)
Forget the recording of a program so it will be recorded again.
Definition: recordinginfo.cpp:1459
MainServer::HandleSetBookmark
void HandleSetBookmark(QStringList &tokens, PlaybackSock *pbs)
Definition: mainserver.cpp:5703
LOC
#define LOC
Definition: mainserver.cpp:99
StorageGroup::FindFile
QString FindFile(const QString &filename)
Definition: storagegroup.cpp:597
MainServer::DeferredDeleteStruct
Definition: mainserver.h:343
ProgramInfo::GetRecordingID
uint GetRecordingID(void) const
Definition: programinfo.h:446
MainServer::m_masterServer
PlaybackSock * m_masterServer
Definition: mainserver.h:330
LiveTVChain::GetID
QString GetID(void) const
Definition: livetvchain.h:54
FreeSpaceUpdater::kRequeryTimeout
static constexpr std::chrono::milliseconds kRequeryTimeout
Definition: mainserver.cpp:242
Scheduler::FillRecordListFromDB
void FillRecordListFromDB(uint recordid=0)
Definition: scheduler.cpp:491
FileSystemInfo::Consolidate
static void Consolidate(QList< FileSystemInfo > &disks, bool merge=true, int64_t fuzz=14000)
Definition: filesysteminfo.cpp:165
MainServer::BackendQueryDiskSpace
void BackendQueryDiskSpace(QStringList &strlist, bool consolidated, bool allHosts)
Definition: mainserver.cpp:5126
LiveTVChain::LoadFromExistingChain
void LoadFromExistingChain(const QString &id)
Definition: livetvchain.cpp:55
MainServer::HandleGetExpiringRecordings
void HandleGetExpiringRecordings(PlaybackSock *pbs)
Definition: mainserver.cpp:3872
MainServer::m_liveTVChainsLock
QMutex m_liveTVChainsLock
Definition: mainserver.h:311
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
mythcoreutil.h
getLoadAvgs
loadArray getLoadAvgs(void)
Returns the system load averages.
Definition: mythmiscutil.cpp:175
sched
Scheduler * sched
JOB_COMMFLAG
@ JOB_COMMFLAG
Definition: jobqueue.h:79
getMemStats
bool getMemStats([[maybe_unused]] int &totalMB, [[maybe_unused]] int &freeMB, [[maybe_unused]] int &totalVM, [[maybe_unused]] int &freeVM)
Definition: mythmiscutil.cpp:109
ProgramInfo::kTitleSubtitle
@ kTitleSubtitle
Definition: programinfo.h:509
MainServer::HandleMessage
void HandleMessage(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7229
MythDownloadManager::queueDownload
void queueDownload(const QString &url, const QString &dest, QObject *caller, bool reload=false)
Adds a url to the download queue.
Definition: mythdownloadmanager.cpp:393
MainServer::DeleteFile
static int DeleteFile(const QString &filename, bool followLinks, bool deleteBrokenSymlinks=false)
Deletes links and unlinks the main file and returns the descriptor.
Definition: mainserver.cpp:2628
MythEvent::ExtraDataCount
int ExtraDataCount() const
Definition: mythevent.h:68
ProgramInfo::QueryJobsRunning
static QMap< QString, bool > QueryJobsRunning(int type)
Definition: programinfo.cpp:5559
MainServer::NewConnection
void NewConnection(qintptr socketDescriptor)
Definition: mainserver.cpp:441
PlaybackSock::FillProgramInfo
bool FillProgramInfo(ProgramInfo &pginfo, const QString &playbackhost)
Definition: playbacksock.cpp:207
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
PreviewGeneratorQueue::RemoveListener
static void RemoveListener(QObject *listener)
Stop receiving notifications when a preview event is generated.
Definition: previewgeneratorqueue.cpp:205
imagemanager.h
Manages a collection of images.
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
MainServer::HandleCommBreakQuery
void HandleCommBreakQuery(const QString &chanid, const QString &starttime, PlaybackSock *pbs)
Definition: mainserver.cpp:5646
MainServer::GetPlaybackBySock
PlaybackSock * GetPlaybackBySock(MythSocket *socket)
Warning you must hold a sockListLock lock before calling this.
Definition: mainserver.cpp:7972
MainServer
Definition: mainserver.h:115
PreviewGeneratorQueue::CreatePreviewGeneratorQueue
static void CreatePreviewGeneratorQueue(PreviewGenerator::Mode mode, uint maxAttempts, std::chrono::seconds minBlockSeconds)
Create the singleton queue of preview generators.
Definition: previewgeneratorqueue.cpp:42
MetaIO
Definition: metaio.h:17
frm_dir_map_t
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:118
AutoExpire
Used to expire recordings to make space for new recordings.
Definition: autoexpire.h:60
kPictureAttribute_Contrast
@ kPictureAttribute_Contrast
Definition: videoouttypes.h:108
RecStatus::Unknown
@ Unknown
Definition: recordingstatus.h:31
getSymlinkTarget
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
Definition: mythmiscutil.cpp:451
RecordingRule
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:28
MainServer::HandleIsRecording
void HandleIsRecording(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7317
MusicMetadata::ID
IdType ID() const
Definition: musicmetadata.h:218
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
MainServer::m_masterFreeSpaceListLock
QMutex m_masterFreeSpaceListLock
Definition: mainserver.h:324
MainServer::OpenAndUnlink
static int OpenAndUnlink(const QString &filename)
Opens a file, unlinks it and returns the file descriptor.
Definition: mainserver.cpp:2685
FreeSpaceUpdater::m_wait
QWaitCondition m_wait
Definition: mainserver.cpp:241
MythSocket::DisconnectFromHost
void DisconnectFromHost(void)
Definition: mythsocket.cpp:498
MainServer::HandleSetVerbose
void HandleSetVerbose(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7257
MainServer::customEvent
void customEvent(QEvent *e) override
Definition: mainserver.cpp:1116
MainServer::HandleGetRecorderNum
void HandleGetRecorderNum(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7144
LiveTVChain::HostSocketCount
uint HostSocketCount(void) const
Definition: livetvchain.cpp:718
MainServer::HandleQueryFileHash
void HandleQueryFileHash(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3632
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
BEProcessRequestRunnable::~BEProcessRequestRunnable
~BEProcessRequestRunnable() override
Definition: mainserver.cpp:158
RenameThread::m_src
QString m_src
Definition: mainserver.h:112
LoadFromRecorded
bool LoadFromRecorded(ProgramList &destination, bool possiblyInProgressRecordingsOnly, const QMap< QString, uint32_t > &inUseMap, const QMap< QString, bool > &isJobRunning, const QMap< QString, ProgramInfo * > &recMap, int sort, const QString &sortBy, bool ignoreLiveTV, bool ignoreDeleted)
Definition: programinfo.cpp:6076
Scheduler::SlaveDisconnected
void SlaveDisconnected(uint cardid)
Definition: scheduler.cpp:906
AutoExpire::GetAllExpiring
void GetAllExpiring(QStringList &strList)
Gets the full list of programs that can expire in expiration order.
Definition: autoexpire.cpp:834
MainServer::autoexpireUpdate
static void autoexpireUpdate(void)
Definition: mainserver.cpp:436
cleanup
static QString cleanup(const QString &str)
Definition: mainserver.cpp:4471
ProgramInfo::UpdateInUseMark
void UpdateInUseMark(bool force=false)
Definition: programinfo.cpp:4962
MusicMetadata
Definition: musicmetadata.h:80
BEFileTransfer::isOpen
bool isOpen(void)
Definition: programs/mythbackend/filetransfer.cpp:65
NUMPROGRAMLINES
static constexpr int8_t NUMPROGRAMLINES
Definition: programinfo.h:28
AlbumArtImages::guessImageType
static ImageType guessImageType(const QString &filename)
Definition: musicmetadata.cpp:2190
DeleteStruct::m_chanid
uint m_chanid
Definition: mainserver.h:67
GetMythDB
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
mythsystemevent.h
MainServer::HandleMusicFindAlbumArt
void HandleMusicFindAlbumArt(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6147
build_compdb.file
file
Definition: build_compdb.py:55
MainServer::m_exitCode
int m_exitCode
Definition: mainserver.h:364
gBackendContext
BackendContext * gBackendContext
Definition: backendcontext.cpp:9
mythdirs.h
TruncateThread::run
void run(void) override
Definition: mainserver.cpp:5503
BEFileTransfer::ReOpen
bool ReOpen(const QString &newFilename="")
Definition: programs/mythbackend/filetransfer.cpp:70
comp_livetvorder
static bool comp_livetvorder(const InputInfo &a, const InputInfo &b)
Definition: mainserver.cpp:4358
force
bool force
Definition: mythcommflag.cpp:70
MusicMetadata::getLocalFilename
QString getLocalFilename(void)
try to find the track on the local file system
Definition: musicmetadata.cpp:1023
myth_system
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
Definition: mythsystemlegacy.cpp:506
ProgramInfo::GetRecordingEndTime
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
Definition: programinfo.h:412
ProgramInfo::GetRecordingGroup
QString GetRecordingGroup(void) const
Definition: programinfo.h:419
MainServer::TruncateAndClose
static bool TruncateAndClose(ProgramInfo *pginfo, int fd, const QString &filename, off_t fsize)
Repeatedly truncate an open file in small increments.
Definition: mainserver.cpp:2726
MainServer::HandleQueryFreeSpace
void HandleQueryFreeSpace(PlaybackSock *pbs, bool allHosts)
Definition: mainserver.cpp:3395
MThreadPool::Stop
void Stop(void)
Definition: mthreadpool.cpp:267
MThreadPool::startReserved
void startReserved(QRunnable *runnable, const QString &debugName, std::chrono::milliseconds waitForAvailMS=0ms)
Definition: mthreadpool.cpp:361
DeleteStruct::m_fd
int m_fd
Definition: mainserver.h:72
kDeletedAutoExpire
@ kDeletedAutoExpire
Definition: programtypes.h:196
BEFileTransfer::GetFileName
QString GetFileName(void)
Definition: programs/mythbackend/filetransfer.cpp:269
hardwareprofile.distros.mythtv_data.data_mythtv.prefix
string prefix
Definition: data_mythtv.py:40
LiveTVChain::DelHostSocket
void DelHostSocket(MythSocket *sock)
Definition: livetvchain.cpp:724
scheduler.h
MainServer::GetFileTransferByID
BEFileTransfer * GetFileTransferByID(int id)
Warning you must hold a sockListLock lock before calling this.
Definition: mainserver.cpp:7981
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
MainServer::m_masterFreeSpaceList
QStringList m_masterFreeSpaceList
Definition: mainserver.h:327
MythEvent::Message
const QString & Message() const
Definition: mythevent.h:65
MythSocket::IsConnected
bool IsConnected(void) const
Definition: mythsocket.cpp:551
MainServer::readyRead
void readyRead(MythSocket *socket) override
Definition: mainserver.cpp:451
kState_Error
@ kState_Error
Error State, if we ever try to enter this state errored is set.
Definition: tv.h:54
AlbumArtImages::getImage
AlbumArtImage * getImage(ImageType type)
Definition: musicmetadata.cpp:2115
ProgramInfo::GetRecordingStartTime
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:404
ProgramInfo::GetPathname
QString GetPathname(void) const
Definition: programinfo.h:343
Scheduler::ResetIdleTime
void ResetIdleTime(void)
Definition: scheduler.cpp:150
PlaybackSock::GenPreviewPixmap
QStringList GenPreviewPixmap(const QString &token, const ProgramInfo *pginfo)
Definition: playbacksock.cpp:278
ProgramInfo::IsWatched
bool IsWatched(void) const
Definition: programinfo.h:482
MainServer::connectionClosed
void connectionClosed(MythSocket *socket) override
Definition: mainserver.cpp:7758
close
#define close
Definition: compat.h:43
MainServer::m_playbackList
std::vector< PlaybackSock * > m_playbackList
Definition: mainserver.h:319
MainServer::m_fsIDcache
QMap< QString, int > m_fsIDcache
Definition: mainserver.h:356
MainServer::GetMediaServerByHostname
PlaybackSock * GetMediaServerByHostname(const QString &hostname)
Definition: mainserver.cpp:7951
mythsystemlegacy.h
kState_ChangingState
@ kState_ChangingState
This is a placeholder state which we never actually enter, but is returned by GetState() when we are ...
Definition: tv.h:89
MythObservable::addListener
void addListener(QObject *listener)
Add a listener to the observable.
Definition: mythobservable.cpp:38
ProgramInfo::QueryBookmark
uint64_t QueryBookmark(void) const
Gets any bookmark position in database, unless the ignore bookmark flag is set.
Definition: programinfo.cpp:2793
kState_WatchingRecording
@ kState_WatchingRecording
Watching Recording is the state for when we are watching an in progress recording,...
Definition: tv.h:80
MainServer::HandleMusicTagUpdateMetadata
void HandleMusicTagUpdateMetadata(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6060
MythSocket
Class for communcating between myth backends and frontends.
Definition: mythsocket.h:25
MainServer::UpdateSystemdStatus
void UpdateSystemdStatus(void)
Definition: mainserver.cpp:8315
MainServer::FreeSpaceUpdater
friend class FreeSpaceUpdater
Definition: mainserver.h:121
FileSystemInfo::setTotalSpace
void setTotalSpace(int64_t size)
Definition: filesysteminfo.h:53
ImageHandler::HandleDbCreate
QStringList HandleDbCreate(QStringList defs) const
Creates images for files created by a copy operation.
Definition: imagemanager.cpp:1328
RecStatus::WillRecord
@ WillRecord
Definition: recordingstatus.h:30
MythRemoveDirectory
bool MythRemoveDirectory(QDir &aDir)
Definition: mythmiscutil.cpp:756
MainServer::HandleGetPendingRecordings
void HandleGetPendingRecordings(PlaybackSock *pbs, const QString &table="", int recordid=-1)
Definition: mainserver.cpp:3788
MainServer::DeleteChain
void DeleteChain(LiveTVChain *chain)
Definition: mainserver.cpp:8036
verboseString
QString verboseString
Definition: logging.cpp:98
MetaIO::supportsEmbeddedImages
virtual bool supportsEmbeddedImages(void)
Does the tag support embedded cover art.
Definition: metaio.h:58
kDisableAutoExpire
@ kDisableAutoExpire
Definition: programtypes.h:194
fileserverutil.h
MainServer::SendSlaveDisconnectedEvent
static void SendSlaveDisconnectedEvent(const QList< uint > &offlineEncoderIDs, bool needsReschedule)
Definition: mainserver.cpp:8300
MythCoreContext::IsRegisteredFileForWrite
bool IsRegisteredFileForWrite(const QString &file)
Definition: mythcorecontext.cpp:2157
PlaybackSock::StopRecording
int StopRecording(const ProgramInfo *pginfo)
Definition: playbacksock.cpp:178
MainServer::HandleSetNextLiveTVDir
void HandleSetNextLiveTVDir(QStringList &commands, PlaybackSock *pbs)
Definition: mainserver.cpp:4862
MainServer::HandleSetLogLevel
void HandleSetLogLevel(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7284
ProgramInfo::MarkAsInUse
void MarkAsInUse(bool inuse, const QString &usedFor="")
Tracks a recording's in use status, to prevent deletion and to allow the storage scheduler to perform...
Definition: programinfo.cpp:5153
MainServer::HandleAnnounce
void HandleAnnounce(QStringList &slist, QStringList commands, MythSocket *socket)
Definition: mainserver.cpp:1709
MainServer::GetChainWithRecording
LiveTVChain * GetChainWithRecording(const ProgramInfo &pginfo)
Definition: mainserver.cpp:8018
autoexpire.h
ProgramInfo::SetPathname
void SetPathname(const QString &pn)
Definition: programinfo.cpp:2438
MythEvent::ExtraData
const QString & ExtraData(int idx=0) const
Definition: mythevent.h:66
StorageGroup::GetFileList
QStringList GetFileList(const QString &Path, bool recursive=false)
Definition: storagegroup.cpp:271
MythDate::fromSecsSinceEpoch
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:72
MainServer::HandleGetFreeInputInfo
void HandleGetFreeInputInfo(PlaybackSock *pbs, uint excluded_input)
Definition: mainserver.cpp:4365
programinfo.h
Scheduler::AddChildInput
void AddChildInput(uint parentid, uint childid)
Definition: scheduler.cpp:5866
MainServer::HandleQueryRecording
void HandleQueryRecording(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:2259
mythlogging.h
MainServer::DoHandleUndeleteRecording
void DoHandleUndeleteRecording(RecordingInfo &recinfo, PlaybackSock *pbs)
Definition: mainserver.cpp:3163
MainServer::m_liveTVChains
std::vector< LiveTVChain * > m_liveTVChains
Definition: mainserver.h:310
logLevelGetName
QString logLevelGetName(LogLevel_t level)
Map a log level enumerated value back to the name.
Definition: logging.cpp:768
MythCoreContext::GenMythURL
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
Definition: mythcorecontext.cpp:760
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:256
MainServer::HandleStopRecording
void HandleStopRecording(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:2838
InputInfo::m_liveTvOrder
uint m_liveTvOrder
order for live TV use
Definition: inputinfo.h:55
MythSocket::WriteStringList
bool WriteStringList(const QStringList &list)
Definition: mythsocket.cpp:301
MainServer::HandleGetConflictingRecordings
void HandleGetConflictingRecordings(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3854
pbs
Definition: pbs.py:1
MainServer::DeletePBS
void DeletePBS(PlaybackSock *sock)
Definition: mainserver.cpp:7746
remotefile.h
MainServer::HandleMusicTagGetImage
void HandleMusicTagGetImage(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6299
MainServer::HandleSlaveDisconnectedEvent
void HandleSlaveDisconnectedEvent(const MythEvent &event)
Definition: mainserver.cpp:8287
FileSystemInfo::setLocal
void setLocal(bool local=true)
Definition: filesysteminfo.h:49
MainServer::ShutSlaveBackendsDown
void ShutSlaveBackendsDown(const QString &haltcmd)
Sends the Slavebackends the request to shut down using haltcmd.
Definition: mainserver.cpp:8268
MainServer::DoHandleStopRecording
void DoHandleStopRecording(RecordingInfo &recinfo, PlaybackSock *pbs)
Definition: mainserver.cpp:2867
MainServer::HandleDeleteRecording
void HandleDeleteRecording(QString &chanid, QString &starttime, PlaybackSock *pbs, bool forceMetadataDelete, bool forgetHistory)
Definition: mainserver.cpp:2956
ProgramInfo::SetInputID
void SetInputID(uint id)
Definition: programinfo.h:540
BEFileTransfer::SetTimeout
void SetTimeout(bool fast)
Definition: programs/mythbackend/filetransfer.cpp:277
MythCoreContext::GetBackendServerPort
int GetBackendServerPort(void)
Returns the locally defined backend control port.
Definition: mythcorecontext.cpp:1064
MainServer::RenameThread
friend class RenameThread
Definition: mainserver.h:122
MythCoreContext::SendSystemEvent
void SendSystemEvent(const QString &msg)
Definition: mythcorecontext.cpp:1544
MythCoreContext::SendEvent
void SendEvent(const MythEvent &event)
Definition: mythcorecontext.cpp:1530
hardwareprofile.config.p
p
Definition: config.py:33
MythCoreContext::GetMasterServerIP
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
Definition: mythcorecontext.cpp:966
MainServer::isClientConnected
bool isClientConnected(bool onlyBlockingClients=false)
Definition: mainserver.cpp:8239
MetaIO::removeAlbumArt
virtual bool removeAlbumArt([[maybe_unused]] const QString &filename, [[maybe_unused]] const AlbumArtImage *albumart)
Definition: metaio.h:80
hardwareprofile.i18n.t
t
Definition: i18n.py:36
statfs
int statfs(const char *path, struct statfs *buffer)
Definition: compat.h:105
MainServer::HandleSettingQuery
void HandleSettingQuery(const QStringList &tokens, PlaybackSock *pbs)
Definition: mainserver.cpp:5738
MainServer::s_truncate_and_close_lock
static QMutex s_truncate_and_close_lock
Definition: mainserver.h:354
ProgramInfo::GetTitle
QString GetTitle(void) const
Definition: programinfo.h:361
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
LiveTVChain::SetHostSocket
void SetHostSocket(MythSocket *sock)
Definition: livetvchain.cpp:704
compat.h
MythCoreContext::GetBackendServerIP
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
Definition: mythcorecontext.cpp:1004
FileSystemInfo::setFSysID
void setFSysID(int id)
Definition: filesysteminfo.h:50
GENERIC_EXIT_SOCKET_ERROR
@ GENERIC_EXIT_SOCKET_ERROR
Socket error.
Definition: exitcodes.h:19
MythServer
Definition: serverpool.h:131
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
JobQueue::DeleteAllJobs
static bool DeleteAllJobs(uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:742
InputInfo::ToStringList
virtual void ToStringList(QStringList &list) const
Definition: inputinfo.cpp:42
GetShareDir
QString GetShareDir(void)
Definition: mythdirs.cpp:254
PlaybackSock::GetFileHash
QString GetFileHash(const QString &filename, const QString &storageGroup)
Definition: playbacksock.cpp:254
MainServer::HandleMusicTagUpdateVolatile
void HandleMusicTagUpdateVolatile(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:5943
MSqlQuery::testDBConnection
static bool testDBConnection()
Checks DB connection + login (login info via Mythcontext)
Definition: mythdbcon.cpp:877
logLevelGet
LogLevel_t logLevelGet(const QString &level)
Map a log level name back to the enumerated value.
Definition: logging.cpp:746
MainServer::HandleScanMusic
void HandleScanMusic(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:5874
MainServer::HandleGoToSleep
void HandleGoToSleep(PlaybackSock *pbs)
Definition: mainserver.cpp:3364
MainServer::HandleMusicFindLyrics
void HandleMusicFindLyrics(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6801
PRT_TIMEOUT
static constexpr std::chrono::milliseconds PRT_TIMEOUT
Milliseconds to wait for an existing thread from process request thread pool.
Definition: mainserver.cpp:95
MainServer::HandleDeleteFile
bool HandleDeleteFile(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:5522
RecStatus::Failing
@ Failing
Definition: recordingstatus.h:17
Scheduler::Stop
void Stop(void)
Definition: scheduler.cpp:138
MainServer::HandleQueryLoad
void HandleQueryLoad(PlaybackSock *pbs)
Definition: mainserver.cpp:3470
MythTimer::restart
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
ProgramInfo::QueryCutList
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
Definition: programinfo.cpp:3464
GetPlaybackURL
QString GetPlaybackURL(ProgramInfo *pginfo, bool storePath)
Definition: fileserverutil.cpp:46
FreeSpaceUpdater::m_parent
MainServer & m_parent
Definition: mainserver.cpp:236
MainServer::m_ismaster
bool m_ismaster
Definition: mainserver.h:332
StorageGroup::GetFileInfo
QStringList GetFileInfo(const QString &filename)
Definition: storagegroup.cpp:380
PlaybackSock::CheckFile
bool CheckFile(ProgramInfo *pginfo)
Definition: playbacksock.cpp:334
DeleteStruct
Definition: mainserver.h:43
MainServer::HandleQueryFindFile
void HandleQueryFindFile(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3962
ProgramInfo::toString
QString toString(Verbosity v=kLongDescription, const QString &sep=":", const QString &grp="\"") const
Definition: programinfo.cpp:1934
AlbumArtImages::getTypeFilename
static QString getTypeFilename(ImageType type)
Definition: musicmetadata.cpp:2173
MainServer::HandleBlockShutdown
void HandleBlockShutdown(bool blockShutdown, PlaybackSock *pbs)
Definition: mainserver.cpp:7719
MainServer::HandleRemoteEncoder
void HandleRemoteEncoder(QStringList &slist, QStringList &commands, PlaybackSock *pbs)
Definition: mainserver.cpp:4924
MetadataFactory::VideoScan
void VideoScan()
Definition: metadatafactory.cpp:268
PreviewGeneratorQueue::AddListener
static void AddListener(QObject *listener)
Request notifications when a preview event is generated.
Definition: previewgeneratorqueue.cpp:189
DeleteThread::run
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: deletethread.cpp:35
ServerPool::DefaultListen
static QList< QHostAddress > DefaultListen(void)
Definition: serverpool.cpp:301
MainServer::reconnectTimeout
void reconnectTimeout(void)
Definition: mainserver.cpp:8155
MainServer::HandleCheckRecordingActive
void HandleCheckRecordingActive(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:2799
scheduledrecording.h
Scheduler::RescheduleMatch
void RescheduleMatch(uint recordid, uint sourceid, uint mplexid, const QDateTime &maxstarttime, const QString &why)
Definition: scheduler.h:59
MainServer::HandleCutlistQuery
void HandleCutlistQuery(const QString &chanid, const QString &starttime, PlaybackSock *pbs)
Definition: mainserver.cpp:5661
MainServer::GetfsID
int GetfsID(const QList< FileSystemInfo >::iterator &fsInfo)
Definition: mainserver.cpp:5089
kPictureAttribute_Brightness
@ kPictureAttribute_Brightness
Definition: videoouttypes.h:107
Scheduler::GetRecording
QMap< QString, ProgramInfo * > GetRecording(void) const override
Definition: scheduler.cpp:1780
loadArray
std::array< double, 3 > loadArray
Definition: mythmiscutil.h:22
storagegroup.h
DeleteStruct::m_recstartts
QDateTime m_recstartts
Definition: mainserver.h:68
Frontend
Definition: backendcontext.h:19
Scheduler::Reschedule
void Reschedule(const QStringList &request)
Definition: scheduler.cpp:1861
jobqueue.h
MainServer::DeferredDeleteStruct::ts
QDateTime ts
Definition: mainserver.h:346
MainServer::HandleCutMapQuery
void HandleCutMapQuery(const QString &chanid, const QString &starttime, PlaybackSock *pbs, bool commbreak)
Definition: mainserver.cpp:5605
AutoExpire::SetMainServer
void SetMainServer(MainServer *ms)
Definition: autoexpire.h:82
MusicMetadata::setFilename
void setFilename(const QString &lfilename)
Definition: musicmetadata.cpp:956
MainServer::MainServer
MainServer(bool master, int port, QMap< int, EncoderLink * > *tvList, Scheduler *sched, AutoExpire *expirer)
Definition: mainserver.cpp:246
MainServer::m_deferredDeleteTimer
QTimer * m_deferredDeleteTimer
Definition: mainserver.h:350
Scheduler::SetMainServer
void SetMainServer(MainServer *ms)
Definition: scheduler.cpp:145
Scheduler::getConflicting
void getConflicting(RecordingInfo *pginfo, QStringList &strlist)
Definition: scheduler.cpp:1711
MainServer::m_expirer
AutoExpire * m_expirer
Definition: mainserver.h:340
FreeSpaceUpdater::KeepRunning
bool KeepRunning(bool dorun)
Definition: mainserver.cpp:220
TVRec::s_inputsLock
static QReadWriteLock s_inputsLock
Definition: tv_rec.h:432
kManualSearch
@ kManualSearch
Definition: recordingtypes.h:85
uint
unsigned int uint
Definition: compat.h:81
ProgramInfo::SetFilesize
virtual void SetFilesize(uint64_t sz)
Definition: programinfo.cpp:6384
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
MainServer::m_masterServerReconnect
QTimer * m_masterServerReconnect
Definition: mainserver.h:329
MainServer::DeferredDeleteStruct::sock
PlaybackSock * sock
Definition: mainserver.h:345
MainServer::HandlePixmapGetIfModified
void HandlePixmapGetIfModified(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7574
AlbumArtImages::getImageByID
AlbumArtImage * getImageByID(int imageID)
Definition: musicmetadata.cpp:2126
getUptime
bool getUptime(std::chrono::seconds &uptime)
Definition: mythmiscutil.cpp:66
MainServer::HandleMusicSaveLyrics
void HandleMusicSaveLyrics(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6975
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:912
MainServer::HandleDone
void HandleDone(MythSocket *socket)
Definition: mainserver.cpp:2073
PlaybackSock
Definition: playbacksock.h:27
MythCoreContext::ResolveIPv6
@ ResolveIPv6
Definition: mythcorecontext.h:211
RenameThread::m_dst
QString m_dst
Definition: mainserver.h:112
MetadataFactory
Definition: metadatafactory.h:85
ProgramInfo::SaveAutoExpire
void SaveAutoExpire(AutoExpireType autoExpire, bool updateDelete=false)
Set "autoexpire" field in "recorded" table to "autoExpire".
Definition: programinfo.cpp:3393
FileSystemInfo::setPath
void setPath(const QString &path)
Definition: filesysteminfo.h:48
MainServer::m_masterFreeSpaceListUpdater
FreeSpaceUpdater *volatile m_masterFreeSpaceListUpdater
Definition: mainserver.h:325
mythmediabuffer.h
MainServer::HandleGetScheduledRecordings
void HandleGetScheduledRecordings(PlaybackSock *pbs)
Definition: mainserver.cpp:3840
MainServer::m_downloadURLs
QMap< QString, QString > m_downloadURLs
Definition: mainserver.h:362
kMSAutoCleanup
@ kMSAutoCleanup
automatically delete if backgrounded
Definition: mythsystem.h:45
frm_pos_map_t
QMap< long long, long long > frm_pos_map_t
Frame # -> File offset map.
Definition: programtypes.h:45
MainServer::getGuideDataThrough
static void getGuideDataThrough(QDateTime &GuideDataThrough)
Definition: mainserver.cpp:3761
kReadTestSize
static constexpr qint64 kReadTestSize
Definition: mythmediabuffer.h:23
InputInfo::m_inputId
uint m_inputId
unique key in DB for this input
Definition: inputinfo.h:49
make_safe
static QString make_safe(const QString &str)
Definition: mainserver.cpp:4478
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:34
StorageGroup::GetRelativePathname
static QString GetRelativePathname(const QString &filename)
Returns the relative pathname of a file by comparing the filename against all Storage Group directori...
Definition: storagegroup.cpp:420
off_t
#define off_t
Definition: mythiowrapper.cpp:241
MainServer::GetSlaveByHostname
PlaybackSock * GetSlaveByHostname(const QString &hostname)
Definition: mainserver.cpp:7928
verboseArgParse
int verboseArgParse(const QString &arg)
Parse the –verbose commandline argument and set the verbose level.
Definition: logging.cpp:894
kMSRunBackground
@ kMSRunBackground
run child in the background
Definition: mythsystem.h:38
PlaybackSock::GetSGFileQuery
QStringList GetSGFileQuery(const QString &host, const QString &groupname, const QString &filename)
Definition: playbacksock.cpp:241
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:906
MainServer::HandleQueryRecordings
void HandleQueryRecordings(const QString &type, PlaybackSock *pbs)
Definition: mainserver.cpp:2132
AutoDeleteDeque
Definition: autodeletedeque.h:8
MainServer::HandleQueryGuideDataThrough
void HandleQueryGuideDataThrough(PlaybackSock *pbs)
Definition: mainserver.cpp:3772
MainServer::HandleLockTuner
void HandleLockTuner(PlaybackSock *pbs, int cardid=-1)
Definition: mainserver.cpp:4244
PreviewGeneratorQueue::TeardownPreviewGeneratorQueue
static void TeardownPreviewGeneratorQueue()
Destroy the singleton queue of preview generators.
Definition: previewgeneratorqueue.cpp:55
FreeSpaceUpdater::~FreeSpaceUpdater
~FreeSpaceUpdater() override
Definition: mainserver.cpp:187
ImageHandler::HandleScanRequest
QStringList HandleScanRequest(const QString &command, int devId=DEVICE_INVALID) const
Process scan requests.
Definition: imagemanager.cpp:1622
ProgramInfo::SetChanID
void SetChanID(uint _chanid)
Definition: programinfo.h:522
Scheduler::RescheduleCheck
void RescheduleCheck(const RecordingInfo &recinfo, const QString &why)
Definition: scheduler.h:63
AlbumArtImage::m_hostname
QString m_hostname
Definition: musicmetadata.h:50
InputInfo::m_sourceId
uint m_sourceId
associated channel listings source
Definition: inputinfo.h:48
recordinginfo.h
MythCoreContext::GetMasterServerPort
static int GetMasterServerPort(void)
Returns the Master Backend control port If no master server port has been defined in the database,...
Definition: mythcorecontext.cpp:980
MythTZ::calc_utc_offset
int calc_utc_offset(void)
Definition: mythtimezone.cpp:20
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:372
MainServer::HandleSetSetting
void HandleSetSetting(const QStringList &tokens, PlaybackSock *pbs)
Definition: mainserver.cpp:5832
MainServer::HandleVersion
void HandleVersion(MythSocket *socket, const QStringList &slist)
Definition: mainserver.cpp:1644
AutoDeleteDeque::push_back
void push_back(T info)
Definition: autodeletedeque.h:69
kTruncatingDeleteInUseID
const QString kTruncatingDeleteInUseID
Definition: programtypes.cpp:22
DeleteStruct::m_forceMetadataDelete
bool m_forceMetadataDelete
Definition: mainserver.h:71
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
MythCoreContext::ResolveIPv4
@ ResolveIPv4
Definition: mythcorecontext.h:211
MainServer::HandleQueryCheckFile
void HandleQueryCheckFile(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3581
mythmiscutil.h
MythDate::secsInPast
std::chrono::seconds secsInPast(const QDateTime &past)
Definition: mythdate.cpp:203
RecStatus::Recording
@ Recording
Definition: recordingstatus.h:29
MainServer::HandleQueryFileExists
void HandleQueryFileExists(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3699
MainServer::HandleUndeleteRecording
void HandleUndeleteRecording(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3145
ProgramInfo::ToStringList
void ToStringList(QStringList &list) const
Serializes ProgramInfo into a QStringList which can be passed over a socket.
Definition: programinfo.cpp:1267
mythcorecontext.h
SendMythSystemPlayEvent
void SendMythSystemPlayEvent(const QString &msg, const ProgramInfo *pginfo)
Definition: mythsystemevent.cpp:352
MainServer::m_stopped
bool m_stopped
Definition: mainserver.h:369
AlbumArtImages
Definition: musicmetadata.h:520
MainServer::HandlePixmapLastModified
void HandlePixmapLastModified(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7510
PlaybackSock::CheckRecordingActive
int CheckRecordingActive(const ProgramInfo *pginfo)
Definition: playbacksock.cpp:155
AlbumArtList
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:56
FileSystemInfo::setWeight
void setWeight(int weight)
Definition: filesysteminfo.h:55
StorageGroup::FindNextDirMostFree
QString FindNextDirMostFree(void)
Definition: storagegroup.cpp:666
MainServer::m_masterBackendOverride
bool m_masterBackendOverride
Definition: mainserver.h:337
cardutil.h
ProgramInfo::IsLocal
bool IsLocal(void) const
Definition: programinfo.h:351
metaio.h
ProgramInfo::SaveDeletePendingFlag
void SaveDeletePendingFlag(bool deleteFlag)
Set "deletepending" field in "recorded" table to "deleteFlag".
Definition: programinfo.cpp:3154
MainServer::ProcessRequest
void ProcessRequest(MythSocket *sock)
Definition: mainserver.cpp:460
MainServer::HandleFileTransferQuery
void HandleFileTransferQuery(QStringList &slist, QStringList &commands, PlaybackSock *pbs)
Definition: mainserver.cpp:7047
MainServer::m_threadPool
MThreadPool m_threadPool
Definition: mainserver.h:335
MainServer::HandleIsActiveBackendQuery
void HandleIsActiveBackendQuery(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:5066
MainServer::~MainServer
~MainServer() override
Definition: mainserver.cpp:360
RenameThread::m_pbs
PlaybackSock & m_pbs
Definition: mainserver.h:111
MainServer::HandleActiveBackendsQuery
void HandleActiveBackendsQuery(PlaybackSock *pbs)
Definition: mainserver.cpp:5058
MainServer::HandleQueryHostname
void HandleQueryHostname(PlaybackSock *pbs)
Definition: mainserver.cpp:3523
FileSystemInfo::setHostname
void setHostname(const QString &hostname)
Definition: filesysteminfo.h:47
MythCoreContext::GetSettingOnHost
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
Definition: mythcorecontext.cpp:926
kMSProcessEvents
@ kMSProcessEvents
process events while waiting
Definition: mythsystem.h:39
BEFileTransfer::Seek
long long Seek(long long curpos, long long pos, int whence)
Definition: programs/mythbackend/filetransfer.cpp:229
MainServer::TruncateThread
friend class TruncateThread
Definition: mainserver.h:120
MainServer::HandleScanVideos
void HandleScanVideos(PlaybackSock *pbs)
Definition: mainserver.cpp:5854
PreviewGeneratorQueue::GetPreviewImage
static void GetPreviewImage(const ProgramInfo &pginfo, const QString &token)
Submit a request for the generation of a preview image.
Definition: previewgeneratorqueue.h:89
MainServer::HandleFreeTuner
void HandleFreeTuner(int cardid, PlaybackSock *pbs)
Definition: mainserver.cpp:4325
statfs
Definition: compat.h:93
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
BEProcessRequestRunnable::m_parent
MainServer & m_parent
Definition: mainserver.cpp:175
MetaIO::getAlbumArtList
virtual AlbumArtList getAlbumArtList([[maybe_unused]] const QString &filename)
Reads the list of embedded images in the tag.
Definition: metaio.h:69
FileSystemInfo
Definition: filesysteminfo.h:15
PlaybackSock::GetSGFileList
QStringList GetSGFileList(const QString &host, const QString &groupname, const QString &directory, bool fileNamesOnly)
Definition: playbacksock.cpp:227
CardUtil::AddChildInput
static uint AddChildInput(uint parentid)
Definition: cardutil.cpp:1592
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
BEFileTransfer::RequestBlock
int RequestBlock(int size)
Definition: programs/mythbackend/filetransfer.cpp:128
Scheduler::UpdateRecStatus
void UpdateRecStatus(RecordingInfo *pginfo)
Definition: scheduler.cpp:649
PreviewGenerator::kLocalAndRemote
@ kLocalAndRemote
Definition: previewgenerator.h:45
StorageGroup::Init
void Init(const QString &group="Default", const QString &hostname="", bool allowFallback=true)
Initilizes the groupname, hostname, and dirlist.
Definition: storagegroup.cpp:106
serverpool.h
MythSocket::IsDataAvailable
bool IsDataAvailable(void)
Definition: mythsocket.cpp:557
MythSocketCBs::error
virtual void error(MythSocket *, int)
Definition: mythsocket_cb.h:18
MythSocket::GetPeerAddress
QHostAddress GetPeerAddress(void) const
Definition: mythsocket.cpp:581
ImageHandler::HandleIgnore
QStringList HandleIgnore(const QString &exclusions) const
Updates exclusion list for images.
Definition: imagemanager.cpp:1602
MainServer::deferredDeleteSlot
void deferredDeleteSlot(void)
Definition: mainserver.cpp:7728
StorageGroup::GetFileInfoList
QStringList GetFileInfoList(const QString &Path)
Definition: storagegroup.cpp:289
AlbumArtImage
Definition: musicmetadata.h:39
TVRec
This is the coordinating class of the Recorder Subsystem.
Definition: tv_rec.h:142
ProgramInfo::QueryInUseMap
static QMap< QString, uint32_t > QueryInUseMap(void)
Definition: programinfo.cpp:5523
AlbumArtImage::m_id
int m_id
Definition: musicmetadata.h:48
MythSocket::GetSocketDescriptor
int GetSocketDescriptor(void) const
Definition: mythsocket.cpp:575
MainServer::m_fsInfosCache
QList< FileSystemInfo > m_fsInfosCache
Definition: mainserver.h:358
MainServer::HandleRescheduleRecordings
void HandleRescheduleRecordings(const QStringList &request, PlaybackSock *pbs)
Definition: mainserver.cpp:3214
RecList
std::deque< RecordingInfo * > RecList
Definition: mythscheduler.h:12
MainServer::m_fsInfosCacheLock
QMutex m_fsInfosCacheLock
Definition: mainserver.h:359
statfs::f_bsize
long f_bsize
Definition: compat.h:95
mythcontext.h
MainServer::HandleMusicGetLyricGrabbers
void HandleMusicGetLyricGrabbers(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6893
MainServer::m_metadatafactory
MetadataFactory * m_metadatafactory
Definition: mainserver.h:316
MainServer::SendResponse
void SendResponse(MythSocket *sock, QStringList &commands)
Definition: mainserver.cpp:2094
FileSystemInfo::setBlockSize
void setBlockSize(int size)
Definition: filesysteminfo.h:52
BEProcessRequestRunnable
Definition: mainserver.cpp:149
MainServer::m_masterFreeSpaceListWait
QWaitCondition m_masterFreeSpaceListWait
Definition: mainserver.h:326
metadatafactory.h
mthread.h
GetAppBinDir
QString GetAppBinDir(void)
Definition: mythdirs.cpp:253
StorageGroup::kSpecialGroups
static const QStringList kSpecialGroups
Definition: storagegroup.h:46
tv_rec.h
MainServer::HandleGetRecorderFromNum
void HandleGetRecorderFromNum(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7192
Scheduler::GetRecStatus
RecStatus::Type GetRecStatus(const ProgramInfo &pginfo)
Definition: scheduler.cpp:1806
kState_WatchingLiveTV
@ kState_WatchingLiveTV
Watching LiveTV is the state for when we are watching a recording and the user has control over the c...
Definition: tv.h:63
StorageGroup
Definition: storagegroup.h:11
FreeSpaceUpdater::m_running
bool m_running
Definition: mainserver.cpp:239
BackendContext::SetFrontendConnected
void SetFrontendConnected(Frontend *frontend)
Definition: backendcontext.cpp:26
InputInfo
Definition: inputinfo.h:14
previewgeneratorqueue.h
mainserver.h
MythDate::current_iso_string
QString current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
Definition: mythdate.cpp:22
MainServer::m_downloadURLsLock
QMutex m_downloadURLsLock
Definition: mainserver.h:361
BEFileTransfer::GetFileSize
uint64_t GetFileSize(void)
Definition: programs/mythbackend/filetransfer.cpp:261
ImageManagerBe::getInstance
static ImageManagerBe * getInstance()
Get Backend Gallery.
Definition: imagemanager.cpp:1942
FreeSpaceUpdater::m_lock
QMutex m_lock
Definition: mainserver.cpp:237
MainServer::HandleForgetRecording
void HandleForgetRecording(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:3342
BrowseDirection
BrowseDirection
Used to request ProgramInfo for channel browsing.
Definition: tv.h:37
ProgramInfo::QueryCommBreakList
void QueryCommBreakList(frm_dir_map_t &frames) const
Definition: programinfo.cpp:3550
MainServer::m_sockListLock
QReadWriteLock m_sockListLock
Definition: mainserver.h:318
ServerPool::listen
bool listen(QList< QHostAddress > addrs, quint16 port, bool requireall=true, PoolServerType type=kTCPServer)
Definition: serverpool.cpp:391
FreeSpaceUpdater::m_dorun
bool m_dorun
Definition: mainserver.cpp:238
BEFileTransfer::WriteBlock
int WriteBlock(int size)
Definition: programs/mythbackend/filetransfer.cpp:168
MainServer::GetCurrentMaxBitrate
size_t GetCurrentMaxBitrate(void)
Definition: mainserver.cpp:5099
CardUtil::GetInputInfo
static bool GetInputInfo(InputInfo &input, std::vector< uint > *groupids=nullptr)
Definition: cardutil.cpp:1696
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:838
Scheduler::GetAllPending
bool GetAllPending(RecList &retList, int recRuleId=0) const
Definition: scheduler.cpp:1741
PlaybackSock::getSocket
MythSocket * getSocket(void) const
Definition: playbacksock.h:36
ImageHandler::HandleDelete
QStringList HandleDelete(const QString &ids) const
Deletes images/dirs.
Definition: imagemanager.cpp:1285
PlaybackSock::GetFindFile
QStringList GetFindFile(const QString &host, const QString &filename, const QString &storageGroup, bool useRegex)
Definition: playbacksock.cpp:264
BEProcessRequestRunnable::BEProcessRequestRunnable
BEProcessRequestRunnable(MainServer &parent, MythSocket *sock)
Definition: mainserver.cpp:152
BEFileTransfer
Definition: programs/mythbackend/filetransfer.h:20
MainServer::HandleSGGetFileList
void HandleSGGetFileList(QStringList &sList, PlaybackSock *pbs)
Definition: mainserver.cpp:3886
MThreadPool::setMaxThreadCount
void setMaxThreadCount(int maxThreadCount)
Definition: mthreadpool.cpp:518
MythCoreContext::ClearSettingsCache
void ClearSettingsCache(const QString &myKey=QString(""))
Definition: mythcorecontext.cpp:828
ProgramInfo::IsSameProgram
bool IsSameProgram(const ProgramInfo &other) const
Checks whether this is the same program as "other", which may or may not be a repeat or on another ch...
Definition: programinfo.cpp:2251
MythCoreContext::IsThisHost
bool IsThisHost(const QString &addr)
is this address mapped to this host
Definition: mythcorecontext.cpp:721
DeleteStruct::m_size
off_t m_size
Definition: mainserver.h:73
musicbrainzngs.caa.hostname
string hostname
Definition: caa.py:17
MainServer::HandleDownloadFile
void HandleDownloadFile(const QStringList &command, PlaybackSock *pbs)
Definition: mainserver.cpp:5758
BEProcessRequestRunnable::m_sock
MythSocket * m_sock
Definition: mainserver.cpp:176
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
RenameThread::m_ms
MainServer & m_ms
Definition: mainserver.h:110
MainServer::SetExitCode
void SetExitCode(int exitCode, bool closeApplication)
Definition: mainserver.cpp:8055
BEProcessRequestRunnable::run
void run(void) override
Definition: mainserver.cpp:167
MainServer::HandleRecorderQuery
void HandleRecorderQuery(QStringList &slist, QStringList &commands, PlaybackSock *pbs)
Definition: mainserver.cpp:4485
MythServer::newConnection
void newConnection(qintptr socket)
kMSDontDisableDrawing
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
CardUtil::GetHostname
static QString GetHostname(uint inputid)
Definition: cardutil.h:305
PlaybackSock::DeleteRecording
int DeleteRecording(const ProgramInfo *pginfo, bool forceMetadataDelete=false)
Definition: playbacksock.cpp:189
MainServer::m_deferredDeleteList
MythDeque< DeferredDeleteStruct > m_deferredDeleteList
Definition: mainserver.h:351
MythCoreContext::GetMasterHostPrefix
QString GetMasterHostPrefix(const QString &storageGroup=QString(), const QString &path=QString())
Definition: mythcorecontext.cpp:798
MainServer::HandleQueryUptime
void HandleQueryUptime(PlaybackSock *pbs)
Definition: mainserver.cpp:3501
RecordingInfo::ApplyRecordRecGroupChange
void ApplyRecordRecGroupChange(const QString &newrecgroup)
Sets the recording group, both in this RecordingInfo and in the database.
Definition: recordinginfo.cpp:655
PlaybackSock::ForwardRequest
QStringList ForwardRequest(const QStringList &slist)
Definition: playbacksock.cpp:536
mythdownloadmanager.h
recordingrule.h
MainServer::HandleMoveFile
void HandleMoveFile(PlaybackSock *pbs, const QString &storagegroup, const QString &src, const QString &dst)
Definition: mainserver.cpp:5429
MainServer::m_decrRefSocketList
std::vector< MythSocket * > m_decrRefSocketList
Definition: mainserver.h:322
AlbumArtImage::m_description
QString m_description
Definition: musicmetadata.h:52
MainServer::HandleGenPreviewPixmap
void HandleGenPreviewPixmap(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:7346
MainServer::HandleMusicTagAddImage
void HandleMusicTagAddImage(const QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:6520
MainServer::DoTruncateThread
void DoTruncateThread(DeleteStruct *ds)
Definition: mainserver.cpp:5509
PlaybackSockEventsMode
PlaybackSockEventsMode
Definition: playbacksock.h:20
ProgramInfo::GetBasename
QString GetBasename(void) const
Definition: programinfo.h:344
ReferenceCounter::IncrRef
virtual int IncrRef(void)
Increments reference count.
Definition: referencecounter.cpp:101
MainServer::m_deletelock
QMutex m_deletelock
Definition: mainserver.h:334
build_compdb.filename
filename
Definition: build_compdb.py:21
MThreadPool::globalInstance
static MThreadPool * globalInstance(void)
Definition: mthreadpool.cpp:307
BEFileTransfer::Stop
void Stop(void)
Definition: programs/mythbackend/filetransfer.cpp:81
FileHash
QString FileHash(const QString &filename)
Definition: mythmiscutil.cpp:548
MainServer::Stop
void Stop(void)
Definition: mainserver.cpp:366
FileSystemInfo::clear
void clear(void)
Definition: filesysteminfo.cpp:84
MainServer::DeleteThread
friend class DeleteThread
Definition: mainserver.h:119
MainServer::GetActiveBackends
void GetActiveBackends(QStringList &hosts)
Definition: mainserver.cpp:5040
MainServer::HandleQueryFreeSpaceSummary
void HandleQueryFreeSpaceSummary(PlaybackSock *pbs)
Definition: mainserver.cpp:3427
MythCoreContext::dispatch
void dispatch(const MythEvent &event)
Definition: mythcorecontext.cpp:1719
ServerPool::setProxy
void setProxy(const QNetworkProxy &proxy)
Definition: serverpool.h:98
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:77
Scheduler::Wait
void Wait(void)
Definition: scheduler.h:53
MusicMetadata::getTagger
MetaIO * getTagger(void)
Definition: musicmetadata.cpp:1388
MythCoreContext::SaveSettingOnHost
bool SaveSettingOnHost(const QString &key, const QString &newValue, const QString &host)
Definition: mythcorecontext.cpp:891
MythRandomStd::MythRandom
uint32_t MythRandom()
generate 32 random bits
Definition: mythrandom.h:20
MythSocket::ReadStringList
bool ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS=kShortTimeout)
Definition: mythsocket.cpp:313
ImageType
ImageType
Definition: musicmetadata.h:28
kState_RecordingOnly
@ kState_RecordingOnly
Recording Only is a TVRec only state for when we are recording a program, but there is no one current...
Definition: tv.h:84
DeleteStruct::m_recordedid
uint m_recordedid
Definition: mainserver.h:70
FreeSpaceUpdater
Definition: mainserver.cpp:179
kPictureAttribute_Hue
@ kPictureAttribute_Hue
Definition: videoouttypes.h:110
MythObservable::removeListener
void removeListener(QObject *listener)
Remove a listener to the observable.
Definition: mythobservable.cpp:55
ImageHandler::HandleCreateThumbnails
QStringList HandleCreateThumbnails(const QStringList &message) const
Creates thumbnails on-demand.
Definition: imagemanager.cpp:1668
LiveTVChain::DeleteProgram
void DeleteProgram(ProgramInfo *pginfo)
Definition: livetvchain.cpp:145
MainServer::m_sched
Scheduler * m_sched
Definition: mainserver.h:339
musicmetadata.h
MainServer::DoDeleteInDB
static void DoDeleteInDB(DeleteStruct *ds)
Definition: mainserver.cpp:2562
mythtimezone.h
videoutils.h
FreeSpaceUpdater::kExitTimeout
static constexpr std::chrono::milliseconds kExitTimeout
Definition: mainserver.cpp:243
ProgramInfo::SendDeletedEvent
void SendDeletedEvent(void) const
Sends event out that the ProgramInfo should be delete from lists.
Definition: programinfo.cpp:2758
MThreadPool::start
void start(QRunnable *runnable, const QString &debugName, int priority=0)
Definition: mthreadpool.cpp:342
MainServer::DoDeleteThread
void DoDeleteThread(DeleteStruct *ds)
Definition: mainserver.cpp:2343
ProgramInfo::HasPathname
bool HasPathname(void) const
Definition: programinfo.h:358
InputInfo::m_mplexId
uint m_mplexId
mplexid restriction if applicable
Definition: inputinfo.h:50
AutoDeleteDeque::size
size_t size(void) const
Definition: autodeletedeque.h:67
MainServer::HandleFillProgramInfo
void HandleFillProgramInfo(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:2304
MainServer::HandleAddChildInput
bool HandleAddChildInput(uint inputid)
Definition: mainserver.cpp:3234
PRT_STARTUP_THREAD_COUNT
static constexpr int PRT_STARTUP_THREAD_COUNT
Number of threads in process request thread pool at startup.
Definition: mainserver.cpp:97
MainServer::HandleSetChannelInfo
void HandleSetChannelInfo(QStringList &slist, PlaybackSock *pbs)
Definition: mainserver.cpp:4889
MetaIO::writeAlbumArt
virtual bool writeAlbumArt([[maybe_unused]] const QString &filename, [[maybe_unused]] const AlbumArtImage *albumart)
Definition: metaio.h:74
PlaybackSock::PixmapLastModified
QDateTime PixmapLastModified(const ProgramInfo *pginfo)
Definition: playbacksock.cpp:319
DeleteStruct::m_ms
MainServer * m_ms
Definition: mainserver.h:64
MainServer::DoHandleDeleteRecording
void DoHandleDeleteRecording(RecordingInfo &recinfo, PlaybackSock *pbs, bool forceMetadataDelete, bool lexpirer=false, bool forgetHistory=false)
Definition: mainserver.cpp:2999
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:898
GetMythDownloadManager
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
Definition: mythdownloadmanager.cpp:145
AlbumArtImage::m_filename
QString m_filename
Definition: musicmetadata.h:49
ProgramInfo::kRecordingKey
@ kRecordingKey
Definition: programinfo.h:510
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
AlbumArtImage::m_embedded
bool m_embedded
Definition: musicmetadata.h:53
MythSystem::Create
static MythSystem * Create(const QStringList &args, uint flags=kMSNone, const QString &startPath=QString(), Priority cpuPriority=kInheritPriority, Priority diskPriority=kInheritPriority)
Definition: mythsystem.cpp:201
kPictureAttribute_Colour
@ kPictureAttribute_Colour
Definition: videoouttypes.h:109
MainServer::HandleBookmarkQuery
void HandleBookmarkQuery(const QString &chanid, const QString &starttime, PlaybackSock *pbs)
Definition: mainserver.cpp:5677
LiveTVChain
Keeps track of recordings in a current LiveTV instance.
Definition: livetvchain.h:32
CardUtil::DeleteInput
static bool DeleteInput(uint inputid)
Definition: cardutil.cpp:2809