dont preview by default, Vault::delete_file

This commit is contained in:
2025-11-02 21:44:10 +01:00
parent 3e33504e22
commit cf1ac6d556
10 changed files with 110 additions and 68 deletions

View File

@@ -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,-concurrency-mt-unsafe,-cert-env33-c' 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,-*-avoid-do-while'

4
.gitignore vendored
View File

@@ -1,2 +1,4 @@
/.cache /.cache
/build /build
*.py
*.dull

View File

@@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets) find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
find_package(PkgConfig REQUIRED)
pkg_check_modules(BOTAN REQUIRED botan-3) pkg_check_modules(BOTAN REQUIRED botan-3)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)

View File

@@ -5,11 +5,14 @@
#include <string> #include <string>
#define ASSERT(cond) \ #define ASSERT(cond) \
if (!(cond)) { \ do { \
std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ << "\n" \ if (!(cond)) { \
<< #cond << std::endl; \ std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ \
abort(); \ << "\n" \
} << #cond << std::endl; \
abort(); \
} \
} while (0)
using u8 = unsigned char; using u8 = unsigned char;
using i16 = int16_t; using i16 = int16_t;

View File

@@ -15,6 +15,8 @@ encrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &plaintext,
ASSERT(key.size() == 32); ASSERT(key.size() == 32);
ASSERT(nonce.size() == 24); ASSERT(nonce.size() == 24);
// XChaCha20 is selected automatically based on the nonce size
// https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375
auto cipher = Botan::AEAD_Mode::create_or_throw( auto cipher = Botan::AEAD_Mode::create_or_throw(
"ChaCha20Poly1305", Botan::Cipher_Dir::Encryption); "ChaCha20Poly1305", Botan::Cipher_Dir::Encryption);

View File

