feat(Storybook): Track pages dir in PagesModel
This commit is contained in:
parent
5d17a6ba7c
commit
f48493a5e4
|
@ -1,43 +1,148 @@
|
||||||
#include "pagesmodel.h"
|
#include "pagesmodel.h"
|
||||||
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const auto categoryUncategorized QStringLiteral("Uncategorized");
|
const auto categoryUncategorized QStringLiteral("Uncategorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
PagesModel::PagesModel(const QString &path, QObject *parent)
|
PagesModel::PagesModel(const QString &path, QObject *parent)
|
||||||
: QAbstractListModel{parent}
|
: QAbstractListModel{parent}, m_path{path},
|
||||||
|
fsWatcher(new QFileSystemWatcher(this))
|
||||||
{
|
{
|
||||||
QDir dir(path);
|
m_items = load();
|
||||||
dir.setFilter(QDir::Files);
|
readMetadata(m_items);
|
||||||
|
|
||||||
|
fsWatcher->addPath(path);
|
||||||
|
|
||||||
|
connect(fsWatcher, &QFileSystemWatcher::directoryChanged,
|
||||||
|
this, &PagesModel::reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<PagesModelItem> PagesModel::load() {
|
||||||
static QRegularExpression fileNameRegex(
|
static QRegularExpression fileNameRegex(
|
||||||
QRegularExpression::anchoredPattern("(.*)Page\\.qml"));
|
QRegularExpression::anchoredPattern("(.*)Page\\.qml"));
|
||||||
static QRegularExpression categoryRegex(
|
|
||||||
"^//(\\s)*category:(.+)$", QRegularExpression::MultilineOption);
|
QDir dir(m_path);
|
||||||
|
dir.setFilter(QDir::Files);
|
||||||
|
|
||||||
const QFileInfoList files = dir.entryInfoList();
|
const QFileInfoList files = dir.entryInfoList();
|
||||||
|
QList<PagesModelItem> items;
|
||||||
|
|
||||||
std::for_each(files.begin(), files.end(), [this] (auto &fileInfo) {
|
std::for_each(files.begin(), files.end(), [this, &items] (auto &fileInfo) {
|
||||||
QString fileName = fileInfo.fileName();
|
QString fileName = fileInfo.fileName();
|
||||||
QRegularExpressionMatch fileNameMatch = fileNameRegex.match(fileName);
|
QRegularExpressionMatch fileNameMatch = fileNameRegex.match(fileName);
|
||||||
|
|
||||||
if (!fileNameMatch.hasMatch())
|
if (!fileNameMatch.hasMatch())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QFile file(fileInfo.filePath());
|
PagesModelItem item;
|
||||||
file.open(QIODevice::ReadOnly);
|
item.path = fileInfo.filePath();
|
||||||
QByteArray content = file.readAll();
|
item.title = fileNameMatch.captured(1);
|
||||||
|
item.lastModified = fileInfo.lastModified();
|
||||||
|
|
||||||
QRegularExpressionMatch categoryMatch = categoryRegex.match(content);
|
items << item;
|
||||||
QString category = categoryMatch.hasMatch()
|
|
||||||
? categoryMatch.captured(2).trimmed() : categoryUncategorized;
|
|
||||||
|
|
||||||
QString title = fileNameMatch.captured(1);
|
|
||||||
m_items << PagesModelItem { title, category };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PagesModel::readMetadata(PagesModelItem& item) {
|
||||||
|
static QRegularExpression categoryRegex(
|
||||||
|
"^//(\\s)*category:(.+)$", QRegularExpression::MultilineOption);
|
||||||
|
|
||||||
|
QFile file(item.path);
|
||||||
|
file.open(QIODevice::ReadOnly);
|
||||||
|
QByteArray content = file.readAll();
|
||||||
|
|
||||||
|
QRegularExpressionMatch categoryMatch = categoryRegex.match(content);
|
||||||
|
QString category = categoryMatch.hasMatch()
|
||||||
|
? categoryMatch.captured(2).trimmed() : categoryUncategorized;
|
||||||
|
|
||||||
|
item.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PagesModel::readMetadata(QList<PagesModelItem> &items) {
|
||||||
|
std::for_each(items.begin(), items.end(), [](auto&item) {
|
||||||
|
readMetadata(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PagesModel::reload() {
|
||||||
|
QList<PagesModelItem> currentItems = load();
|
||||||
|
std::map<QString, PagesModelItem> mapping;
|
||||||
|
|
||||||
|
for (const PagesModelItem &item : qAsConst(m_items))
|
||||||
|
mapping[item.title] = item;
|
||||||
|
|
||||||
|
std::vector<PagesModelItem> newItems;
|
||||||
|
std::vector<PagesModelItem> changedItems;
|
||||||
|
std::vector<PagesModelItem> removedItems;
|
||||||
|
|
||||||
|
for (const PagesModelItem &item : qAsConst(currentItems)) {
|
||||||
|
auto it = mapping.find(item.title);
|
||||||
|
|
||||||
|
if (it == mapping.end()) {
|
||||||
|
newItems.push_back(item);
|
||||||
|
} else {
|
||||||
|
if (item.lastModified != it->second.lastModified)
|
||||||
|
changedItems.push_back(item);
|
||||||
|
|
||||||
|
mapping.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [key, value] : mapping)
|
||||||
|
removedItems.push_back(value);
|
||||||
|
|
||||||
|
for (auto& item : removedItems) {
|
||||||
|
|
||||||
|
auto it = std::find_if(m_items.begin(), m_items.end(), [&item](auto& it){
|
||||||
|
return it.title == item.title;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto index = std::distance(m_items.begin(), it);
|
||||||
|
|
||||||
|
beginRemoveRows(QModelIndex{}, index, index);
|
||||||
|
m_items.removeAt(index);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newItems.size()) {
|
||||||
|
beginInsertRows(QModelIndex{}, rowCount(), rowCount() + newItems.size() - 1);
|
||||||
|
|
||||||
|
for (auto& item : newItems) {
|
||||||
|
readMetadata(item);
|
||||||
|
m_items << item;
|
||||||
|
}
|
||||||
|
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& item : changedItems) {
|
||||||
|
auto it = std::find_if(m_items.begin(), m_items.end(), [&item](auto& it){
|
||||||
|
return it.title == item.title;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto index = std::distance(m_items.begin(), it);
|
||||||
|
const auto& previous = *it;
|
||||||
|
readMetadata(item);
|
||||||
|
|
||||||
|
if (previous.category != item.category) {
|
||||||
|
// For simplicity category change is handled by removing and
|
||||||
|
// adding item. In the future it can be changed to regular dataChanged
|
||||||
|
// event and handled properly in upstream models like SectionSDecoratorModel.
|
||||||
|
beginRemoveRows(QModelIndex{}, index, index);
|
||||||
|
m_items.removeAt(index);
|
||||||
|
endRemoveRows();
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex{}, rowCount(), rowCount());
|
||||||
|
m_items << item;
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> PagesModel::roleNames() const
|
QHash<int, QByteArray> PagesModel::roleNames() const
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
class QFileSystemWatcher;
|
||||||
|
|
||||||
struct PagesModelItem {
|
struct PagesModelItem {
|
||||||
|
QString path;
|
||||||
|
QDateTime lastModified;
|
||||||
QString title;
|
QString title;
|
||||||
QString category;
|
QString category;
|
||||||
};
|
};
|
||||||
|
@ -22,6 +27,14 @@ public:
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
void reload();
|
||||||
private:
|
private:
|
||||||
|
QList<PagesModelItem> load();
|
||||||
|
|
||||||
|
static void readMetadata(PagesModelItem &item);
|
||||||
|
static void readMetadata(QList<PagesModelItem> &items);
|
||||||
|
|
||||||
|
QString m_path;
|
||||||
QList<PagesModelItem> m_items;
|
QList<PagesModelItem> m_items;
|
||||||
|
QFileSystemWatcher* fsWatcher;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue