File indexing completed on 2024-04-28 05:45:26

0001 /*
0002  * SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz19@gmail.com>
0003  * SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "viewproperties.h"
0009 
0010 #include "dolphin_directoryviewpropertysettings.h"
0011 #include "dolphin_generalsettings.h"
0012 #include "dolphindebug.h"
0013 
0014 #include <QCryptographicHash>
0015 
0016 #include <KFileItem>
0017 
0018 namespace
0019 {
0020 const int AdditionalInfoViewPropertiesVersion = 1;
0021 const int NameRolePropertiesVersion = 2;
0022 const int DateRolePropertiesVersion = 4;
0023 const int CurrentViewPropertiesVersion = 4;
0024 
0025 // String representation to mark the additional properties of
0026 // the details view as customized by the user. See
0027 // ViewProperties::visibleRoles() for more information.
0028 const char CustomizedDetailsString[] = "CustomizedDetails";
0029 
0030 // Filename that is used for storing the properties
0031 const char ViewPropertiesFileName[] = ".directory";
0032 }
0033 
0034 ViewProperties::ViewProperties(const QUrl &url)
0035     : m_changedProps(false)
0036     , m_autoSave(true)
0037     , m_node(nullptr)
0038 {
0039     GeneralSettings *settings = GeneralSettings::self();
0040     const bool useGlobalViewProps = settings->globalViewProps() || url.isEmpty();
0041     bool useSearchView = false;
0042     bool useTrashView = false;
0043     bool useRecentDocumentsView = false;
0044     bool useDownloadsView = false;
0045 
0046     // We try and save it to the file .directory in the directory being viewed.
0047     // If the directory is not writable by the user or the directory is not local,
0048     // we store the properties information in a local file.
0049     if (useGlobalViewProps) {
0050         m_filePath = destinationDir(QStringLiteral("global"));
0051     } else if (url.scheme().contains(QLatin1String("search"))) {
0052         m_filePath = destinationDir(QStringLiteral("search/")) + directoryHashForUrl(url);
0053         useSearchView = true;
0054     } else if (url.scheme() == QLatin1String("trash")) {
0055         m_filePath = destinationDir(QStringLiteral("trash"));
0056         useTrashView = true;
0057     } else if (url.scheme() == QLatin1String("recentdocuments")) {
0058         m_filePath = destinationDir(QStringLiteral("recentdocuments"));
0059         useRecentDocumentsView = true;
0060     } else if (url.scheme() == QLatin1String("recentlyused")) {
0061         m_filePath = destinationDir(QStringLiteral("recentlyused"));
0062         useRecentDocumentsView = true;
0063     } else if (url.scheme() == QLatin1String("timeline")) {
0064         m_filePath = destinationDir(QStringLiteral("timeline"));
0065         useRecentDocumentsView = true;
0066     } else if (url.isLocalFile()) {
0067         m_filePath = url.toLocalFile();
0068 
0069         bool useDestinationDir = !isPartOfHome(m_filePath);
0070         if (!useDestinationDir) {
0071             const KFileItem fileItem(url);
0072             useDestinationDir = fileItem.isSlow();
0073         }
0074 
0075         if (!useDestinationDir) {
0076             const QFileInfo dirInfo(m_filePath);
0077             const QFileInfo fileInfo(m_filePath + QDir::separator() + ViewPropertiesFileName);
0078             useDestinationDir = !dirInfo.isWritable() || (dirInfo.size() > 0 && fileInfo.exists() && !(fileInfo.isReadable() && fileInfo.isWritable()));
0079         }
0080 
0081         if (useDestinationDir) {
0082 #ifdef Q_OS_WIN
0083             // m_filePath probably begins with C:/ - the colon is not a valid character for paths though
0084             m_filePath = QDir::separator() + m_filePath.remove(QLatin1Char(':'));
0085 #endif
0086             m_filePath = destinationDir(QStringLiteral("local")) + m_filePath;
0087         }
0088 
0089         if (m_filePath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) {
0090             useDownloadsView = true;
0091         }
0092     } else {
0093         m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath;
0094     }
0095 
0096     const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
0097     m_node = new ViewPropertySettings(KSharedConfig::openConfig(file));
0098 
0099     // If the .directory file does not exist or the timestamp is too old,
0100     // use default values instead.
0101     const bool useDefaultProps = (!useGlobalViewProps || useSearchView || useTrashView || useRecentDocumentsView || useDownloadsView)
0102         && (!QFile::exists(file) || (m_node->timestamp() < settings->viewPropsTimestamp()));
0103     if (useDefaultProps) {
0104         if (useSearchView) {
0105             const QString path = url.path();
0106 
0107             if (path == QLatin1String("/images")) {
0108                 setViewMode(DolphinView::IconsView);
0109                 setPreviewsShown(true);
0110                 setVisibleRoles({"text", "dimensions", "imageDateTime"});
0111             } else if (path == QLatin1String("/audio")) {
0112                 setViewMode(DolphinView::DetailsView);
0113                 setVisibleRoles({"text", "artist", "album", "duration"});
0114             } else if (path == QLatin1String("/videos")) {
0115                 setViewMode(DolphinView::IconsView);
0116                 setPreviewsShown(true);
0117                 setVisibleRoles({"text"});
0118             } else {
0119                 setViewMode(DolphinView::DetailsView);
0120                 setVisibleRoles({"text", "path", "modificationtime"});
0121             }
0122         } else if (useTrashView) {
0123             setViewMode(DolphinView::DetailsView);
0124             setVisibleRoles({"text", "path", "deletiontime"});
0125         } else if (useRecentDocumentsView || useDownloadsView) {
0126             setSortRole(QByteArrayLiteral("modificationtime"));
0127             setSortOrder(Qt::DescendingOrder);
0128             setSortFoldersFirst(false);
0129             setGroupedSorting(true);
0130 
0131             if (useRecentDocumentsView) {
0132                 setViewMode(DolphinView::DetailsView);
0133                 setVisibleRoles({"text", "path", "modificationtime"});
0134             }
0135         } else {
0136             // The global view-properties act as default for directories without
0137             // any view-property configuration. Constructing a ViewProperties
0138             // instance for an empty QUrl ensures that the global view-properties
0139             // are loaded.
0140             QUrl emptyUrl;
0141             ViewProperties defaultProps(emptyUrl);
0142             setDirProperties(defaultProps);
0143 
0144             m_changedProps = false;
0145         }
0146     }
0147 
0148     if (m_node->version() < CurrentViewPropertiesVersion) {
0149         // The view-properties have an outdated version. Convert the properties
0150         // to the changes of the current version.
0151         if (m_node->version() < AdditionalInfoViewPropertiesVersion) {
0152             convertAdditionalInfo();
0153             Q_ASSERT(m_node->version() == AdditionalInfoViewPropertiesVersion);
0154         }
0155 
0156         if (m_node->version() < NameRolePropertiesVersion) {
0157             convertNameRoleToTextRole();
0158             Q_ASSERT(m_node->version() == NameRolePropertiesVersion);
0159         }
0160 
0161         if (m_node->version() < DateRolePropertiesVersion) {
0162             convertDateRoleToModificationTimeRole();
0163             Q_ASSERT(m_node->version() == DateRolePropertiesVersion);
0164         }
0165 
0166         m_node->setVersion(CurrentViewPropertiesVersion);
0167     }
0168 }
0169 
0170 ViewProperties::~ViewProperties()
0171 {
0172     if (m_changedProps && m_autoSave) {
0173         save();
0174     }
0175 
0176     delete m_node;
0177     m_node = nullptr;
0178 }
0179 
0180 void ViewProperties::setViewMode(DolphinView::Mode mode)
0181 {
0182     if (m_node->viewMode() != mode) {
0183         m_node->setViewMode(mode);
0184         update();
0185     }
0186 }
0187 
0188 DolphinView::Mode ViewProperties::viewMode() const
0189 {
0190     const int mode = qBound(0, m_node->viewMode(), 2);
0191     return static_cast<DolphinView::Mode>(mode);
0192 }
0193 
0194 void ViewProperties::setPreviewsShown(bool show)
0195 {
0196     if (m_node->previewsShown() != show) {
0197         m_node->setPreviewsShown(show);
0198         update();
0199     }
0200 }
0201 
0202 bool ViewProperties::previewsShown() const
0203 {
0204     return m_node->previewsShown();
0205 }
0206 
0207 void ViewProperties::setHiddenFilesShown(bool show)
0208 {
0209     if (m_node->hiddenFilesShown() != show) {
0210         m_node->setHiddenFilesShown(show);
0211         update();
0212     }
0213 }
0214 
0215 void ViewProperties::setGroupedSorting(bool grouped)
0216 {
0217     if (m_node->groupedSorting() != grouped) {
0218         m_node->setGroupedSorting(grouped);
0219         update();
0220     }
0221 }
0222 
0223 bool ViewProperties::groupedSorting() const
0224 {
0225     return m_node->groupedSorting();
0226 }
0227 
0228 bool ViewProperties::hiddenFilesShown() const
0229 {
0230     return m_node->hiddenFilesShown();
0231 }
0232 
0233 void ViewProperties::setSortRole(const QByteArray &role)
0234 {
0235     if (m_node->sortRole() != role) {
0236         m_node->setSortRole(role);
0237         update();
0238     }
0239 }
0240 
0241 QByteArray ViewProperties::sortRole() const
0242 {
0243     return m_node->sortRole().toLatin1();
0244 }
0245 
0246 void ViewProperties::setSortOrder(Qt::SortOrder sortOrder)
0247 {
0248     if (m_node->sortOrder() != sortOrder) {
0249         m_node->setSortOrder(sortOrder);
0250         update();
0251     }
0252 }
0253 
0254 Qt::SortOrder ViewProperties::sortOrder() const
0255 {
0256     return static_cast<Qt::SortOrder>(m_node->sortOrder());
0257 }
0258 
0259 void ViewProperties::setSortFoldersFirst(bool foldersFirst)
0260 {
0261     if (m_node->sortFoldersFirst() != foldersFirst) {
0262         m_node->setSortFoldersFirst(foldersFirst);
0263         update();
0264     }
0265 }
0266 
0267 bool ViewProperties::sortFoldersFirst() const
0268 {
0269     return m_node->sortFoldersFirst();
0270 }
0271 
0272 void ViewProperties::setSortHiddenLast(bool hiddenLast)
0273 {
0274     if (m_node->sortHiddenLast() != hiddenLast) {
0275         m_node->setSortHiddenLast(hiddenLast);
0276         update();
0277     }
0278 }
0279 
0280 bool ViewProperties::sortHiddenLast() const
0281 {
0282     return m_node->sortHiddenLast();
0283 }
0284 
0285 void ViewProperties::setVisibleRoles(const QList<QByteArray> &roles)
0286 {
0287     if (roles == visibleRoles()) {
0288         return;
0289     }
0290 
0291     // See ViewProperties::visibleRoles() for the storage format
0292     // of the additional information.
0293 
0294     // Remove the old values stored for the current view-mode
0295     const QStringList oldVisibleRoles = m_node->visibleRoles();
0296     const QString prefix = viewModePrefix();
0297     QStringList newVisibleRoles = oldVisibleRoles;
0298     for (int i = newVisibleRoles.count() - 1; i >= 0; --i) {
0299         if (newVisibleRoles[i].startsWith(prefix)) {
0300             newVisibleRoles.removeAt(i);
0301         }
0302     }
0303 
0304     // Add the updated values for the current view-mode
0305     newVisibleRoles.reserve(roles.count());
0306     for (const QByteArray &role : roles) {
0307         newVisibleRoles.append(prefix + role);
0308     }
0309 
0310     if (oldVisibleRoles != newVisibleRoles) {
0311         const bool markCustomizedDetails = (m_node->viewMode() == DolphinView::DetailsView) && !newVisibleRoles.contains(CustomizedDetailsString);
0312         if (markCustomizedDetails) {
0313             // The additional information of the details-view has been modified. Set a marker,
0314             // so that it is allowed to also show no additional information without doing the
0315             // fallback to show the size and date per default.
0316             newVisibleRoles.append(CustomizedDetailsString);
0317         }
0318 
0319         m_node->setVisibleRoles(newVisibleRoles);
0320         update();
0321     }
0322 }
0323 
0324 QList<QByteArray> ViewProperties::visibleRoles() const
0325 {
0326     // The shown additional information is stored for each view-mode separately as
0327     // string with the view-mode as prefix. Example:
0328     //
0329     // AdditionalInfo=Details_size,Details_date,Details_owner,Icons_size
0330     //
0331     // To get the representation as QList<QByteArray>, the current
0332     // view-mode must be checked and the values of this mode added to the list.
0333     //
0334     // For the details-view a special case must be respected: Per default the size
0335     // and date should be shown without creating a .directory file. Only if
0336     // the user explicitly has modified the properties of the details view (marked
0337     // by "CustomizedDetails"), also a details-view with no additional information
0338     // is accepted.
0339 
0340     QList<QByteArray> roles{"text"};
0341 
0342     // Iterate through all stored keys and append all roles that match to
0343     // the current view mode.
0344     const QString prefix = viewModePrefix();
0345     const int prefixLength = prefix.length();
0346 
0347     const QStringList visibleRoles = m_node->visibleRoles();
0348     for (const QString &visibleRole : visibleRoles) {
0349         if (visibleRole.startsWith(prefix)) {
0350             const QByteArray role = visibleRole.right(visibleRole.length() - prefixLength).toLatin1();
0351             if (role != "text") {
0352                 roles.append(role);
0353             }
0354         }
0355     }
0356 
0357     // For the details view the size and date should be shown per default
0358     // until the additional information has been explicitly changed by the user
0359     const bool useDefaultValues = roles.count() == 1 // "text"
0360         && (m_node->viewMode() == DolphinView::DetailsView) && !visibleRoles.contains(CustomizedDetailsString);
0361     if (useDefaultValues) {
0362         roles.append("size");
0363         roles.append("modificationtime");
0364     }
0365 
0366     return roles;
0367 }
0368 
0369 void ViewProperties::setHeaderColumnWidths(const QList<int> &widths)
0370 {
0371     if (m_node->headerColumnWidths() != widths) {
0372         m_node->setHeaderColumnWidths(widths);
0373         update();
0374     }
0375 }
0376 
0377 QList<int> ViewProperties::headerColumnWidths() const
0378 {
0379     return m_node->headerColumnWidths();
0380 }
0381 
0382 void ViewProperties::setDirProperties(const ViewProperties &props)
0383 {
0384     setViewMode(props.viewMode());
0385     setPreviewsShown(props.previewsShown());
0386     setHiddenFilesShown(props.hiddenFilesShown());
0387     setGroupedSorting(props.groupedSorting());
0388     setSortRole(props.sortRole());
0389     setSortOrder(props.sortOrder());
0390     setSortFoldersFirst(props.sortFoldersFirst());
0391     setSortHiddenLast(props.sortHiddenLast());
0392     setVisibleRoles(props.visibleRoles());
0393     setHeaderColumnWidths(props.headerColumnWidths());
0394     m_node->setVersion(props.m_node->version());
0395 }
0396 
0397 void ViewProperties::setAutoSaveEnabled(bool autoSave)
0398 {
0399     m_autoSave = autoSave;
0400 }
0401 
0402 bool ViewProperties::isAutoSaveEnabled() const
0403 {
0404     return m_autoSave;
0405 }
0406 
0407 void ViewProperties::update()
0408 {
0409     m_changedProps = true;
0410     m_node->setTimestamp(QDateTime::currentDateTime());
0411 }
0412 
0413 void ViewProperties::save()
0414 {
0415     qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath;
0416     QDir dir;
0417     dir.mkpath(m_filePath);
0418     m_node->setVersion(CurrentViewPropertiesVersion);
0419     m_node->save();
0420     m_changedProps = false;
0421 }
0422 
0423 bool ViewProperties::exist() const
0424 {
0425     const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
0426     return QFile::exists(file);
0427 }
0428 
0429 QString ViewProperties::destinationDir(const QString &subDir) const
0430 {
0431     QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0432     path.append("/view_properties/").append(subDir);
0433     return path;
0434 }
0435 
0436 QString ViewProperties::viewModePrefix() const
0437 {
0438     QString prefix;
0439 
0440     switch (m_node->viewMode()) {
0441     case DolphinView::IconsView:
0442         prefix = QStringLiteral("Icons_");
0443         break;
0444     case DolphinView::CompactView:
0445         prefix = QStringLiteral("Compact_");
0446         break;
0447     case DolphinView::DetailsView:
0448         prefix = QStringLiteral("Details_");
0449         break;
0450     default:
0451         qCWarning(DolphinDebug) << "Unknown view-mode of the view properties";
0452     }
0453 
0454     return prefix;
0455 }
0456 
0457 void ViewProperties::convertAdditionalInfo()
0458 {
0459     QStringList visibleRoles = m_node->visibleRoles();
0460 
0461     const QStringList additionalInfo = m_node->additionalInfo();
0462     if (!additionalInfo.isEmpty()) {
0463         // Convert the obsolete values like Icons_Size, Details_Date, ...
0464         // to Icons_size, Details_date, ... where the suffix just represents
0465         // the internal role. One special-case must be handled: "LinkDestination"
0466         // has been used for "destination".
0467         visibleRoles.reserve(visibleRoles.count() + additionalInfo.count());
0468         for (const QString &info : additionalInfo) {
0469             QString visibleRole = info;
0470             int index = visibleRole.indexOf('_');
0471             if (index >= 0 && index + 1 < visibleRole.length()) {
0472                 ++index;
0473                 if (visibleRole[index] == QLatin1Char('L')) {
0474                     visibleRole.replace(QLatin1String("LinkDestination"), QLatin1String("destination"));
0475                 } else {
0476                     visibleRole[index] = visibleRole[index].toLower();
0477                 }
0478             }
0479             if (!visibleRoles.contains(visibleRole)) {
0480                 visibleRoles.append(visibleRole);
0481             }
0482         }
0483     }
0484 
0485     m_node->setAdditionalInfo(QStringList());
0486     m_node->setVisibleRoles(visibleRoles);
0487     m_node->setVersion(AdditionalInfoViewPropertiesVersion);
0488     update();
0489 }
0490 
0491 void ViewProperties::convertNameRoleToTextRole()
0492 {
0493     QStringList visibleRoles = m_node->visibleRoles();
0494     for (int i = 0; i < visibleRoles.count(); ++i) {
0495         if (visibleRoles[i].endsWith(QLatin1String("_name"))) {
0496             const int leftLength = visibleRoles[i].length() - 5;
0497             visibleRoles[i] = visibleRoles[i].left(leftLength) + "_text";
0498         }
0499     }
0500 
0501     QString sortRole = m_node->sortRole();
0502     if (sortRole == QLatin1String("name")) {
0503         sortRole = QStringLiteral("text");
0504     }
0505 
0506     m_node->setVisibleRoles(visibleRoles);
0507     m_node->setSortRole(sortRole);
0508     m_node->setVersion(NameRolePropertiesVersion);
0509     update();
0510 }
0511 
0512 void ViewProperties::convertDateRoleToModificationTimeRole()
0513 {
0514     QStringList visibleRoles = m_node->visibleRoles();
0515     for (int i = 0; i < visibleRoles.count(); ++i) {
0516         if (visibleRoles[i].endsWith(QLatin1String("_date"))) {
0517             const int leftLength = visibleRoles[i].length() - 5;
0518             visibleRoles[i] = visibleRoles[i].left(leftLength) + "_modificationtime";
0519         }
0520     }
0521 
0522     QString sortRole = m_node->sortRole();
0523     if (sortRole == QLatin1String("date")) {
0524         sortRole = QStringLiteral("modificationtime");
0525     }
0526 
0527     m_node->setVisibleRoles(visibleRoles);
0528     m_node->setSortRole(sortRole);
0529     m_node->setVersion(DateRolePropertiesVersion);
0530     update();
0531 }
0532 
0533 bool ViewProperties::isPartOfHome(const QString &filePath)
0534 {
0535     // For performance reasons cache the path in a static QString
0536     // (see QDir::homePath() for more details)
0537     static QString homePath;
0538     if (homePath.isEmpty()) {
0539         homePath = QDir::homePath();
0540         Q_ASSERT(!homePath.isEmpty());
0541     }
0542 
0543     return filePath.startsWith(homePath);
0544 }
0545 
0546 QString ViewProperties::directoryHashForUrl(const QUrl &url)
0547 {
0548     const QByteArray hashValue = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Sha1);
0549     QString hashString = hashValue.toBase64();
0550     hashString.replace('/', '-');
0551     return hashString;
0552 }