1// Copyright 2018 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#if !defined(__has_feature) || !__has_feature(objc_arc) 6#error "This file requires ARC support." 7#endif 8 9#import "remoting/ios/facade/host_list_service.h" 10 11#import <CoreFoundation/CoreFoundation.h> 12 13#include <algorithm> 14 15#import "remoting/ios/domain/user_info.h" 16#import "remoting/ios/facade/remoting_authentication.h" 17#import "remoting/ios/facade/remoting_service.h" 18 19#include "base/bind.h" 20#include "base/logging.h" 21#include "remoting/base/directory_service_client.h" 22#include "remoting/base/protobuf_http_status.h" 23#include "remoting/base/string_resources.h" 24#include "remoting/base/task_util.h" 25#include "remoting/client/chromoting_client_runtime.h" 26#include "services/network/public/cpp/shared_url_loader_factory.h" 27#include "ui/base/l10n/l10n_util.h" 28 29namespace remoting { 30 31namespace { 32 33HostListService::FetchFailureReason MapError( 34 ProtobufHttpStatus::Code status_code) { 35 switch (status_code) { 36 case ProtobufHttpStatus::Code::UNAVAILABLE: 37 case ProtobufHttpStatus::Code::DEADLINE_EXCEEDED: 38 return HostListService::FetchFailureReason::NETWORK_ERROR; 39 case ProtobufHttpStatus::Code::PERMISSION_DENIED: 40 case ProtobufHttpStatus::Code::UNAUTHENTICATED: 41 return HostListService::FetchFailureReason::AUTH_ERROR; 42 default: 43 return HostListService::FetchFailureReason::UNKNOWN_ERROR; 44 } 45} 46 47// Returns true if |h1| should sort before |h2|. 48bool CompareHost(const apis::v1::HostInfo& h1, const apis::v1::HostInfo& h2) { 49 // Online hosts always sort before offline hosts. 50 if (h1.status() != h2.status()) { 51 return h1.status() == apis::v1::HostInfo_Status_ONLINE; 52 } 53 54 // Sort by host name. 55 int name_compare = h1.host_name().compare(h2.host_name()); 56 if (name_compare != 0) { 57 return name_compare < 0; 58 } 59 60 // Sort by last seen time if names are identical. 61 return h1.last_seen_time() < h2.last_seen_time(); 62} 63 64} // namespace 65 66HostListService* HostListService::GetInstance() { 67 static base::NoDestructor<HostListService> instance; 68 return instance.get(); 69} 70 71HostListService::HostListService() 72 : HostListService(ChromotingClientRuntime::GetInstance() 73 ->CreateDirectoryServiceClient()) {} 74 75HostListService::HostListService( 76 base::SequenceBound<DirectoryServiceClient> directory_client) { 77 directory_client_ = std::move(directory_client); 78 Init(); 79} 80 81HostListService::~HostListService() { 82 [NSNotificationCenter.defaultCenter removeObserver:user_update_observer_]; 83} 84 85void HostListService::Init() { 86 auto weak_this = weak_factory_.GetWeakPtr(); 87 user_update_observer_ = [NSNotificationCenter.defaultCenter 88 addObserverForName:kUserDidUpdate 89 object:nil 90 queue:nil 91 usingBlock:^(NSNotification* notification) { 92 UserInfo* user = notification.userInfo[kUserInfo]; 93 if (weak_this) { 94 weak_this->OnUserUpdated(user != nil); 95 } 96 }]; 97} 98 99std::unique_ptr<HostListService::CallbackSubscription> 100HostListService::RegisterHostListStateCallback( 101 const base::RepeatingClosure& callback) { 102 return host_list_state_callbacks_.Add(callback); 103} 104 105std::unique_ptr<HostListService::CallbackSubscription> 106HostListService::RegisterFetchFailureCallback( 107 const base::RepeatingClosure& callback) { 108 return fetch_failure_callbacks_.Add(callback); 109} 110 111void HostListService::RequestFetch() { 112 if (state_ == State::FETCHING) { 113 return; 114 } 115 SetState(State::FETCHING); 116 PostWithCallback(FROM_HERE, &directory_client_, 117 &DirectoryServiceClient::GetHostList, 118 base::BindOnce(&HostListService::HandleHostListResult, 119 weak_factory_.GetWeakPtr())); 120} 121 122void HostListService::SetState(State state) { 123 if (state == state_) { 124 return; 125 } 126 if (state == State::NOT_FETCHED) { 127 hosts_ = {}; 128 } else if (state == State::FETCHING || state == State::FETCHED) { 129 last_fetch_failure_.reset(); 130 } 131 state_ = state; 132 host_list_state_callbacks_.Notify(); 133} 134 135void HostListService::HandleHostListResult( 136 const ProtobufHttpStatus& status, 137 std::unique_ptr<apis::v1::GetHostListResponse> response) { 138 if (!status.ok()) { 139 HandleFetchFailure(status); 140 return; 141 } 142 hosts_.clear(); 143 for (const auto& host : response->hosts()) { 144 hosts_.push_back(host); 145 } 146 std::sort(hosts_.begin(), hosts_.end(), &CompareHost); 147 SetState(State::FETCHED); 148} 149 150void HostListService::HandleFetchFailure(const ProtobufHttpStatus& status) { 151 SetState(State::NOT_FETCHED); 152 153 if (status.error_code() == ProtobufHttpStatus::Code::CANCELLED) { 154 return; 155 } 156 157 last_fetch_failure_ = std::make_unique<FetchFailureInfo>(); 158 last_fetch_failure_->reason = MapError(status.error_code()); 159 160 switch (last_fetch_failure_->reason) { 161 case FetchFailureReason::NETWORK_ERROR: 162 last_fetch_failure_->localized_description = 163 l10n_util::GetStringUTF8(IDS_ERROR_NETWORK_ERROR); 164 break; 165 case FetchFailureReason::AUTH_ERROR: 166 last_fetch_failure_->localized_description = 167 l10n_util::GetStringUTF8(IDS_ERROR_OAUTH_TOKEN_INVALID); 168 break; 169 default: 170 last_fetch_failure_->localized_description = status.error_message(); 171 } 172 LOG(WARNING) << "Failed to fetch host list: " 173 << last_fetch_failure_->localized_description 174 << " reason: " << static_cast<int>(last_fetch_failure_->reason); 175 fetch_failure_callbacks_.Notify(); 176 if (last_fetch_failure_->reason == FetchFailureReason::AUTH_ERROR) { 177 [RemotingService.instance.authentication logout]; 178 } 179} 180 181void HostListService::OnUserUpdated(bool is_user_signed_in) { 182 directory_client_.Post(FROM_HERE, 183 &DirectoryServiceClient::CancelPendingRequests); 184 SetState(State::NOT_FETCHED); 185 if (is_user_signed_in) { 186 RequestFetch(); 187 } 188} 189 190} // namespace remoting 191