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_isReadOnly(false)
47{
48 updateDeviceInfo(udi);
49
50 setBookmark(m_manager->findByAddress(address));
51
52 if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) {
53 m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
54 } else if (udi.isEmpty()) {
55 if (isTrash(m_bookmark)) {
56 KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig);
57 const KConfigGroup group = cfg.group(QStringLiteral("Status"));
58 m_folderIsEmpty = group.readEntry("Empty", true);
59 }
60 }
61
62 // Hide SSHFS network device mounted by kdeconnect, since we already have the kdeconnect:// place.
63 if (isDevice() && m_access && device().vendor() == QLatin1String("fuse.sshfs")) {
64 const QString storageFilePath = m_access->filePath();
65 // Not using findByPath() as it resolves symlinks, potentially blocking,
66 // but here we know we query for an existing actual mount point.
67 const auto mountPoints = KMountPoint::currentMountPoints();
68 auto it = std::find_if(mountPoints.cbegin(), mountPoints.cend(), [&storageFilePath](const KMountPoint::Ptr &mountPoint) {
69 return mountPoint->mountPoint() == storageFilePath;
70 });
71 if (it != mountPoints.cend()) {
72 if ((*it)->mountedFrom().startsWith(QLatin1String("kdeconnect@"))) {
73 // Hide only if the user never set the "Hide" checkbox on the device.
74 if (m_bookmark.metaDataItem(QStringLiteral("IsHidden")).isEmpty()) {
75 setHidden(true);
76 }
77 }
78 }
79 }
80}
81
82KFilePlacesItem::~KFilePlacesItem()
83{
84}
85
86QString KFilePlacesItem::id() const
87{
88 if (isDevice()) {
89 return bookmark().metaDataItem(QStringLiteral("UDI"));
90 } else {
91 return bookmark().metaDataItem(QStringLiteral("ID"));
92 }
93}
94
95bool KFilePlacesItem::hasSupportedScheme(const QStringList &schemes) const
96{
97 if (schemes.isEmpty()) {
98 return true;
99 }
100
101 // StorageAccess is always local, doesn't need to be accessible to know this
102 if (m_access && schemes.contains(QLatin1String("file"))) {
103 return true;
104 }
105
106 if (m_networkShare && schemes.contains(m_networkShare->url().scheme())) {
107 return true;
108 }
109
110 if (m_player) {
111 const QStringList protocols = m_player->supportedProtocols();
112 for (const QString &protocol : protocols) {
113 if (schemes.contains(protocol)) {
114 return true;
115 }
116 }
117 }
118
119 return false;
120}
121
122bool KFilePlacesItem::isDevice() const
123{
124 return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty();
125}
126
127KFilePlacesModel::DeviceAccessibility KFilePlacesItem::deviceAccessibility() const
128{
129 if (m_isTeardownInProgress) {
130 return KFilePlacesModel::TeardownInProgress;
131 } else if (m_isSetupInProgress) {
132 return KFilePlacesModel::SetupInProgress;
133 } else if (m_isAccessible) {
134 return KFilePlacesModel::Accessible;
135 } else {
136 return KFilePlacesModel::SetupNeeded;
137 }
138}
139
140bool KFilePlacesItem::isTeardownAllowed() const
141{
142 return m_isTeardownAllowed;
143}
144
145bool KFilePlacesItem::isTeardownOverlayRecommended() const
146{
147 return m_isTeardownOverlayRecommended;
148}
149
150bool KFilePlacesItem::isEjectAllowed() const
151{
152 return m_isCdrom;
153}
154
155KBookmark KFilePlacesItem::bookmark() const
156{
157 return m_bookmark;
158}
159
160void KFilePlacesItem::setBookmark(const KBookmark &bookmark)
161{
162 m_bookmark = bookmark;
163
164 if (m_device.isValid()) {
165 m_bookmark.setMetaDataItem(QStringLiteral("UDI"), m_device.udi());
166 if (m_volume && !m_volume->uuid().isEmpty()) {
167 m_bookmark.setMetaDataItem(QStringLiteral("uuid"), m_volume->uuid());
168 }
169 }
170
171 if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) {
172 // This context must stay as it is - the translated system bookmark names
173 // are created with 'KFile System Bookmarks' as their context, so this
174 // ensures the right string is picked from the catalog.
175 // (coles, 13th May 2009)
176
177 m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data());
178 } else {
179 m_text = bookmark.text();
180 }
181
182 const KFilePlacesModel::GroupType type = groupType();
183 switch (type) {
185 m_groupName = i18nc("@item", "Places");
186 break;
188 m_groupName = i18nc("@item", "Remote");
189 break;
191 m_groupName = i18nc("@item The place group section name for recent dynamic lists", "Recent");
192 break;
194 m_groupName = i18nc("@item", "Search For");
195 break;
197 m_groupName = i18nc("@item", "Devices");
198 break;
200 m_groupName = i18nc("@item", "Removable Devices");
201 break;
203 m_groupName = i18nc("@item", "Tags");
204 break;
205 default:
206 Q_UNREACHABLE();
207 break;
208 }
209}
210
211Solid::Device KFilePlacesItem::device() const
212{
213 return m_device;
214}
215
216QVariant KFilePlacesItem::data(int role) const
217{
218 if (role == KFilePlacesModel::GroupRole) {
219 return QVariant(m_groupName);
220 } else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) {
221 return deviceData(role);
222 } else {
223 return bookmarkData(role);
224 }
225}
226
227KFilePlacesModel::GroupType KFilePlacesItem::groupType() const
228{
229 if (!isDevice()) {
230 const QString protocol = bookmark().url().scheme();
231 if (protocol == QLatin1String("timeline") || protocol == QLatin1String("recentlyused")) {
233 }
234
235 if (protocol.contains(QLatin1String("search"))) {
237 }
238
239 if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) {
241 }
242
243 if (protocol == QLatin1String("tags")) {
245 }
246
247 if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) {
249 } else {
251 }
252 }
253
254 if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) {
256 } else if (m_networkShare) {
258 } else {
260 }
261}
262
263bool KFilePlacesItem::isHidden() const
264{
265 return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true");
266}
267
268void KFilePlacesItem::setHidden(bool hide)
269{
270 if (m_bookmark.isNull() || isHidden() == hide) {
271 return;
272 }
273 m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false"));
274}
275
276QVariant KFilePlacesItem::bookmarkData(int role) const
277{
278 KBookmark b = bookmark();
279
280 if (b.isNull()) {
281 return QVariant();
282 }
283
284 switch (role) {
285 case Qt::DisplayRole:
286 return m_text;
288 return QIcon::fromTheme(iconNameForBookmark(b));
290 if (isHidden()) {
291 return QColor(Qt::lightGray);
292 } else {
293 return QVariant();
294 }
296 return b.url();
298 return false;
300 return isHidden();
302 return iconNameForBookmark(b);
303 default:
304 return QVariant();
305 }
306}
307
308QVariant KFilePlacesItem::deviceData(int role) const
309{
310 Solid::Device d = device();
311
312 if (d.isValid()) {
313 switch (role) {
314 case Qt::DisplayRole:
315 if (m_deviceDisplayName.isEmpty()) {
316 m_deviceDisplayName = d.displayName();
317 }
318 return m_deviceDisplayName;
320 // qDebug() << "adding emblems" << m_emblems << "to device icon" << m_deviceIconName;
321 return KIconUtils::addOverlays(m_deviceIconName, m_emblems);
323 if (m_access) {
324 const QString path = m_access->filePath();
325 return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path);
326 } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) {
327 Solid::Block *block = d.as<Solid::Block>();
328 if (block) {
329 QString device = block->device();
330 return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device));
331 }
332 // We failed to get the block device. Assume audiocd:/ can
333 // figure it out, but cannot handle multiple disc drives.
334 // See https://bugs.kde.org/show_bug.cgi?id=314544#c40
335 return QUrl(QStringLiteral("audiocd:/"));
336 } else if (m_player) {
337 const QStringList protocols = m_player->supportedProtocols();
338 if (!protocols.isEmpty()) {
339 const QString protocol = protocols.first();
340 if (protocol == QLatin1String("mtp")) {
341 return QUrl(QStringLiteral("%1:udi=%2").arg(protocol, d.udi()));
342 } else {
343 QUrl url;
344 url.setScheme(protocol);
345 url.setHost(d.udi().section(QLatin1Char('/'), -1));
346 url.setPath(QStringLiteral("/"));
347 return url;
348 }
349 }
350 return QVariant();
351 } else {
352 return QVariant();
353 }
355 if (m_access) {
356 return !m_isAccessible;
357 } else {
358 return QVariant();
359 }
360
362 if (m_access) {
363 return m_isTeardownAllowed;
364 } else {
365 return QVariant();
366 }
367
369 return m_isAccessible && m_isCdrom;
370
372 return m_isTeardownOverlayRecommended;
373
375 return deviceAccessibility();
376
378 if (m_drive != nullptr) {
379 return !m_drive->isHotpluggable() && !m_drive->isRemovable();
380 }
381 return true;
382 }
383
385 return m_isAccessible && !m_isCdrom && !m_networkShare && !m_isReadOnly;
386
388 return m_deviceIconName;
389
390 default:
391 return QVariant();
392 }
393 } else {
394 return QVariant();
395 }
396}
397
398KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after)
399{
400 KBookmarkGroup root = manager->root();
401 if (root.isNull()) {
402 return KBookmark();
403 }
404 QString empty_icon = iconName;
405 if (url.toString() == QLatin1String("trash:/")) {
406 if (empty_icon.endsWith(QLatin1String("-full"))) {
407 empty_icon.chop(5);
408 } else if (empty_icon.isEmpty()) {
409 empty_icon = QStringLiteral("user-trash");
410 }
411 }
412 KBookmark bookmark = root.addBookmark(label, url, empty_icon);
413 bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
414
415 if (after) {
416 root.moveBookmark(bookmark, after->bookmark());
417 }
418
419 return bookmark;
420}
421
422KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager,
423 const char *untranslatedLabel,
424 const QUrl &url,
425 const QString &iconName,
426 const KBookmark &after)
427{
428 KBookmark bookmark = createBookmark(manager, QString::fromUtf8(untranslatedLabel), url, iconName);
429 if (!bookmark.isNull()) {
430 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
431 }
432 if (!after.isNull()) {
433 manager->root().moveBookmark(bookmark, after);
434 }
435 return bookmark;
436}
437
438KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const Solid::Device &device)
439{
440 KBookmarkGroup root = manager->root();
441 if (root.isNull()) {
442 return KBookmark();
443 }
444 KBookmark bookmark = root.createNewSeparator();
445 bookmark.setMetaDataItem(QStringLiteral("UDI"), device.udi());
446 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
447
448 const auto storage = device.as<Solid::StorageVolume>();
449 if (storage) {
450 bookmark.setMetaDataItem(QStringLiteral("uuid"), storage->uuid());
451 }
452 return bookmark;
453}
454
455KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, const QString &tag)
456{
457 // TODO: Currently KFilePlacesItem::setBookmark() only decides by the "isSystemItem" property
458 // if the label text should be looked up for translation. So there is a small risk that
459 // labelTexts which match existing untranslated system labels accidentally get translated.
460 KBookmark bookmark = createBookmark(manager, tag, QUrl(QLatin1String("tags:/") + tag), QStringLiteral("tag"));
461 if (!bookmark.isNull()) {
462 bookmark.setMetaDataItem(QStringLiteral("tag"), tag);
463 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
464 }
465
466 return bookmark;
467}
468
469QString KFilePlacesItem::generateNewId()
470{
471 static int count = 0;
472
473 // return QString::number(count++);
474
476
477 // return QString::number(QDateTime::currentSecsSinceEpoch())
478 // + '/' + QString::number(qrand());
479}
480
481bool KFilePlacesItem::updateDeviceInfo(const QString &udi)
482{
483 if (m_device.udi() == udi) {
484 return false;
485 }
486
487 if (m_access) {
488 m_access->disconnect(this);
489 }
490
491 m_device = Solid::Device(udi);
492 if (m_device.isValid()) {
493 m_access = m_device.as<Solid::StorageAccess>();
494 m_volume = m_device.as<Solid::StorageVolume>();
495 m_disc = m_device.as<Solid::OpticalDisc>();
496 m_player = m_device.as<Solid::PortableMediaPlayer>();
497 m_networkShare = m_device.as<Solid::NetworkShare>();
498 m_deviceIconName = m_device.icon();
499 m_emblems = m_device.emblems();
500
501 m_drive = nullptr;
502 Solid::Device parentDevice = m_device;
503 while (parentDevice.isValid() && !m_drive) {
504 m_drive = parentDevice.as<Solid::StorageDrive>();
505 parentDevice = parentDevice.parent();
506 }
507
508 if (m_access) {
509 connect(m_access.data(), &Solid::StorageAccess::setupRequested, this, [this] {
510 m_isSetupInProgress = true;
511 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
512 });
513 connect(m_access.data(), &Solid::StorageAccess::setupDone, this, [this] {
514 m_isSetupInProgress = false;
515 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
516 });
517
518 connect(m_access.data(), &Solid::StorageAccess::teardownRequested, this, [this] {
519 m_isTeardownInProgress = true;
520 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
521 });
522 connect(m_access.data(), &Solid::StorageAccess::teardownDone, this, [this] {
523 m_isTeardownInProgress = false;
524 Q_EMIT itemChanged(id(), {KFilePlacesModel::DeviceAccessibilityRole});
525 });
526
527 connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged);
528 onAccessibilityChanged(m_access->isAccessible());
529 }
530 } else {
531 m_access = nullptr;
532 m_volume = nullptr;
533 m_disc = nullptr;
534 m_player = nullptr;
535 m_drive = nullptr;
536 m_networkShare = nullptr;
537 m_deviceIconName.clear();
538 m_emblems.clear();
539 }
540
541 return true;
542}
543
544void KFilePlacesItem::onAccessibilityChanged(bool isAccessible)
545{
546 m_isAccessible = isAccessible;
547 m_isCdrom =
548 m_device.is<Solid::OpticalDrive>() || m_device.parent().is<Solid::OpticalDrive>() || (m_volume && m_volume->fsType() == QLatin1String("iso9660"));
549 m_emblems = m_device.emblems();
550
551 if (auto generic = m_device.as<Solid::GenericInterface>()) {
552 // TODO add Solid API for this.
553 m_isReadOnly = generic->property(QStringLiteral("ReadOnly")).toBool();
554 }
555
556 m_isTeardownAllowed = isAccessible;
557 if (m_isTeardownAllowed) {
558 if (m_access->filePath() == QDir::rootPath()) {
559 m_isTeardownAllowed = false;
560 } else {
562 if (mountPoint && m_access->filePath() == mountPoint->mountPoint()) {
563 m_isTeardownAllowed = false;
564 }
565 }
566 }
567
568 m_isTeardownOverlayRecommended = m_isTeardownAllowed && !m_networkShare;
569 if (m_isTeardownOverlayRecommended) {
570 if (m_drive && !m_drive->isHotpluggable() && !m_drive->isRemovable()) {
571 m_isTeardownOverlayRecommended = false;
572 }
573 }
574
575 Q_EMIT itemChanged(id());
576}
577
578QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const
579{
580 if (!m_folderIsEmpty && isTrash(bookmark)) {
581 return bookmark.icon() + QLatin1String("-full");
582 } else {
583 return bookmark.icon();
584 }
585}
586
587#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 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 Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.