1 /*****************************************************************************
2 * CrashReporter.h: Mac OS X interface crash reporter
3 *****************************************************************************
4 * Copyright (C) 2009-2013 VLC authors and VideoLAN
7 * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
8 * Pierre d'Herbemont <pdherbemont # videolan org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 #import "CrashReporter.h"
27 #import <AddressBook/AddressBook.h>
29 @implementation VLCCrashReporter
33 static VLCCrashReporter *_sharedMainInstance = nil;
35 + (VLCCrashReporter *)sharedInstance
37 return _sharedMainInstance ? _sharedMainInstance : [[self alloc] init];
42 if (_sharedMainInstance)
45 _sharedMainInstance = [super init];
47 return _sharedMainInstance;
53 [_crashrep_send_btn setTitle: _NS("Send")];
54 [_crashrep_dontSend_btn setTitle: _NS("Don't Send")];
55 [_crashrep_title_txt setStringValue: _NS("VLC crashed previously")];
56 [_crashrep_win setTitle: _NS("VLC crashed previously")];
57 [_crashrep_desc_txt setStringValue: _NS("Do you want to send details on the crash to VLC's development team?\n\nIf you want, you can enter a few lines on what you did before VLC crashed along with other helpful information: a link to download a sample file, a URL of a network stream, ...")];
58 [_crashrep_includeEmail_ckb setTitle: _NS("I agree to be possibly contacted about this bugreport.")];
59 [_crashrep_includeEmail_txt setStringValue: _NS("Only your default E-Mail address will be submitted, including no further information.")];
60 [_crashrep_dontaskagain_ckb setTitle: _NS("Don't ask again")];
65 [_crashLogURLConnection cancel];
66 [_crashLogURLConnection release];
71 #pragma mark - inter-object services
73 - (NSString *)_latestCrashLogPathPreviouslySeen:(BOOL)previouslySeen
75 NSString * crashReporter;
76 if (OSX_MOUNTAIN_LION || OSX_MAVERICKS)
77 crashReporter = [@"~/Library/Logs/DiagnosticReports" stringByExpandingTildeInPath];
79 crashReporter = [@"~/Library/Logs/CrashReporter" stringByExpandingTildeInPath];
80 NSDirectoryEnumerator *direnum = [[NSFileManager defaultManager] enumeratorAtPath:crashReporter];
82 NSString * latestLog = nil;
83 NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
84 int year = !previouslySeen ? [defaults integerForKey:@"LatestCrashReportYear"] : 0;
85 int month = !previouslySeen ? [defaults integerForKey:@"LatestCrashReportMonth"]: 0;
86 int day = !previouslySeen ? [defaults integerForKey:@"LatestCrashReportDay"] : 0;
87 int hours = !previouslySeen ? [defaults integerForKey:@"LatestCrashReportHours"]: 0;
89 while (fname = [direnum nextObject]) {
90 [direnum skipDescendents];
91 if ([fname hasPrefix:@"VLC"] && [fname hasSuffix:@"crash"]) {
92 NSArray * compo = [fname componentsSeparatedByString:@"_"];
93 if ([compo count] < 3)
95 compo = [[compo objectAtIndex:1] componentsSeparatedByString:@"-"];
96 if ([compo count] < 4)
100 if (year < [[compo objectAtIndex:0] intValue] ||
101 (year ==[[compo objectAtIndex:0] intValue] &&
102 (month < [[compo objectAtIndex:1] intValue] ||
103 (month ==[[compo objectAtIndex:1] intValue] &&
104 (day < [[compo objectAtIndex:2] intValue] ||
105 (day ==[[compo objectAtIndex:2] intValue] &&
106 hours < [[compo objectAtIndex:3] intValue])))))) {
107 year = [[compo objectAtIndex:0] intValue];
108 month = [[compo objectAtIndex:1] intValue];
109 day = [[compo objectAtIndex:2] intValue];
110 hours = [[compo objectAtIndex:3] intValue];
111 latestLog = [crashReporter stringByAppendingPathComponent:fname];
116 if (!(latestLog && [[NSFileManager defaultManager] fileExistsAtPath:latestLog]))
119 if (!previouslySeen) {
120 [defaults setInteger:year forKey:@"LatestCrashReportYear"];
121 [defaults setInteger:month forKey:@"LatestCrashReportMonth"];
122 [defaults setInteger:day forKey:@"LatestCrashReportDay"];
123 [defaults setInteger:hours forKey:@"LatestCrashReportHours"];
128 - (NSString *)_latestCrashLogPath
130 return [self _latestCrashLogPathPreviouslySeen:YES];
133 - (void)showDialogAndSendLogIfDesired
135 // This pref key doesn't exists? this VLC is an upgrade, and this crash log come from previous version
136 NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
137 BOOL areCrashLogsTooOld = ![defaults integerForKey:@"LatestCrashReportYear"];
138 NSString * latestLog = [self _latestCrashLogPathPreviouslySeen:NO];
139 if (latestLog && !areCrashLogsTooOld) {
140 if ([defaults integerForKey:@"AlwaysSendCrashReports"] > 0)
141 [self _sendCrashLog:[NSString stringWithContentsOfFile: [self _latestCrashLogPath] encoding: NSUTF8StringEncoding error: NULL] withUserComment: [_crashrep_fld string]];
142 else if ([defaults integerForKey:@"AlwaysSendCrashReports"] == 0) {
143 [NSBundle loadNibNamed:@"CrashReporter" owner:self];
144 [NSApp runModalForWindow:_crashrep_win];
146 if ([self.delegate respondsToSelector: @selector(reporterFinishedAction:)])
147 [self.delegate reporterFinishedAction: self];
152 #pragma mark - UI interaction
154 - (IBAction)buttonAction:(id)sender
157 [_crashrep_win orderOut: sender];
158 if (sender == _crashrep_send_btn) {
159 [self _sendCrashLog:[NSString stringWithContentsOfFile: [self _latestCrashLogPath] encoding: NSUTF8StringEncoding error: NULL] withUserComment: [_crashrep_fld string]];
160 if ([_crashrep_dontaskagain_ckb state])
161 [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"AlwaysSendCrashReports"];
163 if ([_crashrep_dontaskagain_ckb state])
164 [[NSUserDefaults standardUserDefaults] setInteger:-1 forKey:@"AlwaysSendCrashReports"];
165 if ([self.delegate respondsToSelector: @selector(reporterFinishedAction:)])
166 [self.delegate reporterFinishedAction: self];
170 #pragma mark - network handling
172 - (void)_sendCrashLog:(NSString *)crashLog withUserComment:(NSString *)userComment
174 NSString *urlStr = @"http://crash.videolan.org/crashlog/sendcrashreport.php";
175 NSURL *url = [NSURL URLWithString:urlStr];
177 NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
178 [req setHTTPMethod:@"POST"];
181 if ([_crashrep_includeEmail_ckb state] == NSOnState) {
182 ABPerson * contact = [[ABAddressBook sharedAddressBook] me];
183 ABMultiValue *emails = [contact valueForProperty:kABEmailProperty];
184 email = [emails valueAtIndex:[emails indexForIdentifier:
185 [emails primaryIdentifier]]];
188 email = [NSString string];
191 postBody = [NSString stringWithFormat:@"CrashLog=%@&Comment=%@&Email=%@\r\n",
192 [crashLog stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
193 [userComment stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
194 [email stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
196 [req setHTTPBody:[postBody dataUsingEncoding:NSUTF8StringEncoding]];
198 /* Released from delegate */
199 _crashLogURLConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
202 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
204 msg_Dbg(VLCIntf, "crash report successfully sent");
205 [_crashLogURLConnection release];
206 _crashLogURLConnection = nil;
208 if ([self.delegate respondsToSelector: @selector(reporterFinishedAction:)])
209 [self.delegate reporterFinishedAction: self];
212 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
214 msg_Warn(VLCIntf, "Error when sending the crash report: %s (%li)", [[error localizedDescription] UTF8String], [error code]);
215 [_crashLogURLConnection release];
216 _crashLogURLConnection = nil;
218 if ([self.delegate respondsToSelector: @selector(reporterFinishedAction:)])
219 [self.delegate reporterFinishedAction: self];