1// Copyright (c) 2011 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#include "chrome/browser/policy/asynchronous_policy_loader.h"
6
7#include "base/message_loop.h"
8#include "base/task.h"
9#include "content/browser/browser_thread.h"
10
11namespace policy {
12
13AsynchronousPolicyLoader::AsynchronousPolicyLoader(
14    AsynchronousPolicyProvider::Delegate* delegate,
15    int reload_interval_minutes)
16    : delegate_(delegate),
17      reload_task_(NULL),
18      reload_interval_(base::TimeDelta::FromMinutes(reload_interval_minutes)),
19      origin_loop_(MessageLoop::current()),
20      stopped_(false) {}
21
22void AsynchronousPolicyLoader::Init() {
23  policy_.reset(delegate_->Load());
24  // Initialization can happen early when the file thread is not yet available,
25  // but the subclass of the loader must do some of their initialization on the
26  // file thread. Posting to the file thread directly before it is initialized
27  // will cause the task to be forgotten. Instead, post a task to the ui thread
28  // to delay the remainder of initialization until threading is fully
29  // initialized.
30  BrowserThread::PostTask(
31      BrowserThread::UI, FROM_HERE,
32      NewRunnableMethod(
33          this,
34          &AsynchronousPolicyLoader::InitAfterFileThreadAvailable));
35}
36
37void AsynchronousPolicyLoader::Stop() {
38  if (!stopped_) {
39    stopped_ = true;
40    delegate_.reset();
41    FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer,
42                      observer_list_,
43                      OnProviderGoingAway());
44    BrowserThread::PostTask(
45        BrowserThread::FILE, FROM_HERE,
46        NewRunnableMethod(this, &AsynchronousPolicyLoader::StopOnFileThread));
47  }
48}
49
50AsynchronousPolicyLoader::~AsynchronousPolicyLoader() {
51}
52
53// Manages the life cycle of a new policy map during until its life cycle is
54// taken over by the policy loader.
55class UpdatePolicyTask : public Task {
56 public:
57  UpdatePolicyTask(scoped_refptr<AsynchronousPolicyLoader> loader,
58                   DictionaryValue* new_policy)
59      : loader_(loader),
60        new_policy_(new_policy) {}
61
62  virtual void Run() {
63    loader_->UpdatePolicy(new_policy_.release());
64  }
65
66 private:
67  scoped_refptr<AsynchronousPolicyLoader> loader_;
68  scoped_ptr<DictionaryValue> new_policy_;
69  DISALLOW_COPY_AND_ASSIGN(UpdatePolicyTask);
70};
71
72void AsynchronousPolicyLoader::Reload() {
73  if (delegate_.get()) {
74    DictionaryValue* new_policy = delegate_->Load();
75    PostUpdatePolicyTask(new_policy);
76  }
77}
78
79void AsynchronousPolicyLoader::AddObserver(
80    ConfigurationPolicyProvider::Observer* observer) {
81  observer_list_.AddObserver(observer);
82}
83
84void AsynchronousPolicyLoader::RemoveObserver(
85    ConfigurationPolicyProvider::Observer* observer) {
86  observer_list_.RemoveObserver(observer);
87}
88
89void AsynchronousPolicyLoader::CancelReloadTask() {
90  if (reload_task_) {
91    // Only check the thread if there's still a reload task. During
92    // destruction of unit tests, the message loop destruction can
93    // call this method when the file thread is no longer around,
94    // but in that case reload_task_ is NULL.
95    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
96    reload_task_->Cancel();
97    reload_task_ = NULL;
98  }
99}
100
101void AsynchronousPolicyLoader::ScheduleReloadTask(
102    const base::TimeDelta& delay) {
103  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
104
105  CancelReloadTask();
106
107  reload_task_ =
108      NewRunnableMethod(this, &AsynchronousPolicyLoader::ReloadFromTask);
109  BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE, reload_task_,
110                                 delay.InMilliseconds());
111}
112
113void AsynchronousPolicyLoader::ScheduleFallbackReloadTask() {
114  // As a safeguard in case that the load delegate failed to timely notice a
115  // change in policy, schedule a reload task that'll make us recheck after a
116  // reasonable interval.
117  ScheduleReloadTask(reload_interval_);
118}
119
120void AsynchronousPolicyLoader::ReloadFromTask() {
121  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
122
123  // Drop the reference to the reload task, since the task might be the only
124  // referrer that keeps us alive, so we should not Cancel() it.
125  reload_task_ = NULL;
126
127  Reload();
128}
129
130void AsynchronousPolicyLoader::InitOnFileThread() {
131}
132
133void AsynchronousPolicyLoader::StopOnFileThread() {
134  CancelReloadTask();
135}
136
137void AsynchronousPolicyLoader::PostUpdatePolicyTask(
138    DictionaryValue* new_policy) {
139  origin_loop_->PostTask(FROM_HERE, new UpdatePolicyTask(this, new_policy));
140}
141
142void AsynchronousPolicyLoader::UpdatePolicy(DictionaryValue* new_policy_raw) {
143  scoped_ptr<DictionaryValue> new_policy(new_policy_raw);
144  DCHECK(policy_.get());
145  if (!policy_->Equals(new_policy.get())) {
146    policy_.reset(new_policy.release());
147    FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer,
148                      observer_list_,
149                      OnUpdatePolicy());
150  }
151}
152
153void AsynchronousPolicyLoader::InitAfterFileThreadAvailable() {
154  if (!stopped_) {
155    BrowserThread::PostTask(
156        BrowserThread::FILE, FROM_HERE,
157        NewRunnableMethod(this, &AsynchronousPolicyLoader::InitOnFileThread));
158  }
159}
160
161}  // namespace policy
162