diff options
author | Jacek Antonelli | 2008-08-15 23:44:46 -0500 |
---|---|---|
committer | Jacek Antonelli | 2008-08-15 23:44:46 -0500 |
commit | 38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4 (patch) | |
tree | adca584755d22ca041a2dbfc35d4eca01f70b32c /linden/indra/mac_updater/mac_updater.cpp | |
parent | README.txt (diff) | |
download | meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.zip meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.gz meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.bz2 meta-impy-38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4.tar.xz |
Second Life viewer sources 1.13.2.12
Diffstat (limited to '')
-rw-r--r-- | linden/indra/mac_updater/mac_updater.cpp | 1106 |
1 files changed, 1106 insertions, 0 deletions
diff --git a/linden/indra/mac_updater/mac_updater.cpp b/linden/indra/mac_updater/mac_updater.cpp new file mode 100644 index 0000000..25e203c --- /dev/null +++ b/linden/indra/mac_updater/mac_updater.cpp | |||
@@ -0,0 +1,1106 @@ | |||
1 | /** | ||
2 | * @file mac_updater.cpp | ||
3 | * @brief | ||
4 | * | ||
5 | * Copyright (c) 2006-2007, Linden Research, Inc. | ||
6 | * | ||
7 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
8 | * to you under the terms of the GNU General Public License, version 2.0 | ||
9 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
10 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
11 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
12 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
13 | * | ||
14 | * There are special exceptions to the terms and conditions of the GPL as | ||
15 | * it is applied to this Source Code. View the full text of the exception | ||
16 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
17 | * online at http://secondlife.com/developers/opensource/flossexception | ||
18 | * | ||
19 | * By copying, modifying or distributing this software, you acknowledge | ||
20 | * that you have read and understood your obligations described above, | ||
21 | * and agree to abide by those obligations. | ||
22 | * | ||
23 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
24 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
25 | * COMPLETENESS OR PERFORMANCE. | ||
26 | */ | ||
27 | |||
28 | #include "linden_common.h" | ||
29 | |||
30 | #include <stdio.h> | ||
31 | #include <stdlib.h> | ||
32 | //#include <direct.h> | ||
33 | #include <time.h> | ||
34 | #include <sys/types.h> | ||
35 | #include <sys/stat.h> | ||
36 | #include <unistd.h> | ||
37 | |||
38 | #include <curl/curl.h> | ||
39 | #include <pthread.h> | ||
40 | |||
41 | #include "llerror.h" | ||
42 | #include "lltimer.h" | ||
43 | #include "lldir.h" | ||
44 | #include "llfile.h" | ||
45 | |||
46 | #include "llstring.h" | ||
47 | |||
48 | #include <Carbon/Carbon.h> | ||
49 | |||
50 | #include "MoreFilesX.h" | ||
51 | #include "FSCopyObject.h" | ||
52 | |||
53 | enum | ||
54 | { | ||
55 | kEventClassCustom = 'Cust', | ||
56 | kEventCustomProgress = 'Prog', | ||
57 | kEventParamCustomCurValue = 'Cur ', | ||
58 | kEventParamCustomMaxValue = 'Max ', | ||
59 | kEventParamCustomText = 'Text', | ||
60 | kEventCustomDone = 'Done', | ||
61 | }; | ||
62 | |||
63 | WindowRef gWindow = NULL; | ||
64 | EventHandlerRef gEventHandler = NULL; | ||
65 | OSStatus gFailure = noErr; | ||
66 | Boolean gCancelled = false; | ||
67 | |||
68 | char *gUserServer; | ||
69 | char *gProductName; | ||
70 | char gUpdateURL[2048]; | ||
71 | |||
72 | void *updatethreadproc(void*); | ||
73 | |||
74 | pthread_t updatethread; | ||
75 | |||
76 | OSStatus setProgress(int cur, int max) | ||
77 | { | ||
78 | OSStatus err; | ||
79 | ControlRef progressBar = NULL; | ||
80 | ControlID id; | ||
81 | |||
82 | id.signature = 'prog'; | ||
83 | id.id = 0; | ||
84 | |||
85 | err = GetControlByID(gWindow, &id, &progressBar); | ||
86 | if(err == noErr) | ||
87 | { | ||
88 | Boolean indeterminate; | ||
89 | |||
90 | if(max == 0) | ||
91 | { | ||
92 | indeterminate = true; | ||
93 | err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate); | ||
94 | } | ||
95 | else | ||
96 | { | ||
97 | double percentage = (double)cur / (double)max; | ||
98 | SetControlMinimum(progressBar, 0); | ||
99 | SetControlMaximum(progressBar, 100); | ||
100 | SetControlValue(progressBar, (SInt16)(percentage * 100)); | ||
101 | |||
102 | indeterminate = false; | ||
103 | err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate); | ||
104 | |||
105 | Draw1Control(progressBar); | ||
106 | } | ||
107 | } | ||
108 | |||
109 | return(err); | ||
110 | } | ||
111 | |||
112 | OSStatus setProgressText(CFStringRef text) | ||
113 | { | ||
114 | OSStatus err; | ||
115 | ControlRef progressText = NULL; | ||
116 | ControlID id; | ||
117 | |||
118 | id.signature = 'what'; | ||
119 | id.id = 0; | ||
120 | |||
121 | err = GetControlByID(gWindow, &id, &progressText); | ||
122 | if(err == noErr) | ||
123 | { | ||
124 | err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&text); | ||
125 | Draw1Control(progressText); | ||
126 | } | ||
127 | |||
128 | return(err); | ||
129 | } | ||
130 | |||
131 | OSStatus sendProgress(long cur, long max, CFStringRef text = NULL) | ||
132 | { | ||
133 | OSStatus result; | ||
134 | EventRef evt; | ||
135 | |||
136 | result = CreateEvent( | ||
137 | NULL, | ||
138 | kEventClassCustom, | ||
139 | kEventCustomProgress, | ||
140 | 0, | ||
141 | kEventAttributeNone, | ||
142 | &evt); | ||
143 | |||
144 | // This event needs to be targeted at the window so it goes to the window's handler. | ||
145 | if(result == noErr) | ||
146 | { | ||
147 | EventTargetRef target = GetWindowEventTarget(gWindow); | ||
148 | result = SetEventParameter ( | ||
149 | evt, | ||
150 | kEventParamPostTarget, | ||
151 | typeEventTargetRef, | ||
152 | sizeof(target), | ||
153 | &target); | ||
154 | } | ||
155 | |||
156 | if(result == noErr) | ||
157 | { | ||
158 | result = SetEventParameter ( | ||
159 | evt, | ||
160 | kEventParamCustomCurValue, | ||
161 | typeLongInteger, | ||
162 | sizeof(cur), | ||
163 | &cur); | ||
164 | } | ||
165 | |||
166 | if(result == noErr) | ||
167 | { | ||
168 | result = SetEventParameter ( | ||
169 | evt, | ||
170 | kEventParamCustomMaxValue, | ||
171 | typeLongInteger, | ||
172 | sizeof(max), | ||
173 | &max); | ||
174 | } | ||
175 | |||
176 | if(result == noErr) | ||
177 | { | ||
178 | if(text != NULL) | ||
179 | { | ||
180 | result = SetEventParameter ( | ||
181 | evt, | ||
182 | kEventParamCustomText, | ||
183 | typeCFStringRef, | ||
184 | sizeof(text), | ||
185 | &text); | ||
186 | } | ||
187 | } | ||
188 | |||
189 | if(result == noErr) | ||
190 | { | ||
191 | // Send the event | ||
192 | PostEventToQueue( | ||
193 | GetMainEventQueue(), | ||
194 | evt, | ||
195 | kEventPriorityStandard); | ||
196 | |||
197 | } | ||
198 | |||
199 | return(result); | ||
200 | } | ||
201 | |||
202 | OSStatus sendDone(void) | ||
203 | { | ||
204 | OSStatus result; | ||
205 | EventRef evt; | ||
206 | |||
207 | result = CreateEvent( | ||
208 | NULL, | ||
209 | kEventClassCustom, | ||
210 | kEventCustomDone, | ||
211 | 0, | ||
212 | kEventAttributeNone, | ||
213 | &evt); | ||
214 | |||
215 | // This event needs to be targeted at the window so it goes to the window's handler. | ||
216 | if(result == noErr) | ||
217 | { | ||
218 | EventTargetRef target = GetWindowEventTarget(gWindow); | ||
219 | result = SetEventParameter ( | ||
220 | evt, | ||
221 | kEventParamPostTarget, | ||
222 | typeEventTargetRef, | ||
223 | sizeof(target), | ||
224 | &target); | ||
225 | } | ||
226 | |||
227 | if(result == noErr) | ||
228 | { | ||
229 | // Send the event | ||
230 | PostEventToQueue( | ||
231 | GetMainEventQueue(), | ||
232 | evt, | ||
233 | kEventPriorityStandard); | ||
234 | |||
235 | } | ||
236 | |||
237 | return(result); | ||
238 | } | ||
239 | |||
240 | OSStatus dialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata) | ||
241 | { | ||
242 | OSStatus result = eventNotHandledErr; | ||
243 | OSStatus err; | ||
244 | UInt32 evtClass = GetEventClass(event); | ||
245 | UInt32 evtKind = GetEventKind(event); | ||
246 | |||
247 | if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess)) | ||
248 | { | ||
249 | HICommand cmd; | ||
250 | err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd); | ||
251 | |||
252 | if(err == noErr) | ||
253 | { | ||
254 | switch(cmd.commandID) | ||
255 | { | ||
256 | case kHICommandCancel: | ||
257 | gCancelled = true; | ||
258 | // QuitAppModalLoopForWindow(gWindow); | ||
259 | result = noErr; | ||
260 | break; | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomProgress)) | ||
265 | { | ||
266 | // Request to update the progress dialog | ||
267 | long cur = 0; | ||
268 | long max = 0; | ||
269 | CFStringRef text = NULL; | ||
270 | (void) GetEventParameter(event, kEventParamCustomCurValue, typeLongInteger, NULL, sizeof(cur), NULL, &cur); | ||
271 | (void) GetEventParameter(event, kEventParamCustomMaxValue, typeLongInteger, NULL, sizeof(max), NULL, &max); | ||
272 | (void) GetEventParameter(event, kEventParamCustomText, typeCFStringRef, NULL, sizeof(text), NULL, &text); | ||
273 | |||
274 | err = setProgress(cur, max); | ||
275 | if(err == noErr) | ||
276 | { | ||
277 | if(text != NULL) | ||
278 | { | ||
279 | setProgressText(text); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | result = noErr; | ||
284 | } | ||
285 | else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomDone)) | ||
286 | { | ||
287 | // We're done. Exit the modal loop. | ||
288 | QuitAppModalLoopForWindow(gWindow); | ||
289 | result = noErr; | ||
290 | } | ||
291 | |||
292 | return(result); | ||
293 | } | ||
294 | |||
295 | #if 0 | ||
296 | size_t curl_download_callback(void *data, size_t size, size_t nmemb, | ||
297 | void *user_data) | ||
298 | { | ||
299 | S32 bytes = size * nmemb; | ||
300 | char *cdata = (char *) data; | ||
301 | for (int i =0; i < bytes; i += 1) | ||
302 | { | ||
303 | gServerResponse.append(cdata[i]); | ||
304 | } | ||
305 | return bytes; | ||
306 | } | ||
307 | #endif | ||
308 | |||
309 | int curl_progress_callback_func(void *clientp, | ||
310 | double dltotal, | ||
311 | double dlnow, | ||
312 | double ultotal, | ||
313 | double ulnow) | ||
314 | { | ||
315 | int max = (int)(dltotal / 1024.0); | ||
316 | int cur = (int)(dlnow / 1024.0); | ||
317 | sendProgress(cur, max); | ||
318 | |||
319 | if(gCancelled) | ||
320 | return(1); | ||
321 | |||
322 | return(0); | ||
323 | } | ||
324 | |||
325 | int parse_args(int argc, char **argv) | ||
326 | { | ||
327 | // Check for old-type arguments. | ||
328 | if (2 == argc) | ||
329 | { | ||
330 | gUserServer = argv[1]; | ||
331 | return 0; | ||
332 | } | ||
333 | |||
334 | int j; | ||
335 | |||
336 | for (j = 1; j < argc; j++) | ||
337 | { | ||
338 | if ((!strcmp(argv[j], "-userserver")) && (++j < argc)) | ||
339 | { | ||
340 | gUserServer = argv[j]; | ||
341 | } | ||
342 | else if ((!strcmp(argv[j], "-name")) && (++j < argc)) | ||
343 | { | ||
344 | gProductName = argv[j]; | ||
345 | } | ||
346 | } | ||
347 | |||
348 | return 0; | ||
349 | } | ||
350 | |||
351 | int main(int argc, char **argv) | ||
352 | { | ||
353 | // We assume that all the logs we're looking for reside on the current drive | ||
354 | gDirUtilp->initAppDirs("SecondLife"); | ||
355 | |||
356 | ///////////////////////////////////////// | ||
357 | // | ||
358 | // Process command line arguments | ||
359 | // | ||
360 | gUserServer = NULL; | ||
361 | gProductName = NULL; | ||
362 | parse_args(argc, argv); | ||
363 | if (!gUserServer) | ||
364 | { | ||
365 | llinfos << "Usage: mac_updater -userserver <server> [-name <product_name>] [-program <program_name>]" << llendl; | ||
366 | exit(1); | ||
367 | } | ||
368 | else | ||
369 | { | ||
370 | llinfos << "User server is: " << gUserServer << llendl; | ||
371 | if (gProductName) | ||
372 | { | ||
373 | llinfos << "Product name is: " << gProductName << llendl; | ||
374 | } | ||
375 | else | ||
376 | { | ||
377 | gProductName = "Second Life"; | ||
378 | } | ||
379 | } | ||
380 | |||
381 | llinfos << "Starting " << gProductName << " Updater" << llendl; | ||
382 | |||
383 | // Build the URL to download the update | ||
384 | snprintf(gUpdateURL, sizeof(gUpdateURL), "http://secondlife.com/update-macos.php?userserver=%s", gUserServer); | ||
385 | |||
386 | // Real UI... | ||
387 | OSStatus err; | ||
388 | IBNibRef nib = NULL; | ||
389 | |||
390 | err = CreateNibReference(CFSTR("AutoUpdater"), &nib); | ||
391 | |||
392 | char windowTitle[MAX_PATH]; | ||
393 | snprintf(windowTitle, sizeof(windowTitle), "%s Updater", gProductName); | ||
394 | CFStringRef windowTitleRef = NULL; | ||
395 | windowTitleRef = CFStringCreateWithCString(NULL, windowTitle, kCFStringEncodingUTF8); | ||
396 | |||
397 | if(err == noErr) | ||
398 | { | ||
399 | err = CreateWindowFromNib(nib, CFSTR("Updater"), &gWindow); | ||
400 | } | ||
401 | |||
402 | if (err == noErr) | ||
403 | { | ||
404 | err = SetWindowTitleWithCFString(gWindow, windowTitleRef); | ||
405 | } | ||
406 | CFRelease(windowTitleRef); | ||
407 | |||
408 | if(err == noErr) | ||
409 | { | ||
410 | // Set up an event handler for the window. | ||
411 | EventTypeSpec handlerEvents[] = | ||
412 | { | ||
413 | { kEventClassCommand, kEventCommandProcess }, | ||
414 | { kEventClassCustom, kEventCustomProgress }, | ||
415 | { kEventClassCustom, kEventCustomDone } | ||
416 | }; | ||
417 | InstallStandardEventHandler(GetWindowEventTarget(gWindow)); | ||
418 | InstallWindowEventHandler( | ||
419 | gWindow, | ||
420 | NewEventHandlerUPP(dialogHandler), | ||
421 | GetEventTypeCount (handlerEvents), | ||
422 | handlerEvents, | ||
423 | 0, | ||
424 | &gEventHandler); | ||
425 | } | ||
426 | |||
427 | if(err == noErr) | ||
428 | { | ||
429 | ShowWindow(gWindow); | ||
430 | } | ||
431 | |||
432 | if(err == noErr) | ||
433 | { | ||
434 | pthread_create(&updatethread, | ||
435 | NULL, | ||
436 | &updatethreadproc, | ||
437 | NULL); | ||
438 | |||
439 | } | ||
440 | |||
441 | if(err == noErr) | ||
442 | { | ||
443 | RunAppModalLoopForWindow(gWindow); | ||
444 | } | ||
445 | |||
446 | void *threadresult; | ||
447 | |||
448 | pthread_join(updatethread, &threadresult); | ||
449 | |||
450 | if(!gCancelled && (gFailure != noErr)) | ||
451 | { | ||
452 | // Something went wrong. Since we always just tell the user to download a new version, we don't really care what. | ||
453 | AlertStdCFStringAlertParamRec params; | ||
454 | SInt16 retval_mac = 1; | ||
455 | DialogRef alert = NULL; | ||
456 | OSStatus err; | ||
457 | |||
458 | params.version = kStdCFStringAlertVersionOne; | ||
459 | params.movable = false; | ||
460 | params.helpButton = false; | ||
461 | params.defaultText = (CFStringRef)kAlertDefaultOKText; | ||
462 | params.cancelText = 0; | ||
463 | params.otherText = 0; | ||
464 | params.defaultButton = 1; | ||
465 | params.cancelButton = 0; | ||
466 | params.position = kWindowDefaultPosition; | ||
467 | params.flags = 0; | ||
468 | |||
469 | err = CreateStandardAlert( | ||
470 | kAlertStopAlert, | ||
471 | CFSTR("Error"), | ||
472 | CFSTR("An error occurred while updating Second Life. Please download the latest version from www.secondlife.com."), | ||
473 | ¶ms, | ||
474 | &alert); | ||
475 | |||
476 | if(err == noErr) | ||
477 | { | ||
478 | err = RunStandardAlert( | ||
479 | alert, | ||
480 | NULL, | ||
481 | &retval_mac); | ||
482 | } | ||
483 | |||
484 | } | ||
485 | |||
486 | // Don't dispose of things, just exit. This keeps the update thread from potentially getting hosed. | ||
487 | exit(0); | ||
488 | |||
489 | if(gWindow != NULL) | ||
490 | { | ||
491 | DisposeWindow(gWindow); | ||
492 | } | ||
493 | |||
494 | if(nib != NULL) | ||
495 | { | ||
496 | DisposeNibReference(nib); | ||
497 | } | ||
498 | |||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | bool isDirWritable(FSRef &dir) | ||
503 | { | ||
504 | bool result = false; | ||
505 | |||
506 | // Test for a writable directory by creating a directory, then deleting it again. | ||
507 | // This is kinda lame, but will pretty much always give the right answer. | ||
508 | |||
509 | OSStatus err = noErr; | ||
510 | char temp[PATH_MAX]; | ||
511 | |||
512 | err = FSRefMakePath(&dir, (UInt8*)temp, sizeof(temp)); | ||
513 | |||
514 | if(err == noErr) | ||
515 | { | ||
516 | temp[0] = '\0'; | ||
517 | strncat(temp, "/.test_XXXXXX", sizeof(temp) - 1); | ||
518 | |||
519 | if(mkdtemp(temp) != NULL) | ||
520 | { | ||
521 | // We were able to make the directory. This means the directory is writable. | ||
522 | result = true; | ||
523 | |||
524 | // Clean up. | ||
525 | rmdir(temp); | ||
526 | } | ||
527 | } | ||
528 | |||
529 | #if 0 | ||
530 | // This seemed like a good idea, but won't tell us if we're on a volume mounted read-only. | ||
531 | UInt8 perm; | ||
532 | err = FSGetUserPrivilegesPermissions(&targetParentRef, &perm, NULL); | ||
533 | if(err == noErr) | ||
534 | { | ||
535 | if(perm & kioACUserNoMakeChangesMask) | ||
536 | { | ||
537 | // Parent directory isn't writable. | ||
538 | llinfos << "Target parent directory not writable." << llendl; | ||
539 | err = -1; | ||
540 | replacingTarget = false; | ||
541 | } | ||
542 | } | ||
543 | #endif | ||
544 | |||
545 | return result; | ||
546 | } | ||
547 | |||
548 | static void utf8str_to_HFSUniStr255(HFSUniStr255 *dest, const char* src) | ||
549 | { | ||
550 | LLWString wstr = utf8str_to_wstring(src); | ||
551 | llutf16string utf16str = wstring_to_utf16str(wstr); | ||
552 | |||
553 | dest->length = utf16str.size(); | ||
554 | if(dest->length > 255) | ||
555 | { | ||
556 | // There's onl room for 255 chars in a HFSUniStr25.. | ||
557 | // Truncate to avoid stack smaching or other badness. | ||
558 | dest->length = 255; | ||
559 | } | ||
560 | memcpy(dest->unicode, utf16str.data(), sizeof(UniChar)* dest->length); | ||
561 | } | ||
562 | |||
563 | int restoreObject(const char* aside, const char* target, const char* path, const char* object) | ||
564 | { | ||
565 | char source[PATH_MAX]; | ||
566 | char dest[PATH_MAX]; | ||
567 | snprintf(source, sizeof(source), "%s/%s/%s", aside, path, object); | ||
568 | snprintf(dest, sizeof(dest), "%s/%s", target, path); | ||
569 | FSRef sourceRef; | ||
570 | FSRef destRef; | ||
571 | OSStatus err; | ||
572 | err = FSPathMakeRef((UInt8 *)source, &sourceRef, NULL); | ||
573 | if(err != noErr) return false; | ||
574 | err = FSPathMakeRef((UInt8 *)dest, &destRef, NULL); | ||
575 | if(err != noErr) return false; | ||
576 | |||
577 | llinfos << "Copying " << source << " to " << dest << llendl; | ||
578 | |||
579 | err = FSCopyObject( | ||
580 | &sourceRef, | ||
581 | &destRef, | ||
582 | 0, | ||
583 | kFSCatInfoNone, | ||
584 | kDupeActionReplace, | ||
585 | NULL, | ||
586 | false, | ||
587 | false, | ||
588 | NULL, | ||
589 | NULL, | ||
590 | NULL, | ||
591 | NULL); | ||
592 | |||
593 | if(err != noErr) return false; | ||
594 | return true; | ||
595 | } | ||
596 | |||
597 | // Replace any mention of "Second Life" with the product name. | ||
598 | void filterFile(const char* filename) | ||
599 | { | ||
600 | char temp[PATH_MAX]; | ||
601 | // First copy the target's version, so we can run it through sed. | ||
602 | snprintf(temp, sizeof(temp), "cp '%s' '%s.tmp'", filename, filename); | ||
603 | system(temp); | ||
604 | |||
605 | // Now run it through sed. | ||
606 | snprintf(temp, sizeof(temp), | ||
607 | "sed 's/Second Life/%s/g' '%s.tmp' > '%s'", gProductName, filename, filename); | ||
608 | system(temp); | ||
609 | } | ||
610 | |||
611 | void *updatethreadproc(void*) | ||
612 | { | ||
613 | char tempDir[PATH_MAX] = ""; | ||
614 | FSRef tempDirRef; | ||
615 | char temp[PATH_MAX]; | ||
616 | char deviceNode[1024] = ""; | ||
617 | FILE *downloadFile = NULL; | ||
618 | OSStatus err; | ||
619 | ProcessSerialNumber psn; | ||
620 | char target[PATH_MAX]; | ||
621 | FSRef targetRef; | ||
622 | FSRef targetParentRef; | ||
623 | FSVolumeRefNum targetVol; | ||
624 | FSRef trashFolderRef, tempFolderRef; | ||
625 | Boolean replacingTarget = false; | ||
626 | |||
627 | memset(&tempDirRef, 0, sizeof(tempDirRef)); | ||
628 | memset(&targetRef, 0, sizeof(targetRef)); | ||
629 | memset(&targetParentRef, 0, sizeof(targetParentRef)); | ||
630 | |||
631 | try | ||
632 | { | ||
633 | // Attempt to get a reference to the Second Life application bundle containing this updater. | ||
634 | // Any failures during this process will cause us to default to updating /Applications/Second Life.app | ||
635 | { | ||
636 | FSRef myBundle; | ||
637 | |||
638 | err = GetCurrentProcess(&psn); | ||
639 | if(err == noErr) | ||
640 | { | ||
641 | err = GetProcessBundleLocation(&psn, &myBundle); | ||
642 | } | ||
643 | |||
644 | if(err == noErr) | ||
645 | { | ||
646 | // Sanity check: Make sure the name of the item referenced by targetRef is "Second Life.app". | ||
647 | FSRefMakePath(&myBundle, (UInt8*)target, sizeof(target)); | ||
648 | |||
649 | llinfos << "Updater bundle location: " << target << llendl; | ||
650 | } | ||
651 | |||
652 | // Our bundle should be in Second Life.app/Contents/Resources/AutoUpdater.app | ||
653 | // so we need to go up 3 levels to get the path to the main application bundle. | ||
654 | if(err == noErr) | ||
655 | { | ||
656 | err = FSGetParentRef(&myBundle, &targetRef); | ||
657 | } | ||
658 | if(err == noErr) | ||
659 | { | ||
660 | err = FSGetParentRef(&targetRef, &targetRef); | ||
661 | } | ||
662 | if(err == noErr) | ||
663 | { | ||
664 | err = FSGetParentRef(&targetRef, &targetRef); | ||
665 | } | ||
666 | |||
667 | // And once more to get the parent of the target | ||
668 | if(err == noErr) | ||
669 | { | ||
670 | err = FSGetParentRef(&targetRef, &targetParentRef); | ||
671 | } | ||
672 | |||
673 | if(err == noErr) | ||
674 | { | ||
675 | FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target)); | ||
676 | llinfos << "Path to target: " << target << llendl; | ||
677 | } | ||
678 | |||
679 | // Sanity check: make sure the target is a bundle with the right identifier | ||
680 | if(err == noErr) | ||
681 | { | ||
682 | CFURLRef targetURL = NULL; | ||
683 | CFBundleRef targetBundle = NULL; | ||
684 | CFStringRef targetBundleID = NULL; | ||
685 | |||
686 | // Assume the worst... | ||
687 | err = -1; | ||
688 | |||
689 | targetURL = CFURLCreateFromFSRef(NULL, &targetRef); | ||
690 | |||
691 | if(targetURL == NULL) | ||
692 | { | ||
693 | llinfos << "Error creating target URL." << llendl; | ||
694 | } | ||
695 | else | ||
696 | { | ||
697 | targetBundle = CFBundleCreate(NULL, targetURL); | ||
698 | } | ||
699 | |||
700 | if(targetBundle == NULL) | ||
701 | { | ||
702 | llinfos << "Failed to create target bundle." << llendl; | ||
703 | } | ||
704 | else | ||
705 | { | ||
706 | targetBundleID = CFBundleGetIdentifier(targetBundle); | ||
707 | } | ||
708 | |||
709 | if(targetBundleID == NULL) | ||
710 | { | ||
711 | llinfos << "Couldn't retrieve target bundle ID." << llendl; | ||
712 | } | ||
713 | else | ||
714 | { | ||
715 | if(CFStringCompare(targetBundleID, CFSTR("com.secondlife.indra.viewer"), 0) == kCFCompareEqualTo) | ||
716 | { | ||
717 | // This is the bundle we're looking for. | ||
718 | err = noErr; | ||
719 | replacingTarget = true; | ||
720 | } | ||
721 | else | ||
722 | { | ||
723 | llinfos << "Target bundle ID mismatch." << llendl; | ||
724 | } | ||
725 | } | ||
726 | |||
727 | // Don't release targetBundleID -- since we don't retain it, it's released when targetBundle is released. | ||
728 | if(targetURL != NULL) | ||
729 | CFRelease(targetURL); | ||
730 | if(targetBundle != NULL) | ||
731 | CFRelease(targetBundle); | ||
732 | |||
733 | } | ||
734 | |||
735 | // Make sure the target's parent directory is writable. | ||
736 | if(err == noErr) | ||
737 | { | ||
738 | if(!isDirWritable(targetParentRef)) | ||
739 | { | ||
740 | // Parent directory isn't writable. | ||
741 | llinfos << "Target parent directory not writable." << llendl; | ||
742 | err = -1; | ||
743 | replacingTarget = false; | ||
744 | } | ||
745 | } | ||
746 | |||
747 | if(err != noErr) | ||
748 | { | ||
749 | Boolean isDirectory; | ||
750 | llinfos << "Target search failed, defaulting to /Applications/" << gProductName << ".app." << llendl; | ||
751 | |||
752 | // Set up the parent directory | ||
753 | err = FSPathMakeRef((UInt8*)"/Applications", &targetParentRef, &isDirectory); | ||
754 | if((err != noErr) || (!isDirectory)) | ||
755 | { | ||
756 | // We're so hosed. | ||
757 | llinfos << "Applications directory not found, giving up." << llendl; | ||
758 | throw 0; | ||
759 | } | ||
760 | |||
761 | snprintf(target, sizeof(target), "/Applications/%s.app", gProductName); | ||
762 | |||
763 | memset(&targetRef, 0, sizeof(targetRef)); | ||
764 | err = FSPathMakeRef((UInt8*)target, &targetRef, NULL); | ||
765 | if(err == fnfErr) | ||
766 | { | ||
767 | // This is fine, just means we're not replacing anything. | ||
768 | err = noErr; | ||
769 | replacingTarget = false; | ||
770 | } | ||
771 | else | ||
772 | { | ||
773 | replacingTarget = true; | ||
774 | } | ||
775 | |||
776 | // Make sure the target's parent directory is writable. | ||
777 | if(err == noErr) | ||
778 | { | ||
779 | if(!isDirWritable(targetParentRef)) | ||
780 | { | ||
781 | // Parent directory isn't writable. | ||
782 | llinfos << "Target parent directory not writable." << llendl; | ||
783 | err = -1; | ||
784 | replacingTarget = false; | ||
785 | } | ||
786 | } | ||
787 | |||
788 | } | ||
789 | |||
790 | // If we haven't fixed all problems by this point, just bail. | ||
791 | if(err != noErr) | ||
792 | { | ||
793 | llinfos << "Unable to pick a target, giving up." << llendl; | ||
794 | throw 0; | ||
795 | } | ||
796 | } | ||
797 | |||
798 | // Find the volID of the volume the target resides on | ||
799 | { | ||
800 | FSCatalogInfo info; | ||
801 | err = FSGetCatalogInfo( | ||
802 | &targetParentRef, | ||
803 | kFSCatInfoVolume, | ||
804 | &info, | ||
805 | NULL, | ||
806 | NULL, | ||
807 | NULL); | ||
808 | |||
809 | if(err != noErr) | ||
810 | throw 0; | ||
811 | |||
812 | targetVol = info.volume; | ||
813 | } | ||
814 | |||
815 | // Find the temporary items and trash folders on that volume. | ||
816 | err = FSFindFolder( | ||
817 | targetVol, | ||
818 | kTrashFolderType, | ||
819 | true, | ||
820 | &trashFolderRef); | ||
821 | |||
822 | if(err != noErr) | ||
823 | throw 0; | ||
824 | |||
825 | err = FSFindFolder( | ||
826 | targetVol, | ||
827 | kTemporaryFolderType, | ||
828 | true, | ||
829 | &tempFolderRef); | ||
830 | |||
831 | if(err != noErr) | ||
832 | throw 0; | ||
833 | |||
834 | err = FSRefMakePath(&tempFolderRef, (UInt8*)temp, sizeof(temp)); | ||
835 | |||
836 | if(err != noErr) | ||
837 | throw 0; | ||
838 | |||
839 | temp[0] = '\0'; | ||
840 | strncat(temp, "/SecondLifeUpdate_XXXXXX", sizeof(temp) - 1); | ||
841 | if(mkdtemp(temp) == NULL) | ||
842 | { | ||
843 | throw 0; | ||
844 | } | ||
845 | |||
846 | strcpy(tempDir, temp); | ||
847 | |||
848 | llinfos << "tempDir is " << tempDir << llendl; | ||
849 | |||
850 | err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL); | ||
851 | |||
852 | if(err != noErr) | ||
853 | throw 0; | ||
854 | |||
855 | chdir(tempDir); | ||
856 | |||
857 | snprintf(temp, sizeof(temp), "SecondLife.dmg"); | ||
858 | |||
859 | downloadFile = fopen(temp, "wb"); | ||
860 | if(downloadFile == NULL) | ||
861 | { | ||
862 | throw 0; | ||
863 | } | ||
864 | |||
865 | { | ||
866 | CURL *curl = curl_easy_init(); | ||
867 | |||
868 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); | ||
869 | // curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback); | ||
870 | curl_easy_setopt(curl, CURLOPT_FILE, downloadFile); | ||
871 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); | ||
872 | curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &curl_progress_callback_func); | ||
873 | curl_easy_setopt(curl, CURLOPT_URL, gUpdateURL); | ||
874 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); | ||
875 | |||
876 | sendProgress(0, 1, CFSTR("Downloading...")); | ||
877 | |||
878 | CURLcode result = curl_easy_perform(curl); | ||
879 | |||
880 | curl_easy_cleanup(curl); | ||
881 | |||
882 | if(gCancelled) | ||
883 | { | ||
884 | llinfos << "User cancel, bailing out."<< llendl; | ||
885 | throw 0; | ||
886 | } | ||
887 | |||
888 | if(result != CURLE_OK) | ||
889 | { | ||
890 | llinfos << "Error " << result << " while downloading disk image."<< llendl; | ||
891 | throw 0; | ||
892 | } | ||
893 | |||
894 | fclose(downloadFile); | ||
895 | downloadFile = NULL; | ||
896 | } | ||
897 | |||
898 | sendProgress(0, 0, CFSTR("Mounting image...")); | ||
899 | LLFile::mkdir("mnt", 0700); | ||
900 | |||
901 | // NOTE: we could add -private at the end of this command line to keep the image from showing up in the Finder, | ||
902 | // but if our cleanup fails, this makes it much harder for the user to unmount the image. | ||
903 | LLString mountOutput; | ||
904 | FILE *mounter = popen("hdiutil attach SecondLife.dmg -mountpoint mnt", "r"); | ||
905 | |||
906 | if(mounter == NULL) | ||
907 | { | ||
908 | llinfos << "Failed to mount disk image, exiting."<< llendl; | ||
909 | throw 0; | ||
910 | } | ||
911 | |||
912 | // We need to scan the output from hdiutil to find the device node it uses to attach the disk image. | ||
913 | // If we don't have this information, we can't detach it later. | ||
914 | while(mounter != NULL) | ||
915 | { | ||
916 | size_t len = fread(temp, 1, sizeof(temp)-1, mounter); | ||
917 | temp[len] = 0; | ||
918 | mountOutput.append(temp); | ||
919 | if(len < sizeof(temp)-1) | ||
920 | { | ||
921 | // End of file or error. | ||
922 | if(pclose(mounter) != 0) | ||
923 | { | ||
924 | llinfos << "Failed to mount disk image, exiting."<< llendl; | ||
925 | throw 0; | ||
926 | } | ||
927 | mounter = NULL; | ||
928 | } | ||
929 | } | ||
930 | |||
931 | if(!mountOutput.empty()) | ||
932 | { | ||
933 | const char *s = mountOutput.c_str(); | ||
934 | char *prefix = "/dev/"; | ||
935 | char *sub = strstr(s, prefix); | ||
936 | |||
937 | if(sub != NULL) | ||
938 | { | ||
939 | sub += strlen(prefix); | ||
940 | sscanf(sub, "%s", deviceNode); | ||
941 | } | ||
942 | } | ||
943 | |||
944 | if(deviceNode[0] != 0) | ||
945 | { | ||
946 | llinfos << "Disk image attached on /dev/" << deviceNode << llendl; | ||
947 | } | ||
948 | else | ||
949 | { | ||
950 | llinfos << "Disk image device node not found!" << llendl; | ||
951 | } | ||
952 | |||
953 | // Get an FSRef to the new application on the disk image | ||
954 | FSRef sourceRef; | ||
955 | snprintf(temp, sizeof(temp), "%s/mnt/Second Life.app", tempDir); | ||
956 | |||
957 | llinfos << "Source application is: " << temp << llendl; | ||
958 | |||
959 | err = FSPathMakeRef((UInt8 *)temp, &sourceRef, NULL); | ||
960 | if(err != noErr) | ||
961 | throw 0; | ||
962 | |||
963 | FSRef asideRef; | ||
964 | char aside[MAX_PATH]; | ||
965 | |||
966 | // this will hold the name of the destination target | ||
967 | HFSUniStr255 appNameUniStr; | ||
968 | |||
969 | if(replacingTarget) | ||
970 | { | ||
971 | // Get the name of the target we're replacing | ||
972 | err = FSGetCatalogInfo(&targetRef, 0, NULL, &appNameUniStr, NULL, NULL); | ||
973 | if(err != noErr) | ||
974 | throw 0; | ||
975 | |||
976 | // Move aside old version (into work directory) | ||
977 | err = FSMoveObject(&targetRef, &tempDirRef, &asideRef); | ||
978 | if(err != noErr) | ||
979 | throw 0; | ||
980 | |||
981 | // Grab the path for later use. | ||
982 | err = FSRefMakePath(&asideRef, (UInt8*)aside, sizeof(aside)); | ||
983 | } | ||
984 | else | ||
985 | { | ||
986 | // Construct the name of the target based on the product name | ||
987 | char appName[MAX_PATH]; | ||
988 | snprintf(appName, sizeof(appName), "%s.app", gProductName); | ||
989 | utf8str_to_HFSUniStr255( &appNameUniStr, appName ); | ||
990 | } | ||
991 | |||
992 | sendProgress(0, 0, CFSTR("Copying files...")); | ||
993 | |||
994 | llinfos << "Starting copy..." << llendl; | ||
995 | |||
996 | // Copy the new version from the disk image to the target location. | ||
997 | err = FSCopyObject( | ||
998 | &sourceRef, | ||
999 | &targetParentRef, | ||
1000 | 0, | ||
1001 | kFSCatInfoNone, | ||
1002 | kDupeActionStandard, | ||
1003 | &appNameUniStr, | ||
1004 | false, | ||
1005 | false, | ||
1006 | NULL, | ||
1007 | NULL, | ||
1008 | &targetRef, | ||
1009 | NULL); | ||
1010 | |||
1011 | // Grab the path for later use. | ||
1012 | err = FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target)); | ||
1013 | if(err != noErr) | ||
1014 | throw 0; | ||
1015 | |||
1016 | llinfos << "Copy complete. Target = " << target << llendl; | ||
1017 | |||
1018 | if(err != noErr) | ||
1019 | { | ||
1020 | // Something went wrong during the copy. Attempt to put the old version back and bail. | ||
1021 | (void)FSDeleteObjects(&targetRef); | ||
1022 | if(replacingTarget) | ||
1023 | { | ||
1024 | (void)FSMoveObject(&asideRef, &targetParentRef, NULL); | ||
1025 | } | ||
1026 | throw 0; | ||
1027 | } | ||
1028 | else | ||
1029 | { | ||
1030 | // The update has succeeded. Clear the cache directory. | ||
1031 | |||
1032 | sendProgress(0, 0, CFSTR("Clearing cache...")); | ||
1033 | |||
1034 | llinfos << "Clearing cache..." << llendl; | ||
1035 | |||
1036 | char mask[LL_MAX_PATH]; | ||
1037 | sprintf(mask, "%s*.*", gDirUtilp->getDirDelimiter().c_str()); | ||
1038 | gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask); | ||
1039 | |||
1040 | llinfos << "Clear complete." << llendl; | ||
1041 | |||
1042 | } | ||
1043 | } | ||
1044 | catch(...) | ||
1045 | { | ||
1046 | if(!gCancelled) | ||
1047 | if(gFailure == noErr) | ||
1048 | gFailure = -1; | ||
1049 | } | ||
1050 | |||
1051 | // Failures from here on out are all non-fatal and not reported. | ||
1052 | sendProgress(0, 3, CFSTR("Cleaning up...")); | ||
1053 | |||
1054 | // Close disk image file if necessary | ||
1055 | if(downloadFile != NULL) | ||
1056 | { | ||
1057 | llinfos << "Closing download file." << llendl; | ||
1058 | |||
1059 | fclose(downloadFile); | ||
1060 | downloadFile = NULL; | ||
1061 | } | ||
1062 | |||
1063 | sendProgress(1, 3); | ||
1064 | // Unmount image | ||
1065 | if(deviceNode[0] != 0) | ||
1066 | { | ||
1067 | llinfos << "Detaching disk image." << llendl; | ||
1068 | |||
1069 | snprintf(temp, sizeof(temp), "hdiutil detach '%s'", deviceNode); | ||
1070 | system(temp); | ||
1071 | } | ||
1072 | |||
1073 | sendProgress(2, 3); | ||
1074 | |||
1075 | // Move work directory to the trash | ||
1076 | if(tempDir[0] != 0) | ||
1077 | { | ||
1078 | // chdir("/"); | ||
1079 | // FSDeleteObjects(tempDirRef); | ||
1080 | |||
1081 | llinfos << "Moving work directory to the trash." << llendl; | ||
1082 | |||
1083 | err = FSMoveObject(&tempDirRef, &trashFolderRef, NULL); | ||
1084 | |||
1085 | // snprintf(temp, sizeof(temp), "rm -rf '%s'", tempDir); | ||
1086 | // printf("%s\n", temp); | ||
1087 | // system(temp); | ||
1088 | } | ||
1089 | |||
1090 | if(!gCancelled && !gFailure && (target[0] != 0)) | ||
1091 | { | ||
1092 | llinfos << "Touching application bundle." << llendl; | ||
1093 | |||
1094 | snprintf(temp, sizeof(temp), "touch '%s'", target); | ||
1095 | system(temp); | ||
1096 | |||
1097 | llinfos << "Launching updated application." << llendl; | ||
1098 | |||
1099 | snprintf(temp, sizeof(temp), "open '%s'", target); | ||
1100 | system(temp); | ||
1101 | } | ||
1102 | |||
1103 | sendDone(); | ||
1104 | |||
1105 | return(NULL); | ||
1106 | } | ||