aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/mac_updater/mac_updater.cpp
diff options
context:
space:
mode:
authorJacek Antonelli2008-08-15 23:44:46 -0500
committerJacek Antonelli2008-08-15 23:44:46 -0500
commit38d6d37f2d982fa959e9e8a4a3f7e1ccfad7b5d4 (patch)
treeadca584755d22ca041a2dbfc35d4eca01f70b32c /linden/indra/mac_updater/mac_updater.cpp
parentREADME.txt (diff)
downloadmeta-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.cpp1106
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
53enum
54{
55 kEventClassCustom = 'Cust',
56 kEventCustomProgress = 'Prog',
57 kEventParamCustomCurValue = 'Cur ',
58 kEventParamCustomMaxValue = 'Max ',
59 kEventParamCustomText = 'Text',
60 kEventCustomDone = 'Done',
61};
62
63WindowRef gWindow = NULL;
64EventHandlerRef gEventHandler = NULL;
65OSStatus gFailure = noErr;
66Boolean gCancelled = false;
67
68char *gUserServer;
69char *gProductName;
70char gUpdateURL[2048];
71
72void *updatethreadproc(void*);
73
74pthread_t updatethread;
75
76OSStatus 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
112OSStatus 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
131OSStatus 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
202OSStatus 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
240OSStatus 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
296size_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
309int 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
325int 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
351int 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 &params,
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
502bool 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
548static 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
563int 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.
598void 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
611void *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}