File indexing completed on 2024-05-12 05:36:50
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "PagesModel.h" 0008 0009 #include <QDebug> 0010 #include <QDir> 0011 #include <QStandardPaths> 0012 0013 #include <KConfig> 0014 #include <KConfigGroup> 0015 #include <KNSCore/Entry> 0016 0017 #include "PageDataObject.h" 0018 0019 PagesModel::PagesModel(QObject *parent) 0020 : QAbstractListModel(parent) 0021 { 0022 } 0023 0024 QHash<int, QByteArray> PagesModel::roleNames() const 0025 { 0026 static QHash<int, QByteArray> roles{{TitleRole, "title"}, 0027 {DataRole, "data"}, 0028 {IconRole, "icon"}, 0029 {FileNameRole, "fileName"}, 0030 {HiddenRole, "hidden"}, 0031 {FilesWriteableRole, "filesWriteable"}}; 0032 return roles; 0033 } 0034 0035 int PagesModel::rowCount(const QModelIndex &parent) const 0036 { 0037 if (parent.isValid()) { 0038 return 0; 0039 } 0040 return m_pages.count(); 0041 } 0042 0043 QVariant PagesModel::data(const QModelIndex &index, int role) const 0044 { 0045 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent)) { 0046 return QVariant{}; 0047 } 0048 0049 auto data = m_pages.at(index.row()); 0050 switch (role) { 0051 case TitleRole: 0052 return data->value(QStringLiteral("title")); 0053 case DataRole: 0054 return QVariant::fromValue(data); 0055 case IconRole: 0056 return data->value(QStringLiteral("icon")); 0057 case FileNameRole: 0058 return data->fileName(); 0059 case HiddenRole: 0060 return m_hiddenPages.contains(data->fileName()); 0061 case FilesWriteableRole: 0062 return m_writeableCache[data->fileName()]; 0063 default: 0064 return QVariant{}; 0065 } 0066 } 0067 0068 void PagesModel::classBegin() 0069 { 0070 } 0071 0072 void PagesModel::componentComplete() 0073 { 0074 QHash<QString, QString> files; 0075 0076 const auto directories = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); 0077 for (const auto &directory : directories) { 0078 QDir dir{directory}; 0079 const auto entries = dir.entryInfoList({QStringLiteral("*.page")}, QDir::NoDotAndDotDot | QDir::Files); 0080 for (const auto &entry : entries) { 0081 const FilesWriteableStates entryState = entry.isWritable() ? AllWriteable : NotWriteable; 0082 if (!files.contains(entry.fileName())) { 0083 files.insert(entry.fileName(), dir.relativeFilePath(entry.fileName())); 0084 m_writeableCache.insert(entry.fileName(), entryState); 0085 } else { 0086 FilesWriteableStates &state = m_writeableCache[entry.fileName()]; 0087 if ((state == NotWriteable && entryState == AllWriteable) || (state == AllWriteable && entryState == NotWriteable)) { 0088 state = LocalChanges; 0089 } 0090 } 0091 } 0092 } 0093 0094 beginResetModel(); 0095 for (auto itr = files.begin(); itr != files.end(); ++itr) { 0096 auto config = KSharedConfig::openConfig(itr.value(), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); 0097 0098 auto page = new PageDataObject{config, this}; 0099 page->load(*config, QStringLiteral("page")); 0100 0101 connect(page, &PageDataObject::saved, this, [this, page]() { 0102 if (m_writeableCache[page->fileName()] == NotWriteable) { 0103 m_writeableCache[page->fileName()] = LocalChanges; 0104 auto i = m_pages.indexOf(page); 0105 Q_EMIT dataChanged(index(i), index(i), {FilesWriteableRole}); 0106 } 0107 }); 0108 connect(page, &PageDataObject::valueChanged, this, [this, page] { 0109 auto i = m_pages.indexOf(page); 0110 Q_EMIT dataChanged(index(i), index(i), {TitleRole, IconRole}); 0111 }); 0112 connect(page, &PageDataObject::loaded, this, [this, page] { 0113 auto i = m_pages.indexOf(page); 0114 Q_EMIT dataChanged(index(i), index(i), {TitleRole, IconRole}); 0115 }); 0116 m_pages.append(page); 0117 } 0118 sort(); 0119 endResetModel(); 0120 } 0121 0122 void PagesModel::sort(int column, Qt::SortOrder order) 0123 { 0124 Q_UNUSED(column) 0125 Q_UNUSED(order) 0126 0127 if (m_pageOrder.isEmpty()) { 0128 return; 0129 } 0130 0131 Q_EMIT layoutAboutToBeChanged({QPersistentModelIndex()}, QAbstractItemModel::VerticalSortHint); 0132 auto last = std::stable_partition(m_pages.begin(), m_pages.end(), [this](PageDataObject *page) { 0133 return m_pageOrder.contains(page->fileName()); 0134 }); 0135 std::sort(m_pages.begin(), last, [this](PageDataObject *left, PageDataObject *right) { 0136 return m_pageOrder.indexOf(left->fileName()) < m_pageOrder.indexOf(right->fileName()); 0137 }); 0138 std::transform(last, m_pages.end(), std::back_inserter(m_pageOrder), [](PageDataObject *page) { 0139 return page->fileName(); 0140 }); 0141 if (last != m_pages.end()) { 0142 Q_EMIT pageOrderChanged(); 0143 } 0144 changePersistentIndex(QModelIndex(), QModelIndex()); 0145 Q_EMIT layoutChanged(); 0146 } 0147 0148 PageDataObject *PagesModel::addPage(const QString &baseName, const QVariantMap &properties) 0149 { 0150 int counter = 0; 0151 const QString extension = QStringLiteral(".page"); 0152 QString fileName = baseName + extension; 0153 while (m_writeableCache.contains(fileName)) { 0154 fileName = baseName + QString::number(++counter) + extension; 0155 } 0156 0157 KSharedConfig::Ptr config = KSharedConfig::openConfig(fileName, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); 0158 auto page = new PageDataObject(config, this); 0159 page->load(*config, QStringLiteral("page")); 0160 0161 for (auto itr = properties.begin(); itr != properties.end(); ++itr) { 0162 page->insert(itr.key(), itr.value()); 0163 } 0164 m_writeableCache[fileName] = AllWriteable; 0165 0166 connect(page, &PageDataObject::valueChanged, this, [this, page]() { 0167 auto i = m_pages.indexOf(page); 0168 Q_EMIT dataChanged(index(i), index(i), {TitleRole, IconRole, FilesWriteableRole}); 0169 }); 0170 0171 beginInsertRows(QModelIndex{}, m_pages.size(), m_pages.size()); 0172 m_pages.append(page); 0173 m_pageOrder.append(fileName); 0174 Q_EMIT pageOrderChanged(); 0175 endInsertRows(); 0176 0177 connect(page, &PageDataObject::loaded, this, [this, page] { 0178 auto i = m_pages.indexOf(page); 0179 Q_EMIT dataChanged(index(i), index(i), {TitleRole, IconRole}); 0180 }); 0181 0182 return page; 0183 } 0184 0185 QStringList PagesModel::pageOrder() const 0186 { 0187 return m_pageOrder; 0188 } 0189 0190 void PagesModel::setPageOrder(const QStringList &pageOrder) 0191 { 0192 if (pageOrder != m_pageOrder) { 0193 m_pageOrder = pageOrder; 0194 Q_EMIT pageOrderChanged(); 0195 sort(); 0196 } 0197 } 0198 0199 QStringList PagesModel::hiddenPages() const 0200 { 0201 return m_hiddenPages; 0202 } 0203 0204 void PagesModel::setHiddenPages(const QStringList &hiddenPages) 0205 { 0206 if (hiddenPages != m_hiddenPages) { 0207 m_hiddenPages = hiddenPages; 0208 Q_EMIT hiddenPagesChanged(); 0209 Q_EMIT dataChanged(index(0, 0), index(m_pages.count() - 1, 0), {HiddenRole}); 0210 } 0211 } 0212 0213 PageDataObject *PagesModel::importPage(const QUrl &file) 0214 { 0215 const QString fileName = file.fileName(); 0216 if (!fileName.endsWith(QLatin1String(".page"))) { 0217 return nullptr; 0218 } 0219 // Let addPage first figure out the file name to avoid duplicates and then reload the page after we placed the file 0220 auto page = addPage(fileName); 0221 const auto destination = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QChar('/') + page->fileName(); 0222 QFile::copy(file.toLocalFile(), destination); 0223 page->resetPage(); 0224 return page; 0225 } 0226 0227 void PagesModel::removeLocalPageFiles(const QString &fileName) 0228 { 0229 auto it = std::find_if(m_pages.begin(), m_pages.end(), [&](PageDataObject *page) { 0230 return page->fileName() == fileName; 0231 }); 0232 if (it == m_pages.end()) { 0233 return; 0234 } 0235 if (m_writeableCache[fileName] == NotWriteable) { 0236 return; 0237 } 0238 PageDataObject *const page = *it; 0239 const QStringList files = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, page->fileName(), QStandardPaths::LocateFile); 0240 for (const auto &file : files) { 0241 if (QFileInfo(file).isWritable()) { 0242 QFile::remove(file); 0243 } 0244 } 0245 int row = it - m_pages.begin(); 0246 if (m_writeableCache[fileName] == AllWriteable) { 0247 beginRemoveRows(QModelIndex(), row, row); 0248 m_pages.erase(it); 0249 m_hiddenPages.removeAll(fileName); 0250 m_writeableCache.remove(fileName); 0251 endRemoveRows(); 0252 } else { 0253 page->resetPage(); 0254 m_writeableCache[fileName] = NotWriteable; 0255 Q_EMIT dataChanged(index(row, 0), index(row, 0), {TitleRole, IconRole, DataRole, FilesWriteableRole}); 0256 } 0257 } 0258 0259 void PagesModel::ghnsEntryStatusChanged(const KNSCore::Entry &entry) 0260 { 0261 if (!entry.isValid()) { 0262 return; 0263 } 0264 0265 if (entry.status() == KNSCore::Entry::Installed) { 0266 const auto files = entry.installedFiles(); 0267 for (const auto &file : files) { 0268 const QString fileName = QUrl::fromLocalFile(file).fileName(); 0269 if (fileName.endsWith(QLatin1String(".page"))) { 0270 auto it = std::find_if(m_pages.begin(), m_pages.end(), [&](PageDataObject *page) { 0271 return page->fileName() == fileName; 0272 }); 0273 if (it != m_pages.end()) { 0274 // User selected to overwrite the existing file in the kns dialog 0275 (*it)->resetPage(); 0276 if (m_writeableCache[fileName] == NotWriteable) { 0277 m_writeableCache[fileName] = LocalChanges; 0278 } 0279 } else { 0280 addPage(fileName.chopped(strlen(".page"))); 0281 } 0282 } 0283 } 0284 } else if (entry.status() == KNSCore::Entry::Deleted) { 0285 const auto files = entry.uninstalledFiles(); 0286 for (const auto &file : files) { 0287 const QString fileName = QUrl::fromLocalFile(file).fileName(); 0288 if (fileName.endsWith(QLatin1String(".page"))) { 0289 removeLocalPageFiles(fileName); 0290 } 0291 } 0292 } 0293 } 0294 0295 #include "moc_PagesModel.cpp"