File indexing completed on 2024-04-28 05:46:36

0001 /*
0002     SPDX-FileCopyrightText: 2010 Volker Lanz <vl@fidra.de>
0003     SPDX-FileCopyrightText: 2014-2020 Andrius Štikonas <andrius@stikonas.eu>
0004 
0005     SPDX-License-Identifier: GPL-3.0-or-later
0006 */
0007 
0008 #include "gui/smartdialog.h"
0009 #include "gui/smartdialogwidget.h"
0010 
0011 #include <core/device.h>
0012 #include <core/smartstatus.h>
0013 #include <core/smartattribute.h>
0014 
0015 #include <util/helpers.h>
0016 #include <util/htmlreport.h>
0017 
0018 #include <QDialogButtonBox>
0019 #include <QFileDialog>
0020 #include <QFontDatabase>
0021 #include <QPushButton>
0022 #include <QStyle>
0023 #include <QTemporaryFile>
0024 #include <QTextDocumentFragment>
0025 #include <QTextStream>
0026 #include <QTreeWidgetItem>
0027 #include <QtGlobal>
0028 
0029 #include <KConfigGroup>
0030 #include <KFormat>
0031 #include <KLocalizedString>
0032 #include <KIO/CopyJob>
0033 #include <KJobUiDelegate>
0034 #include <KMessageBox>
0035 #include <KSharedConfig>
0036 
0037 #include <sys/utsname.h>
0038 #include <unistd.h>
0039 
0040 #include <config.h>
0041 
0042 /** Creates a new SmartDialog
0043     @param parent pointer to the parent widget
0044     @param d the Device
0045 */
0046 SmartDialog::SmartDialog(QWidget* parent, Device& d) :
0047     QDialog(parent),
0048     m_Device(d),
0049     m_DialogWidget(new SmartDialogWidget(this))
0050 {
0051     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0052     setLayout(mainLayout);
0053     mainLayout->addWidget(&dialogWidget());
0054     setWindowTitle(xi18nc("@title:window", "SMART Properties: <filename>%1</filename>", device().deviceNode()));
0055 
0056     buttonBox = new QDialogButtonBox(this);
0057     buttonBox->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close);
0058     buttonBox->button(QDialogButtonBox::Save)->setText(xi18nc("@action:button", "Save SMART Report"));
0059     buttonBox->button(QDialogButtonBox::Save)->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
0060     mainLayout->addWidget(buttonBox);
0061 
0062     setupDialog();
0063     setupConnections();
0064 
0065     KConfigGroup kcg(KSharedConfig::openConfig(), QStringLiteral("smartDialog"));
0066     restoreGeometry(kcg.readEntry<QByteArray>("Geometry", QByteArray()));
0067 }
0068 
0069 /** Destroys a SmartDialog */
0070 SmartDialog::~SmartDialog()
0071 {
0072     KConfigGroup kcg(KSharedConfig::openConfig(), QStringLiteral("smartDialog"));
0073     kcg.writeEntry("Geometry", saveGeometry());
0074 }
0075 
0076 void SmartDialog::setupDialog()
0077 {
0078     if (device().smartStatus().isValid()) {
0079         if (device().smartStatus().status()) {
0080             dialogWidget().statusText().setText(xi18nc("@label SMART disk status", "good"));
0081             dialogWidget().statusIcon().setPixmap(QIcon::fromTheme(QStringLiteral("dialog-ok"))
0082                     .pixmap(QApplication::style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize)));
0083         } else {
0084             dialogWidget().statusText().setText(xi18nc("@label SMART disk status", "BAD"));
0085             dialogWidget().statusIcon().setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning"))
0086                     .pixmap(QApplication::style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize)));
0087         }
0088 
0089         dialogWidget().modelName().setText(device().smartStatus().modelName());
0090         dialogWidget().firmware().setText(device().smartStatus().firmware());
0091         dialogWidget().serialNumber().setText(device().smartStatus().serial());
0092 
0093         dialogWidget().temperature().setText(SmartStatus::tempToString(device().smartStatus().temp()));
0094         const QString badSectors = device().smartStatus().badSectors() > 0
0095                                    ? QLocale().toString(device().smartStatus().badSectors())
0096                                    : xi18nc("@label SMART number of bad sectors", "none");
0097         dialogWidget().badSectors().setText(badSectors);
0098         dialogWidget().poweredOn().setText(KFormat().formatDuration(device().smartStatus().poweredOn()));
0099         dialogWidget().powerCycles().setText(QLocale().toString(device().smartStatus().powerCycles()));
0100         dialogWidget().overallAssessment().setText(SmartStatus::overallAssessmentToString(device().smartStatus().overall()));
0101         dialogWidget().selfTests().setText(SmartStatus::selfTestStatusToString(device().smartStatus().selfTestStatus()));
0102 
0103         dialogWidget().treeSmartAttributes().clear();
0104 
0105         const QFont f = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
0106         const QString size = f.pixelSize() != -1 ? QStringLiteral("%1px").arg(f.pixelSize()) : QStringLiteral("%1pt").arg(f.pointSize());
0107 
0108         const QString st = QStringLiteral("<span style=\"font-family:%1;font-size:%2;\">").arg(f.family()).arg(size);
0109 
0110         for (const auto &a : device().smartStatus().attributes()) {
0111             QTreeWidgetItem* item = new QTreeWidgetItem(
0112                 QStringList()
0113                 << QLocale().toString(a.id())
0114                 << QStringLiteral("<b>%1</b><br/>%2").arg(a.name()).arg(st + a.desc() + QStringLiteral("</span>"))
0115                 << (a.failureType() == SmartAttribute::FailureType::PreFailure ? xi18nc("@item:intable", "Pre-Failure") : xi18nc("@item:intable", "Old-Age"))
0116                 << (a.updateType() == SmartAttribute::UpdateType::Online ? xi18nc("@item:intable", "Online") : xi18nc("@item:intable", "Offline"))
0117                 << QLocale().toString(a.worst())
0118                 << QLocale().toString(a.current())
0119                 << QLocale().toString(a.threshold())
0120                 << a.raw()
0121                 << a.assessmentToString()
0122                 << a.value()
0123             );
0124             item->setSizeHint(0, QSize(0, 64));
0125             item->setToolTip(1, QTextDocumentFragment::fromHtml(a.desc()).toPlainText());
0126             dialogWidget().treeSmartAttributes().addTopLevelItem(item);
0127         }
0128     } else
0129         dialogWidget().statusText().setText(xi18nc("@label", "(unknown)"));
0130 
0131     setMinimumSize(dialogWidget().size());
0132     resize(dialogWidget().size());
0133 }
0134 
0135 void SmartDialog::setupConnections()
0136 {
0137     connect(buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, this, &SmartDialog::saveSmartReport);
0138     connect(buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &SmartDialog::close);
0139 }
0140 
0141 QString SmartDialog::toHtml() const
0142 {
0143     QString rval;
0144     QTextStream s(&rval);
0145 
0146     if (device().smartStatus().status())
0147         s << HtmlReport::tableLine(i18n("SMART status:"), xi18nc("@label SMART disk status", "good"));
0148     else
0149         s << HtmlReport::tableLine(i18n("SMART status:"), xi18nc("@label SMART disk status", "BAD"));
0150 
0151     const QString badSectors = device().smartStatus().badSectors() > 0
0152                                ? QLocale().toString(device().smartStatus().badSectors())
0153                                : i18nc("@label SMART number of bad sectors", "none");
0154 
0155     s << HtmlReport::tableLine(i18n("Model:"), device().smartStatus().modelName())
0156       << HtmlReport::tableLine(i18n("Serial number:"), device().smartStatus().serial())
0157       << HtmlReport::tableLine(i18n("Firmware revision:"), device().smartStatus().firmware())
0158       << HtmlReport::tableLine(i18n("Temperature:"), SmartStatus::tempToString(device().smartStatus().temp()))
0159       << HtmlReport::tableLine(i18n("Bad sectors:"), badSectors)
0160       << HtmlReport::tableLine(i18n("Powered on for:"), KFormat().formatSpelloutDuration(device().smartStatus().poweredOn()))
0161       << HtmlReport::tableLine(i18n("Power cycles:"), QLocale().toString(device().smartStatus().powerCycles()))
0162       << HtmlReport::tableLine(i18n("Self tests:"), SmartStatus::selfTestStatusToString(device().smartStatus().selfTestStatus()))
0163       << HtmlReport::tableLine(i18n("Overall assessment:"), SmartStatus::overallAssessmentToString(device().smartStatus().overall()));
0164 
0165     s << "</table><br/>";
0166 
0167     if (device().smartStatus().isValid()) {
0168 
0169         const QFont f = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
0170         const QString size = f.pixelSize() != -1 ? QStringLiteral("%1px").arg(f.pixelSize()) : QStringLiteral("%1pt").arg(f.pointSize());
0171 
0172         const QString st = QStringLiteral("<span style=\"font-family:%1;font-size:%2;\">").arg(f.family()).arg(size);
0173 
0174         s << "<table>\n";
0175 
0176         for (const auto &a : device().smartStatus().attributes()) {
0177             s << "<tr>\n";
0178 
0179             s << "<td>" << QLocale().toString(a.id()) << "</td>\n"
0180               << "<td>" << QStringLiteral("<b>%1</b><br/>%2").arg(a.name()).arg(st + a.desc() + QStringLiteral("</span>")) << "</td>\n"
0181               << "<td>" << (a.failureType() == SmartAttribute::FailureType::PreFailure ? xi18nc("@item:intable", "Pre-Failure") : xi18nc("@item:intable", "Old-Age")) << "</td>\n"
0182               << "<td>" << (a.updateType() == SmartAttribute::UpdateType::Online ? xi18nc("@item:intable", "Online") : xi18nc("@item:intable", "Offline")) << "</td>\n"
0183               << "<td>" << QLocale().toString(a.worst()) << "</td>\n"
0184               << "<td>" << QLocale().toString(a.current()) << "</td>\n"
0185               << "<td>" << QLocale().toString(a.threshold()) << "</td>\n"
0186               << "<td>" << a.raw() << "</td>\n"
0187               << "<td>" << a.assessmentToString() << "</td>\n"
0188               << "<td>" << a.value() << "</td>\n";
0189 
0190             s << "</tr>\n";
0191         }
0192 
0193         s << "</table>\n";
0194     } else
0195         s << "(unknown)";
0196 
0197     s.flush();
0198 
0199     return rval;
0200 }
0201 
0202 void SmartDialog::saveSmartReport()
0203 {
0204     const QUrl url = QFileDialog::getSaveFileUrl(this, QString(), QUrl(), tr("HTML-Files (*.htm *.html);;All Files (*)"));
0205 
0206     if (url.isEmpty())
0207         return;
0208 
0209     QTemporaryFile tempFile;
0210 
0211     if (tempFile.open()) {
0212         QTextStream s(&tempFile);
0213 
0214         HtmlReport html;
0215 
0216         s << html.header()
0217           << toHtml()
0218           << html.footer();
0219 
0220         tempFile.close();
0221 
0222         KIO::CopyJob* job = KIO::move(QUrl::fromLocalFile(tempFile.fileName()), url, KIO::HideProgressInfo);
0223         job->exec();
0224         if (job->error())
0225             job->uiDelegate()->showErrorMessage();
0226     } else
0227         KMessageBox::error(this, xi18nc("@info", "Could not create temporary file when trying to save to <filename>%1</filename>.", url.fileName()), xi18nc("@title:window", "Could Not Save SMART Report."));
0228 
0229 }