Contents

Timer Application: C++ System Programming with CMake and PostgreSQL

Timer Application: C++ System Programming with CMake and PostgreSQL

In this post, I’ll share insights from my Timer project, which demonstrates advanced C++ system programming techniques, CMake build system integration, and PostgreSQL database connectivity for building a comprehensive time tracking application.

Project Overview

The Timer application is a sophisticated C++ system that provides comprehensive time tracking capabilities with database persistence, cross-platform compatibility, and modern C++ development practices. This project showcases system programming, database integration, and build system management.

Technical Architecture

Project Structure

Timer/
├── src/
│   ├── core/
│   │   ├── timer.cpp
│   │   ├── timer.h
│   │   ├── time_utils.cpp
│   │   ├── time_utils.h
│   │   ├── session_manager.cpp
│   │   └── session_manager.h
│   ├── database/
│   │   ├── database_manager.cpp
│   │   ├── database_manager.h
│   │   ├── session_dao.cpp
│   │   ├── session_dao.h
│   │   ├── project_dao.cpp
│   │   └── project_dao.h
│   ├── ui/
│   │   ├── console_ui.cpp
│   │   ├── console_ui.h
│   │   ├── gui_ui.cpp
│   │   └── gui_ui.h
│   ├── config/
│   │   ├── config_manager.cpp
│   │   └── config_manager.h
│   └── utils/
│       ├── logger.cpp
│       ├── logger.h
│       ├── file_utils.cpp
│       └── file_utils.h
├── include/
│   └── timer/
│       ├── timer.h
│       ├── database_manager.h
│       └── config_manager.h
├── tests/
│   ├── unit/
│   ├── integration/
│   └── fixtures/
├── scripts/
│   ├── setup_database.sql
│   ├── create_tables.sql
│   └── sample_data.sql
├── docs/
│   ├── api/
│   ├── user_guide/
│   └── developer_guide/
├── CMakeLists.txt
├── CMakeLists.txt.in
├── config.h.in
├── README.md
└── LICENSE

Core Implementation

Timer Core Classes

// src/core/timer.h
#ifndef TIMER_H
#define TIMER_H

#include <chrono>
#include <string>
#include <memory>
#include <vector>
#include <functional>

namespace TimerApp {

class Timer {
public:
    enum class State {
        STOPPED,
        RUNNING,
        PAUSED
    };
    
    struct Session {
        std::string id;
        std::string project_name;
        std::string description;
        std::chrono::system_clock::time_point start_time;
        std::chrono::system_clock::time_point end_time;
        std::chrono::duration<double> duration;
        std::string tags;
        bool is_active;
    };
    
    explicit Timer(const std::string& project_name = "Default");
    ~Timer() = default;
    
    // Timer control
    void start();
    void pause();
    void resume();
    void stop();
    void reset();
    
    // Session management
    void create_session(const std::string& project_name, 
                       const std::string& description = "",
                       const std::string& tags = "");
    void end_session();
    void save_session();
    
    // Getters
    State get_state() const { return state_; }
    std::chrono::duration<double> get_elapsed_time() const;
    std::chrono::duration<double> get_total_time() const;
    const Session& get_current_session() const { return current_session_; }
    const std::vector<Session>& get_sessions() const { return sessions_; }
    
    // Configuration
    void set_project_name(const std::string& project_name);
    void set_description(const std::string& description);
    void set_tags(const std::string& tags);
    
    // Callbacks
    void set_tick_callback(std::function<void(const std::chrono::duration<double>&)> callback);
    void set_session_end_callback(std::function<void(const Session&)> callback);
    
    // Statistics
    struct Statistics {
        std::chrono::duration<double> total_time;
        std::chrono::duration<double> average_session_time;
        size_t total_sessions;
        size_t active_sessions;
        std::string most_used_project;
    };
    
    Statistics get_statistics() const;
    
private:
    State state_;
    Session current_session_;
    std::vector<Session> sessions_;
    std::chrono::system_clock::time_point start_time_;
    std::chrono::system_clock::time_point pause_time_;
    std::chrono::duration<double> total_paused_time_;
    
    // Callbacks
    std::function<void(const std::chrono::duration<double>&)> tick_callback_;
    std::function<void(const Session&)> session_end_callback_;
    
    // Helper methods
    void update_current_session();
    std::string generate_session_id() const;
    void notify_tick();
    void notify_session_end();
};

} // namespace TimerApp

#endif // TIMER_H
// src/core/timer.cpp
#include "timer.h"
#include "logger.h"
#include <random>
#include <sstream>
#include <iomanip>

