1// Copyright 2013 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 "components/policy/core/common/async_policy_loader.h"
6
7#include "base/bind.h"
8#include "base/location.h"
9#include "base/sequenced_task_runner.h"
10#include "components/policy/core/common/policy_bundle.h"
11
12using base::Time;
13using base::TimeDelta;
14
15namespace policy {
16
17namespace {
18
19// Amount of time to wait for the files on disk to settle before trying to load
20// them. This alleviates the problem of reading partially written files and
21// makes it possible to batch quasi-simultaneous changes.
22const int kSettleIntervalSeconds = 5;
23
24// The time interval for rechecking policy. This is the fallback in case the
25// implementation never detects changes.
26const int kReloadIntervalSeconds = 15 * 60;
27
28}  // namespace
29
30AsyncPolicyLoader::AsyncPolicyLoader(
31    scoped_refptr<base::SequencedTaskRunner> task_runner)
32    : task_runner_(task_runner),
33      weak_factory_(this) {}
34
35AsyncPolicyLoader::~AsyncPolicyLoader() {}
36
37Time AsyncPolicyLoader::LastModificationTime() {
38  return Time();
39}
40
41void AsyncPolicyLoader::Reload(bool force) {
42  DCHECK(task_runner_->RunsTasksOnCurrentThread());
43
44  TimeDelta delay;
45  Time now = Time::Now();
46  // Check if there was a recent modification to the underlying files.
47  if (!force && !IsSafeToReload(now, &delay)) {
48    ScheduleNextReload(delay);
49    return;
50  }
51
52  scoped_ptr<PolicyBundle> bundle(Load());
53
54  // Check if there was a modification while reading.
55  if (!force && !IsSafeToReload(now, &delay)) {
56    ScheduleNextReload(delay);
57    return;
58  }
59
60  // Filter out mismatching policies.
61  schema_map_->FilterBundle(bundle.get());
62
63  update_callback_.Run(bundle.Pass());
64  ScheduleNextReload(TimeDelta::FromSeconds(kReloadIntervalSeconds));
65}
66
67scoped_ptr<PolicyBundle> AsyncPolicyLoader::InitialLoad(
68    const scoped_refptr<SchemaMap>& schema_map) {
69  // This is the first load, early during startup. Use this to record the
70  // initial |last_modification_time_|, so that potential changes made before
71  // installing the watches can be detected.
72  last_modification_time_ = LastModificationTime();
73  schema_map_ = schema_map;
74  scoped_ptr<PolicyBundle> bundle(Load());
75  // Filter out mismatching policies.
76  schema_map_->FilterBundle(bundle.get());
77  return bundle.Pass();
78}
79
80void AsyncPolicyLoader::Init(const UpdateCallback& update_callback) {
81  DCHECK(task_runner_->RunsTasksOnCurrentThread());
82  DCHECK(update_callback_.is_null());
83  DCHECK(!update_callback.is_null());
84  update_callback_ = update_callback;
85
86  InitOnBackgroundThread();
87
88  // There might have been changes to the underlying files since the initial
89  // load and before the watchers have been created.
90  if (LastModificationTime() != last_modification_time_)
91    Reload(false);
92
93  // Start periodic refreshes.
94  ScheduleNextReload(TimeDelta::FromSeconds(kReloadIntervalSeconds));
95}
96
97void AsyncPolicyLoader::RefreshPolicies(scoped_refptr<SchemaMap> schema_map) {
98  DCHECK(task_runner_->RunsTasksOnCurrentThread());
99  schema_map_ = schema_map;
100  Reload(true);
101}
102
103void AsyncPolicyLoader::ScheduleNextReload(TimeDelta delay) {
104  DCHECK(task_runner_->RunsTasksOnCurrentThread());
105  weak_factory_.InvalidateWeakPtrs();
106  task_runner_->PostDelayedTask(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 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