File indexing completed on 2024-04-28 15:26:41

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kfileplacesitem_p.h"
0009 
0010 #include <QDateTime>
0011 #include <QDir>
0012 #include <QIcon>
0013 
0014 #include <KBookmarkManager>
0015 #include <KConfig>
0016 #include <KConfigGroup>
0017 #include <KIconUtils>
0018 #include <KLocalizedString>
0019 #include <KMountPoint>
0020 #include <kprotocolinfo.h>
0021 #include <solid/block.h>
0022 #include <solid/networkshare.h>
0023 #include <solid/opticaldisc.h>
0024 #include <solid/opticaldrive.h>
0025 #include <solid/portablemediaplayer.h>
0026 #include <solid/storageaccess.h>
0027 #include <solid/storagedrive.h>
0028 #include <solid/storagevolume.h>
0029 
0030 static bool isTrash(const KBookmark &bk)
0031 {
0032     return bk.url().toString() == QLatin1String("trash:/");
0033 }
0034 
0035 KFilePlacesItem::KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi, KFilePlacesModel *parent)
0036     : QObject(static_cast<QObject *>(parent))
0037     , m_manager(manager)
0038     , m_folderIsEmpty(true)
0039     , m_isCdrom(false)
0040     , m_isAccessible(false)
0041     , m_isTeardownAllowed(false)
0042     , m_isTeardownOverlayRecommended(false)
0043     , m_isTeardownInProgress(false)
0044     , m_isSetupInProgress(false)
0045 {
0046     updateDeviceInfo(udi);
0047     setBookmark(m_manager->findByAddress(address));
0048 
0049     if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) {
0050         m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
0051     } else if (udi.isEmpty()) {
0052         if (isTrash(m_bookmark)) {
0053             KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig);
0054             const KConfigGroup group = cfg.group("Status");
0055             m_folderIsEmpty = group.readEntry("Empty", true);
0056         }
0057     }
0058 
0059     // Hide SSHFS network device mounted by kdeconnect, since we already have the kdeconnect:// place.
0060     if (isDevice() && m_access && device().vendor() == QLatin1String("fuse.sshfs")) {
0061         const QString storageFilePath = m_access->filePath();
0062         // Not using findByPath() as it resolves symlinks, potentially blocking,
0063         // but here we know we query for an existing actual mount point.
0064         const auto mountPoints = KMountPoint::currentMountPoints();
0065         auto it = std::find_if(mountPoints.cbegin(), mountPoints.cend(), [&storageFilePath](const KMountPoint::Ptr &mountPoint) {
0066             return mountPoint->mountPoint() == storageFilePath;
0067         });
0068         if (it != mountPoints.cend()) {
0069             if ((*it)->mountedFrom().startsWith(QLatin1String("kdeconnect@"))) {
0070                 // Hide only if the user never set the "Hide" checkbox on the device.
0071                 if (m_bookmark.metaDataItem(QStringLiteral("IsHidden")).isEmpty()) {
0072                     setHidden(true);
0073                 }
0074             }
0075         }
0076     }
0077 }
0078 
0079 KFilePlacesItem::~KFilePlacesItem()
0080 {
0081 }
0082 
0083 QString KFilePlacesItem::id() const
0084 {
0085     if (isDevice()) {
0086         return bookmark().metaDataItem(QStringLiteral("UDI"));
0087     } else {
0088         return bookmark().metaDataItem(QStringLiteral("ID"));
0089     }
0090 }
0091 
0092 bool KFilePlacesItem::hasSupportedScheme(const QStringList &schemes) const
0093 {
0094     if (schemes.isEmpty()) {
0095         return true;
0096     }
0097 
0098     // StorageAccess is always local, doesn't need to be accessible to know this
0099     if (m_access && schemes.contains(QLatin1String("file"))) {
0100         return true;
0101     }
0102 
0103     if (m_networkShare && schemes.contains(m_networkShare->url().scheme())) {
0104         return true;
0105     }
0106 
0107     if (m_player) {
0108         const QStringList protocols = m_player->supportedProtocols();
0109         for (const QString &protocol : protocols) {
0110             if (schemes.contains(protocol)) {
0111                 return true;
0112             }
0113         }
0114     }
0115 
0116     return false;
0117 }
0118 
0119 bool KFilePlacesItem::isDevice() const
0120 {
0121     return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty();
0122 }
0123 
0124 KFilePlacesModel::DeviceAccessibility KFilePlacesItem::deviceAccessibility() const
0125 {
0126     if (m_isTeardownInProgress) {
0127         return KFilePlacesModel::TeardownInProgress;
0128     } else if (m_isSetupInProgress) {
0129         return KFilePlacesModel::SetupInProgress;
0130     } else if (m_isAccessible) {
0131         return KFilePlacesModel::Accessible;
0132     } else {
0133         return KFilePlacesModel::SetupNeeded;
0134     }
0135 }
0136 
0137 bool KFilePlacesItem::isTeardownAllowed() const
0138 {
0139     return m_isTeardownAllowed;
0140 }
0141 
0142 bool KFilePlacesItem::isTeardownOverlayRecommended() const
0143 {
0144     return m_isTeardownOverlayRecommended;
0145 }
0146 
0147 bool KFilePlacesItem::isEjectAllowed() const
0148 {
0149     return m_isCdrom;
0150 }
0151 
0152 KBookmark KFilePlacesItem::bookmark() const
0153 {
0154     return m_bookmark;
0155 }
0156 
0157 void KFilePlacesItem::setBookmark(const KBookmark &bookmark)
0158 {
0159     m_bookmark = bookmark;
0160 
0161     updateDeviceInfo(m_bookmark.metaDataItem(QStringLiteral("UDI")));
0162 
0163     if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) {
0164         // This context must stay as it is - the translated system bookmark names
0165         // are created with 'KFile System Bookmarks' as their context, so this
0166         // ensures the right string is picked from the catalog.
0167         // (coles, 13th May 2009)
0168 
0169         m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data());
0170     } else {
0171         m_text = bookmark.text();
0172     }
0173 
0174     const KFilePlacesModel::GroupType type = groupType();
0175     switch (type) {
0176     case KFilePlacesModel::PlacesType:
0177         m_groupName = i18nc("@item", "Places");
0178         break;
0179     case KFilePlacesModel::RemoteType:
0180         m_groupName = i18nc("@item", "Remote");
0181         break;
0182     case KFilePlacesModel::RecentlySavedType:
0183         m_groupName = i18nc("@item The place group section name for recent dynamic lists", "Recent");
0184         break;
0185     case KFilePlacesModel::SearchForType:
0186         m_groupName = i18nc("@item", "Search For");
0187         break;
0188     case KFilePlacesModel::DevicesType:
0189         m_groupName = i18nc("@item", "Devices");
0190         break;
0191     case KFilePlacesModel::RemovableDevicesType:
0192         m_groupName = i18nc("@item", "Removable Devices");
0193         break;
0194     case KFilePlacesModel::TagsType:
0195         m_groupName = i18nc("@item", "Tags");
0196         break;
0197     default:
0198         Q_UNREACHABLE();
0199         break;
0200     }
0201 }
0202 
0203 Solid::Device KFilePlacesItem::device() const
0204 {
0205     return m_device;
0206 }
0207 
0208 QVariant KFilePlacesItem::data(int role) const
0209 {
0210     if (role == KFilePlacesModel::GroupRole) {
0211         return QVariant(m_groupName);
0212     } else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) {
0213         return deviceData(role);
0214     } else {
0215         return bookmarkData(role);
0216     }
0217 }
0218 
0219 KFilePlacesModel::GroupType KFilePlacesItem::groupType() const
0220 {
0221     if (!isDevice()) {
0222         const QString protocol = bookmark().url().scheme();
0223         if (protocol == QLatin1String("timeline") || protocol == QLatin1String("recentlyused")) {
0224             return KFilePlacesModel::RecentlySavedType;
0225         }
0226 
0227         if (protocol.contains(QLatin1String("search"))) {
0228             return KFilePlacesModel::SearchForType;
0229         }
0230 
0231         if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) {
0232             return KFilePlacesModel::DevicesType;
0233         }
0234 
0235         if (protocol == QLatin1String("tags")) {
0236             return KFilePlacesModel::TagsType;
0237         }
0238 
0239         if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) {
0240             return KFilePlacesModel::RemoteType;
0241         } else {
0242             return KFilePlacesModel::PlacesType;
0243         }
0244     }
0245 
0246     if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) {
0247         return KFilePlacesModel::RemovableDevicesType;
0248     } else if (m_networkShare) {
0249         return KFilePlacesModel::RemoteType;
0250     } else {
0251         return KFilePlacesModel::DevicesType;
0252     }
0253 }
0254 
0255 bool KFilePlacesItem::isHidden() const
0256 {
0257     return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true");
0258 }
0259 
0260 void KFilePlacesItem::setHidden(bool hide)
0261 {
0262     if (m_bookmark.isNull() || isHidden() == hide) {
0263         return;
0264     }
0265     m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false"));
0266 }
0267 
0268 QVariant KFilePlacesItem::bookmarkData(int role) const
0269 {
0270     KBookmark b = bookmark();
0271 
0272     if (b.isNull()) {
0273         return QVariant();
0274     }
0275 
0276     switch (role) {
0277     case Qt::DisplayRole:
0278         return m_text;
0279     case Qt::DecorationRole:
0280         return QIcon::fromTheme(iconNameForBookmark(b));
0281     case Qt::BackgroundRole:
0282         if (isHidden()) {
0283             return QColor(Qt::lightGray);
0284         } else {
0285             return QVariant();
0286         }
0287     case KFilePlacesModel::UrlRole:
0288         return b.url();
0289     case KFilePlacesModel::SetupNeededRole:
0290         return false;
0291     case KFilePlacesModel::HiddenRole:
0292         return isHidden();
0293     case KFilePlacesModel::IconNameRole:
0294         return iconNameForBookmark(b);
0295     default:
0296         return QVariant();
0297     }
0298 }
0299 
0300 QVariant KFilePlacesItem::deviceData(int role) const
0301 {
0302     Solid::Device d = device();
0303 
0304     if (d.isValid()) {
0305         switch (role) {
0306         case Qt::DisplayRole:
0307             if (m_deviceDisplayName.isEmpty()) {
0308                 m_deviceDisplayName = d.displayName();
0309             }
0310             return m_deviceDisplayName;
0311         case Qt::DecorationRole:
0312             // qDebug() << "adding emblems" << m_emblems << "to device icon" << m_deviceIconName;
0313             return KIconUtils::addOverlays(m_deviceIconName, m_emblems);
0314         case KFilePlacesModel::UrlRole:
0315             if (m_access) {
0316                 const QString path = m_access->filePath();
0317                 return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path);
0318             } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) {
0319                 Solid::Block *block = d.as<Solid::Block>();
0320                 if (block) {
0321                     QString device = block->device();
0322                     return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device));
0323                 }
0324                 // We failed to get the block device. Assume audiocd:/ can
0325                 // figure it out, but cannot handle multiple disc drives.
0326                 // See https://bugs.kde.org/show_bug.cgi?id=314544#c40
0327                 return QUrl(QStringLiteral("audiocd:/"));
0328             } else if (m_player) {
0329                 const QStringList protocols = m_player->supportedProtocols();
0330                 if (!protocols.isEmpty()) {
0331                     return QUrl(QStringLiteral("%1:udi=%2").arg(protocols.first(), d.udi()));
0332                 }
0333                 return QVariant();
0334             } else {
0335                 return QVariant();
0336             }
0337         case KFilePlacesModel::SetupNeededRole:
0338             if (m_access) {
0339                 return !m_isAccessible;
0340             } else {
0341                 return QVariant();
0342             }
0343 
0344         case KFilePlacesModel::TeardownAllowedRole:
0345             if (m_access) {
0346                 return m_isTeardownAllowed;
0347             } else {
0348                 return QVariant();
0349             }
0350 
0351         case KFilePlacesModel::EjectAllowedRole:
0352             return m_isAccessible && m_isCdrom;
0353 
0354         case KFilePlacesModel::TeardownOverlayRecommendedRole:
0355             return m_isTeardownOverlayRecommended;
0356 
0357         case KFilePlacesModel::DeviceAccessibilityRole:
0358             return deviceAccessibility();
0359 
0360         case KFilePlacesModel::FixedDeviceRole: {
0361             if (m_drive != nullptr) {
0362                 return !m_drive->isHotpluggable() && !m_drive->isRemovable();
0363             }
0364             return true;
0365         }
0366 
0367         case KFilePlacesModel::CapacityBarRecommendedRole:
0368             return m_isAccessible && !m_isCdrom && !m_networkShare;
0369 
0370         case KFilePlacesModel::IconNameRole:
0371             return m_deviceIconName;
0372 
0373         default:
0374             return QVariant();
0375         }
0376     } else {
0377         return QVariant();
0378     }
0379 }
0380 
0381 KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after)
0382 {
0383     KBookmarkGroup root = manager->root();
0384     if (root.isNull()) {
0385         return KBookmark();
0386     }
0387     QString empty_icon = iconName;
0388     if (url.toString() == QLatin1String("trash:/")) {
0389         if (empty_icon.endsWith(QLatin1String("-full"))) {
0390             empty_icon.chop(5);
0391         } else if (empty_icon.isEmpty()) {
0392             empty_icon = QStringLiteral("user-trash");
0393         }
0394     }
0395     KBookmark bookmark = root.addBookmark(label, url, empty_icon);
0396     bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
0397 
0398     if (after) {
0399         root.moveBookmark(bookmark, after->bookmark());
0400     }
0401 
0402     return bookmark;
0403 }
0404 
0405 KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager,
0406                                                 const char *untranslatedLabel,
0407                                                 const QUrl &url,
0408                                                 const QString &iconName,
0409                                                 const KBookmark &after)
0410 {
0411     KBookmark bookmark = createBookmark(manager, QString::fromUtf8(untranslatedLabel), url, iconName);
0412     if (!bookmark.isNull()) {
0413         bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
0414     }
0415     if (!after.isNull()) {
0416         manager->root().moveBookmark(bookmark, after);
0417     }
0418     return bookmark;
0419 }
0420 
0421 KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const QString &udi)
0422 {
0423     KBookmarkGroup root = manager->root();
0424     if (root.isNull()) {
0425         return KBookmark();
0426     }
0427     KBookmark bookmark = root.createNewSeparator();
0428     bookmark.setMetaDataItem(QStringLiteral("UDI"), udi);
0429     bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
0430     return bookmark;
0431 }
0432 
0433 KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, const QString &tag)
0434 {
0435     // TODO: Currently KFilePlacesItem::setBookmark() only decides by the "isSystemItem" property
0436     // if the label text should be looked up for translation. So there is a small risk that
0437     // labelTexts which match existing untranslated system labels accidentally get translated.
0438     KBookmark bookmark = createBookmark(manager, tag, QUrl(QLatin1String("tags:/") + tag), QStringLiteral("tag"));
0439     if (!bookmark.isNull()) {
0440         bookmark.setMetaDataItem(QStringLiteral("tag"), tag);
0441         bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
0442     }
0443 
0444     return bookmark;
0445 }
0446 
0447 QString KFilePlacesItem::generateNewId()
0448 {
0449     static int count = 0;
0450 
0451     //    return QString::number(count++);
0452 
0453     return QString::number(QDateTime::currentSecsSinceEpoch()) + QLatin1Char('/') + QString::number(count++);
0454 
0455     //    return QString::number(QDateTime::currentSecsSinceEpoch())
0456     //         + '/' + QString::number(qrand());
0457 }
0458 
0459 bool KFilePlacesItem::updateDeviceInfo(const QString &udi)
0460 {
0461     if (m_device.udi() == udi) {
0462         return false;
0463     }
0464 
0465     if (m_access) {
0466         m_access->disconnect(this);
0467     }
0468 
0469     m_device = Solid::Device(udi);
0470     if (m_device.isValid()) {
0471         m_access = m_device.as<Solid::StorageAccess>();
0472         m_volume = m_device.as<Solid::StorageVolume>();
0473         m_disc = m_device.as<Solid::OpticalDisc>();
0474         m_player = m_device.as<Solid::PortableMediaPlayer>();
0475         m_networkShare = m_device.as<Solid::NetworkShare>();
0476         m_deviceIconName = m_device.icon();
0477         m_emblems = m_device.emblems();
0478 
0479         m_drive = nullptr;
0480         Solid::Device parentDevice = m_device;
0481         while (parentDevice.isValid() && !m_drive) {
0482             m_drive = parentDevice.as<Solid::StorageDrive>();
0483             parentDevice = parentDevice.parent();
0484         }
0485 
0486         if (m_access) {
0487             connect(m_access.data(), &Solid::StorageAccess::setupRequested, this, [this] {
0488                 m_isSetupInProgress = true;
0489                 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
0490             });
0491             connect(m_access.data(), &Solid::StorageAccess::setupDone, this, [this] {
0492                 m_isSetupInProgress = false;
0493                 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
0494             });
0495 
0496             connect(m_access.data(), &Solid::StorageAccess::teardownRequested, this, [this] {
0497                 m_isTeardownInProgress = true;
0498                 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
0499             });
0500             connect(m_access.data(), &Solid::StorageAccess::teardownDone, this, [this] {
0501                 m_isTeardownInProgress = false;
0502                 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
0503             });
0504 
0505             connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged);
0506             onAccessibilityChanged(m_access->isAccessible());
0507         }
0508     } else {
0509         m_access = nullptr;
0510         m_volume = nullptr;
0511         m_disc = nullptr;
0512         m_player = nullptr;
0513         m_drive = nullptr;
0514         m_networkShare = nullptr;
0515         m_deviceIconName.clear();
0516         m_emblems.clear();
0517     }
0518 
0519     return true;
0520 }
0521 
0522 void KFilePlacesItem::onAccessibilityChanged(bool isAccessible)
0523 {
0524     m_isAccessible = isAccessible;
0525     m_isCdrom =
0526         m_device.is<Solid::OpticalDrive>() || m_device.parent().is<Solid::OpticalDrive>() || (m_volume && m_volume->fsType() == QLatin1String("iso9660"));
0527     m_emblems = m_device.emblems();
0528 
0529     m_isTeardownAllowed = isAccessible;
0530     if (m_isTeardownAllowed) {
0531         if (m_access->filePath() == QDir::rootPath()) {
0532             m_isTeardownAllowed = false;
0533         } else {
0534             KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath());
0535             if (mountPoint && m_access->filePath() == mountPoint->mountPoint()) {
0536                 m_isTeardownAllowed = false;
0537             }
0538         }
0539     }
0540 
0541     m_isTeardownOverlayRecommended = m_isTeardownAllowed && !m_networkShare;
0542     if (m_isTeardownOverlayRecommended) {
0543         if (m_drive && !m_drive->isHotpluggable() && !m_drive->isRemovable()) {
0544             m_isTeardownOverlayRecommended = false;
0545         }
0546     }
0547 
0548     Q_EMIT itemChanged(id());
0549 }
0550 
0551 QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const
0552 {
0553     if (!m_folderIsEmpty && isTrash(bookmark)) {
0554         return bookmark.icon() + QLatin1String("-full");
0555     } else {
0556         return bookmark.icon();
0557     }
0558 }
0559 
0560 #include "moc_kfileplacesitem_p.cpp"