namespace TimerApp {

Timer::Timer(const std::string& project_name) 
    : state_(State::STOPPED)
    , total_paused_time_(std::chrono::duration<double>::zero()) {
    
    current_session_.project_name = project_name;
    current_session_.is_active = false;
    
    Logger::get_instance().info("Timer initialized for project: " + project_name);
}

void Timer::start() {
    if (state_ == State::RUNNING) {
        Logger::get_instance().warning("Timer is already running");
        return;
    }
    
    start_time_ = std::chrono::system_clock::now();
    state_ = State::RUNNING;
    
    // Initialize or update current session
    if (!current_session_.is_active) {
        create_session(current_session_.project_name, 
                      current_session_.description, 
                      current_session_.tags);
    }
    
    current_session_.start_time = start_time_;
    current_session_.is_active = true;
    
    Logger::get_instance().info("Timer started for project: " + current_session_.project_name);
}

void Timer::pause() {
    if (state_ != State::RUNNING) {
        Logger::get_instance().warning("Timer is not running, cannot pause");
        return;
    }
    
    pause_time_ = std::chrono::system_clock::now();
    state_ = State::PAUSED;
    
    Logger::get_instance().info("Timer paused");
}

void Timer::resume() {
    if (state_ != State::PAUSED) {
        Logger::get_instance().warning("Timer is not paused, cannot resume");
        return;
    }
    
    auto resume_time = std::chrono::system_clock::now();
    total_paused_time_ += resume_time - pause_time_;
    state_ = State::RUNNING;
    
    Logger::get_instance().info("Timer resumed");
}

void Timer::stop() {
    if (state_ == State::STOPPED) {
        Logger::get_instance().warning("Timer is already stopped");
        return;
    }
    
    auto end_time = std::chrono::system_clock::now();
    state_ = State::STOPPED;
    
    // Update current session
    current_session_.end_time = end_time;
    current_session_.duration = end_time - start_time_ - total_paused_time_;
    current_session_.is_active = false;
    
    // Save session
    sessions_.push_back(current_session_);
    
    Logger::get_instance().info("Timer stopped. Session duration: " + 
                               std::to_string(current_session_.duration.count()) + " seconds");
    
    // Notify callbacks
    notify_session_end();
    
    // Reset for next session
    total_paused_time_ = std::chrono::duration<double>::zero();
}

void Timer::reset() {
    state_ = State::STOPPED;
    total_paused_time_ = std::chrono::duration<double>::zero();
    current_session_ = Session{};
    
    Logger::get_instance().info("Timer reset");
}

void Timer::create_session(const std::string& project_name, 
                          const std::string& description,
                          const std::string& tags) {
    current_session_.id = generate_session_id();
    current_session_.project_name = project_name;
    current_session_.description = description;
    current_session_.tags = tags;
    current_session_.is_active = false;
    
    Logger::get_instance().info("Session created: " + current_session_.id);
}

void Timer::end_session() {
    if (state_ == State::RUNNING) {
        stop();
    }
}

void Timer::save_session() {
    if (current_session_.is_active) {
        Logger::get_instance().warning("Cannot save active session");
        return;
    }
    
    sessions_.push_back(current_session_);
    Logger::get_instance().info("Session saved: " + current_session_.id);
}

std::chrono::duration<double> Timer::get_elapsed_time() const {
    if (state_ == State::STOPPED) {
        return std::chrono::duration<double>::zero();
    }
    
    auto current_time = std::chrono::system_clock::now();
    auto elapsed = current_time - start_time_ - total_paused_time_;
    
    if (state_ == State::PAUSED) {
        elapsed -= (current_time - pause_time_);
    }
    
    return elapsed;
}

std::chrono::duration<double> Timer::get_total_time() const {
    auto total = std::chrono::duration<double>::zero();
    
    for (const auto& session : sessions_) {
        total += session.duration;
    }
    
    // Add current session if active
    if (current_session_.is_active) {
        total += get_elapsed_time();
    }
    
    return total;
}

void Timer::set_tick_callback(std::function<void(const std::chrono::duration<double>&)> callback) {
    tick_callback_ = callback;
}

void Timer::set_session_end_callback(std::function<void(const Session&)> callback) {
    session_end_callback_ = callback;
}

Timer::Statistics Timer::get_statistics() const {
    Statistics stats{};
    
    stats.total_time = get_total_time();
    stats.total_sessions = sessions_.size();
    stats.active_sessions = std::count_if(sessions_.begin(), sessions_.end(),
                                          [](const Session& s) { return s.is_active; });
    
    if (!sessions_.empty()) {
        stats.average_session_time = stats.total_time / stats.total_sessions;
        
        // Find most used project
        std::map<std::string, int> project_counts;
        for (const auto& session : sessions_) {
            project_counts[session.project_name]++;
        }
        
        auto most_used = std::max_element(project_counts.begin(), project_counts.end(),
                                        [](const auto& a, const auto& b) {
                                            return a.second < b.second;
                                        });
        
        if (most_used != project_counts.end()) {
            stats.most_used_project = most_used->first;
        }
    }
    
    return stats;
}

std::string Timer::generate_session_id() const {
    auto now = std::chrono::system_clock::now();
    auto time_t = std::chrono::system_clock::to_time_t(now);
    
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(1000, 9999);
    
    std::stringstream ss;
    ss << "session_" << time_t << "_" << dis(gen);
    
    return ss.str();
}

void Timer::notify_tick() {
    if (tick_callback_) {
        tick_callback_(get_elapsed_time());
    }
}

void Timer::notify_session_end() {
    if (session_end_callback_) {
        session_end_callback_(current_session_);
    }
}

} // namespace TimerApp

