#pragma once #include "app_core/app_dialog.h" #include "foundation/result.h" #include #include #include #include #include #include namespace pp::app { enum class CloudUploadAction { unavailable_no_canvas, show_save_required_warning, prompt_publish, }; enum class CloudBrowseAction { unavailable_no_canvas, show_browser, }; enum class CloudDownloadSelectionAction { wait_for_selection, start_download, }; enum class CloudTransferDirection { download, upload, }; enum class CloudTransferAction { reject_missing_source, reject_missing_destination, start_transfer, }; struct CloudUploadPlan { CloudUploadAction action = CloudUploadAction::unavailable_no_canvas; bool save_before_upload = false; }; struct CloudBulkUploadPlan { std::size_t file_count = 0; int progress_total = 0; bool show_progress = false; }; struct CloudDownloadRequest { std::string selected_file; std::string selected_path; std::string selected_name; }; struct CloudTransferPlan { CloudTransferDirection direction = CloudTransferDirection::download; CloudTransferAction action = CloudTransferAction::reject_missing_source; bool enable_progress = false; bool disable_tls_verification = false; }; struct CloudTransferProgressPlan { bool notify = false; float fraction = 0.0F; }; [[nodiscard]] inline AppMessageDialogPlan plan_cloud_save_required_prompt() { return plan_app_message_dialog( "Warning", "This document needs to be saved before upload.", false); } [[nodiscard]] inline AppMessageDialogPlan plan_cloud_publish_prompt() { return plan_app_message_dialog( "Publish document", "Would you like to upload to the public domain?", true, "Yes", "No"); } [[nodiscard]] inline AppMessageDialogPlan plan_cloud_upload_success_prompt() { return plan_app_message_dialog( "Success", "This document has been succesfully uploaded.", false); } [[nodiscard]] inline AppProgressDialogPlan plan_cloud_upload_progress_dialog() { return plan_app_progress_dialog("Uploading", 0); } [[nodiscard]] inline AppProgressDialogPlan plan_cloud_bulk_upload_progress_dialog(int progress_total) { return plan_app_progress_dialog("Export Pano Image", progress_total); } [[nodiscard]] inline AppMessageDialogPlan plan_cloud_download_progress_prompt() { return plan_app_message_dialog( "Downloading", "Download in progress", true); } [[nodiscard]] inline std::string format_cloud_download_progress_message(float progress_fraction) { char buffer[64] {}; std::snprintf( buffer, sizeof(buffer), "Download in progress %.2f%%", progress_fraction * 100.0F); return buffer; } class CloudServices { public: virtual ~CloudServices() = default; virtual void show_save_required_warning() = 0; virtual void prompt_publish(bool save_before_upload) = 0; virtual void begin_bulk_upload(int progress_total, bool show_progress) = 0; virtual void upload_all_bulk_files() = 0; virtual void end_bulk_upload() = 0; virtual void show_browser() = 0; virtual void start_download(const CloudDownloadRequest& request) = 0; }; [[nodiscard]] constexpr CloudUploadPlan plan_cloud_upload( bool has_canvas, bool is_new_document, bool has_unsaved_changes) noexcept { if (!has_canvas) { return { CloudUploadAction::unavailable_no_canvas, false }; } if (is_new_document) { return { CloudUploadAction::show_save_required_warning, false }; } return { CloudUploadAction::prompt_publish, has_unsaved_changes }; } [[nodiscard]] constexpr CloudBrowseAction plan_cloud_browse(bool has_canvas) noexcept { return has_canvas ? CloudBrowseAction::show_browser : CloudBrowseAction::unavailable_no_canvas; } [[nodiscard]] constexpr CloudDownloadSelectionAction plan_cloud_download_selection( std::string_view selected_file) noexcept { return selected_file.empty() ? CloudDownloadSelectionAction::wait_for_selection : CloudDownloadSelectionAction::start_download; } [[nodiscard]] constexpr CloudBulkUploadPlan plan_cloud_bulk_upload( std::size_t file_count, bool progress_ui_available) noexcept { const auto max_progress_total = static_cast(std::numeric_limits::max()); return { file_count, file_count > max_progress_total ? std::numeric_limits::max() : static_cast(file_count), progress_ui_available, }; } [[nodiscard]] constexpr CloudTransferPlan plan_cloud_download_transfer( std::string_view url, std::string_view destination_path, bool has_progress_callback, bool disables_tls_verification) noexcept { if (url.empty()) { return { CloudTransferDirection::download, CloudTransferAction::reject_missing_source, false, false, }; } if (destination_path.empty()) { return { CloudTransferDirection::download, CloudTransferAction::reject_missing_destination, false, false, }; } return { CloudTransferDirection::download, CloudTransferAction::start_transfer, has_progress_callback, disables_tls_verification, }; } [[nodiscard]] constexpr CloudTransferPlan plan_cloud_upload_transfer( std::string_view filename, bool has_progress_callback, bool disables_tls_verification) noexcept { if (filename.empty()) { return { CloudTransferDirection::upload, CloudTransferAction::reject_missing_source, false, false, }; } return { CloudTransferDirection::upload, CloudTransferAction::start_transfer, has_progress_callback, disables_tls_verification, }; } [[nodiscard]] constexpr CloudTransferProgressPlan plan_cloud_transfer_progress( std::int64_t total, std::int64_t current) noexcept { if (total <= 0) { return {}; } const auto clamped_current = current < 0 ? std::int64_t { 0 } : (current > total ? total : current); return { true, static_cast(static_cast(clamped_current) / static_cast(total)), }; } [[nodiscard]] inline pp::foundation::Status execute_cloud_upload_plan( const CloudUploadPlan& plan, CloudServices& services) { switch (plan.action) { case CloudUploadAction::unavailable_no_canvas: return pp::foundation::Status::success(); case CloudUploadAction::show_save_required_warning: services.show_save_required_warning(); return pp::foundation::Status::success(); case CloudUploadAction::prompt_publish: services.prompt_publish(plan.save_before_upload); return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown cloud upload action"); } [[nodiscard]] inline pp::foundation::Status execute_cloud_bulk_upload_plan( const CloudBulkUploadPlan& plan, CloudServices& services) { services.begin_bulk_upload(plan.progress_total, plan.show_progress); services.upload_all_bulk_files(); services.end_bulk_upload(); return pp::foundation::Status::success(); } [[nodiscard]] inline pp::foundation::Status execute_cloud_browse_action( CloudBrowseAction action, CloudServices& services) { switch (action) { case CloudBrowseAction::unavailable_no_canvas: return pp::foundation::Status::success(); case CloudBrowseAction::show_browser: services.show_browser(); return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown cloud browse action"); } [[nodiscard]] inline pp::foundation::Status execute_cloud_download_selection_action( CloudDownloadSelectionAction action, CloudServices& services, const CloudDownloadRequest& request) { switch (action) { case CloudDownloadSelectionAction::wait_for_selection: return pp::foundation::Status::success(); case CloudDownloadSelectionAction::start_download: if (request.selected_file.empty()) { return pp::foundation::Status::invalid_argument("cloud download requires a selected file"); } services.start_download(request); return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown cloud download selection action"); } }