2 #include <QHostAddress>
3 #include <QNetworkInterface>
13 #include "libmythbase/mythversion.h"
63 strlist <<
"QUERY_IS_ACTIVE_BACKEND";
93 LOG(VB_GENERAL, LOG_ERR,
"StatusBox, theme is missing "
113 item->DisplayState(
"listings",
"icon");
117 item->DisplayState(
"schedule",
"icon");
121 item->DisplayState(
"tuner",
"icon");
125 item->DisplayState(
"jobqueue",
"icon");
129 item->DisplayState(
"decoders",
"icon");
133 item->DisplayState(
"display",
"icon");
137 item->DisplayState(
"render",
"icon");
141 item->DisplayState(
"machine",
"icon");
145 item->DisplayState(
"autoexpire",
"icon");
152 const QString &
help,
153 const QString & detail,
154 const QString & helpdetail,
155 const QString & state,
156 const QString & data)
161 if (detail.isEmpty())
171 if (helpdetail.isEmpty())
183 item->SetFontState(logline.
m_state);
184 item->DisplayState(logline.
m_state,
"status");
185 item->SetText(logline.
m_detail,
"detail");
198 for (
int i = 0; i < actions.size() && !handled; ++i)
200 QString
action = actions[i];
260 currentItem = currentButton->
GetText();
264 if (currentItem == tr(
"Job Queue"))
268 if (jobStatus == JOB_QUEUED)
270 QString message = tr(
"Delete Job?");
275 confirmPopup->SetReturnEvent(
this,
"JobDelete");
276 confirmPopup->SetData(logline.m_data);
278 if (confirmPopup->Create())
281 else if ((jobStatus == JOB_PENDING) ||
282 (jobStatus == JOB_STARTING) ||
283 (jobStatus == JOB_RUNNING) ||
284 (jobStatus == JOB_PAUSED))
286 QString label = tr(
"Job Queue Actions:");
291 if (menuPopup->Create())
294 menuPopup->SetReturnEvent(
this,
"JobModify");
296 QVariant data = QVariant::fromValue(logline.m_data);
298 if (jobStatus == JOB_PAUSED)
299 menuPopup->AddButtonV(tr(
"Resume"), data);
301 menuPopup->AddButtonV(tr(
"Pause"), data);
302 menuPopup->AddButtonV(tr(
"Stop"), data);
303 menuPopup->AddButtonV(tr(
"No Change"), data);
305 else if (jobStatus & JOB_DONE)
307 QString message = tr(
"Requeue Job?");
312 confirmPopup->SetReturnEvent(
this,
"JobRequeue");
313 confirmPopup->SetData(logline.m_data);
315 if (confirmPopup->Create())
319 else if (currentItem == tr(
"AutoExpire List"))
325 QString label = tr(
"AutoExpire Actions:");
330 if (menuPopup->Create())
333 menuPopup->SetReturnEvent(
this,
"AutoExpireManage");
335 menuPopup->AddButtonV(tr(
"Delete Now"), QVariant::fromValue(rec));
336 if ((rec)->GetRecordingGroup() ==
"LiveTV")
338 menuPopup->AddButtonV(tr(
"Move to Default group"),
339 QVariant::fromValue(rec));
341 else if ((rec)->GetRecordingGroup() ==
"Deleted")
343 menuPopup->AddButtonV(tr(
"Undelete"), QVariant::fromValue(rec));
347 menuPopup->AddButtonV(tr(
"Disable AutoExpire"),
348 QVariant::fromValue(rec));
350 menuPopup->AddButtonV(tr(
"No Change"), QVariant::fromValue(rec));
362 QString resultid = dce->GetId();
363 int buttonnum = dce->GetResult();
365 if (resultid ==
"JobDelete")
369 int jobID = dce->GetData().toInt();
375 else if (resultid ==
"JobRequeue")
379 int jobID = dce->GetData().toInt();
384 else if (resultid ==
"JobModify")
386 int jobID = dce->GetData().toInt();
394 else if (buttonnum == 1)
401 else if (resultid ==
"AutoExpireManage")
406 if (!rec || buttonnum == 2)
410 if ((buttonnum == 0) && rec->QueryIsDeleteCandidate())
415 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to delete recording: %1").arg(rec->GetTitle()));
420 else if (buttonnum == 1)
422 if ((rec)->GetRecordingGroup() ==
"Deleted")
430 if ((rec)->GetRecordingGroup() ==
"LiveTV")
458 QString helpmsg(tr(
"Listings Status shows the latest "
459 "status information from "
460 "mythfilldatabase"));
466 QDateTime mfdLastRunStart;
467 QDateTime mfdLastRunEnd;
468 QDateTime mfdNextRunStart;
469 QString mfdLastRunStatus;
471 QDateTime GuideDataThrough;
476 query.
prepare(
"SELECT max(endtime) FROM program WHERE manualid=0;");
490 AddLogLine(tr(
"Mythfrontend version: %1 (%2)")
494 AddLogLine(tr(
"Last mythfilldatabase guide update:"), helpmsg);
495 tmp = tr(
"Started: %1").arg(
500 if (mfdLastRunEnd >= mfdLastRunStart)
502 tmp = tr(
"Finished: %1")
509 AddLogLine(tr(
"Result: %1").arg(mfdLastRunStatus), helpmsg);
512 if (mfdNextRunStart >= mfdLastRunStart)
514 tmp = tr(
"Suggested Next: %1")
521 int DaysOfData = qdtNow.daysTo(GuideDataThrough);
523 if (GuideDataThrough.isNull())
525 AddLogLine(tr(
"There's no guide data available!"), helpmsg,
527 AddLogLine(tr(
"Have you run mythfilldatabase?"), helpmsg,
533 tr(
"There is guide data until %1")
538 AddLogLine(QString(
"(%1).").arg(tr(
"%n day(s)",
"", DaysOfData)),
543 AddLogLine(tr(
"WARNING: is mythfilldatabase running?"), helpmsg,
553 QString helpmsg(tr(
"Schedule Status shows current statistics "
554 "from the scheduler."));
562 query.
prepare(
"SELECT COUNT(*) FROM record WHERE type = :TEMPLATE");
566 QString rules = tr(
"%n template rule(s) (is) defined",
"",
567 query.
value(0).toInt());
576 query.
prepare(
"SELECT COUNT(*) FROM record "
577 "WHERE type <> :TEMPLATE AND search = :NOSEARCH");
582 QString rules = tr(
"%n standard rule(s) (is) defined",
"",
583 query.
value(0).toInt());
592 query.
prepare(
"SELECT COUNT(*) FROM record WHERE search > :NOSEARCH");
596 QString rules = tr(
"%n search rule(s) are defined",
"",
597 query.
value(0).toInt());
606 QMap<RecStatus::Type, int> statusMatch;
607 QMap<RecStatus::Type, QString> statusText;
608 QMap<int, int> sourceMatch;
609 QMap<int, QString> sourceText;
610 QMap<int, int> cardMatch;
611 QMap<int, QString> cardText;
612 QMap<int, int> cardParent;
613 QMap<int, bool> cardSchedGroup;
617 int lowerpriority = 0;
620 query.
prepare(
"SELECT MAX(sourceid) FROM videosource");
624 maxSource = query.
value(0).toInt();
627 query.
prepare(
"SELECT sourceid,name FROM videosource");
631 sourceText[query.
value(0).toInt()] = query.
value(1).toString();
634 query.
prepare(
"SELECT MAX(cardid) FROM capturecard");
638 maxCard = query.
value(0).toInt();
641 query.
prepare(
"SELECT cardid, inputname, displayname, parentid, "
648 int inputid = query.
value(0).toInt();
649 cardText[inputid] = query.
value(2).toString();
650 if (cardText[inputid].isEmpty())
651 cardText[inputid] = QString::number(query.
value(1).toInt());
652 cardParent[inputid] = query.
value(3).toInt();
653 cardSchedGroup[inputid] = query.
value(4).toBool();
660 tmpstr = tr(
"%n matching showing(s)",
"", schedList.
size());
663 for (
auto *s : schedList)
667 if (statusMatch[recstatus] < 1)
670 recstatus, s->GetRecordingRuleType());
673 ++statusMatch[recstatus];
681 ++sourceMatch[s->GetSourceID()];
682 int inputid = s->GetInputID();
685 if (cardParent[inputid] && cardSchedGroup[cardParent[inputid]])
686 inputid = cardParent[inputid];
687 ++cardMatch[inputid];
688 if (s->GetRecordingPriority2() < 0)
690 if (s->GetVideoProperties() & VID_HDTV)
696 #define ADD_STATUS_LOG_LINE(rtype, fstate) \
698 if (statusMatch[rtype] > 0) \
700 tmpstr = QString("%1 %2").arg(statusMatch[rtype]) \
701 .arg(statusText[rtype]); \
702 AddLogLine(tmpstr, helpmsg, tmpstr, tmpstr, fstate);\
718 if (lowerpriority > 0)
720 tmpstr = QString(
"%1 %2 %3").arg(QString::number(lowerpriority),
721 willrec, tr(
"with lower priority"));
722 AddLogLine(tmpstr, helpmsg, tmpstr, tmpstr,
"warning");
726 tmpstr = QString(
"%1 %2 %3").arg(QString::number(hdflag),
727 willrec, tr(
"marked as HDTV"));
730 for (
int i = 1; i <= maxSource; ++i)
732 if (sourceMatch[i] > 0)
734 tmpstr = QString(
"%1 %2 %3 %4 \"%5\"")
735 .arg(QString::number(sourceMatch[i]), willrec,
736 tr(
"from source"), QString::number(i), sourceText[i]);
740 for (
int i = 1; i <= maxCard; ++i)
742 if (cardMatch[i] > 0)
744 tmpstr = QString(
"%1 %2 %3 %4 \"%5\"")
745 .arg(QString::number(cardMatch[i]), willrec,
746 tr(
"on input"), QString::number(i), cardText[i]);
757 int m_unavailable {0};
762 QStringList m_recordings;
764 QMap<QString, struct info>
info;
765 QStringList inputnames;
771 QString helpmsg(tr(
"Input Status shows the current information "
772 "about the state of backend inputs"));
780 "SELECT cardid, displayname "
781 "FROM capturecard ORDER BY displayname, cardid");
791 int inputid = query.
value(0).toInt();
792 QString inputname = query.
value(1).toString();
793 if (!
info.contains(inputname))
794 inputnames.append(inputname);
796 QString cmd = QString(
"QUERY_REMOTEENCODER %1").arg(inputid);
797 QStringList strlist( cmd );
798 strlist <<
"GET_STATE";
801 int state = strlist[0].toInt();
806 strlist << QString(
"QUERY_REMOTEENCODER %1").arg(inputid);
807 strlist <<
"GET_SLEEPSTATUS";
810 int sleepState = strlist[0].toInt();
812 if (sleepState == -1)
813 info[inputname].m_errored += 1;
815 info[inputname].m_unavailable += 1;
817 info[inputname].m_sleeping += 1;
822 info[inputname].m_recording += 1;
826 info[inputname].m_livetv += 1;
830 info[inputname].m_available += 1;
837 strlist = QStringList( QString(
"QUERY_RECORDER %1").arg(inputid));
838 strlist <<
"GET_RECORDING";
843 QString titlesub = pginfo.
GetTitle();
846 info[inputname].m_recordings += titlesub;
851 for (
const QString& inputname : std::as_const(inputnames))
853 QStringList statuslist;
854 if (
info[inputname].m_errored)
855 statuslist << tr(
"%1 errored").arg(
info[inputname].m_errored);
856 if (
info[inputname].m_unavailable)
857 statuslist << tr(
"%1 unavailable").arg(
info[inputname].m_unavailable);
858 if (
info[inputname].m_sleeping)
859 statuslist << tr(
"%1 sleeping").arg(
info[inputname].m_sleeping);
860 if (
info[inputname].m_recording)
861 statuslist << tr(
"%1 recording").arg(
info[inputname].m_recording);
862 if (
info[inputname].m_livetv)
863 statuslist << tr(
"%1 live television").arg(
info[inputname].m_livetv);
864 if (
info[inputname].m_available)
865 statuslist << tr(
"%1 available").arg(
info[inputname].m_available);
868 if (
info[inputname].m_errored)
870 else if (
info[inputname].m_unavailable ||
info[inputname].m_sleeping)
871 fontstate =
"warning";
873 QString shortstatus = tr(
"Input %1: %2")
874 .arg(inputname, statuslist.join(tr(
", ")));
875 QString longstatus = shortstatus +
"\n" +
876 info[inputname].m_recordings.join(
"\n");
878 AddLogLine(shortstatus, helpmsg, longstatus, longstatus, fontstate);
888 QString helpmsg(tr(
"Log Entries shows any unread log entries "
889 "from the system if you have logging enabled"));
896 query.
prepare(
"SELECT logid, module, priority, logdate, host, "
898 "FROM mythlog WHERE acknowledged = 0 "
899 "AND priority <= :PRIORITY ORDER BY logdate DESC;");
908 line = QString(
"%1").arg(query.
value(5).toString());
910 detail = tr(
"On %1 from %2.%3\n%4\n")
914 query.
value(4).toString(),
915 query.
value(1).toString(),
916 query.
value(5).toString());
918 QString
tmp = query.
value(6).toString();
922 detail.append(tr(
"No further details"));
925 "", query.
value(0).toString());
928 if (query.
size() == 0)
930 AddLogLine(tr(
"No items found at priority level %1 or lower.")
932 AddLogLine(tr(
"Use 1-8 to change priority level."), helpmsg);
943 QString helpmsg(tr(
"Job Queue shows any jobs currently in "
944 "MythTV's Job Queue such as a commercial "
951 QMap<int, JobQueueEntry> jobs;
952 QMap<int, JobQueueEntry>::Iterator it;
963 for (it = jobs.begin(); it != jobs.end(); ++it)
965 ProgramInfo pginfo((*it).chanid, (*it).recstartts);
970 detail = QString(
"%1\n%2 %3 @ %4\n%5 %6 %7 %8")
982 if ((*it).status != JOB_QUEUED)
983 detail +=
" (" + (*it).hostname +
')';
987 detail +=
'\n' + tr(
"Scheduled Run Time:") +
' ' +
994 detail +=
'\n' + (*it).comment;
997 line = QString(
"%1 @ %2")
1004 if ((*it).status == JOB_ERRORED)
1006 else if ((*it).status == JOB_ABORTED)
1009 AddLogLine(line, helpmsg, detail, detail, font,
1010 QString(
"%1").arg((*it).id));
1015 AddLogLine(tr(
"Job Queue is currently empty."), helpmsg);
1026 QString ret = QObject::tr(
"Unknown");
1027 if (total > 0.0 && free > 0.0)
1029 double percent = (100.0*free)/total;
1030 ret = StatusBox::tr(
"%1 total, %2 used, %3 (or %4%) free.")
1034 .arg(percent, 0,
'f', (percent >= 10.0) ? 0 : 2);
1041 return usage_str_kb((
long long)(total*1024), (
long long)(used*1024),
1042 (
long long)(free*1024));
1046 long long used,
long long free,
1049 const QString tail = StatusBox::tr(
", using your %1 rate of %2 kb/s");
1056 for (
auto it = prof2bps.begin(); it != prof2bps.end(); ++it)
1059 tail.arg(it.key()).arg((
int)((
float)(*it) / 1024.0F));
1061 long long bytesPerMin = ((*it) >> 1) * 15LL;
1062 uint minLeft = ((free<<5)/bytesPerMin)<<5;
1063 minLeft = (minLeft/15)*15;
1064 uint hoursLeft = minLeft/60;
1065 QString hourstring = StatusBox::tr(
"%n hour(s)",
"", hoursLeft);
1066 QString minstring = StatusBox::tr(
"%n minute(s)",
"", minLeft%60);
1067 QString remainstring = StatusBox::tr(
"%1 remaining",
"time");
1068 if (minLeft%60 == 0)
1069 out<<remainstring.arg(hourstring) + pro;
1070 else if (minLeft > 60)
1072 out<<StatusBox::tr(
"%1 and %2 remaining",
"time")
1073 .arg(hourstring, minstring) + pro;
1077 out<<remainstring.arg(minstring) + pro;
1084 QString str =
" " + StatusBox::tr(
"Uptime") +
": ";
1087 return str + StatusBox::tr(
"unknown",
"unknown uptime");
1089 auto days = duration_cast<std::chrono::days>(uptime);
1090 auto secs = uptime % 24h;
1093 if (
days.count() > 0)
1095 astext = QString(
"%1, %2")
1096 .arg(StatusBox::tr(
"%n day(s)",
"",
days.count()),
1101 return str + astext;
1115 "SELECT sum(filesize) * 8 / "
1116 "sum(((unix_timestamp(endtime) - unix_timestamp(starttime)))) "
1118 "FROM recorded WHERE hostname in (%1) "
1119 "AND (unix_timestamp(endtime) - unix_timestamp(starttime)) > 300;";
1121 query.
prepare(querystr.arg(hostnames));
1123 if (query.
exec() && query.
next() &&
1124 query.
value(0).toDouble() > 0)
1126 QString rateStr = tr(
"average",
"average rate");
1131 (int)(query.
value(0).toDouble());
1135 "SELECT max(filesize * 8 / "
1136 "(unix_timestamp(endtime) - unix_timestamp(starttime))) "
1138 "FROM recorded WHERE hostname in (%1) "
1139 "AND (unix_timestamp(endtime) - unix_timestamp(starttime)) > 300;";
1141 query.
prepare(querystr.arg(hostnames));
1143 if (query.
exec() && query.
next() &&
1144 query.
value(0).toDouble() > 0)
1146 QString rateStr = tr(
"maximum",
"maximum rate");
1151 (int)(query.
value(0).toDouble());
1168 QString machineStr = tr(
"Machine Status shows some operating system "
1169 "statistics of this machine and the MythTV "
1179 line = tr(
"System:");
1181 line = tr(
"This machine:");
1188 Item->SetText(
" " + tr(
"System time") +
": " + QDateTime::currentDateTime().toString());
1190 UpdateTime(timebox);
1196 AddLogLine(
" " + tr(
"OS") + QString(
": %1 (%2)").arg(QSysInfo::prettyProductName(),
1197 QSysInfo::currentCpuArchitecture()));
1198 AddLogLine(
" " + tr(
"Qt version") + QString(
": %1").arg(qVersion()));
1200 QList allInterfaces = QNetworkInterface::allInterfaces();
1201 for (
const QNetworkInterface & iface : std::as_const(allInterfaces))
1203 QNetworkInterface::InterfaceFlags f = iface.flags();
1204 if (!(f & QNetworkInterface::IsUp))
1206 if (!(f & QNetworkInterface::IsRunning))
1208 if ((f & QNetworkInterface::IsLoopBack) != 0U)
1211 QNetworkInterface::InterfaceType
type = iface.type();
1212 QString name =
type == QNetworkInterface::Wifi ? tr(
"WiFi") : tr(
"Ethernet");
1213 AddLogLine(
" " + name + QString(
" (%1): ").arg(iface.humanReadableName()));
1214 AddLogLine(
" " + tr(
"MAC Address") +
": " + iface.hardwareAddress());
1215 QList addresses = iface.addressEntries();
1216 for (
const QNetworkAddressEntry & addr : std::as_const(addresses))
1218 if (addr.ip().protocol() == QAbstractSocket::IPv4Protocol ||
1219 addr.ip().protocol() == QAbstractSocket::IPv6Protocol)
1221 AddLogLine(
" " + tr(
"IP Address") +
": " + addr.ip().toString());
1228 std::chrono::seconds uptime = 0s;
1233 std::chrono::seconds time = 0s;
1239 uptimeitem->
Start(1min);
1243 #if !defined(_WIN32) && !defined(Q_OS_ANDROID)
1247 Item->SetText(QString(
" %1: %2 %3 %4").arg(tr(
"Load")).arg(loads[0], 1,
'f', 2)
1248 .arg(loads[1], 1,
'f', 2).arg(loads[2], 1,
'f', 2));
1251 UpdateLoad(loaditem);
1270 Item->SetText(QString(
" " + tr(
"RAM") +
": " +
usage_str_mb(totm, totm - freem, freem)));
1280 Item->SetText(QString(
" " + tr(
"Swap") +
": " +
usage_str_mb(tots, tots - frees, frees)));
1294 line = tr(
"MythTV server") +
':';
1307 std::chrono::seconds time = 0s;
1313 remoteuptime->
Start(1min);
1324 Item->SetText(QString(
" %1: %2 %3 %4").arg(tr(
"Load")).arg(loads[0], 1,
'f', 2)
1325 .arg(loads[1], 1,
'f', 2).arg(loads[2], 1,
'f', 2));
1328 UpdateRemoteLoad(remoteloaditem);
1330 remoteloaditem->
Start();
1343 Item->SetText(QString(
" " + tr(
"RAM") +
": " +
usage_str_mb(totm, totm - freem, freem)));
1353 Item->SetText(QString(
" " + tr(
"Swap") +
": " +
usage_str_mb(tots, tots - frees, frees)));
1357 UpdateRemoteMem(rmem);
1358 UpdateRemoteSwap(rswap);
1370 for (
int i = 0; i < fsInfos.size(); ++i)
1373 if ((fsInfos.size() == 2) && (i == 0) &&
1374 (fsInfos[i].getPath() !=
"TotalDiskSpace") &&
1375 (fsInfos[i+1].getPath() ==
"TotalDiskSpace"))
1378 hostnames = QString(
"\"%1\"").arg(fsInfos[i].getHostname());
1379 hostnames.replace(
' ',
"");
1380 hostnames.replace(
',',
"\",\"");
1386 fsInfos[i].getTotalSpace(), fsInfos[i].getUsedSpace(),
1387 fsInfos[i].getTotalSpace() - fsInfos[i].getUsedSpace(),
1390 if (fsInfos[i].getPath() ==
"TotalDiskSpace")
1392 line = tr(
"Total Disk Space:");
1397 line = tr(
"MythTV Drive #%1:").arg(fsInfos[i].getFSysID());
1400 QStringList tokens = fsInfos[i].getPath().split(
',');
1402 if (tokens.size() > 1)
1404 AddLogLine(QString(
" ") + tr(
"Directories:"), machineStr);
1407 while (curToken < tokens.size())
1409 tokens[curToken++], machineStr);
1413 AddLogLine(QString(
" " ) + tr(
"Directory:") +
' ' +
1414 fsInfos[i].getPath(), machineStr);
1418 for (
auto & diskinfo : list)
1420 line = QString(
" ") + diskinfo;
1432 QString displayhelp = tr(
"Available hardware decoders for video playback.");
1439 if (decoders.isEmpty())
1445 for (
const QString & decoder : std::as_const(decoders))
1455 auto displayhelp = tr(
"Display information.");
1462 for (
const auto & line : std::as_const(desc))
1471 auto displayhelp = tr(
"Render information.");
1486 uint64_t swapcount = 0;
1489 swapcount = gl->GetSwapCount();
1490 Item->SetText(tr(
"Current fps\t: %1").arg(swapcount));
1495 (void)opengl->GetSwapCount();
1501 if (opengl && (opengl->GetExtraFeatures() &
kGLNVMemory))
1503 auto GetGPUMem = []()
1507 return gl->GetGPUMemory();
1508 return std::tuple<int,int,int> { 0, 0, 0 };
1512 auto mem = GetGPUMem();
1513 int total = std::get<0>(mem);
1516 int avail = std::get<2>(mem);
1517 Item->SetText(tr(
"GPU memory used : %1MB").arg(total - avail));
1523 auto mem = GetGPUMem();
1524 int total = std::get<0>(mem);
1527 int avail = std::get<2>(mem);
1528 int percent =
static_cast<int>((avail /
static_cast<float>(total) * 100.0F));
1529 Item->SetText(tr(
"GPU memory free : %1MB (or %2%)").arg(avail).arg(percent));
1540 UpdateFree(freemem);
1547 auto desc = render->GetDescription();
1548 for (
const auto & line : std::as_const(desc))
1562 QString helpmsg(tr(
"The AutoExpire List shows all recordings "
1563 "which may be expired and the order of "
1564 "their expiration. Recordings at the top "
1565 "of the list will be expired first."));
1571 QString contentLine;
1574 long long totalSize(0);
1575 long long liveTVSize(0);
1577 long long deletedGroupSize(0);
1578 int deletedGroupCount(0);
1580 std::vector<ProgramInfo *>::iterator it;
1604 deletedGroupCount++;
1608 staticInfo = tr(
"%n recording(s) consuming %1 (is) allowed to expire\n",
"",
1612 staticInfo += tr(
"%n (is) LiveTV and consume(s) %1\n",
"", liveTVCount)
1615 if (deletedGroupCount)
1617 staticInfo += tr(
"%n (is) Deleted and consume(s) %1\n",
"",
1634 contentLine += pginfo->
GetTitle() +
1650 AddLogLine(contentLine, staticInfo, detailInfo,
1651 staticInfo + detailInfo);