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