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