From e2d31dc0534053aa3d6eab3574ba7bda64cec56a Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Tue, 27 Jan 2026 09:36:44 -0800 Subject: [PATCH 1/2] Adds plan doc for creating our shared nextcloud library. --- .plans/CreateLibNextcloud.md | 749 +++++++++++++++++++++++++++++++++++ 1 file changed, 749 insertions(+) create mode 100644 .plans/CreateLibNextcloud.md diff --git a/.plans/CreateLibNextcloud.md b/.plans/CreateLibNextcloud.md new file mode 100644 index 0000000..4c11a4f --- /dev/null +++ b/.plans/CreateLibNextcloud.md @@ -0,0 +1,749 @@ +# libnextcloud Shared Library Plan + +**Created**: 2026-01-27 +**Status**: 📋 Planning +**Target**: Cross-platform C++17 shared library for Nextcloud connectivity +**Branch**: `create-libnextcloud` + +--- + +## Overview + +Create `libnextcloud` - a cross-platform C++17 shared library that provides Nextcloud connectivity for embedded devices and homebrew platforms. The library will handle all Nextcloud communication, authentication, file operations, and settings management through a clean, platform-agnostic API. + +**Design Philosophy**: +- **Platform-agnostic**: Works on 3DS, Switch, Wii U, Vita, PC, etc. +- **Minimal dependencies**: Only libcurl, mbedTLS, and tinyxml2 +- **Header-only where possible**: nlohmann/json for configuration +- **Modern C++17**: Using standard library features +- **Testable**: Mock-friendly design with dependency injection +- **Documented**: Doxygen API documentation + +--- + +## Project Structure + +``` +shared/ +├── include/ +│ └── nextcloud/ +│ ├── client.hpp # Main client interface +│ ├── webdav_client.hpp # WebDAV protocol implementation +│ ├── auth.hpp # Authentication manager +│ ├── upload_manager.hpp # File upload with chunking +│ ├── folder_manager.hpp # Remote folder operations +│ ├── config.hpp # Configuration management +│ ├── types.hpp # Common types and enums +│ └── version.hpp # Library version info +├── src/ +│ ├── webdav_client.cpp +│ ├── auth.cpp +│ ├── upload_manager.cpp +│ ├── folder_manager.cpp +│ └── config.cpp +├── tests/ +│ ├── webdav_client_test.cpp +│ ├── auth_test.cpp +│ ├── upload_manager_test.cpp +│ ├── folder_manager_test.cpp +│ ├── config_test.cpp +│ └── mocks/ +│ ├── mock_http_client.hpp +│ └── mock_xml_parser.hpp +├── examples/ +│ ├── simple_upload.cpp +│ ├── folder_listing.cpp +│ └── authentication.cpp +├── docs/ +│ ├── api.md # API documentation +│ ├── architecture.md # Design overview +│ └── Doxyfile # Doxygen configuration +├── CMakeLists.txt +└── README.md +``` + +--- + +## Dependencies + +### Required +- **libcurl** (7.68+): HTTP/HTTPS client +- **mbedTLS** (2.16+): SSL/TLS support (works on embedded) +- **tinyxml2** (9.0+): Lightweight XML parsing + +### Header-Only +- **nlohmann/json** (3.10+): JSON parsing/serialization + +### Testing +- **Google Test** (1.11+): Unit testing framework +- **Google Mock** (1.11+): Mocking framework + +### Build Tools +- **CMake** (3.15+): Build system +- **C++17 compiler**: GCC 7+, Clang 5+, MSVC 2017+ + +--- + +## Implementation Phases + +## Phase 1: Project Scaffolding (Issue #??) + +**Goal**: Set up the library project structure and build system. + +### Tasks +- [ ] Create `shared/` directory structure +- [ ] Set up CMakeLists.txt with: + - Library target (static/shared) + - C++17 standard + - Include directories + - Dependency linking (curl, mbedtls, tinyxml2) + - Install rules + - Export config for downstream projects +- [ ] Create version.hpp with semantic versioning +- [ ] Create types.hpp with common enums and structures: + - `NextcloudError` enum + - `UploadProgress` struct + - `FileInfo` struct + - `FolderInfo` struct +- [ ] Set up Google Test framework +- [ ] Create basic README for the library +- [ ] Add pkg-config file generation +- [ ] Verify library compiles as standalone project + +### Acceptance Criteria +- [ ] CMake builds library successfully +- [ ] Can link against library from test project +- [ ] Version info accessible at runtime +- [ ] Ready for component implementation + +**Estimated Time**: 4-6 hours +**Priority**: Critical (blocks all other work) + +--- + +## Phase 2: WebDAV Client (Issue #8) + +**Goal**: Implement HTTP/WebDAV communication layer. + +### Tasks +- [ ] Create `webdav_client.hpp` interface +- [ ] Implement HTTP wrapper around libcurl: + - GET, PUT, POST, DELETE, custom methods + - Request/response headers + - Request body (string, stream) + - Response body handling + - Timeout configuration + - User-Agent setting +- [ ] Add SSL/TLS support with mbedTLS +- [ ] Implement WebDAV methods: + - PROPFIND (list files/folders) + - MKCOL (create folder) + - PUT (upload file) + - DELETE (remove file/folder) + - MOVE (rename/move) + - COPY (duplicate) +- [ ] Parse WebDAV XML responses (using tinyxml2): + - Multi-status responses + - File properties (size, mtime, type) + - Error responses +- [ ] Implement error handling: + - HTTP status codes + - Network errors + - XML parsing errors + - Error code mapping +- [ ] Add timeout and retry logic +- [ ] Write unit tests with mocked HTTP +- [ ] Document API in Doxygen format + +### API Design +```cpp +class WebDAVClient { +public: + WebDAVClient(const std::string& baseUrl); + + // Core HTTP + Response get(const std::string& path); + Response put(const std::string& path, const std::vector& data); + Response delete_(const std::string& path); + + // WebDAV operations + std::vector propfind(const std::string& path, int depth = 1); + bool mkcol(const std::string& path); + bool move(const std::string& from, const std::string& to); + bool copy(const std::string& from, const std::string& to); + + // Configuration + void setTimeout(int seconds); + void setAuthHeader(const std::string& header); +}; +``` + +### Acceptance Criteria +- [ ] All WebDAV methods implemented +- [ ] SSL/TLS connections working +- [ ] XML responses correctly parsed +- [ ] Error handling comprehensive +- [ ] Unit tests with >80% coverage +- [ ] API documented +- [ ] Works with test Nextcloud servers + +**Estimated Time**: 12-16 hours +**Priority**: Critical (foundation for all features) +**Dependencies**: Phase 1 complete + +--- + +## Phase 3: Authentication System (Issue #9) + +**Goal**: Handle authentication with Nextcloud servers. + +### Tasks +- [ ] Create `auth.hpp` interface +- [ ] Implement HTTP Basic Authentication: + - Base64 encoding + - Authorization header generation + - Credential storage +- [ ] Add authentication verification: + - Test connection to server + - Validate credentials + - Return meaningful error codes +- [ ] Secure credential handling: + - In-memory only (no disk storage by default) + - Platform-specific secure storage hooks +- [ ] Session management: + - Keep-alive connections + - Connection pooling + - Timeout handling +- [ ] Future: OAuth2 support (stubbed for now) +- [ ] Write unit tests for auth flows +- [ ] Document security considerations + +### API Design +```cpp +class AuthManager { +public: + // Basic Auth + void setBasicAuth(const std::string& username, const std::string& password); + std::string getAuthHeader() const; + + // Verification + bool verifyConnection(WebDAVClient& client); + + // Session management + void clearCredentials(); + bool hasCredentials() const; +}; +``` + +### Acceptance Criteria +- [ ] Basic auth working with real Nextcloud +- [ ] Credentials never logged or leaked +- [ ] Connection verification works +- [ ] Error messages helpful +- [ ] Unit tests cover edge cases +- [ ] API documented + +**Estimated Time**: 6-8 hours +**Priority**: High +**Dependencies**: Phase 2 complete + +--- + +## Phase 4: Upload Manager (Issue #10) + +**Goal**: Implement file upload with chunking and progress tracking. + +### Tasks +- [ ] Create `upload_manager.hpp` interface +- [ ] Implement streaming upload: + - Read file in chunks + - Send via PUT requests + - Handle large files (>1GB) +- [ ] Add chunking support: + - Configurable chunk size (default: 10MB) + - Resume capability (future) + - Parallel chunks (future) +- [ ] Progress tracking: + - Callback interface + - Bytes uploaded / total + - Transfer speed + - Time remaining estimate +- [ ] Error handling and retry: + - Network failures + - Server errors (503, 507) + - Automatic retry with backoff + - Partial upload cleanup +- [ ] Path handling: + - URL encoding + - Special characters + - Nested folders +- [ ] Write unit tests with mock uploads +- [ ] Document upload best practices + +### API Design +```cpp +class UploadManager { +public: + using ProgressCallback = std::function; + + UploadManager(WebDAVClient& client, AuthManager& auth); + + // Upload operations + bool uploadFile( + const std::string& localPath, + const std::string& remotePath, + ProgressCallback callback = nullptr + ); + + // Configuration + void setChunkSize(size_t bytes); + void setRetryCount(int count); + void setTimeout(int seconds); +}; + +struct UploadProgress { + std::string filename; + size_t bytesUploaded; + size_t totalBytes; + float percentComplete; + float bytesPerSecond; + int secondsRemaining; +}; +``` + +### Acceptance Criteria +- [ ] Uploads files successfully +- [ ] Chunking works for large files +- [ ] Progress callback accurate +- [ ] Handles network failures gracefully +- [ ] Retries work correctly +- [ ] Unit tests with various file sizes +- [ ] API documented + +**Estimated Time**: 10-12 hours +**Priority**: High +**Dependencies**: Phases 2, 3 complete + +--- + +## Phase 5: Folder Management (Issue #11) + +**Goal**: Remote folder operations and navigation. + +### Tasks +- [ ] Create `folder_manager.hpp` interface +- [ ] Implement folder listing: + - List contents of remote folder + - Parse file metadata (size, mtime, type) + - Recursive listing support + - Filtering (files only, folders only) +- [ ] Folder creation: + - Create single folder + - Create nested folders (mkdir -p) + - Validate folder names +- [ ] Folder navigation: + - Path normalization + - Parent folder detection + - Breadcrumb generation +- [ ] Caching (optional): + - Cache folder contents + - TTL-based invalidation + - Memory limits +- [ ] Write unit tests +- [ ] Document folder operations + +### API Design +```cpp +class FolderManager { +public: + FolderManager(WebDAVClient& client, AuthManager& auth); + + // Folder operations + std::vector listFolder(const std::string& path); + bool createFolder(const std::string& path); + bool createFolders(const std::string& path); // mkdir -p + bool deleteFolder(const std::string& path); + bool folderExists(const std::string& path); + + // Navigation helpers + std::string getParentFolder(const std::string& path); + std::vector getBreadcrumbs(const std::string& path); +}; + +struct FileInfo { + std::string name; + std::string path; + bool isDirectory; + size_t size; + time_t modifiedTime; + std::string contentType; +}; +``` + +### Acceptance Criteria +- [ ] Lists folders correctly +- [ ] Creates folders successfully +- [ ] Handles nested paths +- [ ] Metadata parsing accurate +- [ ] Unit tests comprehensive +- [ ] API documented + +**Estimated Time**: 8-10 hours +**Priority**: Medium +**Dependencies**: Phases 2, 3 complete + +--- + +## Phase 6: Favorites and Recent Folders (Issue #12) + +**Goal**: Persistence layer for user preferences. + +### Tasks +- [ ] Create `config.hpp` interface +- [ ] Implement favorites management: + - Add favorite folder + - Remove favorite folder + - List favorites + - Reorder favorites +- [ ] Implement recent folders tracking: + - Auto-track used folders + - Configurable limit (default: 5) + - LRU eviction + - Duplicate detection +- [ ] JSON persistence: + - Save to file + - Load from file + - Handle missing/corrupt files + - Migration support +- [ ] Platform-specific paths: + - Linux: ~/.config/nextcloud-share/ + - 3DS: /nextcloud-share/ + - Switch: /switch/nextcloud-share/ +- [ ] Write unit tests +- [ ] Document config format + +### API Design +```cpp +class Config { +public: + Config(const std::string& configPath = ""); + + // Favorites + void addFavorite(const std::string& path, const std::string& name = ""); + void removeFavorite(const std::string& path); + std::vector getFavorites() const; + + // Recent folders + void addRecentFolder(const std::string& path); + std::vector getRecentFolders() const; + void setMaxRecentFolders(int count); + + // Persistence + bool load(); + bool save(); +}; + +struct FolderInfo { + std::string path; + std::string displayName; + time_t lastUsed; +}; +``` + +### Acceptance Criteria +- [ ] Favorites persist across restarts +- [ ] Recent folders tracked correctly +- [ ] JSON format clean and readable +- [ ] Handles missing config gracefully +- [ ] Unit tests cover edge cases +- [ ] API documented + +**Estimated Time**: 6-8 hours +**Priority**: Low +**Dependencies**: None (standalone) + +--- + +## Phase 7: Testing and Documentation (Issue #13) + +**Goal**: Comprehensive test coverage and documentation. + +### Tasks +- [ ] Achieve >80% code coverage +- [ ] Integration tests with real Nextcloud: + - Test against cloud.tomusan.com + - Test against disobedient.cloud + - Verify all operations work +- [ ] Mock server for offline testing: + - Mock HTTP responses + - Simulate errors + - Test edge cases +- [ ] Performance testing: + - Large file uploads + - Many small files + - Concurrent operations +- [ ] Generate Doxygen documentation +- [ ] Write architecture document +- [ ] Create usage examples +- [ ] Document build process +- [ ] Add API reference + +### Documentation Structure +``` +docs/ +├── api.md # Full API reference +├── architecture.md # Library design and internals +├── building.md # Build instructions +├── examples.md # Usage examples +├── testing.md # How to run tests +├── contributing.md # Contribution guidelines +└── changelog.md # Version history +``` + +### Acceptance Criteria +- [ ] All classes have Doxygen comments +- [ ] Code coverage >80% +- [ ] Integration tests pass +- [ ] Documentation complete +- [ ] Examples compile and run +- [ ] Ready for platform integration + +**Estimated Time**: 10-12 hours +**Priority**: High +**Dependencies**: All phases complete + +--- + +## Issues to Create + +Based on this plan, create the following Gitea issues: + +### Issue: Initialize libnextcloud Project Structure +**Labels**: library, setup, priority:critical +**Description**: +``` +Set up the shared library project structure and build system for libnextcloud. + +## Tasks +- [ ] Create `shared/` directory structure +- [ ] Set up CMakeLists.txt for library +- [ ] Create version.hpp and types.hpp +- [ ] Set up Google Test framework +- [ ] Create basic README +- [ ] Add pkg-config file generation +- [ ] Verify library compiles standalone + +## Dependencies +- libcurl 7.68+ +- mbedTLS 2.16+ +- tinyxml2 9.0+ +- Google Test 1.11+ + +## Acceptance Criteria +- [ ] CMake builds library successfully +- [ ] Can link against library from test project +- [ ] Version info accessible at runtime +- [ ] Ready for component implementation + +Estimated: 4-6 hours +Blocks: #8, #9, #10, #11, #12, #13 +``` + +### Keep Existing Issues (Update Dependencies) + +**Issue #8**: Design and implement WebDAV client +- Add dependency: "Requires libnextcloud scaffolding (new issue)" +- Priority: High +- Estimated: 12-16 hours + +**Issue #9**: Implement authentication system +- Add dependency: "Requires #8" +- Priority: High +- Estimated: 6-8 hours + +**Issue #10**: Implement file upload functionality +- Add dependency: "Requires #8, #9" +- Priority: High +- Estimated: 10-12 hours + +**Issue #11**: Implement folder management +- Add dependency: "Requires #8, #9" +- Priority: Medium +- Estimated: 8-10 hours + +**Issue #12**: Implement favorites and recent folders +- Priority: Low +- Estimated: 6-8 hours + +**Issue #13**: Write unit tests for shared library +- Add dependency: "Requires #8-#12" +- Priority: High +- Estimated: 10-12 hours + +--- + +## Build System Design + +### CMakeLists.txt Structure + +```cmake +cmake_minimum_required(VERSION 3.15) +project(libnextcloud VERSION 0.1.0 LANGUAGES CXX) + +# C++17 required +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Library sources +add_library(nextcloud + src/webdav_client.cpp + src/auth.cpp + src/upload_manager.cpp + src/folder_manager.cpp + src/config.cpp +) + +# Include directories +target_include_directories(nextcloud + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +# Dependencies +find_package(CURL REQUIRED) +find_package(MbedTLS REQUIRED) +find_package(tinyxml2 REQUIRED) + +target_link_libraries(nextcloud + PUBLIC + CURL::libcurl + MbedTLS::mbedtls + tinyxml2::tinyxml2 +) + +# Testing +if(BUILD_TESTING) + enable_testing() + add_subdirectory(tests) +endif() + +# Install rules +install(TARGETS nextcloud EXPORT nextcloudTargets) +install(DIRECTORY include/ DESTINATION include) +install(EXPORT nextcloudTargets + FILE nextcloudTargets.cmake + NAMESPACE nextcloud:: + DESTINATION lib/cmake/nextcloud +) +``` + +### Platform-Specific Considerations + +**Nintendo 3DS**: +- Static library only (no shared objects) +- mbedTLS available via dkp-pacman +- libcurl with 3DS patches +- Filesystem: `/nextcloud-share/config.json` + +**Nintendo Switch**: +- Static library +- Standard libcurl and mbedTLS +- Filesystem: `/switch/nextcloud-share/config.json` + +**Linux/PC**: +- Shared library (.so) or static +- System libcurl and mbedTLS +- Filesystem: `~/.config/nextcloud-share/config.json` + +--- + +## Testing Strategy + +### Unit Tests +- Mock all external dependencies (HTTP, filesystem) +- Test each class in isolation +- Cover edge cases and error conditions +- Fast execution (<1 second total) + +### Integration Tests +- Real HTTP requests to test Nextcloud servers +- Require valid credentials from config.json +- Test full workflows end-to-end +- Can be skipped if credentials not available + +### Performance Tests +- Upload 1GB file and measure time +- Upload 1000 small files +- Concurrent uploads +- Memory usage profiling + +### Test Configuration +```json +{ + "test": { + "servers": [ + { + "name": "tomusan", + "url": "https://cloud.tomusan.com", + "username": "test", + "password": "xxx" + }, + { + "name": "disobedient", + "url": "https://disobedient.cloud/nextcloud", + "username": "test", + "password": "xxx" + } + ] + } +} +``` + +--- + +## Timeline Estimate + +**Total Estimated Time**: 56-74 hours + +| Phase | Tasks | Estimated Time | +|-------|-------|----------------| +| 1. Scaffolding | Project setup | 4-6 hours | +| 2. WebDAV Client | HTTP/WebDAV implementation | 12-16 hours | +| 3. Authentication | Auth system | 6-8 hours | +| 4. Upload Manager | File uploads | 10-12 hours | +| 5. Folder Management | Folder operations | 8-10 hours | +| 6. Favorites/Recent | Config persistence | 6-8 hours | +| 7. Testing/Docs | Tests and docs | 10-12 hours | + +**Realistic Timeline**: 2-3 weeks working part-time (15-20 hrs/week) + +--- + +## Success Criteria + +The library is complete when: + +- ✅ All phases implemented and tested +- ✅ Code coverage >80% +- ✅ Integration tests pass with real Nextcloud +- ✅ API documentation complete +- ✅ Examples compile and run +- ✅ Can be integrated into 3DS app +- ✅ Ready for additional platforms (Switch, Wii U, etc.) + +--- + +## Next Steps + +1. **Review this plan** - Adjust scope, estimates, priorities +2. **Create Gitea issues** - One for scaffolding, update existing issues +3. **Start Phase 1** - Set up project structure +4. **Implement phases sequentially** - Each phase depends on previous +5. **Test continuously** - Don't defer testing to the end + +--- + +**Ready to proceed?** Let's create the issues and start building! -- 2.49.1 From bed4cc01214a19f81a40330890a34a704f3e607b Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Wed, 28 Jan 2026 09:47:34 -0800 Subject: [PATCH 2/2] feat(lib): initialize libnextcloud C++17 library scaffolding (#25) --- shared/CMakeLists.txt | 131 ++++++++++++ shared/README.md | 288 +++++++++++++++++++++++++++ shared/include/nextcloud/types.hpp | 176 ++++++++++++++++ shared/include/nextcloud/version.hpp | 57 ++++++ shared/libnextcloud.pc.in | 11 + shared/nextcloudConfig.cmake.in | 5 + shared/tests/CMakeLists.txt | 30 +++ shared/tests/smoke_test.cpp | 140 +++++++++++++ 8 files changed, 838 insertions(+) create mode 100644 shared/CMakeLists.txt create mode 100644 shared/README.md create mode 100644 shared/include/nextcloud/types.hpp create mode 100644 shared/include/nextcloud/version.hpp create mode 100644 shared/libnextcloud.pc.in create mode 100644 shared/nextcloudConfig.cmake.in create mode 100644 shared/tests/CMakeLists.txt create mode 100644 shared/tests/smoke_test.cpp diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt new file mode 100644 index 0000000..bb4a3ef --- /dev/null +++ b/shared/CMakeLists.txt @@ -0,0 +1,131 @@ +cmake_minimum_required(VERSION 3.15) +project(libnextcloud VERSION 0.1.0 LANGUAGES CXX) + +# C++17 required +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Options +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) +option(BUILD_TESTING "Build tests" ON) +option(BUILD_EXAMPLES "Build examples" OFF) + +# Library sources (currently empty, will be populated as we implement components) +set(NEXTCLOUD_SOURCES + # src/webdav_client.cpp # Will be added in future + # src/auth.cpp # Will be added in future + # src/upload_manager.cpp # Will be added in future + # src/folder_manager.cpp # Will be added in future + # src/config.cpp # Will be added in future +) + +# Create library target +# For now, it's header-only (version.hpp and types.hpp) +# We'll add sources when we implement components +add_library(nextcloud INTERFACE) + +# Include directories +target_include_directories(nextcloud + INTERFACE + $ + $ +) + +# Require C++17 for consumers +target_compile_features(nextcloud INTERFACE cxx_std_17) + +# Dependencies (will be uncommented as we add implementation) +# find_package(CURL REQUIRED) +# find_package(MbedTLS REQUIRED) +# find_package(tinyxml2 REQUIRED) + +# Link libraries (commented out until we have actual implementation) +# target_link_libraries(nextcloud +# PUBLIC +# CURL::libcurl +# MbedTLS::mbedtls +# tinyxml2::tinyxml2 +# ) + +# Compiler warnings +if(MSVC) + target_compile_options(nextcloud INTERFACE /W4) +else() + target_compile_options(nextcloud INTERFACE -Wall -Wextra -Wpedantic) +endif() + +# Testing +if(BUILD_TESTING) + enable_testing() + add_subdirectory(tests) +endif() + +# Examples +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +# Install rules +install(TARGETS nextcloud + EXPORT nextcloudTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) + +install(DIRECTORY include/ + DESTINATION include + FILES_MATCHING PATTERN "*.hpp" +) + +# Generate and install CMake config files +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/nextcloudConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/nextcloudConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/nextcloudConfig.cmake" + INSTALL_DESTINATION lib/cmake/nextcloud +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/nextcloudConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/nextcloudConfigVersion.cmake" + DESTINATION lib/cmake/nextcloud +) + +install(EXPORT nextcloudTargets + FILE nextcloudTargets.cmake + NAMESPACE nextcloud:: + DESTINATION lib/cmake/nextcloud +) + +# pkg-config file +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/libnextcloud.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/libnextcloud.pc" + @ONLY +) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnextcloud.pc" + DESTINATION lib/pkgconfig +) + +# Print configuration summary +message(STATUS "") +message(STATUS "libnextcloud Configuration Summary:") +message(STATUS " Version: ${PROJECT_VERSION}") +message(STATUS " Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS " C++ standard: C++${CMAKE_CXX_STANDARD}") +message(STATUS " Shared libraries: ${BUILD_SHARED_LIBS}") +message(STATUS " Build tests: ${BUILD_TESTING}") +message(STATUS " Build examples: ${BUILD_EXAMPLES}") +message(STATUS " Install prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "") diff --git a/shared/README.md b/shared/README.md new file mode 100644 index 0000000..2f3045b --- /dev/null +++ b/shared/README.md @@ -0,0 +1,288 @@ +# libnextcloud + +Cross-platform C++17 library for Nextcloud connectivity on embedded devices and homebrew platforms. + +## Overview + +`libnextcloud` provides a clean, modern C++ API for interacting with Nextcloud servers. It's designed to work on resource-constrained devices like game console homebrew (Nintendo 3DS, Switch, Wii U) while also supporting desktop platforms. + +**Status**: 🚧 In Development - Scaffolding complete, components being implemented + +## Features (Planned) + +- ✅ **Version Management**: Semantic versioning with compile-time and runtime queries +- ✅ **Type System**: Comprehensive error codes and data structures +- 🚧 **WebDAV Client**: HTTP/HTTPS communication with Nextcloud WebDAV API +- 🚧 **Authentication**: Basic Auth (OAuth2 planned) +- 🚧 **File Upload**: Streaming uploads with chunking and progress tracking +- 🚧 **Folder Management**: List, create, navigate remote folders +- 🚧 **Favorites & Recent**: Persistent user preferences + +## Requirements + +### Build Tools +- **CMake** 3.15 or later +- **C++17 compiler**: GCC 7+, Clang 5+, MSVC 2017+ + +### Dependencies + +**Required** (when implementation complete): +- libcurl 7.68+ +- mbedTLS 2.16+ +- tinyxml2 9.0+ + +**Testing**: +- Google Test 1.11+ + +**Header-Only**: +- nlohmann/json 3.10+ (for configuration, future) + +### Platform Support + +| Platform | Status | Notes | +|----------|--------|-------| +| Linux | ✅ Development | Primary development platform | +| Nintendo 3DS | ✅ Planned | Via devkitARM in container | +| Nintendo Switch | 🔜 Planned | | +| Wii U | 🔜 Planned | | +| PlayStation Vita | 🔜 Planned | | +| Windows | 🔜 Planned | | +| macOS | 🔜 Planned | | + +## Quick Start + +### Building + +```bash +# Configure (modern CMake style) +cmake -S . -B build + +# Build +cmake --build build + +# Run tests +cmake --build build --target test +# or with CTest for detailed output: +# ctest --test-dir build --output-on-failure +``` + +### Using in Your Project + +#### CMake + +```cmake +find_package(nextcloud REQUIRED) + +add_executable(my_app main.cpp) +target_link_libraries(my_app nextcloud::nextcloud) +``` + +#### pkg-config + +```bash +g++ -std=c++17 main.cpp $(pkg-config --cflags --libs libnextcloud) +``` + +### Basic Usage + +```cpp +#include +#include +#include + +int main() { + // Get library version + std::cout << "libnextcloud v" << nextcloud::getVersionString() << std::endl; + + // Check version compatibility + if (nextcloud::isVersionAtLeast(0, 1, 0)) { + std::cout << "Version is compatible!" << std::endl; + } + + // Use error types + nextcloud::NextcloudError error = nextcloud::NextcloudError::Success; + std::cout << "Status: " << nextcloud::errorToString(error) << std::endl; + + return 0; +} +``` + +## Project Structure + +``` +shared/ +├── CMakeLists.txt # Build configuration +├── README.md # This file +├── libnextcloud.pc.in # pkg-config template +├── nextcloudConfig.cmake.in # CMake config template +├── include/ +│ └── nextcloud/ +│ ├── version.hpp # Version information +│ ├── types.hpp # Common types and errors +│ ├── webdav_client.hpp # (Coming soon) +│ ├── auth.hpp # (Coming soon) +│ ├── upload_manager.hpp # (Coming soon) +│ ├── folder_manager.hpp # (Coming soon) +│ └── config.hpp # (Coming soon) +├── src/ # Implementation files (future) +├── tests/ +│ ├── CMakeLists.txt +│ └── smoke_test.cpp # Basic tests +├── examples/ # Usage examples (future) +└── docs/ # Documentation (future) +``` + +## Building for Platforms + +### Linux (Development) + +```bash +mkdir build && cd build +cmake .. +make +make test +``` + +### Nintendo 3DS (Container) + +```bash +# From project root +podman run --rm -v .:/project:z tomusan/devkitarm-3ds:latest \ + bash -c "cd /project/shared && mkdir -p build && cd build && cmake .. && make" +``` + +## API Documentation + +### Version Information + +```cpp +#include + +// Constants +nextcloud::VERSION_MAJOR // 0 +nextcloud::VERSION_MINOR // 1 +nextcloud::VERSION_PATCH // 0 +nextcloud::VERSION_STRING // "0.1.0" + +// Functions +std::string getVersionString(); +void getVersion(int& major, int& minor, int& patch); +bool isVersionAtLeast(int major, int minor, int patch); +``` + +### Error Handling + +```cpp +#include + +// Error codes +enum class NextcloudError { + Success, + NetworkError, + AuthenticationFailed, + ServerError, + FileNotFound, + // ... more error codes +}; + +// Convert to string +const char* errorToString(NextcloudError error); + +// Result type +nextcloud::Result result; +if (result.isSuccess()) { + // Use result.value +} else { + std::cerr << result.errorMessage() << std::endl; +} +``` + +### Data Structures + +```cpp +// Upload progress +struct UploadProgress { + std::string filename; + uint64_t bytesUploaded; + uint64_t totalBytes; + float percentComplete; + float bytesPerSecond; + int secondsRemaining; +}; + +// File information +struct FileInfo { + std::string name; + std::string path; + bool isDirectory; + uint64_t size; + std::time_t modifiedTime; + std::string contentType; +}; + +// Folder information +struct FolderInfo { + std::string path; + std::string displayName; + std::time_t lastUsed; + bool isFavorite; +}; +``` + +## Testing + +```bash +# Run all tests +cd build +ctest --output-on-failure + +# Or use the custom target +make run_tests + +# Run specific test +./tests/smoke_test +``` + +## Development Status + +- ✅ **Phase 1: Scaffolding** - Complete + - Directory structure + - CMake build system + - Version and types headers + - Google Test integration + - Basic smoke tests + +- 🚧 **Phase 2: WebDAV Client** - Not started + - Issue #8 + +- 🚧 **Phase 3: Authentication** - Not started + - Issue #9 + +- 🚧 **Phase 4: Upload Manager** - Not started + - Issue #10 + +- 🚧 **Phase 5: Folder Management** - Not started + - Issue #11 + +- 🚧 **Phase 6: Favorites & Recent** - Not started + - Issue #12 + +- 🚧 **Phase 7: Testing & Docs** - Not started + - Issue #13 + +## Contributing + +See [CONTRIBUTING.md](../CONTRIBUTING.md) in the project root. + +## License + +[License TBD - to be determined] + +## Related Documentation + +- [Project Plan](../.plans/CreateLibNextcloud.md) +- [Issue #25](https://git.tomusan.com/maj/nextcloud-share/issues/25) - Initialize libnextcloud + +--- + +**Note**: This library is in active development. The API is subject to change until version 1.0.0. diff --git a/shared/include/nextcloud/types.hpp b/shared/include/nextcloud/types.hpp new file mode 100644 index 0000000..5a6740d --- /dev/null +++ b/shared/include/nextcloud/types.hpp @@ -0,0 +1,176 @@ +#ifndef NEXTCLOUD_TYPES_HPP +#define NEXTCLOUD_TYPES_HPP + +#include +#include +#include + +namespace nextcloud { + +/** + * @brief Error codes for Nextcloud operations + */ +enum class NextcloudError { + Success = 0, + + // Network errors + NetworkError, + ConnectionFailed, + Timeout, + SSLError, + + // Authentication errors + AuthenticationFailed, + InvalidCredentials, + Unauthorized, + + // Server errors + ServerError, + ServiceUnavailable, + InsufficientStorage, + + // File/path errors + FileNotFound, + PathNotFound, + InvalidPath, + FileAlreadyExists, + PathAlreadyExists, + + // Operation errors + OperationFailed, + OperationCancelled, + InvalidOperation, + + // Parsing/format errors + ParseError, + InvalidResponse, + UnsupportedFormat, + + // Configuration errors + InvalidConfiguration, + MissingConfiguration, + + // Unknown + Unknown +}; + +/** + * @brief Convert error code to human-readable string + * @param error The error code + * @return String description of the error + */ +inline const char* errorToString(NextcloudError error) { + switch (error) { + case NextcloudError::Success: return "Success"; + case NextcloudError::NetworkError: return "Network error"; + case NextcloudError::ConnectionFailed: return "Connection failed"; + case NextcloudError::Timeout: return "Operation timed out"; + case NextcloudError::SSLError: return "SSL/TLS error"; + case NextcloudError::AuthenticationFailed: return "Authentication failed"; + case NextcloudError::InvalidCredentials: return "Invalid credentials"; + case NextcloudError::Unauthorized: return "Unauthorized"; + case NextcloudError::ServerError: return "Server error"; + case NextcloudError::ServiceUnavailable: return "Service unavailable"; + case NextcloudError::InsufficientStorage: return "Insufficient storage"; + case NextcloudError::FileNotFound: return "File not found"; + case NextcloudError::PathNotFound: return "Path not found"; + case NextcloudError::InvalidPath: return "Invalid path"; + case NextcloudError::FileAlreadyExists: return "File already exists"; + case NextcloudError::PathAlreadyExists: return "Path already exists"; + case NextcloudError::OperationFailed: return "Operation failed"; + case NextcloudError::OperationCancelled: return "Operation cancelled"; + case NextcloudError::InvalidOperation: return "Invalid operation"; + case NextcloudError::ParseError: return "Parse error"; + case NextcloudError::InvalidResponse: return "Invalid response"; + case NextcloudError::UnsupportedFormat: return "Unsupported format"; + case NextcloudError::InvalidConfiguration: return "Invalid configuration"; + case NextcloudError::MissingConfiguration: return "Missing configuration"; + case NextcloudError::Unknown: return "Unknown error"; + default: return "Unrecognized error"; + } +} + +/** + * @brief Result type for operations that can fail + * @tparam T The success value type + */ +template +struct Result { + NextcloudError error; + T value; + + /** + * @brief Check if the operation was successful + * @return True if no error occurred + */ + bool isSuccess() const { return error == NextcloudError::Success; } + + /** + * @brief Check if an error occurred + * @return True if an error occurred + */ + bool isError() const { return error != NextcloudError::Success; } + + /** + * @brief Get error message + * @return Human-readable error description + */ + const char* errorMessage() const { return errorToString(error); } +}; + +/** + * @brief Upload progress information + */ +struct UploadProgress { + std::string filename; ///< Name of file being uploaded + uint64_t bytesUploaded; ///< Number of bytes uploaded so far + uint64_t totalBytes; ///< Total file size in bytes + float percentComplete; ///< Completion percentage (0.0 - 100.0) + float bytesPerSecond; ///< Current transfer speed + int secondsRemaining; ///< Estimated seconds remaining (-1 if unknown) +}; + +/** + * @brief Information about a file or directory + */ +struct FileInfo { + std::string name; ///< File or directory name + std::string path; ///< Full path on server + bool isDirectory; ///< True if this is a directory + uint64_t size; ///< File size in bytes (0 for directories) + std::time_t modifiedTime; ///< Last modification time (Unix timestamp) + std::string contentType; ///< MIME type (e.g., "image/png") + std::string etag; ///< ETag for change detection +}; + +/** + * @brief Information about a favorite or recent folder + */ +struct FolderInfo { + std::string path; ///< Full path on server + std::string displayName; ///< User-friendly name + std::time_t lastUsed; ///< Last access time (Unix timestamp) + bool isFavorite; ///< True if marked as favorite +}; + +/** + * @brief HTTP response information + */ +struct HttpResponse { + int statusCode; ///< HTTP status code (e.g., 200, 404) + std::string body; ///< Response body + std::string contentType; ///< Content-Type header value + NextcloudError error; ///< Error code if request failed + + /** + * @brief Check if response indicates success (2xx status code) + * @return True if status code is 200-299 + */ + bool isSuccess() const { + return statusCode >= 200 && statusCode < 300 && error == NextcloudError::Success; + } +}; + +} // namespace nextcloud + +#endif // NEXTCLOUD_TYPES_HPP diff --git a/shared/include/nextcloud/version.hpp b/shared/include/nextcloud/version.hpp new file mode 100644 index 0000000..11f7168 --- /dev/null +++ b/shared/include/nextcloud/version.hpp @@ -0,0 +1,57 @@ +#ifndef NEXTCLOUD_VERSION_HPP +#define NEXTCLOUD_VERSION_HPP + +#include + +namespace nextcloud { + +// Semantic versioning +constexpr int VERSION_MAJOR = 0; +constexpr int VERSION_MINOR = 1; +constexpr int VERSION_PATCH = 0; + +// Build metadata +constexpr const char* VERSION_STRING = "0.1.0"; +constexpr const char* BUILD_DATE = __DATE__; +constexpr const char* BUILD_TIME = __TIME__; + +/** + * @brief Get the library version as a string + * @return Version string in format "MAJOR.MINOR.PATCH" + */ +inline std::string getVersionString() { + return VERSION_STRING; +} + +/** + * @brief Get the library version components + * @param major Output parameter for major version + * @param minor Output parameter for minor version + * @param patch Output parameter for patch version + */ +inline void getVersion(int& major, int& minor, int& patch) { + major = VERSION_MAJOR; + minor = VERSION_MINOR; + patch = VERSION_PATCH; +} + +/** + * @brief Check if the library version is at least the specified version + * @param major Required major version + * @param minor Required minor version + * @param patch Required patch version + * @return True if current version >= required version + */ +inline bool isVersionAtLeast(int major, int minor, int patch) { + if (VERSION_MAJOR > major) return true; + if (VERSION_MAJOR < major) return false; + + if (VERSION_MINOR > minor) return true; + if (VERSION_MINOR < minor) return false; + + return VERSION_PATCH >= patch; +} + +} // namespace nextcloud + +#endif // NEXTCLOUD_VERSION_HPP diff --git a/shared/libnextcloud.pc.in b/shared/libnextcloud.pc.in new file mode 100644 index 0000000..bfe32f3 --- /dev/null +++ b/shared/libnextcloud.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: libnextcloud +Description: Cross-platform C++ library for Nextcloud connectivity +Version: @PROJECT_VERSION@ +Requires: +Cflags: -I${includedir} +Libs: -L${libdir} -lnextcloud diff --git a/shared/nextcloudConfig.cmake.in b/shared/nextcloudConfig.cmake.in new file mode 100644 index 0000000..7fd79d4 --- /dev/null +++ b/shared/nextcloudConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/nextcloudTargets.cmake") + +check_required_components(nextcloud) diff --git a/shared/tests/CMakeLists.txt b/shared/tests/CMakeLists.txt new file mode 100644 index 0000000..facac71 --- /dev/null +++ b/shared/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.15) + +# Find Google Test +find_package(GTest REQUIRED) + +# Include Google Test helpers +include(GoogleTest) + +# Test executable +add_executable(smoke_test + smoke_test.cpp +) + +# Link against the library and GTest +target_link_libraries(smoke_test + PRIVATE + nextcloud + GTest::GTest + GTest::Main +) + +# Discover and register tests +gtest_discover_tests(smoke_test) + +# Add custom target to run tests +add_custom_target(run_tests + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + DEPENDS smoke_test + COMMENT "Running tests..." +) diff --git a/shared/tests/smoke_test.cpp b/shared/tests/smoke_test.cpp new file mode 100644 index 0000000..3c02e13 --- /dev/null +++ b/shared/tests/smoke_test.cpp @@ -0,0 +1,140 @@ +#include +#include +#include + +/** + * @file smoke_test.cpp + * @brief Basic smoke tests to verify library setup + * + * These tests ensure that the library headers compile correctly + * and basic functionality works as expected. + */ + +namespace { + +// Test version information +TEST(VersionTest, VersionConstants) { + EXPECT_EQ(nextcloud::VERSION_MAJOR, 0); + EXPECT_EQ(nextcloud::VERSION_MINOR, 1); + EXPECT_EQ(nextcloud::VERSION_PATCH, 0); + EXPECT_STREQ(nextcloud::VERSION_STRING, "0.1.0"); +} + +TEST(VersionTest, GetVersionString) { + std::string version = nextcloud::getVersionString(); + EXPECT_EQ(version, "0.1.0"); +} + +TEST(VersionTest, GetVersionComponents) { + int major, minor, patch; + nextcloud::getVersion(major, minor, patch); + EXPECT_EQ(major, 0); + EXPECT_EQ(minor, 1); + EXPECT_EQ(patch, 0); +} + +TEST(VersionTest, IsVersionAtLeast) { + // Current version is 0.1.0 + EXPECT_TRUE(nextcloud::isVersionAtLeast(0, 0, 0)); + EXPECT_TRUE(nextcloud::isVersionAtLeast(0, 1, 0)); + EXPECT_FALSE(nextcloud::isVersionAtLeast(0, 2, 0)); + EXPECT_FALSE(nextcloud::isVersionAtLeast(1, 0, 0)); +} + +// Test error types +TEST(TypesTest, ErrorToString) { + EXPECT_STREQ(nextcloud::errorToString(nextcloud::NextcloudError::Success), "Success"); + EXPECT_STREQ(nextcloud::errorToString(nextcloud::NextcloudError::NetworkError), "Network error"); + EXPECT_STREQ(nextcloud::errorToString(nextcloud::NextcloudError::AuthenticationFailed), "Authentication failed"); + EXPECT_STREQ(nextcloud::errorToString(nextcloud::NextcloudError::FileNotFound), "File not found"); +} + +TEST(TypesTest, ResultSuccess) { + nextcloud::Result result; + result.error = nextcloud::NextcloudError::Success; + result.value = 42; + + EXPECT_TRUE(result.isSuccess()); + EXPECT_FALSE(result.isError()); + EXPECT_EQ(result.value, 42); +} + +TEST(TypesTest, ResultError) { + nextcloud::Result result; + result.error = nextcloud::NextcloudError::NetworkError; + result.value = ""; + + EXPECT_FALSE(result.isSuccess()); + EXPECT_TRUE(result.isError()); + EXPECT_STREQ(result.errorMessage(), "Network error"); +} + +TEST(TypesTest, UploadProgressStruct) { + nextcloud::UploadProgress progress; + progress.filename = "test.txt"; + progress.bytesUploaded = 50; + progress.totalBytes = 100; + progress.percentComplete = 50.0f; + progress.bytesPerSecond = 1024.0f; + progress.secondsRemaining = 1; + + EXPECT_EQ(progress.filename, "test.txt"); + EXPECT_EQ(progress.bytesUploaded, 50); + EXPECT_EQ(progress.totalBytes, 100); + EXPECT_FLOAT_EQ(progress.percentComplete, 50.0f); +} + +TEST(TypesTest, FileInfoStruct) { + nextcloud::FileInfo file; + file.name = "document.pdf"; + file.path = "/Documents/document.pdf"; + file.isDirectory = false; + file.size = 1024; + file.modifiedTime = 1234567890; + file.contentType = "application/pdf"; + + EXPECT_EQ(file.name, "document.pdf"); + EXPECT_EQ(file.path, "/Documents/document.pdf"); + EXPECT_FALSE(file.isDirectory); + EXPECT_EQ(file.size, 1024); +} + +TEST(TypesTest, FolderInfoStruct) { + nextcloud::FolderInfo folder; + folder.path = "/Photos"; + folder.displayName = "My Photos"; + folder.lastUsed = 1234567890; + folder.isFavorite = true; + + EXPECT_EQ(folder.path, "/Photos"); + EXPECT_EQ(folder.displayName, "My Photos"); + EXPECT_TRUE(folder.isFavorite); +} + +TEST(TypesTest, HttpResponseSuccess) { + nextcloud::HttpResponse response; + response.statusCode = 200; + response.body = "OK"; + response.contentType = "text/plain"; + response.error = nextcloud::NextcloudError::Success; + + EXPECT_TRUE(response.isSuccess()); + EXPECT_EQ(response.statusCode, 200); +} + +TEST(TypesTest, HttpResponseError) { + nextcloud::HttpResponse response; + response.statusCode = 404; + response.body = "Not Found"; + response.error = nextcloud::NextcloudError::FileNotFound; + + EXPECT_FALSE(response.isSuccess()); + EXPECT_EQ(response.statusCode, 404); +} + +} // namespace + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- 2.49.1