Database Integration

// src/database/database_manager.h
#ifndef DATABASE_MANAGER_H
#define DATABASE_MANAGER_H

#include <pqxx/pqxx>
#include <memory>
#include <string>
#include <vector>
#include "timer.h"

namespace TimerApp {

class DatabaseManager {
public:
    struct ConnectionConfig {
        std::string host = "localhost";
        int port = 5432;
        std::string database_name = "timer_db";
        std::string username = "timer_user";
        std::string password = "timer_pass";
        int connection_timeout = 30;
    };
    
    explicit DatabaseManager(const ConnectionConfig& config);
    ~DatabaseManager();
    
    // Connection management
    bool connect();
    void disconnect();
    bool is_connected() const;
    
    // Session management
    bool save_session(const Timer::Session& session);
    bool update_session(const Timer::Session& session);
    bool delete_session(const std::string& session_id);
    std::vector<Timer::Session> get_sessions(const std::string& project_name = "");
    Timer::Session get_session(const std::string& session_id);
    
    // Project management
    bool create_project(const std::string& name, const std::string& description = "");
    bool delete_project(const std::string& name);
    std::vector<std::string> get_projects();
    
    // Statistics
    struct DatabaseStatistics {
        size_t total_sessions;
        size_t total_projects;
        std::chrono::duration<double> total_time;
        std::string most_active_project;
        std::chrono::system_clock::time_point first_session;
        std::chrono::system_clock::time_point last_session;
    };
    
    DatabaseStatistics get_statistics();
    
    // Backup and restore
    bool backup_database(const std::string& backup_file);
    bool restore_database(const std::string& backup_file);
    
private:
    ConnectionConfig config_;
    std::unique_ptr<pqxx::connection> connection_;
    
    // Helper methods
    std::string build_connection_string() const;
    Timer::Session session_from_row(const pqxx::row& row);
    bool execute_query(const std::string& query);
    bool table_exists(const std::string& table_name);
    void create_tables();
};

} // namespace TimerApp

#endif // DATABASE_MANAGER_H
// src/database/database_manager.cpp
#include "database_manager.h"
#include "logger.h"
#include <sstream>
#include <iomanip>

namespace TimerApp {

DatabaseManager::DatabaseManager(const ConnectionConfig& config) 
    : config_(config) {
    Logger::get_instance().info("DatabaseManager initialized");
}

DatabaseManager::~DatabaseManager() {
    disconnect();
}

bool DatabaseManager::connect() {
    try {
        std::string connection_string = build_connection_string();
        connection_ = std::make_unique<pqxx::connection>(connection_string);
        
        if (connection_->is_open()) {
            Logger::get_instance().info("Connected to PostgreSQL database");
            
            // Create tables if they don't exist
            create_tables();
            
            return true;
        } else {
            Logger::get_instance().error("Failed to connect to database");
            return false;
        }
    } catch (const std::exception& e) {
        Logger::get_instance().error("Database connection error: " + std::string(e.what()));
        return false;
    }
}

void DatabaseManager::disconnect() {
    if (connection_ && connection_->is_open()) {
        connection_->close();
        Logger::get_instance().info("Disconnected from database");
    }
}

bool DatabaseManager::is_connected() const {
    return connection_ && connection_->is_open();
}

bool DatabaseManager::save_session(const Timer::Session& session) {
    if (!is_connected()) {
        Logger::get_instance().error("Not connected to database");
        return false;
    }
    
    try {
        pqxx::work transaction(*connection_);
        
        std::string query = R"(
            INSERT INTO sessions (id, project_name, description, start_time, end_time, 
                                 duration_seconds, tags, is_active)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
            ON CONFLICT (id) DO UPDATE SET
                project_name = EXCLUDED.project_name,
                description = EXCLUDED.description,
                start_time = EXCLUDED.start_time,
                end_time = EXCLUDED.end_time,
                duration_seconds = EXCLUDED.duration_seconds,
                tags = EXCLUDED.tags,
                is_active = EXCLUDED.is_active
        )";
        
        auto start_time_t = std::chrono::system_clock::to_time_t(session.start_time);
        auto end_time_t = std::chrono::system_clock::to_time_t(session.end_time);
        
        transaction.exec_params(query,
                               session.id,
                               session.project_name,
                               session.description,
                               start_time_t,
                               end_time_t,
                               session.duration.count(),
                               session.tags,
                               session.is_active);
        
        transaction.commit();
        
        Logger::get_instance().info("Session saved: " + session.id);
        return true;
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to save session: " + std::string(e.what()));
        return false;
    }
}

