MythTV master
v2status.cpp
Go to the documentation of this file.
1
2// Program Name: httpstatus.cpp
3//
4// Purpose - Html & XML status HttpServerExtension
5//
6// Created By : David Blain Created On : Oct. 24, 2005
7// Modified By : Modified On:
8//
10
11// POSIX headers
12#include <unistd.h>
13
14// ANSI C headers
15#include <cmath>
16#include <cstdio>
17#include <cstdlib>
18
19// Qt headers
20#include <QtGlobal>
21#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
22#include <QStringConverter>
23#endif
24#include <QTextStream>
25
26// MythTV headers
27#include "libmythbase/compat.h"
36#include "libmythbase/mythversion.h"
37#include "libmythtv/cardutil.h"
38#include "libmythtv/jobqueue.h"
39#include "libmythtv/tv.h"
40#include "libmythtv/tv_rec.h"
42#include "libmythupnp/upnp.h"
43
44// MythBackend
45#include "autoexpire.h"
46#include "backendcontext.h"
47#include "encoderlink.h"
48#include "mainserver.h"
49#include "scheduler.h"
50#include "v2backendStatus.h"
51#include "v2serviceUtil.h"
52#include "v2status.h"
53
54// This will be initialised in a thread safe manner on first use
56 (STATUS_HANDLE, V2Status::staticMetaObject, &V2Status::RegisterCustomTypes))
57
59{
60 qRegisterMetaType<Preformat*>("Preformat");
61 qRegisterMetaType<V2MachineInfo*>("V2MachineInfo");
62 qRegisterMetaType<V2BackendStatus*>("V2BackendStatus");
63 qRegisterMetaType<V2Encoder*>("V2Encoder");
64 qRegisterMetaType<V2Program*>("V2Program");
65 qRegisterMetaType<V2Frontend*>("V2Frontend");
66 qRegisterMetaType<V2StorageGroup*>("V2StorageGroup");
67 qRegisterMetaType<V2Job*>("V2Job");
68 qRegisterMetaType<V2ChannelInfo*>("V2ChannelInfo");
69 qRegisterMetaType<V2RecordingInfo*>("V2RecordingInfo");
70 qRegisterMetaType<V2ArtworkInfoList*>("V2ArtworkInfoList");
71 qRegisterMetaType<V2ArtworkInfo*>("V2ArtworkInfo");
72 qRegisterMetaType<V2CastMemberList*>("V2CastMemberList");
73 qRegisterMetaType<V2CastMember*>("V2CastMember");
74 qRegisterMetaType<V2Input*>("V2Input");
75 qRegisterMetaType<V2Backend*>("V2Backend");
76}
77
79 m_pSched(dynamic_cast<Scheduler*>(gCoreContext->GetScheduler())),
80 m_pEncoders(&gTVList) // extern
81{
82 if (m_pSched)
85 m_nPreRollSeconds = gCoreContext->GetNumSetting("RecordPreRoll", 0);
86}
87
88// HTML
90{
91 return GetStatusHTML();
92}
93
94// XML
96{
97 return GetStatus();
98}
99
100// XML
102{
103 QDomDocument doc( "Status" );
104 // UTF-8 is the default, but good practice to specify it anyway
105 QDomProcessingInstruction encoding =
106 doc.createProcessingInstruction("xml",
107 R"(version="1.0" encoding="UTF-8")");
108 doc.appendChild(encoding);
109 FillStatusXML( &doc );
110 auto *pResult = new Preformat();
111 pResult->setmimetype("application/xml");
112 pResult->setbuffer(doc.toString());
113 return pResult;
114}
115
116// HTML
118{
119 QDomDocument doc( "Status" );
120 FillStatusXML( &doc );
121 QString html;
122 QTextStream stream( &html );
123 PrintStatus( stream, &doc );
124 auto *pResult = new Preformat();
125 pResult->setmimetype("text/html");
126 pResult->setbuffer(html);
127 return pResult;
128}
129
130static QString setting_to_localtime(const char *setting)
131{
132 QString origDateString = gCoreContext->GetSetting(setting);
133 QDateTime origDate = MythDate::fromString(origDateString);
135}
136
137static QDateTime setting_to_qdatetime(const char *setting)
138{
139 QString origDateString = gCoreContext->GetSetting(setting);
140 QDateTime origDate = MythDate::fromString(origDateString);
141 return origDate;
142}
143
144// Standardized version of GetStatus that supports xml, json, etc.
146{
147 auto* pStatus = new V2BackendStatus();
148 pStatus->setAsOf ( MythDate::current() );
149 pStatus->setVersion ( MYTH_BINARY_VERSION );
150 pStatus->setProtoVer ( MYTH_PROTO_VERSION );
151 pStatus->setSourcePath ( GetMythSourcePath() );
152 pStatus->setSourceVer ( GetMythSourceVersion() );
153 pStatus->setHostName ( gCoreContext->GetHostName() );
154 // Encoders
155 FillEncoderList(pStatus->GetEncoders(), pStatus);
156 // Upcoming recordings
157 int nStartIndex = 0;
158 int nCount = 10;
159 // Scheduled Recordings
160 FillUpcomingList(pStatus->GetScheduled(), pStatus,
161 nStartIndex,
162 nCount,
163 true, // bShowAll,
164 -1, // nRecordId,
165 -999); // nRecStatus )
166 // Frontends
167 FillFrontendList(pStatus->GetFrontends(), pStatus,
168 false); // OnLine)
169 // Backends
170 V2Backend *backend {nullptr};
171
172 // Add this host
173 backend = pStatus->AddNewBackend();
174 QString thisHost = gCoreContext->GetHostName();
175 backend->setName(thisHost);
176 backend->setIP(gCoreContext->GetBackendServerIP());
177
178 if (m_pMainServer)
179 {
180 backend->setType("Master");
181 QStringList backends;
183 for (const QString& hostname : std::as_const(backends))
184 {
185 if (hostname != thisHost)
186 {
188 backend = pStatus->AddNewBackend();
189 backend->setName(hostname);
190 backend->setType("Slave");
191 if (pSock)
192 {
193 backend->setIP(pSock->getIP());
194 }
195 }
196 }
197 }
198 else
199 {
200 backend->setType("Slave");
201 QString masterhost = gCoreContext->GetMasterHostName();
202 QString masterip = gCoreContext->GetMasterServerIP();
203 backend = pStatus->AddNewBackend();
204 backend->setName(masterhost);
205 backend->setIP(masterip);
206 backend->setType("Master");
207 }
208
209 // Add Job Queue Entries
210 QMap<int, JobQueueEntry> jobs;
211 QMap<int, JobQueueEntry>::Iterator it;
212
216
217 for (it = jobs.begin(); it != jobs.end(); ++it)
218 {
219 ProgramInfo pginfo((*it).chanid, (*it).recstartts);
220 if (!pginfo.GetChanID())
221 continue;
222
223 V2Job * pJob = pStatus->AddNewJob();
224
225 pJob->setId( (*it).id );
226 pJob->setChanId((*it).chanid );
227 pJob->setStartTime( (*it).recstartts);
228 pJob->setStartTs( (*it).startts );
229 pJob->setInsertTime((*it).inserttime);
230 pJob->setType( (*it).type );
231 pJob->setLocalizedJobName( JobQueue::JobText((*it).type) );
232 pJob->setCmds( (*it).cmds );
233 pJob->setFlags( (*it).flags );
234 pJob->setStatus( (*it).status );
235 pJob->setLocalizedStatus( JobQueue::StatusText((*it).status) );
236 pJob->setStatusTime( (*it).statustime);
237 pJob->setSchedRunTime( (*it).schedruntime);
238 pJob->setArgs( (*it).args );
239 if ((*it).hostname.isEmpty())
240 pJob->setHostName( QObject::tr("master"));
241 else
242 pJob->setHostName((*it).hostname);
243 pJob->setComment((*it).comment);
244 V2Program *pProgram = pJob->Program();
245 V2FillProgramInfo( pProgram, &pginfo, true, false, false);
246 }
247
248 // Machine Info
249 V2MachineInfo *pMachineInfo = pStatus->MachineInfo();
250 FillDriveSpace(pMachineInfo);
251 // load average
252 loadArray rgdAverages = getLoadAvgs();
253 if (rgdAverages[0] != -1)
254 {
255 pMachineInfo->setLoadAvg1(rgdAverages[0]);
256 pMachineInfo->setLoadAvg2(rgdAverages[1]);
257 pMachineInfo->setLoadAvg3(rgdAverages[2]);
258 }
259
260 // Guide Data
261 QDateTime GuideDataThrough;
263 query.prepare("SELECT MAX(endtime) FROM program WHERE manualid = 0;");
264 if (query.exec() && query.next())
265 {
266 GuideDataThrough = MythDate::fromString(query.value(0).toString());
267 }
268 pMachineInfo->setGuideStart
269 (setting_to_qdatetime("mythfilldatabaseLastRunStart"));
270 pMachineInfo->setGuideEnd(
271 setting_to_qdatetime("mythfilldatabaseLastRunEnd"));
272 pMachineInfo->setGuideStatus(
273 gCoreContext->GetSetting("mythfilldatabaseLastRunStatus"));
274 if (gCoreContext->GetBoolSetting("MythFillGrabberSuggestsTime", false))
275 {
276 pMachineInfo->setGuideNext(
277 gCoreContext->GetSetting("MythFillSuggestedRunTime"));
278 }
279
280 if (!GuideDataThrough.isNull())
281 {
282 QDateTime qdtNow = MythDate::current();
283 pMachineInfo->setGuideThru(GuideDataThrough);
284 pMachineInfo->setGuideDays(qdtNow.daysTo(GuideDataThrough));
285 }
286
287 // Add Miscellaneous information
288 QString info_script = gCoreContext->GetSetting("MiscStatusScript");
289 if ((!info_script.isEmpty()) && (info_script != "none"))
290 {
291 uint flags = kMSRunShell | kMSStdOut;
292 MythSystemLegacy ms(info_script, flags);
293 ms.Run(10s);
294 if (ms.Wait() != GENERIC_EXIT_OK)
295 {
296 LOG(VB_GENERAL, LOG_ERR,
297 QString("Error running miscellaneous "
298 "status information script: %1").arg(info_script));
299 }
300
301 QByteArray input = ms.ReadAll();
302 pStatus->setMiscellaneous(QString(input));
303 }
304 return pStatus;
305}
306
308{
309 QStringList strlist;
310 QString hostname;
311 QString directory;
312 QString isLocalstr;
313 QString fsID;
314
315 if (m_pMainServer)
317
318 QStringList::const_iterator sit = strlist.cbegin();
319 while (sit != strlist.cend())
320 {
321 hostname = *(sit++);
322 directory = *(sit++);
323 isLocalstr = *(sit++);
324 fsID = *(sit++);
325 ++sit; // ignore dirID
326 ++sit; // ignore blocksize
327 long long iTotal = (*(sit++)).toLongLong();
328 long long iUsed = (*(sit++)).toLongLong();;
329 long long iAvail = iTotal - iUsed;
330
331 if (fsID == "-2")
332 fsID = "total";
333
334 V2StorageGroup* group = pMachineInfo->AddNewStorageGroup();
335 group->setId(QString(fsID));
336 group->setTotal((int)(iTotal>>10));
337 group->setUsed((int)(iUsed>>10));
338 group->setFree((int)(iAvail>>10));
339 group->setDirectory(directory);
340
341 if (fsID == "total")
342 {
343 long long iLiveTV = -1;
344 long long iDeleted = -1;
345 long long iExpirable = -1;
347 query.prepare("SELECT SUM(filesize) FROM recorded "
348 " WHERE recgroup = :RECGROUP;");
349
350 query.bindValue(":RECGROUP", "LiveTV");
351 if (query.exec() && query.next())
352 {
353 iLiveTV = query.value(0).toLongLong();
354 }
355 query.bindValue(":RECGROUP", "Deleted");
356 if (query.exec() && query.next())
357 {
358 iDeleted = query.value(0).toLongLong();
359 }
360 query.prepare("SELECT SUM(filesize) FROM recorded "
361 " WHERE autoexpire = 1 "
362 " AND recgroup NOT IN ('LiveTV', 'Deleted');");
363 if (query.exec() && query.next())
364 {
365 iExpirable = query.value(0).toLongLong();
366 }
367 group->setLiveTV( (int)(iLiveTV>>20) );
368 group->setDeleted( (int)(iDeleted>>20) );
369 group->setExpirable ( (int)(iExpirable>>20) );
370 }
371 }
372
373}
374
375void V2Status::FillStatusXML( QDomDocument *pDoc )
376{
377 QDateTime qdtNow = MythDate::current();
378
379 // Add Root Node.
380
381 QDomElement root = pDoc->createElement("Status");
382 pDoc->appendChild(root);
383
384 root.setAttribute("date" , MythDate::toString(
386 root.setAttribute("time" ,
388 root.setAttribute("ISODate" , qdtNow.toString(Qt::ISODate) );
389 root.setAttribute("version" , MYTH_BINARY_VERSION );
390 root.setAttribute("protoVer", MYTH_PROTO_VERSION );
391
392 // Add all encoders, if any
393
394 QDomElement encoders = pDoc->createElement("Encoders");
395 root.appendChild(encoders);
396
397 int numencoders = 0;
398 bool isLocal = true;
399
400 TVRec::s_inputsLock.lockForRead();
401
402 for (auto * elink : std::as_const(*m_pEncoders))
403 {
404 if (elink != nullptr)
405 {
406 TVState state = elink->GetState();
407 isLocal = elink->IsLocal();
408
409 QDomElement encoder = pDoc->createElement("Encoder");
410 encoders.appendChild(encoder);
411
412 encoder.setAttribute("id" , elink->GetInputID() );
413 encoder.setAttribute("local" , static_cast<int>(isLocal));
414 encoder.setAttribute("connected" , static_cast<int>(elink->IsConnected()));
415 encoder.setAttribute("state" , state );
416 encoder.setAttribute("sleepstatus" , elink->GetSleepStatus() );
417 //encoder.setAttribute("lowOnFreeSpace", elink->isLowOnFreeSpace());
418
419 if (isLocal)
420 encoder.setAttribute("hostname", gCoreContext->GetHostName());
421 else
422 encoder.setAttribute("hostname", elink->GetHostName());
423
424 encoder.setAttribute("devlabel",
425 CardUtil::GetDeviceLabel(elink->GetInputID()) );
426
427 if (elink->IsConnected())
428 numencoders++;
429
430 switch (state)
431 {
435 {
436 ProgramInfo *pInfo = elink->GetRecording();
437
438 if (pInfo)
439 {
440 FillProgramInfo(pDoc, encoder, pInfo);
441 delete pInfo;
442 }
443
444 break;
445 }
446
447 default:
448 break;
449 }
450 }
451 }
452
453 TVRec::s_inputsLock.unlock();
454
455 encoders.setAttribute("count", numencoders);
456
457 // Add upcoming shows
458
459 QDomElement scheduled = pDoc->createElement("Scheduled");
460 root.appendChild(scheduled);
461
462 RecList recordingList;
463
464 if (m_pSched)
465 m_pSched->GetAllPending(recordingList);
466
467 unsigned int iNum = 10;
468 unsigned int iNumRecordings = 0;
469
470 auto itProg = recordingList.begin();
471 for (; (itProg != recordingList.end()) && iNumRecordings < iNum; ++itProg)
472 {
473 if (((*itProg)->GetRecordingStatus() <= RecStatus::WillRecord) &&
474 ((*itProg)->GetRecordingStartTime() >=
476 {
477 iNumRecordings++;
478 FillProgramInfo(pDoc, scheduled, *itProg);
479 }
480 }
481
482 while (!recordingList.empty())
483 {
484 ProgramInfo *pginfo = recordingList.back();
485 delete pginfo;
486 recordingList.pop_back();
487 }
488
489 scheduled.setAttribute("count", iNumRecordings);
490
491 // Add known frontends
492
493 QDomElement frontends = pDoc->createElement("Frontends");
494 root.appendChild(frontends);
495
497 "urn:schemas-mythtv-org:service:MythFrontend:1");
498 if (fes)
499 {
500 EntryMap map;
501 fes->GetEntryMap(map);
502 fes->DecrRef();
503 fes = nullptr;
504
505 frontends.setAttribute( "count", map.size() );
506 for (const auto & entry : std::as_const(map))
507 {
508 QDomElement fe = pDoc->createElement("Frontend");
509 frontends.appendChild(fe);
510 QUrl url(entry->m_sLocation);
511 fe.setAttribute("name", url.host());
512 fe.setAttribute("url", url.toString(QUrl::RemovePath));
513 entry->DecrRef();
514 }
515 }
516
517 // Other backends
518
519 QDomElement backends = pDoc->createElement("Backends");
520 root.appendChild(backends);
521
522 int numbes = 0;
524 {
525 numbes++;
526 QString masterhost = gCoreContext->GetMasterHostName();
527 QString masterip = gCoreContext->GetMasterServerIP();
528 int masterport = gCoreContext->GetMasterServerStatusPort();
529
530 QDomElement mbe = pDoc->createElement("Backend");
531 backends.appendChild(mbe);
532 mbe.setAttribute("type", "Master");
533 mbe.setAttribute("name", masterhost);
534 mbe.setAttribute("url" , masterip + ":" + QString::number(masterport));
535 }
536
538 "urn:schemas-mythtv-org:device:SlaveMediaServer:1");
539 if (sbes)
540 {
541
542 QString ipaddress = QString();
543 if (!UPnp::g_IPAddrList.isEmpty())
544 ipaddress = UPnp::g_IPAddrList.at(0).toString();
545
546 EntryMap map;
547 sbes->GetEntryMap(map);
548 sbes->DecrRef();
549 sbes = nullptr;
550
551 for (const auto & entry : std::as_const(map))
552 {
553 QUrl url(entry->m_sLocation);
554 if (url.host() != ipaddress)
555 {
556 numbes++;
557 QDomElement mbe = pDoc->createElement("Backend");
558 backends.appendChild(mbe);
559 mbe.setAttribute("type", "Slave");
560 mbe.setAttribute("name", url.host());
561 mbe.setAttribute("url" , url.toString(QUrl::RemovePath));
562 }
563 entry->DecrRef();
564 }
565 }
566
567 backends.setAttribute("count", numbes);
568
569 // Add Job Queue Entries
570
571 QDomElement queue = pDoc->createElement("JobQueue");
572 root.appendChild(queue);
573
574 QMap<int, JobQueueEntry> jobs;
575 QMap<int, JobQueueEntry>::Iterator it;
576
580
581 for (it = jobs.begin(); it != jobs.end(); ++it)
582 {
583 ProgramInfo pginfo((*it).chanid, (*it).recstartts);
584 if (!pginfo.GetChanID())
585 continue;
586
587 QDomElement job = pDoc->createElement("Job");
588 queue.appendChild(job);
589
590 job.setAttribute("id" , (*it).id );
591 job.setAttribute("chanId" , (*it).chanid );
592 job.setAttribute("startTime" ,
593 (*it).recstartts.toString(Qt::ISODate));
594 job.setAttribute("startTs" , (*it).startts );
595 job.setAttribute("insertTime",
596 (*it).inserttime.toString(Qt::ISODate));
597 job.setAttribute("type" , (*it).type );
598 job.setAttribute("cmds" , (*it).cmds );
599 job.setAttribute("flags" , (*it).flags );
600 job.setAttribute("status" , (*it).status );
601 job.setAttribute("statusTime",
602 (*it).statustime.toString(Qt::ISODate));
603 job.setAttribute("schedTime" ,
604 (*it).schedruntime.toString(Qt::ISODate));
605 job.setAttribute("args" , (*it).args );
606
607 if ((*it).hostname.isEmpty())
608 job.setAttribute("hostname", QObject::tr("master"));
609 else
610 job.setAttribute("hostname",(*it).hostname);
611
612 QDomText textNode = pDoc->createTextNode((*it).comment);
613 job.appendChild(textNode);
614
615 FillProgramInfo(pDoc, job, &pginfo);
616 }
617
618 queue.setAttribute( "count", jobs.size() );
619
620 // Add Machine information
621
622 QDomElement mInfo = pDoc->createElement("MachineInfo");
623 QDomElement storage = pDoc->createElement("Storage" );
624 QDomElement load = pDoc->createElement("Load" );
625 QDomElement guide = pDoc->createElement("Guide" );
626
627 root.appendChild (mInfo );
628 mInfo.appendChild(storage);
629 mInfo.appendChild(load );
630 mInfo.appendChild(guide );
631
632 // drive space ---------------------
633
634 QStringList strlist;
635 QString hostname;
636 QString directory;
637 QString isLocalstr;
638 QString fsID;
639
640 if (m_pMainServer)
642
643 QDomElement total;
644
645 // Make a temporary list to hold the per-filesystem elements so that the
646 // total is always the first element.
647 QList<QDomElement> fsXML;
648 QStringList::const_iterator sit = strlist.cbegin();
649 while (sit != strlist.cend())
650 {
651 hostname = *(sit++);
652 directory = *(sit++);
653 isLocalstr = *(sit++);
654 fsID = *(sit++);
655 ++sit; // ignore dirID
656 ++sit; // ignore blocksize
657 long long iTotal = (*(sit++)).toLongLong();
658 long long iUsed = (*(sit++)).toLongLong();;
659 long long iAvail = iTotal - iUsed;
660
661 if (fsID == "-2")
662 fsID = "total";
663
664 QDomElement group = pDoc->createElement("Group");
665
666 group.setAttribute("id" , fsID );
667 group.setAttribute("total", (int)(iTotal>>10) );
668 group.setAttribute("used" , (int)(iUsed>>10) );
669 group.setAttribute("free" , (int)(iAvail>>10) );
670 group.setAttribute("dir" , directory );
671
672 if (fsID == "total")
673 {
674 long long iLiveTV = -1;
675 long long iDeleted = -1;
676 long long iExpirable = -1;
678 query.prepare("SELECT SUM(filesize) FROM recorded "
679 " WHERE recgroup = :RECGROUP;");
680
681 query.bindValue(":RECGROUP", "LiveTV");
682 if (query.exec() && query.next())
683 {
684 iLiveTV = query.value(0).toLongLong();
685 }
686 query.bindValue(":RECGROUP", "Deleted");
687 if (query.exec() && query.next())
688 {
689 iDeleted = query.value(0).toLongLong();
690 }
691 query.prepare("SELECT SUM(filesize) FROM recorded "
692 " WHERE autoexpire = 1 "
693 " AND recgroup NOT IN ('LiveTV', 'Deleted');");
694 if (query.exec() && query.next())
695 {
696 iExpirable = query.value(0).toLongLong();
697 }
698 group.setAttribute("livetv", (int)(iLiveTV>>20) );
699 group.setAttribute("deleted", (int)(iDeleted>>20) );
700 group.setAttribute("expirable", (int)(iExpirable>>20) );
701 total = group;
702 }
703 else
704 {
705 fsXML << group;
706 }
707 }
708
709 storage.appendChild(total);
710 int num_elements = fsXML.size();
711 for (int fs_index = 0; fs_index < num_elements; fs_index++)
712 {
713 storage.appendChild(fsXML[fs_index]);
714 }
715
716 // load average ---------------------
717
718#ifdef Q_OS_ANDROID
719 load.setAttribute("avg1", 0);
720 load.setAttribute("avg2", 1);
721 load.setAttribute("avg3", 2);
722#else
723 loadArray rgdAverages = getLoadAvgs();
724 if (rgdAverages[0] != -1)
725 {
726 load.setAttribute("avg1", rgdAverages[0]);
727 load.setAttribute("avg2", rgdAverages[1]);
728 load.setAttribute("avg3", rgdAverages[2]);
729 }
730#endif
731
732 // Guide Data ---------------------
733
734 QDateTime GuideDataThrough;
735
737 query.prepare("SELECT MAX(endtime) FROM program WHERE manualid = 0;");
738
739 if (query.exec() && query.next())
740 {
741 GuideDataThrough = MythDate::fromString(query.value(0).toString());
742 }
743
744 guide.setAttribute("start",
745 setting_to_localtime("mythfilldatabaseLastRunStart"));
746 guide.setAttribute("end",
747 setting_to_localtime("mythfilldatabaseLastRunEnd"));
748 guide.setAttribute("status",
749 gCoreContext->GetSetting("mythfilldatabaseLastRunStatus"));
750 if (gCoreContext->GetBoolSetting("MythFillGrabberSuggestsTime", false))
751 {
752 guide.setAttribute("next",
753 gCoreContext->GetSetting("MythFillSuggestedRunTime"));
754 }
755
756 if (!GuideDataThrough.isNull())
757 {
758 guide.setAttribute("guideThru",
759 GuideDataThrough.toString(Qt::ISODate));
760 guide.setAttribute("guideDays", qdtNow.daysTo(GuideDataThrough));
761 }
762
763 // Add Miscellaneous information
764
765 QString info_script = gCoreContext->GetSetting("MiscStatusScript");
766 if ((!info_script.isEmpty()) && (info_script != "none"))
767 {
768 QDomElement misc = pDoc->createElement("Miscellaneous");
769 root.appendChild(misc);
770
771 uint flags = kMSRunShell | kMSStdOut;
772 MythSystemLegacy ms(info_script, flags);
773 ms.Run(10s);
774 if (ms.Wait() != GENERIC_EXIT_OK)
775 {
776 LOG(VB_GENERAL, LOG_ERR,
777 QString("Error running miscellaneous "
778 "status information script: %1").arg(info_script));
779 return;
780 }
781
782 QByteArray input = ms.ReadAll();
783
784 QStringList output = QString(input).split('\n',
785 Qt::SkipEmptyParts);
786 for (const auto & line : std::as_const(output))
787 {
788 QDomElement info = pDoc->createElement("Information");
789
790 QStringList list = line.split("[]:[]");
791 unsigned int size = list.size();
792 unsigned int hasAttributes = 0;
793
794 if ((size > 0) && (!list[0].isEmpty()))
795 {
796 info.setAttribute("display", list[0]);
797 hasAttributes++;
798 }
799 if ((size > 1) && (!list[1].isEmpty()))
800 {
801 info.setAttribute("name", list[1]);
802 hasAttributes++;
803 }
804 if ((size > 2) && (!list[2].isEmpty()))
805 {
806 info.setAttribute("value", list[2]);
807 hasAttributes++;
808 }
809
810 if (hasAttributes > 0)
811 misc.appendChild(info);
812 }
813 }
814}
815
817//
819
820void V2Status::PrintStatus( QTextStream &os, QDomDocument *pDoc )
821{
822#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
823 os.setCodec("UTF-8");
824#else
825 os.setEncoding(QStringConverter::Utf8);
826#endif
827
828 QDateTime qdtNow = MythDate::current();
829
830 QDomElement docElem = pDoc->documentElement();
831
832 os << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
833 << "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"
834 << "<html xmlns=\"http://www.w3.org/1999/xhtml\""
835 << " xml:lang=\"en\" lang=\"en\">\r\n"
836 << "<head>\r\n"
837 << " <meta http-equiv=\"Content-Type\""
838 << "content=\"text/html; charset=UTF-8\" />\r\n"
839 << " <link rel=\"stylesheet\" href=\"/css/Status.css\" type=\"text/css\">\r\n"
840 << " <title>MythTV Status - "
841 << docElem.attribute( "date", MythDate::toString(qdtNow, MythDate::kDateShort) )
842 << " "
843 << docElem.attribute( "time", MythDate::toString(qdtNow, MythDate::kTime) ) << " - "
844 << docElem.attribute( "version", MYTH_BINARY_VERSION ) << "</title>\r\n"
845 << "</head>\r\n"
846 << "<body bgcolor=\"#fff\">\r\n"
847 << "<div class=\"status\">\r\n"
848 << " <h1 class=\"status\">MythTV Status</h1>\r\n";
849
850 // encoder information ---------------------
851
852 QDomNode node = docElem.namedItem( "Encoders" );
853
854 if (!node.isNull())
855 PrintEncoderStatus( os, node.toElement() );
856
857 // upcoming shows --------------------------
858
859 node = docElem.namedItem( "Scheduled" );
860
861 if (!node.isNull())
862 PrintScheduled( os, node.toElement());
863
864 // Frontends
865
866 node = docElem.namedItem( "Frontends" );
867
868 if (!node.isNull())
869 PrintFrontends (os, node.toElement());
870
871 // Backends
872
873 node = docElem.namedItem( "Backends" );
874
875 if (!node.isNull())
876 PrintBackends (os, node.toElement());
877
878 // Job Queue Entries -----------------------
879
880 node = docElem.namedItem( "JobQueue" );
881
882 if (!node.isNull())
883 PrintJobQueue( os, node.toElement());
884
885 // Machine information ---------------------
886
887 node = docElem.namedItem( "MachineInfo" );
888
889 if (!node.isNull())
890 PrintMachineInfo( os, node.toElement());
891
892 // Miscellaneous information ---------------
893
894 node = docElem.namedItem( "Miscellaneous" );
895
896 if (!node.isNull())
897 PrintMiscellaneousInfo( os, node.toElement());
898
899 os << "\r\n</div>\r\n</body>\r\n</html>\r\n";
900
901}
902
904//
906
907int V2Status::PrintEncoderStatus( QTextStream &os, const QDomElement& encoders )
908{
909 int nNumEncoders = 0;
910
911 if (encoders.isNull())
912 return 0;
913
914 os << " <div class=\"content\">\r\n"
915 << " <h2 class=\"status\">Encoder Status</h2>\r\n";
916
917 QDomNode node = encoders.firstChild();
918
919 while (!node.isNull())
920 {
921 QDomElement e = node.toElement();
922
923 if (!e.isNull())
924 {
925 if (e.tagName() == "Encoder")
926 {
927 QString sIsLocal = (e.attribute( "local" , "remote" )== "1")
928 ? "local" : "remote";
929 QString sCardId = e.attribute( "id" , "0" );
930 QString sHostName = e.attribute( "hostname" , "Unknown");
931 bool bConnected= static_cast<bool>(e.attribute( "connected", "0" ).toInt());
932
933 bool bIsLowOnFreeSpace=static_cast<bool>(e.attribute( "lowOnFreeSpace", "0").toInt());
934
935 QString sDevlabel = e.attribute( "devlabel", "[ UNKNOWN ]");
936
937 os << " Encoder " << sCardId << " " << sDevlabel
938 << " is " << sIsLocal << " on " << sHostName;
939
940 if ((sIsLocal == "remote") && !bConnected)
941 {
942 SleepStatus sleepStatus =
943 (SleepStatus) e.attribute("sleepstatus",
944 QString::number(sStatus_Undefined)).toInt();
945
946 if (sleepStatus == sStatus_Asleep)
947 os << " (currently asleep).<br />";
948 else
949 os << " (currently not connected).<br />";
950
951 node = node.nextSibling();
952 continue;
953 }
954
955 nNumEncoders++;
956
957 TVState encState = (TVState) e.attribute( "state", "0").toInt();
958
959 switch( encState )
960 {
962 os << " and is watching Live TV";
963 break;
964
967 os << " and is recording";
968 break;
969
970 default:
971 os << " and is not recording.";
972 break;
973 }
974
975 // Display first Program Element listed under the encoder
976
977 QDomNode tmpNode = e.namedItem( "Program" );
978
979 if (!tmpNode.isNull())
980 {
981 QDomElement program = tmpNode.toElement();
982
983 if (!program.isNull())
984 {
985 os << " '" << program.attribute( "title", "Unknown" ) << "'";
986
987 // Get Channel information
988
989 tmpNode = program.namedItem( "Channel" );
990
991 if (!tmpNode.isNull())
992 {
993 QDomElement channel = tmpNode.toElement();
994
995 if (!channel.isNull())
996 os << " on "
997 << channel.attribute( "callSign", "unknown" );
998 }
999
1000 // Get Recording Information (if any)
1001
1002 tmpNode = program.namedItem( "Recording" );
1003
1004 if (!tmpNode.isNull())
1005 {
1006 QDomElement recording = tmpNode.toElement();
1007
1008 if (!recording.isNull())
1009 {
1010 QDateTime endTs = MythDate::fromString(
1011 recording.attribute( "recEndTs", "" ));
1012
1013 os << ". This recording ";
1014 if (endTs < MythDate::current())
1015 os << "was ";
1016 else
1017 os << "is ";
1018
1019 os << "scheduled to end at "
1020 << MythDate::toString(endTs,
1022 }
1023 }
1024 }
1025
1026 os << ".";
1027 }
1028
1029 if (bIsLowOnFreeSpace)
1030 {
1031 os << " <strong>WARNING</strong>:"
1032 << " This backend is low on free disk space!";
1033 }
1034
1035 os << "<br />\r\n";
1036 }
1037 }
1038
1039 node = node.nextSibling();
1040 }
1041
1042 os << " </div>\r\n\r\n";
1043
1044 return( nNumEncoders );
1045}
1046
1048//
1050
1051int V2Status::PrintScheduled( QTextStream &os, const QDomElement& scheduled )
1052{
1053 QDateTime qdtNow = MythDate::current();
1054
1055 if (scheduled.isNull())
1056 return( 0 );
1057
1058 int nNumRecordings= scheduled.attribute( "count", "0" ).toInt();
1059
1060 os << " <div class=\"content\">\r\n"
1061 << " <h2 class=\"status\">Schedule</h2>\r\n";
1062
1063 if (nNumRecordings == 0)
1064 {
1065 os << " There are no shows scheduled for recording.\r\n"
1066 << " </div>\r\n";
1067 return( 0 );
1068 }
1069
1070 os << " The next " << nNumRecordings << " show" << (nNumRecordings == 1 ? "" : "s" )
1071 << " that " << (nNumRecordings == 1 ? "is" : "are")
1072 << " scheduled for recording:\r\n";
1073
1074 os << " <div class=\"schedule\">\r\n";
1075
1076 // Iterate through all scheduled programs
1077
1078 QDomNode node = scheduled.firstChild();
1079
1080 while (!node.isNull())
1081 {
1082 QDomElement e = node.toElement();
1083
1084 if (!e.isNull())
1085 {
1086 QDomNode recNode = e.namedItem( "Recording" );
1087 QDomNode chanNode = e.namedItem( "Channel" );
1088
1089 if ((e.tagName() == "Program") && !recNode.isNull() &&
1090 !chanNode.isNull())
1091 {
1092 QDomElement r = recNode.toElement();
1093 QDomElement c = chanNode.toElement();
1094
1095 QString sTitle = e.attribute( "title" , "" );
1096 QString sSubTitle = e.attribute( "subTitle", "" );
1097 QDateTime airDate = MythDate::fromString( e.attribute( "airdate" ,"" ));
1098 QDateTime startTs = MythDate::fromString( e.attribute( "startTime" ,"" ));
1099 QDateTime endTs = MythDate::fromString( e.attribute( "endTime" ,"" ));
1100 QDateTime recStartTs = MythDate::fromString( r.attribute( "recStartTs","" ));
1101// QDateTime recEndTs = MythDate::fromString( r.attribute( "recEndTs" ,"" ));
1102 int nPreRollSecs = r.attribute( "preRollSeconds", "0" ).toInt();
1103 int nEncoderId = r.attribute( "encoderId" , "0" ).toInt();
1104 QString sProfile = r.attribute( "recProfile" , "" );
1105 QString sChanName = c.attribute( "channelName" , "" );
1106 QString sDesc = "";
1107
1108 QDomText text = e.firstChild().toText();
1109 if (!text.isNull())
1110 sDesc = text.nodeValue();
1111
1112 // Build Time to recording start.
1113
1114 int nTotalSecs = qdtNow.secsTo( recStartTs ) - nPreRollSecs;
1115
1116 //since we're not displaying seconds
1117
1118 nTotalSecs -= 60;
1119
1120 int nTotalDays = nTotalSecs / 86400;
1121 int nTotalHours = (nTotalSecs / 3600)
1122 - (nTotalDays * 24);
1123 int nTotalMins = (nTotalSecs / 60) % 60;
1124
1125 QString sTimeToStart = "in";
1126
1127 sTimeToStart += QObject::tr(" %n day(s),", "", nTotalDays );
1128 sTimeToStart += QObject::tr(" %n hour(s) and", "", nTotalHours);
1129 sTimeToStart += QObject::tr(" %n minute(s)", "", nTotalMins);
1130
1131 if ( nTotalHours == 0 && nTotalMins == 0)
1132 sTimeToStart = QObject::tr("within one minute", "Recording starting");
1133
1134 if ( nTotalSecs < 0)
1135 sTimeToStart = QObject::tr("soon", "Recording starting");
1136
1137 // Output HTML
1138
1139 os << " <a href=\"#\">";
1140 os << MythDate::toString(recStartTs.addSecs(-nPreRollSecs),
1142 MythDate::kSimplify) << " "
1143 << MythDate::toString(recStartTs.addSecs(-nPreRollSecs),
1144 MythDate::kTime) << " - ";
1145
1146 if (nEncoderId > 0)
1147 os << "Encoder " << nEncoderId << " - ";
1148
1149 os << sChanName << " - " << sTitle << "<br />"
1150 << "<span><strong>" << sTitle << "</strong> ("
1151 << MythDate::toString(startTs, MythDate::kTime) << "-"
1152 << MythDate::toString(endTs, MythDate::kTime) << ")<br />";
1153
1154 if ( !sSubTitle.isEmpty())
1155 os << "<em>" << sSubTitle << "</em><br /><br />";
1156
1157 if ( airDate.isValid())
1158 {
1159 os << "Orig. Airdate: "
1162 << "<br /><br />";
1163 }
1164
1165 os << sDesc << "<br /><br />"
1166 << "This recording will start " << sTimeToStart
1167 << " using encoder " << nEncoderId << " with the '"
1168 << sProfile << "' profile.</span></a><hr />\r\n";
1169 }
1170 }
1171
1172 node = node.nextSibling();
1173 }
1174 os << " </div>\r\n";
1175 os << " </div>\r\n\r\n";
1176
1177 return( nNumRecordings );
1178}
1179
1181//
1183
1184int V2Status::PrintFrontends( QTextStream &os, const QDomElement& frontends )
1185{
1186 if (frontends.isNull())
1187 return( 0 );
1188
1189 int nNumFES= frontends.attribute( "count", "0" ).toInt();
1190
1191 if (nNumFES < 1)
1192 return( 0 );
1193
1194
1195 os << " <div class=\"content\">\r\n"
1196 << " <h2 class=\"status\">Frontends</h2>\r\n";
1197
1198 QDomNode node = frontends.firstChild();
1199 while (!node.isNull())
1200 {
1201 QDomElement e = node.toElement();
1202
1203 if (!e.isNull())
1204 {
1205 QString name = e.attribute( "name" , "" );
1206 QString url = e.attribute( "url" , "" );
1207 os << name << "&nbsp(<a href=\"" << url << "\">Status page</a>)<br />";
1208 }
1209
1210 node = node.nextSibling();
1211 }
1212
1213 os << " </div>\r\n\r\n";
1214
1215 return nNumFES;
1216}
1217
1219//
1221
1222int V2Status::PrintBackends( QTextStream &os, const QDomElement& backends )
1223{
1224 if (backends.isNull())
1225 return( 0 );
1226
1227 int nNumBES= backends.attribute( "count", "0" ).toInt();
1228
1229 if (nNumBES < 1)
1230 return( 0 );
1231
1232
1233 os << " <div class=\"content\">\r\n"
1234 << " <h2 class=\"status\">Other Backends</h2>\r\n";
1235
1236 QDomNode node = backends.firstChild();
1237 while (!node.isNull())
1238 {
1239 QDomElement e = node.toElement();
1240
1241 if (!e.isNull())
1242 {
1243 QString type = e.attribute( "type", "" );
1244 QString name = e.attribute( "name" , "" );
1245 QString url = e.attribute( "url" , "" );
1246 os << type << ": " << name << "&nbsp(<a href=\"" << url << "\">Status page</a>)<br />";
1247 }
1248
1249 node = node.nextSibling();
1250 }
1251
1252 os << " </div>\r\n\r\n";
1253
1254 return nNumBES;
1255}
1256
1258//
1260
1261int V2Status::PrintJobQueue( QTextStream &os, const QDomElement& jobs )
1262{
1263 if (jobs.isNull())
1264 return( 0 );
1265
1266 int nNumJobs= jobs.attribute( "count", "0" ).toInt();
1267
1268 os << " <div class=\"content\">\r\n"
1269 << " <h2 class=\"status\">Job Queue</h2>\r\n";
1270
1271 if (nNumJobs != 0)
1272 {
1273 QString statusColor;
1274 QString jobColor;
1275
1276 os << " Jobs currently in Queue or recently ended:\r\n<br />"
1277 << " <div class=\"schedule\">\r\n";
1278
1279
1280 QDomNode node = jobs.firstChild();
1281
1282 while (!node.isNull())
1283 {
1284 QDomElement e = node.toElement();
1285
1286 if (!e.isNull())
1287 {
1288 QDomNode progNode = e.namedItem( "Program" );
1289
1290 if ((e.tagName() == "Job") && !progNode.isNull() )
1291 {
1292 QDomElement p = progNode.toElement();
1293
1294 QDomNode recNode = p.namedItem( "Recording" );
1295 QDomNode chanNode = p.namedItem( "Channel" );
1296
1297 QDomElement r = recNode.toElement();
1298 QDomElement c = chanNode.toElement();
1299
1300 int nType = e.attribute( "type" , "0" ).toInt();
1301 int nStatus = e.attribute( "status", "0" ).toInt();
1302
1303 switch( nStatus )
1304 {
1305 case JOB_ABORTED:
1306 statusColor = " class=\"jobaborted\"";
1307 jobColor = "";
1308 break;
1309
1310 case JOB_ERRORED:
1311 statusColor = " class=\"joberrored\"";
1312 jobColor = " class=\"joberrored\"";
1313 break;
1314
1315 case JOB_FINISHED:
1316 statusColor = " class=\"jobfinished\"";
1317 jobColor = " class=\"jobfinished\"";
1318 break;
1319
1320 case JOB_RUNNING:
1321 statusColor = " class=\"jobrunning\"";
1322 jobColor = " class=\"jobrunning\"";
1323 break;
1324
1325 default:
1326 statusColor = " class=\"jobqueued\"";
1327 jobColor = " class=\"jobqueued\"";
1328 break;
1329 }
1330
1331 QString sTitle = p.attribute( "title" , "" ); //.replace("\"", "&quot;");
1332 QString sSubTitle = p.attribute( "subTitle", "" );
1333 QDateTime startTs = MythDate::fromString( p.attribute( "startTime" ,"" ));
1334 QDateTime endTs = MythDate::fromString( p.attribute( "endTime" ,"" ));
1335 QDateTime recStartTs = MythDate::fromString( r.attribute( "recStartTs","" ));
1336 QDateTime statusTime = MythDate::fromString( e.attribute( "statusTime","" ));
1337 QDateTime schedRunTime = MythDate::fromString( e.attribute( "schedTime","" ));
1338 QString sHostname = e.attribute( "hostname", "master" );
1339 QString sComment = "";
1340
1341 QDomText text = e.firstChild().toText();
1342 if (!text.isNull())
1343 sComment = text.nodeValue();
1344
1345 os << "<a href=\"javascript:void(0)\">"
1348 << " - "
1349 << sTitle << " - <font" << jobColor << ">"
1350 << JobQueue::JobText( nType ) << "</font><br />"
1351 << "<span><strong>" << sTitle << "</strong> ("
1352 << MythDate::toString(startTs, MythDate::kTime) << "-"
1353 << MythDate::toString(endTs, MythDate::kTime) << ")<br />";
1354
1355 if (!sSubTitle.isEmpty())
1356 os << "<em>" << sSubTitle << "</em><br /><br />";
1357
1358 os << "Job: " << JobQueue::JobText( nType ) << "<br />";
1359
1360 if (schedRunTime > MythDate::current())
1361 {
1362 os << "Scheduled Run Time: "
1363 << MythDate::toString(schedRunTime,
1366 << "<br />";
1367 }
1368
1369 os << "Status: <font" << statusColor << ">"
1370 << JobQueue::StatusText( nStatus )
1371 << "</font><br />"
1372 << "Status Time: "
1375 << "<br />";
1376
1377 if ( nStatus != JOB_QUEUED)
1378 os << "Host: " << sHostname << "<br />";
1379
1380 if (!sComment.isEmpty())
1381 os << "<br />Comments:<br />" << sComment << "<br />";
1382
1383 os << "</span></a><hr />\r\n";
1384 }
1385 }
1386
1387 node = node.nextSibling();
1388 }
1389 os << " </div>\r\n";
1390 }
1391 else
1392 {
1393 os << " Job Queue is currently empty.\r\n\r\n";
1394 }
1395
1396 os << " </div>\r\n\r\n ";
1397
1398 return( nNumJobs );
1399
1400}
1401
1403//
1405
1406int V2Status::PrintMachineInfo( QTextStream &os, const QDomElement& info )
1407{
1408 QString sRep;
1409
1410 if (info.isNull())
1411 return( 0 );
1412
1413 os << "<div class=\"content\">\r\n"
1414 << " <h2 class=\"status\">Machine Information</h2>\r\n";
1415
1416 // load average ---------------------
1417
1418 QDomNode node = info.namedItem( "Load" );
1419
1420 if (!node.isNull())
1421 {
1422 QDomElement e = node.toElement();
1423
1424 if (!e.isNull())
1425 {
1426 double dAvg1 = e.attribute( "avg1" , "0" ).toDouble();
1427 double dAvg2 = e.attribute( "avg2" , "0" ).toDouble();
1428 double dAvg3 = e.attribute( "avg3" , "0" ).toDouble();
1429
1430 os << " <div class=\"loadstatus\">\r\n"
1431 << " This machine's load average:"
1432 << "\r\n <ul>\r\n <li>"
1433 << "1 Minute: " << dAvg1 << "</li>\r\n"
1434 << " <li>5 Minutes: " << dAvg2 << "</li>\r\n"
1435 << " <li>15 Minutes: " << dAvg3
1436 << "</li>\r\n </ul>\r\n"
1437 << " </div>\r\n";
1438 }
1439 }
1440
1441 // local drive space ---------------------
1442 node = info.namedItem( "Storage" );
1443 QDomElement storage = node.toElement();
1444 node = storage.firstChild();
1445
1446 // Loop once until we find id == "total". This should be first, but a loop
1447 // separate from the per-filesystem details loop ensures total is first,
1448 // regardless.
1449 while (!node.isNull())
1450 {
1451 QDomElement g = node.toElement();
1452
1453 if (!g.isNull() && g.tagName() == "Group")
1454 {
1455 QString id = g.attribute("id", "" );
1456
1457 if (id == "total")
1458 {
1459 int nFree = g.attribute("free" , "0" ).toInt();
1460 int nTotal = g.attribute("total", "0" ).toInt();
1461 int nUsed = g.attribute("used" , "0" ).toInt();
1462 int nLiveTV = g.attribute("livetv" , "0" ).toInt();
1463 int nDeleted = g.attribute("deleted", "0" ).toInt();
1464 int nExpirable = g.attribute("expirable" , "0" ).toInt();
1465 QString nDir = g.attribute("dir" , "" );
1466
1467 nDir.replace(",", ", ");
1468
1469 os << " Disk Usage Summary:<br />\r\n";
1470 os << " <ul>\r\n";
1471
1472 os << " <li>Total Disk Space:\r\n"
1473 << " <ul>\r\n";
1474
1475 os << " <li>Total Space: ";
1476 sRep = QString("%L1").arg(nTotal) + " MB";
1477 os << sRep << "</li>\r\n";
1478
1479 os << " <li>Space Used: ";
1480 sRep = QString("%L1").arg(nUsed) + " MB";
1481 os << sRep << "</li>\r\n";
1482
1483 os << " <li>Space Free: ";
1484 sRep = QString("%L1").arg(nFree) + " MB";
1485 os << sRep << "</li>\r\n";
1486
1487 if ((nLiveTV + nDeleted + nExpirable) > 0)
1488 {
1489 os << " <li>Space Available "
1490 "After Auto-expire: ";
1491 sRep = QString("%L1").arg(nUsed) + " MB";
1492 sRep = QString("%L1").arg(nFree + nLiveTV +
1493 nDeleted + nExpirable) + " MB";
1494 os << sRep << "\r\n";
1495 os << " <ul>\r\n";
1496 os << " <li>Space Used by LiveTV: ";
1497 sRep = QString("%L1").arg(nLiveTV) + " MB";
1498 os << sRep << "</li>\r\n";
1499 os << " <li>Space Used by "
1500 "Deleted Recordings: ";
1501 sRep = QString("%L1").arg(nDeleted) + " MB";
1502 os << sRep << "</li>\r\n";
1503 os << " <li>Space Used by "
1504 "Auto-expirable Recordings: ";
1505 sRep = QString("%L1").arg(nExpirable) + " MB";
1506 os << sRep << "</li>\r\n";
1507 os << " </ul>\r\n";
1508 os << " </li>\r\n";
1509 }
1510
1511 os << " </ul>\r\n"
1512 << " </li>\r\n";
1513
1514 os << " </ul>\r\n";
1515 break;
1516 }
1517 }
1518
1519 node = node.nextSibling();
1520 }
1521
1522 // Loop again to handle per-filesystem details.
1523 node = storage.firstChild();
1524
1525 os << " Disk Usage Details:<br />\r\n";
1526 os << " <ul>\r\n";
1527
1528
1529 while (!node.isNull())
1530 {
1531 QDomElement g = node.toElement();
1532
1533 if (!g.isNull() && g.tagName() == "Group")
1534 {
1535 int nFree = g.attribute("free" , "0" ).toInt();
1536 int nTotal = g.attribute("total", "0" ).toInt();
1537 int nUsed = g.attribute("used" , "0" ).toInt();
1538 QString nDir = g.attribute("dir" , "" );
1539 QString id = g.attribute("id" , "" );
1540
1541 nDir.replace(",", ", ");
1542
1543
1544 if (id != "total")
1545 {
1546
1547 os << " <li>MythTV Drive #" << id << ":"
1548 << "\r\n"
1549 << " <ul>\r\n";
1550
1551 if (nDir.contains(','))
1552 os << " <li>Directories: ";
1553 else
1554 os << " <li>Directory: ";
1555
1556 os << nDir << "</li>\r\n";
1557
1558 os << " <li>Total Space: ";
1559 sRep = QString("%L1").arg(nTotal) + " MB";
1560 os << sRep << "</li>\r\n";
1561
1562 os << " <li>Space Used: ";
1563 sRep = QString("%L1").arg(nUsed) + " MB";
1564 os << sRep << "</li>\r\n";
1565
1566 os << " <li>Space Free: ";
1567 sRep = QString("%L1").arg(nFree) + " MB";
1568 os << sRep << "</li>\r\n";
1569
1570 os << " </ul>\r\n"
1571 << " </li>\r\n";
1572 }
1573
1574 }
1575
1576 node = node.nextSibling();
1577 }
1578
1579 os << " </ul>\r\n";
1580
1581 // Guide Info ---------------------
1582
1583 node = info.namedItem( "Guide" );
1584
1585 if (!node.isNull())
1586 {
1587 QDomElement e = node.toElement();
1588
1589 if (!e.isNull())
1590 {
1591 int nDays = e.attribute( "guideDays", "0" ).toInt();
1592 QString sStart = e.attribute( "start" , "" );
1593 QString sEnd = e.attribute( "end" , "" );
1594 QString sStatus = e.attribute( "status" , "" );
1595 QDateTime next = MythDate::fromString( e.attribute( "next" , "" ));
1596 QString sNext = next.isNull() ? "" :
1598 QString sMsg = "";
1599
1600 QDateTime thru = MythDate::fromString( e.attribute( "guideThru", "" ));
1601
1602 QDomText text = e.firstChild().toText();
1603
1604 QString mfdblrs =
1605 gCoreContext->GetSetting("mythfilldatabaseLastRunStart");
1606 QDateTime lastrunstart = MythDate::fromString(mfdblrs);
1607
1608 if (!text.isNull())
1609 sMsg = text.nodeValue();
1610
1611 os << " Last mythfilldatabase run started on " << sStart
1612 << " and ";
1613
1614 if (sEnd < sStart)
1615 os << "is ";
1616 else
1617 os << "ended on " << sEnd << ". ";
1618
1619 os << sStatus << "<br />\r\n";
1620
1621 if (!next.isNull() && next >= lastrunstart)
1622 {
1623 os << " Suggested next mythfilldatabase run: "
1624 << sNext << ".<br />\r\n";
1625 }
1626
1627 if (!thru.isNull())
1628 {
1629 os << " There's guide data until "
1631
1632 if (nDays > 0)
1633 os << " " << QObject::tr("(%n day(s))", "", nDays);
1634
1635 os << ".";
1636
1637 if (nDays <= 3)
1638 os << " <strong>WARNING</strong>: is mythfilldatabase running?";
1639 }
1640 else
1641 {
1642 os << " There's <strong>no guide data</strong> available! "
1643 << "Have you run mythfilldatabase?";
1644 }
1645 }
1646 }
1647 os << "\r\n </div>\r\n";
1648
1649 return( 1 );
1650}
1651
1652int V2Status::PrintMiscellaneousInfo( QTextStream &os, const QDomElement& info )
1653{
1654 if (info.isNull())
1655 return( 0 );
1656
1657 // Miscellaneous information
1658
1659 QDomNodeList nodes = info.elementsByTagName("Information");
1660 uint count = nodes.count();
1661 if (count > 0)
1662 {
1663 QString display;
1664 QString linebreak;
1665 //QString name, value;
1666 os << "<div class=\"content\">\r\n"
1667 << " <h2 class=\"status\">Miscellaneous</h2>\r\n";
1668 for (unsigned int i = 0; i < count; i++)
1669 {
1670 QDomNode node = nodes.item(i);
1671 if (node.isNull())
1672 continue;
1673
1674 QDomElement e = node.toElement();
1675 if (e.isNull())
1676 continue;
1677
1678 display = e.attribute("display", "");
1679 //name = e.attribute("name", "");
1680 //value = e.attribute("value", "");
1681
1682 if (display.isEmpty())
1683 continue;
1684
1685 // Only include HTML line break if display value doesn't already
1686 // contain breaks.
1687 if (display.contains("<p>", Qt::CaseInsensitive) ||
1688 display.contains("<br", Qt::CaseInsensitive))
1689 {
1690 // matches <BR> or <br /
1691 linebreak = "\r\n";
1692 }
1693 else
1694 {
1695 linebreak = "<br />\r\n";
1696 }
1697
1698 os << " " << display << linebreak;
1699 }
1700 os << "</div>\r\n";
1701 }
1702
1703 return( 1 );
1704}
1705
1706void V2Status::FillProgramInfo(QDomDocument *pDoc,
1707 QDomNode &node,
1708 ProgramInfo *pInfo,
1709 bool bIncChannel /* = true */,
1710 bool bDetails /* = true */)
1711{
1712 if ((pDoc == nullptr) || (pInfo == nullptr))
1713 return;
1714
1715 // Build Program Element
1716
1717 QDomElement program = pDoc->createElement( "Program" );
1718 node.appendChild( program );
1719
1720 program.setAttribute( "startTime" ,
1722 program.setAttribute( "endTime" , pInfo->GetScheduledEndTime(MythDate::ISODate));
1723 program.setAttribute( "title" , pInfo->GetTitle() );
1724 program.setAttribute( "subTitle" , pInfo->GetSubtitle());
1725 program.setAttribute( "category" , pInfo->GetCategory());
1726 program.setAttribute( "catType" , pInfo->GetCategoryTypeString());
1727 program.setAttribute( "repeat" , static_cast<int>(pInfo->IsRepeat()));
1728
1729 if (bDetails)
1730 {
1731
1732 program.setAttribute( "seriesId" , pInfo->GetSeriesID() );
1733 program.setAttribute( "programId" , pInfo->GetProgramID() );
1734 program.setAttribute( "stars" , pInfo->GetStars() );
1735 program.setAttribute( "fileSize" ,
1736 QString::number( pInfo->GetFilesize() ));
1737 program.setAttribute( "lastModified",
1739 program.setAttribute( "programFlags", pInfo->GetProgramFlags() );
1740 program.setAttribute( "hostname" , pInfo->GetHostname() );
1741
1742 if (pInfo->GetOriginalAirDate().isValid())
1743 program.setAttribute(
1744 "airdate", pInfo->GetOriginalAirDate().toString());
1745
1746 QDomText textNode = pDoc->createTextNode( pInfo->GetDescription() );
1747 program.appendChild( textNode );
1748
1749 }
1750
1751 if ( bIncChannel )
1752 {
1753 // Build Channel Child Element
1754
1755 QDomElement channel = pDoc->createElement( "Channel" );
1756 program.appendChild( channel );
1757
1758 FillChannelInfo( channel, pInfo, bDetails );
1759 }
1760
1761 // Build Recording Child Element
1762
1763 if ( pInfo->GetRecordingStatus() != RecStatus::Unknown )
1764 {
1765 QDomElement recording = pDoc->createElement( "Recording" );
1766 program.appendChild( recording );
1767
1768 recording.setAttribute( "recStatus" ,
1769 pInfo->GetRecordingStatus() );
1770 recording.setAttribute( "recPriority" ,
1771 pInfo->GetRecordingPriority() );
1772 recording.setAttribute( "recStartTs" ,
1774 recording.setAttribute( "recEndTs" ,
1776
1777 if (bDetails)
1778 {
1779 recording.setAttribute( "recordId" ,
1780 pInfo->GetRecordingRuleID() );
1781 recording.setAttribute( "recGroup" ,
1782 pInfo->GetRecordingGroup() );
1783 recording.setAttribute( "playGroup" ,
1784 pInfo->GetPlaybackGroup() );
1785 recording.setAttribute( "recType" ,
1786 pInfo->GetRecordingRuleType() );
1787 recording.setAttribute( "dupInType" ,
1788 pInfo->GetDuplicateCheckSource() );
1789 recording.setAttribute( "dupMethod" ,
1790 pInfo->GetDuplicateCheckMethod() );
1791 recording.setAttribute( "encoderId" ,
1792 pInfo->GetInputID() );
1793 const RecordingInfo ri(*pInfo);
1794 recording.setAttribute( "recProfile" ,
1796 //recording.setAttribute( "preRollSeconds", m_nPreRollSeconds );
1797 }
1798 }
1799}
1800
1802//
1804
1805void V2Status::FillChannelInfo( QDomElement &channel,
1806 ProgramInfo *pInfo,
1807 bool bDetails /* = true */ )
1808{
1809 if (pInfo)
1810 {
1811/*
1812 QString sHostName = gCoreContext->GetHostName();
1813 QString sPort = gCoreContext->GetSettingOnHost( "BackendStatusPort",
1814 sHostName);
1815 QString sIconURL = QString( "http://%1:%2/getChannelIcon?ChanId=%3" )
1816 .arg( sHostName )
1817 .arg( sPort )
1818 .arg( pInfo->chanid );
1819*/
1820
1821 channel.setAttribute( "chanId" , pInfo->GetChanID() );
1822 channel.setAttribute( "chanNum" , pInfo->GetChanNum());
1823 channel.setAttribute( "callSign" , pInfo->GetChannelSchedulingID());
1824 //channel.setAttribute( "iconURL" , sIconURL );
1825 channel.setAttribute( "channelName", pInfo->GetChannelName());
1826
1827 if (bDetails)
1828 {
1829 channel.setAttribute( "chanFilters",
1830 pInfo->GetChannelPlaybackFilters() );
1831 channel.setAttribute( "sourceId" , pInfo->GetSourceID() );
1832 channel.setAttribute( "inputId" , pInfo->GetInputID() );
1833 channel.setAttribute( "commFree" ,
1834 (pInfo->IsCommercialFree()) ? 1 : 0 );
1835 }
1836 }
1837}
1838
1839
1840
1841
1842// vim:set shiftwidth=4 tabstop=4 expandtab:
QMap< int, EncoderLink * > gTVList
static QString GetDeviceLabel(const QString &inputtype, const QString &videodevice)
Definition: cardutil.cpp:2644
static int GetJobsInQueue(QMap< int, JobQueueEntry > &jobs, int findJobs=JOB_LIST_NOT_DONE)
Definition: jobqueue.cpp:1289
static QString JobText(int jobType)
Definition: jobqueue.cpp:1119
static QString StatusText(int status)
Definition: jobqueue.cpp:1142
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
QVariant value(int i) const
Definition: mythdbcon.h:204
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
void BackendQueryDiskSpace(QStringList &strlist, bool consolidated, bool allHosts)
void GetActiveBackends(QStringList &hosts)
PlaybackSock * GetMediaServerByHostname(const QString &hostname)
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
QString GetHostName(void)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
bool IsMasterHost(void)
is this the same host as the master
int GetNumSetting(const QString &key, int defaultval=0)
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
QString GetMasterHostName(void)
bool IsMasterBackend(void)
is this the actual MBE process
bool GetBoolSetting(const QString &key, bool defaultval=false)
uint Wait(std::chrono::seconds timeout=0s)
void Run(std::chrono::seconds timeout=0s)
Runs a command inside the /bin/sh shell. Returns immediately.
QByteArray & ReadAll()
void setIP(const QString &lip)
Definition: playbacksock.h:60
QString getIP(void) const
Definition: playbacksock.h:61
Holds information on recordings and videos.
Definition: programinfo.h:74
float GetStars(void) const
Definition: programinfo.h:453
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:380
uint GetRecordingRuleID(void) const
Definition: programinfo.h:460
RecordingDupMethodType GetDuplicateCheckMethod(void) const
What should be compared to determine if two programs are the same?
Definition: programinfo.h:470
QString GetSeriesID(void) const
Definition: programinfo.h:446
QString GetCategoryTypeString(void) const
Returns catType as a string.
QString GetProgramID(void) const
Definition: programinfo.h:447
QString GetRecordingGroup(void) const
Definition: programinfo.h:427
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:405
QString GetChannelPlaybackFilters(void) const
Definition: programinfo.h:395
RecordingDupInType GetDuplicateCheckSource(void) const
Where should we check for duplicates?
Definition: programinfo.h:466
bool IsRepeat(void) const
Definition: programinfo.h:499
QString GetHostname(void) const
Definition: programinfo.h:429
uint GetSourceID(void) const
Definition: programinfo.h:473
QString GetPlaybackGroup(void) const
Definition: programinfo.h:428
QString GetDescription(void) const
Definition: programinfo.h:372
QDateTime GetLastModifiedTime(void) const
Definition: programinfo.h:440
QString GetChannelName(void) const
This is the channel name in the local market, i.e.
Definition: programinfo.h:394
QString GetTitle(void) const
Definition: programinfo.h:368
QDateTime GetRecordingStartTime(void) const
Approximate time the recording started.
Definition: programinfo.h:412
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:398
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:384
bool IsCommercialFree(void) const
Definition: programinfo.h:489
int GetRecordingPriority(void) const
Definition: programinfo.h:451
QDate GetOriginalAirDate(void) const
Definition: programinfo.h:439
uint GetInputID(void) const
Definition: programinfo.h:474
virtual uint64_t GetFilesize(void) const
uint32_t GetProgramFlags(void) const
Definition: programinfo.h:481
RecStatus::Type GetRecordingStatus(void) const
Definition: programinfo.h:458
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
Definition: programinfo.h:420
QString GetSubtitle(void) const
Definition: programinfo.h:370
QString GetCategory(void) const
Definition: programinfo.h:377
RecordingType GetRecordingRuleType(void) const
Definition: programinfo.h:462
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
Definition: programinfo.h:391
Holds information on a TV Program one might wish to record.
Definition: recordinginfo.h:36
QString GetProgramRecordingProfile(void) const
Returns recording profile name that will be, or was used, for this program, creating "record" field i...
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
void GetEntryMap(EntryMap &map)
Returns a copy of the EntryMap.
Definition: ssdpcache.cpp:87
static SSDPCache * Instance()
Definition: ssdpcache.cpp:285
SSDPCacheEntries * Find(const QString &sURI)
Finds the SSDPCacheEntries in the cache, returns nullptr when absent.
Definition: ssdpcache.cpp:341
MainServer * GetMainServer()
Definition: scheduler.h:104
bool GetAllPending(RecList &retList, int recRuleId=0) const
Definition: scheduler.cpp:1750
static QReadWriteLock s_inputsLock
Definition: tv_rec.h:433
static QList< QHostAddress > g_IPAddrList
Definition: upnp.h:108
QObject * Program
V2StorageGroup * AddNewStorageGroup()
Preformat * GetStatus()
Definition: v2status.cpp:101
static void PrintStatus(QTextStream &os, QDomDocument *pDoc)
Definition: v2status.cpp:820
Preformat * Status()
Definition: v2status.cpp:89
static void FillChannelInfo(QDomElement &channel, ProgramInfo *pInfo, bool bDetails=true)
Definition: v2status.cpp:1805
QMap< int, EncoderLink * > * m_pEncoders
Definition: v2status.h:61
static int PrintBackends(QTextStream &os, const QDomElement &backends)
Definition: v2status.cpp:1222
static void FillProgramInfo(QDomDocument *pDoc, QDomNode &node, ProgramInfo *pInfo, bool bIncChannel=true, bool bDetails=true)
Definition: v2status.cpp:1706
int m_nPreRollSeconds
Definition: v2status.h:64
static int PrintEncoderStatus(QTextStream &os, const QDomElement &encoders)
Definition: v2status.cpp:907
MainServer * m_pMainServer
Definition: v2status.h:62
static int PrintMiscellaneousInfo(QTextStream &os, const QDomElement &info)
Definition: v2status.cpp:1652
Preformat * GetStatusHTML()
Definition: v2status.cpp:117
static int PrintFrontends(QTextStream &os, const QDomElement &frontends)
Definition: v2status.cpp:1184
static int PrintScheduled(QTextStream &os, const QDomElement &scheduled)
Definition: v2status.cpp:1051
V2Status()
Definition: v2status.cpp:78
static int PrintMachineInfo(QTextStream &os, const QDomElement &info)
Definition: v2status.cpp:1406
Preformat * xml()
Definition: v2status.cpp:95
void FillStatusXML(QDomDocument *pDoc)
Definition: v2status.cpp:375
bool m_bIsMaster
Definition: v2status.h:63
Scheduler * m_pSched
Definition: v2status.h:60
void FillDriveSpace(V2MachineInfo *pMachineInfo)
Definition: v2status.cpp:307
static int PrintJobQueue(QTextStream &os, const QDomElement &jobs)
Definition: v2status.cpp:1261
static void RegisterCustomTypes()
V2BackendStatus * GetBackendStatus()
Definition: v2status.cpp:145
unsigned int uint
Definition: compat.h:60
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
@ JOB_LIST_RECENT
Definition: jobqueue.h:71
@ JOB_LIST_NOT_DONE
Definition: jobqueue.h:69
@ JOB_LIST_ERROR
Definition: jobqueue.h:70
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
loadArray getLoadAvgs(void)
Returns the system load averages.
std::array< double, 3 > loadArray
Definition: mythmiscutil.h:26
std::deque< RecordingInfo * > RecList
Definition: mythscheduler.h:12
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:41
@ kMSRunShell
run process through shell
Definition: mythsystem.h:43
const char * GetMythSourceVersion()
Definition: mythversion.cpp:7
const char * GetMythSourcePath()
Definition: mythversion.cpp:12
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateTimeFull
Default local time.
Definition: mythdate.h:23
@ kSimplify
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:26
@ kDateFull
Default local time.
Definition: mythdate.h:19
@ ISODate
Default UTC.
Definition: mythdate.h:17
@ kTime
Default local time.
Definition: mythdate.h:22
@ kAddYear
Add year to string if not included.
Definition: mythdate.h:25
@ kDateShort
Default local time.
Definition: mythdate.h:20
@ kDatabase
Default UTC, database format.
Definition: mythdate.h:27
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
dictionary info
Definition: azlyrics.py:7
string hostname
Definition: caa.py:17
QMap< QString, DeviceLocation * > EntryMap
Key == Unique Service Name (USN)
Definition: ssdpcache.h:34
#define output
SleepStatus
SleepStatus is an enumeration of the awake/sleep status of a slave.
Definition: tv.h:100
@ sStatus_Asleep
A slave is considered asleep when it is not awake and not undefined.
Definition: tv.h:107
@ sStatus_Undefined
A slave's sleep status is undefined when it has never connected to the master backend or is not able ...
Definition: tv.h:120
TVState
TVState is an enumeration of the states used by TV and TVRec.
Definition: tv.h:54
@ 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_WatchingRecording
Watching Recording is the state for when we are watching an in progress recording,...
Definition: tv.h:83
void FillEncoderList(QVariantList &list, QObject *parent)
void FillFrontendList(QVariantList &list, QObject *parent, bool OnLine)
void V2FillProgramInfo(V2Program *pProgram, ProgramInfo *pInfo, bool bIncChannel, bool bDetails, bool bIncCast, bool bIncArtwork, bool bIncRecording)
int FillUpcomingList(QVariantList &list, QObject *parent, int &nStartIndex, int &nCount, bool bShowAll, int nRecordId, int nRecStatus, const QString &Sort, const QString &RecGroup)
static QDateTime setting_to_qdatetime(const char *setting)
Definition: v2status.cpp:137
Q_GLOBAL_STATIC_WITH_ARGS(MythHTTPMetaService, s_service,(STATUS_HANDLE, V2Status::staticMetaObject, &V2Status::RegisterCustomTypes)) void V2Status
Definition: v2status.cpp:55
static QString setting_to_localtime(const char *setting)
Definition: v2status.cpp:130
#define STATUS_HANDLE
Definition: v2status.h:35