| 1 | /* |
| 2 | This software is a contribution of the LiMux project of the city of Munich. |
| 3 | SPDX-FileCopyrightText: 2021 Robert Hoffmann <robert@roberthoffmann.de> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 6 | */ |
| 7 | |
| 8 | #include "knetworkmounts.h" |
| 9 | #include "knetworkmounts_p.h" |
| 10 | |
| 11 | #include <QCoreApplication> |
| 12 | #include <QGlobalStatic> |
| 13 | |
| 14 | #include <QDebug> |
| 15 | #include <QDir> |
| 16 | #include <QStandardPaths> |
| 17 | |
| 18 | KNetworkMountsPrivate::KNetworkMountsPrivate(KNetworkMounts *qq) |
| 19 | : q(qq) |
| 20 | { |
| 21 | } |
| 22 | |
| 23 | KNetworkMounts *KNetworkMounts::self() |
| 24 | { |
| 25 | static KNetworkMounts s_self; |
| 26 | return &s_self; |
| 27 | } |
| 28 | |
| 29 | KNetworkMounts::KNetworkMounts() |
| 30 | : d(new KNetworkMountsPrivate(this)) |
| 31 | { |
| 32 | const QString configFileName = QStringLiteral("%1/network_mounts" ).arg(a: QStandardPaths::writableLocation(type: QStandardPaths::ConfigLocation)); |
| 33 | d->m_settings = new QSettings(configFileName, QSettings::Format::IniFormat, this); |
| 34 | |
| 35 | for (const auto type : {KNetworkMounts::NfsPaths, KNetworkMounts::SmbPaths, KNetworkMounts::SymlinkDirectory, KNetworkMounts::SymlinkToNetworkMount}) { |
| 36 | QString typeStr = enumToString(type); |
| 37 | QStringList slowPaths = d->m_settings->value(key: typeStr, defaultValue: QStringList()).toStringList(); |
| 38 | |
| 39 | if (ensureTrailingSlashes(paths: &slowPaths)) { |
| 40 | d->m_settings->setValue(key: typeStr, value: slowPaths); |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | KNetworkMounts::~KNetworkMounts() |
| 46 | { |
| 47 | } |
| 48 | |
| 49 | bool KNetworkMounts::isSlowPath(const QString &path, KNetworkMountsType type) |
| 50 | { |
| 51 | return !getMatchingPath(path: path, slowPaths: paths(type)).isEmpty(); |
| 52 | } |
| 53 | |
| 54 | bool KNetworkMounts::isOptionEnabledForPath(const QString &path, KNetworkMountOption option) |
| 55 | { |
| 56 | if (!isEnabled()) { |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | if (!isSlowPath(path)) { |
| 61 | return false; |
| 62 | } |
| 63 | |
| 64 | return isOptionEnabled(option, defaultValue: true); |
| 65 | } |
| 66 | |
| 67 | bool KNetworkMounts::isEnabled() const |
| 68 | { |
| 69 | return d->m_settings->value(QStringLiteral("EnableOptimizations" ), defaultValue: false).toBool(); |
| 70 | } |
| 71 | |
| 72 | void KNetworkMounts::setEnabled(const bool value) |
| 73 | { |
| 74 | d->m_settings->setValue(QStringLiteral("EnableOptimizations" ), value); |
| 75 | } |
| 76 | |
| 77 | bool KNetworkMounts::isOptionEnabled(const KNetworkMountOption option, const bool defaultValue) const |
| 78 | { |
| 79 | return d->m_settings->value(key: enumToString(type: option), defaultValue).toBool(); |
| 80 | } |
| 81 | |
| 82 | void KNetworkMounts::setOption(const KNetworkMountOption option, const bool value) |
| 83 | { |
| 84 | d->m_settings->setValue(key: enumToString(type: option), value); |
| 85 | } |
| 86 | |
| 87 | QStringList KNetworkMounts::paths(KNetworkMountsType type) const |
| 88 | { |
| 89 | if (type == Any) { |
| 90 | QStringList paths; |
| 91 | paths.reserve(asize: 4); |
| 92 | for (const auto networkMountType : |
| 93 | {KNetworkMounts::NfsPaths, KNetworkMounts::SmbPaths, KNetworkMounts::SymlinkDirectory, KNetworkMounts::SymlinkToNetworkMount}) { |
| 94 | paths.append(other: d->m_settings->value(key: enumToString(type: networkMountType), defaultValue: QStringList()).toStringList()); |
| 95 | } |
| 96 | return paths; |
| 97 | } else { |
| 98 | return d->m_settings->value(key: enumToString(type), defaultValue: QStringList()).toStringList(); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | void KNetworkMounts::setPaths(const QStringList &paths, KNetworkMountsType type) |
| 103 | { |
| 104 | QStringList _paths(paths); |
| 105 | ensureTrailingSlashes(paths: &_paths); |
| 106 | d->m_settings->setValue(key: enumToString(type), value: _paths); |
| 107 | } |
| 108 | |
| 109 | void KNetworkMounts::addPath(const QString &path, KNetworkMountsType type) |
| 110 | { |
| 111 | QString _path(path); |
| 112 | ensureTrailingSlash(path: &_path); |
| 113 | QStringList newPaths = paths(type); |
| 114 | newPaths.append(t: _path); |
| 115 | d->m_settings->setValue(key: enumToString(type), value: newPaths); |
| 116 | } |
| 117 | |
| 118 | typedef QHash<QString /*symlink*/, QString /*canonical path*/> symlinkCanonicalPathHash; |
| 119 | Q_GLOBAL_STATIC(symlinkCanonicalPathHash, s_canonicalLinkSpacePaths) |
| 120 | |
| 121 | QString KNetworkMounts::canonicalSymlinkPath(const QString &path) |
| 122 | { |
| 123 | bool useCache = isOptionEnabled(option: KNetworkMountOption::SymlinkPathsUseCache, defaultValue: true); |
| 124 | if (useCache) { |
| 125 | const QString resolved = s_canonicalLinkSpacePaths->value(key: path); |
| 126 | |
| 127 | if (!resolved.isEmpty()) { |
| 128 | return resolved; |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | QString symlinkPath = getMatchingPath(path: path, slowPaths: paths(type: KNetworkMountsType::SymlinkToNetworkMount)); |
| 133 | if (!symlinkPath.isEmpty()) { |
| 134 | // remove trailing slash |
| 135 | symlinkPath.chop(n: 1); |
| 136 | |
| 137 | QFileInfo link(symlinkPath); |
| 138 | QString linkPath(path); |
| 139 | QString target = link.symLinkTarget(); |
| 140 | |
| 141 | if (target.isEmpty()) { |
| 142 | // not a symlink |
| 143 | if (useCache) { |
| 144 | s_canonicalLinkSpacePaths->insert(key: path, value: path); |
| 145 | } |
| 146 | return path; |
| 147 | } else { |
| 148 | // symlink |
| 149 | // replace only the first occurence of symlinkPath in linkPath with the link target |
| 150 | // linkPath.startsWith(symlinkPath) because of getMatchingPath |
| 151 | linkPath.replace(i: 0, len: symlinkPath.size(), after: target); |
| 152 | |
| 153 | if (useCache) { |
| 154 | s_canonicalLinkSpacePaths->insert(key: path, value: linkPath); |
| 155 | } |
| 156 | return linkPath; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | QString linkSpacePath = getMatchingPath(path: path, slowPaths: paths(type: KNetworkMountsType::SymlinkDirectory)); |
| 161 | if (!linkSpacePath.isEmpty()) { |
| 162 | QString _path = path; |
| 163 | if (!_path.endsWith(c: QLatin1Char('/'))) { |
| 164 | _path.append(c: QLatin1Char('/')); |
| 165 | } |
| 166 | |
| 167 | if (_path == linkSpacePath) { |
| 168 | if (useCache) { |
| 169 | s_canonicalLinkSpacePaths->insert(key: path, value: path); |
| 170 | } |
| 171 | return path; |
| 172 | } |
| 173 | |
| 174 | // search for symlink, linkSpacePath always ends with '/' |
| 175 | int linkIndex = path.indexOf(ch: QLatin1Char('/'), from: linkSpacePath.length()); |
| 176 | const QString symlink = path.left(n: linkIndex); |
| 177 | |
| 178 | if (useCache && s_canonicalLinkSpacePaths->contains(key: symlink)) { |
| 179 | QString linkPath(path); |
| 180 | // replace only the first occurence of symlink in linkPath |
| 181 | linkPath.replace(i: 0, len: symlink.size(), after: s_canonicalLinkSpacePaths->value(key: symlink)); |
| 182 | s_canonicalLinkSpacePaths->insert(key: path, value: linkPath); |
| 183 | return linkPath; |
| 184 | } else { |
| 185 | QFileInfo link(symlink); |
| 186 | |
| 187 | if (link.isSymLink()) { |
| 188 | QString linkPath(path); |
| 189 | // replace only the first occurence of symlink in linkPath |
| 190 | linkPath.replace(i: 0, len: symlink.size(), after: link.symLinkTarget()); |
| 191 | |
| 192 | if (useCache) { |
| 193 | s_canonicalLinkSpacePaths->insert(key: path, value: linkPath); |
| 194 | } |
| 195 | return linkPath; |
| 196 | } else { |
| 197 | if (useCache) { |
| 198 | s_canonicalLinkSpacePaths->insert(key: path, value: path); |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | return path; |
| 205 | } |
| 206 | |
| 207 | void KNetworkMounts::clearCache() |
| 208 | { |
| 209 | if (s_canonicalLinkSpacePaths.exists()) { |
| 210 | s_canonicalLinkSpacePaths->clear(); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | void KNetworkMounts::sync() |
| 215 | { |
| 216 | d->m_settings->sync(); |
| 217 | } |
| 218 | |
| 219 | #include "moc_knetworkmounts.cpp" |
| 220 | |