OLD | NEW |
(Empty) | |
| 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 <CoreFoundation/CoreFoundation.h> |
| 6 |
| 7 #include "remoting/host/setup/daemon_controller_delegate_mac.h" |
| 8 |
| 9 #include <launch.h> |
| 10 #include <stdio.h> |
| 11 #include <sys/types.h> |
| 12 |
| 13 #include "base/basictypes.h" |
| 14 #include "base/bind.h" |
| 15 #include "base/compiler_specific.h" |
| 16 #include "base/file_util.h" |
| 17 #include "base/files/file_path.h" |
| 18 #include "base/json/json_writer.h" |
| 19 #include "base/logging.h" |
| 20 #include "base/mac/foundation_util.h" |
| 21 #include "base/mac/launchd.h" |
| 22 #include "base/mac/mac_logging.h" |
| 23 #include "base/mac/mac_util.h" |
| 24 #include "base/mac/scoped_launch_data.h" |
| 25 #include "base/time/time.h" |
| 26 #include "base/values.h" |
| 27 #include "remoting/host/constants_mac.h" |
| 28 #include "remoting/host/json_host_config.h" |
| 29 #include "remoting/host/usage_stats_consent.h" |
| 30 |
| 31 namespace remoting { |
| 32 |
| 33 DaemonControllerDelegateMac::DaemonControllerDelegateMac() { |
| 34 } |
| 35 |
| 36 DaemonControllerDelegateMac::~DaemonControllerDelegateMac() { |
| 37 DeregisterForPreferencePaneNotifications(); |
| 38 } |
| 39 |
| 40 DaemonController::State DaemonControllerDelegateMac::GetState() { |
| 41 pid_t job_pid = base::mac::PIDForJob(kServiceName); |
| 42 if (job_pid < 0) { |
| 43 return DaemonController::STATE_NOT_INSTALLED; |
| 44 } else if (job_pid == 0) { |
| 45 // Service is stopped, or a start attempt failed. |
| 46 return DaemonController::STATE_STOPPED; |
| 47 } else { |
| 48 return DaemonController::STATE_STARTED; |
| 49 } |
| 50 } |
| 51 |
| 52 scoped_ptr<base::DictionaryValue> DaemonControllerDelegateMac::GetConfig() { |
| 53 base::FilePath config_path(kHostConfigFilePath); |
| 54 JsonHostConfig host_config(config_path); |
| 55 scoped_ptr<base::DictionaryValue> config; |
| 56 |
| 57 if (host_config.Read()) { |
| 58 config.reset(new base::DictionaryValue()); |
| 59 std::string value; |
| 60 if (host_config.GetString(kHostIdConfigPath, &value)) |
| 61 config.get()->SetString(kHostIdConfigPath, value); |
| 62 if (host_config.GetString(kXmppLoginConfigPath, &value)) |
| 63 config.get()->SetString(kXmppLoginConfigPath, value); |
| 64 } |
| 65 |
| 66 return config.Pass(); |
| 67 } |
| 68 |
| 69 void DaemonControllerDelegateMac::SetConfigAndStart( |
| 70 scoped_ptr<base::DictionaryValue> config, |
| 71 bool consent, |
| 72 const DaemonController::CompletionCallback& done) { |
| 73 config->SetBoolean(kUsageStatsConsentConfigPath, consent); |
| 74 std::string config_data; |
| 75 base::JSONWriter::Write(config.get(), &config_data); |
| 76 ShowPreferencePane(config_data, done); |
| 77 } |
| 78 |
| 79 void DaemonControllerDelegateMac::UpdateConfig( |
| 80 scoped_ptr<base::DictionaryValue> config, |
| 81 const DaemonController::CompletionCallback& done) { |
| 82 base::FilePath config_file_path(kHostConfigFilePath); |
| 83 JsonHostConfig config_file(config_file_path); |
| 84 if (!config_file.Read()) { |
| 85 done.Run(DaemonController::RESULT_FAILED); |
| 86 return; |
| 87 } |
| 88 if (!config_file.CopyFrom(config.get())) { |
| 89 LOG(ERROR) << "Failed to update configuration."; |
| 90 done.Run(DaemonController::RESULT_FAILED); |
| 91 return; |
| 92 } |
| 93 |
| 94 std::string config_data = config_file.GetSerializedData(); |
| 95 ShowPreferencePane(config_data, done); |
| 96 } |
| 97 |
| 98 void DaemonControllerDelegateMac::Stop( |
| 99 const DaemonController::CompletionCallback& done) { |
| 100 ShowPreferencePane("", done); |
| 101 } |
| 102 |
| 103 void DaemonControllerDelegateMac::SetWindow(void* window_handle) { |
| 104 // noop |
| 105 } |
| 106 |
| 107 std::string DaemonControllerDelegateMac::GetVersion() { |
| 108 std::string version = ""; |
| 109 std::string command_line = remoting::kHostHelperScriptPath; |
| 110 command_line += " --host-version"; |
| 111 FILE* script_output = popen(command_line.c_str(), "r"); |
| 112 if (script_output) { |
| 113 char buffer[100]; |
| 114 char* result = fgets(buffer, sizeof(buffer), script_output); |
| 115 pclose(script_output); |
| 116 if (result) { |
| 117 // The string is guaranteed to be null-terminated, but probably contains |
| 118 // a newline character, which we don't want. |
| 119 for (int i = 0; result[i]; ++i) { |
| 120 if (result[i] < ' ') { |
| 121 result[i] = 0; |
| 122 break; |
| 123 } |
| 124 } |
| 125 version = result; |
| 126 } |
| 127 } |
| 128 |
| 129 return version; |
| 130 } |
| 131 |
| 132 DaemonController::UsageStatsConsent |
| 133 DaemonControllerDelegateMac::GetUsageStatsConsent() { |
| 134 DaemonController::UsageStatsConsent consent; |
| 135 consent.supported = true; |
| 136 consent.allowed = false; |
| 137 // set_by_policy is not yet supported. |
| 138 consent.set_by_policy = false; |
| 139 |
| 140 base::FilePath config_file_path(kHostConfigFilePath); |
| 141 JsonHostConfig host_config(config_file_path); |
| 142 if (host_config.Read()) { |
| 143 host_config.GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed); |
| 144 } |
| 145 |
| 146 return consent; |
| 147 } |
| 148 |
| 149 void DaemonControllerDelegateMac::ShowPreferencePane( |
| 150 const std::string& config_data, |
| 151 const DaemonController::CompletionCallback& done) { |
| 152 if (DoShowPreferencePane(config_data)) { |
| 153 RegisterForPreferencePaneNotifications(done); |
| 154 } else { |
| 155 done.Run(DaemonController::RESULT_FAILED); |
| 156 } |
| 157 } |
| 158 |
| 159 // CFNotificationCenterAddObserver ties the thread on which distributed |
| 160 // notifications are received to the one on which it is first called. |
| 161 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback |
| 162 // bounces the invocation to the correct thread, so it doesn't matter |
| 163 // which thread CompletionCallbacks are called on. |
| 164 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications( |
| 165 const DaemonController::CompletionCallback& done) { |
| 166 // We can only have one callback registered at a time. This is enforced by the |
| 167 // UX flow of the web-app. |
| 168 DCHECK(current_callback_.is_null()); |
| 169 current_callback_ = done; |
| 170 |
| 171 CFNotificationCenterAddObserver( |
| 172 CFNotificationCenterGetDistributedCenter(), |
| 173 this, |
| 174 &DaemonControllerDelegateMac::PreferencePaneCallback, |
| 175 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), |
| 176 NULL, |
| 177 CFNotificationSuspensionBehaviorDeliverImmediately); |
| 178 CFNotificationCenterAddObserver( |
| 179 CFNotificationCenterGetDistributedCenter(), |
| 180 this, |
| 181 &DaemonControllerDelegateMac::PreferencePaneCallback, |
| 182 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), |
| 183 NULL, |
| 184 CFNotificationSuspensionBehaviorDeliverImmediately); |
| 185 } |
| 186 |
| 187 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() { |
| 188 CFNotificationCenterRemoveObserver( |
| 189 CFNotificationCenterGetDistributedCenter(), |
| 190 this, |
| 191 CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), |
| 192 NULL); |
| 193 CFNotificationCenterRemoveObserver( |
| 194 CFNotificationCenterGetDistributedCenter(), |
| 195 this, |
| 196 CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), |
| 197 NULL); |
| 198 } |
| 199 |
| 200 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate( |
| 201 CFStringRef name) { |
| 202 DaemonController::AsyncResult result = DaemonController::RESULT_FAILED; |
| 203 if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) == |
| 204 kCFCompareEqualTo) { |
| 205 result = DaemonController::RESULT_OK; |
| 206 } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) == |
| 207 kCFCompareEqualTo) { |
| 208 result = DaemonController::RESULT_FAILED; |
| 209 } else { |
| 210 LOG(WARNING) << "Ignoring unexpected notification: " << name; |
| 211 return; |
| 212 } |
| 213 |
| 214 DCHECK(!current_callback_.is_null()); |
| 215 DaemonController::CompletionCallback done = current_callback_; |
| 216 current_callback_.Reset(); |
| 217 done.Run(result); |
| 218 |
| 219 DeregisterForPreferencePaneNotifications(); |
| 220 } |
| 221 |
| 222 // static |
| 223 bool DaemonControllerDelegateMac::DoShowPreferencePane( |
| 224 const std::string& config_data) { |
| 225 if (!config_data.empty()) { |
| 226 base::FilePath config_path; |
| 227 if (!file_util::GetTempDir(&config_path)) { |
| 228 LOG(ERROR) << "Failed to get filename for saving configuration data."; |
| 229 return false; |
| 230 } |
| 231 config_path = config_path.Append(kHostConfigFileName); |
| 232 |
| 233 int written = file_util::WriteFile(config_path, config_data.data(), |
| 234 config_data.size()); |
| 235 if (written != static_cast<int>(config_data.size())) { |
| 236 LOG(ERROR) << "Failed to save configuration data to: " |
| 237 << config_path.value(); |
| 238 return false; |
| 239 } |
| 240 } |
| 241 |
| 242 base::FilePath pane_path; |
| 243 // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start |
| 244 // building against SDK 10.6. |
| 245 if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) { |
| 246 LOG(ERROR) << "Failed to get directory for local preference panes."; |
| 247 return false; |
| 248 } |
| 249 pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName); |
| 250 |
| 251 FSRef pane_path_ref; |
| 252 if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) { |
| 253 LOG(ERROR) << "Failed to create FSRef"; |
| 254 return false; |
| 255 } |
| 256 OSStatus status = LSOpenFSRef(&pane_path_ref, NULL); |
| 257 if (status != noErr) { |
| 258 OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: " |
| 259 << pane_path.value(); |
| 260 return false; |
| 261 } |
| 262 |
| 263 CFNotificationCenterRef center = |
| 264 CFNotificationCenterGetDistributedCenter(); |
| 265 base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString( |
| 266 kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8)); |
| 267 CFNotificationCenterPostNotification(center, service_name, NULL, NULL, |
| 268 TRUE); |
| 269 return true; |
| 270 } |
| 271 |
| 272 // static |
| 273 void DaemonControllerDelegateMac::PreferencePaneCallback( |
| 274 CFNotificationCenterRef center, |
| 275 void* observer, |
| 276 CFStringRef name, |
| 277 const void* object, |
| 278 CFDictionaryRef user_info) { |
| 279 DaemonControllerDelegateMac* self = |
| 280 reinterpret_cast<DaemonControllerDelegateMac*>(observer); |
| 281 if (!self) { |
| 282 LOG(WARNING) << "Ignoring notification with NULL observer: " << name; |
| 283 return; |
| 284 } |
| 285 |
| 286 self->PreferencePaneCallbackDelegate(name); |
| 287 } |
| 288 |
| 289 scoped_refptr<DaemonController> DaemonController::Create() { |
| 290 scoped_ptr<DaemonController::Delegate> delegate( |
| 291 new DaemonControllerDelegateMac()); |
| 292 return new DaemonController(delegate.Pass()); |
| 293 } |
| 294 |
| 295 } // namespace remoting |
OLD | NEW |