Merge pull request #64 in ITERATE/cyberduck from feature/windows/9074 to master
[cyberduck.git] / source / de / zathras / UKCrashReporter.m
blob1fae386d137523ab5b2d12f45169b6e986e94546
1 //
2 //  UKCrashReporter.m
3 //  NiftyFeatures
4 //
5 //  Created by Uli Kusterer on Sat Feb 04 2006.
6 //  Copyright (c) 2006 M. Uli Kusterer. All rights reserved.
7 //
9 // -----------------------------------------------------------------------------
10 //      Headers:
11 // -----------------------------------------------------------------------------
13 #import "UKCrashReporter.h"
14 #import "UKSystemInfo.h"
15 #import <Cocoa/Cocoa.h>
17 NSString*       UKCrashReporterFindTenFiveCrashReportPath( NSString* appName, NSString* crashLogsFolder );
19 @implementation UKCrashReporter
21 -(id) init
23         if( (self = [super init]) )
24         {
25                 [self checkForCrash];
26         }
28         return self;
31 -(void) dealloc
33         [connection release];
34         connection = nil;
36         [super dealloc];
39 // -----------------------------------------------------------------------------
40 //      checkForCrash:
41 //              This submits the crash report to a CGI form as a POST request by
42 //              passing it as the request variable "crashlog".
43 //      
44 //              KNOWN LIMITATION:       If the app crashes several times in a row, only the
45 //                                                      last crash report will be sent because this doesn't
46 //                                                      walk through the log files to try and determine the
47 //                                                      dates of all reports.
49 //              This is written so it works back to OS X 10.2, or at least gracefully
50 //              fails by just doing nothing on such older OSs. This also should never
51 //              throw exceptions or anything on failure. This is an additional service
52 //              for the developer and *mustn't* interfere with regular operation of the
53 //              application.
54 // -----------------------------------------------------------------------------
56 - (void) checkForCrash
58         NSAutoreleasePool*      pool = [[NSAutoreleasePool alloc] init];
59         
60         NS_DURING
61                 // Try whether the classes we need to talk to the CGI are present:
62                 Class                   NSMutableURLRequestClass = NSClassFromString( @"NSMutableURLRequest" );
63                 Class                   NSURLConnectionClass = NSClassFromString( @"NSURLConnection" );
64                 if( NSMutableURLRequestClass == Nil || NSURLConnectionClass == Nil )
65                 {
66                         [pool release];
67                         NS_VOIDRETURN;
68                 }
69                 
70                 SInt32  sysvMajor = 0, sysvMinor = 0, sysvBugfix = 0;
71                 UKGetSystemVersionComponents( &sysvMajor, &sysvMinor, &sysvBugfix );
72                 BOOL    isTenFiveOrBetter = sysvMajor >= 10 && sysvMinor >= 5;
73                 
74                 // Get the log file, its last change date and last report date:
75                 NSString*               appName = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"CFBundleExecutable"];
76                 NSString*               appRevision = [[[NSBundle mainBundle] infoDictionary] objectForKey: @"CFBundleVersion"];
77                 NSString*               crashLogsFolder = [@"~/Library/Logs/DiagnosticReports/" stringByExpandingTildeInPath];
78                 NSString*               crashLogName = [appName stringByAppendingString: @".crash.log"];
79                 NSString*               crashLogPath = nil;
80                 if( !isTenFiveOrBetter )
81                         crashLogPath = [crashLogsFolder stringByAppendingPathComponent: crashLogName];
82                 else
83                         crashLogPath = UKCrashReporterFindTenFiveCrashReportPath( appName, crashLogsFolder );
84                 NSDictionary*   fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath: crashLogPath error:nil];
85                 NSDate*                 lastTimeCrashLogged = (fileAttrs == nil) ? nil : [fileAttrs fileModificationDate];
86                 NSTimeInterval  lastCrashReportInterval = [[NSUserDefaults standardUserDefaults] floatForKey: @"crashreport.date"];
87                 NSDate*                 lastTimeCrashReported = [NSDate dateWithTimeIntervalSince1970: lastCrashReportInterval];
88                 
89                 if( lastTimeCrashLogged )       // We have a crash log file and its mod date? Means we crashed sometime in the past.
90                 {
91                         // If we never before reported a crash or the last report lies before the last crash:
92                         if( [lastTimeCrashReported compare: lastTimeCrashLogged] == NSOrderedAscending )
93                         {
94                                 //NSLog(@"New crash log found! Running alert panel");
95                                 if( NSRunAlertPanel( NSLocalizedStringFromTable( @"Do you want to report the last crash?", @"Crash", @"" ),
96                                                                         NSLocalizedStringFromTable( @"The application %@ has recently crashed. To help improve it, you can send the crash log to the author.", @"Crash", @"" ),
97                                                                         NSLocalizedStringFromTable( @"Send", @"Crash", @"" ), // NSAlertDefaultReturn
98                                                                         NSLocalizedStringFromTable( @"Don't Send", @"Crash", @"" ), // NSAlertAlternateReturn
99                                                                         @"", appName ) )
100                                 {
101                     // Fetch the newest report from the log:
102                     NSString*                   crashLog = [NSString stringWithContentsOfFile: crashLogPath encoding:NSASCIIStringEncoding error:nil];
103                     NSArray*                    separateReports = [crashLog componentsSeparatedByString: @"\n\n**********\n\n"];
104                     NSString*                   currentReport = [separateReports count] > 0 ? [separateReports objectAtIndex: [separateReports count] -1] : @"*** Couldn't read Report ***";    // 1 since report 0 is empty (file has a delimiter at the top).
105                                         NSData*                         crashReport = [currentReport dataUsingEncoding: NSUTF8StringEncoding];  // 1 since report 0 is empty (file has a delimiter at the top).
107                     NSString            *boundary = @"0xKhTmLbOuNdArY";
109                     // Prepare a request:
110                     NSString            *url = [[[@"https://crash.cyberduck.io/report" stringByAppendingString:@"?revision="] stringByAppendingString:appRevision] stringByAppendingString:@"&os=mac"];
111                     NSMutableURLRequest *postRequest = [NSMutableURLRequestClass requestWithURL:[NSURL URLWithString: url]];
112                     NSString            *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
113                     NSString                    *agent = [NSString stringWithFormat:@"Cyberduck (%@)", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
115                     // Add form trappings to crashReport:
116                     NSData*                     header = [[NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"crashlog\"\r\n\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding];
117                     NSMutableData*      formData = [[header mutableCopy] autorelease];
118                     [formData appendData: crashReport];
119                     [formData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
121                     // setting the headers:
122                     [postRequest setHTTPMethod: @"POST"];
123                     [postRequest setValue: contentType forHTTPHeaderField: @"Content-Type"];
124                     [postRequest setValue: agent forHTTPHeaderField: @"User-Agent"];
125                     NSString *contentLength = [NSString stringWithFormat:@"%lu", [formData length]];
126                     [postRequest setValue: contentLength forHTTPHeaderField: @"Content-Length"];
127                     [postRequest setHTTPBody: formData];
129                     // Go into progress mode and kick off the HTTP post:
130                     connection = [[NSURLConnection connectionWithRequest: postRequest delegate: self] retain];
131                 }
132                 else {
133                     // Don't ask twice:
134                     [[NSUserDefaults standardUserDefaults] setFloat: [[NSDate date] timeIntervalSince1970] forKey: @"crashreport.date"];
135                     [[NSUserDefaults standardUserDefaults] synchronize];
136                 }
137                         }
138                 }
139         NS_HANDLER
140                 NSLog(@"Error during check for crash: %@",localException);
141         NS_ENDHANDLER
142         
143         [pool drain];
146 NSString*       UKCrashReporterFindTenFiveCrashReportPath( NSString* appName, NSString* crashLogsFolder )
148         NSDirectoryEnumerator*  enny = [[NSFileManager defaultManager] enumeratorAtPath: crashLogsFolder];
149         NSString*                               currName = nil;
150         NSString*                               crashLogPrefix = [NSString stringWithFormat: @"%@_",appName];
151         NSString*                               crashLogSuffix = @".crash";
152         NSString*                               foundName = nil;
153         NSDate*                                 foundDate = nil;
154         
155         // Find the newest of our crash log files:
156         while(( currName = [enny nextObject] ))
157         {
158                 if( [currName hasPrefix: crashLogPrefix] && [currName hasSuffix: crashLogSuffix] )
159                 {
160                         NSDate* currDate = [[enny fileAttributes] fileModificationDate];
161                         if( foundName )
162                         {
163                                 if( [currDate isGreaterThan: foundDate] )
164                                 {
165                                         foundName = currName;
166                                         foundDate = currDate;
167                                 }
168                         }
169                         else
170                         {
171                                 foundName = currName;
172                                 foundDate = currDate;
173                         }
174                 }
175         }
176         
177         if( !foundName )
178                 return nil;
179         else
180                 return [crashLogsFolder stringByAppendingPathComponent: foundName];
183 -(void) connectionDidFinishLoading:(NSURLConnection *)conn
185         [connection release];
186         connection = nil;
187         
188         // Now that we successfully sent this crash, don't report it again:
189     [[NSUserDefaults standardUserDefaults] setFloat: [[NSDate date] timeIntervalSince1970] forKey: @"crashreport.date"];
190     [[NSUserDefaults standardUserDefaults] synchronize];
194 -(void) connection:(NSURLConnection *)conn didFailWithError:(NSError *)error
196         [connection release];
197         connection = nil;
200 @end