diff --git a/.clang-tidy b/.clang-tidy index 2445ece..4757d19 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1 +1 @@ -Checks: '*,clang-analyzer-*,-llvmlibc-*,-fuchsia-*,-altera-*,-abseil-*,-android-*,-modernize-use-trailing-return-type,-readability-identifier-length,-*-readability-todo,-*-magic-numbers,-readability-function-cognitive-complexity,-*-easily-swappable-parameters,-*-pro-type-reinterpret-cast,-*-owning-memory' \ No newline at end of file +Checks: '*,clang-analyzer-*,-llvmlibc-*,-fuchsia-*,-altera-*,-abseil-*,-android-*,-modernize-use-trailing-return-type,-readability-identifier-length,-*-readability-todo,-*-magic-numbers,-readability-function-cognitive-complexity,-*-easily-swappable-parameters,-*-pro-type-reinterpret-cast,-*-owning-memory,-concurrency-mt-unsafe,-cert-env33-c' \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c72278..f81e25d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_AUTOUIC ON) qt6_wrap_ui(UI_HEADERS src/mainwindow.ui) -add_executable(${PROJECT_NAME} src/main.cc src/mainwindow.cc ${UI_HEADERS}) +add_executable(${PROJECT_NAME} src/main.cc src/mainwindow.cc src/vault.cc ${UI_HEADERS}) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/common.h b/src/common.h index 63be8e9..f448e9d 100644 --- a/src/common.h +++ b/src/common.h @@ -1,6 +1,7 @@ #pragma once #include +#include #define ASSERT(cond) \ if (!(cond)) { \ @@ -19,4 +20,9 @@ using u64 = uint64_t; static_assert(sizeof(float) * 8 == 32); using f32 = float; static_assert(sizeof(double) * 8 == 64); -using f64 = double; \ No newline at end of file +using f64 = double; + +inline std::string path_to_filename(const std::string &path) { + u64 pos = path.find_last_of("/\\"); + return (pos == std::string::npos) ? path : path.substr(pos + 1); +} \ No newline at end of file diff --git a/src/mainwindow.cc b/src/mainwindow.cc index aa99172..d4da126 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -1,9 +1,8 @@ #include "mainwindow.h" -#include "vault.h" -#include #include -#include -#include +#include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { @@ -17,16 +16,8 @@ MainWindow::MainWindow(QWidget *parent) return; } - { - std::ofstream file(path.toStdString(), std::ios::binary); - Vault::write_header(file); - - Vault::write_file(file, "hello.txt", "Hello, World!"); - Vault::write_file(file, "test.txt", "test test test"); - } - - m_vault_path = path.toStdString(); - reload_vault(); + m_vault = Vault(path.toStdString()); + reload_fs_tree(); }); connect(ui->actionOpen, &QAction::triggered, this, [this]() { @@ -37,52 +28,105 @@ MainWindow::MainWindow(QWidget *parent) return; } - m_vault_path = path.toStdString(); - reload_vault(); + m_vault = Vault(path.toStdString()); + reload_fs_tree(); }); connect(ui->fsTreeWidget, &QTreeWidget::itemClicked, [this](QTreeWidgetItem *item, int column) { preview_file(item->text(column).toStdString()); }); + + connect(ui->fsTreeWidget, &QTreeWidget::customContextMenuRequested, this, + &MainWindow::file_context_menu); + + connect(ui->actionAddFile, &QAction::triggered, this, [this]() { + if (!m_vault) { + return; + } + + QString path = QFileDialog::getOpenFileName(this, "Choose a file to add"); + if (path.isEmpty()) { + return; + } + + std::ifstream file(path.toStdString(), std::ios::binary); + + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + m_vault->write_file(path_to_filename(path.toStdString()), content); + + reload_fs_tree(); + }); + + connect(ui->actionCreateFile, &QAction::triggered, this, [this]() { + if (!m_vault) { + return; + } + + QString path = + QInputDialog::getText(this, "Create File", "Where to create the file"); + if (path.isEmpty()) { + return; + } + + m_vault->write_file(path.toStdString(), ""); + + reload_fs_tree(); + }); } -void MainWindow::reload_vault() { +void MainWindow::reload_fs_tree() { ui->fsTreeWidget->clear(); - std::ifstream file(m_vault_path, std::ios::binary); - Vault::verify_header(file); - - while (true) { - auto header = Vault::read_file_header(file); - if (!header) { - break; - } - file.seekg(static_cast(header->size), std::ios::cur); - + auto headers = m_vault->read_file_headers(); + for (const auto &header : headers) { auto *item = new QTreeWidgetItem(ui->fsTreeWidget); - item->setText(0, QString::fromStdString(header->name)); + item->setText(0, QString::fromStdString(header.name)); } } void MainWindow::preview_file(const std::string &filename) { - std::ifstream file(m_vault_path, std::ios::binary); - Vault::verify_header(file); + auto content = m_vault->read_file(filename); + if (content) { + ui->filePreview->setText(QString::fromStdString(content.value())); + } else { + qWarning() << "File to preview not found"; + } +} - while (true) { - auto header = Vault::read_file_header(file); - if (!header) { - break; +void MainWindow::edit_file(const std::string &filename) { + auto content = m_vault->read_file(filename); + if (content) { + QTemporaryFile temp_file; + ASSERT(temp_file.open()); + { + QTextStream out(&temp_file); + out << QString::fromStdString(content.value()); } + temp_file.flush(); - if (header->name == filename) { - std::string content(header->size, '\0'); - file.read(content.data(), static_cast(header->size)); - ui->filePreview->setText(QString::fromStdString(content)); - return; - } - file.seekg(static_cast(header->size), std::ios::cur); + // TODO: xdg-open or something + std::system(("kwrite " + temp_file.fileName()).toStdString().c_str()); + // TODO: write back + } else { + qWarning() << "File to edit not found"; + } +} + +void MainWindow::file_context_menu(const QPoint &pos) { + QTreeWidgetItem *item = ui->fsTreeWidget->itemAt(pos); + if (item == nullptr) { + return; } - ASSERT(false); + QMenu menu(this); + + QAction *edit_action = menu.addAction( + style()->standardIcon(QStyle::SP_FileDialogDetailedView), "Edit"); + connect(edit_action, &QAction::triggered, this, + [this, item]() { edit_file(item->text(0).toStdString()); }); + + menu.exec(ui->fsTreeWidget->mapToGlobal(pos)); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 3686516..f058d29 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,6 +1,7 @@ #pragma once #include "ui_mainwindow.h" +#include "vault.h" #include class MainWindow : public QMainWindow { @@ -12,8 +13,10 @@ public: private: Ui::MainWindow *ui; - std::string m_vault_path; + std::optional m_vault; - void reload_vault(); + void reload_fs_tree(); void preview_file(const std::string &filename); + void edit_file(const std::string &filename); + void file_context_menu(const QPoint &pos); }; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 4501feb..3a4a89a 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -11,12 +11,15 @@ - MainWindow + dull + + Qt::ContextMenuPolicy::CustomContextMenu + Name @@ -25,8 +28,7 @@ - - + @@ -46,7 +48,15 @@ + + + Files + + + + + @@ -58,6 +68,16 @@ Open + + + Add + + + + + Create + + diff --git a/src/vault.cc b/src/vault.cc new file mode 100644 index 0000000..6248084 --- /dev/null +++ b/src/vault.cc @@ -0,0 +1,92 @@ +#include "vault.h" +#include "common.h" +#include +#include + +Vault::Vault(std::string path) : m_path(std::move(path)) { + m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary); + if (!m_file.is_open()) { + std::ofstream create(m_path, std::ios::binary); + create.write("DULL", 4); + create.write(reinterpret_cast(&VERSION), sizeof(VERSION)); + create.close(); + m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary); + } + + ASSERT(m_file.good()); + + std::array header{}; + ASSERT(static_cast(m_file.read(header.data(), header.size()))); + ASSERT(std::string_view(header.data(), header.size()) == "DULL"); + + std::int16_t version = 0; + ASSERT(m_file.read(reinterpret_cast(&version), sizeof(version))); + ASSERT(version == VERSION); +} + +std::vector Vault::read_file_headers() { + std::vector headers; + + m_file.clear(); + m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); + + while (true) { + u64 name_size = 0; + if (!m_file.read(reinterpret_cast(&name_size), sizeof(u64))) { + break; + } + + FileHeader header{}; + header.name.resize(name_size); + m_file.read(header.name.data(), static_cast(name_size)); + + m_file.read(reinterpret_cast(&header.size), sizeof(u64)); + m_file.seekg(static_cast(header.size), std::ios::cur); + + headers.push_back(header); + } + + return headers; +} + +std::optional Vault::read_file(const std::string &filename) { + m_file.clear(); + m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); + + while (true) { + u64 name_size = 0; + if (!m_file.read(reinterpret_cast(&name_size), sizeof(u64))) { + break; + } + + std::string name; + name.resize(name_size); + m_file.read(name.data(), static_cast(name_size)); + + u64 content_size = 0; + m_file.read(reinterpret_cast(&content_size), sizeof(u64)); + + if (name == filename) { + std::string content(content_size, '\0'); + m_file.read(content.data(), static_cast(content_size)); + return content; + } + + m_file.seekg(static_cast(content_size), std::ios::cur); + } + + return std::nullopt; +} + +void Vault::write_file(const std::string &name, const std::string &content) { + m_file.clear(); + m_file.seekp(0, std::ios::end); + + u64 name_size = name.size(); + u64 content_size = content.size(); + m_file.write(reinterpret_cast(&name_size), sizeof(u64)); + m_file.write(name.data(), static_cast(name_size)); + m_file.write(reinterpret_cast(&content_size), sizeof(u64)); + m_file.write(content.data(), static_cast(content_size)); + m_file.flush(); +} diff --git a/src/vault.h b/src/vault.h index 4cd1ee5..4351f5c 100644 --- a/src/vault.h +++ b/src/vault.h @@ -1,52 +1,27 @@ #pragma once #include "common.h" -#include #include -#include #include +#include -namespace Vault { - -inline void verify_header(std::ifstream &file) { - ASSERT(file.good()); - - std::array header{}; - ASSERT(file.read(header.data(), header.size())); - ASSERT(std::string_view(header.data(), header.size()) == "DULL"); -} +constexpr i16 VERSION = 1; +constexpr u64 AFTER_HEADER_OFFSET = 6; struct FileHeader { std::string name; u64 size; }; -inline std::optional read_file_header(std::ifstream &file) { - FileHeader header{}; +class Vault { +public: + explicit Vault(std::string path); - u64 name_size = 0; - if (!file.read(reinterpret_cast(&name_size), sizeof(u64))) { - return std::nullopt; - } + std::vector read_file_headers(); + std::optional read_file(const std::string &name); + void write_file(const std::string &name, const std::string &content); - header.name = std::string(name_size, '\0'); - file.read(header.name.data(), static_cast(name_size)); - - file.read(reinterpret_cast(&header.size), sizeof(u64)); - - return header; -} - -inline void write_header(std::ofstream &file) { file.write("DULL", 4); } - -inline void write_file(std::ofstream &file, const std::string &name, - const std::string &content) { - u64 name_size = name.size(); - u64 content_size = content.size(); - file.write(reinterpret_cast(&name_size), sizeof(u64)); - file.write(name.data(), static_cast(name_size)); - file.write(reinterpret_cast(&content_size), sizeof(u64)); - file.write(content.data(), static_cast(content_size)); -} - -}; // namespace Vault \ No newline at end of file +private: + std::string m_path; + std::fstream m_file; +};