| // Copyright 2008-2010 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ======================================================================== |
| |
| #include "omaha/worker/job_creator.h" |
| |
| #include <windows.h> |
| #include <atlcom.h> |
| #include <cstring> |
| #include <map> |
| #include "omaha/common/debug.h" |
| #include "omaha/common/error.h" |
| #include "omaha/common/file.h" |
| #include "omaha/common/logging.h" |
| #include "omaha/common/path.h" |
| #include "omaha/common/time.h" |
| #include "omaha/goopdate/config_manager.h" |
| #include "omaha/goopdate/const_goopdate.h" |
| #include "omaha/goopdate/goopdate_xml_parser.h" |
| #include "omaha/goopdate/request.h" |
| #include "omaha/goopdate/resource.h" |
| #include "omaha/goopdate/update_response_data.h" |
| #include "omaha/worker/application_data.h" |
| #include "omaha/worker/ping.h" |
| #include "omaha/worker/worker_event_logger.h" |
| #include "omaha/worker/worker_metrics.h" |
| |
| namespace omaha { |
| |
| HRESULT JobCreator::CreateJobsFromResponses( |
| const UpdateResponses& responses, |
| const ProductDataVector& products, |
| Jobs* jobs, |
| Request* ping_request, |
| CString* event_log_text, |
| CompletionInfo* completion_info) { |
| ASSERT1(jobs); |
| ASSERT1(ping_request); |
| ASSERT1(event_log_text); |
| ASSERT1(completion_info); |
| CORE_LOG(L2, (_T("[JobCreator::CreateJobsFromResponses]"))); |
| |
| // First check to see if GoogleUpdate update is available, if so we only |
| // create the job for googleupdate and don't create jobs for the rest. |
| if (IsGoopdateUpdateAvailable(responses)) { |
| return CreateGoopdateJob(responses, |
| products, |
| jobs, |
| ping_request, |
| event_log_text, |
| completion_info); |
| } |
| |
| return CreateJobsFromResponsesInternal(responses, |
| products, |
| jobs, |
| ping_request, |
| event_log_text, |
| completion_info); |
| } |
| |
| bool JobCreator::IsGoopdateUpdateAvailable(const UpdateResponses& responses) { |
| UpdateResponses::const_iterator iter = responses.find(kGoopdateGuid); |
| if (iter == responses.end()) { |
| return false; |
| } |
| |
| const UpdateResponse& update_response = iter->second; |
| return (_tcsicmp(kResponseStatusOkValue, |
| update_response.update_response_data().status()) == 0); |
| } |
| |
| void JobCreator::AddAppUpdateDeferredPing(const AppData& product_data, |
| Request* ping_request) { |
| ASSERT1(ping_request); |
| |
| AppRequest ping_app_request; |
| AppRequestData ping_app_request_data(product_data); |
| PingEvent ping_event(PingEvent::EVENT_UPDATE_COMPLETE, |
| PingEvent::EVENT_RESULT_UPDATE_DEFERRED, |
| 0, |
| 0, // extra code 1 |
| product_data.previous_version()); |
| ping_app_request_data.AddPingEvent(ping_event); |
| ping_app_request.set_request_data(ping_app_request_data); |
| ping_request->AddAppRequest(ping_app_request); |
| } |
| |
| HRESULT JobCreator::CreateGoopdateJob(const UpdateResponses& responses, |
| const ProductDataVector& products, |
| Jobs* jobs, |
| Request* ping_request, |
| CString* event_log_text, |
| CompletionInfo* completion_info) { |
| ASSERT1(jobs); |
| ASSERT1(ping_request); |
| ASSERT1(event_log_text); |
| ASSERT1(completion_info); |
| |
| UpdateResponses::const_iterator it = responses.find(kGoopdateGuid); |
| if (it == responses.end()) { |
| return E_INVALIDARG; |
| } |
| |
| UpdateResponses goopdate_responses; |
| goopdate_responses[kGoopdateGuid] = it->second; |
| |
| // Create a job for GoogleUpdate only and defer updates for other products, |
| // if updates are available. |
| ProductDataVector goopdate_products; |
| bool is_other_app_update_available(false); |
| for (size_t i = 0; i < products.size(); ++i) { |
| const GUID app_id = products[i].app_data().app_guid(); |
| if (::IsEqualGUID(app_id, kGoopdateGuid)) { |
| goopdate_products.push_back(products[i]); |
| } else { |
| it = responses.find(app_id); |
| if (it != responses.end() && IsUpdateAvailable(it->second)) { |
| is_other_app_update_available = true; |
| AddAppUpdateDeferredPing(products[i].app_data(), ping_request); |
| } |
| } |
| } |
| ASSERT1(goopdate_products.size() == 1); |
| |
| if (is_other_app_update_available) { |
| ++metric_worker_skipped_app_update_for_self_update; |
| } |
| |
| return CreateJobsFromResponsesInternal(goopdate_responses, |
| goopdate_products, |
| jobs, |
| ping_request, |
| event_log_text, |
| completion_info); |
| } |
| |
| HRESULT JobCreator::ReadOfflineManifest(const CString& offline_dir, |
| const CString& app_guid, |
| UpdateResponse* response) { |
| ASSERT1(response); |
| |
| CString manifest_filename = app_guid + _T(".gup"); |
| CString manifest_path = ConcatenatePath(offline_dir, manifest_filename); |
| if (!File::Exists(manifest_path)) { |
| return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
| } |
| |
| UpdateResponses responses; |
| HRESULT hr = GoopdateXmlParser::ParseManifestFile(manifest_path, &responses); |
| if (FAILED(hr)) { |
| OPT_LOG(LE, (_T("[Could not parse manifest][%s]"), manifest_path)); |
| return hr; |
| } |
| ASSERT1(!responses.empty()); |
| ASSERT1(1 == responses.size()); |
| |
| UpdateResponses::const_iterator iter = responses.begin(); |
| *response = iter->second; |
| ASSERT1(!app_guid.CompareNoCase(GuidToString( |
| response->update_response_data().guid()))); |
| return S_OK; |
| } |
| |
| HRESULT JobCreator::FindOfflineFilePath(const CString& offline_dir, |
| const CString& app_guid, |
| CString* file_path) { |
| ASSERT1(file_path); |
| file_path->Empty(); |
| |
| CString offline_app_dir = ConcatenatePath(offline_dir, app_guid); |
| CString pattern(_T("*")); |
| std::vector<CString> files; |
| HRESULT hr = FindFiles(offline_app_dir, pattern, &files); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| CString local_file_path; |
| // Skip over "." and "..". |
| size_t i = 0; |
| for (; i < files.size(); ++i) { |
| local_file_path = ConcatenatePath(offline_app_dir, files[i]); |
| if (!File::IsDirectory(local_file_path)) { |
| break; |
| } |
| } |
| if (i == files.size()) { |
| return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
| } |
| |
| *file_path = local_file_path; |
| return S_OK; |
| } |
| |
| HRESULT JobCreator::CreateOfflineJobs(const CString& offline_dir, |
| const ProductDataVector& products, |
| Jobs* jobs, |
| Request* ping_request, |
| CString* event_log_text, |
| CompletionInfo* completion_info) { |
| CORE_LOG(L2, (_T("[JobCreator::CreateOfflineJobs]"))); |
| ASSERT1(jobs); |
| ASSERT1(ping_request); |
| ASSERT1(event_log_text); |
| ASSERT1(completion_info); |
| |
| ProductDataVector::const_iterator products_it = products.begin(); |
| for (; products_it != products.end(); ++products_it) { |
| const ProductData& product_data = *products_it; |
| CString guid(GuidToString(product_data.app_data().app_guid())); |
| UpdateResponse response; |
| CString file_path; |
| |
| HRESULT hr = ReadOfflineManifest(offline_dir, guid, &response); |
| if (SUCCEEDED(hr)) { |
| hr = FindOfflineFilePath(offline_dir, guid, &file_path); |
| } |
| if (SUCCEEDED(hr)) { |
| hr = goopdate_utils::ValidateDownloadedFile( |
| file_path, |
| response.update_response_data().hash(), |
| static_cast<uint32>(response.update_response_data().size())); |
| } |
| if (FAILED(hr)) { |
| *completion_info = CompletionInfo(COMPLETION_ERROR, hr, _T("")); |
| return hr; |
| } |
| |
| Job* product_job = NULL; |
| hr = HandleProductUpdateIsAvailable(product_data, |
| response, |
| &product_job, |
| ping_request, |
| event_log_text, |
| completion_info); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| product_job->set_download_file_name(file_path); |
| product_job->set_is_offline(true); |
| jobs->push_back(product_job); |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT JobCreator::CreateJobsFromResponsesInternal( |
| const UpdateResponses& responses, |
| const ProductDataVector& products, |
| Jobs* jobs, |
| Request* ping_request, |
| CString* event_log_text, |
| CompletionInfo* completion_info) { |
| ASSERT1(jobs); |
| ASSERT1(ping_request); |
| ASSERT1(event_log_text); |
| ASSERT1(completion_info); |
| CORE_LOG(L2, (_T("[JobCreator::CreateJobsFromResponsesInternal]"))); |
| |
| // The ordering needs to be done based on the products array, instead of |
| // basing this on the responses. This is because the first element in the |
| // job that is created is considered the primary app. |
| ProductDataVector::const_iterator products_it = products.begin(); |
| for (; products_it != products.end(); ++products_it) { |
| const ProductData& product_data = *products_it; |
| AppData product_app_data = product_data.app_data(); |
| |
| UpdateResponses::const_iterator iter = |
| responses.find(product_app_data.app_guid()); |
| if (iter == responses.end()) { |
| AppRequestData ping_app_request_data(product_app_data); |
| HandleResponseNotFound(product_app_data, |
| &ping_app_request_data, |
| event_log_text); |
| continue; |
| } |
| |
| const UpdateResponse& response = iter->second; |
| |
| AppManager app_manager(is_machine_); |
| // If this is an update job, need to call |
| // AppManager::UpdateApplicationState() to make sure the registry is |
| // initialized properly. |
| if (is_update_) { |
| app_manager.UpdateApplicationState(&product_app_data); |
| } |
| |
| // Write the TT Token, if any, with what the server returned. The server |
| // may ask us to clear the token as well. |
| app_manager.WriteTTToken(product_app_data, response.update_response_data()); |
| |
| if (IsUpdateAvailable(response)) { |
| if (product_data.app_data().is_update_disabled()) { |
| ASSERT1(is_update_); |
| ASSERT1(!fail_if_update_not_available_); |
| CORE_LOG(L1, (_T("[Update available for update-disabled app][%s]"), |
| GuidToString(product_data.app_data().app_guid()))); |
| app_manager.ClearUpdateAvailableStats( |
| GUID_NULL, |
| product_data.app_data().app_guid()); |
| } else { |
| ProductData modified_product_data(product_data); |
| modified_product_data.set_app_data(product_app_data); |
| Job* product_job = NULL; |
| HRESULT hr = HandleProductUpdateIsAvailable(modified_product_data, |
| response, |
| &product_job, |
| ping_request, |
| event_log_text, |
| completion_info); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| |
| jobs->push_back(product_job); |
| } |
| } else { |
| AppRequest ping_app_request; |
| AppRequestData ping_app_request_data(product_app_data); |
| HRESULT hr = HandleUpdateNotAvailable(product_app_data, |
| response.update_response_data(), |
| &ping_app_request_data, |
| event_log_text, |
| completion_info); |
| // TODO(omaha): It's unclear why the ping is added before the FAILED |
| // check here but after it in HandleProductUpdateIsAvailable(). Is this |
| // intentional? |
| ping_app_request.set_request_data(ping_app_request_data); |
| ping_request->AddAppRequest(ping_app_request); |
| if (FAILED(hr)) { |
| return hr; |
| } |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT JobCreator::HandleProductUpdateIsAvailable( |
| const ProductData& product_data, |
| const UpdateResponse& response, |
| Job** product_job, |
| Request* ping_request, |
| CString* event_log_text, |
| CompletionInfo* completion_info) { |
| ASSERT1(product_job); |
| |
| AppData product_app_data = product_data.app_data(); |
| AppRequest ping_app_request; |
| AppRequestData ping_app_request_data(product_app_data); |
| |
| if (is_update_) { |
| // previous_version should have been populated above. |
| ASSERT1(!product_app_data.previous_version().IsEmpty()); |
| } else { |
| // Copy the current version of app, if available, to previous version |
| // in the AppData object so it is sent in pings. |
| AppData existing_app_data; |
| AppManager app_manager(is_machine_); |
| HRESULT hr = app_manager.ReadAppDataFromStore( |
| GUID_NULL, |
| product_app_data.app_guid(), |
| &existing_app_data); |
| if (SUCCEEDED(hr)) { |
| product_app_data.set_previous_version(existing_app_data.version()); |
| } |
| } |
| |
| ASSERT1(::IsEqualGUID(GUID_NULL, product_app_data.parent_app_guid())); |
| HandleUpdateIsAvailable(product_app_data, |
| response.update_response_data(), |
| &ping_app_request_data, |
| event_log_text, |
| product_job); |
| ASSERT1(*product_job); |
| ping_app_request.set_request_data(ping_app_request_data); |
| |
| ping_request->AddAppRequest(ping_app_request); |
| return S_OK; |
| } |
| |
| void JobCreator::HandleResponseNotFound(const AppData& app_data, |
| AppRequestData* ping_app_request_data, |
| CString* event_log_text) { |
| ASSERT1(event_log_text); |
| ASSERT1(ping_app_request_data); |
| |
| PingEvent::Types event_type = is_update_ ? |
| PingEvent::EVENT_UPDATE_COMPLETE : |
| PingEvent::EVENT_INSTALL_COMPLETE; |
| PingEvent ping_event(event_type, |
| PingEvent::EVENT_RESULT_ERROR, |
| GOOPDATE_E_NO_SERVER_RESPONSE, |
| 0, // extra code 1 |
| app_data.previous_version()); |
| ping_app_request_data->AddPingEvent(ping_event); |
| |
| event_log_text->AppendFormat( |
| _T("App=%s, Ver=%s, Status=no-response-received\n"), |
| GuidToString(app_data.app_guid()), |
| app_data.version()); |
| } |
| |
| void JobCreator::HandleUpdateIsAvailable( |
| const AppData& app_data, |
| const UpdateResponseData& response_data, |
| AppRequestData* ping_app_request_data, |
| CString* event_log_text, |
| Job** job) { |
| ASSERT1(ping_app_request_data); |
| ASSERT1(event_log_text); |
| ASSERT1(job); |
| ASSERT1(!app_data.is_update_disabled()); |
| |
| if (is_auto_update_) { |
| if (::IsEqualGUID(kGoopdateGuid, app_data.app_guid())) { |
| ++metric_worker_self_updates_available; |
| } else { |
| ++metric_worker_app_updates_available; |
| } |
| |
| // Only record an update available event for updates. |
| // We have other mechanisms, including IID, to track install success. |
| AppManager app_manager(is_machine_); |
| app_manager.UpdateUpdateAvailableStats(app_data.parent_app_guid(), |
| app_data.app_guid()); |
| } |
| |
| // Ping and record events only for "real" jobs. On demand update checks only |
| // jobs should not ping, since no update is actually applied. |
| if (!is_update_check_only_) { |
| PingEvent::Types event_type = is_update_ ? |
| PingEvent::EVENT_UPDATE_APPLICATION_BEGIN : |
| PingEvent::EVENT_INSTALL_APPLICATION_BEGIN; |
| PingEvent ping_event(event_type, |
| PingEvent::EVENT_RESULT_SUCCESS, |
| 0, // error code |
| 0, // extra code 1 |
| app_data.previous_version()); |
| ping_app_request_data->AddPingEvent(ping_event); |
| } |
| const TCHAR* status = is_update_check_only_ ? _T("check only") : |
| is_update_ ? _T("update") : _T("install"); |
| event_log_text->AppendFormat(_T("App=%s, Ver=%s, Status=%s\n"), |
| GuidToString(app_data.app_guid()), |
| app_data.version(), |
| status); |
| |
| // TODO(omaha): When components are implemented, how do we handle the case |
| // that this is just a place holder and does not really need an update? |
| // We may be in an app that only needs an update here because it's child |
| // components need updates so we'll need to flag the job of that somehow. |
| // Unless it's already tagged in the response_data (which does know that |
| // there's nothing to download) but the job state will need to be updated. |
| scoped_ptr<Job> new_job(new Job(is_update_, ping_)); |
| new_job->set_app_data(app_data); |
| new_job->set_update_response_data(response_data); |
| new_job->set_is_background(is_auto_update_); |
| new_job->set_is_update_check_only(is_update_check_only_); |
| |
| *job = new_job.release(); |
| } |
| |
| HRESULT JobCreator::HandleUpdateNotAvailable( |
| const AppData& app_data, |
| const UpdateResponseData& response_data, |
| AppRequestData* ping_app_request_data, |
| CString* event_log_text, |
| CompletionInfo* completion_info) { |
| ASSERT1(ping_app_request_data); |
| ASSERT1(event_log_text); |
| ASSERT1(completion_info); |
| |
| PingEvent::Results result_type = PingEvent::EVENT_RESULT_ERROR; |
| CompletionInfo info = UpdateResponseDataToCompletionInfo( |
| response_data, |
| ping_app_request_data->app_data().display_name()); |
| switch (info.error_code) { |
| case static_cast<DWORD>(GOOPDATE_E_NO_UPDATE_RESPONSE): |
| event_log_text->AppendFormat( |
| _T("App=%s, Ver=%s, Status=no-update\n"), |
| GuidToString(app_data.app_guid()), |
| app_data.version()); |
| if (is_update_) { |
| // In case of updates, a "noupdate" response is not an error. |
| result_type = PingEvent::EVENT_RESULT_NOUPDATE; |
| info.status = COMPLETION_SUCCESS; |
| info.error_code = NOERROR; |
| |
| AppManager app_manager(is_machine_); |
| app_manager.ClearUpdateAvailableStats(app_data.parent_app_guid(), |
| app_data.app_guid()); |
| app_manager.RecordSuccessfulUpdateCheck(app_data.parent_app_guid(), |
| app_data.app_guid()); |
| } |
| break; |
| case static_cast<DWORD>(GOOPDATE_E_RESTRICTED_SERVER_RESPONSE): |
| event_log_text->AppendFormat( |
| _T("App=%s, Ver=%s, Status=restricted\n"), |
| GuidToString(app_data.app_guid()), |
| app_data.version()); |
| break; |
| case GOOPDATE_E_UNKNOWN_APP_SERVER_RESPONSE: |
| case GOOPDATE_E_INTERNAL_ERROR_SERVER_RESPONSE: |
| case GOOPDATE_E_SERVER_RESPONSE_NO_HASH: |
| case GOOPDATE_E_SERVER_RESPONSE_UNSUPPORTED_PROTOCOL: |
| case GOOPDATE_E_UNKNOWN_SERVER_RESPONSE: |
| default: |
| event_log_text->AppendFormat(_T("App=%s, Ver=%s, Status=error:0x%08x\n"), |
| GuidToString(app_data.app_guid()), |
| app_data.version(), |
| info.error_code); |
| break; |
| } |
| |
| // Only ping for server responses other than "noupdate" or errors. |
| if (result_type != PingEvent::EVENT_RESULT_NOUPDATE || |
| info.error_code != NOERROR) { |
| PingEvent::Types event_type = is_update_ ? |
| PingEvent::EVENT_UPDATE_COMPLETE : |
| PingEvent::EVENT_INSTALL_COMPLETE; |
| PingEvent ping_event(event_type, |
| result_type, |
| info.error_code, |
| 0, // extra code 1 |
| app_data.previous_version()); |
| ping_app_request_data->AddPingEvent(ping_event); |
| } |
| |
| if (fail_if_update_not_available_) { |
| *completion_info = info; |
| return info.error_code; |
| } |
| |
| return S_OK; |
| } |
| |
| // app_name in UpdateResponseData is not filled in. See the TODO in that class. |
| CompletionInfo JobCreator::UpdateResponseDataToCompletionInfo( |
| const UpdateResponseData& response_data, |
| const CString& display_name) { |
| CompletionInfo info; |
| const CString& status = response_data.status(); |
| if (_tcsicmp(kResponseStatusOkValue, status) == 0) { |
| info.status = COMPLETION_SUCCESS; |
| info.error_code = 0; |
| } else if (_tcsicmp(kResponseStatusNoUpdate, status) == 0) { |
| // "noupdate" is considered an error but the calling code can map it to |
| // a successful completion, in the cases of silent and on demand updates. |
| info.status = COMPLETION_ERROR; |
| info.error_code = static_cast<DWORD>(GOOPDATE_E_NO_UPDATE_RESPONSE); |
| VERIFY1(info.text.LoadString(IDS_NO_UPDATE_RESPONSE)); |
| } else if (_tcsicmp(kResponseStatusRestrictedExportCountry, status) == 0) { |
| // "restricted" |
| info.status = COMPLETION_ERROR; |
| info.error_code = |
| static_cast<DWORD>(GOOPDATE_E_RESTRICTED_SERVER_RESPONSE); |
| VERIFY1(info.text.LoadString(IDS_RESTRICTED_RESPONSE_FROM_SERVER)); |
| } else if (_tcsicmp(kResponseStatusUnKnownApplication, status) == 0) { |
| // "error-UnKnownApplication" |
| info.status = COMPLETION_ERROR; |
| info.error_code = |
| static_cast<DWORD>(GOOPDATE_E_UNKNOWN_APP_SERVER_RESPONSE); |
| VERIFY1(info.text.LoadString(IDS_UNKNOWN_APPLICATION)); |
| } else if (_tcsicmp(kResponseStatusOsNotSupported, status) == 0) { |
| // "error-OsNotSupported" |
| info.status = COMPLETION_ERROR; |
| info.error_code = static_cast<DWORD>(GOOPDATE_E_OS_NOT_SUPPORTED); |
| if (response_data.error_url().IsEmpty()) { |
| info.text.FormatMessage(IDS_NON_OK_RESPONSE_FROM_SERVER, status); |
| } else { |
| info.text.FormatMessage(IDS_OS_NOT_SUPPORTED, |
| display_name, |
| response_data.error_url()); |
| } |
| } else if (_tcsicmp(kResponseStatusInternalError, status) == 0) { |
| // "error-internal" |
| info.status = COMPLETION_ERROR; |
| info.error_code = |
| static_cast<DWORD>(GOOPDATE_E_INTERNAL_ERROR_SERVER_RESPONSE); |
| info.text.FormatMessage(IDS_NON_OK_RESPONSE_FROM_SERVER, status); |
| } else if (_tcsicmp(kResponseStatusHashError, status) == 0) { |
| // "error-hash" |
| info.status = COMPLETION_ERROR; |
| info.error_code = |
| static_cast<DWORD>(GOOPDATE_E_SERVER_RESPONSE_NO_HASH); |
| info.text.FormatMessage(IDS_NON_OK_RESPONSE_FROM_SERVER, status); |
| } else if (_tcsicmp(kResponseStatusUnsupportedProtocol, status) == 0) { |
| // "error-unsupportedprotocol" |
| info.status = COMPLETION_ERROR; |
| info.error_code = |
| static_cast<DWORD>(GOOPDATE_E_SERVER_RESPONSE_UNSUPPORTED_PROTOCOL); |
| // TODO(omaha): Ideally, we would provide an app-specific URL instead of |
| // just the publisher name. If it was a link, we could use point to a |
| // redirect URL and provide the app GUID rather than somehow obtaining the |
| // app-specific URL. |
| info.text.FormatMessage(IDS_INSTALLER_OLD, kPublisherName); |
| } else { |
| // Unknown response. |
| info.status = COMPLETION_ERROR; |
| info.error_code = |
| static_cast<DWORD>(GOOPDATE_E_UNKNOWN_SERVER_RESPONSE); |
| info.text.FormatMessage(IDS_NON_OK_RESPONSE_FROM_SERVER, status); |
| } |
| |
| return info; |
| } |
| |
| bool JobCreator::IsUpdateAvailable(const UpdateResponseData& response_data) { |
| return _tcsicmp(kResponseStatusOkValue, response_data.status()) == 0; |
| } |
| |
| // Check response and all of its children to see if any of them are in a state |
| // that requires an update job to be created. |
| bool JobCreator::IsUpdateAvailable(const UpdateResponse& response) { |
| // If the top level response needs updating, short circuit here. |
| if (IsUpdateAvailable(response.update_response_data())) { |
| return true; |
| } |
| |
| UpdateResponseDatas::const_iterator it; |
| for (it = response.components_begin(); |
| it != response.components_begin(); |
| ++it) { |
| if (IsUpdateAvailable(it->second)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| } // namespace omaha |
| |