1// Copyright (c) 2012 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/async_policy_loader.h"
6
7#include "base/bind.h"
8#include "chrome/browser/policy/policy_bundle.h"
9#include "chrome/browser/policy/policy_domain_descriptor.h"
10#include "content/public/browser/browser_thread.h"
11
12using base::Time;
13using base::TimeDelta;
14using content::BrowserThread;
15
16namespace policy {
17
18namespace {
19
20// Amount of time to wait for the files on disk to settle before trying to load
21// them. This alleviates the problem of reading partially written files and
22// makes it possible to batch quasi-simultaneous changes.
23const int kSettleIntervalSeconds = 5;
24
25// The time interval for rechecking policy. This is the fallback in case the
26// implementation never detects changes.
27const int kReloadIntervalSeconds = 15 * 60;
28
29}  // namespace
30
31AsyncPolicyLoader::AsyncPolicyLoader()
32    : weak_factory_(this) {}
33
34AsyncPolicyLoader::~AsyncPolicyLoader() {}
35
36base::Time AsyncPolicyLoader::LastModificationTime() {
37  return base::Time();
38}
39
40void AsyncPolicyLoader::Reload(bool force) {
41  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
42
43  TimeDelta delay;
44  Time now = Time::Now();
45  // Check if there was a recent modification to the underlying files.
46  if (!force && !IsSafeToReload(now, &delay)) {
47    ScheduleNextReload(delay);
48    return;
49  }
50
51  scoped_ptr<PolicyBundle> bundle(Load());
52
53  // Check if there was a modification while reading.
54  if (!force && !IsSafeToReload(now, &delay)) {
55    ScheduleNextReload(delay);
56    return;
57  }
58
59  // Filter out mismatching policies.
60  for (DescriptorMap::iterator it = descriptor_map_.begin();
61       it != descriptor_map_.end(); ++it) {
62    it->second->FilterBundle(bundle.get());
63  }
64
65  update_callback_.Run(bundle.Pass());
66  ScheduleNextReload(TimeDelta::FromSeconds(kReloadIntervalSeconds));
67}
68
69void AsyncPolicyLoader::RegisterPolicyDomain(
70    scoped_refptr<const PolicyDomainDescriptor> descriptor) {
71  if (descriptor->domain() != POLICY_DOMAIN_CHROME) {
72    descriptor_map_[descriptor->domain()] = descriptor;
73    Reload(true);
74  }
75}
76
77scoped_ptr<PolicyBundle> AsyncPolicyLoader::InitialLoad() {
78  // This is the first load, early during startup. Use this to record the
79  // initial |last_modification_time_|, so that potential changes made before
80  // installing the watches can be detected.
81  last_modification_time_ = LastModificationTime();
82  return Load();
83}
84
85void AsyncPolicyLoader::Init(const UpdateCallback& update_callback) {
86  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
87  DCHECK(update_callback_.is_null());
88  DCHECK(!update_callback.is_null());
89  update_callback_ = update_callback;
90
91  InitOnFile();
92
93  // There might have been changes to the underlying files since the initial
94  // load and before the watchers have been created.
95  if (LastModificationTime() != last_modification_time_)
96    Reload(false);
97
98  // Start periodic refreshes.
99  ScheduleNextReload(TimeDelta::FromSeconds(kReloadIntervalSeconds));
100}
101
102void AsyncPolicyLoader::ScheduleNextReload(TimeDelta delay) {
103  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
104  weak_factory_.InvalidateWeakPtrs();
105  BrowserThread::PostDelayedTask(
106      BrowserThread::FILE, FROM_HERE,
107      base::Bind(&AsyncPolicyLoader::Reload,
108                 weak_factory_.GetWeakPtr(),
109                 false  /* force */),
110      delay);
111}
112
113bool AsyncPolicyLoader::IsSafeToReload(const Time& now, TimeDelta* delay) {
114  Time last_modification = LastModificationTime();
115  if (last_modification.is_null())
116    return true;
117
118  // If there was a change since the last recorded modification, wait some more.
119  const TimeDelta kSettleInterval(
120      TimeDelta::FromSeconds(kSettleIntervalSeconds));
121  if (last_modification != last_modification_time_) {
122    last_modification_time_ = last_modification;
123    last_modification_clock_ = now;
124    *delay = kSettleInterval;
125    return false;
126  }
127
128  // Check whether the settle interval has elapsed.
129  const base::TimeDelta age = now - last_modification_clock_;
130  if (age < kSettleInterval) {
131    *delay = kSettleInterval - age;
132    return false;
133  }
134
135  return true;
136}
137
138}  // namespace policy
139