]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/views/viewproperties.cpp
viewproperties: Fix leaking file descriptors
[dolphin.git] / src / views / viewproperties.cpp
index d65572f21ee1c203bd1e405c3dd5a7fe28bd3517..8bf3b2531290a68d8be66928e310ada3820e5575 100644 (file)
 #include "dolphindebug.h"
 
 #include <QCryptographicHash>
+#include <QTemporaryFile>
 
 #include <KFileItem>
+#include <KFileMetaData/UserMetaData>
 
 namespace
 {
@@ -31,6 +33,74 @@ const char CustomizedDetailsString[] = "CustomizedDetails";
 const char ViewPropertiesFileName[] = ".directory";
 }
 
+ViewPropertySettings *ViewProperties::loadProperties(const QString &folderPath) const
+{
+    const QString settingsFile = folderPath + QDir::separator() + ViewPropertiesFileName;
+
+    KFileMetaData::UserMetaData metadata(folderPath);
+    if (!metadata.isSupported()) {
+        return new ViewPropertySettings(KSharedConfig::openConfig(settingsFile, KConfig::SimpleConfig));
+    }
+
+    std::unique_ptr<QTemporaryFile> tempFile(new QTemporaryFile());
+    tempFile->setAutoRemove(false);
+    if (!tempFile->open()) {
+        qCWarning(DolphinDebug) << "Could not open temp file";
+        return nullptr;
+    }
+    if (QFile::exists(settingsFile)) {
+        // copy settings to tempfile to load them separately
+        QFile::remove(tempFile->fileName());
+        QFile::copy(settingsFile, tempFile->fileName());
+
+        auto config = KConfig(tempFile->fileName(), KConfig::SimpleConfig);
+        // ignore settings that are outside of dolphin scope
+        if (config.hasGroup("Dolphin") || config.hasGroup("Settings")) {
+            const auto groupList = config.groupList();
+            for (const auto &group : groupList) {
+                if (group != QStringLiteral("Dolphin") && group != QStringLiteral("Settings")) {
+                    config.deleteGroup(group);
+                }
+            }
+            return new ViewPropertySettings(KSharedConfig::openConfig(tempFile->fileName(), KConfig::SimpleConfig));
+
+        } else if (!config.groupList().isEmpty()) {
+            // clear temp file content
+            QFile::remove(tempFile->fileName());
+        }
+    }
+
+    // load from metadata
+    const QString viewPropertiesString = metadata.attribute(QStringLiteral("kde.fm.viewproperties#1"));
+    if (viewPropertiesString.isEmpty()) {
+        return nullptr;
+    }
+    // load view properties from xattr to temp file then loads into ViewPropertySettings
+    QFile outputFile(tempFile->fileName());
+    outputFile.open(QIODevice::WriteOnly);
+    outputFile.write(viewPropertiesString.toUtf8());
+    outputFile.close();
+    return new ViewPropertySettings(KSharedConfig::openConfig(tempFile->fileName(), KConfig::SimpleConfig));
+}
+
+ViewPropertySettings *ViewProperties::defaultProperties() const
+{
+    auto props = loadProperties(destinationDir(QStringLiteral("global")));
+    if (props == nullptr) {
+        qCWarning(DolphinDebug) << "Could not load default global viewproperties";
+        QTemporaryFile tempFile;
+        tempFile.setAutoRemove(false);
+        if (!tempFile.open()) {
+            qCWarning(DolphinDebug) << "Could not open temp file";
+            props = new ViewPropertySettings;
+        } else {
+            props = new ViewPropertySettings(KSharedConfig::openConfig(tempFile.fileName(), KConfig::SimpleConfig));
+        }
+    }
+
+    return props;
+}
+
 ViewProperties::ViewProperties(const QUrl &url)
     : m_changedProps(false)
     , m_autoSave(true)
@@ -90,14 +160,22 @@ ViewProperties::ViewProperties(const QUrl &url)
         m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath;
     }
 
-    const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
-    m_node = new ViewPropertySettings(KSharedConfig::openConfig(file));
+    m_node = loadProperties(m_filePath);
+
+    bool useDefaultSettings = useGlobalViewProps ||
+        // If the props timestamp is too old,
+        // use default values instead.
+        (m_node != nullptr && (!useGlobalViewProps || useSearchView || useTrashView || useRecentDocumentsView || useDownloadsView)
+         && m_node->timestamp() < settings->viewPropsTimestamp());
 
-    // If the .directory file does not exist or the timestamp is too old,
-    // use default values instead.
-    const bool useDefaultProps = (!useGlobalViewProps || useSearchView || useTrashView || useRecentDocumentsView || useDownloadsView)
-        && (!QFile::exists(file) || (m_node->timestamp() < settings->viewPropsTimestamp()));
-    if (useDefaultProps) {
+    if (m_node == nullptr) {
+        // no settings found for m_filepath, load defaults
+        m_node = defaultProperties();
+        useDefaultSettings = true;
+    }
+
+    // default values for special directories
+    if (useDefaultSettings) {
         if (useSearchView) {
             const QString path = url.path();
 
@@ -132,14 +210,6 @@ ViewProperties::ViewProperties(const QUrl &url)
                 setSortRole(QByteArrayLiteral("modificationtime"));
             }
         } else {
-            // The global view-properties act as default for directories without
-            // any view-property configuration. Constructing a ViewProperties
-            // instance for an empty QUrl ensures that the global view-properties
-            // are loaded.
-            QUrl emptyUrl;
-            ViewProperties defaultProps(emptyUrl);
-            setDirProperties(defaultProps);
-
             m_changedProps = false;
         }
     }