std::vector<Timer::Session> DatabaseManager::get_sessions(const std::string& project_name) {
    std::vector<Timer::Session> sessions;
    
    if (!is_connected()) {
        Logger::get_instance().error("Not connected to database");
        return sessions;
    }
    
    try {
        pqxx::work transaction(*connection_);
        
        std::string query = "SELECT * FROM sessions";
        if (!project_name.empty()) {
            query += " WHERE project_name = $1";
        }
        query += " ORDER BY start_time DESC";
        
        pqxx::result result;
        if (!project_name.empty()) {
            result = transaction.exec_params(query, project_name);
        } else {
            result = transaction.exec(query);
        }
        
        for (const auto& row : result) {
            sessions.push_back(session_from_row(row));
        }
        
        Logger::get_instance().info("Retrieved " + std::to_string(sessions.size()) + " sessions");
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to retrieve sessions: " + std::string(e.what()));
    }
    
    return sessions;
}

Timer::Session DatabaseManager::get_session(const std::string& session_id) {
    Timer::Session session;
    
    if (!is_connected()) {
        Logger::get_instance().error("Not connected to database");
        return session;
    }
    
    try {
        pqxx::work transaction(*connection_);
        
        std::string query = "SELECT * FROM sessions WHERE id = $1";
        pqxx::result result = transaction.exec_params(query, session_id);
        
        if (!result.empty()) {
            session = session_from_row(result[0]);
        }
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to retrieve session: " + std::string(e.what()));
    }
    
    return session;
}

bool DatabaseManager::create_project(const std::string& name, const std::string& description) {
    if (!is_connected()) {
        Logger::get_instance().error("Not connected to database");
        return false;
    }
    
    try {
        pqxx::work transaction(*connection_);
        
        std::string query = R"(
            INSERT INTO projects (name, description, created_at)
            VALUES ($1, $2, NOW())
            ON CONFLICT (name) DO NOTHING
        )";
        
        transaction.exec_params(query, name, description);
        transaction.commit();
        
        Logger::get_instance().info("Project created: " + name);
        return true;
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to create project: " + std::string(e.what()));
        return false;
    }
}

std::vector<std::string> DatabaseManager::get_projects() {
    std::vector<std::string> projects;
    
    if (!is_connected()) {
        Logger::get_instance().error("Not connected to database");
        return projects;
    }
    
    try {
        pqxx::work transaction(*connection_);
        
        std::string query = "SELECT name FROM projects ORDER BY name";
        pqxx::result result = transaction.exec(query);
        
        for (const auto& row : result) {
            projects.push_back(row[0].as<std::string>());
        }
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to retrieve projects: " + std::string(e.what()));
    }
    
    return projects;
}

DatabaseManager::DatabaseStatistics DatabaseManager::get_statistics() {
    DatabaseStatistics stats{};
    
    if (!is_connected()) {
        Logger::get_instance().error("Not connected to database");
        return stats;
    }
    
    try {
        pqxx::work transaction(*connection_);
        
        // Total sessions
        pqxx::result result = transaction.exec("SELECT COUNT(*) FROM sessions");
        stats.total_sessions = result[0][0].as<size_t>();
        
        // Total projects
        result = transaction.exec("SELECT COUNT(*) FROM projects");
        stats.total_projects = result[0][0].as<size_t>();
        
        // Total time
        result = transaction.exec("SELECT SUM(duration_seconds) FROM sessions");
        if (!result[0][0].is_null()) {
            stats.total_time = std::chrono::duration<double>(result[0][0].as<double>());
        }
        
        // Most active project
        result = transaction.exec(R"(
            SELECT project_name, COUNT(*) as session_count
            FROM sessions
            GROUP BY project_name
            ORDER BY session_count DESC
            LIMIT 1
        )");
        
        if (!result.empty()) {
            stats.most_active_project = result[0][0].as<std::string>();
        }
        
        // First and last session
        result = transaction.exec("SELECT MIN(start_time), MAX(start_time) FROM sessions");
        if (!result.empty() && !result[0][0].is_null()) {
            stats.first_session = std::chrono::system_clock::from_time_t(result[0][0].as<time_t>());
            stats.last_session = std::chrono::system_clock::from_time_t(result[0][1].as<time_t>());
        }
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to get statistics: " + std::string(e.what()));
    }
    
    return stats;
}

