1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
6 #define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
7 
8 #include <map>
9 #include <vector>
10 
11 #include "base/component_export.h"
12 #include "base/macros.h"
13 #include "base/observer_list.h"
14 #include "base/observer_list_types.h"
15 #include "base/sequence_checker.h"
16 #include "components/account_id/account_id.h"
17 #include "components/services/app_service/public/cpp/app_update.h"
18 
19 namespace apps {
20 
21 // Caches all of the apps::mojom::AppPtr's seen by an apps::mojom::Subscriber.
22 // A Subscriber sees a stream of "deltas", or changes in app state. This cache
23 // also keeps the "sum" of those previous deltas, so that observers of this
24 // object are presented with AppUpdate's, i.e. "state-and-delta"s.
25 //
26 // It can also be queried synchronously, providing answers from its in-memory
27 // cache, even though the underlying App Registry (and its App Publishers)
28 // communicate asynchronously, possibly across process boundaries, via Mojo
29 // IPC. Synchronous APIs can be more suitable for e.g. UI programming that
30 // should not block an event loop on I/O.
31 //
32 // This class is not thread-safe.
33 //
34 // See components/services/app_service/README.md for more details.
COMPONENT_EXPORT(APP_UPDATE)35 class COMPONENT_EXPORT(APP_UPDATE) AppRegistryCache {
36  public:
37   class COMPONENT_EXPORT(APP_UPDATE) Observer : public base::CheckedObserver {
38    public:
39     // The apps::AppUpdate argument shouldn't be accessed after OnAppUpdate
40     // returns.
41     virtual void OnAppUpdate(const AppUpdate& update) = 0;
42 
43     // Called when the AppRegistryCache object (the thing that this observer
44     // observes) will be destroyed. In response, the observer, |this|, should
45     // call "cache->RemoveObserver(this)", whether directly or indirectly (e.g.
46     // via base::ScopedObservation::Remove or via Observe(nullptr)).
47     virtual void OnAppRegistryCacheWillBeDestroyed(AppRegistryCache* cache) = 0;
48 
49    protected:
50     // Use this constructor when the observer |this| is tied to a single
51     // AppRegistryCache for its entire lifetime, or until the observee (the
52     // AppRegistryCache) is destroyed, whichever comes first.
53     explicit Observer(AppRegistryCache* cache);
54 
55     // Use this constructor when the observer |this| wants to observe a
56     // AppRegistryCache for part of its lifetime. It can then call Observe() to
57     // start and stop observing.
58     Observer();
59 
60     ~Observer() override;
61 
62     // Start observing a different AppRegistryCache. |cache| may be nullptr,
63     // meaning to stop observing.
64     void Observe(AppRegistryCache* cache);
65 
66    private:
67     AppRegistryCache* cache_ = nullptr;
68 
69     DISALLOW_COPY_AND_ASSIGN(Observer);
70   };
71 
72   AppRegistryCache();
73   ~AppRegistryCache();
74 
75   void AddObserver(Observer* observer);
76   void RemoveObserver(Observer* observer);
77 
78   // Notifies all observers of state-and-delta AppUpdate's (the state comes
79   // from the internal cache, the delta comes from the argument) and then
80   // merges the cached states with the deltas.
81   //
82   // Notification and merging might be delayed until after OnApps returns. For
83   // example, suppose that the initial set of states is (a0, b0, c0) for three
84   // app_id's ("a", "b", "c"). Now suppose OnApps is called with two updates
85   // (b1, c1), and when notified of b1, an observer calls OnApps again with
86   // (c2, d2). The c1 delta should be processed before the c2 delta, as it was
87   // sent first: c2 should be merged (with "newest wins" semantics) onto c1 and
88   // not vice versa. This means that processing c2 (scheduled by the second
89   // OnApps call) should wait until the first OnApps call has finished
90   // processing b1 (and then c1), which means that processing c2 is delayed
91   // until after the second OnApps call returns.
92   //
93   // The callee will consume the deltas. An apps::mojom::AppPtr has the
94   // ownership semantics of a unique_ptr, and will be deleted when out of
95   // scope. The caller presumably calls OnApps(std::move(deltas)).
96   void OnApps(std::vector<apps::mojom::AppPtr> deltas);
97 
98   apps::mojom::AppType GetAppType(const std::string& app_id);
99 
100   void SetAccountId(const AccountId& account_id);
101 
102   // Calls f, a void-returning function whose arguments are (const
103   // apps::AppUpdate&), on each app in the cache.
104   //
105   // f's argument is an apps::AppUpdate instead of an apps::mojom::AppPtr so
106   // that callers can more easily share code with Observer::OnAppUpdate (which
107   // also takes an apps::AppUpdate), and an apps::AppUpdate also has a
108   // StateIsNull method.
109   //
110   // The apps::AppUpdate argument to f shouldn't be accessed after f returns.
111   //
112   // f must be synchronous, and if it asynchronously calls ForEachApp again,
113   // it's not guaranteed to see a consistent state.
114   template <typename FunctionType>
115   void ForEachApp(FunctionType f) {
116     DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
117 
118     for (const auto& s_iter : states_) {
119       const apps::mojom::App* state = s_iter.second.get();
120 
121       auto d_iter = deltas_in_progress_.find(s_iter.first);
122       const apps::mojom::App* delta =
123           (d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
124 
125       f(apps::AppUpdate(state, delta, account_id_));
126     }
127 
128     for (const auto& d_iter : deltas_in_progress_) {
129       const apps::mojom::App* delta = d_iter.second;
130 
131       auto s_iter = states_.find(d_iter.first);
132       if (s_iter != states_.end()) {
133         continue;
134       }
135 
136       f(apps::AppUpdate(nullptr, delta, account_id_));
137     }
138   }
139 
140   // Calls f, a void-returning function whose arguments are (const
141   // apps::AppUpdate&), on the app in the cache with the given app_id. It will
142   // return true (and call f) if there is such an app, otherwise it will return
143   // false (and not call f). The AppUpdate argument to f has the same semantics
144   // as for ForEachApp, above.
145   //
146   // f must be synchronous, and if it asynchronously calls ForEachApp again,
147   // it's not guaranteed to see a consistent state.
148   template <typename FunctionType>
149   bool ForOneApp(const std::string& app_id, FunctionType f) {
150     DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
151 
152     auto s_iter = states_.find(app_id);
153     const apps::mojom::App* state =
154         (s_iter != states_.end()) ? s_iter->second.get() : nullptr;
155 
156     auto d_iter = deltas_in_progress_.find(app_id);
157     const apps::mojom::App* delta =
158         (d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
159 
160     if (state || delta) {
161       f(apps::AppUpdate(state, delta, account_id_));
162       return true;
163     }
164     return false;
165   }
166 
167  private:
168   void DoOnApps(std::vector<apps::mojom::AppPtr> deltas);
169 
170   base::ObserverList<Observer> observers_;
171 
172   // Maps from app_id to the latest state: the "sum" of all previous deltas.
173   std::map<std::string, apps::mojom::AppPtr> states_;
174 
175   // Track the deltas being processed or are about to be processed by OnApps.
176   // They are separate to manage the "notification and merging might be delayed
177   // until after OnApps returns" concern described above.
178   //
179   // OnApps calls DoOnApps zero or more times. If we're nested, so that there's
180   // multiple OnApps call to this AppRegistryCache in the call stack, the
181   // deeper OnApps call simply adds work to deltas_pending_ and returns without
182   // calling DoOnApps. If we're not nested, OnApps calls DoOnApps one or more
183   // times; "more times" happens if DoOnApps notifying observers leads to more
184   // OnApps calls that enqueue deltas_pending_ work. The deltas_in_progress_
185   // map (keyed by app_id) contains those deltas being considered by DoOnApps.
186   //
187   // Nested OnApps calls are expected to be rare (but still dealt with
188   // sensibly). In the typical case, OnApps should call DoOnApps exactly once,
189   // and deltas_pending_ will stay empty.
190   std::map<std::string, apps::mojom::App*> deltas_in_progress_;
191   std::vector<apps::mojom::AppPtr> deltas_pending_;
192 
193   AccountId account_id_;
194 
195   SEQUENCE_CHECKER(my_sequence_checker_);
196 
197   DISALLOW_COPY_AND_ASSIGN(AppRegistryCache);
198 };
199 
200 }  // namespace apps
201 
202 #endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_APP_REGISTRY_CACHE_H_
203