summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt25
-rw-r--r--src/archive.cpp85
-rw-r--r--src/archive.hpp13
-rw-r--r--src/config.hpp18
-rw-r--r--src/fud_mem.cpp19
-rw-r--r--src/getsuyomi.cpp8
-rw-r--r--src/getsuyomi.hpp5
-rw-r--r--src/main.cpp4
-rw-r--r--src/main_window.cpp159
-rw-r--r--src/main_window.hpp39
-rw-r--r--warnings.cmake67
11 files changed, 400 insertions, 42 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bbcbf38..c609098 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,12 +16,33 @@ qt_add_executable(getsuyomi
src/main.cpp
src/archive.cpp
src/getsuyomi.cpp
+ src/fud_mem.cpp
)
+include(warnings.cmake)
+target_compile_options(getsuyomi PRIVATE ${GETSUYOMI_WARNINGS})
+
+find_package(Fud REQUIRED)
# find_dependency(minizip-ng REQUIRED)
-target_include_directories(getsuyomi PRIVATE libzip)
-target_link_libraries(getsuyomi PRIVATE Qt6::Widgets zip)
+target_include_directories(getsuyomi PRIVATE
+ # libzip
+ ${FUD_INCLUDE_DIR}
+ ${SDL2_INCLUDE_DIRS}
+)
+
+target_link_libraries(getsuyomi PRIVATE
+ Qt6::Widgets
+ zip
+ fud)
+
+set_target_properties(
+ getsuyomi PROPERTIES
+ CXX_STANDARD 20
+ C_STANDARD 23
+ CXX_EXTENSIONS OFF
+ C_EXTENSIONS OFF
+ CXX_STANDARD_REQUIRED ON)
# set_target_properties(getsuyomi PROPERTIES
# WIN32_EXECUTABLE ON
diff --git a/src/archive.cpp b/src/archive.cpp
index 0339d85..1ce79b4 100644
--- a/src/archive.cpp
+++ b/src/archive.cpp
@@ -6,26 +6,81 @@
namespace getsuyomi {
+using fud::FudStatus;
+
ZipArchive::ZipArchive(const QString& filename)
{
auto filenameUtf8 = filename.toUtf8();
qDebug("Open file %s", filenameUtf8.data());
int err;
m_archive = zip_open(filenameUtf8.data(), 0, &err);
+
if (m_archive == nullptr) {
zip_error_t error;
zip_error_init_with_code(&error, err);
qCritical("%s: cannot open zip archive '%s': %s", AppName, filenameUtf8.data(), zip_error_strerror(&error));
zip_error_fini(&error);
- throw std::runtime_error("Bad zip file");
+ return;
}
- populate();
+ auto status = populate();
+ if (status != FudStatus::Success) {
+ cleanup();
+ return;
+ }
+}
+
+ZipArchive::ZipArchive(ZipArchive&& rhs)
+ :m_archive{rhs.m_archive},
+ m_sortedIndices{std::move(rhs.m_sortedIndices)},
+ m_filenames{std::move(rhs.m_filenames)},
+ m_fileSizes{std::move(rhs.m_fileSizes)},
+ m_pages{std::move(rhs.m_pages)}
+{
+ rhs.m_archive = nullptr;
+}
+
+ZipArchive::~ZipArchive()
+{
+ cleanup();
}
-void ZipArchive::populate()
+ZipArchive& ZipArchive::operator=(ZipArchive&& rhs)
{
- auto numEntries = zip_get_num_entries(m_archive, ZIP_FL_UNCHANGED);
+ m_archive = rhs.m_archive;
+ m_sortedIndices = std::move(rhs.m_sortedIndices);
+ m_filenames = std::move(rhs.m_filenames);
+ m_fileSizes = std::move(rhs.m_fileSizes);
+ m_pages = std::move(rhs.m_pages);
+
+ rhs.m_archive = nullptr;
+
+ return *this;
+}
+
+bool ZipArchive::valid() const {
+ return m_archive != nullptr;
+}
+
+void ZipArchive::cleanup() {
+ if (m_archive != nullptr) {
+ zip_discard(m_archive);
+ m_archive = nullptr;
+ }
+}
+
+fud::FudStatus ZipArchive::populate()
+{
+ if (!valid()) {
+ qCritical("Invalid zipArchive");
+ return FudStatus::ObjectInvalid;
+ }
+
+ auto numEntriesResult = zip_get_num_entries(m_archive, ZIP_FL_UNCHANGED);
+ if (numEntriesResult < 0) {
+ return FudStatus::Failure;
+ }
+ size_t numEntries = static_cast<size_t>(numEntriesResult);
qDebug("%zu pages", numEntries);
struct NameIndex {
@@ -42,6 +97,9 @@ void ZipArchive::populate()
struct zip_stat stats;
auto status = zip_stat_index(m_archive, idx, 0, &stats);
+ if (status < 0) {
+ return FudStatus::Failure;
+ }
static_assert(ZIP_STAT_NAME != 0);
static_assert(ZIP_STAT_SIZE != 0);
static_assert(ZIP_STAT_INDEX != 0);
@@ -62,6 +120,11 @@ void ZipArchive::populate()
m_pages.emplace_back(std::nullopt);
}
+
+ if (m_pages.empty()) {
+ return FudStatus::Empty;
+ }
+
std::sort(nameIndices.begin(), nameIndices.end(), [](const auto& lhs, const auto& rhs) {
return std::lexicographical_compare(lhs.name.begin(), lhs.name.end(), rhs.name.begin(), rhs.name.end());
});
@@ -75,14 +138,8 @@ void ZipArchive::populate()
m_filenames.push_back(nameIndex.name);
m_fileSizes.push_back(nameIndex.filesize);
}
-}
-ZipArchive::~ZipArchive()
-{
- if (m_archive != nullptr) {
- zip_discard(m_archive);
- m_archive = nullptr;
- }
+ return FudStatus::Success;
}
ArchiveResult ZipArchive::getPage(size_t page)
@@ -105,11 +162,11 @@ ArchiveResult ZipArchive::getPage(size_t page)
}
QByteArray data;
- data.resize(m_fileSizes[page]);
- auto index = m_sortedIndices[page];
+ data.resize(static_cast<qsizetype>(m_fileSizes[page]));
+ // auto index = m_sortedIndices[page];
qDebug("Reading in page data");
- auto bytesRead = zip_fread(file, data.data(), data.size());
+ auto bytesRead = zip_fread(file, data.data(), static_cast<size_t>(data.size()));
zip_fclose(file);
file = nullptr;
diff --git a/src/archive.hpp b/src/archive.hpp
index d8464e5..e803bd2 100644
--- a/src/archive.hpp
+++ b/src/archive.hpp
@@ -9,6 +9,8 @@
#include <vector>
#include <zip.h>
+#include <fud_status.hpp>
+
namespace getsuyomi {
enum class ArchiveError
@@ -26,17 +28,26 @@ class Archive {
virtual ~Archive() = default;
virtual ArchiveResult getPage(size_t page) = 0;
virtual size_t numPages() const = 0;
+ virtual bool valid() const = 0;
};
class ZipArchive : public Archive {
public:
explicit ZipArchive(const QString& filename);
+ ZipArchive(const ZipArchive&) = delete;
+ ZipArchive(ZipArchive&&);
virtual ~ZipArchive() override;
+ ZipArchive& operator=(const ZipArchive&) = delete;
+ ZipArchive& operator=(ZipArchive&& rhs);
+
virtual ArchiveResult getPage(size_t page) override;
virtual size_t numPages() const override;
+ bool valid() const override;
+
private:
- void populate();
+ void cleanup();
+ fud::FudStatus populate();
zip_t* m_archive{nullptr};
diff --git a/src/config.hpp b/src/config.hpp
new file mode 100644
index 0000000..cb6a74a
--- /dev/null
+++ b/src/config.hpp
@@ -0,0 +1,18 @@
+#ifndef GETSUYOMI_CONFIG_HPP
+#define GETSUYOMI_CONFIG_HPP
+
+#include <string>
+
+namespace getsuyomi {
+
+struct GetsuyomiConfig {
+ std::string home{};
+ std::string dataHome{};
+ std::string configHome{};
+ std::string stateHome{};
+};
+
+} // namespace getsuyomi
+
+
+#endif
diff --git a/src/fud_mem.cpp b/src/fud_mem.cpp
new file mode 100644
index 0000000..d8f6673
--- /dev/null
+++ b/src/fud_mem.cpp
@@ -0,0 +1,19 @@
+#include <fud_memory.hpp>
+
+#include <cstdlib>
+
+namespace fud {
+
+void* fudAlloc(size_t size) {
+ return malloc(size);
+}
+
+void* fudRealloc(void* ptr, size_t size) {
+ return realloc(ptr, size);
+}
+
+void fudFree(void* ptr) {
+ return free(ptr);
+}
+
+} // namespace fud
diff --git a/src/getsuyomi.cpp b/src/getsuyomi.cpp
index 11f273b..5593758 100644
--- a/src/getsuyomi.cpp
+++ b/src/getsuyomi.cpp
@@ -13,6 +13,12 @@ Getsuyomi::Getsuyomi()
setLayout(m_layout);
}
+Getsuyomi::~Getsuyomi() {
+ if (m_archive != nullptr) {
+ delete m_archive;
+ }
+}
+
void Getsuyomi::setArchive(Archive* archive) {
if (archive == m_archive) {
return;
@@ -38,7 +44,7 @@ void Getsuyomi::next() {
}
void Getsuyomi::back() {
- auto numPages = m_archive->numPages();
+ // auto numPages = m_archive->numPages();
size_t decrement = 1;
if (m_pageLayout != PageLayout::Single) {
decrement = 2;
diff --git a/src/getsuyomi.hpp b/src/getsuyomi.hpp
index 3d3c092..3f4da25 100644
--- a/src/getsuyomi.hpp
+++ b/src/getsuyomi.hpp
@@ -21,6 +21,11 @@ class Getsuyomi : public QWidget {
public:
friend class GetsuyomiApp;
Getsuyomi();
+ Getsuyomi(const Getsuyomi&) = delete;
+ Getsuyomi(Getsuyomi&&) = delete;
+ ~Getsuyomi();
+ Getsuyomi& operator=(const Getsuyomi&) = delete;
+ Getsuyomi& operator=(Getsuyomi&&) = delete;
public slots:
void setArchive(Archive* archive);
diff --git a/src/main.cpp b/src/main.cpp
index bac3ff2..56c4563 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,6 +4,10 @@ int main(int argc, char* argv[])
{
QApplication app{argc, argv};
getsuyomi::GetsuyomiApp getsuyomi{};
+ auto status = getsuyomi.setup();
+ if (status != fud::FudStatus::Success) {
+ return static_cast<int>(status);
+ }
QCommandLineParser parser;
parser.setApplicationDescription(QApplication::translate("main", "A comic book and manga reader"));
diff --git a/src/main_window.cpp b/src/main_window.cpp
index b05507c..5c0c401 100644
--- a/src/main_window.cpp
+++ b/src/main_window.cpp
@@ -1,22 +1,43 @@
#include "main_window.hpp"
+#include "config.hpp"
+
#include <QString>
+#include <filesystem>
+#include <fud_result.hpp>
+#include <fud_status.hpp>
+#include <string>
+#include <vector>
#include <zip.h>
namespace getsuyomi {
-GetsuyomiApp::GetsuyomiApp() : m_getsuyomi{new Getsuyomi()}
+using fud::FudStatus;
+using GetEnvResult = fud::Result<GetsuyomiConfig, FudStatus>;
+
+constexpr const char* HOME{"HOME"};
+
+std::optional<std::string> getEnvVar(const char* envVar);
+void getEnvVar(const std::string envVar, std::string& envValue, const char* backup);
+GetEnvResult getEnvironment();
+
+GetsuyomiApp::GetsuyomiApp() : m_getsuyomi{new Getsuyomi()}, m_qtSettings{new QSettings(AppVendor, AppName)}
{
- setCentralWidget(m_getsuyomi);
- setup();
}
-void GetsuyomiApp::setup()
+FudStatus GetsuyomiApp::setup()
{
+ setCentralWidget(m_getsuyomi);
+
QCoreApplication::setApplicationName(AppName);
QCoreApplication::setApplicationVersion(AppVersionString);
setWindowTitle(AppName);
+ auto envResult = getEnvironment();
+ if (envResult.isError()) {
+ return envResult.getError();
+ }
+
createActions();
createMenus();
createToolBar();
@@ -25,7 +46,11 @@ void GetsuyomiApp::setup()
constexpr int minimumHeight = 480;
setMinimumSize(minimumWidth, minimumHeight);
+ readSettings();
+
show();
+
+ return FudStatus::Success;
}
void GetsuyomiApp::createActions()
@@ -81,18 +106,35 @@ void GetsuyomiApp::createToolBar()
void GetsuyomiApp::openFile()
{
- auto filename = QFileDialog::getOpenFileName(
+ auto dialog = QFileDialog(
this,
tr("Open Archive"),
- QDir::homePath(),
+ m_lastOpenedDirectory,
tr("Archive types (*.zip *.cbz *.cbr *.gz)"));
+ dialog.setFileMode(QFileDialog::ExistingFile);
+ QString filename;
+ if (dialog.exec()) {
+ auto filenames = dialog.selectedFiles();
+ if (filenames.length() == 0) {
+ qWarning("No files selected.");
+ return;
+ } else if (filenames.length() > 1) {
+ qWarning("Too many files selected %llu.", filenames.length());
+ return;
+ }
+ filename = filenames[0];
+ m_lastOpenedDirectory = dialog.directory().absolutePath();
+ } else {
+ qWarning("File dialog did not execute");
+ return;
+ }
if (filename.endsWith(".zip")) {
- try {
- auto* archive = new ZipArchive(filename);
- m_getsuyomi->setArchive(archive);
- } catch (std::runtime_error& exc) {
+ auto* archive = new ZipArchive(filename);
+ if (!archive->valid()) {
qCritical("Failed to change archive");
+ } else {
+ m_getsuyomi->setArchive(archive);
}
} else {
qCritical("Unsupported file extension");
@@ -101,12 +143,28 @@ void GetsuyomiApp::openFile()
void GetsuyomiApp::openDirectory()
{
- QString directory = QFileDialog::getExistingDirectory(
+ auto dialog = QFileDialog(
this,
tr("Open Directory"),
- QDir::homePath(),
- QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
- qDebug("Open directory %s", qPrintable(directory));
+ m_lastOpenedDirectory);
+ dialog.setFileMode(QFileDialog::Directory);
+ QString directoryName;
+ if (dialog.exec()) {
+ auto filenames = dialog.selectedFiles();
+ if (filenames.length() == 0) {
+ qWarning("No files selected.");
+ return;
+ } else if (filenames.length() > 1) {
+ qWarning("Too many files selected %llu.", filenames.length());
+ return;
+ }
+ directoryName = filenames[0];
+ m_lastOpenedDirectory = dialog.directory().absolutePath();
+ } else {
+ qWarning("File dialog did not execute");
+ return;
+ }
+ qDebug("Opened directory %s", qPrintable(directoryName));
}
void GetsuyomiApp::quit()
@@ -126,4 +184,77 @@ void GetsuyomiApp::back()
m_getsuyomi->back();
}
+void GetsuyomiApp::closeEvent(QCloseEvent* event)
+{
+ QMainWindow::closeEvent(event);
+}
+
+void GetsuyomiApp::readSettings()
+{
+ restoreGeometry(m_qtSettings->value("geometry").toByteArray());
+ restoreState(m_qtSettings->value("windowState").toByteArray());
+ m_lastOpenedDirectory = m_qtSettings->value("lastOpenedDirectory", QDir::homePath()).toString();
+ qDebug("ReadSettings - last directory is %s", qPrintable(m_lastOpenedDirectory));
+}
+
+void GetsuyomiApp::writeSettings()
+{
+ m_qtSettings->setValue("geometry", saveGeometry());
+ m_qtSettings->setValue("windowState", saveState());
+ m_qtSettings->setValue("lastOpenedDirectory", m_lastOpenedDirectory);
+}
+
+std::optional<std::string> getEnvVar(const char* envVar)
+{
+ const QByteArray defaultArray{};
+ if (envVar == nullptr) {
+ return std::nullopt;
+ }
+ QByteArray varArray = qgetenv(envVar);
+ if (varArray == defaultArray) {
+ return std::nullopt;
+ }
+ return varArray.toStdString();
+}
+
+void getEnvVar(const std::string envVar, std::string& envValue, const char* backup)
+{
+ auto envValueOpt = getEnvVar(envVar.c_str());
+ if (envValueOpt == std::nullopt || envValueOpt->length() == 0) {
+ std::filesystem::path envValuePath{HOME};
+ envValuePath.append(backup);
+ envValue = envValuePath;
+ } else {
+ envValue = *envValueOpt;
+ }
+ qDebug("%s is %s", envVar.c_str(), envValue.c_str());
+}
+
+GetEnvResult getEnvironment()
+{
+ GetsuyomiConfig config{};
+
+ auto homeOpt = getEnvVar(HOME);
+ if (homeOpt == std::nullopt || homeOpt->length() == 0) {
+ qCritical("Error getting home");
+ return GetEnvResult::error(FudStatus::Failure);
+ }
+ config.home = *homeOpt;
+ qDebug("Home is %s", config.home.c_str());
+
+ /* If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. */
+ const std::string XdgDataHome{"XDG_DATA_HOME"};
+ getEnvVar(XdgDataHome, config.dataHome, ".local/share");
+
+ /* If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used. */
+ const std::string XdgConfigHome{"XDG_CONFIG_HOME"};
+ getEnvVar(XdgConfigHome, config.configHome, ".config");
+
+ /* If $XDG_STATE_HOME is either not set or empty, a default equal to $HOME/.local/state should be used. */
+ const std::string XdgStateHome{"XDG_STATE_HOME"};
+ getEnvVar(XdgStateHome, config.stateHome, ".local/state");
+
+ return GetEnvResult::okay(config);
+}
+
} // namespace getsuyomi
diff --git a/src/main_window.hpp b/src/main_window.hpp
index bc23b01..de1cb28 100644
--- a/src/main_window.hpp
+++ b/src/main_window.hpp
@@ -5,10 +5,14 @@
#include "getsuyomi.hpp"
#include <QtWidgets>
+#include <fud_status.hpp>
namespace getsuyomi {
-constexpr const char* AppName = "GetsuYomi";
+constexpr const char* AppVendor = "libfud";
+
+constexpr const char* AppName = "getsuyomi";
+
constexpr const char* AppVersionString = "1.0.0";
class GetsuyomiApp : public QMainWindow {
@@ -16,29 +20,44 @@ class GetsuyomiApp : public QMainWindow {
public:
GetsuyomiApp();
+ ~GetsuyomiApp() = default;
+ GetsuyomiApp(const GetsuyomiApp&) = delete;
+ GetsuyomiApp(GetsuyomiApp&&) = delete;
+ GetsuyomiApp& operator=(const GetsuyomiApp&) = delete;
+ GetsuyomiApp& operator=(GetsuyomiApp&&) = delete;
+
+ fud::FudStatus setup();
private:
- void setup();
+ /* Private methods */
void createActions();
void createMenus();
void createToolBar();
- Getsuyomi* m_getsuyomi;
+ /* Private data */
+ Getsuyomi* m_getsuyomi{nullptr};
- QAction* m_openFile;
- QAction* m_openDirectory;
- QAction* m_quitAction;
- QAction* m_nextAction;
- QAction* m_backAction;
+ QAction* m_openFile{nullptr};
+ QAction* m_openDirectory{nullptr};
+ QAction* m_quitAction{nullptr};
+ QAction* m_nextAction{nullptr};
+ QAction* m_backAction{nullptr};
- QMenu* m_menuBar;
- QToolBar* m_toolBar;
+ QMenu* m_menuBar{nullptr};
+ QToolBar* m_toolBar{nullptr};
+
+ QSettings* m_qtSettings{nullptr};
+ QString m_lastOpenedDirectory{};
private slots:
void openFile();
void openDirectory();
void quit();
+ void readSettings();
+ void writeSettings();
+ void closeEvent(QCloseEvent* event) override;
+
void next();
void back();
};
diff --git a/warnings.cmake b/warnings.cmake
new file mode 100644
index 0000000..7228542
--- /dev/null
+++ b/warnings.cmake
@@ -0,0 +1,67 @@
+set(GETSUYOMI_WARNINGS
+ -Werror
+ -Wstack-usage=2048
+ -Wno-long-long
+ -Wno-error=long-long
+ # -Wno-error=inline
+ -Wno-error=mismatched-tags
+ -Wall
+ -Weffc++
+ -pedantic
+ -Wsizeof-pointer-memaccess
+ -pedantic-errors
+ -Wextra
+ -Wpacked
+ -Wshadow
+ -Wvla
+ -Wnull-dereference
+ -Wuninitialized
+ -Wstack-protector
+ -Warray-bounds
+ # -Wno-error=array-bounds
+ -Woverlength-strings
+ -Wwrite-strings
+ -Wcast-qual
+ -Wcast-align
+ -Wdisabled-optimization
+ -Wmissing-field-initializers
+ -Wimport
+ -Winit-self
+ -Wno-inline
+ -Wchar-subscripts
+ -Wcomment
+ -Wconversion
+ -Wfloat-equal
+ -Wformat
+ -Wformat=2
+ -Wformat-nonliteral
+ -Wformat-security
+ -Wformat-y2k
+ -Wmissing-format-attribute
+ -Wmissing-braces
+ -Winvalid-pch
+ -Wmissing-include-dirs
+ -Wmissing-noreturn
+ -Wparentheses
+ -Wpointer-arith
+ -Wredundant-decls
+ -Wreturn-type
+ -Wsequence-point
+ -Wsign-compare
+ -Wstrict-aliasing
+ -Wstrict-aliasing=2
+ -Wswitch
+ -Wswitch-default
+ -Wswitch-enum
+ -Wtrigraphs
+ -Wunknown-pragmas
+ -Wunreachable-code
+ -Wunused
+ -Wunused-function
+ -Wunused-label
+ -Wunused-parameter
+ -Wunused-value
+ -Wunused-variable
+ -Wvariadic-macros
+ -Wvolatile-register-var
+)