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.
5 #import "ios/chrome/browser/updatable_config/updatable_config_base.h"
7 #include "base/logging.h"
8 #import "base/mac/bind_objc_block.h"
9 #include "base/mac/scoped_nsobject.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/memory/scoped_ptr.h"
12 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
13 #import "ios/public/provider/chrome/browser/updatable_resource_provider.h"
14 #include "ios/web/public/web_thread.h"
15 #import "net/base/mac/url_conversions.h"
16 #include "net/http/http_status_code.h"
17 #include "net/url_request/url_fetcher.h"
18 #include "net/url_request/url_fetcher_delegate.h"
19 #include "net/url_request/url_request_context.h"
20 #include "net/url_request/url_request_context_getter.h"
23 @interface UpdatableConfigBase ()
24 // Returns the application ID to use for fetching updatable configuration.
25 + (NSString*)defaultAppId;
26 // Fetches server-side updatable configuration plist.
29 // A method that will be executed on a delay if -startUpdate: was NOT called.
30 - (void)startUpdateNotCalled:(id)config;
31 // Schedules a call to -startUpdateNotCalled: for later to make sure that
32 // -startUpdate: will be called.
33 - (void)scheduleConsistencyCheck;
34 // Cancels the delayed call to -startUpdateNotCalled:.
35 - (void)cancelConsistencyCheck;
36 #endif // !defined(NDEBUG)
42 // Global flag to enable or disable debug check that -startUpdate:
44 BOOL g_consistency_check_enabled = NO;
47 // Periodically fetch configuration updates from server.
48 const int64_t kPeriodicCheckInNanoseconds = 60 * 60 * 24 * NSEC_PER_SEC;
50 // Class to fetch config update |url| and also act as the delegate to
51 // handle callbacks from URLFetcher.
52 class ConfigFetcher : public net::URLFetcherDelegate {
54 ConfigFetcher(UpdatableConfigBase* owner,
55 id<UpdatableResourceDescriptorBridge> descriptor)
56 : owner_(owner), descriptor_(descriptor) {}
58 // Starts fetching |url| for updated configuration.
59 void Fetch(const GURL& url, net::URLRequestContextGetter* context) {
60 fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
61 fetcher_->SetRequestContext(context);
65 void OnURLFetchComplete(const net::URLFetcher* fetcher) override {
66 DCHECK_EQ(fetcher_, fetcher);
67 NSData* responseData = nil;
68 if (fetcher_->GetResponseCode() == net::HTTP_OK) {
70 fetcher_->GetResponseAsString(&response);
72 [NSData dataWithBytes:response.c_str() length:response.length()];
75 // If data was fetched, write the fetched data to local store in a
76 // separate thread. Then update the resource descriptor that configuration
77 // update is completed. Finally, schedule the next update check.
78 web::WebThread::PostBlockingPoolTask(FROM_HERE, base::BindBlock(^{
79 BOOL updateSuccess = NO;
81 NSString* path = [descriptor_ updateResourcePath];
82 updateSuccess = [responseData writeToFile:path atomically:YES];
84 dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^() {
85 [descriptor_ updateCheckDidFinishWithSuccess:updateSuccess];
88 dispatch_time(DISPATCH_TIME_NOW, kPeriodicCheckInNanoseconds),
89 dispatch_get_main_queue(), ^{
96 UpdatableConfigBase* owner_;
97 id<UpdatableResourceDescriptorBridge> descriptor_;
98 scoped_ptr<net::URLFetcher> fetcher_;
103 @implementation UpdatableConfigBase {
104 base::scoped_nsprotocol<id<UpdatableResourceBridge>> _updatableResource;
105 scoped_ptr<ConfigFetcher> _configFetcher;
106 scoped_refptr<net::URLRequestContextGetter> _requestContextGetter;
109 + (void)enableConsistencyCheck {
111 g_consistency_check_enabled = YES;
115 // Overrides default designated initializer.
116 - (instancetype)initWithAppId:(NSString*)appId
117 version:(NSString*)appVersion
118 plist:(NSString*)plistName {
121 _updatableResource.reset([self newResource:plistName]);
122 // UpdatableResourceBridge initializes the appId to what is in the
123 // application bundle. The following overrides that with either the |appId|
124 // passed in or a default based on experimental settings if |appId| is nil.
126 appId = [UpdatableConfigBase defaultAppId];
127 [[_updatableResource descriptor] setApplicationIdentifier:appId];
128 // Overrides the default application version if necessary.
130 [[_updatableResource descriptor] setApplicationVersion:appVersion];
131 // [UpdatableResourceBridge -loadDefaults] updates the resource in
132 // two phases and is probably not MT safe. However,
133 // initWithAppId:version:plist: is called from a singleton's
134 // initialization loop and thus will not be called more than once.
135 // TODO(pkl): -loadDefaults accesses the file system to load in the
136 // plist. This should be done via PostBlockingPoolTask.
137 [_updatableResource loadDefaults];
139 NSString* notificationName = ios::GetChromeBrowserProvider()
140 ->GetUpdatableResourceProvider()
141 ->GetUpdateNotificationName();
142 [[NSNotificationCenter defaultCenter]
144 selector:@selector(resourceDidUpdate:)
145 name:notificationName
146 object:[_updatableResource descriptor]];
149 [self scheduleConsistencyCheck];
155 - (instancetype)init {
161 [[NSNotificationCenter defaultCenter] removeObserver:self];
163 [self cancelConsistencyCheck];
168 - (void)startUpdate:(net::URLRequestContextGetter*)requestContextGetter {
170 [self cancelConsistencyCheck];
172 _requestContextGetter = requestContextGetter;
176 - (void)stopUpdateChecks {
177 _requestContextGetter = nullptr;
180 - (void)resourceDidUpdate:(NSNotification*)notification {
181 id sender = [notification object];
182 DCHECK([_updatableResource descriptor] == sender);
186 #pragma mark For Subclasses
188 - (id<UpdatableResourceBridge>)newResource:(NSString*)resourceName {
189 // Subclasses must override this factory method.
194 - (id<UpdatableResourceBridge>)updatableResource {
195 return _updatableResource.get();
199 #pragma mark For Debug Compilations
202 - (void)scheduleConsistencyCheck {
203 if (!g_consistency_check_enabled)
205 // Sets a delayed call that will cause a DCHECK if -startUpdate:
207 [self performSelector:@selector(startUpdateNotCalled:)
212 - (void)cancelConsistencyCheck {
213 if (!g_consistency_check_enabled)
215 // Cancels the delayed error check since -startUpdate: has been called.
216 // Added for completeness since singletons should never be deallocated.
218 cancelPreviousPerformRequestsWithTarget:self
219 selector:@selector(startUpdateNotCalled:)
223 - (void)startUpdateNotCalled:(id)config {
224 DCHECK(self == config);
225 DCHECK(g_consistency_check_enabled);
226 // Make sure that |startUpdate:| was called for this config.
227 NOTREACHED() << "startUpdate: was not called for "
228 << [[self description] UTF8String];
230 #endif // !defined(NDEBUG)
233 #pragma mark For Unit Testing
235 - (void)setUpdatableResource:(id<UpdatableResourceBridge>)resource {
236 _updatableResource.reset([resource retain]);
242 + (NSString*)defaultAppId {
243 // During development and dogfooding, allow a different configuration
244 // update file to be used for testing.
245 NSString* flag = [[NSUserDefaults standardUserDefaults]
246 stringForKey:@"UpdatableConfigLocation"];
248 if ([flag isEqualToString:@"Stable"])
249 return @"com.google.chrome.ios";
250 else if ([flag isEqualToString:@"Dogfood"])
251 return @"com.google.chrome.ios.beta";
252 else if ([flag isEqualToString:@"None"])
253 return @"this.does.not.update";
255 return [[NSBundle mainBundle] bundleIdentifier];
258 - (void)checkUpdate {
259 if (!_requestContextGetter.get())
261 if (!_configFetcher) {
262 _configFetcher.reset(
263 new ConfigFetcher(self, [_updatableResource descriptor]));
265 GURL url = net::GURLWithNSURL([[_updatableResource descriptor] updateURL]);
266 _configFetcher->Fetch(url, _requestContextGetter.get());