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