KIO

kfileplacesitem.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kfileplacesitem_p.h"
9
10#include <QDateTime>
11#include <QDir>
12#include <QIcon>
13
14#include <KBookmarkManager>
15#include <KConfig>
16#include <KConfigGroup>
17#include <KIconUtils>
18#include <KLocalizedString>
19#include <KMountPoint>
20#include <kprotocolinfo.h>
21#include <solid/block.h>
22#include <solid/genericinterface.h>
23#include <solid/networkshare.h>
24#include <solid/opticaldisc.h>
25#include <solid/opticaldrive.h>
26#include <solid/portablemediaplayer.h>
27#include <solid/storageaccess.h>
28#include <solid/storagedrive.h>
29#include <solid/storagevolume.h>
30
31static bool isTrash(const KBookmark &bk)
32{
33 return bk.url().toString() == QLatin1String("trash:/");
34}
35
36KFilePlacesItem::KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi, KFilePlacesModel *parent)
37 : QObject(static_cast<QObject *>(parent))
38 , m_manager(manager)
39 , m_folderIsEmpty(true)
40 , m_isCdrom(false)
41 , m_isAccessible(false)
42 , m_isTeardownAllowed(false)
43 , m_isTeardownOverlayRecommended(false)
44 , m_isTeardownInProgress(false)
45 , m_isSetupInProgress(false)
46 , m_isEjectInProgress(false)
47 , m_isReadOnly(false)
48{
49 updateDeviceInfo(udi);
50
51 setBookmark(m_manager->findByAddress(address));
52
53 if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) {
54 m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
55 } else if (udi.isEmpty()) {
56 if (isTrash(m_bookmark)) {
57 KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig);
58 const KConfigGroup group = cfg.group(QStringLiteral("Status"));
59 m_folderIsEmpty = group.readEntry("Empty", true);
60 }
61 }
62
63 // Hide SSHFS network device mounted by kdeconnect, since we already have the kdeconnect:// place.
64 if (isDevice() && m_access && device().vendor() == QLatin1String("fuse.sshfs")) {
65 const QString storageFilePath = m_access->filePath();
66 // Not using findByPath() as it resolves symlinks, potentially blocking,
67 // but here we know we query for an existing actual mount point.
68 const auto mountPoints = KMountPoint::currentMountPoints();
69 auto it = std::find_if(mountPoints.cbegin(), mountPoints.cend(), [&storageFilePath](const KMountPoint::Ptr &mountPoint) {
70 return mountPoint->mountPoint() == storageFilePath;
71 });
72 if (it != mountPoints.cend()) {
73 if ((*it)->mountedFrom().startsWith(QLatin1String("kdeconnect@"))) {
74 // Hide only if the user never set the "Hide" checkbox on the device.
75 if (m_bookmark.metaDataItem(QStringLiteral("IsHidden")).isEmpty()) {
76 setHidden(true);
77 }
78 }
79 }
80 }
81}
82
83KFilePlacesItem::~KFilePlacesItem()
84{
85}
86
87QString KFilePlacesItem::id() const
88{
89 if (isDevice()) {
90 return bookmark().metaDataItem(QStringLiteral("UDI"));
91 } else {
92 return bookmark().metaDataItem(QStringLiteral("ID"));
93 }
94}
95
96bool KFilePlacesItem::hasSupportedScheme(const QStringList &schemes) const
97{
98 if (schemes.isEmpty()) {
99 return true;
100 }
101
102 // StorageAccess is always local, doesn't need to be accessible to know this
103 if (m_access && schemes.contains(QLatin1String("file"))) {
104 return true;
105 }
106
107 if (m_networkShare && schemes.contains(m_networkShare->url().scheme())) {
108 return true;
109 }
110
111 if (m_player) {
112 const QStringList protocols = m_player->supportedProtocols();
113 for (const QString &protocol : protocols) {
114 if (schemes.contains(protocol)) {
115 return true;
116 }
117 }
118 }
119
120 return false;
121}
122
123bool KFilePlacesItem::isDevice() const
124{
125 return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty();
126}
127
128KFilePlacesModel::DeviceAccessibility KFilePlacesItem::deviceAccessibility() const
129{
130 if (m_isTeardownInProgress || m_isEjectInProgress) {
131 return KFilePlacesModel::TeardownInProgress;
132 } else if (m_isSetupInProgress) {
133 return KFilePlacesModel::SetupInProgress;
134 } else if (m_isAccessible) {
135 return KFilePlacesModel::Accessible;
136 } else {
137 return KFilePlacesModel::SetupNeeded;
138 }
139}
140
141bool KFilePlacesItem::isTeardownAllowed() const
142{
143 return m_isTeardownAllowed;
144}
145
146bool KFilePlacesItem::isTeardownOverlayRecommended() const
147{
148 return m_isTeardownOverlayRecommended;
149}
150
151bool KFilePlacesItem::isEjectAllowed() const
152{
153 return m_isCdrom;
154}
155
156KBookmark KFilePlacesItem::bookmark() const
157{
158 return m_bookmark;
159}
160
161void KFilePlacesItem::setBookmark(const KBookmark &bookmark)
162{
163 m_bookmark = bookmark;
164
165 if (m_device.isValid()) {
166 m_bookmark.setMetaDataItem(QStringLiteral("UDI"), m_device.udi());
167 if (m_volume && !m_volume->uuid().isEmpty()) {
168 m_bookmark.setMetaDataItem(QStringLiteral("uuid"), m_volume->uuid());
169 }
170 }
171
172 if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) {
173 // This context must stay as it is - the translated system bookmark names
174 // are created with 'KFile System Bookmarks' as their context, so this
175 // ensures the right string is picked from the catalog.
176 // (coles, 13th May 2009)
177
178 m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data());
179 } else {
180 m_text = bookmark.text();
181 }
182
183 const KFilePlacesModel::GroupType type = groupType();
184 switch (type) {
186 m_groupName = i18nc("@item", "Places");
187 break;
189 m_groupName = i18nc("@item", "Remote");
190 break;
192 m_groupName = i18nc("@item The place group section name for recent dynamic lists", "Recent");
193 break;
195 m_groupName = i18nc("@item", "Search For");
196 break;
198 m_groupName = i18nc("@item", "Devices");
199 break;
201 m_groupName = i18nc("@item", "Removable Devices");
202 break;
204 m_groupName = i18nc("@item", "Tags");
205 break;
206 default:
207 Q_UNREACHABLE();
208 break;
209 }
210}
211
212Solid::Device KFilePlacesItem::device() const
213{
214 return m_device;
215}
216
217QVariant KFilePlacesItem::data(int role) const
218{
219 if (role == KFilePlacesModel::GroupRole) {
220 return QVariant(m_groupName);
221 } else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) {
222 return deviceData(role);
223 } else {
224 return bookmarkData(role);
225 }
226}
227
228KFilePlacesModel::GroupType KFilePlacesItem::groupType() const
229{
230 if (!isDevice()) {
231 const QString protocol = bookmark().url().scheme();
232 if (protocol == QLatin1String("timeline") || protocol == QLatin1String("recentlyused")) {
234 }
235
236 if (protocol.contains(QLatin1String("search"))) {
238 }
239
240 if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) {
242 }
243
244 if (protocol == QLatin1String("tags")) {
246 }
247
248 if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) {
250 } else {
252 }
253 }
254
255 if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) {
257 } else if (m_networkShare) {
259 } else {
261 }
262}
263
264bool KFilePlacesItem::isHidden() const
265{
266 return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true");
267}
268
269void KFilePlacesItem::setHidden(bool hide)
270{
271 if (m_bookmark.isNull() || isHidden() == hide) {
272 return;
273 }
274 m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false"));
275}
276
277QVariant KFilePlacesItem::bookmarkData(int role) const
278{
279 KBookmark b = bookmark();
280
281 if (b.isNull()) {
282 return QVariant();
283 }
284
285 switch (role) {
286 case Qt::DisplayRole:
287 return m_text;
289 return QIcon::fromTheme(iconNameForBookmark(b));
291 if (isHidden()) {
292 return QColor(Qt::lightGray);
293 } else {
294 return QVariant();
295 }
297 return b.url();
299 return false;
301 return isHidden();
303 return iconNameForBookmark(b);
304 default:
305 return QVariant();
306 }
307}
308
309QVariant KFilePlacesItem::deviceData(int role) const
310{
311 Solid::Device d = device();
312
313 if (d.isValid()) {
314 switch (role) {
315 case Qt::DisplayRole:
316 if (m_deviceDisplayName.isEmpty()) {
317 m_deviceDisplayName = d.displayName();
318 }
319 return m_deviceDisplayName;
321 // qDebug() << "adding emblems" << m_emblems << "to device icon" << m_deviceIconName;
322 return KIconUtils::addOverlays(m_deviceIconName, m_emblems);
324 if (m_access) {
325 const QString path = m_access->filePath();
326 return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path);
327 } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) {
328 Solid::Block *block = d.as<Solid::Block>();
329 if (block) {
330 QString device = block->device();
331 return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device));
332 }
333 // We failed to get the block device. Assume audiocd:/ can
334 // figure it out, but cannot handle multiple disc drives.
335 // See https://bugs.kde.org/show_bug.cgi?id=314544#c40
336 return QUrl(QStringLiteral("audiocd:/"));
337 } else if (m_player) {
338 const QStringList protocols = m_player->supportedProtocols();
339 if (!protocols.isEmpty()) {
340 const QString protocol = protocols.first();
341 if (protocol == QLatin1String("mtp")) {
342 return QUrl(QStringLiteral("%1:udi=%2").arg(protocol, d.udi()));
343 } else {
344 QUrl url;
345 url.setScheme(protocol);
346 url.setHost(d.udi().section(QLatin1Char('/'), -1));
347 url.setPath(QStringLiteral("/"));
348 return url;
349 }
350 }
351 return QVariant();
352 } else {
353 return QVariant();
354 }
356 if (m_access) {
357 return !m_isAccessible;
358 } else {
359 return QVariant();
360 }
361
363 if (m_access) {
364 return m_isTeardownAllowed;
365 } else {
366 return QVariant();
367 }
368
370 return m_isAccessible && m_isCdrom;
371
373 return m_isTeardownOverlayRecommended;
374
376 return deviceAccessibility();
377
379 if (m_drive != nullptr) {
380 return !m_drive->isHotpluggable() && !m_drive->isRemovable();
381 }
382 return true;
383 }
384
386 return m_isAccessible && !m_isCdrom && !m_networkShare && !m_isReadOnly;
387
389 return m_deviceIconName;
390
391 default:
392 return QVariant();
393 }
394 } else {
395 return QVariant();
396 }
397}
398
399KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after)
400{
401 KBookmarkGroup root = manager->root();
402 if (root.isNull()) {
403 return KBookmark();
404 }
405 QString empty_icon = iconName;
406 if (url.toString() == QLatin1String("trash:/")) {
407 if (empty_icon.endsWith(QLatin1String("-full"))) {
408 empty_icon.chop(5);
409 } else if (empty_icon.isEmpty()) {
410 empty_icon = QStringLiteral("user-trash");
411 }
412 }
413 KBookmark bookmark = root.addBookmark(label, url, empty_icon);
414 bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
415
416 if (after) {
417 root.moveBookmark(bookmark, after->bookmark());
418 }
419
420 return bookmark;
421}
422
423KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager,
424 const char *untranslatedLabel,
425 const QUrl &url,
426 const QString &iconName,
427 const KBookmark &after)
428{
429 KBookmark bookmark = createBookmark(manager, QString::fromUtf8(untranslatedLabel), url, iconName);
430 if (!bookmark.isNull()) {
431 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
432 }
433 if (!after.isNull()) {
434 manager->root().moveBookmark(bookmark, after);
435 }
436 return bookmark;
437}
438
439KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const Solid::Device &device)
440{
441 KBookmarkGroup root = manager->root();
442 if (root.isNull()) {
443 return KBookmark();
444 }
445 KBookmark bookmark = root.createNewSeparator();
446 bookmark.setMetaDataItem(QStringLiteral("UDI"), device.udi());
447 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
448
449 const auto storage = device.as<Solid::StorageVolume>();
450 if (storage) {
451 bookmark.setMetaDataItem(QStringLiteral("uuid"), storage->uuid());
452 }
453 return bookmark;
454}
455
456KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, const QString &tag)
457{
458 // TODO: Currently KFilePlacesItem::setBookmark() only decides by the "isSystemItem" property
459 // if the label text should be looked up for translation. So there is a small risk that
460 // labelTexts which match existing untranslated system labels accidentally get translated.
461 KBookmark bookmark = createBookmark(manager, tag, QUrl(QLatin1String("tags:/") + tag), QStringLiteral("tag"));
462 if (!bookmark.isNull()) {
463 bookmark.setMetaDataItem(QStringLiteral("tag"), tag);
464 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
465 }
466
467 return bookmark;
468}
469
470QString KFilePlacesItem::generateNewId()
471{
472 static int count = 0;
473
474 // return QString::number(count++);
475
477
478 // return QString::number(QDateTime::currentSecsSinceEpoch())
479 // + '/' + QString::number(qrand());
480}
481
482bool KFilePlacesItem::updateDeviceInfo(const QString &udi)
483{
484 if (m_device.udi() == udi) {
485 return false;
486 }
487
488 if (m_access) {
489 m_access->disconnect(this);
490 }
491
492 if (m_opticalDrive) {
493 m_opticalDrive->disconnect(this);
494 }
495
496 m_device = Solid::Device(udi);
497 if (m_device.isValid()) {
498 m_access = m_device.as<Solid::StorageAccess>();
499 m_volume = m_device.as<Solid::StorageVolume>();
500 m_disc = m_device.as<Solid::OpticalDisc>();
501 m_player = m_device.as<Solid::PortableMediaPlayer>();
502 m_networkShare = m_device.as<Solid::NetworkShare>();
503 m_deviceIconName = m_device.icon();
504 m_emblems = m_device.emblems();
505
506 m_drive = nullptr;
507 m_opticalDrive = nullptr;
508
509 Solid::Device parentDevice = m_device;
510 while (parentDevice.isValid() && !m_drive) {
511 m_drive = parentDevice.as<Solid::StorageDrive>();
512 m_opticalDrive = parentDevice.as<Solid::OpticalDrive>();
513 parentDevice = parentDevice.parent();
514 }
515
516 if (m_access) {
517 connect(m_access.data(), &Solid::StorageAccess::setupRequested, this, [this] {
518 m_isSetupInProgress = true;
519 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
520 });
521 connect(m_access.data(), &Solid::StorageAccess::setupDone, this, [this] {
522 m_isSetupInProgress = false;
523 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
524 });
525
526 connect(m_access.data(), &Solid::StorageAccess::teardownRequested, this, [this] {
527 m_isTeardownInProgress = true;
528 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
529 });
530 connect(m_access.data(), &Solid::StorageAccess::teardownDone, this, [this] {
531 m_isTeardownInProgress = false;
532 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
533 });
534
535 connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged);
536 onAccessibilityChanged(m_access->isAccessible());
537 }
538
539 if (m_opticalDrive) {
540 connect(m_opticalDrive.data(), &Solid::OpticalDrive::ejectRequested, this, [this] {
541 m_isEjectInProgress = true;
542 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
543 });
544 connect(m_opticalDrive.data(), &Solid::OpticalDrive::ejectDone, this, [this] {
545 m_isEjectInProgress = false;
546 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
547 });
548 }
549 } else {
550 m_access = nullptr;
551 m_volume = nullptr;
552 m_disc = nullptr;
553 m_player = nullptr;
554 m_drive = nullptr;
555 m_opticalDrive = nullptr;
556 m_networkShare = nullptr;
557 m_deviceIconName.clear();
558 m_emblems.clear();
559 }
560
561 return true;
562}
563
564void KFilePlacesItem::onAccessibilityChanged(bool isAccessible)
565{
566 m_isAccessible = isAccessible;
567 m_isCdrom = m_device.is<Solid::OpticalDrive>() || m_opticalDrive || (m_volume && m_volume->fsType() == QLatin1String("iso9660"));
568 m_emblems = m_device.emblems();
569
570 if (auto generic = m_device.as<Solid::GenericInterface>()) {
571 // TODO add Solid API for this.
572 m_isReadOnly = generic->property(QStringLiteral("ReadOnly")).toBool();
573 }
574
575 m_isTeardownAllowed = isAccessible;
576 if (m_isTeardownAllowed) {
577 if (m_access->filePath() == QDir::rootPath()) {
578 m_isTeardownAllowed = false;
579 } else {
581 if (mountPoint && m_access->filePath() == mountPoint->mountPoint()) {
582 m_isTeardownAllowed = false;
583 }
584 }
585 }
586
587 m_isTeardownOverlayRecommended = m_isTeardownAllowed && !m_networkShare;
588 if (m_isTeardownOverlayRecommended) {
589 if (m_drive && !m_drive->isHotpluggable() && !m_drive->isRemovable()) {
590 m_isTeardownOverlayRecommended = false;
591 }
592 }
593
594 Q_EMIT itemChanged(id());
595}
596
597QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const
598{
599 if (!m_folderIsEmpty && isTrash(bookmark)) {
600 return bookmark.icon() + QLatin1String("-full");
601 } else {
602 return bookmark.icon();
603 }
604}
605
606#include "moc_kfileplacesitem_p.cpp"
bool moveBookmark(const KBookmark &bookmark, const KBookmark &after)
KBookmark createNewSeparator()
KBookmark addBookmark(const KBookmark &bm)
KBookmarkGroup root() const
QString text() const
QString icon() const
void setMetaDataItem(const QString &key, const QString &value, MetaDataOverwriteMode mode=OverwriteMetaData)
bool isNull() const
QUrl url() const
QString metaDataItem(const QString &key) const
KConfigGroup group(const QString &group)
QString readEntry(const char *key, const char *aDefault=nullptr) const
This class is a list view model.
GroupType
Describes the available group types used in this model.
@ PlacesType
"Places" section
@ RemoteType
"Remote" section
@ RemovableDevicesType
"Removable Devices" section
@ RecentlySavedType
"Recent" section
@ DevicesType
"Devices" section
@ TagsType
"Tags" section.
@ SearchForType
"Search for" section
@ EjectAllowedRole
roleName is "isEjectAllowed".
@ GroupRole
The name of the group, for example "Remote" or "Devices".
@ IconNameRole
roleName is "iconName".
@ HiddenRole
roleName is "isHidden".
@ TeardownOverlayRecommendedRole
roleName is "isTeardownOverlayRecommended".
@ CapacityBarRecommendedRole
Whether the place should have its free space displayed in a capacity bar.
@ FixedDeviceRole
Whether the place is a fixed device (neither hotpluggable nor removable).
@ SetupNeededRole
roleName is "isSetupNeeded".
@ TeardownAllowedRole
roleName is "isTeardownAllowed".
@ DeviceAccessibilityRole
roleName is "deviceAccessibility".
@ UrlRole
roleName is "url".
Ptr findByPath(const QString &path) const
Find the mountpoint on which resides path For instance if /home is a separate partition,...
static List currentMountPoints(DetailsNeededFlags infoNeeded=BasicInfoNeeded)
Returns a list of all current mountpoints.
static QString protocolClass(const QString &protocol)
Returns the protocol class for the specified protocol.
QString device() const
QString udi() const
QString displayName() const
bool isValid() const
DevIface * as()
void ejectDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
void ejectRequested(const QString &udi)
void setupRequested(const QString &udi)
void accessibilityChanged(bool accessible, const QString &udi)
void teardownRequested(const QString &udi)
void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
QIcon addOverlays(const QIcon &icon, const QHash< Qt::Corner, QIcon > &overlays)
QString path(const QString &relativePath)
char * data()
qint64 currentSecsSinceEpoch()
QString homePath()
QString rootPath()
QIcon fromTheme(const QString &name)
T & first()
bool isEmpty() const const
QObject * parent() const const
QVariant property(const char *name) const const
void chop(qsizetype n)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
lightGray
BackgroundRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString scheme() const const
void setHost(const QString &host, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setScheme(const QString &scheme)
QString toString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:49:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.