Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge of changes from SVN repository:

- Changed NTSynchronousTask to also give the status return value and direct stderror output to the outputData.
- Changed includes so this builds as part of an app, too, not just as a framework
- Made sure SUAppcast's dealloc releases some leaked ivars.
- Added infoURL, extracted from link, that can point to a "more Info" page for download-less URLs
- Added support for version attribute on item so we can support update notifications that don't include an enclosure (e.g. paid upgrades, or upgrades that would require a system update)
- Added/improved a few description methods to ease debugging.
- Added SULog so one can ask for a special log with additional information when there are update issues.
- Added mayUpdateAndRestart for apps that absolutely, positively can't restart right now (e.g. cuz they're burning a CD and would produce a coaster).
- Added updaterWillRelaunchApplication delegate method, analogous to the notification. Useful to have app delegate quit helper apps during installation.
- Made SUBasicUpdateDriver's abortUpdate implicitly retain/autorelease the update driver, because the notification center otherwise releases it and it goes away, causing crashes in superclass's abortUpdate.
- Merge of SUKeepDownloadOnFailedInstallKey and SUFixedHTMLDisplaySizeKey.
- Avoid a few warnings about missing prototypes
- Be paranoid, hdiutil can verify the download again, so let it. Better for internal apps where we turn off DSA checks, too.
- SUHost has an -installationPath now, independent from the bundlePath, so one can normalize the app name from "MyApp 1.1b4" back to "MyApp" Users assume the file name contains the correct version number when there is one in it. Saves support a few round-trips each time.
- Be better at threading: Try calling non-thread-safe methods on main thread only, and don't assume delegates know when they need to be thread-safe, call them on main thread where possible.
- Added a method to put the old copy of the app in the trash. 1.5git changed in this spot, so I didn't actually merge the code that uses it back in yet.
- Fix version comparison so it doesn't get confused by bracketed build numbers in version strings
- Make sure cancel button is disabled during extraction, otherwise user would crash.
- Don't put auto-update window at floating window level. It's huge and can't be switched to background! If you're an NSBGOnly where you need that, turn it on only in that case, but don't generally do such nonsense.
- Hide release notes view if there aren't any.
- Test whether we are on dial-up before checking for updates in background. It's not nice to cause (possibly expensive) dial-up periodically.
- Temporarily comment out DSA complaints for easier testing.
- Don't store (possibly already invalidated) one-shot NSTimers in an ivar. It's bad style. Retain it instead.
- Decompress some monster expressions with nested method calls in ternary operators and nested in method calls again.
- Don't use implicit "id" for params or return types.
- finish_installation now puts up a progress window, so user knows update is still not finished.
- Use ThreadSafePreferences (included dummy version that uses regular prefs for projects that don't use ThreadSafePreferences).

- Todo later: Change finish_installation to be prettier.
  • Loading branch information...
commit 43a0a7db4f806985df073c899da783eacab75e73 1 parent bbcfe7e
@uliwitness uliwitness authored
Showing with 1,985 additions and 397 deletions.
  1. +15 −0 Elgato/ThreadSafePreferences.h
  2. +1 −1  NTSynchronousTask.h
  3. +18 −8 NTSynchronousTask.m
  4. +19 −1 SUAppcast.m
  5. +4 −0 SUAppcastItem.h
  6. +29 −3 SUAppcastItem.m
  7. +1 −1  SUAutomaticUpdateAlert.m
  8. +1 −0  SUAutomaticUpdateDriver.m
  9. +49 −31 SUBasicUpdateDriver.m
  10. +20 −0 SUConstants.h
  11. +7 −1 SUConstants.m
  12. +2 −0  SUDSAVerifier.h
  13. +6 −0 SUDSAVerifier.m
  14. +17 −3 SUDiskImageUnarchiver.m
  15. +5 −1 SUHost.h
  16. +20 −8 SUHost.m
  17. +2 −0  SUInstaller.h
  18. +37 −9 SUInstaller.m
  19. +31 −0 SULog.h
  20. +80 −0 SULog.m
  21. +5 −1 SUPackageInstaller.h
  22. +1 −0  SUPackageInstaller.m
  23. +15 −1 SUPipedUnarchiver.m
  24. +6 −1 SUPlainInstaller.h
  25. +6 −0 SUPlainInstaller.m
  26. +99 −37 SUPlainInstallerInternals.m
  27. +5 −1 SUScheduledUpdateDriver.m
  28. +12 −4 SUStandardVersionComparator.m
  29. +56 −0 SUStatus.nib/classes.nib
  30. +20 −0 SUStatus.nib/info.nib
  31. +12 −3 SUStatusController.m
  32. +2 −0  SUSystemProfiler.h
  33. +3 −1 SUUIBasedUpdateDriver.h
  34. +16 −2 SUUIBasedUpdateDriver.m
  35. +5 −1 SUUnarchiver.m
  36. +4 −1 SUUpdateAlert.h
  37. +79 −9 SUUpdateAlert.m
  38. +2 −1  SUUpdateDriver.m
  39. +1 −0  SUUpdatePermissionPrompt.m
  40. +21 −5 SUUpdater.h
  41. +94 −14 SUUpdater.m
  42. +3 −1 SUVersionComparisonProtocol.h
  43. +0 −245 Sparkle.xcodeproj/default.pbxuser
  44. +19 −0 Sparkle.xcodeproj/project.pbxproj
  45. +43 −0 Test Application/English.lproj/MainMenu.nib/classes.nib
  46. +21 −0 Test Application/English.lproj/MainMenu.nib/info.nib
  47. +29 −0 da.lproj/SUAutomaticUpdateAlert.nib/classes.nib
  48. +18 −0 da.lproj/SUAutomaticUpdateAlert.nib/info.nib
  49. 0  da.lproj/SUAutomaticUpdateAlert.strings
  50. +40 −0 da.lproj/SUUpdateAlert.nib/classes.nib
  51. +22 −0 da.lproj/SUUpdateAlert.nib/info.nib
  52. BIN  da.lproj/SUUpdateAlert.nib/keyedobjects.nib
  53. 0  da.lproj/SUUpdateAlert.strings
  54. +34 −0 da.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  55. +10 −0 da.lproj/SUUpdatePermissionPrompt.nib/data.dependency
  56. +16 −0 da.lproj/SUUpdatePermissionPrompt.nib/info.nib
  57. BIN  da.lproj/SUUpdatePermissionPrompt.nib/keyedobjects.nib
  58. 0  da.lproj/Sparkle.strings
  59. BIN  de.lproj/SUAutomaticUpdateAlert.strings
  60. BIN  de.lproj/SUUpdateAlert.strings
  61. +59 −0 de.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  62. +18 −0 de.lproj/SUUpdatePermissionPrompt.nib/info.nib
  63. BIN  de.lproj/SUUpdatePermissionPrompt.strings
  64. BIN  de.lproj/Sparkle.strings
  65. BIN  en.lproj/SUUpdatePermissionPrompt.strings
  66. BIN  es.lproj/SUAutomaticUpdateAlert.strings
  67. BIN  es.lproj/SUUpdateAlert.strings
  68. +59 −0 es.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  69. +18 −0 es.lproj/SUUpdatePermissionPrompt.nib/info.nib
  70. BIN  es.lproj/SUUpdatePermissionPrompt.strings
  71. +24 −2 finish_installation.m
  72. +59 −0 fr.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  73. +18 −0 fr.lproj/SUUpdatePermissionPrompt.nib/info.nib
  74. BIN  fr.lproj/SUUpdatePermissionPrompt.strings
  75. BIN  it.lproj/SUAutomaticUpdateAlert.strings
  76. BIN  it.lproj/SUUpdateAlert.strings
  77. +59 −0 it.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  78. +18 −0 it.lproj/SUUpdatePermissionPrompt.nib/info.nib
  79. BIN  it.lproj/SUUpdatePermissionPrompt.strings
  80. BIN  ja.lproj/SUUpdatePermissionPrompt.strings
  81. +59 −0 nl.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  82. +16 −0 nl.lproj/SUUpdatePermissionPrompt.nib/info.nib
  83. +50 −0 pt.lproj/SUAutomaticUpdateAlert.nib/classes.nib
  84. +18 −0 pt.lproj/SUAutomaticUpdateAlert.nib/info.nib
  85. BIN  pt.lproj/SUAutomaticUpdateAlert.nib/keyedobjects.nib
  86. +12 −0 pt.lproj/SUAutomaticUpdateAlert.strings
  87. +69 −0 pt.lproj/SUUpdateAlert.nib/classes.nib
  88. +18 −0 pt.lproj/SUUpdateAlert.nib/info.nib
  89. BIN  pt.lproj/SUUpdateAlert.nib/keyedobjects.nib
  90. BIN  pt.lproj/SUUpdateAlert.strings
  91. +59 −0 pt.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  92. +18 −0 pt.lproj/SUUpdatePermissionPrompt.nib/info.nib
  93. BIN  pt.lproj/SUUpdatePermissionPrompt.nib/keyedobjects.nib
  94. BIN  pt.lproj/SUUpdatePermissionPrompt.strings
  95. +95 −0 pt.lproj/Sparkle.strings
  96. +59 −0 ru.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  97. +18 −0 ru.lproj/SUUpdatePermissionPrompt.nib/info.nib
  98. +59 −0 sv.lproj/SUUpdatePermissionPrompt.nib/classes.nib
  99. +20 −0 sv.lproj/SUUpdatePermissionPrompt.nib/info.nib
  100. BIN  zh_CN.lproj/SUUpdatePermissionPrompt.strings
View
15 Elgato/ThreadSafePreferences.h
@@ -0,0 +1,15 @@
+// Header that shouldn't be included in anything but the Sparkle finish_installation tool
+// because it includes files also used in EyeTV, but doesn't need to be thread safe
+// as it's a single-threaded process anyway.
+
+#if !EYETV && !__TOAST__ && !TURBO
+
+#define ThreadSafePreferences_CopyAppValue CFPreferencesCopyAppValue
+#define ThreadSafePreferences_SetValue CFPreferencesSetValue
+#define ThreadSafePreferences_Synchronize CFPreferencesSynchronize
+
+#else
+
+#error This header shouldn't be included here!
+
+#endif
View
2  NTSynchronousTask.h
@@ -22,7 +22,7 @@
// pass nil for directory if not needed
// returns the result
-+ (NSData*)task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input;
++(int) task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input output: (NSData**)outData;
@end
View
26 NTSynchronousTask.m
@@ -6,7 +6,11 @@
// Copyright 2005 Steve Gehrman. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "NTSynchronousTask.h"
@interface NTSynchronousTask (Private)
@@ -44,6 +48,7 @@ - (id)init;
[[self task] setStandardInput:[self inputPipe]];
[[self task] setStandardOutput:[self outputPipe]];
+ [[self task] setStandardError:[self outputPipe]];
}
return self;
@@ -64,11 +69,13 @@ - (void)dealloc
[super dealloc];
}
-+ (NSData*)task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input;
++(int) task:(NSString*)toolPath directory:(NSString*)currentDirectory withArgs:(NSArray*)args input:(NSData*)input output: (NSData**)outData
{
// we need this wacky pool here, otherwise we run out of pipes, the pipes are internally autoreleased
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSData* result=nil;
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+ int taskResult = 0;
+ if( outData )
+ *outData = nil;
NS_DURING
{
@@ -76,20 +83,23 @@ + (NSData*)task:(NSString*)toolPath directory:(NSString*)currentDirectory withAr
[task run:toolPath directory:currentDirectory withArgs:args input:input];
- if ([task result] == 0)
- result = [[task output] retain];
+ taskResult = [task result];
+ if( outData )
+ *outData = [[task output] retain];
[task release];
}
NS_HANDLER;
+ taskResult = errCppGeneral;
NS_ENDHANDLER;
[pool drain];
// retained above
- [result autorelease];
+ if( outData )
+ [*outData autorelease];
- return result;
+ return taskResult;
}
@end
View
20 SUAppcast.m
@@ -6,8 +6,15 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUAppcast.h"
+#import "SUConstants.h"
+#import "SULog.h"
+
@interface SUAppcast (Private)
- (void)reportError:(NSError *)error;
@@ -19,7 +26,12 @@ @implementation SUAppcast
- (void)dealloc
{
[items release];
+ items = nil;
[userAgentString release];
+ userAgentString = nil;
+ [downloadFilename release];
+ downloadFilename = nil;
+
[super dealloc];
}
@@ -59,6 +71,12 @@ - (void)downloadDidFinish:(NSURLDownload *)download
BOOL failed = NO;
NSArray *xmlItems = nil;
NSMutableArray *appcastItems = [NSMutableArray array];
+
+ #if DEBUG
+ NSString* debugXML = [NSString stringWithContentsOfFile: downloadFilename encoding: NSUTF8StringEncoding error: nil];
+ SULog(@"<<<< XML >>>>\n%@\n>>>> XML <<<<", debugXML);
+ #endif
+
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
[[NSFileManager defaultManager] removeFileAtPath:downloadFilename handler:nil];
#else
View
4 SUAppcastItem.h
@@ -24,6 +24,8 @@
NSString *displayVersionString;
NSDictionary *propertiesDictionary;
+
+ NSURL *infoURL; // UK 2007-08-31
}
// Initializes with data from a dictionary provided by the RSS class.
@@ -43,6 +45,8 @@
// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions.
- (NSDictionary *)propertiesDictionary;
+- (NSURL *)infoURL; // UK 2007-08-31
+
@end
#endif
View
32 SUAppcastItem.m
@@ -6,7 +6,11 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUAppcastItem.h"
@implementation SUAppcastItem
@@ -101,6 +105,16 @@ - (void)setMinimumSystemVersion:(NSString *)systemVersionString
minimumSystemVersion = [systemVersionString copy];
}
+
+- (NSURL *)infoURL { return [[infoURL retain] autorelease]; } // UK 2007-08-31 (whole method)
+
+- (void)setInfoURL:(NSURL *)aFileURL // UK 2007-08-31 (whole method)
+{
+ if( aFileURL == infoURL ) return;
+ [infoURL release];
+ infoURL = [aFileURL copy];
+}
+
- initWithDictionary:(NSDictionary *)dict
{
return [self initWithDictionary:dict failureReason:nil];
@@ -130,7 +144,9 @@ - (void)setMinimumSystemVersion:(NSString *)systemVersionString
// The big caveat with this is that you can't have underscores in your version strings, as that'll confuse Sparkle.
// Feel free to change the separator string to a hyphen or something more suited to your needs if you like.
NSString *newVersion = [enclosure objectForKey:@"sparkle:version"];
- if (newVersion == nil) // no sparkle:version attribute
+ if( newVersion == nil )
+ newVersion = [dict objectForKey:@"sparkle:version"]; // UK 2007-08-31 Get version from the item, in case it's a download-less item (i.e. paid upgrade).
+ if (newVersion == nil) // no sparkle:version attribute anywhere?
{
// Separate the url by underscores and take the last component, as that'll be closest to the end,
// then we remove the extension. Hopefully, this will be the version.
@@ -160,7 +176,16 @@ - (void)setMinimumSystemVersion:(NSString *)systemVersionString
[self setDate:[dict objectForKey:@"pubDate"]];
[self setItemDescription:[dict objectForKey:@"description"]];
- [self setFileURL:[NSURL URLWithString:[[enclosure objectForKey:@"url"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
+ NSString* theInfoURL = [dict objectForKey:@"link"];
+ if( theInfoURL )
+ {
+ if( ![theInfoURL isKindOfClass: [NSString class]] )
+ NSLog(@"SUAppcastItem -initWithDictionary: Info URL is not of valid type.");
+ else
+ [self setInfoURL:[NSURL URLWithString:theInfoURL]];
+ }
+
+ [self setFileURL:[NSURL URLWithString:[[enclosure objectForKey:@"url"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
[self setDSASignature:[enclosure objectForKey:@"sparkle:dsaSignature"]];
[self setVersionString:newVersion];
@@ -193,6 +218,7 @@ - (void)dealloc
[self setFileURL:nil];
[self setVersionString:nil];
[self setDisplayVersionString:nil];
+ [self setInfoURL:nil];
[propertiesDictionary release];
[super dealloc];
}
View
2  SUAutomaticUpdateAlert.m
@@ -33,7 +33,7 @@ - (void)dealloc
[super dealloc];
}
-- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [host bundlePath]]; }
+- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [host bundlePath], [host installationPath]]; }
- (IBAction)installNow:sender
{
View
1  SUAutomaticUpdateDriver.m
@@ -10,6 +10,7 @@
#import "SUAutomaticUpdateAlert.h"
#import "SUHost.h"
+#import "SUConstants.h"
@implementation SUAutomaticUpdateDriver
View
80 SUBasicUpdateDriver.m
@@ -13,6 +13,9 @@
#import "SUInstaller.h"
#import "SUStandardVersionComparator.h"
#import "SUUnarchiver.h"
+#import "SUConstants.h"
+#import "SULog.h"
+
@implementation SUBasicUpdateDriver
@@ -165,6 +168,7 @@ - (void)download:(NSURLDownload *)d decideDestinationWithSuggestedFilename:(NSSt
- (void)downloadDidFinish:(NSURLDownload *)d
{
+ #if 0 // +++
// New in Sparkle 1.5: we're now checking signatures on all non-secure downloads, where "secure" is defined as both the appcast and the download being transmitted over SSL.
NSURL *downloadURL = [[d request] URL];
if (!(([[downloadURL scheme] isEqualToString:@"https"] && [[appcastURL scheme] isEqualToString:@"https"]) ||
@@ -176,6 +180,7 @@ - (void)downloadDidFinish:(NSURLDownload *)d
return;
}
}
+ #endif
[self extractUpdate];
}
@@ -200,7 +205,7 @@ - (void)extractUpdate
SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForPath:downloadPath];
if (!unarchiver)
{
- NSLog(@"Sparkle Error: No valid unarchiver for %@!", downloadPath);
+ SULog(@"Sparkle Error: No valid unarchiver for %@!", downloadPath);
[self unarchiverDidFail:nil];
return;
}
@@ -231,7 +236,11 @@ - (void)installUpdate
NSString *relaunchPathToCopy = [[NSBundle bundleForClass:[self class]] pathForResource:@"finish_installation" ofType:@""];
NSString *appSupportFolder = [[@"~/Library/Application Support/" stringByExpandingTildeInPath] stringByAppendingPathComponent: [host name]];
NSString *targetPath = [appSupportFolder stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
- [[NSFileManager defaultManager] createDirectoryAtPath: targetPath withIntermediateDirectories: YES attributes: [NSDictionary dictionary] error: NULL];
+#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+ [[NSFileManager defaultManager] createDirectoryAtPath: [targetPath stringByDeletingLastPathComponent] attributes: [NSDictionary dictionary]];
+#else
+ [[NSFileManager defaultManager] createDirectoryAtPath: [targetPath stringByDeletingLastPathComponent] withIntermediateDirectories: YES attributes: [NSDictionary dictionary] error: NULL];
+#endif
// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
NSError *error = nil;
@@ -251,36 +260,43 @@ - (void)installUpdate
- (void)installAndRelaunchWithTool
{
- // Give the host app an opportunity to postpone the relaunch.
- static BOOL postponedOnce = NO;
- if (!postponedOnce && [[updater delegate] respondsToSelector:@selector(updater:shouldPostponeRelaunchForUpdate:untilInvoking:)])
+ BOOL mayRelaunchAtAll = [updater mayUpdateAndRestart];
+
+ if( mayRelaunchAtAll )
{
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(relaunchHostApp)]];
- [invocation setSelector:@selector(relaunchHostApp)];
- [invocation setTarget:self];
- postponedOnce = YES;
- if ([[updater delegate] updater:updater shouldPostponeRelaunchForUpdate:updateItem untilInvoking:invocation])
- return;
- }
-
- [[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
- if ([[updater delegate] respondsToSelector:@selector(updaterWillRelaunchApplication:)])
- [[updater delegate] updaterWillRelaunchApplication:updater];
+ // Give the host app an opportunity to postpone the relaunch.
+ static BOOL postponedOnce = NO;
+ if (!postponedOnce && [[updater delegate] respondsToSelector:@selector(updater:shouldPostponeRelaunchForUpdate:untilInvoking:)])
+ {
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:@selector(relaunchHostApp)]];
+ [invocation setSelector:@selector(relaunchHostApp)];
+ [invocation setTarget:self];
+ postponedOnce = YES;
+ if ([[updater delegate] updater:updater shouldPostponeRelaunchForUpdate:updateItem untilInvoking:invocation])
+ return;
+ }
- if(!relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:relaunchPath])
- {
- // Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
- [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [host name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]];
- // We intentionally don't abandon the update here so that the host won't initiate another.
- return;
- }
+ [[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
+ if ([[updater delegate] respondsToSelector:@selector(updaterWillRelaunchApplication:)])
+ [[updater delegate] updaterWillRelaunchApplication:updater];
- NSString *pathToRelaunch = [host bundlePath];
- if ([[updater delegate] respondsToSelector:@selector(pathToRelaunchForUpdater:)])
- pathToRelaunch = [[updater delegate] pathToRelaunchForUpdater:updater];
- [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], [downloadPath stringByDeletingLastPathComponent], nil]];
-
- [NSApp terminate:self];
+ if(!relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:relaunchPath])
+ {
+ // Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
+ [self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [host name]], NSLocalizedDescriptionKey, [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", relaunchPath], NSLocalizedFailureReasonErrorKey, nil]]];
+ // We intentionally don't abandon the update here so that the host won't initiate another.
+ return;
+ }
+
+ NSString *pathToRelaunch = [host bundlePath];
+ if ([[updater delegate] respondsToSelector:@selector(pathToRelaunchForUpdater:)])
+ pathToRelaunch = [[updater delegate] pathToRelaunchForUpdater:updater];
+ [NSTask launchedTaskWithLaunchPath:relaunchPath arguments:[NSArray arrayWithObjects:pathToRelaunch, [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]], [downloadPath stringByDeletingLastPathComponent], nil]];
+
+ [NSApp terminate:self];
+ }
+ else
+ [self abortUpdate];
}
- (void)cleanUp
@@ -295,11 +311,13 @@ - (void)cleanUp
- (void)installerForHost:(SUHost *)aHost failedWithError:(NSError *)error
{
if (aHost != host) { return; }
+ [[NSFileManager defaultManager] removeFileAtPath:relaunchPath handler:NULL]; // Clean up the copied relauncher.
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil), NSLocalizedDescriptionKey, [error localizedDescription], NSLocalizedFailureReasonErrorKey, nil]]];
}
- (void)abortUpdate
{
+ [[self retain] autorelease]; // In case the notification center was the last one holding on to us.
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super abortUpdate];
}
@@ -307,9 +325,9 @@ - (void)abortUpdate
- (void)abortUpdateWithError:(NSError *)error
{
if ([error code] != SUNoUpdateError) // Let's not bother logging this.
- NSLog(@"Sparkle Error: %@", [error localizedDescription]);
+ SULog(@"Sparkle Error: %@", [error localizedDescription]);
if ([error localizedFailureReason])
- NSLog(@"Sparkle Error (continued): %@", [error localizedFailureReason]);
+ SULog(@"Sparkle Error (continued): %@", [error localizedFailureReason]);
if (download)
[download cancel];
[self abortUpdate];
View
20 SUConstants.h
@@ -11,9 +11,18 @@
#define SUCONSTANTS_H
+// -----------------------------------------------------------------------------
+// Notifications:
+// -----------------------------------------------------------------------------
+
extern NSString *SUUpdaterWillRestartNotification;
+
extern NSString *SUTechnicalErrorInformationKey;
+// -----------------------------------------------------------------------------
+// PList keys::
+// -----------------------------------------------------------------------------
+
extern NSString *SUFeedURLKey;
extern NSString *SUHasLaunchedBeforeKey;
extern NSString *SUShowReleaseNotesKey;
@@ -29,6 +38,12 @@ extern NSString *SUEnableAutomaticChecksKeyOld;
extern NSString *SUEnableSystemProfilingKey;
extern NSString *SUSendProfileInfoKey;
extern NSString *SULastProfileSubmitDateKey;
+extern NSString *SUFixedHTMLDisplaySizeKey;
+extern NSString *SUKeepDownloadOnFailedInstallKey;
+
+// -----------------------------------------------------------------------------
+// Errors:
+// -----------------------------------------------------------------------------
extern NSString *SUSparkleErrorDomain;
// Appcast phase errors.
@@ -53,6 +68,11 @@ extern OSStatus SURelaunchError;
extern OSStatus SUInstallationError;
extern OSStatus SUDowngradeError;
+
+// -----------------------------------------------------------------------------
+// NSInteger fixer-upper:
+// -----------------------------------------------------------------------------
+
// NSInteger is a type that was added to Leopard.
// Here is some glue to ensure that NSInteger will work with pre-10.5 SDKs:
#ifndef NSINTEGER_DEFINED
View
8 SUConstants.m
@@ -6,7 +6,11 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUConstants.h"
NSString *SUUpdaterWillRestartNotification = @"SUUpdaterWillRestartNotificationName";
@@ -28,6 +32,8 @@
NSString *SUEnableAutomaticChecksKeyOld = @"SUCheckAtStartup";
NSString *SUSendProfileInfoKey = @"SUSendProfileInfo";
NSString *SULastProfileSubmitDateKey = @"SULastProfileSubmissionDate";
+NSString *SUFixedHTMLDisplaySizeKey = @"SUFixedHTMLDisplaySize";
+NSString *SUKeepDownloadOnFailedInstallKey = @"SUKeepDownloadOnFailedInstall";
NSString *SUSparkleErrorDomain = @"SUSparkleErrorDomain";
OSStatus SUAppcastParseError = 1000;
View
2  SUDSAVerifier.h
@@ -9,6 +9,8 @@
#ifndef SUDSAVERIFIER_H
#define SUDSAVERIFIER_H
+#import <Cocoa/Cocoa.h>
+
// For the paranoid folks!
@interface SUDSAVerifier : NSObject {}
+ (BOOL)validatePath:(NSString *)path withEncodedDSASignature:(NSString *)encodedSignature withPublicDSAKey:(NSString *)pkeyString;
View
6 SUDSAVerifier.m
@@ -9,6 +9,7 @@
// DSA stuff adapted from code provided by Allan Odgaard. Thanks, Allan!
#import "SUDSAVerifier.h"
+#import <Cocoa/Cocoa.h>
#import <stdio.h>
#import <openssl/evp.h>
@@ -17,6 +18,11 @@
#import <openssl/rsa.h>
#import <openssl/sha.h>
+// Alex: Avoid prototype warning
+long b64decode(unsigned char* str);
+EVP_PKEY* load_dsa_key(char *key);
+
+
long b64decode(unsigned char* str)
{
unsigned char *cur, *start;
View
20 SUDiskImageUnarchiver.m
@@ -9,8 +9,10 @@
#import "SUDiskImageUnarchiver.h"
#import "SUUnarchiver_Private.h"
#import "NTSynchronousTask.h"
+#import "SULog.h"
#import <CoreServices/CoreServices.h>
+
@implementation SUDiskImageUnarchiver
+ (BOOL)_canUnarchivePath:(NSString *)path
@@ -25,9 +27,13 @@ - (void)start
- (void)_extractDMG
{
+ // GETS CALLED ON NON-MAIN THREAD!!!
+
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL mountedSuccessfully = NO;
+ SULog(@"Extracting %@ as a DMG", archivePath);
+
// get a unique mount point path
NSString *mountPointName = nil;
NSString *mountPoint = nil;
@@ -46,13 +52,19 @@ - (void)_extractDMG
}
while (noErr == FSPathMakeRefWithOptions((UInt8 *)[mountPoint fileSystemRepresentation], kFSPathMakeRefDoNotFollowLeafSymlink, &tmpRef, NULL));
- NSArray* arguments = [NSArray arrayWithObjects:@"attach", archivePath, @"-mountpoint", mountPoint, @"-noverify", @"-nobrowse", @"-noautoopen", nil];
+ NSArray* arguments = [NSArray arrayWithObjects:@"attach", archivePath, @"-mountpoint", mountPoint, /*@"-noverify",*/ @"-nobrowse", @"-noautoopen", nil];
// set up a pipe and push "yes" (y works too), this will accept any license agreement crap
// not every .dmg needs this, but this will make sure it works with everyone
NSData* yesData = [[[NSData alloc] initWithBytes:"yes\n" length:4] autorelease];
- NSData *result = [NTSynchronousTask task:@"/usr/bin/hdiutil" directory:@"/" withArgs:arguments input:yesData];
- if (!result) goto reportError;
+ NSData *output = nil;
+ int returnCode = [NTSynchronousTask task:@"/usr/bin/hdiutil" directory:@"/" withArgs:arguments input:yesData output: &output];
+ if ( returnCode != 0 )
+ {
+ NSString* resultStr = output ? [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease] : nil;
+ SULog( @"hdiutil failed with code: %d data: <<%@>>", returnCode, resultStr );
+ goto reportError;
+ }
mountedSuccessfully = YES;
// Now that we've mounted it, we need to copy out its contents.
@@ -75,6 +87,8 @@ - (void)_extractDMG
finally:
if (mountedSuccessfully)
[NSTask launchedTaskWithLaunchPath:@"/usr/bin/hdiutil" arguments:[NSArray arrayWithObjects:@"detach", mountPoint, @"-force", nil]];
+ else
+ SULog(@"Can't mount DMG %@",archivePath);
[pool drain];
}
View
6 SUHost.h
@@ -5,7 +5,10 @@
// Copyright 2008 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
@interface SUHost : NSObject
{
@@ -17,6 +20,7 @@
- (id)initWithBundle:(NSBundle *)aBundle;
- (NSBundle *)bundle;
- (NSString *)bundlePath;
+- (NSString *)installationPath;
- (NSString *)name;
- (NSString *)version;
- (NSString *)displayVersion;
View
28 SUHost.m
@@ -10,6 +10,9 @@
#import "SUConstants.h"
#import "SUSystemProfiler.h"
#import <sys/mount.h> // For statfs for isRunningOnReadOnlyVolume
+#import "ThreadSafePreferences.h"
+#import "SULog.h"
+
@implementation SUHost
@@ -31,7 +34,7 @@ - (void)dealloc
[super dealloc];
}
-- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [self bundlePath]]; }
+- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [self bundlePath], [self installationPath]]; }
- (NSBundle *)bundle
{
@@ -43,6 +46,15 @@ - (NSString *)bundlePath
return [bundle bundlePath];
}
+- (NSString *)installationPath
+{
+#if 1
+ return [[[bundle bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent: [NSString stringWithFormat: @"%@.%@", [bundle objectForInfoDictionaryKey:@"CFBundleName"], [[bundle bundlePath] pathExtension]]];
+#else
+ return [bundle bundlePath];
+#endif
+}
+
- (NSString *)name
{
NSString *name = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
@@ -115,7 +127,7 @@ - (NSString *)publicDSAKey
// More likely, we've got a reference to a Resources file by filename:
NSString *keyFilename = [self objectForInfoDictionaryKey:SUPublicDSAKeyFileKey];
if (!keyFilename) { return nil; }
- NSError *ignoreErr;
+ NSError *ignoreErr = nil;
return [NSString stringWithContentsOfFile:[bundle pathForResource:keyFilename ofType:nil] encoding:NSASCIIStringEncoding error: &ignoreErr];
}
@@ -142,7 +154,7 @@ - (id)objectForUserDefaultsKey:(NSString *)defaultName
if (bundle == [NSBundle mainBundle])
return [[NSUserDefaults standardUserDefaults] objectForKey:defaultName];
- CFPropertyListRef obj = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]);
+ CFPropertyListRef obj = ThreadSafePreferences_CopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]);
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
return [NSMakeCollectable(obj) autorelease];
#else
@@ -159,8 +171,8 @@ - (void)setObject:(id)value forUserDefaultsKey:(NSString *)defaultName;
}
else
{
- CFPreferencesSetValue((CFStringRef)defaultName, value, (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
- CFPreferencesSynchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ ThreadSafePreferences_SetValue((CFStringRef)defaultName, value, (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ ThreadSafePreferences_Synchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
}
}
@@ -170,7 +182,7 @@ - (BOOL)boolForUserDefaultsKey:(NSString *)defaultName
return [[NSUserDefaults standardUserDefaults] boolForKey:defaultName];
BOOL value;
- CFPropertyListRef plr = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]);
+ CFPropertyListRef plr = ThreadSafePreferences_CopyAppValue((CFStringRef)defaultName, (CFStringRef)[bundle bundleIdentifier]);
if (plr == NULL)
value = NO;
else
@@ -190,8 +202,8 @@ - (void)setBool:(BOOL)value forUserDefaultsKey:(NSString *)defaultName
}
else
{
- CFPreferencesSetValue((CFStringRef)defaultName, (CFBooleanRef)[NSNumber numberWithBool:value], (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
- CFPreferencesSynchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ ThreadSafePreferences_SetValue((CFStringRef)defaultName, (CFBooleanRef)[NSNumber numberWithBool:value], (CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ ThreadSafePreferences_Synchronize((CFStringRef)[bundle bundleIdentifier], kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
}
}
View
2  SUInstaller.h
@@ -16,6 +16,8 @@
@interface SUInstaller : NSObject { }
+ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously versionComparator:(id <SUVersionComparison>)comparator;
+ (void)_finishInstallationWithResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:delegate;
+
++ (NSString*)updateFolder;
@end
@interface NSObject (SUInstallerDelegateInformalProtocol)
View
46 SUInstaller.m
@@ -11,9 +11,18 @@
#import "SUPackageInstaller.h"
#import "SUHost.h"
#import "SUConstants.h"
+#import "SULog.h"
+
@implementation SUInstaller
+static NSString* sUpdateFolder = nil;
+
++(NSString*) updateFolder
+{
+ return sUpdateFolder;
+}
+
+ (BOOL)_isAliasFolderAtPath:(NSString *)path
{
FSRef fileRef;
@@ -34,19 +43,23 @@ + (BOOL)_isAliasFolderAtPath:(NSString *)path
}
-+ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously versionComparator:(id <SUVersionComparison>)comparator
++ (void)installFromUpdateFolder:(NSString *)inUpdateFolder overHost:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously versionComparator:(id <SUVersionComparison>)comparator
{
// Search subdirectories for the application
- NSString *currentFile, *newAppDownloadPath = nil,
+ NSString *currentFile,
+ *newAppDownloadPath = nil,
*bundleFileName = [[host bundlePath] lastPathComponent],
- *alternateBundleFileName = [[host name] stringByAppendingPathExtension: [[host bundlePath] pathExtension]];
+ *alternateBundleFileName = [[host name] stringByAppendingPathExtension:[[host bundlePath] pathExtension]];
BOOL isPackage = NO;
NSString *fallbackPackagePath = nil;
- NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: updateFolder];
+ NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath: inUpdateFolder];
+
+ [sUpdateFolder release];
+ sUpdateFolder = [inUpdateFolder retain];
while ((currentFile = [dirEnum nextObject]))
{
- NSString *currentPath = [updateFolder stringByAppendingPathComponent:currentFile];
+ NSString *currentPath = [inUpdateFolder stringByAppendingPathComponent:currentFile];
if ([[currentFile lastPathComponent] isEqualToString:bundleFileName] ||
[[currentFile lastPathComponent] isEqualToString:alternateBundleFileName]) // We found one!
{
@@ -106,9 +119,11 @@ + (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host
+ (void)_mdimportHost:(SUHost *)host
{
+ // *** GETS CALLED ON NON-MAIN THREAD!
+
NSTask *mdimport = [[[NSTask alloc] init] autorelease];
[mdimport setLaunchPath:@"/usr/bin/mdimport"];
- [mdimport setArguments:[NSArray arrayWithObject:[host bundlePath]]];
+ [mdimport setArguments:[NSArray arrayWithObject:[host installationPath]]];
@try
{
[mdimport launch];
@@ -116,23 +131,36 @@ + (void)_mdimportHost:(SUHost *)host
@catch (NSException * launchException)
{
// No big deal.
- NSLog(@"Sparkle Error: %@", [launchException description]);
+ SULog(@"Sparkle Error: %@", [launchException description]);
}
}
+
+#define SUNotifyDictHostKey @"SUNotifyDictHost"
+#define SUNotifyDictErrorKey @"SUNotifyDictError"
+#define SUNotifyDictDelegateKey @"SUNotifyDictDelegate"
+
+ (void)_finishInstallationWithResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:delegate
{
+ // *** GETS CALLED ON NON-MAIN THREAD!
+
if (result == YES)
{
[self _mdimportHost:host];
if ([delegate respondsToSelector:@selector(installerFinishedForHost:)])
- [delegate installerFinishedForHost:host];
+ [delegate performSelectorOnMainThread: @selector(installerFinishedForHost:) withObject: host waitUntilDone: NO];
}
else
{
if ([delegate respondsToSelector:@selector(installerForHost:failedWithError:)])
- [delegate installerForHost:host failedWithError:error];
+ [self performSelectorOnMainThread: @selector(notifyDelegateOfFailure:) withObject: [NSDictionary dictionaryWithObjectsAndKeys: host, SUNotifyDictHostKey, error, SUNotifyDictErrorKey, delegate, SUNotifyDictDelegateKey, nil] waitUntilDone: NO];
}
}
+
++(void) notifyDelegateOfFailure: (NSDictionary*)dict
+{
+ [[dict objectForKey: SUNotifyDictDelegateKey] installerForHost: [dict objectForKey: SUNotifyDictHostKey] failedWithError: [dict objectForKey: SUNotifyDictErrorKey]];
+}
+
@end
View
31 SULog.h
@@ -0,0 +1,31 @@
+/*
+ * SULog.h
+ * EyeTV
+ *
+ * Created by Uli Kusterer on 12/03/2009.
+ * Copyright 2008 Elgato Systems GmbH. All rights reserved.
+ *
+ */
+
+/*
+ Log output for troubleshooting Sparkle failures on end-user machines.
+ Your tech support will hug you if you tell them about this.
+*/
+
+#pragma once
+
+// -----------------------------------------------------------------------------
+// Headers:
+// -----------------------------------------------------------------------------
+
+#include <Foundation/Foundation.h>
+
+
+// -----------------------------------------------------------------------------
+// Prototypes:
+// -----------------------------------------------------------------------------
+
+void SUClearLog( void );
+void SULog( NSString* format, ... );
+
+
View
80 SULog.m
@@ -0,0 +1,80 @@
+/*
+ * SULog.m
+ * EyeTV
+ *
+ * Created by Uli Kusterer on 12/03/2009.
+ * Copyright 2009 Elgato Systems GmbH. All rights reserved.
+ *
+ */
+
+// -----------------------------------------------------------------------------
+// Headers:
+// -----------------------------------------------------------------------------
+
+#include "SULog.h"
+
+
+// -----------------------------------------------------------------------------
+// Constants:
+// -----------------------------------------------------------------------------
+
+#define LOG_FILE_PATH @"~/Library/Logs/SparkleUpdateLog.log"
+
+
+// -----------------------------------------------------------------------------
+// SUClearLog:
+// Erase the log at the start of an update. We don't want to litter the
+// user's hard disk with logging data that's mostly unused, so each app
+// should clear the log before it starts updating, so only the most recent
+// update is kept around.
+//
+// TAKES:
+// sender - Object that sent this message, typically of type X.
+//
+// GIVES:
+// param - who owns the returned value?
+// result - same here.
+// -----------------------------------------------------------------------------
+
+void SUClearLog( void )
+{
+ FILE* logfile = fopen([[LOG_FILE_PATH stringByExpandingTildeInPath] fileSystemRepresentation],"w");
+ if( logfile )
+ fclose(logfile);
+ else
+ NSLog(@"----- Sparkle Log -----");
+}
+
+
+// -----------------------------------------------------------------------------
+// SULog:
+// Like NSLog, but logs to one specific log file. Each line is prefixed
+// with the current date and time, to help in regressing issues.
+//
+// TAKES:
+// format - NSLog/printf-style format string.
+// ... - More parameters depending on format string's contents.
+// -----------------------------------------------------------------------------
+
+void SULog( NSString* format, ... )
+{
+ va_list ap;
+ va_start(ap, format);
+ NSString* theStr = [[[NSString alloc] initWithFormat: format arguments: ap] autorelease];
+ FILE* logfile = fopen([[LOG_FILE_PATH stringByExpandingTildeInPath] fileSystemRepresentation],"a");
+ if( !logfile )
+ NSLog( @"%@",theStr );
+ else
+ {
+ theStr = [NSString stringWithFormat: @"%@: %@", [NSDate date], theStr];
+ NSData* theData = [theStr dataUsingEncoding: NSUTF8StringEncoding];
+ char newlineChar = '\n';
+ fwrite( [theData bytes], 1, [theData length], logfile );
+ fwrite( &newlineChar, 1, 1, logfile ); // Append a newline.
+ fclose( logfile );
+ logfile = NULL;
+ }
+ va_end(ap);
+}
+
+
View
6 SUPackageInstaller.h
@@ -9,7 +9,11 @@
#ifndef SUPACKAGEINSTALLER_H
#define SUPACKAGEINSTALLER_H
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUPlainInstaller.h"
@interface SUPackageInstaller : SUPlainInstaller { }
View
1  SUPackageInstaller.m
@@ -7,6 +7,7 @@
//
#import "SUPackageInstaller.h"
+#import <Cocoa/Cocoa.h>
#import "SUConstants.h"
#ifndef NSAppKitVersionNumber10_4
View
16 SUPipedUnarchiver.m
@@ -8,6 +8,8 @@
#import "SUPipedUnarchiver.h"
#import "SUUnarchiver_Private.h"
+#import "SULog.h"
+
@implementation SUPipedUnarchiver
@@ -18,7 +20,7 @@ + (SEL)_selectorConformingToTypeOfPath:(NSString *)path
typeSelectorDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:@"_extractZIP", @".zip", @"_extractTAR", @".tar",
@"_extractTGZ", @".tar.gz", @"_extractTGZ", @".tgz",
@"_extractTBZ", @".tar.bz2", @"_extractTBZ", @".tbz", nil] retain];
-
+
NSString *lastPathComponent = [path lastPathComponent];
NSEnumerator *typeEnumerator = [typeSelectorDictionary keyEnumerator];
id currentType;
@@ -44,6 +46,10 @@ + (BOOL)_canUnarchivePath:(NSString *)path
// This method abstracts the types that use a command line tool piping data from stdin.
- (void)_extractArchivePipingDataToCommand:(NSString *)command
{
+ // *** GETS CALLED ON NON-MAIN THREAD!!!
+
+ SULog(@"Extracting %@ using '%@'",archivePath,command);
+
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
FILE *fp = NULL, *cmdFP = NULL;
@@ -97,21 +103,29 @@ - (void)_extractArchivePipingDataToCommand:(NSString *)command
- (void)_extractTAR
{
+ // *** GETS CALLED ON NON-MAIN THREAD!!!
+
return [self _extractArchivePipingDataToCommand:@"tar -xC \"$DESTINATION\""];
}
- (void)_extractTGZ
{
+ // *** GETS CALLED ON NON-MAIN THREAD!!!
+
return [self _extractArchivePipingDataToCommand:@"tar -zxC \"$DESTINATION\""];
}
- (void)_extractTBZ
{
+ // *** GETS CALLED ON NON-MAIN THREAD!!!
+
return [self _extractArchivePipingDataToCommand:@"tar -jxC \"$DESTINATION\""];
}
- (void)_extractZIP
{
+ // *** GETS CALLED ON NON-MAIN THREAD!!!
+
return [self _extractArchivePipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""];
}
View
7 SUPlainInstaller.h
@@ -9,11 +9,16 @@
#ifndef SUPLAININSTALLER_H
#define SUPLAININSTALLER_H
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUInstaller.h"
#import "SUVersionComparisonProtocol.h"
@class SUHost;
+
@interface SUPlainInstaller : SUInstaller { }
+ (void)performInstallationWithPath:(NSString *)path host:(SUHost *)host delegate:delegate synchronously:(BOOL)synchronously versionComparator:(id <SUVersionComparison>)comparator;
@end
View
6 SUPlainInstaller.m
@@ -9,6 +9,8 @@
#import "SUPlainInstaller.h"
#import "SUPlainInstallerInternals.h"
#import "SUConstants.h"
+#import "SUHost.h"
+
NSString *SUInstallerPathKey = @"SUInstallerPath";
NSString *SUInstallerTargetPathKey = @"SUInstallerTargetPath";
@@ -22,11 +24,15 @@ @implementation SUPlainInstaller
+ (void)_finishInstallationWithInfo:(NSDictionary *)info
{
+ // *** GETS CALLED ON NON-MAIN THREAD!
+
[self _finishInstallationWithResult:[[info objectForKey:SUInstallerResultKey] boolValue] host:[info objectForKey:SUInstallerHostKey] error:[info objectForKey:SUInstallerErrorKey] delegate:[info objectForKey:SUInstallerDelegateKey]];
}
+ (void)_performInstallationWithInfo:(NSDictionary *)info
{
+ // *** GETS CALLED ON NON-MAIN THREAD!
+
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSError *error = nil;
View
136 SUPlainInstallerInternals.m
@@ -6,7 +6,11 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUPlainInstallerInternals.h"
#import "SUConstants.h"
@@ -16,6 +20,8 @@
#import <sys/wait.h>
#import <dirent.h>
#import <unistd.h>
+#import <sys/param.h>
+
@interface SUPlainInstaller (MMExtendedAttributes)
// Removes the directory tree rooted at |root| from the file quarantine.
@@ -43,6 +49,8 @@ + (void)releaseFromQuarantine:(NSString*)root;
static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments)
{
+ // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
+
sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL);
BOOL returnValue = YES;
@@ -86,8 +94,67 @@ + (NSString *)temporaryNameForPath:(NSString *)path
return [tempDir lastPathComponent];
}
++ (NSString *)_temporaryCopyNameForPath:(NSString *)path didFindTrash: (BOOL*)outDidFindTrash
+{
+ // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
+ NSString *tempDir = nil;
+
+ UInt8 trashPath[MAXPATHLEN +1] = { 0 };
+ FSRef trashRef, pathRef;
+ FSVolumeRefNum vSrcRefNum = kFSInvalidVolumeRefNum;
+ FSCatalogInfo catInfo = { 0 };
+ OSStatus err = FSPathMakeRef( (UInt8*) [path fileSystemRepresentation], &pathRef, NULL );
+ if( err == noErr )
+ {
+ err = FSGetCatalogInfo( &pathRef, kFSCatInfoVolume, &catInfo, NULL, NULL, NULL );
+ vSrcRefNum = catInfo.volume;
+ }
+ if( err == noErr )
+ err = FSFindFolder( vSrcRefNum, kTrashFolderType, kCreateFolder, &trashRef );
+ if( err == noErr )
+ err = FSGetCatalogInfo( &trashRef, kFSCatInfoVolume, &catInfo, NULL, NULL, NULL );
+ if( err == noErr && vSrcRefNum != catInfo.volume )
+ err = nsvErr; // Couldn't find a trash folder on same volume as given path. Docs say this may happen in the future.
+ if( err == noErr )
+ err = FSRefMakePath( &trashRef, trashPath, MAXPATHLEN );
+ if( err == noErr )
+ tempDir = [NSString stringWithUTF8String: (char*) trashPath];
+ if( outDidFindTrash )
+ *outDidFindTrash = (tempDir != nil);
+ if( !tempDir )
+ tempDir = [path stringByDeletingLastPathComponent];
+
+ // Let's try to read the version number so the filename will be more meaningful.
+ NSString *postFix = nil;
+ NSString *version = nil;
+ if ((version = [[NSBundle bundleWithPath: path] objectForInfoDictionaryKey:@"CFBundleVersion"]) && ![version isEqualToString:@""])
+ {
+ // We'll clean it up a little for safety.
+ // The cast is necessary because of a bug in the headers in pre-10.5 SDKs
+ NSMutableCharacterSet *validCharacters = (id)[NSMutableCharacterSet alphanumericCharacterSet];
+ [validCharacters formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@".-()"]];
+ postFix = [version stringByTrimmingCharactersInSet:[validCharacters invertedSet]];
+ }
+ else
+ postFix = @"old";
+ NSString *prefix = [NSString stringWithFormat: @"%@ (%@)", [[path lastPathComponent] stringByDeletingPathExtension], postFix];
+ NSString *tempName = [prefix stringByAppendingPathExtension: [path pathExtension]];
+ tempDir = [tempDir stringByAppendingPathComponent: tempName];
+
+ // Now let's make sure we get a unique path.
+ int cnt=2;
+ while ([[NSFileManager defaultManager] fileExistsAtPath:tempDir] && cnt <= 9999)
+ tempDir = [[tempDir stringByDeletingLastPathComponent] stringByAppendingPathComponent: [NSString stringWithFormat:@"%@ %d.%@", prefix, cnt++, [path pathExtension]]];
+
+ return tempDir;
+}
+
+ (BOOL)_copyPathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst temporaryPath:(NSString *)tmp error:(NSError **)error
{
+ // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
+
+ //BOOL foundTrash = NO; // +++ Using trash as tmp folder to begin with would be way cooler, IMHO.
+ //NSString* tmp = [self _temporaryCopyNameForPath:dst didFindTrash: &foundTrash];
const char* srcPath = [src fileSystemRepresentation];
const char* tmpPath = [tmp fileSystemRepresentation];
const char* dstPath = [dst fileSystemRepresentation];
@@ -112,35 +179,35 @@ + (BOOL)_copyPathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst
snprintf(uidgid, sizeof(uidgid), "%d:%d",
dstSB.st_uid, dstSB.st_gid);
- const char* executables[] = {
- "/bin/rm",
- "/bin/mv",
- "/bin/mv",
- "/bin/rm",
- NULL, // pause here and do some housekeeping before
- // continuing
- "/usr/sbin/chown",
- NULL // stop here for real
- };
+ if( res ) // Set permissions while it's still in source, so we have it with working and correct perms when it arrives at destination.
+ {
+ const char* coParams[] = { "-R", uidgid, srcPath, NULL };
+ res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/usr/sbin/chown", kAuthorizationFlagDefaults, coParams );
+ }
- // 4 is the maximum number of arguments to any command,
- // including the NULL that signals the end of an argument
- // list.
- const char* const argumentLists[][4] = {
- { "-rf", tmpPath, NULL }, // make room for the temporary file... this is kinda unsafe; should probably do something better.
- { "-f", dstPath, tmpPath, NULL }, // mv
- { "-f", srcPath, dstPath, NULL }, // mv
- { "-rf", tmpPath, NULL }, // rm
- { NULL }, // pause
- { "-R", uidgid, dstPath, NULL }, // chown
- { NULL } // stop
- };
+ BOOL haveDst = [[NSFileManager defaultManager] fileExistsAtPath: dst];
+ if( res && haveDst ) // If there's something at our tmp path (previous failed update or whatever) delete that first.
+ {
+ const char* rmParams[] = { "-rf", tmpPath, NULL };
+ res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams );
+ }
- // Process the commands up until the first NULL
- int commandIndex = 0;
- for (; executables[commandIndex] != NULL; ++commandIndex) {
- if (res)
- res = AuthorizationExecuteWithPrivilegesAndWait(auth, executables[commandIndex], kAuthorizationFlagDefaults, argumentLists[commandIndex]);
+ if( res && haveDst ) // Move old exe to tmp path.
+ {
+ const char* mvParams[] = { "-f", dstPath, tmpPath, NULL };
+ res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams );
+ }
+
+ if( res ) // Move new exe to old exe's path.
+ {
+ const char* mvParams2[] = { "-f", srcPath, dstPath, NULL };
+ res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams2 );
+ }
+
+ if( res && haveDst /*&& !foundTrash*/ ) // If we managed to put the old exe in the trash, leave it there for the user to delete or recover.
+ { // ... Otherwise we better delete it, wouldn't want dozens of old versions lying around next to the new one.
+ const char* rmParams2[] = { "-rf", tmpPath, NULL };
+ res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams2 );
}
// If the currently-running application is trusted, the new
@@ -159,15 +226,6 @@ + (BOOL)_copyPathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst
[self performSelectorOnMainThread:@selector(releaseFromQuarantine:) withObject:dst waitUntilDone:YES];
}
- // Now move past the NULL we found and continue executing
- // commands from the list.
- ++commandIndex;
-
- for (; executables[commandIndex] != NULL; ++commandIndex) {
- if (res)
- res = AuthorizationExecuteWithPrivilegesAndWait(auth, executables[commandIndex], kAuthorizationFlagDefaults, argumentLists[commandIndex]);
- }
-
AuthorizationFree(auth, 0);
if (!res)
@@ -269,6 +327,8 @@ + (int)removeXAttr:(const char*)name
fromFile:(NSString*)file
options:(int)options
{
+ // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
+
typedef int (*removexattr_type)(const char*, const char*, int);
// Reference removexattr directly, it's in the SDK.
static removexattr_type removexattr_func = removexattr;
@@ -300,6 +360,8 @@ + (int)removeXAttr:(const char*)name
+ (void)releaseFromQuarantine:(NSString*)root
{
+ // *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
+
const char* quarantineAttribute = "com.apple.quarantine";
const int removeXAttrOptions = XATTR_NOFOLLOW;
View
6 SUScheduledUpdateDriver.m
@@ -7,7 +7,11 @@
//
#import "SUScheduledUpdateDriver.h"
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
@implementation SUScheduledUpdateDriver
View
16 SUStandardVersionComparator.m
@@ -6,7 +6,11 @@
// Copyright 2007 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUStandardVersionComparator.h"
@implementation SUStandardVersionComparator
@@ -22,15 +26,19 @@ + (SUStandardVersionComparator *)defaultComparator
typedef enum {
kNumberType,
kStringType,
- kPeriodType
+ kSeparatorType,
} SUCharacterType;
- (SUCharacterType)typeOfCharacter:(NSString *)character
{
if ([character isEqualToString:@"."]) {
- return kPeriodType;
+ return kSeparatorType;
} else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[character characterAtIndex:0]]) {
return kNumberType;
+ } else if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[character characterAtIndex:0]]) {
+ return kSeparatorType;
+ } else if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:[character characterAtIndex:0]]) {
+ return kSeparatorType;
} else {
return kStringType;
}
@@ -52,7 +60,7 @@ - (NSArray *)splitVersionString:(NSString *)version
for (i = 1; i <= n; ++i) {
character = [version substringWithRange:NSMakeRange(i, 1)];
newType = [self typeOfCharacter:character];
- if (oldType != newType || oldType == kPeriodType) {
+ if (oldType != newType || oldType == kSeparatorType) {
// We've reached a new segment
NSString *aPart = [[NSString alloc] initWithString:s];
[parts addObject:aPart];
View
56 SUStatus.nib/classes.nib
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBClasses</key>
+ <array>
+ <dict>
+ <key>CLASS</key>
+ <string>SUWindowController</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSWindowController</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>NSApplication</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSResponder</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>FirstResponder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>NSObject</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>SUStatusController</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>OUTLETS</key>
+ <dict>
+ <key>actionButton</key>
+ <string>NSButton</string>
+ <key>progressBar</key>
+ <string>NSProgressIndicator</string>
+ </dict>
+ <key>SUPERCLASS</key>
+ <string>SUWindowController</string>
+ </dict>
+ </array>
+ <key>IBVersion</key>
+ <string>1</string>
+</dict>
+</plist>
View
20 SUStatus.nib/info.nib
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBFramework Version</key>
+ <string>670</string>
+ <key>IBLastKnownRelativeProjectPath</key>
+ <string>Sparkle.xcodeproj</string>
+ <key>IBOldestOS</key>
+ <integer>5</integer>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>6</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>10A96</string>
+ <key>targetFramework</key>
+ <string>IBCocoaFramework</string>
+</dict>
+</plist>
View
15 SUStatusController.m
@@ -6,8 +6,14 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUStatusController.h"
+#import "SUHost.h"
+
@implementation SUStatusController
@@ -31,7 +37,7 @@ - (void)dealloc
[super dealloc];
}
-- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [host bundlePath]]; }
+- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [host bundlePath], [host installationPath]]; }
- (void)awakeFromNib
{
@@ -60,7 +66,7 @@ - (void)beginActionWithTitle:(NSString *)aTitle maxProgressValue:(double)aMaxPro
[self setStatusText:aStatusText];
}
-- (void)setButtonTitle:(NSString *)aButtonTitle target:target action:(SEL)action isDefault:(BOOL)isDefault
+- (void)setButtonTitle:(NSString *)aButtonTitle target: (id)target action:(SEL)action isDefault:(BOOL)isDefault
{
[self willChangeValueForKey:@"buttonTitle"];
if (buttonTitle != aButtonTitle)
@@ -82,6 +88,9 @@ - (void)setButtonTitle:(NSString *)aButtonTitle target:target action:(SEL)action
[actionButton setTarget:target];
[actionButton setAction:action];
[actionButton setKeyEquivalent:isDefault ? @"\r" : @""];
+
+ // 06/05/2008 Alex: Avoid a crash when cancelling during the extraction
+ [self setButtonEnabled:(target != NULL)];
}
- (BOOL)progressBarShouldAnimate
View
2  SUSystemProfiler.h
@@ -9,6 +9,8 @@
#ifndef SUSYSTEMPROFILER_H
#define SUSYSTEMPROFILER_H
+#import <Cocoa/Cocoa.h>
+
@class SUHost;
@interface SUSystemProfiler : NSObject {}
+ (SUSystemProfiler *)sharedSystemProfiler;
View
4 SUUIBasedUpdateDriver.h
@@ -13,7 +13,9 @@
#import "SUBasicUpdateDriver.h"
@class SUStatusController, SUUpdateAlert;
-@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver {
+
+@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver
+{
SUStatusController *statusController;
SUUpdateAlert *updateAlert;
}
View
18 SUUIBasedUpdateDriver.m
@@ -11,6 +11,7 @@
#import "SUUpdateAlert.h"
#import "SUHost.h"
#import "SUStatusController.h"
+#import "SUConstants.h"
@implementation SUUIBasedUpdateDriver
@@ -66,9 +67,17 @@ - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoi
[statusController showWindow:self];
[self downloadUpdate];
break;
-
+
+ case SUOpenInfoURLChoice:
+ [[NSWorkspace sharedWorkspace] openURL: [updateItem infoURL]];
+ [self abortUpdate];
+ break;
+
case SUSkipThisVersionChoice:
[host setObject:[updateItem versionString] forUserDefaultsKey:SUSkippedVersionKey];
+ [self abortUpdate];
+ break;
+
case SURemindMeLaterChoice:
[self abortUpdate];
break;
@@ -80,6 +89,7 @@ - (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)r
[statusController setMaxProgressValue:[response expectedContentLength]];
}
+
- (NSString *)_humanReadableSizeFromDouble:(double)value
{
if (value < 1024)
@@ -139,7 +149,11 @@ - (void)unarchiverDidFinish:(SUUnarchiver *)ua
[NSApp requestUserAttention:NSInformationalRequest];
}
-- (void)installAndRestart:sender { [self installUpdate]; }
+- (void)installAndRestart: (id)sender
+{
+ if( [updater mayUpdateAndRestart] )
+ [self installUpdate];
+}
- (void)installUpdate
{
View
6 SUUnarchiver.m
@@ -7,7 +7,11 @@
//
-#import "Sparkle.h"
+#import "SUUpdater.h"
+
+#import "SUAppcast.h"
+#import "SUAppcastItem.h"
+#import "SUVersionComparisonProtocol.h"
#import "SUUnarchiver.h"
#import "SUUnarchiver_Private.h"
View
5 SUUpdateAlert.h
@@ -15,7 +15,8 @@ typedef enum
{
SUInstallUpdateChoice,
SURemindMeLaterChoice,
- SUSkipThisVersionChoice
+ SUSkipThisVersionChoice,
+ SUOpenInfoURLChoice
} SUUpdateAlertChoice;
@class WebView, SUAppcastItem, SUHost;
@@ -26,6 +27,7 @@ typedef enum
IBOutlet WebView *releaseNotesView;
IBOutlet NSTextField *description;
+ IBOutlet NSButton *installButton; // UK 2007-08-31.
NSProgressIndicator *releaseNotesSpinner;
BOOL webViewFinishedLoading;
}
@@ -41,6 +43,7 @@ typedef enum
@interface NSObject (SUUpdateAlertDelegate)
- (void)updateAlert:(SUUpdateAlert *)updateAlert finishedWithChoice:(SUUpdateAlertChoice)updateChoice;
+- (void)updateAlert:(SUUpdateAlert *)updateAlert shouldAllowAutoUpdate: (BOOL*)shouldAllowAutoUpdate;
@end
#endif
View
88 SUUpdateAlert.m
@@ -6,11 +6,25 @@
// Copyright 2006 Andy Matuschak. All rights reserved.
//
+// -----------------------------------------------------------------------------
+// Headers:
+// -----------------------------------------------------------------------------
+
#import "SUUpdateAlert.h"
#import "SUHost.h"
#import <WebKit/WebKit.h>
+#import "SUConstants.h"
+
+
+@interface WebView (SUTenFiveProperty)
+
+-(void) setDrawsBackground: (BOOL)state;
+
+@end
+
+
@implementation SUUpdateAlert
- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost
@@ -50,6 +64,11 @@ - (IBAction)installUpdate:sender
[self endWithSelection:SUInstallUpdateChoice];
}
+- (IBAction)openInfoURL:sender
+{
+ [self endWithSelection:SUOpenInfoURLChoice];
+}
+
- (IBAction)skipThisVersion:sender
{
[self endWithSelection:SUSkipThisVersionChoice];
@@ -99,26 +118,40 @@ - (BOOL)showsReleaseNotes
{
NSNumber *shouldShowReleaseNotes = [host objectForInfoDictionaryKey:SUShowReleaseNotesKey];
if (shouldShowReleaseNotes == nil)
- return YES; // defaults to YES
+ {
+ // UK 2007-09-18: Don't show release notes if RSS item contains no description and no release notes URL:
+ return( ([updateItem itemDescription] != nil
+ && [[[updateItem itemDescription] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0)
+ || [updateItem releaseNotesURL] != nil );
+ }
else
return [shouldShowReleaseNotes boolValue];
}
- (BOOL)allowsAutomaticUpdates
{
- if (![host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey])
- return YES; // defaults to YES
- return [host boolForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey];
+ BOOL allowAutoUpdates = YES; // Defaults to YES.
+ if( [host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] )
+ allowAutoUpdates = [host boolForInfoDictionaryKey: SUAllowsAutomaticUpdatesKey];
+
+ // UK 2007-08-31: Give delegate a chance to modify this choice:
+ if( delegate && [delegate respondsToSelector: @selector(updateAlert:shouldAllowAutoUpdate:)] )
+ [delegate updateAlert: self shouldAllowAutoUpdate: &allowAutoUpdates];
+
+ return allowAutoUpdates;
}
- (void)awakeFromNib
{
- [[self window] setLevel:NSFloatingWindowLevel];
+ NSString* sizeStr = [host objectForInfoDictionaryKey:SUFixedHTMLDisplaySizeKey];
+
+ //[[self window] setLevel:NSFloatingWindowLevel]; // This means the window will float over all other apps, if our app is switched out ?! UK 2007-09-04
+ [[self window] setFrameAutosaveName: sizeStr ? @"" : @"SUUpdateAlertFrame"];
// We're gonna do some frame magic to match the window's size to the description field and the presence of the release notes view.
- NSRect frame = [[self window] frame];
-
- if (![self showsReleaseNotes])
+ NSRect frame = [[self window] frame];
+ BOOL showReleaseNotes = [self showsReleaseNotes]; // UK 2007-09-18
+ if (!showReleaseNotes) // UK 2007-09-18
{
// Resize the window to be appropriate for not having a huge release notes view.
frame.size.height -= [releaseNotesView frame].size.height + 40; // Extra 40 is for the release notes label and margin.
@@ -133,15 +166,52 @@ - (void)awakeFromNib
[[[releaseNotesView superview] superview] setFrame:boxFrame];
}
+ if( showReleaseNotes ) // UK 2007-09-18 (whole block)
+ {
+ if( sizeStr )
+ {
+ NSSize desiredSize = NSSizeFromString( sizeStr );
+ NSSize sizeDiff = NSZeroSize;
+ NSBox* boxView = (NSBox*)[[releaseNotesView superview] superview];
+
+ //[boxView setBorderType: NSNoBorder];
+ [releaseNotesView setDrawsBackground: NO];
+
+ sizeDiff.width = desiredSize.width -[releaseNotesView frame].size.width;
+ sizeDiff.height = desiredSize.height -[releaseNotesView frame].size.height;
+ frame.size.width += sizeDiff.width;
+ frame.size.height += sizeDiff.height;
+
+ // No resizing:
+ [[self window] setShowsResizeIndicator:NO];
+ [[self window] setMinSize:frame.size];
+ [[self window] setMaxSize:frame.size];
+ }
+ }
+
[[self window] setFrame:frame display:NO];
[[self window] center];
- if ([self showsReleaseNotes])
+ if (showReleaseNotes) // UK 2007-09-18
{
[self displayReleaseNotes];
}
+
+ [[[releaseNotesView superview] superview] setHidden: !showReleaseNotes]; // UK 2007-09-18
+
+ if( [updateItem fileURL] == nil ) // UK 2007-08-31 (whole if clause)
+ {
+ [installButton setTitle: SULocalizedString( @"Learn More...", @"Alternate title for 'Install Update' button." )];
+ [installButton setAction: @selector(openInfoURL:)];
+ }
}
+-(BOOL)showsReleaseNotesText
+{
+ return( [host objectForInfoDictionaryKey:SUFixedHTMLDisplaySizeKey] == nil );
+}
+
+
- (BOOL)windowShouldClose:note
{
[self endWithSelection:SURemindMeLaterChoice];
View
3  SUUpdateDriver.m
@@ -7,6 +7,7 @@
//
#import "SUUpdateDriver.h"
+#import "SUHost.h"
NSString *SUUpdateDriverFinishedNotification = @"SUUpdateDriverFinished";
@@ -18,7 +19,7 @@ @implementation SUUpdateDriver
return self;
}
-- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [host bundlePath]]; }
+- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [host bundlePath], [host installationPath]]; }
- (void)checkForUpdatesAtURL:(NSURL *)URL host:(SUHost *)h
{
View
1  SUUpdatePermissionPrompt.m
@@ -9,6 +9,7 @@
#import "SUUpdatePermissionPrompt.h"
#import "SUHost.h"
+#import "SUConstants.h"
@implementation SUUpdatePermissionPrompt
View
26 SUUpdater.h
@@ -9,7 +9,7 @@
#ifndef SUUPDATER_H
#define SUUPDATER_H
-#import <Sparkle/SUVersionComparisonProtocol.h>
+#import "SUVersionComparisonProtocol.h"
@class SUUpdateDriver, SUAppcastItem, SUHost, SUAppcast;
@interface SUUpdater : NSObject {
@@ -37,7 +37,7 @@
- (NSTimeInterval)updateCheckInterval;
- (void)setFeedURL:(NSURL *)feedURL;
-- (NSURL *)feedURL;
+- (NSURL *)feedURL; // *** MUST BE CALLED ON MAIN THREAD ***
- (void)setUserAgentString:(NSString *)userAgent;
- (NSString *)userAgentString;
@@ -50,7 +50,7 @@
// This IBAction is meant for a main menu item. Hook up any menu item to this action,
// and Sparkle will check for updates and report back its findings verbosely.
-- (IBAction)checkForUpdates:sender;
+- (IBAction)checkForUpdates:(id)sender;
// This kicks off an update meant to be programmatically initiated. That is, it will display no UI unless it actually finds an update,
// in which case it proceeds as usual. If the fully automated updating is turned on, however, this will invoke that behavior, and if an
@@ -68,12 +68,22 @@
- (void)resetUpdateCycle;
- (BOOL)updateInProgress;
+
+-(BOOL) mayUpdateAndRestart; // If we can't restart, don't update, because that'd mean anything the old app (still running) reads from disk
+
@end
@interface NSObject (SUUpdaterDelegateInformalProtocol)
+
+// Use this to keep Sparkle from popping up e.g. while your setup assistant is showing:
+- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle;
+
// This method allows you to add extra parameters to the appcast URL, potentially based on whether or not Sparkle will also be sending along the system profile. This method should return an array of dictionaries with keys: "key", "value", "displayKey", "displayValue", the latter two being specifically for display to the user.
- (NSArray *)feedParametersForUpdater:(SUUpdater *)updater sendingSystemProfile:(BOOL)sendingProfile;
+// If you need to generate the whole URL:
+-(NSString*) feedURLStringForUpdater: (SUUpdater*)updater;
+
// Use this to override the default behavior for Sparkle prompting the user about automatic update checks.
- (BOOL)updaterShouldPromptForPermissionToCheckForUpdates:(SUUpdater *)bundle;
@@ -94,8 +104,14 @@
- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update;
// Return YES to delay the relaunch until you do some processing; invoke the given NSInvocation to continue.
+// This is not called if the user didn't relaunch on the previous update, in that case it will immediately
+// restart.
- (BOOL)updater:(SUUpdater *)updater shouldPostponeRelaunchForUpdate:(SUAppcastItem *)update untilInvoking:(NSInvocation *)invocation;
+// Some apps *can not* be relaunched in certain circumstances. They can use this method
+// to prevent a relaunch "hard":
+- (BOOL)updaterShouldRelaunchApplication:(SUUpdater *)updater;
+
// Called immediately before relaunching.
- (void)updaterWillRelaunchApplication:(SUUpdater *)updater;
@@ -109,13 +125,13 @@
@end
// Define some minimum intervals to avoid DOS-like checking attacks. These are in seconds.
-#ifdef DEBUG
+#ifdef DEBUG && 0
#define SU_MIN_CHECK_INTERVAL 60
#else
#define SU_MIN_CHECK_INTERVAL 60*60
#endif
-#ifdef DEBUG
+#ifdef DEBUG && 0
#define SU_DEFAULT_CHECK_INTERVAL 60
#else
#define SU_DEFAULT_CHECK_INTERVAL 60*60*24
View
108 SUUpdater.m
@@ -15,9 +15,13 @@
#import "SUProbingUpdateDriver.h"
#import "SUUserInitiatedUpdateDriver.h"
#import "SUScheduledUpdateDriver.h"
+#import "SUConstants.h"
+#import "SULog.h"
+#include <SystemConfiguration/SCNetwork.h> // UK 2007-04-27
+
@interface SUUpdater (Private)
-- initForBundle:(NSBundle *)bundle;
+- (id)initForBundle:(NSBundle *)bundle;
- (void)startUpdateCycle;
- (void)checkForUpdatesWithDriver:(SUUpdateDriver *)updateDriver;
- (BOOL)automaticallyDownloadsUpdates;
@@ -25,7 +29,6 @@ - (void)scheduleNextUpdateCheck;
- (void)registerAsObserver;
- (void)unregisterAsObserver;
- (void)updateDriverDidFinish:(NSNotification *)note;
-- initForBundle:(NSBundle *)bundle;
- (NSURL *)parameterizedFeedURL;
@end
@@ -52,7 +55,7 @@ + (SUUpdater *)updaterForBundle:(NSBundle *)bundle
}
// This is the designated initializer for SUUpdater, important for subclasses
-- initForBundle:(NSBundle *)bundle
+- (id)initForBundle:(NSBundle *)bundle
{
self = [super init];
if (bundle == nil) bundle = [NSBundle mainBundle];
@@ -70,10 +73,11 @@ + (SUUpdater *)updaterForBundle:(NSBundle *)bundle
host = [[SUHost alloc] initWithBundle:bundle];
[self registerAsObserver];
+#if 0
// Saving-the-developer-from-a-stupid-mistake-check:
if (![[[self feedURL] scheme] isEqualToString:@"https"] && ![host publicDSAKey])
NSRunAlertPanel(@"Insecure update error!", @"For security reasons, you need to distribute your appcast over SSL or sign your updates. See Sparkle's documentation for more information.", @"OK", nil, nil);
-
+#endif
// This runs the permission prompt if needed, but never before the app has finished launching because the runloop won't run before that
[self performSelector:@selector(startUpdateCycle) withObject:nil afterDelay:0];
}
@@ -86,7 +90,7 @@ - (id)init
return [self initForBundle:[NSBundle mainBundle]];
}
-- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [host bundlePath]]; }
+- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [host bundlePath], [host installationPath]]; }
- (void)startUpdateCycle
{
@@ -157,6 +161,7 @@ - (void)scheduleNextUpdateCheck
if (checkTimer)
{
[checkTimer invalidate];
+ [checkTimer release]; // UK 2009-03-16 Timer is non-repeating, may have invalidated itself, so we had to retain it.
checkTimer = nil;
}
if (![self automaticallyChecksForUpdates]) return;
@@ -174,17 +179,79 @@ - (void)scheduleNextUpdateCheck
delayUntilCheck = (updateCheckInterval - intervalSinceCheck); // It hasn't been long enough.
else
delayUntilCheck = 0; // We're overdue! Run one now.
- checkTimer = [NSTimer scheduledTimerWithTimeInterval:delayUntilCheck target:self selector:@selector(checkForUpdatesInBackground) userInfo:nil repeats:NO];
+ checkTimer = [[NSTimer scheduledTimerWithTimeInterval:delayUntilCheck target:self selector:@selector(checkForUpdatesInBackground) userInfo:nil repeats:NO] retain]; // UK 2009-03-16 Timer is non-repeating, may have invalidated itself, so we had to retain it.
+}
+
+
+-(void) putFeedURLIntoDictionary: (NSMutableDictionary*)theDict // You release this.
+{
+ [theDict setObject: [self feedURL] forKey: @"feedURL"];
+}
+
+-(void) checkForUpdatesInBgReachabilityCheckWithDriver: (SUUpdateDriver*)inDriver /* RUNS ON ITS OWN THREAD */
+{
+ NS_DURING
+ // This method *must* be called on its own thread. SCNetworkReachabilityCheckByName
+ // can block, and it can be waiting a long time on slow networks, and we
+ // wouldn't want to beachball the main thread for a background operation.