buffet: Publish state updates.

This is very simple implementation which periodically
publishes all the state updates if any.  It should be
improved to reduce traffic/CPU consumption, see TODO.

BUG=chromium:434767
TEST=manual&&cros_workon_make --test buffet
CQ-DEPEND=CL:231035

Change-Id: I5f1e001c4407373381f0e0f864c1973369955422
Reviewed-on: https://chromium-review.googlesource.com/231024
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Anton Muhin <antonm@chromium.org>
Tested-by: Anton Muhin <antonm@chromium.org>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index b36f114..25c6ee1 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -153,7 +153,7 @@
 
 DeviceRegistrationInfo::DeviceRegistrationInfo(
     const std::shared_ptr<CommandManager>& command_manager,
-    const std::shared_ptr<const StateManager>& state_manager)
+    const std::shared_ptr<StateManager>& state_manager)
     : DeviceRegistrationInfo(
         command_manager,
         state_manager,
@@ -165,7 +165,7 @@
 
 DeviceRegistrationInfo::DeviceRegistrationInfo(
     const std::shared_ptr<CommandManager>& command_manager,
-    const std::shared_ptr<const StateManager>& state_manager,
+    const std::shared_ptr<StateManager>& state_manager,
     const std::shared_ptr<chromeos::http::Transport>& transport,
     const std::shared_ptr<StorageInterface>& storage)
     : transport_{transport},
@@ -748,6 +748,14 @@
           base::Bind(&DeviceRegistrationInfo::PublishCommands,
                      base::Unretained(this))),
       base::TimeDelta::FromSeconds(7));
+  // TODO(antonm): Use better trigger: when StateManager registers new updates,
+  // it should call closure which will post a task, probabluy with some
+  // throtlling, to publish state updates.
+  PostRepeatingTask(
+      FROM_HERE,
+      base::Bind(&DeviceRegistrationInfo::PublishStateUpdates,
+                 base::Unretained(this)),
+      base::TimeDelta::FromSeconds(7));
 }
 
 void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) {
@@ -779,4 +787,42 @@
   }
 }
 
+void DeviceRegistrationInfo::PublishStateUpdates() {
+  VLOG(1) << "PublishStateUpdates";
+  const std::vector<StateChange> state_changes{
+      state_manager_->GetAndClearRecordedStateChanges()};
+  if (state_changes.empty())
+    return;
+
+  std::unique_ptr<base::ListValue> patches{new base::ListValue};
+  for (const auto& state_change : state_changes) {
+    std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
+    // TODO(antonm): Weird part: API requires long here while there is no
+    // such thing like long in JSON.  Also, ToJavaTime produces int64.
+    patch->SetInteger("timeMs", state_change.timestamp.ToJavaTime());
+
+    std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
+    for (const auto& pair : state_change.changed_properties) {
+      auto value = pair.second->ToJson(nullptr);
+      if (!value) {
+        return;
+      }
+      changes->SetWithoutPathExpansion(pair.first, value.release());
+    }
+    patch->Set("patch", changes.release());
+
+    patches->Append(patch.release());
+  }
+
+  base::DictionaryValue body;
+  body.SetInteger("requestTimeMs", base::Time::Now().ToJavaTime());
+  body.Set("patches", patches.release());
+
+  DoCloudRequest(
+      chromeos::http::request_type::kPost,
+      GetDeviceURL("patchState"),
+      &body,
+      base::Bind(&IgnoreCloudResult), base::Bind(&IgnoreCloudError));
+}
+
 }  // namespace buffet