diff --git a/lib/incredis b/lib/incredis index 0aee978..d2fff64 160000 --- a/lib/incredis +++ b/lib/incredis @@ -1 +1 @@ -Subproject commit 0aee978661e0f31722a28ff039490a98c829644b +Subproject commit d2fff64381402a4ada9bbe3673603bbc81b2d0f6 diff --git a/src/kamokan_impl/CMakeLists.txt b/src/kamokan_impl/CMakeLists.txt index 0fc0df1..adc743b 100644 --- a/src/kamokan_impl/CMakeLists.txt +++ b/src/kamokan_impl/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(${PROJECT_NAME} STATIC string_conv.cpp edit_response.cpp general_pastie_response.cpp + ${CMAKE_CURRENT_BINARY_DIR}/include/lua_scripts_for_redis.cpp ) target_include_directories(${PROJECT_NAME} @@ -62,3 +63,10 @@ configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/kamokan_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/include/kamokan_config.h" ) + +add_custom_command( + OUTPUT include/lua_scripts_for_redis.cpp + COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" -P ${CMAKE_CURRENT_SOURCE_DIR}/lua_to_cpp.cmake + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/retrieve_pastie.lua ${CMAKE_CURRENT_SOURCE_DIR}/save_pastie.lua + COMMENT "Embedding save/load lua scripts into the c++ code" +) diff --git a/src/kamokan_impl/lua_scripts_for_redis.hpp b/src/kamokan_impl/lua_scripts_for_redis.hpp new file mode 100644 index 0000000..c2c2cd3 --- /dev/null +++ b/src/kamokan_impl/lua_scripts_for_redis.hpp @@ -0,0 +1,27 @@ +/* Copyright 2017, Michele Santullo + * This file is part of "kamokan". + * + * "kamokan" is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * "kamokan" is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with "kamokan". If not, see . + */ + +#pragma once + +#include + +namespace kamokan { + extern const char g_save_script[]; + extern const char g_load_script[]; + extern const std::size_t g_save_script_size; + extern const std::size_t g_load_script_size; +} //namespace kamokan diff --git a/src/kamokan_impl/lua_to_cpp.cmake b/src/kamokan_impl/lua_to_cpp.cmake new file mode 100644 index 0000000..18987ec --- /dev/null +++ b/src/kamokan_impl/lua_to_cpp.cmake @@ -0,0 +1,31 @@ +file(READ ${SOURCE_DIR}/retrieve_pastie.lua retrieve_pastie) +file(READ ${SOURCE_DIR}/save_pastie.lua save_pastie) +string(REGEX REPLACE "[ \t]*=[ \t]*" "=" retrieve_pastie "${retrieve_pastie}") +string(REGEX REPLACE "[ \t]*=[ \t]*" "=" save_pastie "${save_pastie}") +string(REGEX REPLACE ",[ \t]*" "," retrieve_pastie "${retrieve_pastie}") +string(REGEX REPLACE ",[ \t]*" "," save_pastie "${save_pastie}") +string(REGEX REPLACE "(^|\n)[ \t]+" "\\1" retrieve_pastie "${retrieve_pastie}") +string(REGEX REPLACE "(^|\n)[ \t]+" "\\1" save_pastie "${save_pastie}") +string(LENGTH "${retrieve_pastie}" retrieve_pastie_length) +string(LENGTH "${save_pastie}" save_pastie_length) + +set(lua_scripts_for_redis_content "//File autogenerated by cmake, changes will be lost +#include +namespace kamokan { +extern const char g_save_script[] = R\"lua(${save_pastie})lua\"; +extern const char g_load_script[] = R\"lua(${retrieve_pastie})lua\"; +extern const std::size_t g_save_script_size = ${save_pastie_length} + 1; +extern const std::size_t g_load_script_size = ${retrieve_pastie_length} + 1; +} //namespace kamokan +") + +file(WRITE + include/lua_scripts_for_redis.cpp + "${lua_scripts_for_redis_content}" +) + +unset(lua_scripts_for_redis_content) +unset(save_pastie_length) +unset(retrieve_pastie_length) +unset(save_pastie) +unset(retrieve_pastie) diff --git a/src/kamokan_impl/retrieve_pastie.lua b/src/kamokan_impl/retrieve_pastie.lua new file mode 100644 index 0000000..de9ff4f --- /dev/null +++ b/src/kamokan_impl/retrieve_pastie.lua @@ -0,0 +1,10 @@ +local token = KEYS[1] +local result = redis.call("HMGET", token, "pastie", "selfdes", "lang") +local selfdes = 0 +local deleted = 0 +if result[2] == 1 then + deleted = redis.call("DEL", token) + selfdes = 1 +end + +return {result[1], selfdes, deleted, result[3]} diff --git a/src/kamokan_impl/save_pastie.lua b/src/kamokan_impl/save_pastie.lua new file mode 100644 index 0000000..3dc39c9 --- /dev/null +++ b/src/kamokan_impl/save_pastie.lua @@ -0,0 +1,29 @@ +function num_to_value (num) + local retval = "" + local running = true + + while (running) + do + local remainder = num % 26 + retval = string.char(97 + remainder) .. retval + num = math.floor(num / 26) + running = (num ~= 0) + end + return retval +end + +paste_counter = KEYS[1] +next_id = redis.call("INCR", paste_counter) - 1 +token = num_to_token(next_id) +text = ARGV[1] +ttl = ARGV[2] +lang = ARGV[3] +selfdestruct = ARGV[4] +redis.call("HMSET", token, + "pastie", text, + "max_ttl", ttl, + "lang", lang, + "selfdes", selfdestruct +) + +return token diff --git a/src/kamokan_impl/storage.cpp b/src/kamokan_impl/storage.cpp index 9193b77..19bf212 100644 --- a/src/kamokan_impl/storage.cpp +++ b/src/kamokan_impl/storage.cpp @@ -23,6 +23,7 @@ #include "spdlog.hpp" #include "truncated_string.hpp" #include "string_conv.hpp" +#include "lua_scripts_for_redis.hpp" #include #include #include @@ -129,6 +130,7 @@ namespace kamokan { const std::string& parRemoteIP ) const { using tawashi::ErrorReasons; + using redis::RedisInt; if (not is_connected()) return make_submission_result(ErrorReasons::RedisDisconnected); @@ -156,7 +158,7 @@ namespace kamokan { "pastie", parText, "max_ttl", dhandy::lexical_cast(parExpiry), "lang", parLang, - "selfdes", (parSelfDestruct ? "1" : "0")) + "selfdes", static_cast(parSelfDestruct ? 1 : 0)) ) { redis.set(parRemoteIP, ""); redis.expire(parRemoteIP, m_settings->as("resubmit_wait")); @@ -168,7 +170,6 @@ namespace kamokan { } auto Storage::retrieve_pastie (const boost::string_view& parToken, uint32_t parMaxTokenLen) const -> RetrievedPastie { - using opt_string = redis::IncRedis::opt_string; using opt_string_list = redis::IncRedis::opt_string_list; RetrievedPastie retval; @@ -176,18 +177,26 @@ namespace kamokan { if (not retval.valid_token) return retval; - opt_string_list pastie_reply = m_redis->hmget(parToken, "pastie", "selfdes", "lang"); - retval.pastie = (pastie_reply and not pastie_reply->empty() ? (*pastie_reply)[0] : opt_string()); - opt_string selfdes = (pastie_reply and pastie_reply->size() > 1 ? (*pastie_reply)[1] : opt_string()); - retval.lang = (pastie_reply and pastie_reply->size() > 2 ? (*pastie_reply)[2] : opt_string()); - if (selfdes and string_conv(*selfdes)) { - const bool deleted = m_redis->del(parToken); - retval.self_destructed = deleted; - if (not deleted) { - auto statuslog = spdlog::get("statuslog"); - statuslog->error("Pastie \"{}\" was marked as self-destructing but DEL failed to delete it", parToken); - } + redis::Script retrieve = m_redis->command().make_script(boost::string_view(g_load_script, g_load_script_size - 1)); + auto batch = m_redis->command().make_batch(); + retrieve.run(batch, std::make_tuple(parToken), std::make_tuple()); + auto raw_replies = batch.replies(); + if (raw_replies.empty()) + return retval; + + assert(not raw_replies.front().is_error()); + auto pastie_reply = get_array(raw_replies.front()); + + retval.pastie = get_string(pastie_reply[0]); + const redis::RedisInt selfdes = get_integer(pastie_reply[1]); + const redis::RedisInt deleted = get_integer(pastie_reply[2]); + retval.lang = get_string(pastie_reply[3]); + retval.self_destructed = selfdes and deleted; + + if (selfdes and not deleted) { + auto statuslog = spdlog::get("statuslog"); + statuslog->error("Pastie \"{}\" was marked as self-destructing but DEL failed to delete it", parToken); } #if defined(SPDLOG_DEBUG_ON)