std::string DatabaseManager::build_connection_string() const {
    std::stringstream ss;
    ss << "host=" << config_.host
       << " port=" << config_.port
       << " dbname=" << config_.database_name
       << " user=" << config_.username
       << " password=" << config_.password
       << " connect_timeout=" << config_.connection_timeout;
    
    return ss.str();
}

Timer::Session DatabaseManager::session_from_row(const pqxx::row& row) {
    Timer::Session session;
    
    session.id = row[0].as<std::string>();
    session.project_name = row[1].as<std::string>();
    session.description = row[2].as<std::string>();
    session.start_time = std::chrono::system_clock::from_time_t(row[3].as<time_t>());
    session.end_time = std::chrono::system_clock::from_time_t(row[4].as<time_t>());
    session.duration = std::chrono::duration<double>(row[5].as<double>());
    session.tags = row[6].as<std::string>();
    session.is_active = row[7].as<bool>();
    
    return session;
}

void DatabaseManager::create_tables() {
    try {
        pqxx::work transaction(*connection_);
        
        // Create projects table
        transaction.exec(R"(
            CREATE TABLE IF NOT EXISTS projects (
                name VARCHAR(255) PRIMARY KEY,
                description TEXT,
                created_at TIMESTAMP DEFAULT NOW()
            )
        )");
        
        // Create sessions table
        transaction.exec(R"(
            CREATE TABLE IF NOT EXISTS sessions (
                id VARCHAR(255) PRIMARY KEY,
                project_name VARCHAR(255) NOT NULL,
                description TEXT,
                start_time TIMESTAMP NOT NULL,
                end_time TIMESTAMP,
                duration_seconds DOUBLE PRECISION,
                tags TEXT,
                is_active BOOLEAN DEFAULT FALSE,
                created_at TIMESTAMP DEFAULT NOW(),
                FOREIGN KEY (project_name) REFERENCES projects(name)
            )
        )");
        
        // Create indexes
        transaction.exec("CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_name)");
        transaction.exec("CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time)");
        transaction.exec("CREATE INDEX IF NOT EXISTS idx_sessions_active ON sessions(is_active)");
        
        transaction.commit();
        
        Logger::get_instance().info("Database tables created successfully");
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to create tables: " + std::string(e.what()));
    }
}

} // namespace TimerApp

CMake Build System

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(Timer VERSION 1.0.0 LANGUAGES CXX)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Compiler flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG")
endif()

# Find required packages
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)

# Find PostgreSQL
find_package(PostgreSQL REQUIRED)
if(NOT PostgreSQL_FOUND)
    message(FATAL_ERROR "PostgreSQL not found. Please install libpq-dev or postgresql-devel")
endif()

# Find libpqxx
pkg_check_modules(LIBPQXX REQUIRED libpqxx)
if(NOT LIBPQXX_FOUND)
    message(FATAL_ERROR "libpqxx not found. Please install libpqxx-dev")
endif()

# Include directories
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${LIBPQXX_INCLUDE_DIRS})

# Source files
set(CORE_SOURCES
    src/core/timer.cpp
    src/core/time_utils.cpp
    src/core/session_manager.cpp
)

set(DATABASE_SOURCES
    src/database/database_manager.cpp
    src/database/session_dao.cpp
    src/database/project_dao.cpp
)

set(UI_SOURCES
    src/ui/console_ui.cpp
    src/ui/gui_ui.cpp
)

set(CONFIG_SOURCES
    src/config/config_manager.cpp
)

set(UTILS_SOURCES
    src/utils/logger.cpp
    src/utils/file_utils.cpp
)

set(ALL_SOURCES
    ${CORE_SOURCES}
    ${DATABASE_SOURCES}
    ${UI_SOURCES}
    ${CONFIG_SOURCES}
    ${UTILS_SOURCES}
)

# Header files
set(HEADERS
    include/timer/timer.h
    include/timer/database_manager.h
    include/timer/config_manager.h
)

# Create library
add_library(TimerLib STATIC ${ALL_SOURCES} ${HEADERS})

# Link libraries
target_link_libraries(TimerLib 
    ${LIBPQXX_LIBRARIES}
    Threads::Threads
)

# Link PostgreSQL
target_link_libraries(TimerLib ${PostgreSQL_LIBRARIES})

