| 1 | /* Copyright (C) 2008-2009 Peter Speck |
|---|
| 2 | * |
|---|
| 3 | * This program is free software: you can redistribute it and/or modify |
|---|
| 4 | * it under the terms of the GNU General Public License as published by |
|---|
| 5 | * the Free Software Foundation, either version 3 of the License, or |
|---|
| 6 | * (at your option) any later version. |
|---|
| 7 | * |
|---|
| 8 | * This program is distributed in the hope that it will be useful, |
|---|
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 11 | * GNU General Public License for more details. |
|---|
| 12 | * |
|---|
| 13 | * You should have received a copy of the GNU General Public License |
|---|
| 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 15 | */ |
|---|
| 16 | |
|---|
| 17 | #import "ProxyScriptEditor.h" |
|---|
| 18 | #import "GBDebug.h" |
|---|
| 19 | #import "ClickActionTextField.h" |
|---|
| 20 | #import "ServerCommunication.h" |
|---|
| 21 | #import "Xml.h" |
|---|
| 22 | #import "Prefs.h" |
|---|
| 23 | #import "NamedEnum.h" |
|---|
| 24 | #import "AlertHelper.h" |
|---|
| 25 | |
|---|
| 26 | NamedEnum* proxyTypeEnum; |
|---|
| 27 | |
|---|
| 28 | @interface ProxyScriptEditor () |
|---|
| 29 | |
|---|
| 30 | - (BOOL)isJavascriptTextStorage:(NSTextStorage*)storage; |
|---|
| 31 | |
|---|
| 32 | - (BOOL)validateSyntax; |
|---|
| 33 | |
|---|
| 34 | - (void)startTimerWithImmediateExecute:(BOOL)immediate; |
|---|
| 35 | - (void)stopTimer; |
|---|
| 36 | - (void)timerCallback:(NSTimer*)theTimer; |
|---|
| 37 | |
|---|
| 38 | - (void)updateNetworkCodeView; |
|---|
| 39 | |
|---|
| 40 | @end |
|---|
| 41 | |
|---|
| 42 | @implementation ProxyScriptEditor |
|---|
| 43 | |
|---|
| 44 | + (void)initProxyScriptEditor |
|---|
| 45 | { |
|---|
| 46 | if (proxyTypeEnum) |
|---|
| 47 | return; |
|---|
| 48 | proxyTypeEnum = [[NamedEnum alloc] initWithSemicolonList:@"+none;javascript;static"]; |
|---|
| 49 | } |
|---|
| 50 | |
|---|
| 51 | - (id)initWithServerCommunication:(ServerCommunication*)comm |
|---|
| 52 | withBundle:(NSBundle*)bundle |
|---|
| 53 | withDelegate:(id<ProxyScriptEditorDelegate>)delegate |
|---|
| 54 | { |
|---|
| 55 | if (!(self = [super init])) |
|---|
| 56 | return nil; |
|---|
| 57 | NSAssert([NSBundle loadNibNamed: @"ProxyScriptEdit" owner: self], |
|---|
| 58 | @"Can't load nib 'ProxyScriptEdit'"); |
|---|
| 59 | NSAssert(scriptSheet, @"scriptSheet is null"); |
|---|
| 60 | // |
|---|
| 61 | _bundle = [bundle retain]; |
|---|
| 62 | _delegate = delegate; |
|---|
| 63 | _comm = [comm retain]; |
|---|
| 64 | _prefs = [[_comm prefs] retain]; |
|---|
| 65 | _alertHelper = [[_prefs alertHelper] retain]; |
|---|
| 66 | _codeEditHelper = [[CodeEditHelper alloc] initWithDelegate:self]; |
|---|
| 67 | // |
|---|
| 68 | [tabView selectFirstTabViewItem:self]; |
|---|
| 69 | [tabView setDelegate:self]; |
|---|
| 70 | [scriptHelpTextLink setTarget:self selector:@selector(scriptHelpAction:)]; |
|---|
| 71 | _helpMessage = [[scriptMessageField stringValue] retain]; |
|---|
| 72 | [networkCodeView setEditable:NO]; |
|---|
| 73 | // |
|---|
| 74 | [_codeEditHelper setCodeTextView:scriptCodeView |
|---|
| 75 | withText:[_prefs stringForXPath:@"network-settings/proxy-script"]]; |
|---|
| 76 | [scriptCodeView setDelegate:self]; |
|---|
| 77 | [[scriptCodeView layoutManager] setAllowsNonContiguousLayout:YES]; |
|---|
| 78 | [[scriptCodeView textStorage] setDelegate:self]; |
|---|
| 79 | [_codeEditHelper syntaxHighlight:[scriptCodeView textStorage] isJavascript:YES]; |
|---|
| 80 | // |
|---|
| 81 | NSXMLElement* scriptE = (NSXMLElement*)[_prefs nodeForXPath:@"network-settings/proxy-script"]; |
|---|
| 82 | _oldSettingsData = scriptE ? [[Xml subtreeToData:scriptE] retain] : NULL; |
|---|
| 83 | // |
|---|
| 84 | [scriptSheet setFrameUsingName:@"ProxyScriptEditSheet"]; |
|---|
| 85 | [self retain]; // until sheet is dismissed. |
|---|
| 86 | [NSApp beginSheet: scriptSheet |
|---|
| 87 | modalForWindow: [_alertHelper window] |
|---|
| 88 | modalDelegate: nil |
|---|
| 89 | didEndSelector: nil |
|---|
| 90 | contextInfo: nil]; |
|---|
| 91 | [self startTimerWithImmediateExecute:YES]; |
|---|
| 92 | return self; |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | - (void)dealloc |
|---|
| 96 | { |
|---|
| 97 | [self stopTimer]; |
|---|
| 98 | [_alertHelper release]; |
|---|
| 99 | [_prefs release]; |
|---|
| 100 | [_comm release]; |
|---|
| 101 | [_codeEditHelper release]; |
|---|
| 102 | [_oldSettingsData release]; |
|---|
| 103 | [_helpMessage release]; |
|---|
| 104 | [_bundle release]; |
|---|
| 105 | [scriptSheet release]; |
|---|
| 106 | [super dealloc]; |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | - (BOOL)isJavascriptTextStorage:(NSTextStorage*)storage |
|---|
| 110 | { |
|---|
| 111 | return YES; |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | - (void)textStorageDidProcessEditing:(NSNotification *)noti |
|---|
| 115 | { |
|---|
| 116 | [_codeEditHelper textStorageDidProcessEditing:noti]; |
|---|
| 117 | [self startTimerWithImmediateExecute:NO]; |
|---|
| 118 | } |
|---|
| 119 | |
|---|
| 120 | //==================================================================================== |
|---|
| 121 | |
|---|
| 122 | - (void)updateNetworkCodeView |
|---|
| 123 | { |
|---|
| 124 | NSTask* task = [[NSTask alloc] init]; |
|---|
| 125 | NSPipe *readPipe = NULL; |
|---|
| 126 | NSFileHandle *readHandle = NULL; |
|---|
| 127 | NSMutableData* outputData = [[NSMutableData dataWithCapacity:0] retain]; |
|---|
| 128 | NSAssert(_bundle, @"_bundle"); |
|---|
| 129 | NSLog(@"bundle path: %@", [_bundle bundlePath]); |
|---|
| 130 | @try { |
|---|
| 131 | NSString *path = [NSString stringWithFormat:@"%@%@%@", |
|---|
| 132 | [_bundle bundlePath], |
|---|
| 133 | @"/Contents/GlimmerBlockerProxy.app", |
|---|
| 134 | @"/Contents/MacOS/GBNetworkConfigurationListener"]; |
|---|
| 135 | NSLog(@"NHT: %@", path); |
|---|
| 136 | [task setLaunchPath:path]; |
|---|
| 137 | [task setCurrentDirectoryPath:@"/tmp"]; |
|---|
| 138 | readPipe = [[NSPipe pipe] retain]; |
|---|
| 139 | readHandle = [[readPipe fileHandleForReading] retain]; |
|---|
| 140 | [task setStandardOutput:readPipe]; |
|---|
| 141 | [task launch]; |
|---|
| 142 | while (1) { |
|---|
| 143 | NSData* data = [readHandle availableData]; |
|---|
| 144 | if (!data || ![data length]) |
|---|
| 145 | break; |
|---|
| 146 | [outputData appendData:data]; |
|---|
| 147 | } |
|---|
| 148 | [task waitUntilExit]; |
|---|
| 149 | //DebugNSLog(@"task retains: %d", [task retainCount]); |
|---|
| 150 | } |
|---|
| 151 | @catch (NSException* ex) { |
|---|
| 152 | NSLog(@"GBNetworkConfigurationListener failed with exception: %@", [ex name]); |
|---|
| 153 | [GBDebug debugDumpStackframe]; |
|---|
| 154 | } |
|---|
| 155 | if ([task isRunning]) |
|---|
| 156 | [task terminate]; |
|---|
| 157 | [task release]; |
|---|
| 158 | [readHandle release]; |
|---|
| 159 | [readPipe release]; |
|---|
| 160 | NSString* json = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; |
|---|
| 161 | [outputData release]; |
|---|
| 162 | if ([json isEqual:[networkCodeView string]]) |
|---|
| 163 | return; // no need to spend time reformatting code. |
|---|
| 164 | [networkCodeView setString:json]; |
|---|
| 165 | [_codeEditHelper syntaxHighlight:[networkCodeView textStorage] isJavascript:YES]; |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | //==================================================================================== |
|---|
| 169 | |
|---|
| 170 | - (void)startTimerWithImmediateExecute:(BOOL)immediate |
|---|
| 171 | { |
|---|
| 172 | [self stopTimer]; |
|---|
| 173 | _timer = [[NSTimer scheduledTimerWithTimeInterval:(immediate ? 0.1 : 1.2) |
|---|
| 174 | target:self |
|---|
| 175 | selector:@selector(timerCallback:) |
|---|
| 176 | userInfo:NULL |
|---|
| 177 | repeats:NO] retain]; |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | - (void)stopTimer |
|---|
| 181 | { |
|---|
| 182 | if (!_timer) |
|---|
| 183 | return; |
|---|
| 184 | [_timer invalidate]; |
|---|
| 185 | [_timer release]; |
|---|
| 186 | _timer = NULL; |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | - (void)timerCallback:(NSTimer*)theTimer |
|---|
| 190 | { |
|---|
| 191 | [self validateSyntax]; |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | //==================================================================================== |
|---|
| 195 | |
|---|
| 196 | - (BOOL)textView:(NSTextView*)aTextView doCommandBySelector:(SEL)aSelector |
|---|
| 197 | { |
|---|
| 198 | #ifdef DEBUG_DCBS |
|---|
| 199 | DebugNSLog(@"doCommandBySelector: %s", aSelector); |
|---|
| 200 | #endif |
|---|
| 201 | BOOL newlineSelector = (aSelector == @selector(insertNewline:)); |
|---|
| 202 | // I can't find noop: anywhere, so I don't know what the expected return type is. |
|---|
| 203 | BOOL noopSelector = [NSStringFromSelector(aSelector) isEqual:@"noop:"]; |
|---|
| 204 | if (!newlineSelector && !noopSelector) |
|---|
| 205 | return NO; // No, I did not handle it. |
|---|
| 206 | NSEvent* event = [NSApp currentEvent]; |
|---|
| 207 | if ([event type] != NSKeyDown || [event isARepeat]) |
|---|
| 208 | return NO; // No, I did not handle it. |
|---|
| 209 | #ifdef DEBUG_DCBS |
|---|
| 210 | DebugNSLog(@" current keydown event: %@", [NSApp currentEvent]); |
|---|
| 211 | #endif |
|---|
| 212 | NSUInteger modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; |
|---|
| 213 | NSString* s = [event charactersIgnoringModifiers]; |
|---|
| 214 | BOOL enter = newlineSelector && !modifiers && [s length] == 1 && [s characterAtIndex:0] == 3; |
|---|
| 215 | BOOL appleReturn = noopSelector && (modifiers == NSCommandKeyMask) && [s length] == 1 && [s characterAtIndex:0] == 13; |
|---|
| 216 | #ifdef DEBUG_DCBS |
|---|
| 217 | DebugNSLog(@"KeyDown(%d = %x) for %d, enter = %d, appleReturn = %d", modifiers, modifiers, [s characterAtIndex:0], enter, appleReturn); |
|---|
| 218 | #endif |
|---|
| 219 | if (!enter && !appleReturn) |
|---|
| 220 | return NO; // No, I did not handle it. |
|---|
| 221 | #ifdef DEBUG_DCBS |
|---|
| 222 | DebugNSLog(@"KeyDown executes self.commitAction:"); |
|---|
| 223 | #endif |
|---|
| 224 | [saveBtn highlight:YES]; |
|---|
| 225 | [self performSelectorOnMainThread:@selector(commitAction:) |
|---|
| 226 | withObject:self |
|---|
| 227 | waitUntilDone:NO]; |
|---|
| 228 | return YES; // Yes, I did handle it. |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | //==================================================================================== |
|---|
| 232 | |
|---|
| 233 | - (IBAction)scriptHelpAction:(id)sender |
|---|
| 234 | { |
|---|
| 235 | NSURL* url = [NSURL URLWithString:@"http://glimmerblocker.org/wiki/Proxies"]; |
|---|
| 236 | [[NSWorkspace sharedWorkspace] openURL:url]; |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | - (void)dismissSheet |
|---|
| 240 | { |
|---|
| 241 | [scriptSheet saveFrameUsingName:@"ProxyScriptEditSheet"]; |
|---|
| 242 | [NSApp endSheet:scriptSheet]; |
|---|
| 243 | [scriptSheet orderOut:self]; |
|---|
| 244 | } |
|---|
| 245 | |
|---|
| 246 | - (IBAction)cancelAction:(id)sender |
|---|
| 247 | { |
|---|
| 248 | [self stopTimer]; |
|---|
| 249 | [self dismissSheet]; |
|---|
| 250 | [self autorelease]; |
|---|
| 251 | } |
|---|
| 252 | |
|---|
| 253 | - (BOOL)validateSyntax |
|---|
| 254 | { |
|---|
| 255 | NSString* js = [_codeEditHelper trimmedTextViewString:scriptCodeView]; |
|---|
| 256 | NSString* pathArgs = [NSString stringWithFormat:@"/site-proxy/validate?javascript=%@", |
|---|
| 257 | [GBDebug percentEscapeUrlParameterValue:js]]; |
|---|
| 258 | XmlDoc* doc = [_comm curlToXml:pathArgs withActionDescription:NULL]; |
|---|
| 259 | if (!doc) { |
|---|
| 260 | DebugNSLog(@"Problem validating JS."); |
|---|
| 261 | return YES; // don't stop the user saving it, if the proxy failed running. |
|---|
| 262 | } |
|---|
| 263 | NSString* msg = [doc stringForXPath:@"@proxy-error"]; |
|---|
| 264 | if ([msg length]) { |
|---|
| 265 | [scriptMessageField setStringValue:msg]; |
|---|
| 266 | [scriptMessageField setTextColor:[NSColor redColor]]; |
|---|
| 267 | return NO; |
|---|
| 268 | } else { |
|---|
| 269 | [scriptMessageField setStringValue:_helpMessage]; |
|---|
| 270 | [scriptMessageField setTextColor:[NSColor blackColor]]; |
|---|
| 271 | return YES; |
|---|
| 272 | } |
|---|
| 273 | } |
|---|
| 274 | |
|---|
| 275 | - (void)saveSettingsInElement:(NSXMLElement*)scriptE |
|---|
| 276 | { |
|---|
| 277 | NSString* js = [_codeEditHelper trimmedTextViewString:scriptCodeView]; |
|---|
| 278 | //DebugNSLog(@"Script: {{{%@}}}", js); |
|---|
| 279 | if (![js length]) |
|---|
| 280 | return; |
|---|
| 281 | [Xml setCData:js forXPath:@"." withRoot:scriptE]; |
|---|
| 282 | [Xml setAttribute:@"js" withName:@"language" inElement:scriptE]; |
|---|
| 283 | [Xml setAttribute:@"1" withName:@"version" inElement:scriptE]; |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | - (BOOL)isDirty |
|---|
| 287 | { |
|---|
| 288 | NSXMLElement* scriptE = [Xml createElementWithName:@"proxy-script"]; |
|---|
| 289 | [self saveSettingsInElement:scriptE]; |
|---|
| 290 | NSData* data = [Xml subtreeToData:scriptE]; |
|---|
| 291 | return ![data isEqualToData:_oldSettingsData]; |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | - (IBAction)commitAction:(id)sender |
|---|
| 295 | { |
|---|
| 296 | [self stopTimer]; |
|---|
| 297 | BOOL dirty = [self isDirty]; |
|---|
| 298 | if (dirty) { |
|---|
| 299 | NSXMLElement* scriptE = [_prefs singletonElementForXPath:@"network-settings/proxy-script"]; |
|---|
| 300 | [self saveSettingsInElement:scriptE]; |
|---|
| 301 | [_prefs markAsDirty]; |
|---|
| 302 | [_comm saveDirtyPrefsAndNotify]; |
|---|
| 303 | } |
|---|
| 304 | [self dismissSheet]; |
|---|
| 305 | [_delegate notifyProxyScriptEditorCommitted:self]; |
|---|
| 306 | [self autorelease]; |
|---|
| 307 | } |
|---|
| 308 | |
|---|
| 309 | @end |
|---|
| 310 | |
|---|
| 311 | @implementation ProxyScriptEditor(NSTabViewDelegate) |
|---|
| 312 | |
|---|
| 313 | - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem |
|---|
| 314 | { |
|---|
| 315 | if ([[tabViewItem identifier] isEqual:@"network"]) |
|---|
| 316 | [self updateNetworkCodeView]; |
|---|
| 317 | } |
|---|
| 318 | |
|---|
| 319 | @end |
|---|