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"