# Include directories for the library
target_include_directories(TimerLib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# Create executable
add_executable(timer src/main.cpp)
target_link_libraries(timer TimerLib)

# Optional GUI executable
option(BUILD_GUI "Build GUI version" OFF)
if(BUILD_GUI)
    find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
    add_executable(timer_gui src/gui_main.cpp)
    target_link_libraries(timer_gui TimerLib Qt5::Core Qt5::Widgets)
endif()

# Testing
enable_testing()
add_subdirectory(tests)

# Installation
install(TARGETS TimerLib timer
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)

install(DIRECTORY include/ DESTINATION include)

# CPack configuration
set(CPACK_PACKAGE_NAME "Timer")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A comprehensive timer application")
set(CPACK_PACKAGE_VENDOR "Safa Bayar")
set(CPACK_PACKAGE_CONTACT "bayarsafa@gmail.com")

include(CPack)

# Configuration file
configure_file(
    "${CMAKE_SOURCE_DIR}/config.h.in"
    "${CMAKE_BINARY_DIR}/config.h"
)

# Add configuration to include path
target_include_directories(TimerLib PRIVATE ${CMAKE_BINARY_DIR})

# Print configuration summary
message(STATUS "Timer Configuration Summary:")
message(STATUS "  Version: ${PROJECT_VERSION}")
message(STATUS "  C++ Standard: ${CMAKE_CXX_STANDARD}")
message(STATUS "  Build Type: ${CMAKE_BUILD_TYPE}")
message(STATUS "  PostgreSQL: ${PostgreSQL_VERSION}")
message(STATUS "  libpqxx: ${LIBPQXX_VERSION}")
message(STATUS "  GUI Build: ${BUILD_GUI}")

Console User Interface

// src/ui/console_ui.cpp
#include "console_ui.h"
#include "logger.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <algorithm>

namespace TimerApp {

ConsoleUI::ConsoleUI() : running_(false) {
    Logger::get_instance().info("Console UI initialized");
}

void ConsoleUI::run() {
    running_ = true;
    
    std::cout << "=== Timer Application ===" << std::endl;
    std::cout << "Commands: start, pause, resume, stop, reset, status, stats, quit" << std::endl;
    std::cout << "Type 'help' for more information" << std::endl;
    
    while (running_) {
        std::cout << "\n> ";
        std::string command;
        std::getline(std::cin, command);
        
        process_command(command);
    }
}

void ConsoleUI::process_command(const std::string& command) {
    std::istringstream iss(command);
    std::string cmd;
    iss >> cmd;
    
    std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower);
    
    if (cmd == "start") {
        handle_start_command(iss);
    } else if (cmd == "pause") {
        handle_pause_command();
    } else if (cmd == "resume") {
        handle_resume_command();
    } else if (cmd == "stop") {
        handle_stop_command();
    } else if (cmd == "reset") {
        handle_reset_command();
    } else if (cmd == "status") {
        handle_status_command();
    } else if (cmd == "stats") {
        handle_stats_command();
    } else if (cmd == "projects") {
        handle_projects_command();
    } else if (cmd == "sessions") {
        handle_sessions_command(iss);
    } else if (cmd == "help") {
        handle_help_command();
    } else if (cmd == "quit" || cmd == "exit") {
        handle_quit_command();
    } else {
        std::cout << "Unknown command: " << cmd << std::endl;
        std::cout << "Type 'help' for available commands" << std::endl;
    }
}

void ConsoleUI::handle_start_command(std::istringstream& iss) {
    std::string project_name;
    std::string description;
    
    iss >> project_name;
    if (project_name.empty()) {
        project_name = "Default";
    }
    
    // Get description if provided
    std::string remaining;
    std::getline(iss, remaining);
    if (!remaining.empty()) {
        description = remaining.substr(1); // Remove leading space
    }
    
    timer_.set_project_name(project_name);
    timer_.set_description(description);
    timer_.start();
    
    std::cout << "Timer started for project: " << project_name << std::endl;
    if (!description.empty()) {
        std::cout << "Description: " << description << std::endl;
    }
}

void ConsoleUI::handle_pause_command() {
    timer_.pause();
    std::cout << "Timer paused" << std::endl;
}

void ConsoleUI::handle_resume_command() {
    timer_.resume();
    std::cout << "Timer resumed" << std::endl;
}

void ConsoleUI::handle_stop_command() {
    timer_.stop();
    std::cout << "Timer stopped" << std::endl;
}

void ConsoleUI::handle_reset_command() {
    timer_.reset();
    std::cout << "Timer reset" << std::endl;
}

void ConsoleUI::handle_status_command() {
    auto state = timer_.get_state();
    auto elapsed = timer_.get_elapsed_time();
    auto total = timer_.get_total_time();
    
    std::cout << "\n=== Timer Status ===" << std::endl;
    std::cout << "State: " << state_to_string(state) << std::endl;
    std::cout << "Elapsed Time: " << format_duration(elapsed) << std::endl;
    std::cout << "Total Time: " << format_duration(total) << std::endl;
    
    const auto& session = timer_.get_current_session();
    if (!session.id.empty()) {
        std::cout << "Current Session: " << session.id << std::endl;
        std::cout << "Project: " << session.project_name << std::endl;
        if (!session.description.empty()) {
            std::cout << "Description: " << session.description << std::endl;
        }
    }
}

void ConsoleUI::handle_stats_command() {
    auto stats = timer_.get_statistics();
    
    std::cout << "\n=== Statistics ===" << std::endl;
    std::cout << "Total Time: " << format_duration(stats.total_time) << std::endl;
    std::cout << "Total Sessions: " << stats.total_sessions << std::endl;
    std::cout << "Active Sessions: " << stats.active_sessions << std::endl;
    std::cout << "Average Session Time: " << format_duration(stats.average_session_time) << std::endl;
    
    if (!stats.most_used_project.empty()) {
        std::cout << "Most Used Project: " << stats.most_used_project << std::endl;
    }
}

void ConsoleUI::handle_projects_command() {
    if (!db_manager_) {
        std::cout << "Database not connected" << std::endl;
        return;
    }
    
    auto projects = db_manager_->get_projects();
    
    std::cout << "\n=== Projects ===" << std::endl;
    if (projects.empty()) {
        std::cout << "No projects found" << std::endl;
    } else {
        for (const auto& project : projects) {
            std::cout << "- " << project << std::endl;
        }
    }
}

void ConsoleUI::handle_sessions_command(std::istringstream& iss) {
    std::string project_name;
    iss >> project_name;
    
    if (!db_manager_) {
        std::cout << "Database not connected" << std::endl;
        return;
    }
    
    auto sessions = db_manager_->get_sessions(project_name);
    
    std::cout << "\n=== Sessions ===" << std::endl;
    if (sessions.empty()) {
        std::cout << "No sessions found" << std::endl;
    } else {
        std::cout << std::left << std::setw(20) << "ID" 
                  << std::setw(15) << "Project"
                  << std::setw(20) << "Duration"
                  << std::setw(20) << "Start Time" << std::endl;
        std::cout << std::string(75, '-') << std::endl;
        
        for (const auto& session : sessions) {
            std::cout << std::left << std::setw(20) << session.id.substr(0, 19)
                      << std::setw(15) << session.project_name.substr(0, 14)
                      << std::setw(20) << format_duration(session.duration)
                      << std::setw(20) << format_time(session.start_time) << std::endl;
        }
    }
}

void ConsoleUI::handle_help_command() {
    std::cout << "\n=== Available Commands ===" << std::endl;
    std::cout << "start [project] [description] - Start timer for project" << std::endl;
    std::cout << "pause                        - Pause current timer" << std::endl;
    std::cout << "resume                       - Resume paused timer" << std::endl;
    std::cout << "stop                         - Stop current timer" << std::endl;
    std::cout << "reset                        - Reset timer" << std::endl;
    std::cout << "status                       - Show current status" << std::endl;
    std::cout << "stats                        - Show statistics" << std::endl;
    std::cout << "projects                     - List all projects" << std::endl;
    std::cout << "sessions [project]           - List sessions" << std::endl;
    std::cout << "help                         - Show this help" << std::endl;
    std::cout << "quit/exit                    - Exit application" << std::endl;
}

void ConsoleUI::handle_quit_command() {
    if (timer_.get_state() == Timer::State::RUNNING) {
        std::cout << "Timer is running. Stop it first or it will be saved." << std::endl;
        std::cout << "Continue? (y/n): ";
        char response;
        std::cin >> response;
        
        if (response == 'y' || response == 'Y') {
            timer_.stop();
        }
    }
    
    running_ = false;
    std::cout << "Goodbye!" << std::endl;
}

std::string ConsoleUI::state_to_string(Timer::State state) {
    switch (state) {
        case Timer::State::STOPPED: return "Stopped";
        case Timer::State::RUNNING: return "Running";
        case Timer::State::PAUSED: return "Paused";
        default: return "Unknown";
    }
}

std::string ConsoleUI::format_duration(const std::chrono::duration<double>& duration) {
    auto total_seconds = static_cast<int>(duration.count());
    auto hours = total_seconds / 3600;
    auto minutes = (total_seconds % 3600) / 60;
    auto seconds = total_seconds % 60;
    
    std::ostringstream oss;
    oss << std::setfill('0') << std::setw(2) << hours << ":"
        << std::setw(2) << minutes << ":" << std::setw(2) << seconds;
    
    return oss.str();
}

std::string ConsoleUI::format_time(const std::chrono::system_clock::time_point& time_point) {
    auto time_t = std::chrono::system_clock::to_time_t(time_point);
    auto tm = *std::localtime(&time_t);
    
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
    
    return oss.str();
}

} // namespace TimerApp

