File indexing completed on 2024-04-21 03:55:19

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