File indexing completed on 2023-10-01 04:05:46
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"