| // Copyright 2015 The Weave Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/commands/command_queue.h" |
| |
| #include <base/bind.h> |
| #include <base/time/time.h> |
| |
| namespace weave { |
| |
| namespace { |
| const int kRemoveCommandDelayMin = 5; |
| |
| std::string GetCommandHandlerKey(const std::string& component_path, |
| const std::string& command_name) { |
| return component_path + ":" + command_name; |
| } |
| } |
| |
| CommandQueue::CommandQueue(provider::TaskRunner* task_runner, |
| base::Clock* clock) |
| : task_runner_{task_runner}, clock_{clock} {} |
| |
| void CommandQueue::AddCommandAddedCallback(const CommandCallback& callback) { |
| on_command_added_.push_back(callback); |
| // Send all pre-existed commands. |
| for (const auto& command : map_) |
| callback.Run(command.second.get()); |
| } |
| |
| void CommandQueue::AddCommandRemovedCallback(const CommandCallback& callback) { |
| on_command_removed_.push_back(callback); |
| } |
| |
| void CommandQueue::AddCommandHandler( |
| const std::string& component_path, |
| const std::string& command_name, |
| const Device::CommandHandlerCallback& callback) { |
| if (!command_name.empty()) { |
| CHECK(default_command_callback_.is_null()) |
| << "Commands specific handler are not allowed after default one"; |
| |
| for (const auto& command : map_) { |
| if (command.second->GetState() == Command::State::kQueued && |
| command.second->GetName() == command_name && |
| command.second->GetComponent() == component_path) { |
| callback.Run(command.second); |
| } |
| } |
| |
| std::string key = GetCommandHandlerKey(component_path, command_name); |
| CHECK(command_callbacks_.insert(std::make_pair(key, callback)).second) |
| << command_name << " already has handler"; |
| |
| } else { |
| CHECK(component_path.empty()) |
| << "Default handler must not be component-specific"; |
| for (const auto& command : map_) { |
| std::string key = GetCommandHandlerKey(command.second->GetComponent(), |
| command.second->GetName()); |
| if (command.second->GetState() == Command::State::kQueued && |
| command_callbacks_.find(key) == command_callbacks_.end()) { |
| callback.Run(command.second); |
| } |
| } |
| |
| CHECK(default_command_callback_.is_null()) << "Already has default handler"; |
| default_command_callback_ = callback; |
| } |
| } |
| |
| void CommandQueue::Add(std::unique_ptr<CommandInstance> instance) { |
| std::string id = instance->GetID(); |
| LOG_IF(FATAL, id.empty()) << "Command has no ID"; |
| instance->AttachToQueue(this); |
| auto pair = map_.insert(std::make_pair(id, std::move(instance))); |
| LOG_IF(FATAL, !pair.second) << "Command with ID '" << id |
| << "' is already in the queue"; |
| for (const auto& cb : on_command_added_) |
| cb.Run(pair.first->second.get()); |
| |
| std::string key = GetCommandHandlerKey(pair.first->second->GetComponent(), |
| pair.first->second->GetName()); |
| auto it_handler = command_callbacks_.find(key); |
| |
| if (it_handler != command_callbacks_.end()) |
| it_handler->second.Run(pair.first->second); |
| else if (!default_command_callback_.is_null()) |
| default_command_callback_.Run(pair.first->second); |
| } |
| |
| void CommandQueue::RemoveLater(const std::string& id) { |
| auto p = map_.find(id); |
| if (p == map_.end()) |
| return; |
| auto remove_delay = base::TimeDelta::FromMinutes(kRemoveCommandDelayMin); |
| remove_queue_.push(std::make_pair(clock_->Now() + remove_delay, id)); |
| if (remove_queue_.size() == 1) { |
| // The queue was empty, this is the first command to be removed, schedule |
| // a clean-up task. |
| ScheduleCleanup(remove_delay); |
| } |
| } |
| |
| bool CommandQueue::Remove(const std::string& id) { |
| auto p = map_.find(id); |
| if (p == map_.end()) |
| return false; |
| std::shared_ptr<CommandInstance> instance = p->second; |
| instance->DetachFromQueue(); |
| map_.erase(p); |
| for (const auto& cb : on_command_removed_) |
| cb.Run(instance.get()); |
| return true; |
| } |
| |
| void CommandQueue::Cleanup(const base::Time& cutoff_time) { |
| while (!remove_queue_.empty() && remove_queue_.top().first <= cutoff_time) { |
| Remove(remove_queue_.top().second); |
| remove_queue_.pop(); |
| } |
| } |
| |
| void CommandQueue::ScheduleCleanup(base::TimeDelta delay) { |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&CommandQueue::PerformScheduledCleanup, |
| weak_ptr_factory_.GetWeakPtr()), |
| delay); |
| } |
| |
| void CommandQueue::PerformScheduledCleanup() { |
| base::Time now = clock_->Now(); |
| Cleanup(now); |
| if (!remove_queue_.empty()) |
| ScheduleCleanup(remove_queue_.top().first - now); |
| } |
| |
| CommandInstance* CommandQueue::Find(const std::string& id) const { |
| auto p = map_.find(id); |
| return (p != map_.end()) ? p->second.get() : nullptr; |
| } |
| |
| } // namespace weave |