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 <QIcon>
12
13 #include <KBookmarkManager>
14 #include <KConfig>
15 #include <KConfigGroup>
16 #include <KIconUtils>
17 #include <KLocalizedString>
18 #include <KMountPoint>
19 #include <kprotocolinfo.h>
20 #include <solid/block.h>
21 #include <solid/networkshare.h>
22 #include <solid/opticaldisc.h>
23 #include <solid/opticaldrive.h>
24 #include <solid/portablemediaplayer.h>
25 #include <solid/storageaccess.h>
26 #include <solid/storagedrive.h>
27 #include <solid/storagevolume.h>
28
isTrash(const KBookmark & bk)29 static bool isTrash(const KBookmark &bk)
30 {
31 return bk.url().toString() == QLatin1String("trash:/");
32 }
33
KFilePlacesItem(KBookmarkManager * manager,const QString & address,const QString & udi,KFilePlacesModel * parent)34 KFilePlacesItem::KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi, KFilePlacesModel *parent)
35 : QObject(static_cast<QObject *>(parent))
36 , m_manager(manager)
37 , m_folderIsEmpty(true)
38 , m_isCdrom(false)
39 , m_isAccessible(false)
40 {
41 updateDeviceInfo(udi);
42 setBookmark(m_manager->findByAddress(address));
43
44 if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) {
45 m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
46 } else if (udi.isEmpty()) {
47 if (isTrash(m_bookmark)) {
48 KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig);
49 const KConfigGroup group = cfg.group("Status");
50 m_folderIsEmpty = group.readEntry("Empty", true);
51 }
52 }
53
54 // Hide SSHFS network device mounted by kdeconnect, since we already have the kdeconnect:// place.
55 if (isDevice() && m_access && device().vendor() == QLatin1String("fuse.sshfs")) {
56 // Not using findByPath() as it resolves symlinks, potentially blocking,
57 // but here we know we query for an existing actual mount point.
58 const auto mountPoints = KMountPoint::currentMountPoints();
59 for (const auto &mountPoint : mountPoints) {
60 if (mountPoint->mountPoint() == m_access->filePath()) {
61 if (mountPoint->mountedFrom().startsWith(QLatin1String("kdeconnect@"))) {
62 // Hide only if the user never set the "Hide" checkbox on the device.
63 if (m_bookmark.metaDataItem(QStringLiteral("IsHidden")).isEmpty()) {
64 setHidden(true);
65 }
66 }
67 break;
68 }
69 }
70 }
71 }
72
~KFilePlacesItem()73 KFilePlacesItem::~KFilePlacesItem()
74 {
75 }
76
id() const77 QString KFilePlacesItem::id() const
78 {
79 if (isDevice()) {
80 return bookmark().metaDataItem(QStringLiteral("UDI"));
81 } else {
82 return bookmark().metaDataItem(QStringLiteral("ID"));
83 }
84 }
85
hasSupportedScheme(const QStringList & schemes) const86 bool KFilePlacesItem::hasSupportedScheme(const QStringList &schemes) const
87 {
88 if (schemes.isEmpty()) {
89 return true;
90 }
91
92 // StorageAccess is always local, doesn't need to be accessible to know this
93 if (m_access && schemes.contains(QLatin1String("file"))) {
94 return true;
95 }
96
97 if (m_networkShare && schemes.contains(m_networkShare->url().scheme())) {
98 return true;
99 }
100
101 if (m_player) {
102 const QStringList protocols = m_player->supportedProtocols();
103 for (const QString &protocol : protocols) {
104 if (schemes.contains(protocol)) {
105 return true;
106 }
107 }
108 }
109
110 return false;
111 }
112
isDevice() const113 bool KFilePlacesItem::isDevice() const
114 {
115 return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty();
116 }
117
bookmark() const118 KBookmark KFilePlacesItem::bookmark() const
119 {
120 return m_bookmark;
121 }
122
setBookmark(const KBookmark & bookmark)123 void KFilePlacesItem::setBookmark(const KBookmark &bookmark)
124 {
125 m_bookmark = bookmark;
126
127 updateDeviceInfo(m_bookmark.metaDataItem(QStringLiteral("UDI")));
128
129 if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) {
130 // This context must stay as it is - the translated system bookmark names
131 // are created with 'KFile System Bookmarks' as their context, so this
132 // ensures the right string is picked from the catalog.
133 // (coles, 13th May 2009)
134
135 m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data());
136 } else {
137 m_text = bookmark.text();
138 }
139
140 const KFilePlacesModel::GroupType type = groupType();
141 switch (type) {
142 case KFilePlacesModel::PlacesType:
143 m_groupName = i18nc("@item", "Places");
144 break;
145 case KFilePlacesModel::RemoteType:
146 m_groupName = i18nc("@item", "Remote");
147 break;
148 case KFilePlacesModel::RecentlySavedType:
149 m_groupName = i18nc("@item The place group section name for recent dynamic lists", "Recent");
150 break;
151 case KFilePlacesModel::SearchForType:
152 m_groupName = i18nc("@item", "Search For");
153 break;
154 case KFilePlacesModel::DevicesType:
155 m_groupName = i18nc("@item", "Devices");
156 break;
157 case KFilePlacesModel::RemovableDevicesType:
158 m_groupName = i18nc("@item", "Removable Devices");
159 break;
160 case KFilePlacesModel::TagsType:
161 m_groupName = i18nc("@item", "Tags");
162 break;
163 default:
164 Q_UNREACHABLE();
165 break;
166 }
167 }
168
device() const169 Solid::Device KFilePlacesItem::device() const
170 {
171 return m_device;
172 }
173
data(int role) const174 QVariant KFilePlacesItem::data(int role) const
175 {
176 if (role == KFilePlacesModel::GroupRole) {
177 return QVariant(m_groupName);
178 } else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) {
179 return deviceData(role);
180 } else {
181 return bookmarkData(role);
182 }
183 }
184
groupType() const185 KFilePlacesModel::GroupType KFilePlacesItem::groupType() const
186 {
187 if (!isDevice()) {
188 const QString protocol = bookmark().url().scheme();
189 if (protocol == QLatin1String("timeline") || protocol == QLatin1String("recentlyused")) {
190 return KFilePlacesModel::RecentlySavedType;
191 }
192
193 if (protocol.contains(QLatin1String("search"))) {
194 return KFilePlacesModel::SearchForType;
195 }
196
197 if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) {
198 return KFilePlacesModel::DevicesType;
199 }
200
201 if (protocol == QLatin1String("tags")) {
202 return KFilePlacesModel::TagsType;
203 }
204
205 if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) {
206 return KFilePlacesModel::RemoteType;
207 } else {
208 return KFilePlacesModel::PlacesType;
209 }
210 }
211
212 if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) {
213 return KFilePlacesModel::RemovableDevicesType;
214 } else if (m_networkShare) {
215 return KFilePlacesModel::RemoteType;
216 } else {
217 return KFilePlacesModel::DevicesType;
218 }
219 }
220
isHidden() const221 bool KFilePlacesItem::isHidden() const
222 {
223 return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true");
224 }
225
setHidden(bool hide)226 void KFilePlacesItem::setHidden(bool hide)
227 {
228 if (m_bookmark.isNull() || isHidden() == hide) {
229 return;
230 }
231 m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false"));
232 }
233
bookmarkData(int role) const234 QVariant KFilePlacesItem::bookmarkData(int role) const
235 {
236 KBookmark b = bookmark();
237
238 if (b.isNull()) {
239 return QVariant();
240 }
241
242 switch (role) {
243 case Qt::DisplayRole:
244 return m_text;
245 case Qt::DecorationRole:
246 return QIcon::fromTheme(iconNameForBookmark(b));
247 case Qt::BackgroundRole:
248 if (isHidden()) {
249 return QColor(Qt::lightGray);
250 } else {
251 return QVariant();
252 }
253 case KFilePlacesModel::UrlRole:
254 return b.url();
255 case KFilePlacesModel::SetupNeededRole:
256 return false;
257 case KFilePlacesModel::HiddenRole:
258 return isHidden();
259 case KFilePlacesModel::IconNameRole:
260 return iconNameForBookmark(b);
261 default:
262 return QVariant();
263 }
264 }
265
deviceData(int role) const266 QVariant KFilePlacesItem::deviceData(int role) const
267 {
268 Solid::Device d = device();
269
270 if (d.isValid()) {
271 switch (role) {
272 case Qt::DisplayRole:
273 return d.displayName();
274 case Qt::DecorationRole:
275 // qDebug() << "adding emblems" << m_emblems << "to device icon" << m_deviceIconName;
276 return KIconUtils::addOverlays(m_deviceIconName, m_emblems);
277 case KFilePlacesModel::UrlRole:
278 if (m_access) {
279 const QString path = m_access->filePath();
280 return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path);
281 } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) {
282 Solid::Block *block = d.as<Solid::Block>();
283 if (block) {
284 QString device = block->device();
285 return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device));
286 }
287 // We failed to get the block device. Assume audiocd:/ can
288 // figure it out, but cannot handle multiple disc drives.
289 // See https://bugs.kde.org/show_bug.cgi?id=314544#c40
290 return QUrl(QStringLiteral("audiocd:/"));
291 } else if (m_player) {
292 const QStringList protocols = m_player->supportedProtocols();
293 if (!protocols.isEmpty()) {
294 return QUrl(QStringLiteral("%1:udi=%2").arg(protocols.first(), d.udi()));
295 }
296 return QVariant();
297 } else {
298 return QVariant();
299 }
300 case KFilePlacesModel::SetupNeededRole:
301 if (m_access) {
302 return !m_isAccessible;
303 } else {
304 return QVariant();
305 }
306
307 case KFilePlacesModel::FixedDeviceRole: {
308 if (m_drive != nullptr) {
309 return !m_drive->isHotpluggable() && !m_drive->isRemovable();
310 }
311 return true;
312 }
313
314 case KFilePlacesModel::CapacityBarRecommendedRole:
315 return m_isAccessible && !m_isCdrom;
316
317 case KFilePlacesModel::IconNameRole:
318 return m_deviceIconName;
319
320 default:
321 return QVariant();
322 }
323 } else {
324 return QVariant();
325 }
326 }
327
createBookmark(KBookmarkManager * manager,const QString & label,const QUrl & url,const QString & iconName,KFilePlacesItem * after)328 KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after)
329 {
330 KBookmarkGroup root = manager->root();
331 if (root.isNull()) {
332 return KBookmark();
333 }
334 QString empty_icon = iconName;
335 if (url.toString() == QLatin1String("trash:/")) {
336 if (empty_icon.endsWith(QLatin1String("-full"))) {
337 empty_icon.chop(5);
338 } else if (empty_icon.isEmpty()) {
339 empty_icon = QStringLiteral("user-trash");
340 }
341 }
342 KBookmark bookmark = root.addBookmark(label, url, empty_icon);
343 bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId());
344
345 if (after) {
346 root.moveBookmark(bookmark, after->bookmark());
347 }
348
349 return bookmark;
350 }
351
createSystemBookmark(KBookmarkManager * manager,const char * translationContext,const QByteArray & untranslatedLabel,const QUrl & url,const QString & iconName,const KBookmark & after)352 KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager,
353 const char *translationContext,
354 const QByteArray &untranslatedLabel,
355 const QUrl &url,
356 const QString &iconName,
357 const KBookmark &after)
358 {
359 Q_UNUSED(translationContext); // parameter is only necessary to force the caller
360 // to provide a marked-for-translation string for the label, with context
361
362 KBookmark bookmark = createBookmark(manager, QString::fromUtf8(untranslatedLabel), url, iconName);
363 if (!bookmark.isNull()) {
364 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
365 }
366 if (!after.isNull()) {
367 manager->root().moveBookmark(bookmark, after);
368 }
369 return bookmark;
370 }
371
createDeviceBookmark(KBookmarkManager * manager,const QString & udi)372 KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const QString &udi)
373 {
374 KBookmarkGroup root = manager->root();
375 if (root.isNull()) {
376 return KBookmark();
377 }
378 KBookmark bookmark = root.createNewSeparator();
379 bookmark.setMetaDataItem(QStringLiteral("UDI"), udi);
380 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
381 return bookmark;
382 }
383
createTagBookmark(KBookmarkManager * manager,const QString & tag)384 KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, const QString &tag)
385 {
386 KBookmark bookmark = createSystemBookmark(manager, tag.toUtf8().data(), tag.toUtf8(), QUrl(QLatin1String("tags:/") + tag), QLatin1String("tag"));
387 bookmark.setMetaDataItem(QStringLiteral("tag"), tag);
388 bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true"));
389
390 return bookmark;
391 }
392
generateNewId()393 QString KFilePlacesItem::generateNewId()
394 {
395 static int count = 0;
396
397 // return QString::number(count++);
398
399 return QString::number(QDateTime::currentSecsSinceEpoch()) + QLatin1Char('/') + QString::number(count++);
400
401 // return QString::number(QDateTime::currentSecsSinceEpoch())
402 // + '/' + QString::number(qrand());
403 }
404
updateDeviceInfo(const QString & udi)405 bool KFilePlacesItem::updateDeviceInfo(const QString &udi)
406 {
407 if (m_device.udi() == udi) {
408 return false;
409 }
410
411 if (m_access) {
412 m_access->disconnect(this);
413 }
414
415 m_device = Solid::Device(udi);
416 if (m_device.isValid()) {
417 m_access = m_device.as<Solid::StorageAccess>();
418 m_volume = m_device.as<Solid::StorageVolume>();
419 m_disc = m_device.as<Solid::OpticalDisc>();
420 m_player = m_device.as<Solid::PortableMediaPlayer>();
421 m_networkShare = m_device.as<Solid::NetworkShare>();
422 m_deviceIconName = m_device.icon();
423 m_emblems = m_device.emblems();
424
425 m_drive = nullptr;
426 Solid::Device parentDevice = m_device;
427 while (parentDevice.isValid() && !m_drive) {
428 m_drive = parentDevice.as<Solid::StorageDrive>();
429 parentDevice = parentDevice.parent();
430 }
431
432 if (m_access) {
433 connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged);
434 onAccessibilityChanged(m_access->isAccessible());
435 }
436 } else {
437 m_access = nullptr;
438 m_volume = nullptr;
439 m_disc = nullptr;
440 m_player = nullptr;
441 m_drive = nullptr;
442 m_networkShare = nullptr;
443 m_deviceIconName.clear();
444 m_emblems.clear();
445 }
446
447 return true;
448 }
449
onAccessibilityChanged(bool isAccessible)450 void KFilePlacesItem::onAccessibilityChanged(bool isAccessible)
451 {
452 m_isAccessible = isAccessible;
453 m_isCdrom =
454 m_device.is<Solid::OpticalDrive>() || m_device.parent().is<Solid::OpticalDrive>() || (m_volume && m_volume->fsType() == QLatin1String("iso9660"));
455 m_emblems = m_device.emblems();
456
457 Q_EMIT itemChanged(id());
458 }
459
iconNameForBookmark(const KBookmark & bookmark) const460 QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const
461 {
462 if (!m_folderIsEmpty && isTrash(bookmark)) {
463 return bookmark.icon() + QLatin1String("-full");
464 } else {
465 return bookmark.icon();
466 }
467 }
468
469 #include "moc_kfileplacesitem_p.cpp"
470