Testing Framework

Unit Tests

// tests/unit/test_timer.cpp
#include <gtest/gtest.h>
#include "timer.h"
#include <thread>
#include <chrono>

class TimerTest : public ::testing::Test {
protected:
    void SetUp() override {
        timer = std::make_unique<TimerApp::Timer>("TestProject");
    }
    
    std::unique_ptr<TimerApp::Timer> timer;
};

TEST_F(TimerTest, InitialState) {
    EXPECT_EQ(timer->get_state(), TimerApp::Timer::State::STOPPED);
    EXPECT_EQ(timer->get_elapsed_time().count(), 0);
}

TEST_F(TimerTest, StartTimer) {
    timer->start();
    EXPECT_EQ(timer->get_state(), TimerApp::Timer::State::RUNNING);
    
    // Wait a bit and check elapsed time
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    EXPECT_GT(timer->get_elapsed_time().count(), 0);
}

TEST_F(TimerTest, PauseResume) {
    timer->start();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    auto elapsed_before_pause = timer->get_elapsed_time();
    timer->pause();
    EXPECT_EQ(timer->get_state(), TimerApp::Timer::State::PAUSED);
    
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    auto elapsed_after_pause = timer->get_elapsed_time();
    
    // Elapsed time should not change while paused
    EXPECT_EQ(elapsed_before_pause.count(), elapsed_after_pause.count());
    
    timer->resume();
    EXPECT_EQ(timer->get_state(), TimerApp::Timer::State::RUNNING);
}

