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