Index: ios/chrome/browser/omaha/omaha_service.mm |
diff --git a/ios/chrome/browser/omaha/omaha_service.mm b/ios/chrome/browser/omaha/omaha_service.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ea46a95678461572b408983fc866a0de81201080 |
--- /dev/null |
+++ b/ios/chrome/browser/omaha/omaha_service.mm |
@@ -0,0 +1,710 @@ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ios/chrome/browser/omaha/omaha_service.h" |
+ |
+#include <Foundation/Foundation.h> |
+ |
+#include <memory> |
+#include <utility> |
+ |
+#include "base/bind.h" |
+#include "base/i18n/time_formatting.h" |
+#include "base/ios/device_util.h" |
+#include "base/logging.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/metrics/field_trial.h" |
+#include "base/rand_util.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "base/sys_info.h" |
+#include "base/time/time.h" |
+#include "base/values.h" |
+#include "components/metrics/metrics_pref_names.h" |
+#include "components/prefs/pref_service.h" |
+#include "components/version_info/version_info.h" |
+#include "ios/chrome/browser/application_context.h" |
+#include "ios/chrome/browser/arch_util.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h" |
+#include "ios/chrome/browser/browser_state_metrics/browser_state_metrics.h" |
+#include "ios/chrome/browser/install_time_util.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#include "ios/chrome/browser/upgrade/upgrade_recommended_details.h" |
+#include "ios/chrome/common/channel_info.h" |
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#include "ios/public/provider/chrome/browser/omaha/omaha_service_provider.h" |
+#include "ios/public/provider/chrome/browser/omaha/omaha_xml_writer.h" |
+#include "ios/web/public/web_thread.h" |
+#include "libxml/xmlwriter.h" |
+#include "net/base/backoff_entry.h" |
+#include "net/base/load_flags.h" |
+#include "net/url_request/url_fetcher.h" |
+#include "url/gurl.h" |
+ |
+namespace { |
+// Number of hours to wait between successful requests. |
+const int kHoursBetweenRequests = 5; |
+// Minimal time to wait between retry requests. |
+const CFTimeInterval kPostRetryBaseSeconds = 3600; |
+// Maximal time to wait between retry requests. |
+const CFTimeInterval kPostRetryMaxSeconds = 6 * kPostRetryBaseSeconds; |
+ |
+// Default last sent application version when none has been sent yet. |
+const char kDefaultLastSentVersion[] = "0.0.0.0"; |
+ |
+// Key for saving states in the UserDefaults. |
+NSString* const kNextTriesTimesKey = @"ChromeOmahaServiceNextTries"; |
+NSString* const kCurrentPingKey = @"ChromeOmahaServiceCurrentPing"; |
+NSString* const kNumberTriesKey = @"ChromeOmahaServiceNumberTries"; |
+NSString* const kLastSentVersionKey = @"ChromeOmahaServiceLastSentVersion"; |
+NSString* const kLastSentTimeKey = @"ChromeOmahaServiceLastSentTime"; |
+NSString* const kRetryRequestIdKey = @"ChromeOmahaServiceRetryRequestId"; |
+ |
+class XmlWrapper : public OmahaXmlWriter { |
+ public: |
+ XmlWrapper() |
+ : buffer_(xmlBufferCreate()), |
+ writer_(xmlNewTextWriterMemory(buffer_, /* compression */ 0)) { |
+ DCHECK(buffer_); |
+ DCHECK(writer_); |
+ } |
+ |
+ ~XmlWrapper() override { |
+ xmlFreeTextWriter(writer_); |
+ xmlBufferFree(buffer_); |
+ } |
+ |
+ void StartElement(const char* name) override { |
+ DCHECK(name); |
+ int result = xmlTextWriterStartElement( |
+ writer_, reinterpret_cast<const xmlChar*>(name)); |
+ DCHECK_GE(result, 0); |
+ } |
+ |
+ void EndElement() override { |
+ int result = xmlTextWriterEndElement(writer_); |
+ DCHECK_GE(result, 0); |
+ } |
+ |
+ void WriteAttribute(const char* name, const char* value) override { |
+ DCHECK(name); |
+ int result = xmlTextWriterWriteAttribute( |
+ writer_, reinterpret_cast<const xmlChar*>(name), |
+ reinterpret_cast<const xmlChar*>(value)); |
+ DCHECK_GE(result, 0); |
+ } |
+ |
+ void Finalize() override { |
+ int result = xmlTextWriterEndDocument(writer_); |
+ DCHECK_GE(result, 0); |
+ } |
+ |
+ std::string GetContentAsString() override { |
+ return std::string(reinterpret_cast<char*>(buffer_->content)); |
+ } |
+ |
+ private: |
+ xmlBufferPtr buffer_; |
+ xmlTextWriterPtr writer_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(XmlWrapper); |
+}; |
+ |
+} // namespace |
+ |
+#pragma mark - |
+ |
+// XML parser for the server response. |
+@interface ResponseParser : NSObject<NSXMLParserDelegate> { |
+ BOOL hasError_; |
+ BOOL responseIsParsed_; |
+ BOOL appIsParsed_; |
+ BOOL updateCheckIsParsed_; |
+ BOOL urlIsParsed_; |
+ BOOL manifestIsParsed_; |
+ BOOL pingIsParsed_; |
+ BOOL eventIsParsed_; |
+ base::scoped_nsobject<NSString> appId_; |
+ std::unique_ptr<UpgradeRecommendedDetails> updateInformation_; |
+} |
+ |
+// Initialization method. |appId| is the application id one expects to find in |
+// the response message. |
+- (instancetype)initWithAppId:(NSString*)appId; |
+ |
+// Returns YES if the message has been correctly parsed. |
+- (BOOL)isCorrect; |
+ |
+// If an upgrade is possible, returns the details of the notification to send. |
+// Otherwise, return NULL. |
+- (UpgradeRecommendedDetails*)upgradeRecommendedDetails; |
+ |
+@end |
+ |
+@implementation ResponseParser |
+ |
+- (instancetype)initWithAppId:(NSString*)appId { |
+ if (self = [super init]) { |
+ appId_.reset([appId retain]); |
+ } |
+ return self; |
+} |
+ |
+- (BOOL)isCorrect { |
+ // A response should have either a ping ACK or an event ACK, depending on the |
+ // contents of the request. |
+ return !hasError_ && (pingIsParsed_ || eventIsParsed_); |
+} |
+ |
+- (UpgradeRecommendedDetails*)upgradeRecommendedDetails { |
+ return updateInformation_.get(); |
+} |
+ |
+// This method is parsing a message with the following type: |
+// <response...> |
+// <daystart.../> |
+// <app...> |
+// <updatecheck status="ok"> |
+// <urls> |
+// <url codebase="???"/> |
+// </urls> |
+// <manifest version="???"> |
+// <packages> |
+// <package hash="0" name="Chrome" required="true" size="0"/> |
+// </packages> |
+// <actions> |
+// <action event="update" run="Chrome"/> |
+// <action event="postinstall"/> |
+// </actions> |
+// </manifest> |
+// </updatecheck> |
+// <ping.../> |
+// </app> |
+// </response> |
+// --- OR --- |
+// <response...> |
+// <daystart.../> |
+// <app...> |
+// <event.../> |
+// </app> |
+// </response> |
+// See http://code.google.com/p/omaha/wiki/ServerProtocol for details. |
+- (void)parser:(NSXMLParser*)parser |
+ didStartElement:(NSString*)elementName |
+ namespaceURI:(NSString*)namespaceURI |
+ qualifiedName:(NSString*)qualifiedName |
+ attributes:(NSDictionary*)attributeDict { |
+ if (hasError_) |
+ return; |
+ |
+ // Array of uninteresting tags in the Omaha xml response. |
+ NSArray* ignoredTagNames = |
+ @[ @"action", @"actions", @"daystart", @"package", @"packages", @"urls" ]; |
+ if ([ignoredTagNames containsObject:elementName]) |
+ return; |
+ |
+ if (!responseIsParsed_) { |
+ if ([elementName isEqualToString:@"response"] && |
+ [[attributeDict valueForKey:@"protocol"] isEqualToString:@"3.0"] && |
+ [[attributeDict valueForKey:@"server"] isEqualToString:@"prod"]) { |
+ responseIsParsed_ = YES; |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else if (!appIsParsed_) { |
+ if ([elementName isEqualToString:@"app"] && |
+ [[attributeDict valueForKey:@"status"] isEqualToString:@"ok"] && |
+ [[attributeDict valueForKey:@"appid"] isEqualToString:appId_]) { |
+ appIsParsed_ = YES; |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else if (!eventIsParsed_ && !updateCheckIsParsed_) { |
+ if ([elementName isEqualToString:@"updatecheck"]) { |
+ updateCheckIsParsed_ = YES; |
+ NSString* status = [attributeDict valueForKey:@"status"]; |
+ if ([status isEqualToString:@"noupdate"]) { |
+ // No update is available on the Market, so we won't get a <url> or |
+ // <manifest> tag. |
+ urlIsParsed_ = YES; |
+ manifestIsParsed_ = YES; |
+ } else if ([status isEqualToString:@"ok"]) { |
+ updateInformation_ = base::MakeUnique<UpgradeRecommendedDetails>(); |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else if ([elementName isEqualToString:@"event"]) { |
+ if ([[attributeDict valueForKey:@"status"] isEqualToString:@"ok"]) { |
+ eventIsParsed_ = YES; |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else if (!urlIsParsed_) { |
+ if ([elementName isEqualToString:@"url"] && |
+ [[attributeDict valueForKey:@"codebase"] length] > 0) { |
+ urlIsParsed_ = YES; |
+ DCHECK(updateInformation_); |
+ NSString* url = [attributeDict valueForKey:@"codebase"]; |
+ if ([[url substringFromIndex:([url length] - 1)] isEqualToString:@"/"]) |
+ url = [url substringToIndex:([url length] - 1)]; |
+ updateInformation_.get()->upgrade_url = |
+ GURL(base::SysNSStringToUTF8(url)); |
+ if (!updateInformation_.get()->upgrade_url.is_valid()) |
+ hasError_ = YES; |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else if (!manifestIsParsed_) { |
+ if ([elementName isEqualToString:@"manifest"] && |
+ [attributeDict valueForKey:@"version"]) { |
+ manifestIsParsed_ = YES; |
+ DCHECK(updateInformation_); |
+ updateInformation_.get()->next_version = |
+ base::SysNSStringToUTF8([attributeDict valueForKey:@"version"]); |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else if (!pingIsParsed_) { |
+ if ([elementName isEqualToString:@"ping"] && |
+ [[attributeDict valueForKey:@"status"] isEqualToString:@"ok"]) { |
+ pingIsParsed_ = YES; |
+ } else { |
+ hasError_ = YES; |
+ } |
+ } else { |
+ hasError_ = YES; |
+ } |
+} |
+ |
+@end |
+ |
+// static |
+OmahaService* OmahaService::GetInstance() { |
+ return base::Singleton<OmahaService>::get(); |
+} |
+ |
+// static |
+void OmahaService::Start(net::URLRequestContextGetter* request_context_getter, |
+ const UpgradeRecommendedCallback& callback) { |
+ DCHECK(request_context_getter); |
+ DCHECK(!callback.is_null()); |
+ OmahaService* result = GetInstance(); |
+ result->set_upgrade_recommended_callback(callback); |
+ // This should only be called once. |
+ DCHECK(!result->request_context_getter_); |
+ result->request_context_getter_ = request_context_getter; |
+ result->locale_lang_ = GetApplicationContext()->GetApplicationLocale(); |
+ web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
+ base::Bind(&OmahaService::SendOrScheduleNextPing, |
+ base::Unretained(result))); |
+} |
+ |
+OmahaService::OmahaService() |
+ : request_context_getter_(NULL), |
+ schedule_(true), |
+ application_install_date_(0), |
+ sending_install_event_(false) { |
+ Initialize(); |
+} |
+ |
+OmahaService::OmahaService(bool schedule) |
+ : request_context_getter_(NULL), |
+ schedule_(schedule), |
+ application_install_date_(0), |
+ sending_install_event_(false) { |
+ Initialize(); |
+} |
+ |
+OmahaService::~OmahaService() {} |
+ |
+void OmahaService::Initialize() { |
+ // Initialize the provider at the same time as the rest of the service. |
+ ios::GetChromeBrowserProvider()->GetOmahaServiceProvider()->Initialize(); |
+ |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ next_tries_time_ = base::Time::FromCFAbsoluteTime( |
+ [defaults doubleForKey:kNextTriesTimesKey]); |
+ current_ping_time_ = |
+ base::Time::FromCFAbsoluteTime([defaults doubleForKey:kCurrentPingKey]); |
+ number_of_tries_ = [defaults integerForKey:kNumberTriesKey]; |
+ last_sent_time_ = |
+ base::Time::FromCFAbsoluteTime([defaults doubleForKey:kLastSentTimeKey]); |
+ NSString* lastSentVersion = [defaults stringForKey:kLastSentVersionKey]; |
+ if (lastSentVersion) { |
+ last_sent_version_ = |
+ base::Version(base::SysNSStringToUTF8(lastSentVersion)); |
+ } else { |
+ last_sent_version_ = base::Version(kDefaultLastSentVersion); |
+ } |
+ |
+ application_install_date_ = |
+ GetApplicationContext()->GetLocalState()->GetInt64( |
+ metrics::prefs::kInstallDate); |
+ DCHECK(application_install_date_); |
+ |
+ // Whether data should be persisted again to the user preferences. |
+ bool persist_again = false; |
+ |
+ base::Time now = base::Time::Now(); |
+ // If |last_sent_time_| is in the future, the clock has been tampered with. |
+ // Reset |last_sent_time_| to now. |
+ if (last_sent_time_ > now) { |
+ last_sent_time_ = now; |
+ persist_again = true; |
+ } |
+ |
+ // If the |next_tries_time_| is more than kHoursBetweenRequests hours away, |
+ // there is a possibility that the clock has been tampered with. Reschedule |
+ // the ping to be the usual interval after the last successful one. |
+ if (next_tries_time_ - now > |
+ base::TimeDelta::FromHours(kHoursBetweenRequests)) { |
+ next_tries_time_ = |
+ last_sent_time_ + base::TimeDelta::FromHours(kHoursBetweenRequests); |
+ persist_again = true; |
+ } |
+ |
+ // Fire a ping as early as possible if the version changed. |
+ base::Version current_version(version_info::GetVersionNumber()); |
+ if (last_sent_version_ < current_version) { |
+ next_tries_time_ = base::Time::Now() - base::TimeDelta::FromSeconds(1); |
+ number_of_tries_ = 0; |
+ persist_again = true; |
+ } |
+ |
+ if (persist_again) |
+ PersistStates(); |
+} |
+ |
+// static |
+void OmahaService::GetDebugInformation( |
+ const base::Callback<void(base::DictionaryValue*)> callback) { |
+ web::WebThread::PostTask( |
+ web::WebThread::IO, FROM_HERE, |
+ base::Bind(&OmahaService::GetDebugInformationOnIOThread, |
+ base::Unretained(GetInstance()), callback)); |
+} |
+ |
+// static |
+base::TimeDelta OmahaService::GetBackOff(uint8_t number_of_tries) { |
+ // Configuration for the service exponential backoff |
+ static net::BackoffEntry::Policy kBackoffPolicy = { |
+ 0, // num_errors_to_ignore |
+ kPostRetryBaseSeconds * 1000, // initial_delay_ms |
+ 2.0, // multiply_factor |
+ 0.1, // jitter_factor |
+ kPostRetryMaxSeconds * 1000, // maximum_backoff_ms |
+ -1, // entry_lifetime_ms |
+ false // always_use_initial_delay |
+ }; |
+ |
+ net::BackoffEntry backoff_entry(&kBackoffPolicy); |
+ for (int i = 0; i < number_of_tries; ++i) { |
+ backoff_entry.InformOfRequest(false); |
+ } |
+ |
+ return backoff_entry.GetTimeUntilRelease(); |
+} |
+ |
+std::string OmahaService::GetPingContent(const std::string& requestId, |
+ const std::string& sessionId, |
+ const std::string& versionName, |
+ const std::string& channelName, |
+ const base::Time& installationTime, |
+ PingContent pingContent) { |
+ OmahaServiceProvider* provider = |
+ ios::GetChromeBrowserProvider()->GetOmahaServiceProvider(); |
+ |
+ XmlWrapper xml_wrapper; |
+ xml_wrapper.StartElement("request"); |
+ xml_wrapper.WriteAttribute("protocol", "3.0"); |
+ xml_wrapper.WriteAttribute("version", "iOS-1.0.0.0"); |
+ xml_wrapper.WriteAttribute("ismachine", "1"); |
+ xml_wrapper.WriteAttribute("requestid", requestId.c_str()); |
+ xml_wrapper.WriteAttribute("sessionid", sessionId.c_str()); |
+ provider->AppendExtraAttributes("request", &xml_wrapper); |
+ xml_wrapper.WriteAttribute("hardware_class", |
+ ios::device_util::GetPlatform().c_str()); |
+ // Set up <os platform="ios"... /> |
+ xml_wrapper.StartElement("os"); |
+ xml_wrapper.WriteAttribute("platform", "ios"); |
+ xml_wrapper.WriteAttribute("version", |
+ base::SysInfo::OperatingSystemVersion().c_str()); |
+ xml_wrapper.WriteAttribute("arch", arch_util::kCurrentArch); |
+ xml_wrapper.EndElement(); |
+ |
+ bool is_first_install = |
+ pingContent == INSTALL_EVENT && |
+ last_sent_version_ == base::Version(kDefaultLastSentVersion); |
+ |
+ // Set up <app version="" ...> |
+ xml_wrapper.StartElement("app"); |
+ if (pingContent == INSTALL_EVENT) { |
+ std::string previous_version = |
+ is_first_install ? "" : last_sent_version_.GetString(); |
+ xml_wrapper.WriteAttribute("version", previous_version.c_str()); |
+ xml_wrapper.WriteAttribute("nextversion", versionName.c_str()); |
+ } else { |
+ xml_wrapper.WriteAttribute("version", versionName.c_str()); |
+ xml_wrapper.WriteAttribute("nextversion", ""); |
+ } |
+ xml_wrapper.WriteAttribute("lang", locale_lang_.c_str()); |
+ xml_wrapper.WriteAttribute("brand", provider->GetBrandCode().c_str()); |
+ xml_wrapper.WriteAttribute("client", ""); |
+ std::string application_id = provider->GetApplicationID(); |
+ xml_wrapper.WriteAttribute("appid", application_id.c_str()); |
+ std::string install_age; |
+ if (is_first_install) { |
+ install_age = "-1"; |
+ } else if (!installationTime.is_null() && |
+ installationTime.ToTimeT() != |
+ install_time_util::kUnknownInstallDate) { |
+ install_age = base::StringPrintf( |
+ "%d", (base::Time::Now() - installationTime).InDays()); |
+ } |
+ provider->AppendExtraAttributes("app", &xml_wrapper); |
+ // If the install date is unknown, send nothing. |
+ if (!install_age.empty()) |
+ xml_wrapper.WriteAttribute("installage", install_age.c_str()); |
+ |
+ if (pingContent == INSTALL_EVENT) { |
+ // Add an install complete event. |
+ xml_wrapper.StartElement("event"); |
+ if (is_first_install) { |
+ xml_wrapper.WriteAttribute("eventtype", "2"); // install |
+ } else { |
+ xml_wrapper.WriteAttribute("eventtype", "3"); // update |
+ } |
+ xml_wrapper.WriteAttribute("eventresult", "1"); // succeeded |
+ xml_wrapper.EndElement(); |
+ } else { |
+ // Set up <updatecheck/> |
+ xml_wrapper.StartElement("updatecheck"); |
+ xml_wrapper.WriteAttribute("tag", channelName.c_str()); |
+ xml_wrapper.EndElement(); |
+ |
+ // Set up <ping active=1/> |
+ xml_wrapper.StartElement("ping"); |
+ xml_wrapper.WriteAttribute("active", "1"); |
+ xml_wrapper.EndElement(); |
+ } |
+ |
+ // End app. |
+ xml_wrapper.EndElement(); |
+ // End request. |
+ xml_wrapper.EndElement(); |
+ |
+ xml_wrapper.Finalize(); |
+ return xml_wrapper.GetContentAsString(); |
+} |
+ |
+std::string OmahaService::GetCurrentPingContent() { |
+ base::Version current_version(version_info::GetVersionNumber()); |
+ sending_install_event_ = last_sent_version_ < current_version; |
+ PingContent ping_content = |
+ sending_install_event_ ? INSTALL_EVENT : USAGE_PING; |
+ |
+ // An install retry ping only makes sense if an install event must be send. |
+ DCHECK(sending_install_event_ || !IsNextPingInstallRetry()); |
+ std::string request_id = GetNextPingRequestId(ping_content); |
+ return GetPingContent(request_id, ios::device_util::GetRandomId(), |
+ version_info::GetVersionNumber(), GetChannelString(), |
+ base::Time::FromTimeT(application_install_date_), |
+ ping_content); |
+} |
+ |
+void OmahaService::SendPing() { |
+ // Check that no request is in progress. |
+ DCHECK(!fetcher_); |
+ |
+ GURL url(ios::GetChromeBrowserProvider() |
+ ->GetOmahaServiceProvider() |
+ ->GetUpdateServerURL()); |
+ if (!url.is_valid()) { |
+ return; |
+ } |
+ |
+ fetcher_ = net::URLFetcher::Create(0, url, net::URLFetcher::POST, this); |
+ fetcher_->SetRequestContext(request_context_getter_); |
+ fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
+ net::LOAD_DO_NOT_SAVE_COOKIES); |
+ fetcher_->SetUploadData("text/xml", GetCurrentPingContent()); |
+ |
+ // If this is not the first try, notify the omaha server. |
+ if (number_of_tries_ && IsNextPingInstallRetry()) { |
+ fetcher_->SetExtraRequestHeaders(base::StringPrintf( |
+ "X-RequestAge: %lld", |
+ (base::Time::Now() - current_ping_time_).InSeconds())); |
+ } |
+ |
+ // Update last fail time and number of tries, so that if anything fails |
+ // catastrophically, the fail is taken into account. |
+ if (number_of_tries_ < 30) |
+ ++number_of_tries_; |
+ next_tries_time_ = base::Time::Now() + GetBackOff(number_of_tries_); |
+ PersistStates(); |
+ |
+ fetcher_->Start(); |
+} |
+ |
+void OmahaService::SendOrScheduleNextPing() { |
+ base::Time now = base::Time::Now(); |
+ if (next_tries_time_ <= now) { |
+ SendPing(); |
+ return; |
+ } |
+ if (schedule_) { |
+ timer_.Start(FROM_HERE, next_tries_time_ - now, this, |
+ &OmahaService::SendPing); |
+ } |
+} |
+ |
+void OmahaService::PersistStates() { |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ |
+ [defaults setDouble:next_tries_time_.ToCFAbsoluteTime() |
+ forKey:kNextTriesTimesKey]; |
+ [defaults setDouble:current_ping_time_.ToCFAbsoluteTime() |
+ forKey:kCurrentPingKey]; |
+ [defaults setDouble:last_sent_time_.ToCFAbsoluteTime() |
+ forKey:kLastSentTimeKey]; |
+ [defaults setInteger:number_of_tries_ forKey:kNumberTriesKey]; |
+ [defaults setObject:base::SysUTF8ToNSString(last_sent_version_.GetString()) |
+ forKey:kLastSentVersionKey]; |
+ |
+ // Save critical state information for usage reporting. |
+ [defaults synchronize]; |
+} |
+ |
+void OmahaService::OnURLFetchComplete(const net::URLFetcher* fetcher) { |
+ DCHECK(fetcher_.get() == fetcher); |
+ // Transfer the ownership of fetcher_ to this method. |
+ std::unique_ptr<net::URLFetcher> local_fetcher = std::move(fetcher_); |
+ |
+ if (fetcher->GetResponseCode() != 200) { |
+ DLOG(WARNING) << "Error contacting the Omaha server"; |
+ SendOrScheduleNextPing(); |
+ return; |
+ } |
+ |
+ std::string response; |
+ bool result = fetcher->GetResponseAsString(&response); |
+ DCHECK(result); |
+ NSData* xml = [NSData dataWithBytes:response.data() length:response.length()]; |
+ base::scoped_nsobject<NSXMLParser> parser( |
+ [[NSXMLParser alloc] initWithData:xml]); |
+ const std::string application_id = ios::GetChromeBrowserProvider() |
+ ->GetOmahaServiceProvider() |
+ ->GetApplicationID(); |
+ base::scoped_nsobject<ResponseParser> delegate([[ResponseParser alloc] |
+ initWithAppId:base::SysUTF8ToNSString(application_id)]); |
+ parser.get().delegate = delegate.get(); |
+ |
+ if (![parser parse] || ![delegate isCorrect]) { |
+ DLOG(ERROR) << "Unable to parse XML response from Omaha server."; |
+ SendOrScheduleNextPing(); |
+ return; |
+ } |
+ // Handle success. |
+ number_of_tries_ = 0; |
+ // Schedule the next request. If requset that just finished was an install |
+ // notification, send an active ping immediately. |
+ next_tries_time_ = sending_install_event_ |
+ ? base::Time::Now() |
+ : base::Time::Now() + base::TimeDelta::FromHours( |
+ kHoursBetweenRequests); |
+ current_ping_time_ = next_tries_time_; |
+ last_sent_time_ = base::Time::Now(); |
+ last_sent_version_ = base::Version(version_info::GetVersionNumber()); |
+ sending_install_event_ = false; |
+ ClearInstallRetryRequestId(); |
+ PersistStates(); |
+ SendOrScheduleNextPing(); |
+ |
+ // Send notification for updates if needed. |
+ UpgradeRecommendedDetails* details = |
+ [delegate.get() upgradeRecommendedDetails]; |
+ if (details) { |
+ web::WebThread::PostTask( |
+ web::WebThread::UI, FROM_HERE, |
+ base::Bind(upgrade_recommended_callback_, *details)); |
+ } |
+} |
+ |
+void OmahaService::GetDebugInformationOnIOThread( |
+ const base::Callback<void(base::DictionaryValue*)> callback) { |
+ auto result = base::MakeUnique<base::DictionaryValue>(); |
+ |
+ result->SetString("message", GetCurrentPingContent()); |
+ result->SetString("last_sent_time", |
+ base::TimeFormatShortDateAndTime(last_sent_time_)); |
+ result->SetString("next_tries_time", |
+ base::TimeFormatShortDateAndTime(next_tries_time_)); |
+ result->SetString("current_ping_time", |
+ base::TimeFormatShortDateAndTime(current_ping_time_)); |
+ result->SetString("last_sent_version", last_sent_version_.GetString()); |
+ result->SetString("number_of_tries", |
+ base::StringPrintf("%d", number_of_tries_)); |
+ result->SetString("timer_running", |
+ base::StringPrintf("%d", timer_.IsRunning())); |
+ result->SetString( |
+ "timer_current_delay", |
+ base::StringPrintf("%llds", timer_.GetCurrentDelay().InSeconds())); |
+ result->SetString("timer_desired_run_time", |
+ base::TimeFormatShortDateAndTime( |
+ base::Time::Now() + |
+ (timer_.desired_run_time() - base::TimeTicks::Now()))); |
+ |
+ // Sending the value to the callback. |
+ web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, |
+ base::Bind(callback, base::Owned(result.release()))); |
+} |
+ |
+bool OmahaService::IsNextPingInstallRetry() { |
+ return [[NSUserDefaults standardUserDefaults] |
+ stringForKey:kRetryRequestIdKey] != nil; |
+} |
+ |
+std::string OmahaService::GetNextPingRequestId(PingContent ping_content) { |
+ NSString* stored_id = |
+ [[NSUserDefaults standardUserDefaults] stringForKey:kRetryRequestIdKey]; |
+ if (stored_id) { |
+ DCHECK(ping_content == INSTALL_EVENT); |
+ return base::SysNSStringToUTF8(stored_id); |
+ } else { |
+ std::string identifier = ios::device_util::GetRandomId(); |
+ if (ping_content == INSTALL_EVENT) |
+ OmahaService::SetInstallRetryRequestId(identifier); |
+ return identifier; |
+ } |
+} |
+ |
+void OmahaService::SetInstallRetryRequestId(const std::string& request_id) { |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ [defaults setObject:base::SysUTF8ToNSString(request_id) |
+ forKey:kRetryRequestIdKey]; |
+ // Save critical state information for usage reporting. |
+ [defaults synchronize]; |
+} |
+ |
+void OmahaService::ClearInstallRetryRequestId() { |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ [defaults removeObjectForKey:kRetryRequestIdKey]; |
+ // Clear critical state information for usage reporting. |
+ [defaults synchronize]; |
+} |
+ |
+void OmahaService::ClearPersistentStateForTests() { |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ [defaults removeObjectForKey:kNextTriesTimesKey]; |
+ [defaults removeObjectForKey:kCurrentPingKey]; |
+ [defaults removeObjectForKey:kNumberTriesKey]; |
+ [defaults removeObjectForKey:kLastSentVersionKey]; |
+ [defaults removeObjectForKey:kLastSentTimeKey]; |
+ [defaults removeObjectForKey:kRetryRequestIdKey]; |
+} |