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