@@ -6,9 +6,11 @@
#include <QTemporaryFile> #include <QTemporaryFile>
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow) { : QMainWindow(parent), ui(std::make_unique<Ui::MainWindow>()) {
ui->setupUi(this); ui->setupUi(this);
ui->filePreview->setVisible(false);
connect(ui->actionNew, &QAction::triggered, this, [this]() { connect(ui->actionNew, &QAction::triggered, this, [this]() {
QString path = QFileDialog::getSaveFileName(this, "Choose vault location", QString path = QFileDialog::getSaveFileName(this, "Choose vault location",
QDir::currentPath(), QDir::currentPath(),
@@ -33,10 +35,9 @@ MainWindow::MainWindow(QWidget *parent)
reload_fs_tree(); reload_fs_tree();
}); });
connect(ui->fsTreeWidget, &QTreeWidget::itemClicked, connect(
[this](QTreeWidgetItem *item, int column) { ui->fsTreeWidget, &QTreeWidget::itemClicked,
preview_file(item->text(column).toStdString()); [this](QTreeWidgetItem *, int) { ui->filePreview->setVisible(false); });
});
connect(ui->fsTreeWidget, &QTreeWidget::customContextMenuRequested, this, connect(ui->fsTreeWidget, &QTreeWidget::customContextMenuRequested, this,
&MainWindow::file_context_menu); &MainWindow::file_context_menu);
@@ -54,7 +55,7 @@ MainWindow::MainWindow(QWidget *parent)
std::string content((std::istreambuf_iterator<char>(file)), std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()); std::istreambuf_iterator<char>());
m_vault->write_file(path_to_filename(path.toStdString()), content); m_vault->create_file(path_to_filename(path.toStdString()), content);
} }
reload_fs_tree(); reload_fs_tree();
@@ -62,6 +63,7 @@ MainWindow::MainWindow(QWidget *parent)
} }
void MainWindow::reload_fs_tree() { void MainWindow::reload_fs_tree() {
ui->menuFiles->setEnabled(true);
ui->fsTreeWidget->clear(); ui->fsTreeWidget->clear();
auto headers = m_vault->read_file_headers(); auto headers = m_vault->read_file_headers();
@@ -69,11 +71,14 @@ void MainWindow::reload_fs_tree() {
auto *item = new QTreeWidgetItem(ui->fsTreeWidget); auto *item = new QTreeWidgetItem(ui->fsTreeWidget);
item->setText(0, QString::fromStdString(header.name)); item->setText(0, QString::fromStdString(header.name));
item->setText(1, QString::number(header.size)); item->setText(1, QString::number(header.size));
item->setText(2, QString::number(header.offset));
} }
ui->fsTreeWidget->resizeColumnToContents(0); ui->fsTreeWidget->resizeColumnToContents(0);
} }
void MainWindow::preview_file(const std::string &filename) { void MainWindow::preview_file(const std::string &filename) {
ui->filePreview->setVisible(true);
auto content = m_vault->read_file(filename); auto content = m_vault->read_file(filename);
if (content) { if (content) {
ui->filePreview->setText(QString::fromStdString(content.value())); ui->filePreview->setText(QString::fromStdString(content.value()));
@@ -102,6 +107,8 @@ void MainWindow::edit_file(const std::string &filename) {
QTextStream in(&temp_file); QTextStream in(&temp_file);
m_vault->update_file(filename, in.readAll().toStdString()); m_vault->update_file(filename, in.readAll().toStdString());
reload_fs_tree(); reload_fs_tree();
// QTemporaryFile gets deleted when it goes out of scope
} else { } else {
qWarning() << "File to edit not found"; qWarning() << "File to edit not found";
} }
@@ -115,6 +122,11 @@ void MainWindow::file_context_menu(const QPoint &pos) {
QMenu menu(this); QMenu menu(this);
QAction *preview_action = menu.addAction(
style()->standardIcon(QStyle::SP_FileDialogContentsView), "Preview");
connect(preview_action, &QAction::triggered, this,
[this, item]() { preview_file(item->text(0).toStdString()); });
QAction *edit_action = menu.addAction( QAction *edit_action = menu.addAction(
style()->standardIcon(QStyle::SP_FileDialogDetailedView), "Edit"); style()->standardIcon(QStyle::SP_FileDialogDetailedView), "Edit");
connect(edit_action, &QAction::triggered, this, connect(edit_action, &QAction::triggered, this,

View File

@@ -12,7 +12,7 @@ public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
private: private:
Ui::MainWindow *ui; std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<Vault> m_vault; std::unique_ptr<Vault> m_vault;

View File

@@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>900</width>
<height>600</height> <height>600</height>
</rect> </rect>
</property> </property>
@@ -33,6 +33,11 @@
<string>Size</string> <string>Size</string>
</property> </property>
</column> </column>
<column>
<property name="text">
<string>Offset</string>
</property>
</column>
</widget> </widget>
</item> </item>
<item> <item>
@@ -46,10 +51,10 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>30</height> <height>32</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuVault">
<property name="title"> <property name="title">
<string>Vault</string> <string>Vault</string>
</property> </property>
@@ -57,12 +62,15 @@
<addaction name="actionOpen"/> <addaction name="actionOpen"/>
</widget> </widget>
<widget class="QMenu" name="menuFiles"> <widget class="QMenu" name="menuFiles">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title"> <property name="title">
<string>Files</string> <string>Files</string>
</property> </property>
<addaction name="actionAddFiles"/> <addaction name="actionAddFiles"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuVault"/>
<addaction name="menuFiles"/> <addaction name="menuFiles"/>
</widget> </widget>
<action name="actionNew"> <action name="actionNew">

View File

@@ -19,7 +19,7 @@ Vault::Vault(std::string path) : m_path(std::move(path)) {
ASSERT(static_cast<bool>(m_file.read(header.data(), header.size()))); ASSERT(static_cast<bool>(m_file.read(header.data(), header.size())));
ASSERT(std::string_view(header.data(), header.size()) == "DULL"); ASSERT(std::string_view(header.data(), header.size()) == "DULL");
std::int16_t version = 0; i16 version = 0;
ASSERT(m_file.read(reinterpret_cast<char *>(&version), sizeof(version))); ASSERT(m_file.read(reinterpret_cast<char *>(&version), sizeof(version)));
ASSERT(version == VERSION); ASSERT(version == VERSION);
} }
@@ -31,19 +31,13 @@ std::vector<FileHeader> Vault::read_file_headers() {
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
while (true) { while (true) {
u64 name_size = 0; auto header = read_file_header(m_file);
if (!m_file.read(reinterpret_cast<char *>(&name_size), sizeof(u64))) { if (header) {
headers.push_back(header.value());
m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
} else {
break; break;
} }
FileHeader header{};
header.name.resize(name_size);
m_file.read(header.name.data(), static_cast<i64>(name_size));
m_file.read(reinterpret_cast<char *>(&header.size), sizeof(u64));
m_file.seekg(static_cast<i64>(header.size), std::ios::cur);
headers.push_back(header);
} }
return headers; return headers;
@@ -54,46 +48,42 @@ std::optional<std::string> Vault::read_file(const std::string &filename) {
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
while (true) { while (true) {
u64 name_size = 0; auto header = read_file_header(m_file);
if (!m_file.read(reinterpret_cast<char *>(&name_size), sizeof(u64))) { if (!header) {
break; break;
} }
std::string name; if (header->name == filename) {
name.resize(name_size); std::string content(header->size, '\0');
m_file.read(name.data(), static_cast<i64>(name_size)); if (!m_file.read(content.data(), static_cast<i64>(header->size))) {
break;
u64 content_size = 0; }
m_file.read(reinterpret_cast<char *>(&content_size), sizeof(u64));
if (name == filename) {
std::string content(content_size, '\0');
m_file.read(content.data(), static_cast<i64>(content_size));
return content; return content;
} }
m_file.seekg(static_cast<i64>(content_size), std::ios::cur); m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
} }
return std::nullopt; return std::nullopt;
} }
void Vault::write_file(const std::string &filename, void Vault::create_file(const std::string &filename,
const std::string &content) { const std::string &content) {
m_file.clear(); m_file.clear();
m_file.seekp(0, std::ios::end); m_file.seekp(0, std::ios::end);
u64 filename_size = filename.size(); u64 filename_size = filename.size();
u64 content_size = content.size(); u64 content_size = content.size();
m_file.write(reinterpret_cast<const char *>(&filename_size), sizeof(u64)); ASSERT(m_file.write(reinterpret_cast<const char *>(&filename_size),
m_file.write(filename.data(), static_cast<i64>(filename_size)); sizeof(u64)));
m_file.write(reinterpret_cast<const char *>(&content_size), sizeof(u64)); ASSERT(m_file.write(filename.data(), static_cast<i64>(filename_size)));
m_file.write(content.data(), static_cast<i64>(content_size)); ASSERT(
m_file.write(reinterpret_cast<const char *>(&content_size), sizeof(u64)));
ASSERT(m_file.write(content.data(), static_cast<i64>(content_size)));
m_file.flush(); m_file.flush();
} }
void Vault::update_file(const std::string &filename, void Vault::delete_file(const std::string &filename) {
const std::string &content) {
m_file.clear(); m_file.clear();
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
@@ -103,26 +93,20 @@ void Vault::update_file(const std::string &filename,
while (true) { while (true) {
i64 current_pos = m_file.tellg(); i64 current_pos = m_file.tellg();
u64 name_size = 0; auto header = read_file_header(m_file);
if (!m_file.read(reinterpret_cast<char *>(&name_size), sizeof(u64))) { if (!header) {
break; break;
} }
std::string name; if (header->name == filename) {
name.resize(name_size);
m_file.read(name.data(), static_cast<i64>(name_size));
u64 content_size = 0;
m_file.read(reinterpret_cast<char *>(&content_size), sizeof(u64));
if (name == filename) {
entry_start = current_pos; entry_start = current_pos;
entry_total_size = sizeof(u64) + name_size + sizeof(u64) + content_size; entry_total_size =
m_file.seekg(static_cast<i64>(content_size), std::ios::cur); sizeof(u64) + header->name.length() + sizeof(u64) + header->size;
m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
break; break;
} }
m_file.seekg(static_cast<i64>(content_size), std::ios::cur); m_file.seekg(static_cast<i64>(header->size), std::ios::cur);
} }
if (entry_start != -1) { if (entry_start != -1) {
@@ -136,7 +120,7 @@ void Vault::update_file(const std::string &filename,
m_file.clear(); m_file.clear();
m_file.seekp(entry_start, std::ios::beg); m_file.seekp(entry_start, std::ios::beg);
m_file.write(remaining.data(), static_cast<i64>(remaining.size())); ASSERT(m_file.write(remaining.data(), static_cast<i64>(remaining.size())));
i64 new_size = entry_start + static_cast<i64>(remaining.size()); i64 new_size = entry_start + static_cast<i64>(remaining.size());
m_file.flush(); m_file.flush();
@@ -147,6 +131,32 @@ void Vault::update_file(const std::string &filename,
m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary); m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary);
ASSERT(m_file.good()); ASSERT(m_file.good());
} }
}
write_file(filename, content); void Vault::update_file(const std::string &filename,
} const std::string &content) {
delete_file(filename);
create_file(filename, content);
}
std::optional<FileHeader> Vault::read_file_header(std::fstream &file) {
FileHeader header{};
header.offset = file.tellg();
u64 name_size = 0;
if (!file.read(reinterpret_cast<char *>(&name_size), sizeof(u64))) {
return std::nullopt;
}
ASSERT(name_size < 10000);
header.name.resize(name_size);
if (!file.read(header.name.data(), static_cast<i64>(name_size))) {
return std::nullopt;
}
if (!file.read(reinterpret_cast<char *>(&header.size), sizeof(u64))) {
return std::nullopt;
}
return header;
}

View File

@@ -11,6 +11,7 @@ constexpr u64 AFTER_HEADER_OFFSET = 6;
struct FileHeader { struct FileHeader {
std::string name; std::string name;
u64 size; u64 size;
u64 offset;
}; };
class Vault { class Vault {
@@ -19,9 +20,12 @@ public:
std::vector<FileHeader> read_file_headers(); std::vector<FileHeader> read_file_headers();
std::optional<std::string> read_file(const std::string &name); std::optional<std::string> read_file(const std::string &name);
void write_file(const std::string &name, const std::string &content); void create_file(const std::string &name, const std::string &content);
void delete_file(const std::string &name);
void update_file(const std::string &name, const std::string &content); void update_file(const std::string &name, const std::string &content);
static std::optional<FileHeader> read_file_header(std::fstream &file);
private: private:
std::string m_path; std::string m_path;
std::fstream m_file; std::fstream m_file;