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