TEST_F(TimerTest, StopTimer) {
    timer->start();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    timer->stop();
    EXPECT_EQ(timer->get_state(), TimerApp::Timer::State::STOPPED);
    
    auto sessions = timer->get_sessions();
    EXPECT_EQ(sessions.size(), 1);
    EXPECT_GT(sessions[0].duration.count(), 0);
}

TEST_F(TimerTest, ResetTimer) {
    timer->start();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    timer->stop();
    
    auto sessions_before = timer->get_sessions().size();
    timer->reset();
    
    EXPECT_EQ(timer->get_state(), TimerApp::Timer::State::STOPPED);
    EXPECT_EQ(timer->get_elapsed_time().count(), 0);
    EXPECT_EQ(timer->get_sessions().size(), sessions_before);
}

TEST_F(TimerTest, Statistics) {
    timer->start();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    timer->stop();
    
    auto stats = timer->get_statistics();
    EXPECT_EQ(stats.total_sessions, 1);
    EXPECT_GT(stats.total_time.count(), 0);
}

Lessons Learned

C++ System Programming

  • Modern C++: Use of C++17 features and best practices
  • Memory Management: Smart pointers and RAII principles
  • Thread Safety: Proper synchronization and thread management
  • Error Handling: Comprehensive exception handling

Database Integration

  • PostgreSQL: Advanced PostgreSQL integration with libpqxx
  • Connection Management: Proper database connection handling
  • Transaction Management: ACID compliance and transaction safety
  • Performance: Optimized queries and indexing strategies

Build Systems

  • CMake: Modern CMake practices and cross-platform builds
  • Dependencies: Proper dependency management and linking
  • Configuration: Build-time configuration and feature flags
  • Packaging: CPack integration for distribution

Software Architecture

  • Modular Design: Clean separation of concerns
  • Interface Design: Well-defined APIs and abstractions
  • Testing: Comprehensive unit and integration testing
  • Documentation: Clear code documentation and user guides

Future Enhancements

Advanced Features

  • Web Interface: REST API and web-based UI
  • Mobile App: Cross-platform mobile application
  • Team Features: Multi-user support and collaboration
  • Reporting: Advanced reporting and analytics

Technical Improvements

  • Performance: Optimized database queries and caching
  • Scalability: Support for large datasets and concurrent users
  • Security: Authentication and authorization
  • Integration: Third-party service integrations

Conclusion

The Timer application demonstrates comprehensive C++ system programming skills and database integration expertise. Key achievements include:

  • System Programming: Advanced C++ system programming techniques
  • Database Integration: Comprehensive PostgreSQL integration
  • Build System: Modern CMake build system implementation
  • User Interface: Console-based user interface
  • Testing: Comprehensive unit testing framework
  • Documentation: Clear documentation and user guides

The project is available on GitHub and serves as a comprehensive example of C++ system programming and database application development.


This project represents my deep dive into C++ system programming and demonstrates how modern C++ can be used to build robust, efficient applications with database integration. The lessons learned here continue to influence my approach to system-level programming and database application development.