@@ -172,6 +242,11 @@ ViewProperties::~ViewProperties()
         save();
     }
 
+    if (!m_node->config()->name().endsWith(ViewPropertiesFileName)) {
+        // remove temp file
+        QFile::remove(m_node->config()->name());
+    }
+
     delete m_node;
     m_node = nullptr;
 }
@@ -281,6 +356,19 @@ bool ViewProperties::sortHiddenLast() const
     return m_node->sortHiddenLast();
 }
 
+void ViewProperties::setDynamicViewPassed(bool dynamicViewPassed)
+{
+    if (m_node->dynamicViewPassed() != dynamicViewPassed) {
+        m_node->setDynamicViewPassed(dynamicViewPassed);
+        update();
+    }
+}
+
+bool ViewProperties::dynamicViewPassed() const
+{
+    return m_node->dynamicViewPassed();
+}
+
 void ViewProperties::setVisibleRoles(const QList<QByteArray> &roles)
 {
     if (roles == visibleRoles()) {
@@ -412,17 +500,112 @@ void ViewProperties::update()
 void ViewProperties::save()
 {
     qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath;
+
+    auto cleanDotDirectoryFile = [this]() {
+        const QString settingsFile = m_filePath + QDir::separator() + ViewPropertiesFileName;
+        if (QFile::exists(settingsFile)) {
+            qCDebug(DolphinDebug) << "cleaning .directory" << settingsFile;
+            KConfig cfg(settingsFile, KConfig::OpenFlag::SimpleConfig);
+            const auto groupList = cfg.groupList();
+            for (const auto &group : groupList) {
+                if (group == QStringLiteral("Dolphin") || group == QStringLiteral("Settings")) {
+                    cfg.deleteGroup(group);
+                }
+            }
+            if (cfg.groupList().isEmpty()) {
+                QFile::remove(settingsFile);
+            } else if (cfg.isDirty()) {
+                cfg.sync();
+            }
+        }
+    };
+
+    // ensures the destination dir exists, in case we don't write metadata directly on the folder
+    QDir destinationDir(m_filePath);
+    if (!destinationDir.exists() && !destinationDir.mkpath(m_filePath)) {
+        qCWarning(DolphinDebug) << "Could not create fake directory to store metadata";
+    }
+
+    KFileMetaData::UserMetaData metaData(m_filePath);
+    if (metaData.isSupported()) {
+        const auto metaDataKey = QStringLiteral("kde.fm.viewproperties#1");
+
+        const auto items = m_node->items();
+        const auto defaultConfig = defaultProperties();
+        bool allDefault = true;
+        for (const auto item : items) {
+            if (item->name() == "Timestamp") {
+                continue;
+            }
+            if (item->name() == "Version") {
+                if (m_node->version() != CurrentViewPropertiesVersion) {
+                    allDefault = false;
+                    break;
+                } else {
+                    continue;
+                }
+            }
+            auto defaultItem = defaultConfig->findItem(item->name());
+            if (!defaultItem || defaultItem->property() != item->property()) {
+                allDefault = false;
+                break;
+            }
+        }
+
+        if (allDefault) {
+            if (metaData.hasAttribute(metaDataKey)) {
+                qCDebug(DolphinDebug) << "clearing extended attributes for " << m_filePath;
+                const auto result = metaData.setAttribute(metaDataKey, QString());
+                if (result != KFileMetaData::UserMetaData::NoError) {
+                    qCWarning(DolphinDebug) << "could not clear extended attributes for " << m_filePath << "error:" << result;
+                }
+            }
+            cleanDotDirectoryFile();
+            return;
+        }
+
+        // save config to disk
+        if (!m_node->save()) {
+            qCWarning(DolphinDebug) << "could not save viewproperties" << m_node->config()->name();
+            return;
+        }
+
+        QFile configFile(m_node->config()->name());
+        if (!configFile.open(QIODevice::ReadOnly)) {
+            qCWarning(DolphinDebug) << "Could not open readonly config file" << m_node->config()->name();
+        } else {
+            // load config from disk
+            const QString viewPropertiesString = configFile.readAll();
+
+            // save to xattr
+            const auto result = metaData.setAttribute(metaDataKey, viewPropertiesString);
+            if (result != KFileMetaData::UserMetaData::NoError) {
+                if (result == KFileMetaData::UserMetaData::NoSpace) {
+                    // copy settings to dotDirectory file as fallback
+                    if (!configFile.copy(m_filePath + QDir::separator() + ViewPropertiesFileName)) {
+                        qCWarning(DolphinDebug) << "could not write viewproperties to .directory for dir " << m_filePath;
+                    }
+                    // free the space used by viewproperties from the file metadata
+                    metaData.setAttribute(metaDataKey, "");
+                } else {
+                    qCWarning(DolphinDebug) << "could not save viewproperties to extended attributes for dir " << m_filePath << "error:" << result;
+                }
+                // keep .directory file
+                return;
+            }
+            cleanDotDirectoryFile();
+        }
+
+        m_changedProps = false;
+        return;
+    }
+
     QDir dir;
     dir.mkpath(m_filePath);
     m_node->setVersion(CurrentViewPropertiesVersion);
     m_node->save();
-    m_changedProps = false;
-}
 
-bool ViewProperties::exist() const
-{
-    const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName;
-    return QFile::exists(file);
+    m_changedProps = false;
 }
 
 QString ViewProperties::destinationDir(const QString &subDir) const