blob: 7183ed9fb8ac2cbe90c5e6f5e988ee92104603f3 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
#define CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
#include <map>
#include <vector>
#include "base/macros.h"
#include "base/observer_list_types.h"
#include "base/sequence_checker.h"
#include "chrome/services/app_service/public/cpp/app_update.h"
namespace apps {
// Caches all of the apps::mojom::AppPtr's seen by an apps::mojom::Subscriber.
// A Subscriber sees a stream of "deltas", or changes in app state. This cache
// also keeps the "sum" of those previous deltas, so that observers of this
// object are presented with AppUpdate's, i.e. "state-and-delta"s.
//
// It can also be queried synchronously, providing answers from its in-memory
// cache, even though the underlying App Registry (and its App Publishers)
// communicate asynchronously, possibly across process boundaries, via Mojo
// IPC. Synchronous APIs can be more suitable for e.g. UI programming that
// should not block an event loop on I/O.
//
// This class is not thread-safe.
//
// See //chrome/services/app_service/README.md for more details.
class AppRegistryCache {
public:
class Observer : public base::CheckedObserver {
public:
// The apps::AppUpdate argument shouldn't be accessed after OnAppUpdate
// returns.
virtual void OnAppUpdate(const AppUpdate& update) = 0;
protected:
// Use this constructor when the object is tied to a single
// AppRegistryCache for its entire lifetime.
explicit Observer(AppRegistryCache* cache);
// Use this constructor when the object wants to observe a AppRegistryCache
// for part of its lifetime. It can then call Observe() to start and stop
// observing.
Observer();
~Observer() override;
// Start observing a different AppRegistryCache; used with the default
// constructor.
void Observe(AppRegistryCache* cache);
private:
AppRegistryCache* cache_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(Observer);
};
AppRegistryCache();
~AppRegistryCache();
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Notifies all observers of state-and-delta AppUpdate's (the state comes
// from the internal cache, the delta comes from the argument) and then
// merges the cached states with the deltas.
//
// Notification and merging might be delayed until after OnApps returns. For
// example, suppose that the initial set of states is (a0, b0, c0) for three
// app_id's ("a", "b", "c"). Now suppose OnApps is called with two updates
// (b1, c1), and when notified of b1, an observer calls OnApps again with
// (c2, d2). The c1 delta should be processed before the c2 delta, as it was
// sent first: c2 should be merged (with "newest wins" semantics) onto c1 and
// not vice versa. This means that processing c2 (scheduled by the second
// OnApps call) should wait until the first OnApps call has finished
// processing b1 (and then c1), which means that processing c2 is delayed
// until after the second OnApps call returns.
//
// The callee will consume the deltas. An apps::mojom::AppPtr has the
// ownership semantics of a unique_ptr, and will be deleted when out of
// scope. The caller presumably calls OnApps(std::move(deltas)).
void OnApps(std::vector<apps::mojom::AppPtr> deltas);
apps::mojom::AppType GetAppType(const std::string& app_id);
// Calls f, a void-returning function whose arguments are (const
// apps::AppUpdate&), on each app in the cache.
//
// f's argument is an apps::AppUpdate instead of an apps::mojom::AppPtr so
// that callers can more easily share code with Observer::OnAppUpdate (which
// also takes an apps::AppUpdate), and an apps::AppUpdate also has a
// StateIsNull method.
//
// The apps::AppUpdate argument to f shouldn't be accessed after f returns.
//
// f must be synchronous, and if it asynchronously calls ForEachApp again,
// it's not guaranteed to see a consistent state.
template <typename FunctionType>
void ForEachApp(FunctionType f) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
for (const auto& s_iter : states_) {
const apps::mojom::App* state = s_iter.second.get();
auto d_iter = deltas_in_progress_.find(s_iter.first);
const apps::mojom::App* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
f(apps::AppUpdate(state, delta));
}
for (const auto& d_iter : deltas_in_progress_) {
const apps::mojom::App* delta = d_iter.second;
auto s_iter = states_.find(d_iter.first);
if (s_iter != states_.end()) {
continue;
}
f(apps::AppUpdate(nullptr, delta));
}
}
// Calls f, a void-returning function whose arguments are (const
// apps::AppUpdate&), on the app in the cache with the given app_id. It will
// return true (and call f) if there is such an app, otherwise it will return
// false (and not call f). The AppUpdate argument to f has the same semantics
// as for ForEachApp, above.
//
// f must be synchronous, and if it asynchronously calls ForEachApp again,
// it's not guaranteed to see a consistent state.
template <typename FunctionType>
bool ForOneApp(const std::string& app_id, FunctionType f) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
auto s_iter = states_.find(app_id);
const apps::mojom::App* state =
(s_iter != states_.end()) ? s_iter->second.get() : nullptr;
auto d_iter = deltas_in_progress_.find(app_id);
const apps::mojom::App* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
if (state || delta) {
f(apps::AppUpdate(state, delta));
return true;
}
return false;
}
private:
void DoOnApps(std::vector<apps::mojom::AppPtr> deltas);
base::ObserverList<Observer> observers_;
// Maps from app_id to the latest state: the "sum" of all previous deltas.
std::map<std::string, apps::mojom::AppPtr> states_;
// Track the deltas being processed or are about to be processed by OnApps.
// They are separate to manage the "notification and merging might be delayed
// until after OnApps returns" concern described above.
//
// OnApps calls DoOnApps zero or more times. If we're nested, so that there's
// multiple OnApps call to this AppRegistryCache in the call stack, the
// deeper OnApps call simply adds work to deltas_pending_ and returns without
// calling DoOnApps. If we're not nested, OnApps calls DoOnApps one or more
// times; "more times" happens if DoOnApps notifying observers leads to more
// OnApps calls that enqueue deltas_pending_ work. The deltas_in_progress_
// map (keyed by app_id) contains those deltas being considered by DoOnApps.
//
// Nested OnApps calls are expected to be rare (but still dealt with
// sensibly). In the typical case, OnApps should call DoOnApps exactly once,
// and deltas_pending_ will stay empty.
std::map<std::string, apps::mojom::App*> deltas_in_progress_;
std::vector<apps::mojom::AppPtr> deltas_pending_;
SEQUENCE_CHECKER(my_sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(AppRegistryCache);
};
} // namespace apps
#endif // CHROME_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_