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