v2.0.2-rc (#27)
	
		
			
	
		
	
	
		
	
		
			Some checks reported errors
		
		
	
	
		
			
				
	
				BlockStorage/repertory/pipeline/head Something is wrong with the build of this commit
				
			
		
		
	
	
				
					
				
			
		
			Some checks reported errors
		
		
	
	BlockStorage/repertory/pipeline/head Something is wrong with the build of this commit
				
			## v2.0.2-rc ### BREAKING CHANGES * Refactored `config.json` - will need to verify configuration settings prior to mounting ### Issues * \#12 \[Unit Test\] Complete all providers unit tests * \#14 \[Unit Test\] SQLite mini-ORM unit tests and cleanup * \#16 Add support for bucket name in Sia provider * \#17 Update to common c++ build system * A single 64-bit Linux Jenkins server is used to build all Linux and Windows versions * All dependency sources are now included * MSVC is no longer supported * MSYS2 is required for building Windows binaries on Windows * OS X support is temporarily disabled * \#19 \[bug\] Rename file is broken for files that are existing * \#23 \[bug\] Incorrect file size displayed while upload is pending * \#24 RocksDB implementations should be transactional * \#25 Writes should block when maximum cache size is reached * \#26 Complete ring buffer and direct download support ### Changes from v2.0.1-rc * Ability to choose between RocksDB and SQLite databases * Added direct reads and implemented download fallback * Corrected file times on S3 and Sia providers * Corrected handling of `chown()` and `chmod()` * Fixed erroneous download of chunks after resize Reviewed-on: #27
This commit is contained in:
		
							
								
								
									
										174
									
								
								support/src/utils/common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								support/src/utils/common.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/common.hpp" | ||||
|  | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory::utils { | ||||
| auto compare_version_strings(std::string version1, | ||||
|                              std::string version2) -> std::int32_t { | ||||
|  | ||||
|   if (utils::string::contains(version1, "-")) { | ||||
|     version1 = utils::string::split(version1, '-', true)[0U]; | ||||
|   } | ||||
|  | ||||
|   if (utils::string::contains(version2, "-")) { | ||||
|     version2 = utils::string::split(version2, '-', true)[0U]; | ||||
|   } | ||||
|  | ||||
|   auto nums1 = utils::string::split(version1, '.', true); | ||||
|   auto nums2 = utils::string::split(version2, '.', true); | ||||
|  | ||||
|   while (nums1.size() > nums2.size()) { | ||||
|     nums2.emplace_back("0"); | ||||
|   } | ||||
|  | ||||
|   while (nums2.size() > nums1.size()) { | ||||
|     nums1.emplace_back("0"); | ||||
|   } | ||||
|  | ||||
|   for (std::size_t idx = 0U; idx < nums1.size(); idx++) { | ||||
|     auto int1 = utils::string::to_uint32(nums1[idx]); | ||||
|     auto int2 = utils::string::to_uint32(nums2[idx]); | ||||
|     auto res = std::memcmp(&int1, &int2, sizeof(int1)); | ||||
|     if (res != 0) { | ||||
|       return res; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| auto compare_version_strings(std::wstring_view version1, | ||||
|                              std::wstring_view version2) -> std::int32_t { | ||||
|   return compare_version_strings(utils::string::to_utf8(version1), | ||||
|                                  utils::string::to_utf8(version2)); | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_STDUUID) | ||||
| auto create_uuid_string() -> std::string { | ||||
|   std::random_device random_device{}; | ||||
|   auto seed_data = std::array<int, std::mt19937::state_size>{}; | ||||
|   std::generate(std::begin(seed_data), std::end(seed_data), | ||||
|                 std::ref(random_device)); | ||||
|   std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); | ||||
|   std::mt19937 generator(seq); | ||||
|   uuids::uuid_random_generator gen{generator}; | ||||
|  | ||||
|   return uuids::to_string(gen()); | ||||
| } | ||||
|  | ||||
| auto create_uuid_wstring() -> std::wstring { | ||||
|   return utils::string::from_utf8(create_uuid_string()); | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_STDUUID) | ||||
|  | ||||
| auto generate_random_string(std::size_t length) -> std::string { | ||||
|   std::string ret; | ||||
|   if (length == 0U) { | ||||
|     return ret; | ||||
|   } | ||||
|  | ||||
|   ret.resize(length); | ||||
|   for (auto &ch : ret) { | ||||
|     std::array<unsigned int, 3U> random_list{ | ||||
|         generate_random_between(0U, 57U), | ||||
|         generate_random_between(65U, 90U), | ||||
|         generate_random_between(97U, 255U), | ||||
|     }; | ||||
|     ch = static_cast<char>( | ||||
|         random_list.at(generate_random_between(0U, 2U)) % 74U + 48U); | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| auto generate_random_wstring(std::size_t length) -> std::wstring { | ||||
|   return utils::string::from_utf8(generate_random_string(length)); | ||||
| } | ||||
|  | ||||
| auto get_environment_variable(std::string_view variable) -> std::string { | ||||
|   static std::mutex mtx{}; | ||||
|   mutex_lock lock{mtx}; | ||||
|  | ||||
|   const auto *val = std::getenv(std::string{variable}.c_str()); | ||||
|   return std::string{val == nullptr ? "" : val}; | ||||
| } | ||||
|  | ||||
| auto get_environment_variable(std::wstring_view variable) -> std::wstring { | ||||
|   return utils::string::from_utf8( | ||||
|       get_environment_variable(utils::string::to_utf8(variable))); | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_BOOST) | ||||
| auto get_next_available_port(std::uint16_t first_port, | ||||
|                              std::uint16_t &available_port) -> bool { | ||||
|   if (first_port == 0U) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   using namespace boost::asio; | ||||
|   using ip::tcp; | ||||
|  | ||||
|   boost::system::error_code error_code{}; | ||||
|   while (first_port != 0U) { | ||||
|     io_context ctx{}; | ||||
|     tcp::acceptor acceptor(ctx); | ||||
|     acceptor.open(tcp::v4(), error_code) || | ||||
|         acceptor.bind({tcp::v4(), first_port}, error_code); | ||||
|     if (not error_code) { | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     ++first_port; | ||||
|   } | ||||
|  | ||||
|   if (not error_code) { | ||||
|     available_port = first_port; | ||||
|   } | ||||
|  | ||||
|   return not error_code; | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
| auto retry_action(retryable_action_t action, std::size_t retry_count, | ||||
|                   std::chrono::milliseconds retry_wait) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     for (std::size_t idx = 0U; idx < retry_count; ++idx) { | ||||
|       if (action()) { | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       std::this_thread::sleep_for(retry_wait); | ||||
|     } | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
| } // namespace repertory::utils | ||||
							
								
								
									
										250
									
								
								support/src/utils/db/sqlite/db_common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								support/src/utils/db/sqlite/db_common.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_SQLITE) | ||||
|  | ||||
| #include "utils/db/sqlite/db_common.hpp" | ||||
|  | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/error.hpp" | ||||
|  | ||||
| namespace repertory::utils::db::sqlite { | ||||
| void sqlite3_deleter::operator()(sqlite3 *db3) const { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (db3 == nullptr) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   std::string err_msg; | ||||
|   if (not execute_sql(*db3, "VACUUM;", err_msg)) { | ||||
|     utils::error::handle_error(function_name, | ||||
|                                utils::error::create_error_message({ | ||||
|                                    "failed to vacuum database", | ||||
|                                    err_msg, | ||||
|                                })); | ||||
|   } | ||||
|  | ||||
|   if (not utils::retry_action([&db3]() -> bool { | ||||
|         auto res = sqlite3_close_v2(db3); | ||||
|         if (res == SQLITE_OK) { | ||||
|           return true; | ||||
|         } | ||||
|  | ||||
|         auto &&err_str = sqlite3_errstr(res); | ||||
|         utils::error::handle_error( | ||||
|             function_name, | ||||
|             utils::error::create_error_message({ | ||||
|                 "failed to close database", | ||||
|                 (err_str == nullptr ? std::to_string(res) : err_str), | ||||
|             })); | ||||
|         return false; | ||||
|       })) { | ||||
|     repertory::utils::error::handle_error(function_name, | ||||
|                                           "failed to close database"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| db_result::db_column::db_column(std::int32_t index, std::string name, | ||||
|                                 db_types_t value) noexcept | ||||
|     : index_(index), name_(std::move(name)), value_(std::move(value)) {} | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_JSON) | ||||
| auto db_result::db_column::get_value_as_json() const -> nlohmann::json { | ||||
|   return std::visit( | ||||
|       overloaded{ | ||||
|           [this](std::int64_t value) -> auto { | ||||
|             return nlohmann::json({{name_, value}}); | ||||
|           }, | ||||
|           [](auto &&value) -> auto { return nlohmann::json::parse(value); }, | ||||
|       }, | ||||
|       value_); | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_JSON) | ||||
|  | ||||
| db_result::db_row::db_row(std::shared_ptr<context> ctx) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto column_count = sqlite3_column_count(ctx->stmt.get()); | ||||
|   for (std::int32_t col = 0; col < column_count; col++) { | ||||
|     std::string name{sqlite3_column_name(ctx->stmt.get(), col)}; | ||||
|     auto column_type = sqlite3_column_type(ctx->stmt.get(), col); | ||||
|  | ||||
|     db_types_t value; | ||||
|     switch (column_type) { | ||||
|     case SQLITE_INTEGER: { | ||||
|       value = sqlite3_column_int64(ctx->stmt.get(), col); | ||||
|     } break; | ||||
|  | ||||
|     case SQLITE_TEXT: { | ||||
|       const auto *text = reinterpret_cast<const char *>( | ||||
|           sqlite3_column_text(ctx->stmt.get(), col)); | ||||
|       value = std::string(text == nullptr ? "" : text); | ||||
|     } break; | ||||
|  | ||||
|     default: | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "column type not implemented", | ||||
|                                                std::to_string(column_type), | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     columns_[name] = db_column{col, name, value}; | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto db_result::db_row::get_columns() const -> std::vector<db_column> { | ||||
|   std::vector<db_column> ret; | ||||
|   for (const auto &item : columns_) { | ||||
|     ret.push_back(item.second); | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| auto db_result::db_row::get_column(std::int32_t index) const -> db_column { | ||||
|   auto iter = std::find_if( | ||||
|       columns_.begin(), columns_.end(), | ||||
|       [&index](auto &&col) -> bool { return col.second.get_index() == index; }); | ||||
|   if (iter == columns_.end()) { | ||||
|     throw std::out_of_range(""); | ||||
|   } | ||||
|  | ||||
|   return iter->second; | ||||
| } | ||||
|  | ||||
| auto db_result::db_row::get_column(std::string name) const -> db_column { | ||||
|   return columns_.at(name); | ||||
| } | ||||
|  | ||||
| db_result::db_result(db3_stmt_t stmt, std::int32_t res) | ||||
|     : ctx_(std::make_shared<context>()), res_(res) { | ||||
|   ctx_->stmt = std::move(stmt); | ||||
|   if (res == SQLITE_OK) { | ||||
|     set_res(sqlite3_step(ctx_->stmt.get())); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto db_result::get_error_str() const -> std::string { | ||||
|   auto &&err_msg = sqlite3_errstr(res_); | ||||
|   return err_msg == nullptr ? std::to_string(res_) : err_msg; | ||||
| } | ||||
|  | ||||
| auto db_result::get_row(std::optional<row> &opt_row) const -> bool { | ||||
|   opt_row.reset(); | ||||
|  | ||||
|   if (not has_row()) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   opt_row = db_row{ctx_}; | ||||
|   set_res(sqlite3_step(ctx_->stmt.get())); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| auto db_result::has_row() const -> bool { return res_ == SQLITE_ROW; } | ||||
|  | ||||
| void db_result::next_row() const { | ||||
|   if (not has_row()) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   set_res(sqlite3_step(ctx_->stmt.get())); | ||||
| } | ||||
|  | ||||
| auto db_result::ok() const -> bool { | ||||
|   return res_ == SQLITE_DONE || res_ == SQLITE_ROW; | ||||
| } | ||||
|  | ||||
| auto create_db(std::string db_path, | ||||
|                const std::map<std::string, std::string> &sql_create_tables) | ||||
|     -> db3_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   sqlite3 *db_ptr{nullptr}; | ||||
|   auto db_res = | ||||
|       sqlite3_open_v2(db_path.c_str(), &db_ptr, | ||||
|                       SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); | ||||
|   if (db_res != SQLITE_OK) { | ||||
|     const auto *msg = sqlite3_errstr(db_res); | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "failed to open db", | ||||
|                            db_path, | ||||
|                            (msg == nullptr ? std::to_string(db_res) : msg), | ||||
|                        }); | ||||
|   } | ||||
|  | ||||
|   auto db3 = db3_t{ | ||||
|       db_ptr, | ||||
|       sqlite3_deleter(), | ||||
|   }; | ||||
|  | ||||
|   for (auto &&create_item : sql_create_tables) { | ||||
|     std::string err_msg; | ||||
|     if (not sqlite::execute_sql(*db3, create_item.second, err_msg)) { | ||||
|       db3.reset(); | ||||
|       throw utils::error::create_exception(function_name, { | ||||
|                                                               err_msg, | ||||
|                                                           }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   set_journal_mode(*db3); | ||||
|  | ||||
|   return db3; | ||||
| } | ||||
|  | ||||
| auto execute_sql(sqlite3 &db3, const std::string &sql, std::string &err) | ||||
|     -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   char *err_msg{nullptr}; | ||||
|   auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg); | ||||
|   if (err_msg != nullptr) { | ||||
|     err = err_msg; | ||||
|     sqlite3_free(err_msg); | ||||
|     err_msg = nullptr; | ||||
|   } | ||||
|  | ||||
|   if (res == SQLITE_OK) { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   err = utils::error::create_error_message({ | ||||
|       function_name, | ||||
|       "failed to execute sql", | ||||
|       err, | ||||
|       sql, | ||||
|   }); | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| void set_journal_mode(sqlite3 &db3) { | ||||
|   sqlite3_exec(&db3, | ||||
|                "PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;PRAGMA " | ||||
|                "auto_vacuum = NONE;", | ||||
|                nullptr, nullptr, nullptr); | ||||
| } | ||||
| } // namespace repertory::utils::db::sqlite | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_SQLITE) | ||||
							
								
								
									
										115
									
								
								support/src/utils/db/sqlite/db_delete.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								support/src/utils/db/sqlite/db_delete.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_SQLITE) | ||||
|  | ||||
| #include "utils/db/sqlite/db_delete.hpp" | ||||
|  | ||||
| namespace repertory::utils::db::sqlite { | ||||
| auto db_delete::context::db_delete_op_t::dump() const -> std::string { | ||||
|   return db_delete{ctx}.dump(); | ||||
| } | ||||
|  | ||||
| auto db_delete::context::db_delete_op_t::go() const -> db_result { | ||||
|   return db_delete{ctx}.go(); | ||||
| } | ||||
|  | ||||
| auto db_delete::dump() const -> std::string { | ||||
|   std::stringstream query; | ||||
|   query << "DELETE FROM \"" << ctx_->table_name << "\""; | ||||
|  | ||||
|   if (ctx_->where_data) { | ||||
|     std::int32_t idx{}; | ||||
|     query << " WHERE " << ctx_->where_data->base.dump(idx); | ||||
|   } | ||||
|  | ||||
|   query << ';'; | ||||
|  | ||||
|   return query.str(); | ||||
| } | ||||
|  | ||||
| auto db_delete::go() const -> db_result { | ||||
|   sqlite3_stmt *stmt_ptr{nullptr}; | ||||
|   auto query_str = dump(); | ||||
|   auto res = | ||||
|       sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); | ||||
|  | ||||
|   auto stmt = db3_stmt_t{ | ||||
|       stmt_ptr, | ||||
|       sqlite3_statement_deleter(), | ||||
|   }; | ||||
|  | ||||
|   if (res != SQLITE_OK) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   if (not ctx_->where_data) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->where_data->values.size()); | ||||
|        idx++) { | ||||
|     res = | ||||
|         std::visit(overloaded{ | ||||
|                        [&stmt, &idx](std::int64_t data) -> std::int32_t { | ||||
|                          return sqlite3_bind_int64(stmt.get(), idx + 1, data); | ||||
|                        }, | ||||
|                        [&stmt, &idx](const std::string &data) -> std::int32_t { | ||||
|                          return sqlite3_bind_text(stmt.get(), idx + 1, | ||||
|                                                   data.c_str(), -1, nullptr); | ||||
|                        }, | ||||
|                    }, | ||||
|                    ctx_->where_data->values.at(static_cast<std::size_t>(idx))); | ||||
|     if (res != SQLITE_OK) { | ||||
|       return {std::move(stmt), res}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return {std::move(stmt), res}; | ||||
| } | ||||
|  | ||||
| auto db_delete::group(context::w_t::group_func_t func) -> context::w_t::wn_t { | ||||
|   if (not ctx_->where_data) { | ||||
|     ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{ | ||||
|         context::w_t{0U, ctx_}, | ||||
|         {}, | ||||
|         {}, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return ctx_->where_data->base.group(std::move(func)); | ||||
| } | ||||
|  | ||||
| auto db_delete::where(std::string column_name) const -> context::w_t::cn_t { | ||||
|   if (not ctx_->where_data) { | ||||
|     ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{ | ||||
|         context::w_t{0U, ctx_}, | ||||
|         {}, | ||||
|         {}, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return ctx_->where_data->base.where(column_name); | ||||
| } | ||||
| } // namespace repertory::utils::db::sqlite | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_SQLITE) | ||||
							
								
								
									
										99
									
								
								support/src/utils/db/sqlite/db_insert.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								support/src/utils/db/sqlite/db_insert.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_SQLITE) | ||||
|  | ||||
| #include "utils/db/sqlite/db_insert.hpp" | ||||
|  | ||||
| namespace repertory::utils::db::sqlite { | ||||
| auto db_insert::column_value(std::string column_name, db_types_t value) | ||||
|     -> db_insert { | ||||
|   ctx_->values[column_name] = value; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_insert::dump() const -> std::string { | ||||
|   std::stringstream query; | ||||
|   query << "INSERT "; | ||||
|   if (ctx_->or_replace) { | ||||
|     query << "OR REPLACE "; | ||||
|   } | ||||
|   query << "INTO \"" << ctx_->table_name << "\" ("; | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) { | ||||
|     if (idx > 0) { | ||||
|       query << ", "; | ||||
|     } | ||||
|     query << '"' << std::next(ctx_->values.begin(), idx)->first << '"'; | ||||
|   } | ||||
|  | ||||
|   query << ") VALUES ("; | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) { | ||||
|     if (idx > 0) { | ||||
|       query << ", "; | ||||
|     } | ||||
|     query << "?" << (idx + 1); | ||||
|   } | ||||
|   query << ");"; | ||||
|  | ||||
|   return query.str(); | ||||
| } | ||||
|  | ||||
| auto db_insert::go() const -> db_result { | ||||
|   sqlite3_stmt *stmt_ptr{nullptr}; | ||||
|   auto query_str = dump(); | ||||
|   auto res = | ||||
|       sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); | ||||
|  | ||||
|   auto stmt = db3_stmt_t{ | ||||
|       stmt_ptr, | ||||
|       sqlite3_statement_deleter(), | ||||
|   }; | ||||
|  | ||||
|   if (res != SQLITE_OK) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) { | ||||
|     res = | ||||
|         std::visit(overloaded{ | ||||
|                        [&idx, &stmt](std::int64_t data) -> std::int32_t { | ||||
|                          return sqlite3_bind_int64(stmt.get(), idx + 1, data); | ||||
|                        }, | ||||
|                        [&idx, &stmt](const std::string &data) -> std::int32_t { | ||||
|                          return sqlite3_bind_text(stmt.get(), idx + 1, | ||||
|                                                   data.c_str(), -1, nullptr); | ||||
|                        }, | ||||
|                    }, | ||||
|                    std::next(ctx_->values.begin(), idx)->second); | ||||
|     if (res != SQLITE_OK) { | ||||
|       return {std::move(stmt), res}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return {std::move(stmt), res}; | ||||
| } | ||||
| } // namespace repertory::utils::db::sqlite | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_SQLITE) | ||||
							
								
								
									
										221
									
								
								support/src/utils/db/sqlite/db_select.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								support/src/utils/db/sqlite/db_select.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_SQLITE) | ||||
|  | ||||
| #include "utils/db/sqlite/db_select.hpp" | ||||
|  | ||||
| namespace repertory::utils::db::sqlite { | ||||
| auto db_select::context::db_select_op_t::dump() const -> std::string { | ||||
|   return db_select{ctx}.dump(); | ||||
| } | ||||
|  | ||||
| auto db_select::context::db_select_op_t::go() const -> db_result { | ||||
|   return db_select{ctx}.go(); | ||||
| } | ||||
|  | ||||
| auto db_select::context::db_select_op_t::group_by(std::string column_name) | ||||
|     -> db_select::context::db_select_op_t { | ||||
|   db_select{ctx}.group_by(column_name); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::context::db_select_op_t::limit(std::int32_t value) | ||||
|     -> db_select::context::db_select_op_t { | ||||
|   db_select{ctx}.limit(value); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::context::db_select_op_t::offset(std::int32_t value) | ||||
|     -> db_select::context::db_select_op_t { | ||||
|   db_select{ctx}.offset(value); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::context::db_select_op_t::order_by(std::string column_name, | ||||
|                                                   bool ascending) | ||||
|     -> db_select::context::db_select_op_t { | ||||
|   db_select{ctx}.order_by(column_name, ascending); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::column(std::string column_name) -> db_select { | ||||
|   ctx_->columns.push_back(column_name); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::count(std::string column_name, std::string as_column_name) | ||||
|     -> db_select { | ||||
|   ctx_->count_columns[column_name] = as_column_name; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::dump() const -> std::string { | ||||
|   std::stringstream query; | ||||
|   query << "SELECT "; | ||||
|   bool has_column{false}; | ||||
|   if (ctx_->columns.empty()) { | ||||
|     if (ctx_->count_columns.empty()) { | ||||
|       query << "*"; | ||||
|       has_column = true; | ||||
|     } | ||||
|   } else { | ||||
|     has_column = not ctx_->columns.empty(); | ||||
|     for (std::size_t idx = 0U; idx < ctx_->columns.size(); idx++) { | ||||
|       if (idx > 0U) { | ||||
|         query << ", "; | ||||
|       } | ||||
|       query << ctx_->columns.at(idx); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   for (std::int32_t idx = 0U; | ||||
|        idx < static_cast<std::int32_t>(ctx_->count_columns.size()); idx++) { | ||||
|     if (has_column || idx > 0) { | ||||
|       query << ", "; | ||||
|     } | ||||
|     query << "COUNT(\""; | ||||
|     auto &count_column = *std::next(ctx_->count_columns.begin(), idx); | ||||
|     query << count_column.first << "\") AS \"" << count_column.second << '"'; | ||||
|   } | ||||
|   query << " FROM \"" << ctx_->table_name << "\""; | ||||
|  | ||||
|   if (ctx_->where_data) { | ||||
|     std::int32_t idx{}; | ||||
|     query << " WHERE " << ctx_->where_data->base.dump(idx); | ||||
|   } | ||||
|  | ||||
|   if (not ctx_->group_by.empty()) { | ||||
|     query << " GROUP BY "; | ||||
|     for (std::size_t idx = 0U; idx < ctx_->group_by.size(); idx++) { | ||||
|       if (idx > 0U) { | ||||
|         query << ", "; | ||||
|       } | ||||
|  | ||||
|       query << "\"" << ctx_->group_by.at(idx) << "\""; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (ctx_->order_by.has_value()) { | ||||
|     query << " ORDER BY \"" << ctx_->order_by.value().first << "\" "; | ||||
|     query << (ctx_->order_by.value().second ? "ASC" : "DESC"); | ||||
|   } | ||||
|  | ||||
|   if (ctx_->limit.has_value()) { | ||||
|     query << " LIMIT " << ctx_->limit.value(); | ||||
|   } | ||||
|  | ||||
|   if (ctx_->offset.has_value()) { | ||||
|     query << " OFFSET " << ctx_->offset.value(); | ||||
|   } | ||||
|  | ||||
|   query << ';'; | ||||
|  | ||||
|   return query.str(); | ||||
| } | ||||
|  | ||||
| auto db_select::go() const -> db_result { | ||||
|   sqlite3_stmt *stmt_ptr{nullptr}; | ||||
|   auto query_str = dump(); | ||||
|   auto res = | ||||
|       sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); | ||||
|  | ||||
|   auto stmt = db3_stmt_t{ | ||||
|       stmt_ptr, | ||||
|       sqlite3_statement_deleter(), | ||||
|   }; | ||||
|  | ||||
|   if (res != SQLITE_OK) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   if (not ctx_->where_data) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->where_data->values.size()); | ||||
|        idx++) { | ||||
|     res = | ||||
|         std::visit(overloaded{ | ||||
|                        [&idx, &stmt](std::int64_t data) -> std::int32_t { | ||||
|                          return sqlite3_bind_int64(stmt.get(), idx + 1, data); | ||||
|                        }, | ||||
|                        [&idx, &stmt](const std::string &data) -> std::int32_t { | ||||
|                          return sqlite3_bind_text(stmt.get(), idx + 1, | ||||
|                                                   data.c_str(), -1, nullptr); | ||||
|                        }, | ||||
|                    }, | ||||
|                    ctx_->where_data->values.at(static_cast<std::size_t>(idx))); | ||||
|     if (res != SQLITE_OK) { | ||||
|       return {std::move(stmt), res}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return {std::move(stmt), res}; | ||||
| } | ||||
|  | ||||
| auto db_select::group(context::w_t::group_func_t func) -> context::w_t::wn_t { | ||||
|   if (not ctx_->where_data) { | ||||
|     ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{ | ||||
|         context::w_t{0U, ctx_}, | ||||
|         {}, | ||||
|         {}, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return ctx_->where_data->base.group(std::move(func)); | ||||
| } | ||||
|  | ||||
| auto db_select::group_by(std::string column_name) -> db_select { | ||||
|   ctx_->group_by.emplace_back(std::move(column_name)); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::limit(std::int32_t value) -> db_select { | ||||
|   ctx_->limit = value; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::offset(std::int32_t value) -> db_select { | ||||
|   ctx_->offset = value; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::order_by(std::string column_name, bool ascending) -> db_select { | ||||
|   ctx_->order_by = {column_name, ascending}; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_select::where(std::string column_name) const -> context::w_t::cn_t { | ||||
|   if (not ctx_->where_data) { | ||||
|     ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{ | ||||
|         context::w_t{0U, ctx_}, | ||||
|         {}, | ||||
|         {}, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return ctx_->where_data->base.where(column_name); | ||||
| } | ||||
| } // namespace repertory::utils::db::sqlite | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_SQLITE) | ||||
							
								
								
									
										189
									
								
								support/src/utils/db/sqlite/db_update.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								support/src/utils/db/sqlite/db_update.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_SQLITE) | ||||
|  | ||||
| #include "utils/db/sqlite/db_update.hpp" | ||||
|  | ||||
| namespace repertory::utils::db::sqlite { | ||||
| auto db_update::context::db_update_op_t::dump() const -> std::string { | ||||
|   return db_update{ctx}.dump(); | ||||
| } | ||||
|  | ||||
| auto db_update::context::db_update_op_t::go() const -> db_result { | ||||
|   return db_update{ctx}.go(); | ||||
| } | ||||
|  | ||||
| auto db_update::context::db_update_op_t::limit(std::int32_t value) | ||||
|     -> db_update::context::db_update_op_t { | ||||
|   db_update{ctx}.limit(value); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_update::context::db_update_op_t::order_by(std::string column_name, | ||||
|                                                   bool ascending) | ||||
|     -> db_update::context::db_update_op_t { | ||||
|   db_update{ctx}.order_by(column_name, ascending); | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_update::column_value(std::string column_name, db_types_t value) | ||||
|     -> db_update { | ||||
|   ctx_->column_values[column_name] = value; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_update::dump() const -> std::string { | ||||
|   std::stringstream query; | ||||
|   query << "UPDATE \"" << ctx_->table_name << "\" SET "; | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) { | ||||
|     if (idx > 0) { | ||||
|       query << ", "; | ||||
|     } | ||||
|  | ||||
|     auto column = std::next(ctx_->column_values.begin(), idx); | ||||
|     query << '"' << column->first << "\"=?" + std::to_string(idx + 1); | ||||
|   } | ||||
|  | ||||
|   if (ctx_->where_data) { | ||||
|     auto idx{static_cast<std::int32_t>(ctx_->column_values.size())}; | ||||
|     query << " WHERE " << ctx_->where_data->base.dump(idx); | ||||
|   } | ||||
|  | ||||
|   if (ctx_->order_by.has_value()) { | ||||
|     query << " ORDER BY \"" << ctx_->order_by.value().first << "\" "; | ||||
|     query << (ctx_->order_by.value().second ? "ASC" : "DESC"); | ||||
|   } | ||||
|  | ||||
|   if (ctx_->limit.has_value()) { | ||||
|     query << " LIMIT " << ctx_->limit.value(); | ||||
|   } | ||||
|  | ||||
|   query << ';'; | ||||
|  | ||||
|   return query.str(); | ||||
| } | ||||
|  | ||||
| auto db_update::go() const -> db_result { | ||||
|   sqlite3_stmt *stmt_ptr{nullptr}; | ||||
|  | ||||
|   auto query_str = dump(); | ||||
|   auto res = | ||||
|       sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr); | ||||
|  | ||||
|   auto stmt = db3_stmt_t{ | ||||
|       stmt_ptr, | ||||
|       sqlite3_statement_deleter(), | ||||
|   }; | ||||
|  | ||||
|   if (res != SQLITE_OK) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) { | ||||
|     res = | ||||
|         std::visit(overloaded{ | ||||
|                        [&idx, &stmt](std::int64_t data) -> std::int32_t { | ||||
|                          return sqlite3_bind_int64(stmt.get(), idx + 1, data); | ||||
|                        }, | ||||
|                        [&idx, &stmt](const std::string &data) -> std::int32_t { | ||||
|                          return sqlite3_bind_text(stmt.get(), idx + 1, | ||||
|                                                   data.c_str(), -1, nullptr); | ||||
|                        }, | ||||
|                    }, | ||||
|                    std::next(ctx_->column_values.begin(), idx)->second); | ||||
|     if (res != SQLITE_OK) { | ||||
|       return {std::move(stmt), res}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (not ctx_->where_data) { | ||||
|     return {std::move(stmt), res}; | ||||
|   } | ||||
|  | ||||
|   for (std::int32_t idx = 0; | ||||
|        idx < static_cast<std::int32_t>(ctx_->where_data->values.size()); | ||||
|        idx++) { | ||||
|     res = std::visit( | ||||
|         overloaded{ | ||||
|             [this, &idx, &stmt](std::int64_t data) -> std::int32_t { | ||||
|               return sqlite3_bind_int64( | ||||
|                   stmt.get(), | ||||
|                   idx + static_cast<std::int32_t>(ctx_->column_values.size()) + | ||||
|                       1, | ||||
|                   data); | ||||
|             }, | ||||
|             [this, &idx, &stmt](const std::string &data) -> std::int32_t { | ||||
|               return sqlite3_bind_text( | ||||
|                   stmt.get(), | ||||
|                   idx + static_cast<std::int32_t>(ctx_->column_values.size()) + | ||||
|                       1, | ||||
|                   data.c_str(), -1, nullptr); | ||||
|             }, | ||||
|         }, | ||||
|         ctx_->where_data->values.at(static_cast<std::size_t>(idx))); | ||||
|     if (res != SQLITE_OK) { | ||||
|       return {std::move(stmt), res}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return {std::move(stmt), res}; | ||||
| } | ||||
|  | ||||
| auto db_update::group(context::w_t::group_func_t func) -> context::w_t::wn_t { | ||||
|   if (not ctx_->where_data) { | ||||
|     ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{ | ||||
|         context::w_t{0U, ctx_}, | ||||
|         {}, | ||||
|         {}, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return ctx_->where_data->base.group(std::move(func)); | ||||
| } | ||||
|  | ||||
| auto db_update::limit(std::int32_t value) -> db_update { | ||||
|   ctx_->limit = value; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_update::order_by(std::string column_name, bool ascending) -> db_update { | ||||
|   ctx_->order_by = {column_name, ascending}; | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| auto db_update::where(std::string column_name) const -> context::w_t::cn_t { | ||||
|   if (not ctx_->where_data) { | ||||
|     ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{ | ||||
|         context::w_t{0U, ctx_}, | ||||
|         {}, | ||||
|         {}, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   return ctx_->where_data->base.where(column_name); | ||||
| } | ||||
| } // namespace repertory::utils::db::sqlite | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_SQLITE) | ||||
							
								
								
									
										450
									
								
								support/src/utils/encrypting_reader.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								support/src/utils/encrypting_reader.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,450 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
| #include "utils/encrypting_reader.hpp" | ||||
|  | ||||
| #include "utils/collection.hpp" | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/encryption.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/file.hpp" | ||||
| #include "utils/unix.hpp" | ||||
| #include "utils/windows.hpp" | ||||
|  | ||||
| #if !defined(CURL_READFUNC_ABORT) | ||||
| #define CURL_READFUNC_ABORT (-1) | ||||
| #endif // !defined(CURL_READFUNC_ABORT) | ||||
|  | ||||
| namespace repertory::utils::encryption { | ||||
| class encrypting_streambuf final : public encrypting_reader::streambuf { | ||||
| public: | ||||
|   encrypting_streambuf(const encrypting_streambuf &) = default; | ||||
|   encrypting_streambuf(encrypting_streambuf &&) = delete; | ||||
|   auto | ||||
|   operator=(const encrypting_streambuf &) -> encrypting_streambuf & = delete; | ||||
|   auto operator=(encrypting_streambuf &&) -> encrypting_streambuf & = delete; | ||||
|  | ||||
|   explicit encrypting_streambuf(const encrypting_reader &reader) | ||||
|       : reader_(reader) { | ||||
|     setg(reinterpret_cast<char *>(0), reinterpret_cast<char *>(0), | ||||
|          reinterpret_cast<char *>(reader_.get_total_size())); | ||||
|   } | ||||
|  | ||||
|   ~encrypting_streambuf() override = default; | ||||
|  | ||||
| private: | ||||
|   encrypting_reader reader_; | ||||
|  | ||||
| protected: | ||||
|   auto seekoff(off_type off, std::ios_base::seekdir dir, | ||||
|                std::ios_base::openmode which = std::ios_base::out | | ||||
|                                                std::ios_base::in) | ||||
|       -> pos_type override { | ||||
|     REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|     if ((which & std::ios_base::in) != std::ios_base::in) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "output is not supported", | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     const auto set_position = [this](char *next) -> pos_type { | ||||
|       if ((next <= egptr()) && (next >= eback())) { | ||||
|         setg(eback(), next, reinterpret_cast<char *>(reader_.get_total_size())); | ||||
|         return static_cast<std::streamoff>( | ||||
|             reinterpret_cast<std::uintptr_t>(gptr())); | ||||
|       } | ||||
|  | ||||
|       return {traits_type::eof()}; | ||||
|     }; | ||||
|  | ||||
|     switch (dir) { | ||||
|     case std::ios_base::beg: | ||||
|       return set_position(eback() + off); | ||||
|  | ||||
|     case std::ios_base::cur: | ||||
|       return set_position(gptr() + off); | ||||
|  | ||||
|     case std::ios_base::end: | ||||
|       return set_position(egptr() + off); | ||||
|     } | ||||
|  | ||||
|     return encrypting_reader::streambuf::seekoff(off, dir, which); | ||||
|   } | ||||
|  | ||||
|   auto seekpos(pos_type pos, std::ios_base::openmode which = | ||||
|                                  std::ios_base::out | | ||||
|                                  std::ios_base::in) -> pos_type override { | ||||
|     return seekoff(pos, std::ios_base::beg, which); | ||||
|   } | ||||
|  | ||||
|   auto uflow() -> int_type override { | ||||
|     auto ret = underflow(); | ||||
|     if (ret == traits_type::eof()) { | ||||
|       return ret; | ||||
|     } | ||||
|  | ||||
|     gbump(1); | ||||
|  | ||||
|     return ret; | ||||
|   } | ||||
|  | ||||
|   auto underflow() -> int_type override { | ||||
|     if (gptr() == egptr()) { | ||||
|       return traits_type::eof(); | ||||
|     } | ||||
|  | ||||
|     reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr())); | ||||
|  | ||||
|     char c{}; | ||||
|     const auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_); | ||||
|     if (res != 1) { | ||||
|       return traits_type::eof(); | ||||
|     } | ||||
|  | ||||
|     return c; | ||||
|   } | ||||
|  | ||||
|   auto xsgetn(char *ptr, std::streamsize count) -> std::streamsize override { | ||||
|     if (gptr() == egptr()) { | ||||
|       return traits_type::eof(); | ||||
|     } | ||||
|  | ||||
|     reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr())); | ||||
|  | ||||
|     auto res = encrypting_reader::reader_function( | ||||
|         ptr, 1U, static_cast<std::size_t>(count), &reader_); | ||||
|     if ((res == reader_.get_error_return()) || | ||||
|         (reader_.get_stop_requested() && | ||||
|          (res == static_cast<std::size_t>(CURL_READFUNC_ABORT)))) { | ||||
|       return traits_type::eof(); | ||||
|     } | ||||
|  | ||||
|     setg(eback(), gptr() + res, | ||||
|          reinterpret_cast<char *>(reader_.get_total_size())); | ||||
|     return static_cast<std::streamsize>(res); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class encrypting_reader_iostream final : public encrypting_reader::iostream { | ||||
| public: | ||||
|   encrypting_reader_iostream(const encrypting_reader_iostream &) = delete; | ||||
|   encrypting_reader_iostream(encrypting_reader_iostream &&) = delete; | ||||
|  | ||||
|   auto operator=(const encrypting_reader_iostream &) | ||||
|       -> encrypting_reader_iostream & = delete; | ||||
|   auto operator=(encrypting_reader_iostream &&) | ||||
|       -> encrypting_reader_iostream & = delete; | ||||
|  | ||||
|   explicit encrypting_reader_iostream( | ||||
|       std::unique_ptr<encrypting_streambuf> buffer) | ||||
|       : encrypting_reader::iostream(buffer.get()), buffer_(std::move(buffer)) {} | ||||
|  | ||||
|   ~encrypting_reader_iostream() override = default; | ||||
|  | ||||
| private: | ||||
|   std::unique_ptr<encrypting_streambuf> buffer_; | ||||
| }; | ||||
|  | ||||
| const std::size_t encrypting_reader::header_size_ = ([]() { | ||||
|   return crypto_aead_xchacha20poly1305_IETF_NPUBBYTES + | ||||
|          crypto_aead_xchacha20poly1305_IETF_ABYTES; | ||||
| })(); | ||||
| const std::size_t encrypting_reader::data_chunk_size_ = (8UL * 1024UL * 1024UL); | ||||
| const std::size_t encrypting_reader::encrypted_chunk_size_ = | ||||
|     data_chunk_size_ + header_size_; | ||||
|  | ||||
| encrypting_reader::encrypting_reader( | ||||
|     std::string_view file_name, std::string_view source_path, | ||||
|     stop_type &stop_requested, std::string_view token, | ||||
|     std::optional<std::string> relative_parent_path, std::size_t error_return) | ||||
|     : key_(utils::encryption::generate_key<utils::encryption::hash_256_t>( | ||||
|           token)), | ||||
|       stop_requested_(stop_requested), | ||||
|       error_return_(error_return), | ||||
|       source_file_(utils::file::file::open_or_create_file(source_path, true)) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not *source_file_) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "file open failed", | ||||
|                                                             source_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   data_buffer result; | ||||
|   utils::encryption::encrypt_data( | ||||
|       key_, reinterpret_cast<const unsigned char *>(file_name.data()), | ||||
|       file_name.size(), result); | ||||
|   encrypted_file_name_ = utils::collection::to_hex_string(result); | ||||
|  | ||||
|   if (relative_parent_path.has_value()) { | ||||
|     for (auto &&part : | ||||
|          utils::string::split(relative_parent_path.value(), | ||||
|                               utils::path::directory_seperator, false)) { | ||||
|       utils::encryption::encrypt_data( | ||||
|           key_, reinterpret_cast<const unsigned char *>(part.c_str()), | ||||
|           strnlen(part.c_str(), part.size()), result); | ||||
|       encrypted_file_path_ += '/' + utils::collection::to_hex_string(result); | ||||
|     } | ||||
|     encrypted_file_path_ += '/' + encrypted_file_name_; | ||||
|   } | ||||
|  | ||||
|   auto opt_size = source_file_->size(); | ||||
|   if (not opt_size.has_value()) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to get file size", | ||||
|                                              source_file_->get_path(), | ||||
|                                          }); | ||||
|   } | ||||
|   auto file_size = opt_size.value(); | ||||
|  | ||||
|   const auto total_chunks = utils::divide_with_ceiling( | ||||
|       file_size, static_cast<std::uint64_t>(data_chunk_size_)); | ||||
|   total_size_ = file_size + (total_chunks * encryption_header_size); | ||||
|   last_data_chunk_ = total_chunks - 1U; | ||||
|   last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size | ||||
|                           : (file_size % data_chunk_size_) == 0U | ||||
|                               ? data_chunk_size_ | ||||
|                               : file_size % data_chunk_size_; | ||||
|   iv_list_.resize(total_chunks); | ||||
|   for (auto &iv : iv_list_) { | ||||
|     randombytes_buf(iv.data(), iv.size()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| encrypting_reader::encrypting_reader(std::string_view encrypted_file_path, | ||||
|                                      std::string_view source_path, | ||||
|                                      stop_type &stop_requested, | ||||
|                                      std::string_view token, | ||||
|                                      std::size_t error_return) | ||||
|     : key_(utils::encryption::generate_key<utils::encryption::hash_256_t>( | ||||
|           token)), | ||||
|       stop_requested_(stop_requested), | ||||
|       error_return_(error_return), | ||||
|       source_file_(utils::file::file::open_or_create_file(source_path, true)) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not *source_file_) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "file open failed", | ||||
|                                                             source_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   encrypted_file_path_ = encrypted_file_path; | ||||
|   encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_); | ||||
|  | ||||
|   auto opt_size = source_file_->size(); | ||||
|   if (not opt_size.has_value()) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to get file size", | ||||
|                                              source_file_->get_path(), | ||||
|                                          }); | ||||
|   } | ||||
|   auto file_size = opt_size.value(); | ||||
|  | ||||
|   const auto total_chunks = utils::divide_with_ceiling( | ||||
|       file_size, static_cast<std::uint64_t>(data_chunk_size_)); | ||||
|   total_size_ = file_size + (total_chunks * encryption_header_size); | ||||
|   last_data_chunk_ = total_chunks - 1U; | ||||
|   last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size | ||||
|                           : (file_size % data_chunk_size_) == 0U | ||||
|                               ? data_chunk_size_ | ||||
|                               : file_size % data_chunk_size_; | ||||
|   iv_list_.resize(total_chunks); | ||||
|   for (auto &iv : iv_list_) { | ||||
|     randombytes_buf(iv.data(), iv.size()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| encrypting_reader::encrypting_reader( | ||||
|     std::string_view encrypted_file_path, std::string_view source_path, | ||||
|     stop_type &stop_requested, std::string_view token, | ||||
|     std::vector< | ||||
|         std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>> | ||||
|         iv_list, | ||||
|     std::size_t error_return) | ||||
|     : key_(utils::encryption::generate_key<utils::encryption::hash_256_t>( | ||||
|           token)), | ||||
|       stop_requested_(stop_requested), | ||||
|       error_return_(error_return), | ||||
|       source_file_(utils::file::file::open_or_create_file(source_path, true)) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not *source_file_) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "file open failed", | ||||
|                                                             source_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   encrypted_file_path_ = encrypted_file_path; | ||||
|   encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_); | ||||
|  | ||||
|   auto opt_size = source_file_->size(); | ||||
|   if (not opt_size.has_value()) { | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "get file size failed", | ||||
|                            std::to_string(utils::get_last_error_code()), | ||||
|                            source_file_->get_path(), | ||||
|                        }); | ||||
|   } | ||||
|   auto file_size{opt_size.value()}; | ||||
|  | ||||
|   const auto total_chunks = utils::divide_with_ceiling( | ||||
|       file_size, static_cast<std::uint64_t>(data_chunk_size_)); | ||||
|   total_size_ = file_size + (total_chunks * encryption_header_size); | ||||
|   last_data_chunk_ = total_chunks - 1U; | ||||
|   last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size | ||||
|                           : (file_size % data_chunk_size_) == 0U | ||||
|                               ? data_chunk_size_ | ||||
|                               : file_size % data_chunk_size_; | ||||
|   iv_list_ = std::move(iv_list); | ||||
| } | ||||
|  | ||||
| encrypting_reader::encrypting_reader(const encrypting_reader &reader) | ||||
|     : key_(reader.key_), | ||||
|       stop_requested_(reader.stop_requested_), | ||||
|       error_return_(reader.error_return_), | ||||
|       source_file_( | ||||
|           utils::file::file::open_file(reader.source_file_->get_path(), true)), | ||||
|       chunk_buffers_(reader.chunk_buffers_), | ||||
|       encrypted_file_name_(reader.encrypted_file_name_), | ||||
|       encrypted_file_path_(reader.encrypted_file_path_), | ||||
|       iv_list_(reader.iv_list_), | ||||
|       last_data_chunk_(reader.last_data_chunk_), | ||||
|       last_data_chunk_size_(reader.last_data_chunk_size_), | ||||
|       read_offset_(reader.read_offset_), | ||||
|       total_size_(reader.total_size_) { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not *source_file_) { | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "file open failed", | ||||
|                            std::to_string(utils::get_last_error_code()), | ||||
|                            source_file_->get_path(), | ||||
|                        }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size) | ||||
|     -> std::uint64_t { | ||||
|   return total_size - (utils::divide_with_ceiling( | ||||
|                            total_size, static_cast<std::uint64_t>( | ||||
|                                            get_encrypted_chunk_size())) * | ||||
|                        encryption_header_size); | ||||
| } | ||||
|  | ||||
| auto encrypting_reader::calculate_encrypted_size(std::string_view source_path) | ||||
|     -> std::uint64_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto opt_size = utils::file::file{source_path}.size(); | ||||
|   if (not opt_size.has_value()) { | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "get file size failed", | ||||
|                            std::to_string(utils::get_last_error_code()), | ||||
|                            source_path, | ||||
|                        }); | ||||
|   } | ||||
|   auto file_size{opt_size.value()}; | ||||
|  | ||||
|   const auto total_chunks = utils::divide_with_ceiling( | ||||
|       file_size, static_cast<std::uint64_t>(data_chunk_size_)); | ||||
|   return file_size + (total_chunks * encryption_header_size); | ||||
| } | ||||
|  | ||||
| auto encrypting_reader::create_iostream() const | ||||
|     -> std::shared_ptr<encrypting_reader::iostream> { | ||||
|   return std::make_shared<encrypting_reader_iostream>( | ||||
|       std::make_unique<encrypting_streambuf>(*this)); | ||||
| } | ||||
|  | ||||
| auto encrypting_reader::reader_function(char *buffer, size_t size, | ||||
|                                         size_t nitems) -> size_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   const auto read_size = static_cast<std::size_t>(std::min( | ||||
|       static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_)); | ||||
|  | ||||
|   auto chunk = read_offset_ / encrypted_chunk_size_; | ||||
|   auto chunk_offset = read_offset_ % encrypted_chunk_size_; | ||||
|   std::size_t total_read{}; | ||||
|  | ||||
|   auto ret = false; | ||||
|   if (read_offset_ < total_size_) { | ||||
|     try { | ||||
|       ret = true; | ||||
|       auto remain = read_size; | ||||
|       while (not stop_requested_ && ret && (remain != 0U)) { | ||||
|         if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) { | ||||
|           auto &chunk_buffer = chunk_buffers_[chunk]; | ||||
|           data_buffer file_data(chunk == last_data_chunk_ | ||||
|                                     ? last_data_chunk_size_ | ||||
|                                     : data_chunk_size_); | ||||
|           chunk_buffer.resize(file_data.size() + encryption_header_size); | ||||
|  | ||||
|           std::size_t bytes_read{}; | ||||
|           if ((ret = source_file_->read(file_data, chunk * data_chunk_size_, | ||||
|                                         &bytes_read))) { | ||||
|             utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data, | ||||
|                                             chunk_buffer); | ||||
|           } | ||||
|         } else if (chunk) { | ||||
|           chunk_buffers_.erase(chunk - 1u); | ||||
|         } | ||||
|  | ||||
|         auto &chunk_buffer = chunk_buffers_[chunk]; | ||||
|         const auto to_read = std::min( | ||||
|             static_cast<std::size_t>(chunk_buffer.size() - chunk_offset), | ||||
|             remain); | ||||
|         std::memcpy(buffer + total_read, &chunk_buffer[chunk_offset], to_read); | ||||
|         total_read += to_read; | ||||
|         remain -= to_read; | ||||
|         chunk_offset = 0u; | ||||
|         chunk++; | ||||
|         read_offset_ += to_read; | ||||
|       } | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::handle_exception(function_name, e); | ||||
|       ret = false; | ||||
|     } catch (...) { | ||||
|       utils::error::handle_exception(function_name); | ||||
|       ret = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return stop_requested_ ? static_cast<std::size_t>(CURL_READFUNC_ABORT) | ||||
|          : ret           ? total_read | ||||
|                          : error_return_; | ||||
| } | ||||
| } // namespace repertory::utils::encryption | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
							
								
								
									
										117
									
								
								support/src/utils/encryption.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								support/src/utils/encryption.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
| #include "utils/encryption.hpp" | ||||
|  | ||||
| #include "utils/collection.hpp" | ||||
| #include "utils/encrypting_reader.hpp" | ||||
|  | ||||
| namespace repertory::utils::encryption { | ||||
| auto decrypt_file_path(std::string_view encryption_token, | ||||
|                        std::string &file_path) -> bool { | ||||
|   std::string decrypted_file_path{}; | ||||
|   for (const auto &part : std::filesystem::path(file_path)) { | ||||
|     auto file_name = part.string(); | ||||
|     if (file_name == "/") { | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     auto res = decrypt_file_name(encryption_token, file_name); | ||||
|     if (not res) { | ||||
|       return res; | ||||
|     } | ||||
|  | ||||
|     decrypted_file_path += '/' + file_name; | ||||
|   } | ||||
|  | ||||
|   file_path = decrypted_file_path; | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| auto decrypt_file_name(std::string_view encryption_token, | ||||
|                        std::string &file_name) -> bool { | ||||
|   data_buffer buffer; | ||||
|   if (not utils::collection::from_hex_string(file_name, buffer)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   file_name.clear(); | ||||
|   if (not utils::encryption::decrypt_data(encryption_token, buffer, | ||||
|                                           file_name)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_CURL) | ||||
| auto read_encrypted_range(const http_range &range, | ||||
|                           const utils::encryption::hash_256_t &key, | ||||
|                           reader_func_t reader_func, std::uint64_t total_size, | ||||
|                           data_buffer &data) -> bool { | ||||
|   const auto encrypted_chunk_size = | ||||
|       utils::encryption::encrypting_reader::get_encrypted_chunk_size(); | ||||
|   const auto data_chunk_size = | ||||
|       utils::encryption::encrypting_reader::get_data_chunk_size(); | ||||
|  | ||||
|   const auto start_chunk = | ||||
|       static_cast<std::size_t>(range.begin / data_chunk_size); | ||||
|   const auto end_chunk = static_cast<std::size_t>(range.end / data_chunk_size); | ||||
|   auto remain = range.end - range.begin + 1U; | ||||
|   auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size); | ||||
|  | ||||
|   for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) { | ||||
|     data_buffer cypher; | ||||
|     const auto start_offset = chunk * encrypted_chunk_size; | ||||
|     const auto end_offset = std::min( | ||||
|         start_offset + (total_size - (chunk * data_chunk_size)) + | ||||
|             encryption_header_size - 1U, | ||||
|         static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U)); | ||||
|  | ||||
|     if (not reader_func(cypher, start_offset, end_offset)) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     data_buffer source_buffer; | ||||
|     if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) { | ||||
|       return false; | ||||
|     } | ||||
|     cypher.clear(); | ||||
|  | ||||
|     const auto data_size = static_cast<std::size_t>(std::min( | ||||
|         remain, static_cast<std::uint64_t>(data_chunk_size - source_offset))); | ||||
|     std::copy(std::next(source_buffer.begin(), | ||||
|                         static_cast<std::int64_t>(source_offset)), | ||||
|               std::next(source_buffer.begin(), | ||||
|                         static_cast<std::int64_t>(source_offset + data_size)), | ||||
|               std::back_inserter(data)); | ||||
|     remain -= data_size; | ||||
|     source_offset = 0U; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_CURL) | ||||
| } // namespace repertory::utils::encryption | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST) | ||||
							
								
								
									
										82
									
								
								support/src/utils/error.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								support/src/utils/error.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/error.hpp" | ||||
|  | ||||
| namespace repertory::utils::error { | ||||
| std::atomic<const i_exception_handler *> exception_handler{ | ||||
|     &default_exception_handler}; | ||||
|  | ||||
| auto create_error_message(std::vector<std::string_view> items) -> std::string { | ||||
|   std::stringstream stream{}; | ||||
|   for (std::size_t idx = 0U; idx < items.size(); ++idx) { | ||||
|     if (idx > 0) { | ||||
|       stream << '|'; | ||||
|     } | ||||
|  | ||||
|     stream << items.at(idx); | ||||
|   } | ||||
|  | ||||
|   return stream.str(); | ||||
| } | ||||
|  | ||||
| auto create_exception(std::string_view function_name, | ||||
|                       std::vector<std::string_view> items) | ||||
|     -> std::runtime_error { | ||||
|   items.insert(items.begin(), function_name); | ||||
|   return std::runtime_error(create_error_message(items)); | ||||
| } | ||||
|  | ||||
| void handle_error(std::string_view function_name, std::string_view msg) { | ||||
|   const i_exception_handler *handler{exception_handler}; | ||||
|   if (handler != nullptr) { | ||||
|     handler->handle_error(function_name, msg); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   default_exception_handler.handle_error(function_name, msg); | ||||
| } | ||||
|  | ||||
| void handle_exception(std::string_view function_name) { | ||||
|   const i_exception_handler *handler{exception_handler}; | ||||
|   if (handler != nullptr) { | ||||
|     handler->handle_exception(function_name); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   default_exception_handler.handle_exception(function_name); | ||||
| } | ||||
|  | ||||
| void handle_exception(std::string_view function_name, | ||||
|                       const std::exception &ex) { | ||||
|   const i_exception_handler *handler{exception_handler}; | ||||
|   if (handler != nullptr) { | ||||
|     handler->handle_exception(function_name, ex); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   default_exception_handler.handle_exception(function_name, ex); | ||||
| } | ||||
|  | ||||
| void set_exception_handler(const i_exception_handler *handler) { | ||||
|   exception_handler = handler; | ||||
| } | ||||
| } // namespace repertory::utils::error | ||||
							
								
								
									
										713
									
								
								support/src/utils/file.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										713
									
								
								support/src/utils/file.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,713 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/file.hpp" | ||||
|  | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/encryption.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/path.hpp" | ||||
| #include "utils/string.hpp" | ||||
| #include "utils/time.hpp" | ||||
| #include "utils/unix.hpp" | ||||
| #include "utils/windows.hpp" | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| auto change_to_process_directory() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
| #if defined(_WIN32) | ||||
|     std::string file_name; | ||||
|     file_name.resize(repertory::max_path_length + 1U); | ||||
|  | ||||
|     ::GetModuleFileNameA(nullptr, file_name.data(), | ||||
|                          static_cast<DWORD>(file_name.size() - 1U)); | ||||
|     auto path = utils::path::strip_to_file_name(file_name.c_str()); | ||||
|     ::SetCurrentDirectoryA(path.c_str()); | ||||
| #else // !defined(_WIN32) | ||||
|     std::string path; | ||||
|     path.resize(PATH_MAX + 1); | ||||
| #if defined(__APPLE__) | ||||
|     proc_pidpath(getpid(), path.c_str(), path.size()); | ||||
| #else  // !defined(__APPLE__) | ||||
|     auto res = readlink("/proc/self/exe", path.data(), path.size()); | ||||
|     if (res == -1) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to readlink", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
| #endif // defined(__APPLE__) | ||||
|     path = utils::path::get_parent_path(path); | ||||
|     res = chdir(path.c_str()); | ||||
|     if (res != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to chdir", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto create_temp_name(std::string_view file_part) -> std::string { | ||||
|   std::array<std::uint8_t, 8U> data{ | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|       utils::generate_random_between<std::uint8_t>(0U, 9U), | ||||
|   }; | ||||
|  | ||||
|   return std::accumulate(data.begin(), data.end(), std::string{file_part} + '_', | ||||
|                          [](auto &&name, auto &&val) -> auto { | ||||
|                            return name + std::to_string(val); | ||||
|                          }); | ||||
| } | ||||
|  | ||||
| auto create_temp_name(std::wstring_view file_part) -> std::wstring { | ||||
|   return utils::string::from_utf8( | ||||
|       create_temp_name(utils::string::to_utf8(file_part))); | ||||
| } | ||||
|  | ||||
| auto get_free_drive_space(std::string_view path) | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
| #if defined(_WIN32) | ||||
|     ULARGE_INTEGER li{}; | ||||
|     if (not::GetDiskFreeSpaceEx(std::string{path}.c_str(), &li, nullptr, | ||||
|                                 nullptr)) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get free disk space", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     return li.QuadPart; | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
| #if defined(__linux__) | ||||
|     struct statfs64 st{}; | ||||
|     if (statfs64(std::string{path}.c_str(), &st) != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get free disk space", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     return st.f_bfree * static_cast<std::uint64_t>(st.f_bsize); | ||||
| #endif // defined(__linux__) | ||||
|  | ||||
| #if defined(__APPLE__) | ||||
|     struct statvfs st{}; | ||||
|     if (statvfs(path.c_str(), &st) != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get free disk space", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     return st.f_bfree * static_cast<std::uint64_t>(st.f_frsize); | ||||
| #endif // defined(__APPLE__) | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
|  | ||||
| auto get_free_drive_space(std::wstring_view path) | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   return get_free_drive_space(utils::string::to_utf8(path)); | ||||
| } | ||||
|  | ||||
| auto get_time(std::string_view path, time_type type) | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   auto times = get_times(path); | ||||
|   if (times.has_value()) { | ||||
|     return times->get(type); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
|  | ||||
| auto get_time(std::wstring_view path, time_type type) | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   return get_time(utils::string::to_utf8(path), type); | ||||
| } | ||||
|  | ||||
| auto get_times(std::string_view path) -> std::optional<file_times> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     file_times ret{}; | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|     auto file_handle = ::CreateFileA( | ||||
|         std::string{path}.c_str(), GENERIC_READ, | ||||
|         FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, | ||||
|         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); | ||||
|     if (file_handle != INVALID_HANDLE_VALUE) { | ||||
|       std::array<FILETIME, 3U> times{}; | ||||
|       auto res = ::GetFileTime(file_handle, ×.at(0U), ×.at(1U), | ||||
|                                ×.at(2U)); | ||||
|       ::CloseHandle(file_handle); | ||||
|       if (res) { | ||||
|         ret.accessed = | ||||
|             utils::time::windows_file_time_to_unix_time(times.at(1U)); | ||||
|         ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U)); | ||||
|         ret.modified = | ||||
|             utils::time::windows_file_time_to_unix_time(times.at(2U)); | ||||
|         ret.written = utils::time::windows_file_time_to_unix_time(times.at(2U)); | ||||
|         return ret; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     struct _stat64 st{}; | ||||
|     if (_stat64(std::string{path}.c_str(), &st) != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get file times", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     ret.accessed = utils::time::windows_time_t_to_unix_time(st.st_atime); | ||||
|     ret.created = utils::time::windows_time_t_to_unix_time(st.st_ctime); | ||||
|     ret.modified = utils::time::windows_time_t_to_unix_time(st.st_mtime); | ||||
|     ret.written = utils::time::windows_time_t_to_unix_time(st.st_mtime); | ||||
| #else // !defined(_WIN32) | ||||
|     struct stat64 st{}; | ||||
|     if (stat64(std::string{path}.c_str(), &st) != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get file times", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     ret.accessed = static_cast<std::uint64_t>(st.st_atim.tv_nsec) + | ||||
|                    static_cast<std::uint64_t>(st.st_atim.tv_sec) * | ||||
|                        utils::time::NANOS_PER_SECOND; | ||||
|     ret.created = static_cast<std::uint64_t>(st.st_ctim.tv_nsec) + | ||||
|                   static_cast<std::uint64_t>(st.st_ctim.tv_sec) * | ||||
|                       utils::time::NANOS_PER_SECOND; | ||||
|     ret.modified = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) + | ||||
|                    static_cast<std::uint64_t>(st.st_mtim.tv_sec) * | ||||
|                        utils::time::NANOS_PER_SECOND; | ||||
|     ret.written = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) + | ||||
|                   static_cast<std::uint64_t>(st.st_mtim.tv_sec) * | ||||
|                       utils::time::NANOS_PER_SECOND; | ||||
|  | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
|  | ||||
| auto get_times(std::wstring_view path) -> std::optional<file_times> { | ||||
|   return get_times(utils::string::to_utf8(path)); | ||||
| } | ||||
|  | ||||
| auto get_total_drive_space(std::string_view path) | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
| #if defined(_WIN32) | ||||
|     ULARGE_INTEGER li{}; | ||||
|     if (not::GetDiskFreeSpaceEx(std::string{path}.c_str(), nullptr, &li, | ||||
|                                 nullptr)) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get total disk space", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     return li.QuadPart; | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
| #if defined(__linux__) | ||||
|     struct statfs64 st{}; | ||||
|     if (statfs64(std::string{path}.c_str(), &st) != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get total disk space", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     return st.f_blocks * static_cast<std::uint64_t>(st.f_bsize); | ||||
| #endif // defined(__linux__) | ||||
|  | ||||
| #if defined(__APPLE__) | ||||
|     struct statvfs st{}; | ||||
|     if (statvfs(path.c_str(), &st) != 0) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to get total disk space", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     return st.f_blocks * static_cast<std::uint64_t>(st.f_frsize); | ||||
| #endif // defined(__APPLE__) | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
|  | ||||
| auto get_total_drive_space(std::wstring_view path) | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   return get_total_drive_space(utils::string::to_utf8(path)); | ||||
| } | ||||
|  | ||||
| auto i_fs_item::get_time(time_type type) const -> std::optional<std::uint64_t> { | ||||
|   return utils::file::get_time(get_path(), type); | ||||
| } | ||||
|  | ||||
| auto i_file::read_all(data_buffer &data, std::uint64_t offset, | ||||
|                       std::size_t *total_read) -> bool { | ||||
|   data_buffer buffer; | ||||
|   buffer.resize(get_read_buffer_size()); | ||||
|  | ||||
|   std::size_t current_read{}; | ||||
|   while (read(reinterpret_cast<unsigned char *>(buffer.data()), | ||||
|               buffer.size() * sizeof(data_buffer::value_type), offset, | ||||
|               ¤t_read)) { | ||||
|     if (total_read != nullptr) { | ||||
|       *total_read += current_read; | ||||
|     } | ||||
|  | ||||
|     if (current_read != 0U) { | ||||
|       offset += current_read; | ||||
|  | ||||
|       data.insert( | ||||
|           data.end(), buffer.begin(), | ||||
|           std::next(buffer.begin(), | ||||
|                     static_cast<std::int64_t>( | ||||
|                         current_read / sizeof(data_buffer::value_type)))); | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_JSON) | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| auto read_json_file(std::string_view path, nlohmann::json &data, | ||||
|                     std::optional<std::string_view> password) -> bool { | ||||
| #else  // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| auto read_json_file(std::string_view path, nlohmann::json &data) -> bool { | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto abs_path = utils::path::absolute(path); | ||||
|     auto file = file::open_file(abs_path); | ||||
|     if (not*file) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|       data_buffer buffer{}; | ||||
|       if (not file->read_all(buffer, 0U)) { | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|       if (password.has_value()) { | ||||
|         data_buffer decrypted_data{}; | ||||
|         if (not utils::encryption::decrypt_data(*password, buffer, | ||||
|                                                 decrypted_data)) { | ||||
|           return false; | ||||
|         } | ||||
|  | ||||
|         buffer = decrypted_data; | ||||
|       } | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
|       std::string json_str(buffer.begin(), buffer.end()); | ||||
|       if (not json_str.empty()) { | ||||
|         data = nlohmann::json::parse(json_str); | ||||
|       } | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::handle_exception(function_name, e); | ||||
|       return false; | ||||
|     } catch (...) { | ||||
|       utils::error::handle_exception(function_name); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| auto write_json_file(std::string_view path, const nlohmann::json &data, | ||||
|                      std::optional<std::string_view> password) -> bool { | ||||
| #else  // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| auto write_json_file(std::string_view path, const nlohmann::json &data) | ||||
|     -> bool { | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto file = file::open_or_create_file(path); | ||||
|     if (not file->truncate()) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to truncate file", | ||||
|                              std::to_string(utils::get_last_error_code()), | ||||
|                              path, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|     if (password.has_value()) { | ||||
|       const auto str_data = data.dump(2); | ||||
|  | ||||
|       data_buffer encrypted_data{}; | ||||
|       utils::encryption::encrypt_data( | ||||
|           *password, reinterpret_cast<const unsigned char *>(str_data.c_str()), | ||||
|           str_data.size(), encrypted_data); | ||||
|       return file->write(encrypted_data, 0U); | ||||
|     } | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
|     auto json_str = data.dump(2); | ||||
|     return file->write( | ||||
|         reinterpret_cast<const unsigned char *>(json_str.c_str()), | ||||
|         json_str.size(), 0U); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| auto read_json_file(std::wstring_view path, nlohmann::json &data, | ||||
|                     std::optional<std::wstring_view> password) -> bool { | ||||
|   if (password.has_value()) { | ||||
|     auto password_a = utils::string::to_utf8(*password); | ||||
|     return read_json_file(utils::string::to_utf8(path), data, password_a); | ||||
|   } | ||||
|  | ||||
|   return read_json_file(utils::string::to_utf8(path), data, std::nullopt); | ||||
| } | ||||
|  | ||||
| auto write_json_file(std::wstring_view path, const nlohmann::json &data, | ||||
|                      std::optional<std::wstring_view> password) -> bool { | ||||
|   if (password.has_value()) { | ||||
|     auto password_a = utils::string::to_utf8(*password); | ||||
|     return write_json_file(utils::string::to_utf8(path), data, password_a); | ||||
|   } | ||||
|  | ||||
|   return write_json_file(utils::string::to_utf8(path), data, std::nullopt); | ||||
| } | ||||
| #else  // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| auto read_json_file(std::wstring_view path, nlohmann::json &data) -> bool { | ||||
|   return read_json_file(utils::string::to_utf8(path), data); | ||||
| } | ||||
|  | ||||
| auto write_json_file(std::wstring_view path, const nlohmann::json &data) | ||||
|     -> bool { | ||||
|   return write_json_file(utils::string::to_utf8(path), data); | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
| #endif // defined(PROJECT_ENABLE_JSON) | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_LIBDSM) | ||||
| static constexpr const auto validate_smb_path = | ||||
|     [](std::string_view path) -> bool { | ||||
|   return (not utils::string::begins_with(path, "///") && | ||||
|           utils::string::begins_with(path, "//") && | ||||
|           // not utils::string::contains(path, " ") && | ||||
|           std::count(path.begin(), path.end(), '/') >= 3U); | ||||
| }; | ||||
|  | ||||
| auto smb_create_smb_path(std::string_view smb_path, std::string_view rel_path) | ||||
|     -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   std::string path{rel_path}; | ||||
|   utils::path::format_path(path, "/", "\\"); | ||||
|   utils::string::left_trim(path, '/'); | ||||
|  | ||||
|   auto old_parts = | ||||
|       repertory::utils::string::split(smb_path.substr(2U), '/', false); | ||||
|  | ||||
|   auto new_parts = repertory::utils::string::split(path, '/', false); | ||||
|   old_parts.insert(old_parts.end(), new_parts.begin(), new_parts.end()); | ||||
|  | ||||
|   path = utils::string::join(old_parts, '/'); | ||||
|   path = "//" + utils::path::format_path(path, "/", "\\"); | ||||
|  | ||||
|   if (not validate_smb_path(path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   return path; | ||||
| } | ||||
|  | ||||
| auto smb_create_and_validate_relative_path(std::string_view smb_path, | ||||
|                                            std::string_view path) | ||||
|     -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   std::string dir_path; | ||||
|   if (utils::string::begins_with(path, "//")) { | ||||
|     if (not utils::file::smb_parent_is_same(smb_path, path)) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to validate path", | ||||
|                                                "parent paths are not the same", | ||||
|                                                smb_path, | ||||
|                                                path, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return utils::file::smb_create_relative_path(path); | ||||
|   } | ||||
|  | ||||
|   return utils::file::smb_create_relative_path(std::string{smb_path} + '/' + | ||||
|                                                std::string{path}); | ||||
| } | ||||
|  | ||||
| auto smb_create_relative_path(std::string_view smb_path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   std::string path{smb_path}; | ||||
|   utils::path::format_path(path, "\\", "/"); | ||||
|   utils::string::left_trim(path, '\\'); | ||||
|  | ||||
|   auto parts = repertory::utils::string::split(path, '\\', false); | ||||
|   parts.erase(parts.begin(), std::next(parts.begin(), 2U)); | ||||
|   return "\\" + utils::string::join(parts, '\\'); | ||||
| } | ||||
|  | ||||
| auto smb_create_search_path(std::string_view smb_path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   std::string path{smb_path}; | ||||
|   utils::string::left_trim(path, '/'); | ||||
|  | ||||
|   auto parts = repertory::utils::string::split(path, '/', false); | ||||
|   parts.erase(parts.begin(), std::next(parts.begin(), 2U)); | ||||
|  | ||||
|   auto search_path = repertory::utils::string::join(parts, '\\'); | ||||
|   return search_path.empty() ? "\\*" : "\\" + search_path + "\\*"; | ||||
| } | ||||
|  | ||||
| auto smb_get_parent_path(std::string_view smb_path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   auto parts = repertory::utils::string::split(smb_path.substr(2U), '/', false); | ||||
|   if (parts.size() > 2U) { | ||||
|     parts.erase(std::prev(parts.end()), parts.end()); | ||||
|   } | ||||
|  | ||||
|   auto parent_smb_path = "//" + utils::string::join(parts, '/'); | ||||
|   if (not validate_smb_path(parent_smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "invalid parent smb path", | ||||
|                                              parent_smb_path, | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   return parent_smb_path; | ||||
| } | ||||
|  | ||||
| auto smb_get_root_path(std::string_view smb_path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   auto parts = repertory::utils::string::split(smb_path.substr(2U), '/', false); | ||||
|   if (parts.size() > 2U) { | ||||
|     parts.erase(std::next(parts.begin(), 2U), parts.end()); | ||||
|   } | ||||
|  | ||||
|   return "//" + utils::string::join(parts, '/'); | ||||
| } | ||||
|  | ||||
| auto smb_get_unc_path(std::string_view smb_path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   std::string unc_path{smb_path}; | ||||
|   utils::path::format_path(unc_path, "\\", "/"); | ||||
|   return '\\' + unc_path; | ||||
| } | ||||
|  | ||||
| auto smb_get_uri_path(std::string_view smb_path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   return "smb:" + std::string{smb_path}; | ||||
| } | ||||
|  | ||||
| auto smb_get_uri_path(std::string_view smb_path, std::string_view user, | ||||
|                       std::string_view password) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not validate_smb_path(smb_path)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "invalid smb path", | ||||
|                                                             smb_path, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
|   return "smb://" + std::string{user} + ':' + std::string{password} + '@' + | ||||
|          std::string{smb_path.substr(2U)}; | ||||
| } | ||||
|  | ||||
| auto smb_parent_is_same(std::string_view smb_path1, std::string_view smb_path2) | ||||
|     -> bool { | ||||
|   if (not(validate_smb_path(smb_path1) && validate_smb_path(smb_path2))) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   auto parts1 = utils::string::split(smb_path1.substr(2U), "/", false); | ||||
|   auto parts2 = utils::string::split(smb_path2.substr(2U), "/", false); | ||||
|   if (parts1.size() < 2U || parts2.size() < 2U) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (parts2.at(1U).empty() || parts1.at(1U).empty()) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return std::equal(parts1.begin(), std::next(parts1.begin(), 2U), | ||||
|                     parts2.begin()); | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_LIBDSM) | ||||
| } // namespace repertory::utils::file | ||||
							
								
								
									
										486
									
								
								support/src/utils/file_directory.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										486
									
								
								support/src/utils/file_directory.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,486 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/file_directory.hpp" | ||||
|  | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/string.hpp" | ||||
| #include "utils/unix.hpp" | ||||
| #include "utils/windows.hpp" | ||||
|  | ||||
| namespace { | ||||
| auto traverse_directory( | ||||
|     std::string_view path, | ||||
|     std::function<bool(repertory::utils::file::directory)> directory_action, | ||||
|     std::function<bool(repertory::utils::file::file)> file_action, | ||||
|     repertory::stop_type *stop_requested) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto res{true}; | ||||
|  | ||||
|   const auto is_stop_requested = [&stop_requested]() -> bool { | ||||
|     return (stop_requested != nullptr && *stop_requested); | ||||
|   }; | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   WIN32_FIND_DATAA fd{}; | ||||
|   auto search = repertory::utils::path::combine(path, {"*.*"}); | ||||
|   auto find = ::FindFirstFileA(search.c_str(), &fd); | ||||
|   if (find == INVALID_HANDLE_VALUE) { | ||||
|     throw repertory::utils::error::create_exception( | ||||
|         function_name, | ||||
|         { | ||||
|             "failed to open directory", | ||||
|             std::to_string(repertory::utils::get_last_error_code()), | ||||
|             path, | ||||
|         }); | ||||
|   } | ||||
|  | ||||
|   do { | ||||
|     if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | ||||
|       if ((std::string_view(fd.cFileName) != ".") && | ||||
|           (std::string_view(fd.cFileName) != "..")) { | ||||
|         res = directory_action(repertory::utils::file::directory{ | ||||
|             repertory::utils::path::combine(path, {fd.cFileName}), | ||||
|             stop_requested, | ||||
|         }); | ||||
|       } | ||||
|     } else { | ||||
|       res = file_action(repertory::utils::file::file( | ||||
|           repertory::utils::path::combine(path, {fd.cFileName}))); | ||||
|     } | ||||
|   } while (res && (::FindNextFileA(find, &fd) != 0) && !is_stop_requested()); | ||||
|  | ||||
|   ::FindClose(find); | ||||
| #else  // !defined(_WIN32) | ||||
|   auto *root = opendir(std::string{path}.c_str()); | ||||
|   if (root == nullptr) { | ||||
|     throw repertory::utils::error::create_exception( | ||||
|         function_name, | ||||
|         { | ||||
|             "failed to open directory", | ||||
|             std::to_string(repertory::utils::get_last_error_code()), | ||||
|             path, | ||||
|         }); | ||||
|   } | ||||
|  | ||||
|   struct dirent *de{nullptr}; | ||||
|   while (res && (de = readdir(root)) && !is_stop_requested()) { | ||||
|     if (de->d_type == DT_DIR) { | ||||
|       if ((std::string_view(de->d_name) != ".") && | ||||
|           (std::string_view(de->d_name) != "..")) { | ||||
|         res = directory_action(repertory::utils::file::directory( | ||||
|             repertory::utils::path::combine(path, {de->d_name}))); | ||||
|       } | ||||
|     } else { | ||||
|       res = file_action(repertory::utils::file::file( | ||||
|           repertory::utils::path::combine(path, {de->d_name}))); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   closedir(root); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   return res; | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| auto directory::copy_to(std::string_view new_path, | ||||
|                         bool overwrite) const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "failed to copy directory", | ||||
|                            "not implemented", | ||||
|                            utils::string::from_bool(overwrite), | ||||
|                            new_path, | ||||
|                            path_, | ||||
|                        }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto directory::count(bool recursive) const -> std::uint64_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     std::uint64_t ret{0U}; | ||||
|  | ||||
|     traverse_directory( | ||||
|         path_, | ||||
|         [&ret, &recursive](auto dir_item) -> bool { | ||||
|           if (recursive) { | ||||
|             ret += dir_item.count(true); | ||||
|           } | ||||
|  | ||||
|           ++ret; | ||||
|           return true; | ||||
|         }, | ||||
|         [&ret](auto /* file_item */) -> bool { | ||||
|           ++ret; | ||||
|           return true; | ||||
|         }, | ||||
|         stop_requested_); | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return 0U; | ||||
| } | ||||
|  | ||||
| auto directory::create_directory(std::string_view path) const | ||||
|     -> fs_directory_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto abs_path = utils::path::combine(path_, {path}); | ||||
|     if (directory{abs_path, stop_requested_}.exists()) { | ||||
|       return std::make_unique<directory>(abs_path); | ||||
|     } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|     auto res = ::SHCreateDirectory(nullptr, | ||||
|                                    utils::string::from_utf8(abs_path).c_str()); | ||||
|     if (res != ERROR_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to create directory", | ||||
|                                                std::to_string(res), | ||||
|                                                abs_path, | ||||
|                                            }); | ||||
|     } | ||||
| #else  // !defined(_WIN32) | ||||
|     auto ret{true}; | ||||
|     auto paths = | ||||
|         utils::string::split(abs_path, utils::path::directory_seperator, false); | ||||
|  | ||||
|     std::string current_path; | ||||
|     for (std::size_t idx = 0U; | ||||
|          !is_stop_requested() && ret && (idx < paths.size()); ++idx) { | ||||
|       if (paths.at(idx).empty()) { | ||||
|         current_path = utils::path::directory_seperator; | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       current_path = utils::path::combine(current_path, {paths.at(idx)}); | ||||
|       auto status = mkdir(current_path.c_str(), S_IRWXU); | ||||
|       ret = ((status == 0) || (errno == EEXIST)); | ||||
|     } | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|     return std::make_unique<directory>(abs_path); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto directory::exists() const -> bool { | ||||
| #if defined(_WIN32) | ||||
|   return ::PathIsDirectoryA(path_.c_str()) != 0; | ||||
| #else  // !defined(_WIN32) | ||||
|   struct stat64 st {}; | ||||
|   return (stat64(path_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto directory::get_directory(std::string_view path) const -> fs_directory_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto dir_path = utils::path::combine(path_, {path}); | ||||
|     return fs_directory_t{ | ||||
|         directory{dir_path, stop_requested_}.exists() | ||||
|             ? new directory(dir_path, stop_requested_) | ||||
|             : nullptr, | ||||
|     }; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto directory::get_directories() const -> std::vector<fs_directory_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     std::vector<fs_directory_t> ret{}; | ||||
|  | ||||
|     traverse_directory( | ||||
|         path_, | ||||
|         [this, &ret](auto dir_item) -> bool { | ||||
|           ret.emplace_back(fs_directory_t{ | ||||
|               new directory(dir_item.get_path(), stop_requested_), | ||||
|           }); | ||||
|  | ||||
|           return true; | ||||
|         }, | ||||
|         [](auto /* file_item */) -> bool { return true; }, stop_requested_); | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| auto directory::create_file(std::string_view file_name, | ||||
|                             bool read_only) const -> fs_file_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto file_path = utils::path::combine(path_, {file_name}); | ||||
|     return file::open_or_create_file(file_path, read_only); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto directory::get_file(std::string_view path) const -> fs_file_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto file_path = utils::path::combine(path_, {path}); | ||||
|     return fs_file_t{ | ||||
|         file{file_path}.exists() ? new file(file_path) : nullptr, | ||||
|     }; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto directory::get_files() const -> std::vector<fs_file_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     std::vector<fs_file_t> ret{}; | ||||
|  | ||||
|     traverse_directory( | ||||
|         path_, [](auto /* dir_item */) -> bool { return true; }, | ||||
|         [&ret](auto file_item) -> bool { | ||||
|           ret.emplace_back(fs_file_t{ | ||||
|               new file(file_item.get_path()), | ||||
|           }); | ||||
|           return true; | ||||
|         }, | ||||
|         stop_requested_); | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| auto directory::get_items() const -> std::vector<fs_item_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     std::vector<fs_item_t> ret{}; | ||||
|  | ||||
|     traverse_directory( | ||||
|         path_, | ||||
|         [this, &ret](auto dir_item) -> bool { | ||||
|           ret.emplace_back(fs_item_t{ | ||||
|               new directory(dir_item.get_path(), stop_requested_), | ||||
|           }); | ||||
|           return true; | ||||
|         }, | ||||
|         [&ret](auto file_item) -> bool { | ||||
|           ret.emplace_back(fs_item_t{ | ||||
|               new file(file_item.get_path()), | ||||
|           }); | ||||
|           return true; | ||||
|         }, | ||||
|         stop_requested_); | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| auto directory::is_stop_requested() const -> bool { | ||||
|   return (stop_requested_ != nullptr) && *stop_requested_; | ||||
| } | ||||
|  | ||||
| auto directory::is_symlink() const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     return std::filesystem::is_symlink(path_); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto directory::move_to(std::string_view new_path) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to move directory", | ||||
|                                              "not implemented", | ||||
|                                              new_path, | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto directory::remove() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   return utils::retry_action([this]() -> bool { | ||||
|     try { | ||||
| #if defined(_WIN32) | ||||
|       auto ret = not exists() || (::RemoveDirectoryA(path_.c_str()) != 0); | ||||
| #else  // !defined(_WIN32) | ||||
|       auto ret = not exists() || (rmdir(path_.c_str()) == 0); | ||||
| #endif // defined(_WIN32) | ||||
|       if (not ret) { | ||||
|         utils::error::handle_error(function_name, | ||||
|                                    utils::error::create_error_message({ | ||||
|                                        "failed to remove directory", | ||||
|                                        path_, | ||||
|                                    })); | ||||
|       } | ||||
|  | ||||
|       return ret; | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::handle_exception(function_name, e); | ||||
|     } catch (...) { | ||||
|       utils::error::handle_exception(function_name); | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| auto directory::remove_recursively() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not exists()) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     if (not traverse_directory( | ||||
|             path_, | ||||
|             [](auto dir_item) -> bool { return dir_item.remove_recursively(); }, | ||||
|             [](auto file_item) -> bool { return file_item.remove(); }, | ||||
|             stop_requested_)) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     return remove(); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto directory::size(bool recursive) const -> std::uint64_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     std::uint64_t ret{0U}; | ||||
|  | ||||
|     traverse_directory( | ||||
|         path_, | ||||
|         [&ret, &recursive](auto dir_item) -> bool { | ||||
|           if (recursive) { | ||||
|             ret += dir_item.size(true); | ||||
|           } | ||||
|  | ||||
|           return true; | ||||
|         }, | ||||
|         [&ret](auto file_item) -> bool { | ||||
|           auto cur_size = file_item.size(); | ||||
|           if (cur_size.has_value()) { | ||||
|             ret += cur_size.value(); | ||||
|           } | ||||
|           return true; | ||||
|         }, | ||||
|         stop_requested_); | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return 0U; | ||||
| } | ||||
| } // namespace repertory::utils::file | ||||
							
								
								
									
										53
									
								
								support/src/utils/file_enc_file.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								support/src/utils/file_enc_file.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
| #include "utils/file_enc_file.hpp" | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| auto enc_file::attach_file(fs_file_t file) -> fs_file_t {} | ||||
|  | ||||
| enc_file::enc_file(fs_file_t file) : file_(std::move(file)) {} | ||||
|  | ||||
| void enc_file::close() {} | ||||
|  | ||||
| auto enc_file::copy_to(std::string_view new_path, | ||||
|                        bool overwrite) const -> bool {} | ||||
|  | ||||
| void enc_file::flush() const {} | ||||
|  | ||||
| auto enc_file::move_to(std::string_view path) -> bool {} | ||||
|  | ||||
| auto enc_file::read(unsigned char *data, std::size_t to_read, | ||||
|                     std::uint64_t offset, std::size_t *total_read) -> bool {} | ||||
|  | ||||
| auto enc_file::remove() -> bool {} | ||||
|  | ||||
| auto enc_file::truncate(std::size_t size) -> bool {} | ||||
|  | ||||
| auto enc_file::write(const unsigned char *data, std::size_t to_write, | ||||
|                      std::size_t offset, std::size_t *total_written) -> bool {} | ||||
|  | ||||
| auto enc_file::size() const -> std::optional<std::uint64_t> {} | ||||
| } // namespace repertory::utils::file | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) | ||||
							
								
								
									
										594
									
								
								support/src/utils/file_file.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										594
									
								
								support/src/utils/file_file.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,594 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/file_file.hpp" | ||||
|  | ||||
| #include "utils/collection.hpp" | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/path.hpp" | ||||
|  | ||||
| namespace { | ||||
| [[nodiscard]] auto get_file_size(std::string_view path, | ||||
|                                  std::uint64_t &file_size) -> bool { | ||||
|   auto abs_path = repertory::utils::path::absolute(path); | ||||
|   file_size = 0U; | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   struct _stat64 st{}; | ||||
|   auto res = _stat64(std::string{path}.c_str(), &st); | ||||
|   if (res != 0) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   file_size = static_cast<std::uint64_t>(st.st_size); | ||||
|   return true; | ||||
| #else  // !defined(_WIN32) | ||||
|   std::error_code ec{}; | ||||
|   file_size = std::filesystem::file_size(abs_path, ec); | ||||
|   return (ec.value() == 0); | ||||
| #endif // defined(_WIN32) | ||||
| } | ||||
|  | ||||
| [[nodiscard]] auto is_file(std::string_view path) -> bool { | ||||
|   auto abs_path = repertory::utils::path::absolute(path); | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   return ((::PathFileExistsA(abs_path.c_str()) != 0) && | ||||
|           (::PathIsDirectoryA(abs_path.c_str()) == 0)); | ||||
| #else  // !defined(_WIN32) | ||||
|   struct stat64 st{}; | ||||
|   return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode)); | ||||
| #endif // defined(_WIN32) | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| // auto file::attach_file(native_handle handle, | ||||
| //                        bool read_only) -> fs_file_t { | ||||
| // REPERTORY_USES_FUNCTION_NAME(); | ||||
| // | ||||
| //   try { | ||||
| //     std::string path; | ||||
| // | ||||
| // #if defined(_WIN32) | ||||
| //     path.resize(repertory::max_path_length + 1U); | ||||
| //     ::GetFinalPathNameByHandleA(handle, path.data(), | ||||
| //                                 static_cast<DWORD>(path.size()), | ||||
| //                                 FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); | ||||
| // #else // !defined(_WIN32) | ||||
| //     path.resize(repertory::max_path_length + 1U); | ||||
| // | ||||
| // #if defined(__APPLE__) | ||||
| //     fcntl(handle, F_GETPATH, source_path.data()); | ||||
| // #else  // !defined(__APPLE__) | ||||
| //     readlink(("/proc/self/fd/" + std::to_string(handle)).c_str(), | ||||
| //     path.data(), | ||||
| //              path.size()); | ||||
| // #endif // defined(__APPLE__) | ||||
| // #endif // defined(_WIN32) | ||||
| // | ||||
| //     path = path.c_str(); | ||||
| // | ||||
| // #if defined(_WIN32) | ||||
| //     auto *ptr = _fdopen( | ||||
| //         static_cast<int>(_open_osfhandle(reinterpret_cast<intptr_t>(handle), | ||||
| //                                          read_only ? _O_RDONLY : _O_RDWR)), | ||||
| //         read_only ? "rb" : "rb+"); | ||||
| // #else  // !defined(_WIN32) | ||||
| //     auto *ptr = fdopen(handle, read_only ? "rb" : "rb+"); | ||||
| // #endif // defined(_WIN32) | ||||
| // | ||||
| //     return fs_file_t(new file{ | ||||
| //         file_t{ptr}, | ||||
| //         utils::path::absolute(path), | ||||
| //         read_only, | ||||
| //     }); | ||||
| //   } catch (const std::exception &e) { | ||||
| //     utils::error::handle_exception(function_name, e); | ||||
| //   } catch (...) { | ||||
| //     utils::error::handle_exception(function_name); | ||||
| //   } | ||||
| // | ||||
| //   return nullptr; | ||||
| // } | ||||
|  | ||||
| void file::open() { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (not is_file(path_)) { | ||||
|     throw utils::error::create_exception(function_name, { | ||||
|                                                             "file not found", | ||||
|                                                             path_, | ||||
|                                                         }); | ||||
|   } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   file_ = file_t{ | ||||
|       _fsopen(path_.c_str(), read_only_ ? "rb" : "rb+", _SH_DENYNO), | ||||
|       file_deleter(), | ||||
|   }; | ||||
| #else  // !defined(_WIN32) | ||||
|   file_ = file_t{ | ||||
|       fopen(path_.c_str(), read_only_ ? "rb" : "rb+"), | ||||
|       file_deleter(), | ||||
|   }; | ||||
| #endif // defined(_WIN32) | ||||
| } | ||||
|  | ||||
| auto file::open_file(std::string_view path, bool read_only) -> fs_file_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto *ptr = new file{ | ||||
|       nullptr, | ||||
|       utils::path::absolute(path), | ||||
|       read_only, | ||||
|   }; | ||||
|   auto new_file = fs_file_t(ptr); | ||||
|  | ||||
|   try { | ||||
|     ptr->open(); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return new_file; | ||||
| } | ||||
|  | ||||
| auto file::open_or_create_file(std::string_view path, bool read_only) | ||||
|     -> fs_file_t { | ||||
|   auto abs_path = utils::path::absolute(path); | ||||
|   if (not is_file(abs_path)) { | ||||
| #if defined(_WIN32) | ||||
|     int old_mode{}; | ||||
|     _umask_s(077, &old_mode); | ||||
| #else  // !defined(_WIN32) | ||||
|     auto old_mode = umask(077); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|     auto *ptr = _fsopen(abs_path.c_str(), "ab+", _SH_DENYNO); | ||||
| #else  // !defined(_WIN32) | ||||
|     auto *ptr = fopen(abs_path.c_str(), "ab+"); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|     if (ptr != nullptr) { | ||||
|       fclose(ptr); | ||||
|     } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|     _umask_s(old_mode, nullptr); | ||||
| #else  // !defined(_WIN32) | ||||
|     umask(old_mode); | ||||
| #endif // defined(_WIN32) | ||||
|   } | ||||
|  | ||||
|   return open_file(abs_path, read_only); | ||||
| } | ||||
|  | ||||
| void file::close() { file_.reset(); } | ||||
|  | ||||
| auto file::copy_to(std::string_view new_path, bool overwrite) const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     auto to_path = utils::path::absolute(new_path); | ||||
|     if (directory(to_path).exists()) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|     return ::CopyFileA(path_.c_str(), to_path.c_str(), | ||||
|                        overwrite ? TRUE : FALSE) != 0; | ||||
| #else  // !defined(_WIN32) | ||||
|     return std::filesystem::copy_file( | ||||
|         path_, to_path, | ||||
|         overwrite ? std::filesystem::copy_options::overwrite_existing | ||||
|                   : std::filesystem::copy_options::skip_existing); | ||||
| #endif // defined(_WIN32) | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto file::exists() const -> bool { return is_file(path_); } | ||||
|  | ||||
| void file::flush() const { | ||||
|   if (file_) { | ||||
|     fflush(file_.get()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto file::get_handle() const -> native_handle { | ||||
|   if (file_) { | ||||
| #if defined(_WIN32) | ||||
|     return reinterpret_cast<native_handle>( | ||||
|         _get_osfhandle(_fileno(file_.get()))); | ||||
| #else  // !defined(_WIN32) | ||||
|     return fileno(file_.get()); | ||||
| #endif // defined(_WIN32) | ||||
|   } | ||||
|  | ||||
|   return INVALID_HANDLE_VALUE; | ||||
| } | ||||
|  | ||||
| auto file::is_symlink() const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     return std::filesystem::is_symlink(path_); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto file::move_to(std::string_view path) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto abs_path = utils::path::absolute(path); | ||||
|  | ||||
|   auto reopen{false}; | ||||
|   if (file_) { | ||||
|     reopen = true; | ||||
|     close(); | ||||
|   } | ||||
|  | ||||
|   auto success{false}; | ||||
| #if defined(_WIN32) | ||||
|   success = ::MoveFileExA(path_.c_str(), abs_path.c_str(), | ||||
|                           MOVEFILE_REPLACE_EXISTING) != 0; | ||||
| #else  // !// defined(_WIN32) | ||||
|   std::error_code ec{}; | ||||
|   std::filesystem::rename(path_, abs_path, ec); | ||||
|   success = ec.value() == 0; | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   if (success) { | ||||
|     path_ = abs_path; | ||||
|   } | ||||
|  | ||||
|   if (reopen) { | ||||
|     try { | ||||
|       open(); | ||||
|       return success; | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::handle_exception(function_name, e); | ||||
|     } catch (...) { | ||||
|       utils::error::handle_exception(function_name); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto file::read(unsigned char *data, std::size_t to_read, std::uint64_t offset, | ||||
|                 std::size_t *total_read) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (total_read != nullptr) { | ||||
|     (*total_read) = 0U; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     if (not file_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "file is not open for reading", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     if (fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET) == | ||||
|         -1) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to seek before read", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     std::size_t bytes_read{0U}; | ||||
|     while (bytes_read != to_read) { | ||||
|       auto res = | ||||
|           fread(&data[bytes_read], 1U, to_read - bytes_read, file_.get()); | ||||
|       if (not feof(file_.get()) && ferror(file_.get())) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to read file bytes", | ||||
|                                                  path_, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       if (res == 0) { | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       bytes_read += static_cast<std::size_t>(res); | ||||
|     } | ||||
|  | ||||
|     if (total_read != nullptr) { | ||||
|       (*total_read) = bytes_read; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) | ||||
| auto file::sha256() -> std::optional<std::string> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto should_close{false}; | ||||
|   auto read_only{read_only_}; | ||||
|   std::optional<std::string> ret; | ||||
|  | ||||
|   try { | ||||
|     if (file_ == nullptr) { | ||||
|       should_close = true; | ||||
|       read_only_ = true; | ||||
|       this->open(); | ||||
|     } | ||||
|  | ||||
|     crypto_hash_sha256_state state{}; | ||||
|     auto res = crypto_hash_sha256_init(&state); | ||||
|     if (res != 0) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to initialize sha256", | ||||
|                                                std::to_string(res), | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     { | ||||
|       data_buffer buffer(get_read_buffer_size()); | ||||
|       std::uint64_t read_offset{0U}; | ||||
|       std::size_t bytes_read{0U}; | ||||
|       while (i_file::read(buffer, read_offset, &bytes_read)) { | ||||
|         if (not bytes_read) { | ||||
|           break; | ||||
|         } | ||||
|  | ||||
|         read_offset += bytes_read; | ||||
|         res = crypto_hash_sha256_update( | ||||
|             &state, reinterpret_cast<const unsigned char *>(buffer.data()), | ||||
|             bytes_read); | ||||
|         if (res != 0) { | ||||
|           throw utils::error::create_exception(function_name, | ||||
|                                                { | ||||
|                                                    "failed to update sha256", | ||||
|                                                    std::to_string(res), | ||||
|                                                    path_, | ||||
|                                                }); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     std::array<unsigned char, crypto_hash_sha256_BYTES> out{}; | ||||
|     res = crypto_hash_sha256_final(&state, out.data()); | ||||
|     if (res != 0) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to finalize sha256", | ||||
|                                                std::to_string(res), | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     ret = utils::collection::to_hex_string(out); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   if (should_close) { | ||||
|     read_only_ = read_only; | ||||
|     close(); | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) | ||||
|  | ||||
| auto file::remove() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   close(); | ||||
|  | ||||
|   return utils::retry_action([this]() -> bool { | ||||
|     try { | ||||
| #if defined(_WIN32) | ||||
|       auto ret = not exists() || (::DeleteFileA(path_.c_str()) != 0); | ||||
| #else  // !defined(_WIN32) | ||||
|       std::error_code ec{}; | ||||
|       auto ret = not exists() || std::filesystem::remove(path_, ec); | ||||
| #endif // defined(_WIN32) | ||||
|       if (not ret) { | ||||
|         utils::error::handle_error(function_name, | ||||
|                                    utils::error::create_error_message({ | ||||
|                                        "failed to remove file", | ||||
|                                        path_, | ||||
|                                    })); | ||||
|       } | ||||
|  | ||||
|       return ret; | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::handle_exception(function_name, e); | ||||
|     } catch (...) { | ||||
|       utils::error::handle_exception(function_name); | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
|   }); | ||||
| } | ||||
|  | ||||
| auto file::truncate(std::size_t size) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   auto reopen{false}; | ||||
|   if (file_) { | ||||
|     reopen = true; | ||||
|     close(); | ||||
|   } | ||||
|  | ||||
|   std::error_code ec{}; | ||||
|   std::filesystem::resize_file(path_, size, ec); | ||||
|  | ||||
|   auto success{ec.value() == 0}; | ||||
|  | ||||
|   if (reopen) { | ||||
|     try { | ||||
|       open(); | ||||
|     } catch (const std::exception &e) { | ||||
|       utils::error::handle_exception(function_name, e); | ||||
|       success = false; | ||||
|     } catch (...) { | ||||
|       utils::error::handle_exception(function_name); | ||||
|       success = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return success; | ||||
| } | ||||
|  | ||||
| auto file::write(const unsigned char *data, std::size_t to_write, | ||||
|                  std::size_t offset, std::size_t *total_written) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   if (total_written != nullptr) { | ||||
|     (*total_written) = 0U; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     if (not file_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "file is not open for writing", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto res = fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET); | ||||
|     if (res == -1) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to seek before write", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     std::size_t bytes_written{0U}; | ||||
|     while (bytes_written != to_write) { | ||||
|       auto written = | ||||
|           fwrite(reinterpret_cast<const char *>(&data[bytes_written]), 1U, | ||||
|                  to_write - bytes_written, file_.get()); | ||||
|       if (not feof(file_.get()) && ferror(file_.get())) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to write file bytes", | ||||
|                                                  path_, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       if (written == 0U) { | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       bytes_written += written; | ||||
|     } | ||||
|  | ||||
|     flush(); | ||||
|  | ||||
|     if (total_written != nullptr) { | ||||
|       (*total_written) = bytes_written; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto file::size() const -> std::optional<std::uint64_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (file_) { | ||||
|       if (fseeko(file_.get(), 0, SEEK_END) == -1) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to seek", | ||||
|                                                  path_, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       auto size = ftello(file_.get()); | ||||
|       if (size == -1) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to get position", | ||||
|                                                  path_, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       return static_cast<std::uint64_t>(size); | ||||
|     } | ||||
|  | ||||
|     std::uint64_t size{}; | ||||
|     if (not get_file_size(path_, size)) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to get file size", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return size; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
| } // namespace repertory::utils::file | ||||
							
								
								
									
										789
									
								
								support/src/utils/file_smb_directory.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										789
									
								
								support/src/utils/file_smb_directory.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,789 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_LIBDSM) | ||||
|  | ||||
| #include "utils/file_smb_directory.hpp" | ||||
|  | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/unix.hpp" | ||||
| #include "utils/windows.hpp" | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| auto smb_directory::open(std::string_view host, std::string_view user, | ||||
|                          std::string_view password, std::string_view path, | ||||
|                          stop_type *stop_requested) -> smb_directory_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     smb_session_t session{ | ||||
|         smb_session_new(), | ||||
|         smb_session_deleter, | ||||
|     }; | ||||
|     netbios_ns_t ns{ | ||||
|         netbios_ns_new(), | ||||
|         netbios_ns_deleter(), | ||||
|     }; | ||||
|  | ||||
|     sockaddr_in addr{}; | ||||
|  | ||||
|     auto res = netbios_ns_resolve( | ||||
|         ns.get(), std::string{host}.c_str(), NETBIOS_FILESERVER, | ||||
|         reinterpret_cast<std::uint32_t *>(&addr.sin_addr.s_addr)); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       res = inet_pton(AF_INET, std::string{host}.c_str(), &addr.sin_addr); | ||||
|       if (res != 1) { | ||||
|         throw utils::error::create_exception( | ||||
|             function_name, { | ||||
|                                "failed to resolve host", | ||||
|                                std::to_string(utils::get_last_error_code()), | ||||
|                                host, | ||||
|                            }); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     res = smb_session_connect(session.get(), std::string{host}.c_str(), | ||||
|                               static_cast<std::uint32_t>(addr.sin_addr.s_addr), | ||||
|                               SMB_TRANSPORT_TCP); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to connect to host", | ||||
|                                                std::to_string(res), | ||||
|                                                host, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_session_set_creds(session.get(), std::string{host}.c_str(), | ||||
|                           std::string{user}.c_str(), | ||||
|                           std::string{password}.c_str()); | ||||
|     res = smb_session_login(session.get()); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to logon to host", | ||||
|                                                std::to_string(res), | ||||
|                                                host, | ||||
|                                                user, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto share_name = utils::string::split(path, '/', false).at(0U); | ||||
|  | ||||
|     smb_tid tid{}; | ||||
|     res = smb_tree_connect(session.get(), share_name.c_str(), &tid); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to connect to share", | ||||
|                                                std::to_string(res), | ||||
|                                                share_name, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return smb_directory_t{ | ||||
|         new smb_directory{ | ||||
|             "//" + std::string{host} + "/" + std::string{path}, | ||||
|             session, | ||||
|             share_name, | ||||
|             tid, | ||||
|             stop_requested, | ||||
|         }, | ||||
|     }; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto smb_directory::open(std::wstring_view host, std::wstring_view user, | ||||
|                          std::wstring_view password, std::wstring_view path, | ||||
|                          stop_type *stop_requested) -> smb_directory_t { | ||||
|   return open(utils::string::to_utf8(host), utils::string::to_utf8(user), | ||||
|               utils::string::to_utf8(password), utils::string::to_utf8(path), | ||||
|               stop_requested); | ||||
| } | ||||
|  | ||||
| auto smb_directory::copy_to(std::string_view new_path, | ||||
|                             bool overwrite) const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     // auto to_path = utils::path::absolute(new_path); | ||||
|  | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to copy directory", | ||||
|                                              "not implemented", | ||||
|                                              new_path, | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_directory::count(bool recursive) const -> std::uint64_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_stat_list_t list{ | ||||
|         smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), | ||||
|         smb_stat_list_deleter(), | ||||
|     }; | ||||
|     auto count = smb_stat_list_count(list.get()); | ||||
|  | ||||
|     if (not recursive) { | ||||
|       return count; | ||||
|     } | ||||
|  | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "failed to get directory count recursively", | ||||
|                            "not implemented", | ||||
|                            utils::string::from_bool(recursive), | ||||
|                            path_, | ||||
|                        }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return 0U; | ||||
| } | ||||
|  | ||||
| auto smb_directory::create_directory(std::string_view path) const | ||||
|     -> fs_directory_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto dir = get_directory(path); | ||||
|     if (dir) { | ||||
|       return dir; | ||||
|     } | ||||
|  | ||||
|     auto res = smb_directory_create( | ||||
|         session_.get(), tid_, | ||||
|         smb_create_and_validate_relative_path(path_, path).c_str()); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to create directory", | ||||
|                                                std::to_string(res), | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return get_directory(path); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto smb_directory::create_file(std::string_view file_name, | ||||
|                                 bool read_only) const -> fs_file_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto fs_file = get_file(file_name); | ||||
|     if (fs_file) { | ||||
|       if (not dynamic_cast<smb_file *>(fs_file.get())->open(read_only)) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to open existing file", | ||||
|                                                  file_name, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       return fs_file; | ||||
|     } | ||||
|  | ||||
|     auto rel_path = smb_create_and_validate_relative_path(path_, file_name); | ||||
|  | ||||
|     smb_fd fd{}; | ||||
|     auto res = | ||||
|         smb_fopen(session_.get(), tid_, rel_path.c_str(), SMB_MOD_RW, &fd); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       return nullptr; | ||||
|     } | ||||
|     smb_fclose(session_.get(), fd); | ||||
|  | ||||
|     res = smb_fopen(session_.get(), tid_, rel_path.c_str(), | ||||
|                     read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       return nullptr; | ||||
|     } | ||||
|  | ||||
|     return std::make_unique<smb_file>( | ||||
|         fd, smb_create_smb_path(path_, std::string{rel_path}), session_, | ||||
|         share_name_, tid_); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
| auto smb_directory::exists() const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_stat_t st{ | ||||
|         smb_fstat(session_.get(), tid_, | ||||
|                   smb_create_relative_path(path_).c_str()), | ||||
|         smb_stat_deleter(), | ||||
|     }; | ||||
|     if (not st) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     return smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_directory::get_directory(std::string_view path) const | ||||
|     -> fs_directory_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto rel_path = smb_create_and_validate_relative_path(path_, path); | ||||
|     smb_stat_t st{ | ||||
|         smb_fstat(session_.get(), tid_, rel_path.c_str()), | ||||
|         smb_stat_deleter(), | ||||
|     }; | ||||
|     if (not st) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to stat directory", | ||||
|                                                rel_path, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U}; | ||||
|     if (not is_dir) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "path is not a directory", | ||||
|                                                rel_path, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return smb_directory_t{ | ||||
|         new smb_directory{ | ||||
|             smb_create_smb_path(path_, rel_path), | ||||
|             session_, | ||||
|             share_name_, | ||||
|             tid_, | ||||
|             stop_requested_, | ||||
|         }, | ||||
|     }; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto smb_directory::get_directories() const -> std::vector<fs_directory_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|     smb_stat_list_t list{ | ||||
|         smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), | ||||
|  | ||||
|         smb_stat_list_deleter(), | ||||
|     }; | ||||
|     if (not list) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to get directory list", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     std::vector<fs_directory_t> ret{}; | ||||
|  | ||||
|     auto count = smb_stat_list_count(list.get()); | ||||
|     for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) { | ||||
|       auto *item_st = smb_stat_list_at(list.get(), idx); | ||||
|  | ||||
|       bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U}; | ||||
|       if (not is_dir) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       std::string name{smb_stat_name(item_st)}; | ||||
|       if (name == "." || name == "..") { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         ret.emplace_back(smb_directory_t{ | ||||
|             new smb_directory{ | ||||
|                 smb_create_smb_path(path_, name), | ||||
|                 session_, | ||||
|                 share_name_, | ||||
|                 tid_, | ||||
|                 stop_requested_, | ||||
|             }, | ||||
|         }); | ||||
|       } catch (const std::exception &e) { | ||||
|         utils::error::handle_exception(function_name, e); | ||||
|       } catch (...) { | ||||
|         utils::error::handle_exception(function_name); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| auto smb_directory::get_file(std::string_view path) const -> fs_file_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto rel_path = smb_create_and_validate_relative_path(path_, path); | ||||
|     smb_stat_t st{ | ||||
|         smb_fstat(session_.get(), tid_, rel_path.c_str()), | ||||
|         smb_stat_deleter(), | ||||
|     }; | ||||
|     if (not st) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to stat file", | ||||
|                                                rel_path, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U}; | ||||
|     if (is_dir) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "path is not a file", | ||||
|                                                rel_path, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return std::make_unique<smb_file>( | ||||
|         std::nullopt, smb_create_smb_path(path_, std::string{rel_path}), | ||||
|         session_, share_name_, tid_); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return nullptr; | ||||
| } | ||||
|  | ||||
| auto smb_directory::get_files() const -> std::vector<fs_file_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_stat_list_t list{ | ||||
|         smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), | ||||
|         smb_stat_list_deleter(), | ||||
|     }; | ||||
|     if (not list) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to get file list", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     std::vector<fs_file_t> ret{}; | ||||
|  | ||||
|     auto count = smb_stat_list_count(list.get()); | ||||
|     for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) { | ||||
|       auto *item_st = smb_stat_list_at(list.get(), idx); | ||||
|  | ||||
|       bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U}; | ||||
|       if (is_dir) { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         std::string name{smb_stat_name(item_st)}; | ||||
|         ret.emplace_back(std::make_unique<smb_file>( | ||||
|             std::nullopt, smb_create_smb_path(path_, name), session_, | ||||
|             share_name_, tid_)); | ||||
|       } catch (const std::exception &e) { | ||||
|         utils::error::handle_exception(function_name, e); | ||||
|       } catch (...) { | ||||
|         utils::error::handle_exception(function_name); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| auto smb_directory::get_items() const -> std::vector<fs_item_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_stat_list_t list{ | ||||
|         smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()), | ||||
|         smb_stat_list_deleter(), | ||||
|     }; | ||||
|     if (not list) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to get item list", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|     std::vector<fs_item_t> ret{}; | ||||
|  | ||||
|     auto count = smb_stat_list_count(list.get()); | ||||
|     for (std::size_t idx = 0U; !is_stop_requested() && idx < count; ++idx) { | ||||
|       auto *item_st = smb_stat_list_at(list.get(), idx); | ||||
|  | ||||
|       bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U}; | ||||
|       std::string name{smb_stat_name(item_st)}; | ||||
|  | ||||
|       if (is_dir) { | ||||
|         if (name == "." || name == "..") { | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|           ret.emplace_back(smb_directory_t{ | ||||
|               new smb_directory{ | ||||
|                   path_ + '/' + name, | ||||
|                   session_, | ||||
|                   share_name_, | ||||
|                   tid_, | ||||
|                   stop_requested_, | ||||
|               }, | ||||
|           }); | ||||
|         } catch (const std::exception &e) { | ||||
|           utils::error::handle_exception(function_name, e); | ||||
|         } catch (...) { | ||||
|           utils::error::handle_exception(function_name); | ||||
|         } | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         ret.emplace_back(std::make_unique<smb_file>( | ||||
|             std::nullopt, smb_create_smb_path(path_, name), session_, | ||||
|             share_name_, tid_)); | ||||
|       } catch (const std::exception &e) { | ||||
|         utils::error::handle_exception(function_name, e); | ||||
|       } catch (...) { | ||||
|         utils::error::handle_exception(function_name); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return ret; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| auto smb_directory::get_time(time_type type) const | ||||
|     -> std::optional<std::uint64_t> { | ||||
|   return smb_file::get_time(session_.get(), tid_, path_, type); | ||||
| } | ||||
|  | ||||
| auto smb_directory::is_stop_requested() const -> bool { | ||||
|   return (stop_requested_ != nullptr) && *stop_requested_; | ||||
| } | ||||
|  | ||||
| auto smb_directory::is_symlink() const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_directory::move_to(std::string_view new_path) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to move directory", | ||||
|                                              "not implemented", | ||||
|                                              new_path, | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_directory::remove() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return utils::retry_action([this]() -> bool { | ||||
|       if (not exists()) { | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         auto res = smb_directory_rm(session_.get(), tid_, | ||||
|                                     smb_create_relative_path(path_).c_str()); | ||||
|         if (res != DSM_SUCCESS) { | ||||
|           throw utils::error::create_exception(function_name, | ||||
|                                                { | ||||
|                                                    "failed to remove directory", | ||||
|                                                    std::to_string(res), | ||||
|                                                    path_, | ||||
|                                                }); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|       } catch (const std::exception &e) { | ||||
|         utils::error::handle_exception(function_name, e); | ||||
|       } catch (...) { | ||||
|         utils::error::handle_exception(function_name); | ||||
|       } | ||||
|  | ||||
|       return false; | ||||
|     }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_directory::remove_recursively() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     if (not exists()) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "failed to remove directory recursively", | ||||
|                            "not implemented", | ||||
|                            path_, | ||||
|                        }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_directory::size(bool /* recursive */) const -> std::uint64_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to get directory size", | ||||
|                                              "not implemented", | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
| } // namespace repertory::utils::file | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_LIBDSM) | ||||
							
								
								
									
										549
									
								
								support/src/utils/file_smb_file.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										549
									
								
								support/src/utils/file_smb_file.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,549 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_LIBDSM) | ||||
|  | ||||
| #include "utils/file_smb_file.hpp" | ||||
|  | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| void smb_file::close() { | ||||
|   if (fd_.has_value()) { | ||||
|     smb_fclose(session_.get(), *fd_); | ||||
|     fd_.reset(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto smb_file::copy_to(std::string_view new_path, | ||||
|                        bool overwrite) const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     // auto to_path = utils::path::absolute(new_path); | ||||
|  | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to copy file", | ||||
|                                              "not implemented", | ||||
|                                              std::to_string(overwrite), | ||||
|                                              new_path, | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::exists() const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_stat_t st{ | ||||
|         smb_fstat(session_.get(), tid_, | ||||
|                   smb_create_relative_path(path_).c_str()), | ||||
|         smb_stat_deleter(), | ||||
|     }; | ||||
|     if (not st) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     return smb_stat_get(st.get(), SMB_STAT_ISDIR) == 0U; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| void smb_file::flush() const { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to flush file", | ||||
|                                              "not implemented", | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
| } | ||||
|  | ||||
| auto smb_file::get_time(smb_session *session, smb_tid tid, std::string path, | ||||
|                         time_type type) -> std::optional<std::uint64_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (session == nullptr) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto rel_path = smb_create_relative_path(path); | ||||
|     smb_stat_t st{ | ||||
|         smb_fstat(session, tid, rel_path.c_str()), | ||||
|         smb_stat_deleter(), | ||||
|     }; | ||||
|     if (not st) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to stat file", | ||||
|                                                "not implemented", | ||||
|                                                rel_path, | ||||
|                                                path, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     switch (type) { | ||||
|     case time_type::accessed: | ||||
|       return smb_stat_get(st.get(), SMB_STAT_ATIME); | ||||
|  | ||||
|     case time_type::created: | ||||
|       return smb_stat_get(st.get(), SMB_STAT_CTIME); | ||||
|  | ||||
|     case time_type::modified: | ||||
|       return smb_stat_get(st.get(), SMB_STAT_MTIME); | ||||
|  | ||||
|     case time_type::written: | ||||
|       return smb_stat_get(st.get(), SMB_STAT_WTIME); | ||||
|     } | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
|  | ||||
| auto smb_file::is_symlink() const -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::move_to(std::string_view new_path) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (utils::string::begins_with(new_path, "//")) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to move file", | ||||
|                                                "new path must be in same share", | ||||
|                                                new_path, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto from_path = smb_create_relative_path(path_); | ||||
|     auto to_path = smb_create_and_validate_relative_path( | ||||
|         utils::string::begins_with(new_path, "/") ? smb_get_root_path(path_) | ||||
|                                                   : smb_get_parent_path(path_), | ||||
|         new_path); | ||||
|  | ||||
|     auto was_open{false}; | ||||
|     if (fd_.has_value()) { | ||||
|       close(); | ||||
|       was_open = true; | ||||
|     } | ||||
|  | ||||
|     auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to connect to share", | ||||
|                                                std::to_string(res), | ||||
|                                                share_name_, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     res = smb_file_mv(session_.get(), tid_, from_path.c_str(), to_path.c_str()); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to move file", | ||||
|                                                std::to_string(res), | ||||
|                                                from_path, | ||||
|                                                to_path, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     path_ = smb_create_smb_path(path_, to_path); | ||||
|     if (was_open) { | ||||
|       return open(read_only_); | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::open(bool read_only) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (fd_.has_value()) { | ||||
|       if (read_only == read_only_) { | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       close(); | ||||
|     } | ||||
|  | ||||
|     auto rel_path = smb_create_relative_path(path_); | ||||
|  | ||||
|     auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to connect to share", | ||||
|                                                std::to_string(res), | ||||
|                                                share_name_, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     smb_fd fd{}; | ||||
|     res = smb_fopen(session_.get(), tid_, rel_path.c_str(), | ||||
|                     read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd); | ||||
|     if (res != DSM_SUCCESS) { | ||||
|       throw utils::error::create_exception( | ||||
|           function_name, { | ||||
|                              "failed to open file", | ||||
|                              std::to_string(res), | ||||
|                              utils::string::from_bool(read_only), | ||||
|                              rel_path, | ||||
|                              path_, | ||||
|                          }); | ||||
|     } | ||||
|  | ||||
|     fd_ = fd; | ||||
|     read_only_ = read_only; | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::read(unsigned char *data, std::size_t to_read, | ||||
|                     std::uint64_t offset, std::size_t *total_read) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (total_read != nullptr) { | ||||
|       (*total_read) = 0U; | ||||
|     } | ||||
|  | ||||
|     if (not fd_.has_value()) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to read file", | ||||
|                                                "file not open", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset), | ||||
|                          SMB_SEEK_SET); | ||||
|     if (res == -1) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to seek file", | ||||
|                                                std::to_string(res), | ||||
|                                                std::to_string(offset), | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     std::size_t bytes_read{0U}; | ||||
|     while (bytes_read != to_read) { | ||||
|       res = smb_fread(session_.get(), *fd_, &data[bytes_read], | ||||
|                       to_read - bytes_read); | ||||
|       if (res == -1) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to read file", | ||||
|                                                  std::to_string(res), | ||||
|                                                  std::to_string(offset), | ||||
|                                                  std::to_string(to_read), | ||||
|                                                  path_, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       if (res == 0) { | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       bytes_read += static_cast<std::size_t>(res); | ||||
|     } | ||||
|  | ||||
|     if (total_read != nullptr) { | ||||
|       (*total_read) = bytes_read; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::remove() -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     close(); | ||||
|  | ||||
|     return utils::retry_action([this]() -> bool { | ||||
|       if (not exists()) { | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       try { | ||||
|         auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_); | ||||
|         if (res != DSM_SUCCESS) { | ||||
|           throw utils::error::create_exception(function_name, | ||||
|                                                { | ||||
|                                                    "failed to connect to share", | ||||
|                                                    std::to_string(res), | ||||
|                                                    share_name_, | ||||
|                                                    path_, | ||||
|                                                }); | ||||
|         } | ||||
|  | ||||
|         auto rel_path = smb_create_relative_path(path_); | ||||
|         res = smb_file_rm(session_.get(), tid_, rel_path.c_str()); | ||||
|         if (res != DSM_SUCCESS) { | ||||
|           throw utils::error::create_exception( | ||||
|               function_name, | ||||
|               { | ||||
|                   "failed to remove file", | ||||
|                   std::to_string(res), | ||||
|                   std::to_string(smb_session_get_nt_status(session_.get())), | ||||
|                   rel_path, | ||||
|                   path_, | ||||
|               }); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|       } catch (const std::exception &e) { | ||||
|         utils::error::handle_exception(function_name, e); | ||||
|       } catch (...) { | ||||
|         utils::error::handle_exception(function_name); | ||||
|       } | ||||
|  | ||||
|       return false; | ||||
|     }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::size() const -> std::optional<std::uint64_t> { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (not session_) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "session not found", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto rel_path = smb_create_relative_path(path_); | ||||
|     smb_stat_t st{ | ||||
|         smb_fstat(session_.get(), tid_, rel_path.c_str()), | ||||
|         smb_stat_deleter(), | ||||
|     }; | ||||
|     if (not st) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to stat directory", | ||||
|                                                rel_path, | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     return smb_stat_get(st.get(), SMB_STAT_SIZE); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return std::nullopt; | ||||
| } | ||||
|  | ||||
| auto smb_file::truncate(std::size_t size) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to truncate file", | ||||
|                                              "not implemented", | ||||
|                                              std::to_string(size), | ||||
|                                              path_, | ||||
|                                          }); | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| auto smb_file::write(const unsigned char *data, std::size_t to_write, | ||||
|                      std::size_t offset, std::size_t *total_written) -> bool { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   try { | ||||
|     if (total_written != nullptr) { | ||||
|       (*total_written) = 0U; | ||||
|     } | ||||
|  | ||||
|     if (not fd_.has_value()) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to write file", | ||||
|                                                "file not open", | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset), | ||||
|                          SMB_SEEK_SET); | ||||
|     if (res == -1) { | ||||
|       throw utils::error::create_exception(function_name, | ||||
|                                            { | ||||
|                                                "failed to seek file", | ||||
|                                                std::to_string(res), | ||||
|                                                std::to_string(offset), | ||||
|                                                path_, | ||||
|                                            }); | ||||
|     } | ||||
|  | ||||
|     std::size_t bytes_written{0U}; | ||||
|     while (bytes_written != to_write) { | ||||
|       res = smb_fwrite(session_.get(), *fd_, | ||||
|                        const_cast<unsigned char *>(&data[bytes_written]), | ||||
|                        to_write - bytes_written); | ||||
|       if (res == -1) { | ||||
|         throw utils::error::create_exception(function_name, | ||||
|                                              { | ||||
|                                                  "failed to write file", | ||||
|                                                  std::to_string(res), | ||||
|                                                  std::to_string(offset), | ||||
|                                                  std::to_string(to_write), | ||||
|                                                  path_, | ||||
|                                              }); | ||||
|       } | ||||
|  | ||||
|       if (res == 0) { | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       bytes_written += static_cast<std::size_t>(res); | ||||
|     } | ||||
|  | ||||
|     if (total_written != nullptr) { | ||||
|       (*total_written) = bytes_written; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } catch (const std::exception &e) { | ||||
|     utils::error::handle_exception(function_name, e); | ||||
|   } catch (...) { | ||||
|     utils::error::handle_exception(function_name); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
| } // namespace repertory::utils::file | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_LIBDSM) | ||||
							
								
								
									
										65
									
								
								support/src/utils/file_thread_file.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								support/src/utils/file_thread_file.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/file_thread_file.hpp" | ||||
|  | ||||
| namespace repertory::utils::file { | ||||
| // auto thread_file::attach_file(native_handle handle, | ||||
| //                               bool read_only) -> fs_file_t {} | ||||
|  | ||||
| thread_file::thread_file(std::string_view path) | ||||
|     : file_(new repertory::utils::file::file(path)) {} | ||||
|  | ||||
| thread_file::thread_file(std::wstring_view path) | ||||
|     : file_(new repertory::utils::file::file(utils::string::to_utf8(path))) {} | ||||
|  | ||||
| auto thread_file::attach_file(fs_file_t file) -> fs_file_t {} | ||||
|  | ||||
| auto thread_file::open_file(std::string_view path, | ||||
|                             bool read_only) -> fs_file_t {} | ||||
|  | ||||
| auto thread_file::open_or_create_file(std::string_view path, | ||||
|                                       bool read_only) -> fs_file_t {} | ||||
|  | ||||
| thread_file::thread_file(fs_file_t file) : file_(std::move(file)) {} | ||||
|  | ||||
| void thread_file::close() {} | ||||
|  | ||||
| auto thread_file::copy_to(std::string_view new_path, | ||||
|                           bool overwrite) const -> bool {} | ||||
|  | ||||
| void thread_file::flush() const {} | ||||
|  | ||||
| auto thread_file::move_to(std::string_view path) -> bool {} | ||||
|  | ||||
| auto thread_file::read(unsigned char *data, std::size_t to_read, | ||||
|                        std::uint64_t offset, std::size_t *total_read) -> bool {} | ||||
|  | ||||
| auto thread_file::remove() -> bool {} | ||||
|  | ||||
| auto thread_file::truncate(std::size_t size) -> bool {} | ||||
|  | ||||
| auto thread_file::write(const unsigned char *data, std::size_t to_write, | ||||
|                         std::size_t offset, | ||||
|                         std::size_t *total_written) -> bool {} | ||||
|  | ||||
| auto thread_file::size() const -> std::optional<std::uint64_t> {} | ||||
| } // namespace repertory::utils::file | ||||
							
								
								
									
										189
									
								
								support/src/utils/hash.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								support/src/utils/hash.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(PROJECT_ENABLE_LIBSODIUM) | ||||
|  | ||||
| #include "utils/hash.hpp" | ||||
|  | ||||
| #include "utils/error.hpp" | ||||
|  | ||||
| namespace repertory::utils::encryption { | ||||
| auto create_hash_blake2b_256(std::string_view data) -> hash_256_t { | ||||
|   return create_hash_blake2b_t<hash_256_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), data.size()); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_256(std::wstring_view data) -> hash_256_t { | ||||
|   return create_hash_blake2b_t<hash_256_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(wchar_t)); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_256(const data_buffer &data) -> hash_256_t { | ||||
|   return create_hash_blake2b_t<hash_256_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(data_buffer::value_type)); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_384(std::string_view data) -> hash_384_t { | ||||
|   return create_hash_blake2b_t<hash_384_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), data.size()); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_384(std::wstring_view data) -> hash_384_t { | ||||
|   return create_hash_blake2b_t<hash_384_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(wchar_t)); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_384(const data_buffer &data) -> hash_384_t { | ||||
|   return create_hash_blake2b_t<hash_384_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(data_buffer::value_type)); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_512(std::string_view data) -> hash_512_t { | ||||
|   return create_hash_blake2b_t<hash_512_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), data.size()); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_512(std::wstring_view data) -> hash_512_t { | ||||
|   return create_hash_blake2b_t<hash_512_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(wchar_t)); | ||||
| } | ||||
|  | ||||
| auto create_hash_blake2b_512(const data_buffer &data) -> hash_512_t { | ||||
|   return create_hash_blake2b_t<hash_512_t>( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(data_buffer::value_type)); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha256(std::string_view data) -> hash_256_t { | ||||
|   return create_hash_sha256( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), data.size()); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha256(std::wstring_view data) -> hash_256_t { | ||||
|   return create_hash_sha256( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(wchar_t)); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha256(const data_buffer &data) -> hash_256_t { | ||||
|   return create_hash_sha256( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(data_buffer::value_type)); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha512(std::string_view data) -> hash_512_t { | ||||
|   return create_hash_sha512( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), data.size()); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha512(std::wstring_view data) -> hash_512_t { | ||||
|   return create_hash_sha512( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(wchar_t)); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha512(const data_buffer &data) -> hash_512_t { | ||||
|   return create_hash_sha512( | ||||
|       reinterpret_cast<const unsigned char *>(data.data()), | ||||
|       data.size() * sizeof(data_buffer::value_type)); | ||||
| } | ||||
|  | ||||
| auto create_hash_sha512(const unsigned char *data, | ||||
|                         std::size_t data_size) -> hash_512_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   hash_512_t hash{}; | ||||
|  | ||||
|   crypto_hash_sha512_state state{}; | ||||
|   auto res = crypto_hash_sha512_init(&state); | ||||
|   if (res != 0) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to initialize sha-512", | ||||
|                                              std::to_string(res), | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   res = crypto_hash_sha512_update(&state, data, data_size); | ||||
|   if (res != 0) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to update sha-512", | ||||
|                                              std::to_string(res), | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   res = crypto_hash_sha512_final(&state, hash.data()); | ||||
|   if (res != 0) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to finalize sha-512", | ||||
|                                              std::to_string(res), | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   return hash; | ||||
| } | ||||
|  | ||||
| auto create_hash_sha256(const unsigned char *data, | ||||
|                         std::size_t data_size) -> hash_256_t { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   hash_256_t hash{}; | ||||
|  | ||||
|   crypto_hash_sha256_state state{}; | ||||
|   auto res = crypto_hash_sha256_init(&state); | ||||
|   if (res != 0) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to initialize sha-256", | ||||
|                                              std::to_string(res), | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   res = crypto_hash_sha256_update(&state, data, data_size); | ||||
|   if (res != 0) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to update sha-256", | ||||
|                                              std::to_string(res), | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   res = crypto_hash_sha256_final(&state, hash.data()); | ||||
|   if (res != 0) { | ||||
|     throw utils::error::create_exception(function_name, | ||||
|                                          { | ||||
|                                              "failed to finalize sha-256", | ||||
|                                              std::to_string(res), | ||||
|                                          }); | ||||
|   } | ||||
|  | ||||
|   return hash; | ||||
| } | ||||
| } // namespace repertory::utils::encryption | ||||
|  | ||||
| #endif // defined(PROJECT_ENABLE_LIBSODIUM) | ||||
							
								
								
									
										315
									
								
								support/src/utils/path.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								support/src/utils/path.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,315 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/path.hpp" | ||||
|  | ||||
| #include "utils/common.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/file.hpp" | ||||
| #include "utils/string.hpp" | ||||
| #include "utils/unix.hpp" | ||||
|  | ||||
| namespace { | ||||
| [[nodiscard]] auto resolve(std::string path) -> std::string { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   if (repertory::utils::string::contains(path, "~\\")) { | ||||
|     repertory::utils::string::replace(path, "~\\", "%USERPROFILE%\\"); | ||||
|   } | ||||
|  | ||||
|   if (repertory::utils::string::contains(path, "~/")) { | ||||
|     repertory::utils::string::replace(path, "~/", "%USERPROFILE%\\"); | ||||
|   } | ||||
|  | ||||
|   if (repertory::utils::string::contains(path, "%")) { | ||||
|     auto size = ::ExpandEnvironmentStringsA(path.c_str(), nullptr, 0); | ||||
|  | ||||
|     std::string dest; | ||||
|     dest.resize(size); | ||||
|     ::ExpandEnvironmentStringsA(path.c_str(), dest.data(), | ||||
|                                 static_cast<DWORD>(dest.size())); | ||||
|     path = dest.c_str(); | ||||
|   } | ||||
| #else  // !defined (_WIN32) | ||||
|   if (repertory::utils::string::contains(path, "~\\")) { | ||||
|     repertory::utils::string::replace(path, "~\\", "~/"); | ||||
|   } | ||||
|  | ||||
|   if (repertory::utils::string::contains(path, "~/")) { | ||||
|     std::string home{}; | ||||
|     auto res = | ||||
|         repertory::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) { | ||||
|           home = (pw->pw_dir ? pw->pw_dir : ""); | ||||
|           if (home.empty() || | ||||
|               ((home == repertory::utils::path::slash) && (getuid() != 0))) { | ||||
|             home = repertory::utils::path::combine("/home", {pw->pw_name}); | ||||
|           } | ||||
|         }); | ||||
|     if (not res) { | ||||
|       throw repertory::utils::error::create_exception(function_name, | ||||
|                                                       { | ||||
|                                                           "failed to getpwuid", | ||||
|                                                           res.reason, | ||||
|                                                       }); | ||||
|     } | ||||
|  | ||||
|     path = repertory::utils::string::replace(path, "~/", home + "/"); | ||||
|   } | ||||
| #endif // defined (_WIN32) | ||||
|  | ||||
|   return repertory::utils::path::finalize(path); | ||||
| } | ||||
| } // namespace | ||||
|  | ||||
| namespace repertory::utils::path { | ||||
| auto absolute(std::string_view path) -> std::string { | ||||
|   std::string abs_path{path}; | ||||
|   if (abs_path.empty()) { | ||||
|     return abs_path; | ||||
|   } | ||||
|  | ||||
|   abs_path = resolve(abs_path); | ||||
| #if defined(_WIN32) | ||||
|   if (not utils::string::contains(abs_path, dot)) { | ||||
|     return abs_path; | ||||
|   } | ||||
|  | ||||
|   std::string temp; | ||||
|   temp.resize(repertory::max_path_length + 1U); | ||||
|   ::GetFullPathNameA(abs_path.c_str(), static_cast<DWORD>(temp.size()), | ||||
|                      temp.data(), nullptr); | ||||
| #else  // !defined(_WIN32) | ||||
|   if (not utils::string::contains(abs_path, dot) || | ||||
|       utils::string::begins_with(abs_path, slash)) { | ||||
|     return abs_path; | ||||
|   } | ||||
|  | ||||
|   auto found{false}; | ||||
|   std::string tmp{abs_path}; | ||||
|   do { | ||||
|     auto *res = realpath(tmp.c_str(), nullptr); | ||||
|     if (res != nullptr) { | ||||
|       abs_path = | ||||
|           res + std::string{directory_seperator} + abs_path.substr(tmp.size()); | ||||
|       free(res); | ||||
|       found = true; | ||||
|     } else if (tmp == dot) { | ||||
|       found = true; | ||||
|     } else { | ||||
|       tmp = dirname(tmp.data()); | ||||
|     } | ||||
|   } while (not found); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   return finalize(abs_path); | ||||
| } | ||||
|  | ||||
| auto absolute(std::wstring_view path) -> std::wstring { | ||||
|   return utils::string::from_utf8(absolute(utils::string::to_utf8(path))); | ||||
| } | ||||
|  | ||||
| auto exists(std::string_view path) -> bool { | ||||
|   return utils::file::file{path}.exists() || | ||||
|          utils::file::directory{path}.exists(); | ||||
| } | ||||
|  | ||||
| auto exists(std::wstring_view path) -> bool { | ||||
|   return exists(utils::string::to_utf8(path)); | ||||
| } | ||||
|  | ||||
| auto find_program_in_path(const std::string &name_without_extension) | ||||
|     -> std::string { | ||||
|   static std::mutex mtx{}; | ||||
|   static std::unordered_map<std::string, std::string> found_items{}; | ||||
|  | ||||
|   mutex_lock lock(mtx); | ||||
|   if (found_items.contains(name_without_extension)) { | ||||
|     return found_items.at(name_without_extension); | ||||
|   } | ||||
|  | ||||
|   auto path = utils::get_environment_variable("PATH"); | ||||
|   if (path.empty()) { | ||||
|     return path; | ||||
|   } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   static constexpr const std::array<std::string_view, 4U> extension_list{ | ||||
|       ".bat", | ||||
|       ".cmd", | ||||
|       ".exe", | ||||
|       ".ps1", | ||||
|   }; | ||||
|   static constexpr const auto split_char = ';'; | ||||
| #else  // !defined(_WIN32) | ||||
|   static constexpr const std::array<std::string_view, 2U> extension_list{ | ||||
|       "", | ||||
|       ".sh", | ||||
|   }; | ||||
|   static constexpr const auto split_char = ':'; | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   auto search_path_list = utils::string::split(path, split_char, false); | ||||
|   for (auto &&search_path : search_path_list) { | ||||
|     for (auto &&extension : extension_list) { | ||||
|       auto exec_path = combine( | ||||
|           search_path, {name_without_extension + std::string{extension}}); | ||||
|       if (utils::file::file(exec_path).exists()) { | ||||
|         found_items[name_without_extension] = exec_path; | ||||
|         return exec_path; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ""; | ||||
| } | ||||
|  | ||||
| [[nodiscard]] auto | ||||
| find_program_in_path(std::wstring_view name_without_extension) -> std::wstring { | ||||
|   return utils::string::from_utf8( | ||||
|       find_program_in_path(utils::string::to_utf8(name_without_extension))); | ||||
| } | ||||
|  | ||||
| auto get_parent_path(std::string_view path) -> std::string { | ||||
|   auto abs_path = absolute(path); | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   ::PathRemoveFileSpecA(abs_path.data()); | ||||
|   abs_path = abs_path.c_str(); | ||||
| #else  // !defined(_WIN32) | ||||
|   abs_path = std::filesystem::path{abs_path}.parent_path().string(); | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
|   return finalize(abs_path); | ||||
| } | ||||
|  | ||||
| auto get_parent_path(std::wstring_view path) -> std::wstring { | ||||
|   return utils::string::from_utf8( | ||||
|       get_parent_path(utils::string::to_utf8(path))); | ||||
| } | ||||
|  | ||||
| auto get_relative_path(std::string_view path, std::string_view root_path) | ||||
|     -> std::string { | ||||
|   auto abs_path = absolute(path); | ||||
|   auto abs_root_path = | ||||
|       absolute(root_path) + std::string{get_directory_seperator<char>()}; | ||||
| #if defined(_WIN32) | ||||
|   if (utils::string::to_lower(abs_path).starts_with( | ||||
|           utils::string::to_lower(abs_root_path))) { | ||||
| #else  // !defined(_WIN32) | ||||
|   if (abs_path.starts_with(abs_root_path)) { | ||||
| #endif // defined(_WIN32) | ||||
|     return abs_path.substr(abs_root_path.size()); | ||||
|   } | ||||
|  | ||||
|   return abs_path; | ||||
| } | ||||
|  | ||||
| auto get_relative_path(std::wstring_view path, std::wstring_view root_path) | ||||
|     -> std::wstring { | ||||
|   return utils::string::from_utf8(get_relative_path( | ||||
|       utils::string::to_utf8(path), utils::string::to_utf8(root_path))); | ||||
| } | ||||
|  | ||||
| auto contains_trash_directory(std::string_view path) -> bool { | ||||
|   auto parts = utils::string::split(utils::string::to_lower(absolute(path)), | ||||
|                                     get_directory_seperator<char>(), false); | ||||
|  | ||||
|   return std::find_if(parts.begin(), parts.end(), [](auto &&part) -> bool { | ||||
|            return utils::string::begins_with(part, ".trash-") || | ||||
|                   part == ".trashes" || part == "$recycle.bin"; | ||||
|          }) != parts.end(); | ||||
| } | ||||
|  | ||||
| auto contains_trash_directory(std::wstring_view path) -> bool { | ||||
|   return contains_trash_directory(utils::string::to_utf8(path)); | ||||
| } | ||||
|  | ||||
| auto make_file_uri(std::string_view path) -> std::string { | ||||
|   auto abs_path = absolute(path); | ||||
| #if defined(_WIN32) | ||||
|   utils::string::replace(abs_path, backslash, slash); | ||||
|   abs_path = std::string{slash} + abs_path; | ||||
| #endif // defined(_WIN32) | ||||
|   return "file://" + abs_path; | ||||
| } | ||||
|  | ||||
| auto make_file_uri(std::wstring_view path) -> std::wstring { | ||||
|   return utils::string::from_utf8(make_file_uri(utils::string::to_utf8(path))); | ||||
| } | ||||
|  | ||||
| auto strip_to_file_name(std::string path) -> std::string { | ||||
|   if (path == "." || path == "..") { | ||||
|     return path; | ||||
|   } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
|   return ::PathFindFileNameA(path.c_str()); | ||||
| #else  // !defined(_WIN32) | ||||
|   return utils::string::contains(path, slash) ? basename(path.data()) : path; | ||||
| #endif // defined(_WIN32) | ||||
| } | ||||
|  | ||||
| auto strip_to_file_name(std::wstring path) -> std::wstring { | ||||
|   return utils::string::from_utf8( | ||||
|       strip_to_file_name(utils::string::to_utf8(path))); | ||||
| } | ||||
|  | ||||
| auto unmake_file_uri(std::string_view uri) -> std::string { | ||||
|   static constexpr const std::array<std::string_view, 23U> escape_characters = { | ||||
|       { | ||||
|           " ", "<", ">", "#", "%", "+", "{", "}", "|", "\\", "^", "~", | ||||
|           "[", "]", "`", ";", "/", "?", ":", "@", "=", "&",  "$", | ||||
|       }}; | ||||
|   static constexpr const std::array<std::string_view, 23U> | ||||
|       escape_sequences_lower = {{ | ||||
|           "%20", "%3c", "%3e", "%23", "%25", "%2b", "%7b", "%7d", | ||||
|           "%7c", "%5c", "%5e", "%7e", "%5b", "%5d", "%60", "%3b", | ||||
|           "%2f", "%3f", "%3a", "%40", "%3d", "%26", "%24", | ||||
|       }}; | ||||
|   static constexpr const std::array<std::string_view, 23U> | ||||
|       escape_sequences_upper = {{ | ||||
|           "%20", "%3C", "%3E", "%23", "%25", "%2B", "%7B", "%7D", | ||||
|           "%7C", "%5C", "%5E", "%7E", "%5B", "%5D", "%60", "%3B", | ||||
|           "%2F", "%3F", "%3A", "%40", "%3D", "%26", "%24", | ||||
|       }}; | ||||
|  | ||||
|   std::string ret_uri{uri}; | ||||
| #if defined(_WIN32) | ||||
|   ret_uri = ret_uri.substr(8U); | ||||
| #else | ||||
|   ret_uri = ret_uri.substr(7U); | ||||
| #endif | ||||
|  | ||||
|   for (std::size_t idx = 0U; idx < escape_characters.size(); idx++) { | ||||
|     utils::string::replace(ret_uri, escape_sequences_lower.at(idx), | ||||
|                            escape_characters.at(idx)); | ||||
|     utils::string::replace(ret_uri, escape_sequences_upper.at(idx), | ||||
|                            escape_characters.at(idx)); | ||||
|   } | ||||
|  | ||||
|   return absolute(ret_uri); | ||||
| } | ||||
|  | ||||
| auto unmake_file_uri(std::wstring_view uri) -> std::wstring { | ||||
|   return utils::string::from_utf8(unmake_file_uri(utils::string::to_utf8(uri))); | ||||
| } | ||||
| } // namespace repertory::utils::path | ||||
							
								
								
									
										138
									
								
								support/src/utils/string.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								support/src/utils/string.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory::utils::string { | ||||
| auto from_bool(bool val) -> std::string { | ||||
|   return std::to_string(static_cast<int>(val)); | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_BOOST) | ||||
| auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string { | ||||
|   std::stringstream stream; | ||||
|   boost::archive::text_oarchive archive(stream); | ||||
|   archive << bitset; | ||||
|   return stream.str(); | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
| auto from_utf8(std::string_view str) -> std::wstring { | ||||
|   return str.empty() | ||||
|              ? L"" | ||||
|              : std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>() | ||||
|                    .from_bytes(std::string{str}); | ||||
| } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_SFML) | ||||
| auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with, | ||||
|                 std::size_t start_pos) -> sf::String & { | ||||
|   if (not src.isEmpty() && (start_pos < src.getSize())) { | ||||
|     while ((start_pos = src.find(find, start_pos)) != std::string::npos) { | ||||
|       src.replace(start_pos, find.getSize(), with); | ||||
|       start_pos += with.getSize(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return src; | ||||
| } | ||||
|  | ||||
| auto split_sf(sf::String str, wchar_t delim, | ||||
|               bool should_trim) -> std::vector<sf::String> { | ||||
|   auto result = std::views::split(str.toWideString(), delim); | ||||
|  | ||||
|   std::vector<sf::String> ret{}; | ||||
|   for (auto &&word : result) { | ||||
|     auto val = std::wstring{word.begin(), word.end()}; | ||||
|     if (should_trim) { | ||||
|       trim(val); | ||||
|     } | ||||
|     ret.emplace_back(val); | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_SFML) | ||||
|  | ||||
| auto to_bool(std::string val) -> bool { | ||||
|   auto ret = false; | ||||
|  | ||||
|   trim(val); | ||||
|   if (is_numeric(val)) { | ||||
|     if (contains(val, ".")) { | ||||
|       ret = (to_double(val) != 0.0); | ||||
|     } else { | ||||
|       std::istringstream(val) >> ret; | ||||
|     } | ||||
|   } else { | ||||
|     std::istringstream(to_lower(val)) >> std::boolalpha >> ret; | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| auto to_double(const std::string &str) -> double { return std::stod(str); } | ||||
|  | ||||
| #if defined(PROJECT_ENABLE_BOOST) | ||||
| auto to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<> { | ||||
|   std::stringstream stream(val); | ||||
|   boost::dynamic_bitset<> bitset; | ||||
|   boost::archive::text_iarchive archive(stream); | ||||
|   archive >> bitset; | ||||
|   return bitset; | ||||
| } | ||||
| #endif // defined(PROJECT_ENABLE_BOOST) | ||||
|  | ||||
| auto to_int32(const std::string &val) -> std::int32_t { return std::stoi(val); } | ||||
|  | ||||
| auto to_int64(const std::string &val) -> std::int64_t { | ||||
|   return std::stoll(val); | ||||
| } | ||||
|  | ||||
| auto to_size_t(const std::string &val) -> std::size_t { | ||||
|   return static_cast<std::size_t>(std::stoull(val)); | ||||
| } | ||||
|  | ||||
| auto to_uint8(const std::string &val) -> std::uint8_t { | ||||
|   return static_cast<std::uint8_t>(std::stoul(val)); | ||||
| } | ||||
|  | ||||
| auto to_uint16(const std::string &val) -> std::uint16_t { | ||||
|   return static_cast<std::uint16_t>(std::stoul(val)); | ||||
| } | ||||
|  | ||||
| auto to_uint32(const std::string &val) -> std::uint32_t { | ||||
|   return static_cast<std::uint32_t>(std::stoul(val)); | ||||
| } | ||||
|  | ||||
| auto to_uint64(const std::string &val) -> std::uint64_t { | ||||
|   return std::stoull(val); | ||||
| } | ||||
|  | ||||
| auto to_utf8(std::string_view str) -> std::string { return std::string{str}; } | ||||
|  | ||||
| auto to_utf8(std::wstring_view str) -> std::string { | ||||
|   return str.empty() | ||||
|              ? "" | ||||
|              : std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>() | ||||
|                    .to_bytes(std::wstring{str}); | ||||
| } | ||||
| } // namespace repertory::utils::string | ||||
							
								
								
									
										88
									
								
								support/src/utils/time.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								support/src/utils/time.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #include "utils/time.hpp" | ||||
|  | ||||
| namespace repertory::utils::time { | ||||
| void get_local_time_now(struct tm &local_time) { | ||||
|   std::memset(&local_time, 0, sizeof(local_time)); | ||||
|  | ||||
|   const auto now = | ||||
|       std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); | ||||
| #if defined(_WIN32) | ||||
|   localtime_s(&local_time, &now); | ||||
| #else  // !defined(_WIN32) | ||||
|   localtime_r(&now, &local_time); | ||||
| #endif // defined(_WIN32) | ||||
| } | ||||
|  | ||||
| auto get_time_now() -> std::uint64_t { | ||||
|   return static_cast<std::uint64_t>( | ||||
|       std::chrono::duration_cast<std::chrono::nanoseconds>( | ||||
|           std::chrono::system_clock::now().time_since_epoch()) | ||||
|           .count()); | ||||
| } | ||||
|  | ||||
| #if defined(_WIN32) | ||||
| // https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows | ||||
| auto strptime(const char *s, const char *f, struct tm *tm) -> const char * { | ||||
|   std::istringstream input{s}; | ||||
|   input.imbue(std::locale(setlocale(LC_ALL, nullptr))); | ||||
|   input >> std::get_time(tm, f); | ||||
|   if (input.fail()) { | ||||
|     return nullptr; | ||||
|   } | ||||
|  | ||||
|   return reinterpret_cast<const char *>(s + input.tellg()); | ||||
| } | ||||
|  | ||||
| // https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ | ||||
| auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME { | ||||
|   auto win_time = unix_time_to_windows_time(unix_time); | ||||
|  | ||||
|   FILETIME file_time{}; | ||||
|   file_time.dwHighDateTime = static_cast<DWORD>(win_time >> 32U); | ||||
|   file_time.dwLowDateTime = win_time & 0xFFFFFFFF; | ||||
|   return file_time; | ||||
| } | ||||
|  | ||||
| auto windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t { | ||||
|   return windows_time_to_unix_time( | ||||
|       (static_cast<std::uint64_t>(win_time.dwHighDateTime) << 32ULL) | | ||||
|       static_cast<std::uint64_t>(win_time.dwLowDateTime)); | ||||
| } | ||||
|  | ||||
| auto windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t { | ||||
|   return static_cast<std::uint64_t>( | ||||
|       std::chrono::duration_cast<std::chrono::nanoseconds>( | ||||
|           std::chrono::system_clock::from_time_t(win_time).time_since_epoch()) | ||||
|           .count()); | ||||
| } | ||||
| #endif // defined(_WIN32) | ||||
|  | ||||
| auto unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t { | ||||
|   return (unix_time / WIN32_TIME_NANOS_PER_TICK) + WIN32_TIME_CONVERSION; | ||||
| } | ||||
|  | ||||
| auto windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t { | ||||
|   return (win_time - WIN32_TIME_CONVERSION) * WIN32_TIME_NANOS_PER_TICK; | ||||
| } | ||||
| } // namespace repertory::utils::time | ||||
							
								
								
									
										83
									
								
								support/src/utils/unix.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								support/src/utils/unix.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if !defined(_WIN32) | ||||
|  | ||||
| #include "utils/unix.hpp" | ||||
| #include "utils/collection.hpp" | ||||
|  | ||||
| namespace repertory::utils { | ||||
| #if !defined(__APPLE__) | ||||
| auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t { | ||||
|   return static_cast<std::uint64_t>(thread); | ||||
| } | ||||
| #endif // !defined(__APPLE__) | ||||
|  | ||||
| auto get_last_error_code() -> int { return errno; } | ||||
|  | ||||
| auto get_thread_id() -> std::uint64_t { | ||||
|   return convert_to_uint64(pthread_self()); | ||||
| } | ||||
|  | ||||
| auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool { | ||||
|   std::vector<gid_t> groups{}; | ||||
|   auto res = use_getpwuid(uid, [&groups](struct passwd *pass) { | ||||
|     int group_count{}; | ||||
|     if (getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count) < 0) { | ||||
|       groups.resize(static_cast<std::size_t>(group_count)); | ||||
| #if defined(__APPLE__) | ||||
|       getgrouplist(pass->pw_name, pass->pw_gid, | ||||
|                    reinterpret_cast<int *>(groups.data()), &group_count); | ||||
| #else  // !defined(__APPLE__) | ||||
|       getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count); | ||||
| #endif // defined(__APPLE__) | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   if (not res) { | ||||
|     throw utils::error::create_exception(res.function_name, | ||||
|                                          {"use_getpwuid failed", res.reason}); | ||||
|   } | ||||
|  | ||||
|   return collection::includes(groups, gid); | ||||
| } | ||||
|  | ||||
| auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   static std::mutex mtx{}; | ||||
|   mutex_lock lock{mtx}; | ||||
|  | ||||
|   auto *temp_pw = getpwuid(uid); | ||||
|   if (temp_pw == nullptr) { | ||||
|     return { | ||||
|         std::string{function_name}, | ||||
|         false, | ||||
|         "'getpwuid' returned nullptr", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   callback(temp_pw); | ||||
|   return {std::string{function_name}}; | ||||
| } | ||||
| } // namespace repertory::utils | ||||
|  | ||||
| #endif // !defined(_WIN32) | ||||
							
								
								
									
										144
									
								
								support/src/utils/windows.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								support/src/utils/windows.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| /* | ||||
|   Copyright <2018-2024> <scott.e.graves@protonmail.com> | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|   of this software and associated documentation files (the "Software"), to deal | ||||
|   in the Software without restriction, including without limitation the rights | ||||
|   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|   copies of the Software, and to permit persons to whom the Software is | ||||
|   furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all | ||||
|   copies or substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|   SOFTWARE. | ||||
| */ | ||||
| #if defined(_WIN32) | ||||
|  | ||||
| #include "utils/windows.hpp" | ||||
|  | ||||
| #include "utils/com_init_wrapper.hpp" | ||||
| #include "utils/error.hpp" | ||||
| #include "utils/string.hpp" | ||||
|  | ||||
| namespace repertory::utils { | ||||
| void create_console() { | ||||
|   if (::AllocConsole() == 0) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   FILE *dummy{nullptr}; | ||||
|   freopen_s(&dummy, "CONOUT$", "w", stdout); | ||||
|   freopen_s(&dummy, "CONOUT$", "w", stderr); | ||||
|   freopen_s(&dummy, "CONIN$", "r", stdin); | ||||
|   std::cout.clear(); | ||||
|   std::clog.clear(); | ||||
|   std::cerr.clear(); | ||||
|   std::cin.clear(); | ||||
|  | ||||
|   auto *out_w = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, | ||||
|                             FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, | ||||
|                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); | ||||
|   auto *in_w = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, | ||||
|                            FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, | ||||
|                            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); | ||||
|   SetStdHandle(STD_OUTPUT_HANDLE, out_w); | ||||
|   SetStdHandle(STD_ERROR_HANDLE, out_w); | ||||
|   SetStdHandle(STD_INPUT_HANDLE, in_w); | ||||
|   std::wcout.clear(); | ||||
|   std::wclog.clear(); | ||||
|   std::wcerr.clear(); | ||||
|   std::wcin.clear(); | ||||
| } | ||||
|  | ||||
| void free_console() { ::FreeConsole(); } | ||||
|  | ||||
| auto get_last_error_code() -> DWORD { return ::GetLastError(); } | ||||
|  | ||||
| auto get_local_app_data_directory() -> const std::string & { | ||||
|   REPERTORY_USES_FUNCTION_NAME(); | ||||
|  | ||||
|   static std::string app_data = ([]() -> std::string { | ||||
|     com_init_wrapper cw; | ||||
|     PWSTR local_app_data{}; | ||||
|     if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, | ||||
|                                          &local_app_data))) { | ||||
|       auto ret = utils::string::to_utf8(local_app_data); | ||||
|       ::CoTaskMemFree(local_app_data); | ||||
|       return ret; | ||||
|     } | ||||
|  | ||||
|     throw utils::error::create_exception( | ||||
|         function_name, { | ||||
|                            "unable to detect local application data folder", | ||||
|                        }); | ||||
|   })(); | ||||
|  | ||||
|   return app_data; | ||||
| } | ||||
|  | ||||
| auto get_thread_id() -> std::uint64_t { | ||||
|   return static_cast<std::uint64_t>(::GetCurrentThreadId()); | ||||
| } | ||||
|  | ||||
| auto is_process_elevated() -> bool { | ||||
|   auto ret{false}; | ||||
|   HANDLE token{INVALID_HANDLE_VALUE}; | ||||
|   if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { | ||||
|     TOKEN_ELEVATION te{}; | ||||
|     DWORD sz = sizeof(te); | ||||
|     if (::GetTokenInformation(token, TokenElevation, &te, sizeof(te), &sz)) { | ||||
|       ret = (te.TokenIsElevated != 0); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (token != INVALID_HANDLE_VALUE) { | ||||
|     ::CloseHandle(token); | ||||
|   } | ||||
|  | ||||
|   return ret; | ||||
| } | ||||
|  | ||||
| auto run_process_elevated(std::vector<const char *> args) -> int { | ||||
|   std::cout << "Elevating Process" << std::endl; | ||||
|   std::string parameters{"-hidden"}; | ||||
|   for (std::size_t idx = 1U; idx < args.size(); idx++) { | ||||
|     parameters += | ||||
|         (parameters.empty() ? args.at(idx) : " " + std::string(args.at(idx))); | ||||
|   } | ||||
|  | ||||
|   std::string full_path; | ||||
|   full_path.resize(repertory::max_path_length + 1); | ||||
|  | ||||
|   if (::GetModuleFileNameA(nullptr, full_path.data(), | ||||
|                            repertory::max_path_length)) { | ||||
|     SHELLEXECUTEINFOA sei{}; | ||||
|     sei.fMask = SEE_MASK_NOCLOSEPROCESS; | ||||
|     sei.cbSize = sizeof(sei); | ||||
|     sei.lpVerb = "runas"; | ||||
|     sei.lpFile = full_path.c_str(); | ||||
|     sei.lpParameters = parameters.c_str(); | ||||
|     sei.hwnd = nullptr; | ||||
|     sei.nShow = SW_NORMAL; | ||||
|     if (::ShellExecuteExA(&sei)) { | ||||
|       ::WaitForSingleObject(sei.hProcess, INFINITE); | ||||
|       DWORD exit_code{}; | ||||
|       ::GetExitCodeProcess(sei.hProcess, &exit_code); | ||||
|       ::CloseHandle(sei.hProcess); | ||||
|       return static_cast<int>(exit_code); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return static_cast<int>(::GetLastError()); | ||||
| } | ||||
|  | ||||
| void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); } | ||||
| } // namespace repertory::utils | ||||
|  | ||||
| #endif // defined(_WIN32) | ||||
		Reference in New Issue
	
	Block a user