File indexing completed on 2024-04-28 17:06:14

0001 /*
0002     SPDX-FileCopyrightText: 2000 Shie Erlich <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "kmountmangui.h"
0010 #include "../Dialogs/krspecialwidgets.h"
0011 #include "../FileSystem/filesystem.h"
0012 #include "../compat.h"
0013 #include "../defaults.h"
0014 #include "../icon.h"
0015 #include "../krglobal.h"
0016 
0017 // QtCore
0018 #include <QCryptographicHash>
0019 #include <QFileInfo>
0020 #include <QList>
0021 // QtGui
0022 #include <QBitmap>
0023 #include <QCursor>
0024 #include <QPixmap>
0025 // QtWidgets
0026 #include <QCheckBox>
0027 #include <QDialogButtonBox>
0028 #include <QGridLayout>
0029 #include <QGroupBox>
0030 #include <QHeaderView>
0031 #include <QMenu>
0032 #include <QPushButton>
0033 
0034 #include <KCodecs/KCodecs>
0035 #include <KConfigCore/KSharedConfig>
0036 #include <KI18n/KLocalizedString>
0037 #include <KIOCore/KDiskFreeSpaceInfo>
0038 #include <KIOCore/KMountPoint>
0039 #include <KWidgetsAddons/KGuiItem>
0040 #include <KWidgetsAddons/KMessageBox>
0041 
0042 #include <Solid/StorageVolume>
0043 
0044 #ifndef BSD
0045 #define MTAB "/etc/mtab"
0046 #endif
0047 
0048 KMountManGUI::KMountManGUI(KMountMan *mntMan)
0049     : QDialog(mntMan->parentWindow)
0050     , mountMan(mntMan)
0051     , info(nullptr)
0052     , mountList(nullptr)
0053     , cbShowOnlyRemovable(nullptr)
0054     , watcher(nullptr)
0055     , sizeX(-1)
0056     , sizeY(-1)
0057 {
0058     setWindowTitle(i18n("MountMan - Your Mount-Manager"));
0059     setWindowModality(Qt::WindowModal);
0060 
0061     auto *mainLayout = new QVBoxLayout;
0062     setLayout(mainLayout);
0063 
0064     watcher = new QTimer(this);
0065     connect(watcher, &QTimer::timeout, this, &KMountManGUI::checkMountChange);
0066 
0067     mainLayout->addLayout(createMainPage());
0068 
0069     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0070     mainLayout->addWidget(buttonBox);
0071 
0072     ejectButton = new QPushButton(i18n("&Eject"));
0073     ejectButton->setIcon(Icon(QStringLiteral("media-eject")));
0074     ejectButton->setEnabled(false);
0075     buttonBox->addButton(ejectButton, QDialogButtonBox::ActionRole);
0076 
0077     mountButton = new QPushButton(i18n("&Unmount"));
0078     mountButton->setEnabled(false);
0079     buttonBox->addButton(mountButton, QDialogButtonBox::ActionRole);
0080 
0081     // connections
0082     connect(buttonBox, &QDialogButtonBox::rejected, this, &KMountManGUI::reject);
0083     connect(ejectButton, &QPushButton::clicked, this, &KMountManGUI::slotEject);
0084     connect(mountButton, &QPushButton::clicked, this, &KMountManGUI::slotToggleMount);
0085     connect(mountList, &KrTreeWidget::itemDoubleClicked, this, &KMountManGUI::doubleClicked);
0086     connect(mountList, &KrTreeWidget::itemRightClicked, this, &KMountManGUI::clicked);
0087     connect(mountList, &KrTreeWidget::itemClicked, this, QOverload<QTreeWidgetItem *>::of(&KMountManGUI::changeActive));
0088     connect(mountList, &KrTreeWidget::itemSelectionChanged, this, QOverload<>::of(&KMountManGUI::changeActive));
0089 
0090     KConfigGroup group(krConfig, "MountMan");
0091     int sx = group.readEntry("Window Width", -1);
0092     int sy = group.readEntry("Window Height", -1);
0093 
0094     if (sx != -1 && sy != -1)
0095         resize(sx, sy);
0096     else
0097         resize(600, 300);
0098 
0099     if (group.readEntry("Window Maximized", false))
0100         showMaximized();
0101     else
0102         show();
0103 
0104     getSpaceData();
0105 
0106     exec();
0107 }
0108 
0109 KMountManGUI::~KMountManGUI()
0110 {
0111     watcher->stop();
0112     delete watcher;
0113 
0114     KConfigGroup group(krConfig, "MountMan");
0115 
0116     group.writeEntry("Window Width", sizeX);
0117     group.writeEntry("Window Height", sizeY);
0118     group.writeEntry("Window Maximized", isMaximized());
0119     group.writeEntry("Last State", mountList->header()->saveState());
0120     group.writeEntry("ShowOnlyRemovable", cbShowOnlyRemovable->isChecked());
0121 }
0122 
0123 void KMountManGUI::resizeEvent(QResizeEvent *e)
0124 {
0125     if (!isMaximized()) {
0126         sizeX = e->size().width();
0127         sizeY = e->size().height();
0128     }
0129 
0130     QDialog::resizeEvent(e);
0131 }
0132 
0133 QLayout *KMountManGUI::createMainPage()
0134 {
0135     auto *layout = new QGridLayout();
0136     layout->setSpacing(10);
0137     mountList = new KrTreeWidget(this); // create the main container
0138     KConfigGroup grp(krConfig, "Look&Feel");
0139     mountList->setFont(grp.readEntry("Filelist Font", _FilelistFont));
0140     mountList->setSelectionMode(QAbstractItemView::SingleSelection);
0141     mountList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0142     mountList->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0143 
0144     QStringList labels;
0145     labels << i18n("Name");
0146     labels << i18n("Type");
0147     labels << i18n("Mnt.Point");
0148     labels << i18n("Total Size");
0149     labels << i18n("Free Size");
0150     labels << i18n("Free %");
0151     mountList->setHeaderLabels(labels);
0152 
0153     mountList->header()->setSectionResizeMode(QHeaderView::Interactive);
0154 
0155     grp = KConfigGroup(krConfig, "MountMan");
0156     if (grp.hasKey("Last State"))
0157         mountList->header()->restoreState(grp.readEntry("Last State", QByteArray()));
0158     else {
0159         int i = QFontMetrics(mountList->font()).horizontalAdvance("W");
0160         int j = QFontMetrics(mountList->font()).horizontalAdvance("0");
0161         j = (i > j ? i : j);
0162 
0163         mountList->setColumnWidth(0, j * 8);
0164         mountList->setColumnWidth(1, j * 4);
0165         mountList->setColumnWidth(2, j * 8);
0166         mountList->setColumnWidth(3, j * 6);
0167         mountList->setColumnWidth(4, j * 6);
0168         mountList->setColumnWidth(5, j * 5);
0169     }
0170 
0171     mountList->setAllColumnsShowFocus(true);
0172     mountList->header()->setSortIndicatorShown(true);
0173 
0174     mountList->sortItems(0, Qt::AscendingOrder);
0175 
0176     // now the list is created, time to fill it with data.
0177     //=>mountMan->forceUpdate();
0178 
0179     QGroupBox *box = new QGroupBox(i18n("MountMan.Info"), this);
0180     box->setAlignment(Qt::AlignHCenter);
0181     auto *vboxl = new QVBoxLayout(box);
0182     info = new KrFSDisplay(box);
0183     vboxl->addWidget(info);
0184     info->resize(info->width(), height());
0185 
0186     cbShowOnlyRemovable = new QCheckBox(i18n("Show only removable devices"), this);
0187     cbShowOnlyRemovable->setChecked(grp.readEntry("ShowOnlyRemovable", false));
0188     connect(cbShowOnlyRemovable, &QCheckBox::stateChanged, this, &KMountManGUI::updateList);
0189 
0190     layout->addWidget(box, 0, 0);
0191     layout->addWidget(cbShowOnlyRemovable, 1, 0);
0192     layout->addWidget(mountList, 0, 1, 2, 1);
0193 
0194     return layout;
0195 }
0196 
0197 void KMountManGUI::getSpaceData()
0198 {
0199     fileSystems.clear();
0200     KrMountDetector::getInstance()->hasMountsChanged();
0201 
0202     mounted = KMountPoint::currentMountPoints();
0203     possible = KMountPoint::possibleMountPoints();
0204     if (mounted.size() == 0) { // nothing is mounted
0205         addNonMounted();
0206         updateList(); // let's continue
0207         return;
0208     }
0209 
0210     for (auto &it : mounted) {
0211         // don't bother with invalid file systems
0212         if (mountMan->invalidFilesystem(it->mountType())) {
0213             continue;
0214         }
0215         KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(it->mountPoint());
0216         if (!info.isValid()) {
0217             continue;
0218         }
0219         fsData data;
0220         data.setMntPoint(it->mountPoint());
0221         data.setMounted(true);
0222         data.setTotalBlks(info.size() / 1024);
0223         data.setFreeBlks(info.available() / 1024);
0224         data.setName(it->mountedFrom());
0225         data.setType(it->mountType());
0226         fileSystems.append(data);
0227     }
0228     addNonMounted();
0229     updateList();
0230 }
0231 
0232 void KMountManGUI::addNonMounted()
0233 {
0234     // handle the non-mounted ones
0235     for (auto &it : possible) {
0236         // make sure we don't add things we've already added
0237         if (KMountMan::findInListByMntPoint(mounted, it->mountPoint())) {
0238             continue;
0239         } else {
0240             fsData data;
0241             data.setMntPoint(it->mountPoint());
0242             data.setMounted(false);
0243             data.setType(it->mountType());
0244             data.setName(it->mountedFrom());
0245 
0246             if (mountMan->invalidFilesystem(data.type()))
0247                 continue;
0248 
0249             fileSystems.append(data);
0250         }
0251     }
0252 }
0253 
0254 void KMountManGUI::addItemToMountList(KrTreeWidget *lst, fsData &fs)
0255 {
0256     Solid::Device device(mountMan->findUdiForPath(fs.mntPoint(), Solid::DeviceInterface::StorageAccess));
0257 
0258     if (cbShowOnlyRemovable->isChecked() && !mountMan->removable(device))
0259         return;
0260 
0261     bool mtd = fs.mounted();
0262 
0263     QString tSize = QString("%1").arg(KIO::convertSizeFromKiB(fs.totalBlks()));
0264     QString fSize = QString("%1").arg(KIO::convertSizeFromKiB(fs.freeBlks()));
0265     QString sPrct = QString("%1%").arg(100 - (fs.usedPerct()));
0266     auto *item = new QTreeWidgetItem(lst);
0267     item->setText(0, fs.name());
0268     item->setText(1, fs.type());
0269     item->setText(2, fs.mntPoint());
0270     item->setText(3, (mtd ? tSize : QString("N/A")));
0271     item->setText(4, (mtd ? fSize : QString("N/A")));
0272     item->setText(5, (mtd ? sPrct : QString("N/A")));
0273 
0274     auto *vol = device.as<Solid::StorageVolume>();
0275     QString iconName;
0276 
0277     if (device.isValid())
0278         iconName = device.icon();
0279     else if (mountMan->networkFilesystem(fs.type()))
0280         iconName = "folder-remote";
0281     QStringList overlays;
0282     if (mtd) {
0283         overlays << "emblem-mounted";
0284     } else {
0285         overlays << QString(); // We have to guarantee the placement of the next emblem
0286     }
0287     if (vol && vol->usage() == Solid::StorageVolume::Encrypted) {
0288         overlays << "security-high";
0289     }
0290     item->setIcon(0, Icon(iconName, overlays));
0291 }
0292 
0293 void KMountManGUI::updateList()
0294 {
0295     QString currentMP;
0296     int currentIdx = 0;
0297     QTreeWidgetItem *currentItem = mountList->currentItem();
0298     if (currentItem) {
0299         currentMP = getMntPoint(currentItem);
0300         currentIdx = mountList->indexOfTopLevelItem(currentItem);
0301     }
0302 
0303     mountList->clearSelection();
0304     mountList->clear();
0305 
0306     for (auto &fileSystem : fileSystems)
0307         addItemToMountList(mountList, fileSystem);
0308 
0309     currentItem = mountList->topLevelItem(currentIdx);
0310     for (int i = 0; i < mountList->topLevelItemCount(); i++) {
0311         QTreeWidgetItem *item = mountList->topLevelItem(i);
0312         if (getMntPoint(item) == currentMP)
0313             currentItem = item;
0314     }
0315     if (!currentItem)
0316         currentItem = mountList->topLevelItem(0);
0317     mountList->setCurrentItem(currentItem);
0318     changeActive(currentItem);
0319 
0320     mountList->setFocus();
0321 
0322     watcher->setSingleShot(true);
0323     watcher->start(WATCHER_DELAY); // starting the watch timer ( single shot )
0324 }
0325 
0326 void KMountManGUI::checkMountChange()
0327 {
0328     if (KrMountDetector::getInstance()->hasMountsChanged())
0329         getSpaceData();
0330     watcher->setSingleShot(true);
0331     watcher->start(WATCHER_DELAY); // starting the watch timer ( single shot )
0332 }
0333 
0334 void KMountManGUI::doubleClicked(QTreeWidgetItem *i)
0335 {
0336     if (!i)
0337         return; // we don't want to refresh to swap, do we ?
0338 
0339     // change the active panel to this mountpoint
0340     mountMan->emitRefreshPanel(QUrl::fromLocalFile(getMntPoint(i)));
0341     close();
0342 }
0343 
0344 void KMountManGUI::changeActive()
0345 {
0346     QList<QTreeWidgetItem *> seld = mountList->selectedItems();
0347     if (seld.count() > 0)
0348         changeActive(seld[0]);
0349 }
0350 
0351 // when user clicks on a filesystem, change information
0352 void KMountManGUI::changeActive(QTreeWidgetItem *i)
0353 {
0354     if (!i) {
0355         if (info) {
0356             info->setEmpty(true);
0357             info->update();
0358         }
0359         mountButton->setEnabled(false);
0360         ejectButton->setEnabled(false);
0361         return;
0362     }
0363 
0364     fsData *system = getFsData(i);
0365 
0366     info->setAlias(system->mntPoint());
0367     info->setRealName(system->name());
0368     info->setMounted(system->mounted());
0369     info->setEmpty(false);
0370     info->setTotalSpace(system->totalBlks());
0371     info->setFreeSpace(system->freeBlks());
0372     info->repaint();
0373 
0374     if (system->mounted())
0375         mountButton->setText(i18n("&Unmount"));
0376     else
0377         mountButton->setText(i18n("&Mount"));
0378 
0379     ejectButton->setEnabled(mountMan->ejectable(system->mntPoint()));
0380     mountButton->setEnabled(true);
0381 }
0382 
0383 // called when right-clicked on a filesystem
0384 void KMountManGUI::clicked(QTreeWidgetItem *item, const QPoint &pos)
0385 {
0386     // these are the values that will exist in the menu
0387 #define MOUNT_ID 90
0388 #define UNMOUNT_ID 91
0389 #define FORMAT_ID 93
0390 #define EJECT_ID 94
0391     //////////////////////////////////////////////////////////
0392     if (!item)
0393         return;
0394 
0395     fsData *system = getFsData(item);
0396     // create the menu
0397     QMenu popup;
0398     popup.setTitle(i18n("MountMan"));
0399     if (!system->mounted()) {
0400         QAction *mountAct = popup.addAction(i18n("Mount"));
0401         mountAct->setData(QVariant(MOUNT_ID));
0402         bool enable = !(mountMan->nonmountFilesystem(system->type(), system->mntPoint()));
0403         mountAct->setEnabled(enable);
0404     } else {
0405         QAction *umountAct = popup.addAction(i18n("Unmount"));
0406         umountAct->setData(QVariant(UNMOUNT_ID));
0407         bool enable = !(mountMan->nonmountFilesystem(system->type(), system->mntPoint()));
0408         umountAct->setEnabled(enable);
0409     }
0410     if (mountMan->ejectable(system->mntPoint()))
0411         //  if (system->type()=="iso9660" || mountMan->followLink(system->name()).left(2)=="cd")
0412         popup.addAction(i18n("Eject"))->setData(QVariant(EJECT_ID));
0413     else {
0414         QAction *formatAct = popup.addAction(i18n("Format"));
0415         formatAct->setData(QVariant(FORMAT_ID));
0416         formatAct->setEnabled(false);
0417     }
0418 
0419     QString mountPoint = system->mntPoint();
0420 
0421     QAction *res = popup.exec(pos);
0422     int result = -1;
0423 
0424     if (res && res->data().canConvert<int>())
0425         result = res->data().toInt();
0426 
0427     // check out the user's option
0428     switch (result) {
0429     case -1:
0430         return; // the user clicked outside of the menu
0431     case MOUNT_ID:
0432     case UNMOUNT_ID:
0433         mountMan->toggleMount(mountPoint);
0434         break;
0435     case FORMAT_ID:
0436         break;
0437     case EJECT_ID:
0438         mountMan->eject(mountPoint);
0439         break;
0440     }
0441 }
0442 
0443 void KMountManGUI::slotToggleMount()
0444 {
0445     QTreeWidgetItem *item = mountList->currentItem();
0446     if (item) {
0447         mountMan->toggleMount(getFsData(item)->mntPoint());
0448     }
0449 }
0450 
0451 void KMountManGUI::slotEject()
0452 {
0453     QTreeWidgetItem *item = mountList->currentItem();
0454     if (item) {
0455         mountMan->eject(getFsData(item)->mntPoint());
0456     }
0457 }
0458 
0459 fsData *KMountManGUI::getFsData(QTreeWidgetItem *item)
0460 {
0461     for (auto &fileSystem : fileSystems) {
0462         // the only thing which is unique is the mount point
0463         if (fileSystem.mntPoint() == getMntPoint(item)) {
0464             return &fileSystem;
0465         }
0466     }
0467     // this point shouldn't be reached
0468     abort();
0469     return nullptr;
0470 }
0471 
0472 QString KMountManGUI::getMntPoint(QTreeWidgetItem *item)
0473 {
0474     return item->text(2); // text(2) ? ugly ugly ugly
0475 }
0476 
0477 KrMountDetector::KrMountDetector()
0478 {
0479     hasMountsChanged();
0480 }
0481 
0482 bool KrMountDetector::hasMountsChanged()
0483 {
0484     bool result = false;
0485 #ifndef BSD
0486     QFileInfo mtabInfo(MTAB);
0487     if (!mtabInfo.exists() || mtabInfo.isSymLink()) { // if mtab is a symlimk to /proc/mounts the mtime is unusable
0488 #endif
0489         KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName);
0490         QCryptographicHash md5(QCryptographicHash::Md5);
0491         for (auto &mountPoint : mountPoints) {
0492             md5.addData(mountPoint->mountedFrom().toUtf8());
0493             md5.addData(mountPoint->realDeviceName().toUtf8());
0494             md5.addData(mountPoint->mountPoint().toUtf8());
0495             md5.addData(mountPoint->mountType().toUtf8());
0496         }
0497         QString s = md5.result();
0498         result = s != checksum;
0499         checksum = s;
0500 #ifndef BSD
0501     } else {
0502         result = mtabInfo.lastModified() != lastMtab;
0503         lastMtab = mtabInfo.lastModified();
0504     }
0505 #endif
0506     return result;
0507 }
0508 
0509 KrMountDetector krMountDetector;
0510 KrMountDetector *KrMountDetector::getInstance()
0511 {
0512     return &krMountDetector;
0513 }