sdbus++: async: server: generate set-property fn

Add binding generation for set-property functions.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I3fce183f668ea339a035b8186630ffc04aa9af82
diff --git a/example/calculator-aserver.cpp b/example/calculator-aserver.cpp
index 536ee25..290e0aa 100644
--- a/example/calculator-aserver.cpp
+++ b/example/calculator-aserver.cpp
@@ -13,21 +13,23 @@
         ctx.spawn(startup());
     }
 
-    auto get_property(owner_t) const
-    {
-        return "asdf";
-    }
-
     auto get_property(last_result_t) const
     {
         return 42;
     }
 
+    bool set_property(last_result_t, auto)
+    {
+        return false;
+    }
+
   private:
     auto startup() -> sdbusplus::async::task<>
     {
         ctx.get_bus().request_name("net.poettering.Calculator");
 
+        status(State::Error);
+
         while (1)
         {
             using namespace std::literals;
diff --git a/include/sdbusplus/async/server.hpp b/include/sdbusplus/async/server.hpp
index cfc4d78..9710d5f 100644
--- a/include/sdbusplus/async/server.hpp
+++ b/include/sdbusplus/async/server.hpp
@@ -76,6 +76,23 @@
             i.get_property(Tag{}, m);
         });
 
+/* Determine if a type has a set_property call. */
+template <typename Tag, typename Instance, typename Arg>
+concept has_set_property_nomsg = requires(
+    Instance& i, Arg&& a) { i.set_property(Tag{}, std::forward<Arg>(a)); };
+
+/* Determine if a type has a set property call that requries a msg. */
+template <typename Tag, typename Instance, typename Arg>
+concept has_set_property_msg =
+    requires(Instance& i, sdbusplus::message_t& m, Arg&& a) {
+        i.set_property(Tag{}, m, std::forward<Arg>(a));
+    };
+
+/* Determine if a type has any set_property call. */
+template <typename Tag, typename Instance, typename Arg>
+concept has_set_property = has_set_property_nomsg<Tag, Instance, Arg> ||
+                           has_set_property_msg<Tag, Instance, Arg>;
+
 } // namespace server::details
 
 } // namespace sdbusplus::async
diff --git a/tools/meson.build b/tools/meson.build
index dfc1f03..1538289 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -26,6 +26,7 @@
   'sdbusplus/templates/property.md.mako',
   'sdbusplus/templates/property.aserver.callback.hpp.mako',
   'sdbusplus/templates/property.aserver.get.hpp.mako',
+  'sdbusplus/templates/property.aserver.set.hpp.mako',
   'sdbusplus/templates/property.aserver.tag.hpp.mako',
   'sdbusplus/templates/property.aserver.typeid.hpp.mako',
   'sdbusplus/templates/property.aserver.value.hpp.mako',
diff --git a/tools/sdbusplus/templates/interface.aserver.hpp.mako b/tools/sdbusplus/templates/interface.aserver.hpp.mako
index f6e419b..2f1189e 100644
--- a/tools/sdbusplus/templates/interface.aserver.hpp.mako
+++ b/tools/sdbusplus/templates/interface.aserver.hpp.mako
@@ -76,6 +76,9 @@
 % for p in interface.properties:
 ${p.render(loader, "property.aserver.get.hpp.mako", property=p, interface=interface)}
 % endfor
+% for p in interface.properties:
+${p.render(loader, "property.aserver.set.hpp.mako", property=p, interface=interface)}
+% endfor
 
   private:
     /** @return the async context */
diff --git a/tools/sdbusplus/templates/property.aserver.callback.hpp.mako b/tools/sdbusplus/templates/property.aserver.callback.hpp.mako
index aa43b98..58f3df4 100644
--- a/tools/sdbusplus/templates/property.aserver.callback.hpp.mako
+++ b/tools/sdbusplus/templates/property.aserver.callback.hpp.mako
@@ -57,6 +57,41 @@
         sd_bus_message* value[[maybe_unused]], void* context [[maybe_unused]],
         sd_bus_error* error [[maybe_unused]])
     {
-        return -EINVAL;
+        auto self = static_cast<${i_name}*>(context);
+
+        try
+        {
+            auto m = sdbusplus::message_t{value};
+
+            // Set up the transaction.
+            server::transaction::set_id(m);
+
+            auto new_value = m.unpack<${p_type}>();
+
+            // Get property value and add to message.
+            if constexpr (server_details::has_set_property_msg<
+                              ${p_tag}, Instance, ${p_type}>)
+            {
+                self->${p_name}(m, std::move(new_value));
+            }
+            else
+            {
+                self->${p_name}(std::move(new_value));
+            }
+        }
+        % for e in property.errors:
+        catch (const ${interface.errorNamespacedClass(e)}& e)
+        {
+            return sd_bus_error_set(error, e.name(), e.description());
+        }
+        % endfor
+        catch (const std::exception&)
+        {
+            self->_context().get_bus().set_current_exception(
+                std::current_exception());
+            return -EINVAL;
+        }
+
+        return 1;
     }
 % endif
diff --git a/tools/sdbusplus/templates/property.aserver.set.hpp.mako b/tools/sdbusplus/templates/property.aserver.set.hpp.mako
new file mode 100644
index 0000000..e6dfc49
--- /dev/null
+++ b/tools/sdbusplus/templates/property.aserver.set.hpp.mako
@@ -0,0 +1,51 @@
+<%
+p_name = property.snake_case;
+p_tag = property.snake_case + "_t"
+p_type = property.cppTypeParam(interface.name)
+i_name = interface.joinedName("_", "interface")
+%>\
+    template <bool EmitSignal = true, typename Arg = ${p_type}>
+    void ${p_name}(Arg&& new_value)
+        requires server_details::has_set_property_nomsg<${p_tag}, Instance,
+                                                        ${p_type}>
+    {
+        bool changed = static_cast<Instance*>(this)->set_property(
+            ${p_tag}{}, std::forward<Arg>(new_value));
+
+        if (changed && EmitSignal)
+        {
+            _${i_name}.property_changed("${property.name}");
+        }
+    }
+
+    template <bool EmitSignal = true, typename Arg = ${p_type}>
+    void ${p_name}(sdbusplus::message_t& m, Arg&& new_value)
+        requires server_details::has_set_property_msg<${p_tag}, Instance,
+                                                      ${p_type}>
+    {
+        bool changed = static_cast<Instance*>(this)->set_property(
+            ${p_tag}{}, m, std::forward<Arg>(new_value));
+
+        if (changed && EmitSignal)
+        {
+            _${i_name}.property_changed("${property.name}");
+        }
+    }
+
+    template <bool EmitSignal = true, typename Arg = ${p_type}>
+    void ${p_name}(Arg&& new_value)
+        requires (!server_details::has_set_property<${p_tag}, Instance,
+                                                    ${p_type}>)
+    {
+        static_assert(
+            !server_details::has_get_property<${p_tag}, Instance>,
+            "Cannot create default set-property for '${p_tag}' with get-property overload.");
+
+        bool changed = (new_value != _${p_name});
+        _${p_name} = std::forward<Arg>(new_value);
+
+        if (changed && EmitSignal)
+        {
+            _${i_name}.property_changed("${property.name}");
+        }
+    }