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