3 * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 #import <sys/socket.h>
18 #import <netinet/in.h>
20 #import "GDBpConnection.h"
22 #import "AppDelegate.h"
24 // GDBpConnection (Private) ////////////////////////////////////////////////////
26 @interface GDBpConnection ()
27 @property (readwrite
, copy
) NSString
* status
;
28 @property (assign
) CFSocketRef socket
;
29 @property (assign
) CFReadStreamRef readStream
;
30 @property int lastReadTransaction
;
31 @property (retain
) NSMutableString
* currentPacket
;
32 @property (assign
) CFWriteStreamRef writeStream
;
33 @property int lastWrittenTransaction
;
34 @property (retain
) NSMutableArray
* queuedWrites
;
38 - (void)socketDidAccept
;
39 - (void)socketDisconnected
;
40 - (void)readStreamHasData
;
41 - (void)send
:(NSString
*)command
;
42 - (void)performSend
:(NSString
*)command
;
43 - (void)errorEncountered
:(NSString
*)error
;
45 - (void)handleResponse
:(NSXMLDocument
*)response
;
46 - (void)initReceived
:(NSXMLDocument
*)response
;
47 - (void)updateStatus
:(NSXMLDocument
*)response
;
48 - (void)debuggerStep
:(NSXMLDocument
*)response
;
49 - (void)rebuildStack
:(NSXMLDocument
*)response
;
50 - (void)getStackFrame
:(NSXMLDocument
*)response
;
51 - (void)setSource
:(NSXMLDocument
*)response
;
52 - (void)contextsReceived
:(NSXMLDocument
*)response
;
54 - (NSNumber
*)sendCommandWithCallback
:(SEL)callback format
:(NSString
*)format
, ...
;
56 - (void)sendQueuedWrites
;
58 - (StackFrame
*)createStackFrame
:(int)depth
;
59 - (NSString
*)escapedURIPath
:(NSString
*)path
;
62 // CFNetwork Callbacks /////////////////////////////////////////////////////////
64 void ReadStreamCallback(CFReadStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
66 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
69 case kCFStreamEventHasBytesAvailable
:
70 NSLog(@
"About to read.");
71 [connection readStreamHasData
];
74 case kCFStreamEventErrorOccurred
:
76 CFErrorRef error
= CFReadStreamCopyError(stream
);
77 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
78 CFReadStreamClose(stream
);
80 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
84 case kCFStreamEventEndEncountered
:
85 CFReadStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
86 CFReadStreamClose(stream
);
88 [connection socketDisconnected
];
93 void WriteStreamCallback(CFWriteStreamRef stream
, CFStreamEventType eventType
, void* connectionRaw
)
95 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
98 case kCFStreamEventCanAcceptBytes
:
99 [connection sendQueuedWrites
];
102 case kCFStreamEventErrorOccurred
:
104 CFErrorRef error
= CFWriteStreamCopyError(stream
);
105 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
106 CFWriteStreamClose(stream
);
108 [connection errorEncountered
:[[(NSError
*)error autorelease
] description
]];
112 case kCFStreamEventEndEncountered
:
113 CFWriteStreamUnscheduleFromRunLoop(stream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
114 CFWriteStreamClose(stream
);
116 [connection socketDisconnected
];
121 void SocketAcceptCallback(CFSocketRef socket
,
122 CFSocketCallBackType callbackType
,
127 assert(callbackType
== kCFSocketAcceptCallBack
);
128 NSLog(@
"SocketAcceptCallback()");
130 GDBpConnection
* connection
= (GDBpConnection
*)connectionRaw
;
132 CFReadStreamRef readStream
;
133 CFWriteStreamRef writeStream
;
135 // Create the streams on the socket.
136 CFStreamCreatePairWithSocket(kCFAllocatorDefault
,
137 *(CFSocketNativeHandle
*)data
, // Socket handle.
138 &readStream
, // Read stream in-pointer.
139 &writeStream
); // Write stream in-pointer.
141 // Create struct to register callbacks for the stream.
142 CFStreamClientContext context
;
144 context.info
= connection
;
145 context.retain
= NULL
;
146 context.release
= NULL
;
147 context.copyDescription
= NULL
;
149 // Set the client of the read stream.
150 CFOptionFlags readFlags
=
151 kCFStreamEventOpenCompleted |
152 kCFStreamEventHasBytesAvailable |
153 kCFStreamEventErrorOccurred |
154 kCFStreamEventEndEncountered
;
155 if (CFReadStreamSetClient(readStream
, readFlags
, ReadStreamCallback
, &context
))
156 // Schedule in run loop to do asynchronous communication with the engine.
157 CFReadStreamScheduleWithRunLoop(readStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
161 // Open the stream now that it's scheduled on the run loop.
162 if (!CFReadStreamOpen(readStream
))
164 CFStreamError error
= CFReadStreamGetError(readStream
);
165 NSLog(@
"error! %@", error
);
169 // Set the client of the write stream.
170 CFOptionFlags writeFlags
=
171 kCFStreamEventOpenCompleted |
172 kCFStreamEventCanAcceptBytes |
173 kCFStreamEventErrorOccurred |
174 kCFStreamEventEndEncountered
;
175 if (CFWriteStreamSetClient(writeStream
, writeFlags
, WriteStreamCallback
, &context
))
176 // Schedule it in the run loop to receive error information.
177 CFWriteStreamScheduleWithRunLoop(writeStream
, CFRunLoopGetCurrent(), kCFRunLoopCommonModes
);
181 // Open the write stream.
182 if (!CFWriteStreamOpen(writeStream
))
184 CFStreamError error
= CFWriteStreamGetError(writeStream
);
185 NSLog(@
"error! %@", error
);
189 connection.readStream
= readStream
;
190 connection.writeStream
= writeStream
;
191 [connection socketDidAccept
];
194 // GDBpConnection //////////////////////////////////////////////////////////////
196 @implementation GDBpConnection
197 @synthesize socket
= socket_
;
198 @synthesize readStream
= readStream_
;
199 @synthesize lastReadTransaction
= lastReadTransaction_
;
200 @synthesize currentPacket
= currentPacket_
;
201 @synthesize writeStream
= writeStream_
;
202 @synthesize lastWrittenTransaction
= lastWrittenTransaction_
;
203 @synthesize queuedWrites
= queuedWrites_
;
205 @synthesize delegate
;
208 * Creates a new DebuggerConnection and initializes the socket from the given connection
211 - (id)initWithPort
:(int)aPort
213 if (self = [super init
])
218 [[BreakpointManager sharedManager
] setConnection
:self];
226 * Deallocates the object
231 self.currentPacket
= nil;
237 * Gets the port number
245 * Returns the name of the remote host
247 - (NSString
*)remoteHost
251 return @
"(DISCONNECTED)";
253 // TODO: Either impl or remove.
258 * Returns whether or not we have an active connection
266 * Called by SocketWrapper after the connection is successful. This immediately calls
267 * -[SocketWrapper receive] to clear the way for communication, though the information
268 * could be useful server information that we don't use right now.
270 - (void)socketDidAccept
274 stackFrames_
= [[NSMutableDictionary alloc
] init
];
275 self.queuedWrites
= [NSMutableArray array
];
276 writeQueueLock_
= [NSRecursiveLock
new];
277 callTable_
= [NSMutableDictionary
new];
278 callbackContext_
= [NSMutableDictionary
new];
282 * Receives errors from the SocketWrapper and updates the display
284 - (void)errorEncountered
:(NSString
*)error
286 [delegate errorEncountered
:error
];
290 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
291 * created every time you want to debug a page
296 self.status
= @
"Connecting";
301 * Creates an entirely new stack and returns it as an array of StackFrame objects.
303 - (NSArray
*)getCurrentStack
305 NSMutableArray
* stack
= [NSMutableArray array
];
306 NSLog(@
"NOTIMPLEMENTED(): %s", _cmd
);
311 * Tells the debugger to continue running the script. Returns the current stack frame.
315 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"run"];
319 * Tells the debugger to step into the current command.
323 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_into"];
327 * Tells the debugger to step out of the current context
331 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_out"];
335 * Tells the debugger to step over the current function
339 [self sendCommandWithCallback
:@selector(debuggerStep
:) format
:@
"step_over"];
343 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
344 * that requested it so that the child can be attached.
346 - (NSArray
*)getProperty
:(NSString
*)property
348 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"property_get -n \"%@\"", property
]]];
350 NSXMLDocument
* doc
= [self processData
:[socket receive
]];
354 <property> <!-- this is the one we requested -->
355 <property ... /> <!-- these are what we want -->
360 // we now have to detach all the children so we can insert them into another document
361 NSXMLElement
* parent
= (NSXMLElement
*)[[doc rootElement
] childAtIndex
:0];
362 NSArray
* children
= [parent children
];
363 [parent setChildren
:nil];
367 #pragma mark Breakpoints
370 * Send an add breakpoint command
372 - (void)addBreakpoint
:(Breakpoint
*)bp
377 NSString
* file
= [self escapedURIPath
:[bp transformedPath
]];
378 NSString
* cmd
= [self createCommand
:[NSString stringWithFormat
:@
"breakpoint_set -t line -f %@ -n %i", file
, [bp line
]]];
380 NSXMLDocument
* info
= [self processData
:[socket receive
]];
381 [bp setDebuggerId
:[[[[info rootElement
] attributeForName
:@
"id"] stringValue
] intValue
]];
385 * Removes a breakpoint
387 - (void)removeBreakpoint
:(Breakpoint
*)bp
394 [self sendCommandWithCallback
:nil format
:@
"breakpoint_remove -d %i", [bp debuggerId
]];
397 #pragma mark Socket and Stream Callbacks
400 * Creates, connects to, and schedules a CFSocket.
404 // Pass ourselves to the callback so we don't have to use ugly globals.
405 CFSocketContext context
;
408 context.retain
= NULL
;
409 context.release
= NULL
;
410 context.copyDescription
= NULL
;
412 // Create the address structure.
413 struct sockaddr_in address
;
414 memset(&address
, 0, sizeof(address
));
415 address.sin_len
= sizeof(address
);
416 address.sin_family
= AF_INET
;
417 address.sin_port
= htons(port
);
418 address.sin_addr.s_addr
= htonl(INADDR_ANY
);
420 // Create the socket signature.
421 CFSocketSignature signature
;
422 signature.protocolFamily
= PF_INET
;
423 signature.socketType
= SOCK_STREAM
;
424 signature.protocol
= IPPROTO_TCP
;
425 signature.address
= (CFDataRef
)[NSData dataWithBytes
:&address length
:sizeof(address
)];
427 socket_
= CFSocketCreateWithSocketSignature(kCFAllocatorDefault
,
428 &signature
, // Socket signature.
429 kCFSocketAcceptCallBack
, // Callback types.
430 SocketAcceptCallback
, // Callout function pointer.
431 &context
); // Context to pass to callout.
434 [self errorEncountered
:@
"Could not open socket."];
438 // Allow old, yet-to-be recycled sockets to be reused.
440 setsockopt(CFSocketGetNative(socket_
), SOL_SOCKET
, SO_REUSEADDR
, &yes
, sizeof(BOOL));
442 // Schedule the socket on the run loop.
443 CFRunLoopSourceRef source
= CFSocketCreateRunLoopSource(kCFAllocatorDefault
, socket_
, 0);
444 CFRunLoopAddSource(CFRunLoopGetCurrent(), source
, kCFRunLoopCommonModes
);
447 self.status
= @
"Connecting";
451 * Closes a socket and releases the ref.
455 // The socket goes down, so do the streams, which clean themselves up.
456 CFSocketInvalidate(socket_
);
458 [stackFrames_ release
];
459 self.queuedWrites
= nil;
460 [writeQueueLock_ release
];
461 [callTable_ release
];
462 [callbackContext_ release
];
466 * Notification that the socket disconnected.
468 - (void)socketDisconnected
471 [delegate debuggerDisconnected
];
475 * Callback from the CFReadStream that there is data waiting to be read.
477 - (void)readStreamHasData
480 CFIndex bytesRead
= CFReadStreamRead(readStream_
, buffer
, 1024);
481 const char* charBuffer
= (const char*)buffer
;
483 // We haven't finished reading a packet, so just read more data in.
484 if (currentPacketIndex_
< packetSize_
)
486 currentPacketIndex_
+= bytesRead
;
487 CFStringRef bufferString
= CFStringCreateWithBytes(kCFAllocatorDefault
,
490 kCFStringEncodingUTF8
,
492 [self.currentPacket appendString
:(NSString
*)bufferString
];
493 CFRelease(bufferString
);
495 // Time to read a new packet.
498 // Read the message header: the size.
499 packetSize_
= atoi(charBuffer
);
500 currentPacketIndex_
= bytesRead
- strlen(charBuffer
);
501 CFStringRef bufferString
= CFStringCreateWithBytes(kCFAllocatorDefault
,
502 buffer
+ strlen(charBuffer
) + 1,
503 bytesRead
- strlen(charBuffer
) - 1,
504 kCFStringEncodingUTF8
,
506 self.currentPacket
= [NSMutableString stringWithString
:(NSString
*)bufferString
];
507 CFRelease(bufferString
);
510 // We have finished reading the packet.
511 if (currentPacketIndex_
>= packetSize_
)
514 currentPacketIndex_
= 0;
516 // Test if we can convert it into an NSXMLDocument.
517 NSError
* error
= nil;
518 NSXMLDocument
* xmlTest
= [[NSXMLDocument alloc
] initWithXMLString
:currentPacket_ options
:NSXMLDocumentTidyXML error
:&error
];
521 NSLog(@
"Could not parse XML? --- %@", error
);
522 NSLog(@
"Error UserInfo: %@", [error userInfo
]);
523 NSLog(@
"This is the XML Document: %@", currentPacket_
);
526 [self handleResponse
:[xmlTest autorelease
]];
531 * Writes a command into the write stream. If the stream is ready for writing,
532 * we do so immediately. If not, the command is queued and will be written
533 * when the stream is ready.
535 - (void)send
:(NSString
*)command
537 if (lastReadTransaction_
>= lastWrittenTransaction_
&& CFWriteStreamCanAcceptBytes(writeStream_
))
538 [self performSend
:command
];
540 [queuedWrites_ addObject
:command
];
544 * This performs a blocking send. This should ONLY be called when we know we
545 * have write access to the stream. We will busy wait in case we don't do a full
548 - (void)performSend
:(NSString
*)command
552 char* string
= (char*)[command UTF8String
];
553 int stringLength
= strlen(string
);
555 // Log the command if TransportDebug is enabled.
556 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
557 NSLog(@
"--> %@", command
);
559 // Busy wait while writing. BAADD. Should background this operation.
562 if (CFWriteStreamCanAcceptBytes(writeStream_
))
564 // Include the NULL byte in the string when we write.
565 int bytesWritten
= CFWriteStreamWrite(writeStream_
, (UInt8
*)string
, stringLength
+ 1);
566 if (bytesWritten
< 0)
568 NSLog(@
"write error");
571 else if (bytesWritten
< strlen(string
))
573 // Adjust the buffer and wait for another chance to write.
574 stringLength
-= bytesWritten
;
575 memmove(string
, string
+ bytesWritten
, stringLength
);
581 // We need to scan the string to find the transactionID.
582 NSRange occurrence
= [command rangeOfString
:@
"-i "];
583 if (occurrence.location
== NSNotFound
)
585 NSLog(@
"sent %@ without a transaction ID", command
);
588 NSString
* transaction
= [command substringFromIndex
:occurrence.location
+ occurrence.length
];
589 lastWrittenTransaction_
= [transaction intValue
];
590 NSLog(@
"command = %@", command
);
591 NSLog(@
"read=%d, write=%d", lastReadTransaction_
, lastWrittenTransaction_
);
597 #pragma mark Response Handlers
599 - (void)handleResponse
:(NSXMLDocument
*)response
601 // Check and see if there's an error.
602 NSArray
* error
= [[response rootElement
] elementsForName
:@
"error"];
603 if ([error count
] > 0)
605 NSLog(@
"Xdebug error: %@", error
);
606 [delegate errorEncountered
:[[[[error objectAtIndex
:0] children
] objectAtIndex
:0] stringValue
]];
609 // If TransportDebug is enabled, log the response.
610 if ([[[[NSProcessInfo processInfo
] environment
] objectForKey
:@
"TransportDebug"] boolValue
])
611 NSLog(@
"<-- %@", response
);
613 // Get the name of the command from the engine's response.
614 NSInteger transaction
= [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
615 if (transaction
< lastReadTransaction_
)
616 NSLog(@
"out of date transaction %@", response
);
618 if (transaction
!= lastWrittenTransaction_
)
619 NSLog(@
"txn doesn't match last written %@", response
);
621 lastReadTransaction_
= transaction
;
622 NSLog(@
"read=%d, write=%d", lastReadTransaction_
, lastWrittenTransaction_
);
624 if ([[[response rootElement
] name
] isEqualToString
:@
"init"])
626 [self initReceived
:response
];
630 SEL callback
= NSSelectorFromString([callTable_ objectForKey
:[NSNumber numberWithInt
:lastReadTransaction_
]]);
631 [self performSelector
:callback withObject
:response
];
633 [self sendQueuedWrites
];
637 * Initial packet received. We've started a brand-new connection to the engine.
639 - (void)initReceived
:(NSXMLDocument
*)response
641 // Register any breakpoints that exist offline.
642 for (Breakpoint
* bp
in [[BreakpointManager sharedManager
] breakpoints
])
643 [self addBreakpoint
:bp
];
645 // Load the debugger to make it look active.
646 [delegate debuggerConnected
];
648 // TODO: update the status.
652 * Receiver for status updates. This just freshens up the UI.
654 - (void)updateStatus
:(NSXMLDocument
*)response
656 self.status
= [[[[response rootElement
] attributeForName
:@
"status"] stringValue
] capitalizedString
];
657 if (status
== nil ||
[status isEqualToString
:@
"Stopped"] ||
[status isEqualToString
:@
"Stopping"])
661 [delegate debuggerDisconnected
];
663 self.status
= @
"Stopped";
668 * Step in/out/over and run all take this path. We first get the status of the
669 * debugger and then request fresh stack information.
671 - (void)debuggerStep
:(NSXMLDocument
*)response
673 [self sendCommandWithCallback
:@selector(updateStatus
:) format
:@
"status"];
674 NSString
* command
= [[[response rootElement
] attributeForName
:@
"command"] stringValue
];
676 // If this is the run command, tell the delegate that a bunch of updates
677 // are coming. Also remove all existing stack routes and request a new stack.
678 // TODO: figure out if we can not clobber the stack every time.
679 if (YES ||
[command isEqualToString
:@
"run"])
681 //[delegate clobberStack];
682 [stackFrames_ removeAllObjects
];
683 [self sendCommandWithCallback
:@selector(rebuildStack
:) format
:@
"stack_depth"];
688 * We ask for the stack_depth and now we clobber the stack and start rebuilding
691 - (void)rebuildStack
:(NSXMLDocument
*)response
693 NSInteger depth
= [[[[response rootElement
] attributeForName
:@
"depth"] stringValue
] intValue
];
695 // We now need to alloc a bunch of stack frames and get the basic information
697 for (NSInteger i
= 0; i
< depth
; i
++)
699 // Use the transaction ID to create a routing path.
700 NSNumber
* routingID
= [self sendCommandWithCallback
:@selector(getStackFrame
:) format
:@
"stack_get -d %d", i
];
701 [stackFrames_ setObject
:[StackFrame alloc
] forKey
:routingID
];
706 * The initial rebuild of the stack frame. We now have enough to initialize
707 * a StackFrame object.
709 - (void)getStackFrame
:(NSXMLDocument
*)response
711 // Get the routing information.
712 NSUInteger routingID
= [[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
];
713 NSNumber
* routingNumber
= [NSNumber numberWithInt
:routingID
];
715 // Make sure we initialized this frame in our last |-rebuildStack:|.
716 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
720 NSXMLElement
* xmlframe
= [[[response rootElement
] children
] objectAtIndex
:0];
722 // Initialize the stack frame.
723 [frame initWithIndex
:[[[xmlframe attributeForName
:@
"level"] stringValue
] intValue
]
724 withFilename
:[[xmlframe attributeForName
:@
"filename"] stringValue
]
726 atLine
:[[[xmlframe attributeForName
:@
"lineno"] stringValue
] intValue
]
727 inFunction
:[[xmlframe attributeForName
:@
"where"] stringValue
]
730 // Get the source code of the file. Escape % in URL chars.
731 NSString
* escapedFilename
= [frame.filename stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];
732 NSNumber
* transaction
= [self sendCommandWithCallback
:@selector(setSource
:) format
:@
"source -f %@", escapedFilename
];
733 [callbackContext_ setObject
:routingNumber forKey
:transaction
];
735 // Get the names of all the contexts.
736 transaction
= [self sendCommandWithCallback
:@selector(contextsReceived
:) format
:@
"context_names -d %d", frame.index
];
737 [callbackContext_ setObject
:routingNumber forKey
:transaction
];
740 - (void)setSource
:(NSXMLDocument
*)response
742 NSNumber
* transaction
= [NSNumber numberWithInt
:[[[[response rootElement
] attributeForName
:@
"transaction_id"] stringValue
] intValue
]];
743 NSNumber
* routingNumber
= [callbackContext_ objectForKey
:transaction
];
747 [callbackContext_ removeObjectForKey
:transaction
];
748 StackFrame
* frame
= [stackFrames_ objectForKey
:routingNumber
];
752 frame.source
= [[response rootElement
] value
];
753 NSLog(@
"frame.source = %@", frame.source
);
756 - (void)contextsReceived
:(NSXMLDocument
*)response
758 NSLog(@
"got contexts = %@", response
);
764 * This will send a command to the debugger engine. It will append the
765 * transaction ID automatically. It accepts a NSString command along with a
766 * a variable number of arguments to substitute into the command, a la
767 * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
769 - (NSNumber
*)sendCommandWithCallback
:(SEL)callback format
:(NSString
*)format
, ...
771 // Collect varargs and format command.
773 va_start(args
, format
);
774 NSString
* command
= [[NSString alloc
] initWithFormat
:format arguments
:args
];
777 NSNumber
* callbackKey
= [NSNumber numberWithInt
:transactionID
++];
779 [callTable_ setObject
:NSStringFromSelector(callback
) forKey
:callbackKey
];
781 [self send
:[NSString stringWithFormat
:@
"%@ -i %@", [command autorelease
], callbackKey
]];
787 * Checks if there are unsent commands in the |queuedWrites_| queue and sends
788 * them if it's OK to do so. This will not block.
790 - (void)sendQueuedWrites
792 [writeQueueLock_ lock
];
793 if (lastReadTransaction_
>= lastWrittenTransaction_
&& [queuedWrites_ count
] > 0)
795 NSString
* command
= [queuedWrites_ objectAtIndex
:0];
796 NSLog(@
"Sending queued write: %@", command
);
798 // We don't want to block because this is called from the main thread.
799 // |-performSend:| busy waits when the stream is not ready. Bail out
800 // before we do that becuase busy waiting is BAD.
801 if (CFWriteStreamCanAcceptBytes(writeStream_
))
803 [self performSend
:command
];
804 [queuedWrites_ removeObjectAtIndex
:0];
807 [writeQueueLock_ unlock
];
811 * Generates a stack frame for the given depth
813 - (StackFrame
*)createStackFrame
:(int)stackDepth
815 // get the names of all the contexts
816 NSXMLElement
* contextNames
= [[self processData
:[socket receive
]] rootElement
];
817 NSMutableArray
* variables
= [NSMutableArray array
];
818 for (NSXMLElement
* context
in [contextNames children
])
820 NSString
* name
= [[context attributeForName
:@
"name"] stringValue
];
821 int cid
= [[[context attributeForName
:@
"id"] stringValue
] intValue
];
823 // fetch the contexts
824 [socket send
:[self createCommand
:[NSString stringWithFormat
:@
"context_get -d %d -c %d", stackDepth
, cid
]]];
825 NSArray
* addVars
= [[[self processData
:[socket receive
]] rootElement
] children
];
826 if (addVars
!= nil && name
!= nil)
827 [variables addObjectsFromArray
:addVars
];
834 * Given a file path, this returns a file:// URI and escapes any spaces for the
837 - (NSString
*)escapedURIPath
:(NSString
*)path
839 // Custon GDBp paths are fine.
840 if ([[path substringToIndex
:4] isEqualToString
:@
"gdbp"])
843 // Create a temporary URL that will escape all the nasty characters.
844 NSURL
* url
= [NSURL fileURLWithPath
:path
];
845 NSString
* urlString
= [url absoluteString
];
847 // Remove the host because this is a file:// URL;
848 urlString
= [urlString stringByReplacingOccurrencesOfString
:[url host
] withString
:@
""];
850 // Escape % for use in printf-style NSString formatters.
851 urlString
= [urlString stringByReplacingOccurrencesOfString
:@
"%" withString
:@
"%%"];