This compiles, but will most certainly not work. This is just the initial separation of
methods and ivars that needs to be rewired.
1E02C3D50C60EC2C006F1752 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E02C3D40C60EC2C006F1752 /* AppDelegate.m */; };
1E02C5710C610158006F1752 /* DebuggerProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E02C5700C610158006F1752 /* DebuggerProcessor.m */; };
1E02C5F60C610724006F1752 /* DebuggerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E02C5F50C610724006F1752 /* DebuggerController.m */; };
+ 1E0724E311B47BCC0017AD3C /* DebuggerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */; };
1E0AFBB90FC2518700C67031 /* HUDIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E0AFBB80FC2518700C67031 /* HUDIcon.png */; };
1E1E53030DF9B89800D334F9 /* Breakpoints.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E1E53010DF9B89800D334F9 /* Breakpoints.xib */; };
1E35FC760C6579CA0030F527 /* NSXMLElementAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E35FC750C6579CA0030F527 /* NSXMLElementAdditions.m */; };
1E02C5700C610158006F1752 /* DebuggerProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerProcessor.m; path = Source/DebuggerProcessor.m; sourceTree = "<group>"; };
1E02C5F40C610724006F1752 /* DebuggerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DebuggerController.h; path = Source/DebuggerController.h; sourceTree = "<group>"; };
1E02C5F50C610724006F1752 /* DebuggerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerController.m; path = Source/DebuggerController.m; sourceTree = "<group>"; };
+ 1E0724E111B47BCC0017AD3C /* DebuggerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DebuggerConnection.h; path = Source/DebuggerConnection.h; sourceTree = "<group>"; };
+ 1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerConnection.m; path = Source/DebuggerConnection.m; sourceTree = "<group>"; };
1E0AFBB80FC2518700C67031 /* HUDIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = HUDIcon.png; path = Icons/HUDIcon.png; sourceTree = "<group>"; };
1E1E53020DF9B89800D334F9 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/Breakpoints.xib; sourceTree = "<group>"; };
1E35FC750C6579CA0030F527 /* NSXMLElementAdditions.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = NSXMLElementAdditions.m; path = Source/NSXMLElementAdditions.m; sourceTree = "<group>"; };
1E1E52C10DF9B1FB00D334F9 /* Connection */ = {
isa = PBXGroup;
children = (
+ 1E0724E111B47BCC0017AD3C /* DebuggerConnection.h */,
+ 1E0724E211B47BCC0017AD3C /* DebuggerConnection.m */,
1E02C56F0C610158006F1752 /* DebuggerProcessor.h */,
1E02C5700C610158006F1752 /* DebuggerProcessor.m */,
1E35FFB00C65A74C0030F527 /* NSXMLElementAdditions.h */,
1EBF4D5D0EE35F0700B62769 /* StackController.m in Sources */,
1E67E6FD0F3C052000E68F1B /* PreferencesPathsArrayController.m in Sources */,
1E6B5947116106FE001189D2 /* LoggingController.m in Sources */,
+ 1E0724E311B47BCC0017AD3C /* DebuggerConnection.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
--- /dev/null
+/*
+ * MacGDBp
+ * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@class LoggingController;
+
+@interface DebuggerConnection : NSObject
+{
+ // The port to connect on.
+ NSUInteger port;
+
+ // If the connection to the debugger engine is currently active.
+ BOOL connected;
+
+ // The raw CFSocket on which the two streams are based. Strong.
+ CFSocketRef socket_;
+
+ // The read stream that is scheduled on the main run loop. Weak.
+ CFReadStreamRef readStream_;
+
+ // The write stream. Weak.
+ CFWriteStreamRef writeStream_;
+
+ // An ever-increasing integer that gives each transaction a unique ID for the
+ // debugging engine.
+ NSUInteger transactionID;
+
+ // The most recently received transaction ID.
+ NSUInteger lastReadTransaction_;
+
+ // The last transactionID written to the stream.
+ NSUInteger lastWrittenTransaction_;
+
+ // Callback table. This maps transaction IDs to selectors. When the engine
+ // returns a response to the debugger, we will dispatch the response XML to
+ // the selector, based on transaction_id.
+ NSMutableDictionary* callTable_;
+
+ // To prevent blocked writing, we enqueue all writes and then wait for the
+ // write stream to tell us it's ready. We store the pending commands in this
+ // array. We use this as a stack (FIFO), with index 0 being first.
+ NSMutableArray* queuedWrites_;
+
+ // We send queued writes in multiple places, sometimes off a run loop event.
+ // Because of this, we need to ensure that only one client is dequeing and
+ // sending at a time.
+ NSRecursiveLock* writeQueueLock_;
+
+ // Information about the current read loop. We append to |currentPacket_|
+ // until |currentPacketSize_| has reached |packetSize_|.
+ NSMutableString* currentPacket_;
+ int packetSize_;
+ int currentPacketIndex_;
+
+ // The delegate.
+ id delegate_;
+}
+
+- (void)connect;
+- (void)close;
+- (void)socketDidAccept;
+- (void)socketDisconnected;
+- (void)readStreamHasData;
+- (void)send:(NSString*)command;
+- (void)performSend:(NSString*)command;
+- (void)errorEncountered:(NSString*)error;
+
+- (void)handleResponse:(NSXMLDocument*)response;
+- (void)handlePacket:(NSString*)packet;
+
+- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...;
+
+- (void)sendQueuedWrites;
+
+- (NSString*)escapedURIPath:(NSString*)path;
+- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response;
+- (NSInteger)transactionIDFromCommand:(NSString*)command;
+
+@end
--- /dev/null
+/*
+ * MacGDBp
+ * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if not,
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#import "DebuggerConnection.h"
+
+#import <sys/socket.h>
+#import <netinet/in.h>
+
+#import "AppDelegate.h"
+#import "LoggingController.h"
+
+// DebuggerConnection (Private) ////////////////////////////////////////////////
+
+@interface DebuggerConnection ()
+
+@property (assign) CFSocketRef socket;
+@property (assign) CFReadStreamRef readStream;
+@property NSUInteger lastReadTransaction;
+@property (retain) NSMutableString* currentPacket;
+@property (assign) CFWriteStreamRef writeStream;
+@property NSUInteger lastWrittenTransaction;
+@property (retain) NSMutableArray* queuedWrites;
+
+@end
+
+// CFNetwork Callbacks /////////////////////////////////////////////////////////
+
+void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
+{
+ DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
+ switch (eventType)
+ {
+ case kCFStreamEventHasBytesAvailable:
+ [connection readStreamHasData];
+ break;
+
+ case kCFStreamEventErrorOccurred:
+ {
+ CFErrorRef error = CFReadStreamCopyError(stream);
+ CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFReadStreamClose(stream);
+ CFRelease(stream);
+ [connection errorEncountered:[[(NSError*)error autorelease] description]];
+ break;
+ }
+
+ case kCFStreamEventEndEncountered:
+ CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFReadStreamClose(stream);
+ CFRelease(stream);
+ [connection socketDisconnected];
+ break;
+ };
+}
+
+void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
+{
+ DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
+ switch (eventType)
+ {
+ case kCFStreamEventCanAcceptBytes:
+ [connection sendQueuedWrites];
+ break;
+
+ case kCFStreamEventErrorOccurred:
+ {
+ CFErrorRef error = CFWriteStreamCopyError(stream);
+ CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFWriteStreamClose(stream);
+ CFRelease(stream);
+ [connection errorEncountered:[[(NSError*)error autorelease] description]];
+ break;
+ }
+
+ case kCFStreamEventEndEncountered:
+ CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFWriteStreamClose(stream);
+ CFRelease(stream);
+ [connection socketDisconnected];
+ break;
+ }
+}
+
+void SocketAcceptCallback(CFSocketRef socket,
+ CFSocketCallBackType callbackType,
+ CFDataRef address,
+ const void* data,
+ void* connectionRaw)
+{
+ assert(callbackType == kCFSocketAcceptCallBack);
+ NSLog(@"SocketAcceptCallback()");
+
+ DebuggerConnection* connection = (DebuggerConnection*)connectionRaw;
+
+ CFReadStreamRef readStream;
+ CFWriteStreamRef writeStream;
+
+ // Create the streams on the socket.
+ CFStreamCreatePairWithSocket(kCFAllocatorDefault,
+ *(CFSocketNativeHandle*)data, // Socket handle.
+ &readStream, // Read stream in-pointer.
+ &writeStream); // Write stream in-pointer.
+
+ // Create struct to register callbacks for the stream.
+ CFStreamClientContext context;
+ context.version = 0;
+ context.info = connection;
+ context.retain = NULL;
+ context.release = NULL;
+ context.copyDescription = NULL;
+
+ // Set the client of the read stream.
+ CFOptionFlags readFlags =
+ kCFStreamEventOpenCompleted |
+ kCFStreamEventHasBytesAvailable |
+ kCFStreamEventErrorOccurred |
+ kCFStreamEventEndEncountered;
+ if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
+ // Schedule in run loop to do asynchronous communication with the engine.
+ CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ else
+ return;
+
+ // Open the stream now that it's scheduled on the run loop.
+ if (!CFReadStreamOpen(readStream))
+ {
+ CFStreamError error = CFReadStreamGetError(readStream);
+ NSLog(@"error! %@", error);
+ return;
+ }
+
+ // Set the client of the write stream.
+ CFOptionFlags writeFlags =
+ kCFStreamEventOpenCompleted |
+ kCFStreamEventCanAcceptBytes |
+ kCFStreamEventErrorOccurred |
+ kCFStreamEventEndEncountered;
+ if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
+ // Schedule it in the run loop to receive error information.
+ CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ else
+ return;
+
+ // Open the write stream.
+ if (!CFWriteStreamOpen(writeStream))
+ {
+ CFStreamError error = CFWriteStreamGetError(writeStream);
+ NSLog(@"error! %@", error);
+ return;
+ }
+
+ connection.readStream = readStream;
+ connection.writeStream = writeStream;
+ [connection socketDidAccept];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+@implementation DebuggerConnection
+
+@synthesize socket = socket_;
+@synthesize readStream = readStream_;
+@synthesize lastReadTransaction = lastReadTransaction_;
+@synthesize currentPacket = currentPacket_;
+@synthesize writeStream = writeStream_;
+@synthesize lastWrittenTransaction = lastWrittenTransaction_;
+@synthesize queuedWrites = queuedWrites_;
+
+- (void)dealloc
+{
+ self.currentPacket = nil;
+ [super dealloc];
+}
+
+/**
+ * Called by SocketWrapper after the connection is successful. This immediately calls
+ * -[SocketWrapper receive] to clear the way for communication, though the information
+ * could be useful server information that we don't use right now.
+ */
+- (void)socketDidAccept
+{
+ connected = YES;
+ transactionID = 1;
+ self.queuedWrites = [NSMutableArray array];
+ writeQueueLock_ = [NSRecursiveLock new];
+ callTable_ = [NSMutableDictionary new];
+}
+
+/**
+ * Receives errors from the SocketWrapper and updates the display
+ */
+- (void)errorEncountered:(NSString*)error
+{
+ [delegate_ errorEncountered:error];
+}
+
+/**
+ * Creates, connects to, and schedules a CFSocket.
+ */
+- (void)connect
+{
+ // Pass ourselves to the callback so we don't have to use ugly globals.
+ CFSocketContext context;
+ context.version = 0;
+ context.info = self;
+ context.retain = NULL;
+ context.release = NULL;
+ context.copyDescription = NULL;
+
+ // Create the address structure.
+ struct sockaddr_in address;
+ memset(&address, 0, sizeof(address));
+ address.sin_len = sizeof(address);
+ address.sin_family = AF_INET;
+ address.sin_port = htons(port);
+ address.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ // Create the socket signature.
+ CFSocketSignature signature;
+ signature.protocolFamily = PF_INET;
+ signature.socketType = SOCK_STREAM;
+ signature.protocol = IPPROTO_TCP;
+ signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
+
+ socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
+ &signature, // Socket signature.
+ kCFSocketAcceptCallBack, // Callback types.
+ SocketAcceptCallback, // Callout function pointer.
+ &context); // Context to pass to callout.
+ if (!socket_)
+ {
+ [self errorEncountered:@"Could not open socket."];
+ return;
+ }
+
+ // Allow old, yet-to-be recycled sockets to be reused.
+ BOOL yes = YES;
+ setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
+
+ // Schedule the socket on the run loop.
+ CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
+ CFRelease(source);
+}
+
+/**
+ * Closes a socket and releases the ref.
+ */
+- (void)close
+{
+ // The socket goes down, so do the streams, which clean themselves up.
+ CFSocketInvalidate(socket_);
+ CFRelease(socket_);
+ self.queuedWrites = nil;
+ [writeQueueLock_ release];
+ [callTable_ release];
+}
+
+/**
+ * Notification that the socket disconnected.
+ */
+- (void)socketDisconnected
+{
+ [self close];
+ [delegate_ debuggerDisconnected];
+}
+
+/**
+ * Callback from the CFReadStream that there is data waiting to be read.
+ */
+- (void)readStreamHasData
+{
+ const NSUInteger kBufferSize = 1024;
+ UInt8 buffer[kBufferSize];
+ CFIndex bufferOffset = 0; // Starting point in |buffer| to work with.
+ CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
+ const char* charBuffer = (const char*)buffer;
+
+ // The read loop works by going through the buffer until all the bytes have
+ // been processed.
+ while (bufferOffset < bytesRead)
+ {
+ // Find the NULL separator, or the end of the string.
+ NSUInteger partLength = 0;
+ for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
+
+ // If there is not a current packet, set some state.
+ if (!self.currentPacket)
+ {
+ // Read the message header: the size. This will be |partLength| bytes.
+ packetSize_ = atoi(charBuffer + bufferOffset);
+ currentPacketIndex_ = 0;
+ self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
+ bufferOffset += partLength + 1; // Pass over the NULL byte.
+ continue; // Spin the loop to begin reading actual data.
+ }
+
+ // Substring the byte stream and append it to the packet string.
+ CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
+ buffer + bufferOffset, // Byte pointer, offset by start index.
+ partLength, // Length.
+ kCFStringEncodingUTF8,
+ true);
+ [self.currentPacket appendString:(NSString*)bufferString];
+ CFRelease(bufferString);
+
+ // Advance counters.
+ currentPacketIndex_ += partLength;
+ bufferOffset += partLength + 1;
+
+ // If this read finished the packet, handle it and reset.
+ NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
+ if (currentPacketIndex_ >= packetSize_)
+ {
+ [self handlePacket:[[currentPacket_ retain] autorelease]];
+ self.currentPacket = nil;
+ packetSize_ = 0;
+ currentPacketIndex_ = 0;
+ }
+ }
+}
+
+/**
+ * Performs the packet handling of a raw string XML packet. From this point on,
+ * the packets are associated with a transaction and are then dispatched.
+ */
+- (void)handlePacket:(NSString*)packet
+{
+ // Test if we can convert it into an NSXMLDocument.
+ NSError* error = nil;
+ NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
+
+ // Try to recover if we encountered an error.
+ if (!xmlTest)
+ {
+ // We do not want to starve the write queue, so manually parse out the
+ // transaction ID.
+ NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
+ if (location.location != NSNotFound)
+ {
+ NSUInteger start = location.location + location.length;
+ NSUInteger end = start;
+
+ NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
+
+ // Loop over the characters after the attribute name to extract the ID.
+ while (end < [currentPacket_ length])
+ {
+ unichar c = [currentPacket_ characterAtIndex:end];
+ if ([numericSet characterIsMember:c])
+ {
+ // If this character is numeric, extend the range to substring.
+ ++end;
+ }
+ else
+ {
+ if (start == end)
+ {
+ // If this character is nonnumeric and we have nothing in the
+ // range, skip this character.
+ ++start;
+ ++end;
+ }
+ else
+ {
+ // We've moved past the numeric ID so we should stop searching.
+ break;
+ }
+ }
+ }
+
+ // If we were able to extract the transaction ID, update the last read.
+ NSRange substringRange = NSMakeRange(start, end - start);
+ NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
+ if ([transactionStr length])
+ lastReadTransaction_ = [transactionStr intValue];
+ }
+
+ // Otherwise, assume +1 and hope it works.
+ ++lastReadTransaction_;
+ }
+ else
+ {
+ // See if the transaction can be parsed out.
+ NSInteger transaction = [self transactionIDFromResponse:xmlTest];
+ if (transaction < lastReadTransaction_)
+ {
+ NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
+ NSLog(@"out of date transaction %@", packet);
+ return;
+ }
+
+ if (transaction != lastWrittenTransaction_)
+ NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
+
+ lastReadTransaction_ = transaction;
+ }
+
+ // Log this receive event.
+ LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
+ LogEntry* log = [logger recordReceive:currentPacket_];
+ log.error = error;
+ log.lastWrittenTransactionID = lastWrittenTransaction_;
+ log.lastReadTransactionID = lastReadTransaction_;
+
+ // Finally, dispatch the handler for this response.
+ [self handleResponse:[xmlTest autorelease]];
+}
+
+/**
+ * Writes a command into the write stream. If the stream is ready for writing,
+ * we do so immediately. If not, the command is queued and will be written
+ * when the stream is ready.
+ */
+- (void)send:(NSString*)command
+{
+ if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
+ [self performSend:command];
+ else
+ [queuedWrites_ addObject:command];
+ [self sendQueuedWrites];
+}
+
+/**
+ * This performs a blocking send. This should ONLY be called when we know we
+ * have write access to the stream. We will busy wait in case we don't do a full
+ * send.
+ */
+- (void)performSend:(NSString*)command
+{
+ // If this is an out-of-date transaction, do not bother sending it.
+ NSInteger transaction = [self transactionIDFromCommand:command];
+ if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
+ return;
+
+ BOOL done = NO;
+
+ char* string = (char*)[command UTF8String];
+ int stringLength = strlen(string);
+
+ // Busy wait while writing. BAADD. Should background this operation.
+ while (!done)
+ {
+ if (CFWriteStreamCanAcceptBytes(writeStream_))
+ {
+ // Include the NULL byte in the string when we write.
+ int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
+ if (bytesWritten < 0)
+ {
+ NSLog(@"write error");
+ }
+ // Incomplete write.
+ else if (bytesWritten < strlen(string))
+ {
+ // Adjust the buffer and wait for another chance to write.
+ stringLength -= bytesWritten;
+ memmove(string, string + bytesWritten, stringLength);
+ }
+ else
+ {
+ done = YES;
+
+ // We need to scan the string to find the transactionID.
+ if (transaction == NSNotFound)
+ {
+ NSLog(@"sent %@ without a transaction ID", command);
+ continue;
+ }
+ lastWrittenTransaction_ = transaction;
+ }
+ }
+ }
+
+ // Log this trancation.
+ LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
+ LogEntry* log = [logger recordSend:command];
+ log.lastWrittenTransactionID = lastWrittenTransaction_;
+ log.lastReadTransactionID = lastReadTransaction_;
+}
+
+- (void)handleResponse:(NSXMLDocument*)response
+{
+ // Check and see if there's an error.
+ NSArray* error = [[response rootElement] elementsForName:@"error"];
+ if ([error count] > 0)
+ {
+ NSLog(@"Xdebug error: %@", error);
+ [delegate_ errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
+ }
+
+ if ([[[response rootElement] name] isEqualToString:@"init"])
+ {
+ [self initReceived:response];
+ return;
+ }
+
+ NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
+ if (callbackStr)
+ {
+ SEL callback = NSSelectorFromString(callbackStr);
+ [self performSelector:callback withObject:response];
+ }
+
+ [self sendQueuedWrites];
+}
+
+#pragma mark Private
+
+/**
+ * This will send a command to the debugger engine. It will append the
+ * transaction ID automatically. It accepts a NSString command along with a
+ * a variable number of arguments to substitute into the command, a la
+ * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
+ */
+- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
+{
+ // Collect varargs and format command.
+ va_list args;
+ va_start(args, format);
+ NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
+ va_end(args);
+
+ NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
+ if (callback)
+ [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
+
+ [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
+
+ return callbackKey;
+}
+
+/**
+ * Checks if there are unsent commands in the |queuedWrites_| queue and sends
+ * them if it's OK to do so. This will not block.
+ */
+- (void)sendQueuedWrites
+{
+ if (!connected)
+ return;
+
+ [writeQueueLock_ lock];
+ if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
+ {
+ NSString* command = [queuedWrites_ objectAtIndex:0];
+
+ // We don't want to block because this is called from the main thread.
+ // |-performSend:| busy waits when the stream is not ready. Bail out
+ // before we do that becuase busy waiting is BAD.
+ if (CFWriteStreamCanAcceptBytes(writeStream_))
+ {
+ [self performSend:command];
+ [queuedWrites_ removeObjectAtIndex:0];
+ }
+ }
+ [writeQueueLock_ unlock];
+}
+
+
+/**
+ * Returns the transaction_id from an NSXMLDocument.
+ */
+- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
+{
+ return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
+}
+
+/**
+ * Scans a command string for the transaction ID component. If it is not found,
+ * returns NSNotFound.
+ */
+- (NSInteger)transactionIDFromCommand:(NSString*)command
+{
+ NSRange occurrence = [command rangeOfString:@"-i "];
+ if (occurrence.location == NSNotFound)
+ return NSNotFound;
+ NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
+ return [transaction intValue];
+}
+
+@end
#import "Breakpoint.h"
#import "StackFrame.h"
-@class LoggingController;
-
@protocol DebuggerProcessorDelegate;
// The DebuggerConnection is the communication layer between the application
// The connection's delegate.
id <DebuggerProcessorDelegate> delegate;
-
- // The raw CFSocket on which the two streams are based. Strong.
- CFSocketRef socket_;
-
- // The read stream that is scheduled on the main run loop. Weak.
- CFReadStreamRef readStream_;
-
- // The write stream. Weak.
- CFWriteStreamRef writeStream_;
-
- // An ever-increasing integer that gives each transaction a unique ID for the
- // debugging engine.
- NSUInteger transactionID;
-
- // The most recently received transaction ID.
- NSUInteger lastReadTransaction_;
-
- // The last transactionID written to the stream.
- NSUInteger lastWrittenTransaction_;
-
- // Callback table. This maps transaction IDs to selectors. When the engine
- // returns a response to the debugger, we will dispatch the response XML to
- // the selector, based on transaction_id.
- NSMutableDictionary* callTable_;
-
- // To prevent blocked writing, we enqueue all writes and then wait for the
- // write stream to tell us it's ready. We store the pending commands in this
- // array. We use this as a stack (FIFO), with index 0 being first.
- NSMutableArray* queuedWrites_;
-
- // We send queued writes in multiple places, sometimes off a run loop event.
- // Because of this, we need to ensure that only one client is dequeing and
- // sending at a time.
- NSRecursiveLock* writeQueueLock_;
-
- // Information about the current read loop. We append to |currentPacket_|
- // until |currentPacketSize_| has reached |packetSize_|.
- NSMutableString* currentPacket_;
- int packetSize_;
- int currentPacketIndex_;
// A dictionary that maps routingIDs to StackFrame objects.
NSMutableDictionary* stackFrames_;
* write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
-#import <sys/socket.h>
-#import <netinet/in.h>
-
#import "DebuggerProcessor.h"
#import "AppDelegate.h"
-#import "LoggingController.h"
// GDBpConnection (Private) ////////////////////////////////////////////////////
@interface DebuggerProcessor ()
@property (readwrite, copy) NSString* status;
-@property (assign) CFSocketRef socket;
-@property (assign) CFReadStreamRef readStream;
-@property NSUInteger lastReadTransaction;
-@property (retain) NSMutableString* currentPacket;
-@property (assign) CFWriteStreamRef writeStream;
-@property NSUInteger lastWrittenTransaction;
-@property (retain) NSMutableArray* queuedWrites;
-
-- (void)connect;
-- (void)close;
-- (void)socketDidAccept;
-- (void)socketDisconnected;
-- (void)readStreamHasData;
-- (void)send:(NSString*)command;
-- (void)performSend:(NSString*)command;
-- (void)errorEncountered:(NSString*)error;
-
-- (void)handleResponse:(NSXMLDocument*)response;
-- (void)handlePacket:(NSString*)packet;
- (void)initReceived:(NSXMLDocument*)response;
- (void)updateStatus:(NSXMLDocument*)response;
- (void)variablesReceived:(NSXMLDocument*)response;
- (void)propertiesReceived:(NSXMLDocument*)response;
-- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...;
-
-- (void)sendQueuedWrites;
-
-- (NSString*)escapedURIPath:(NSString*)path;
-- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response;
-- (NSInteger)transactionIDFromCommand:(NSString*)command;
@end
-// CFNetwork Callbacks /////////////////////////////////////////////////////////
-
-void ReadStreamCallback(CFReadStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
-{
- DebuggerProcessor* connection = (DebuggerProcessor*)connectionRaw;
- switch (eventType)
- {
- case kCFStreamEventHasBytesAvailable:
- [connection readStreamHasData];
- break;
-
- case kCFStreamEventErrorOccurred:
- {
- CFErrorRef error = CFReadStreamCopyError(stream);
- CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
- CFReadStreamClose(stream);
- CFRelease(stream);
- [connection errorEncountered:[[(NSError*)error autorelease] description]];
- break;
- }
-
- case kCFStreamEventEndEncountered:
- CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
- CFReadStreamClose(stream);
- CFRelease(stream);
- [connection socketDisconnected];
- break;
- };
-}
-
-void WriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType eventType, void* connectionRaw)
-{
- DebuggerProcessor* connection = (DebuggerProcessor*)connectionRaw;
- switch (eventType)
- {
- case kCFStreamEventCanAcceptBytes:
- [connection sendQueuedWrites];
- break;
-
- case kCFStreamEventErrorOccurred:
- {
- CFErrorRef error = CFWriteStreamCopyError(stream);
- CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
- CFWriteStreamClose(stream);
- CFRelease(stream);
- [connection errorEncountered:[[(NSError*)error autorelease] description]];
- break;
- }
-
- case kCFStreamEventEndEncountered:
- CFWriteStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
- CFWriteStreamClose(stream);
- CFRelease(stream);
- [connection socketDisconnected];
- break;
- }
-}
-
-void SocketAcceptCallback(CFSocketRef socket,
- CFSocketCallBackType callbackType,
- CFDataRef address,
- const void* data,
- void* connectionRaw)
-{
- assert(callbackType == kCFSocketAcceptCallBack);
- NSLog(@"SocketAcceptCallback()");
-
- DebuggerProcessor* connection = (DebuggerProcessor*)connectionRaw;
-
- CFReadStreamRef readStream;
- CFWriteStreamRef writeStream;
-
- // Create the streams on the socket.
- CFStreamCreatePairWithSocket(kCFAllocatorDefault,
- *(CFSocketNativeHandle*)data, // Socket handle.
- &readStream, // Read stream in-pointer.
- &writeStream); // Write stream in-pointer.
-
- // Create struct to register callbacks for the stream.
- CFStreamClientContext context;
- context.version = 0;
- context.info = connection;
- context.retain = NULL;
- context.release = NULL;
- context.copyDescription = NULL;
-
- // Set the client of the read stream.
- CFOptionFlags readFlags =
- kCFStreamEventOpenCompleted |
- kCFStreamEventHasBytesAvailable |
- kCFStreamEventErrorOccurred |
- kCFStreamEventEndEncountered;
- if (CFReadStreamSetClient(readStream, readFlags, ReadStreamCallback, &context))
- // Schedule in run loop to do asynchronous communication with the engine.
- CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
- else
- return;
-
- // Open the stream now that it's scheduled on the run loop.
- if (!CFReadStreamOpen(readStream))
- {
- CFStreamError error = CFReadStreamGetError(readStream);
- NSLog(@"error! %@", error);
- return;
- }
-
- // Set the client of the write stream.
- CFOptionFlags writeFlags =
- kCFStreamEventOpenCompleted |
- kCFStreamEventCanAcceptBytes |
- kCFStreamEventErrorOccurred |
- kCFStreamEventEndEncountered;
- if (CFWriteStreamSetClient(writeStream, writeFlags, WriteStreamCallback, &context))
- // Schedule it in the run loop to receive error information.
- CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
- else
- return;
-
- // Open the write stream.
- if (!CFWriteStreamOpen(writeStream))
- {
- CFStreamError error = CFWriteStreamGetError(writeStream);
- NSLog(@"error! %@", error);
- return;
- }
-
- connection.readStream = readStream;
- connection.writeStream = writeStream;
- [connection socketDidAccept];
-}
-
// GDBpConnection //////////////////////////////////////////////////////////////
@implementation DebuggerProcessor
-@synthesize socket = socket_;
-@synthesize readStream = readStream_;
-@synthesize lastReadTransaction = lastReadTransaction_;
-@synthesize currentPacket = currentPacket_;
-@synthesize writeStream = writeStream_;
-@synthesize lastWrittenTransaction = lastWrittenTransaction_;
-@synthesize queuedWrites = queuedWrites_;
+
@synthesize status;
@synthesize delegate;
{
port = aPort;
connected = NO;
-
+
+ stackFrames_ = [[NSMutableDictionary alloc] init];
+ callbackContext_ = [NSMutableDictionary new];
+
[[BreakpointManager sharedManager] setConnection:self];
[self connect];
}
- (void)dealloc
{
[self close];
- self.currentPacket = nil;
-
+ [stackFrames_ release];
+ [callbackContext_ release];
[super dealloc];
}
[self sendCommandWithCallback:nil format:@"breakpoint_remove -d %i", [bp debuggerId]];
}
-
-// Socket and Stream Callbacks /////////////////////////////////////////////////
-#pragma mark Callbacks
-
-/**
- * Called by SocketWrapper after the connection is successful. This immediately calls
- * -[SocketWrapper receive] to clear the way for communication, though the information
- * could be useful server information that we don't use right now.
- */
-- (void)socketDidAccept
-{
- connected = YES;
- transactionID = 1;
- stackFrames_ = [[NSMutableDictionary alloc] init];
- self.queuedWrites = [NSMutableArray array];
- writeQueueLock_ = [NSRecursiveLock new];
- callTable_ = [NSMutableDictionary new];
- callbackContext_ = [NSMutableDictionary new];
-}
-
-/**
- * Receives errors from the SocketWrapper and updates the display
- */
-- (void)errorEncountered:(NSString*)error
-{
- [delegate errorEncountered:error];
-}
-
-/**
- * Creates, connects to, and schedules a CFSocket.
- */
-- (void)connect
-{
- // Pass ourselves to the callback so we don't have to use ugly globals.
- CFSocketContext context;
- context.version = 0;
- context.info = self;
- context.retain = NULL;
- context.release = NULL;
- context.copyDescription = NULL;
-
- // Create the address structure.
- struct sockaddr_in address;
- memset(&address, 0, sizeof(address));
- address.sin_len = sizeof(address);
- address.sin_family = AF_INET;
- address.sin_port = htons(port);
- address.sin_addr.s_addr = htonl(INADDR_ANY);
-
- // Create the socket signature.
- CFSocketSignature signature;
- signature.protocolFamily = PF_INET;
- signature.socketType = SOCK_STREAM;
- signature.protocol = IPPROTO_TCP;
- signature.address = (CFDataRef)[NSData dataWithBytes:&address length:sizeof(address)];
-
- socket_ = CFSocketCreateWithSocketSignature(kCFAllocatorDefault,
- &signature, // Socket signature.
- kCFSocketAcceptCallBack, // Callback types.
- SocketAcceptCallback, // Callout function pointer.
- &context); // Context to pass to callout.
- if (!socket_)
- {
- [self errorEncountered:@"Could not open socket."];
- return;
- }
-
- // Allow old, yet-to-be recycled sockets to be reused.
- BOOL yes = YES;
- setsockopt(CFSocketGetNative(socket_), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(BOOL));
-
- // Schedule the socket on the run loop.
- CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
- CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
- CFRelease(source);
-
- self.status = @"Connecting";
-}
-
-/**
- * Closes a socket and releases the ref.
- */
-- (void)close
-{
- // The socket goes down, so do the streams, which clean themselves up.
- CFSocketInvalidate(socket_);
- CFRelease(socket_);
- [stackFrames_ release];
- self.queuedWrites = nil;
- [writeQueueLock_ release];
- [callTable_ release];
- [callbackContext_ release];
-}
-
-/**
- * Notification that the socket disconnected.
- */
-- (void)socketDisconnected
-{
- [self close];
- [delegate debuggerDisconnected];
-}
-
-/**
- * Callback from the CFReadStream that there is data waiting to be read.
- */
-- (void)readStreamHasData
-{
- const NSUInteger kBufferSize = 1024;
- UInt8 buffer[kBufferSize];
- CFIndex bufferOffset = 0; // Starting point in |buffer| to work with.
- CFIndex bytesRead = CFReadStreamRead(readStream_, buffer, kBufferSize);
- const char* charBuffer = (const char*)buffer;
-
- // The read loop works by going through the buffer until all the bytes have
- // been processed.
- while (bufferOffset < bytesRead)
- {
- // Find the NULL separator, or the end of the string.
- NSUInteger partLength = 0;
- for (NSUInteger i = bufferOffset; i < bytesRead && charBuffer[i] != '\0'; ++i, ++partLength) ;
-
- // If there is not a current packet, set some state.
- if (!self.currentPacket)
- {
- // Read the message header: the size. This will be |partLength| bytes.
- packetSize_ = atoi(charBuffer + bufferOffset);
- currentPacketIndex_ = 0;
- self.currentPacket = [NSMutableString stringWithCapacity:packetSize_];
- bufferOffset += partLength + 1; // Pass over the NULL byte.
- continue; // Spin the loop to begin reading actual data.
- }
-
- // Substring the byte stream and append it to the packet string.
- CFStringRef bufferString = CFStringCreateWithBytes(kCFAllocatorDefault,
- buffer + bufferOffset, // Byte pointer, offset by start index.
- partLength, // Length.
- kCFStringEncodingUTF8,
- true);
- [self.currentPacket appendString:(NSString*)bufferString];
- CFRelease(bufferString);
-
- // Advance counters.
- currentPacketIndex_ += partLength;
- bufferOffset += partLength + 1;
-
- // If this read finished the packet, handle it and reset.
- NSLog(@"cpi %d ps %d br %d ds %d", currentPacketIndex_, packetSize_, bytesRead, partLength);
- if (currentPacketIndex_ >= packetSize_)
- {
- [self handlePacket:[[currentPacket_ retain] autorelease]];
- self.currentPacket = nil;
- packetSize_ = 0;
- currentPacketIndex_ = 0;
- }
- }
-}
-
-/**
- * Performs the packet handling of a raw string XML packet. From this point on,
- * the packets are associated with a transaction and are then dispatched.
- */
-- (void)handlePacket:(NSString*)packet
-{
- // Test if we can convert it into an NSXMLDocument.
- NSError* error = nil;
- NSXMLDocument* xmlTest = [[NSXMLDocument alloc] initWithXMLString:currentPacket_ options:NSXMLDocumentTidyXML error:&error];
-
- // Try to recover if we encountered an error.
- if (!xmlTest)
- {
- // We do not want to starve the write queue, so manually parse out the
- // transaction ID.
- NSRange location = [currentPacket_ rangeOfString:@"transaction_id"];
- if (location.location != NSNotFound)
- {
- NSUInteger start = location.location + location.length;
- NSUInteger end = start;
-
- NSCharacterSet* numericSet = [NSCharacterSet decimalDigitCharacterSet];
-
- // Loop over the characters after the attribute name to extract the ID.
- while (end < [currentPacket_ length])
- {
- unichar c = [currentPacket_ characterAtIndex:end];
- if ([numericSet characterIsMember:c])
- {
- // If this character is numeric, extend the range to substring.
- ++end;
- }
- else
- {
- if (start == end)
- {
- // If this character is nonnumeric and we have nothing in the
- // range, skip this character.
- ++start;
- ++end;
- }
- else
- {
- // We've moved past the numeric ID so we should stop searching.
- break;
- }
- }
- }
-
- // If we were able to extract the transaction ID, update the last read.
- NSRange substringRange = NSMakeRange(start, end - start);
- NSString* transactionStr = [currentPacket_ substringWithRange:substringRange];
- if ([transactionStr length])
- lastReadTransaction_ = [transactionStr intValue];
- }
-
- // Otherwise, assume +1 and hope it works.
- ++lastReadTransaction_;
- }
- else
- {
- // See if the transaction can be parsed out.
- NSInteger transaction = [self transactionIDFromResponse:xmlTest];
- if (transaction < lastReadTransaction_)
- {
- NSLog(@"tx = %d vs %d", transaction, lastReadTransaction_);
- NSLog(@"out of date transaction %@", packet);
- return;
- }
-
- if (transaction != lastWrittenTransaction_)
- NSLog(@"txn %d <> %d last written, %d last read", transaction, lastWrittenTransaction_, lastReadTransaction_);
-
- lastReadTransaction_ = transaction;
- }
-
- // Log this receive event.
- LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
- LogEntry* log = [logger recordReceive:currentPacket_];
- log.error = error;
- log.lastWrittenTransactionID = lastWrittenTransaction_;
- log.lastReadTransactionID = lastReadTransaction_;
-
- // Finally, dispatch the handler for this response.
- [self handleResponse:[xmlTest autorelease]];
-}
-
-/**
- * Writes a command into the write stream. If the stream is ready for writing,
- * we do so immediately. If not, the command is queued and will be written
- * when the stream is ready.
- */
-- (void)send:(NSString*)command
-{
- if (lastReadTransaction_ >= lastWrittenTransaction_ && CFWriteStreamCanAcceptBytes(writeStream_))
- [self performSend:command];
- else
- [queuedWrites_ addObject:command];
- [self sendQueuedWrites];
-}
-
-/**
- * This performs a blocking send. This should ONLY be called when we know we
- * have write access to the stream. We will busy wait in case we don't do a full
- * send.
- */
-- (void)performSend:(NSString*)command
-{
- // If this is an out-of-date transaction, do not bother sending it.
- NSInteger transaction = [self transactionIDFromCommand:command];
- if (transaction != NSNotFound && transaction < lastWrittenTransaction_)
- return;
-
- BOOL done = NO;
-
- char* string = (char*)[command UTF8String];
- int stringLength = strlen(string);
-
- // Busy wait while writing. BAADD. Should background this operation.
- while (!done)
- {
- if (CFWriteStreamCanAcceptBytes(writeStream_))
- {
- // Include the NULL byte in the string when we write.
- int bytesWritten = CFWriteStreamWrite(writeStream_, (UInt8*)string, stringLength + 1);
- if (bytesWritten < 0)
- {
- NSLog(@"write error");
- }
- // Incomplete write.
- else if (bytesWritten < strlen(string))
- {
- // Adjust the buffer and wait for another chance to write.
- stringLength -= bytesWritten;
- memmove(string, string + bytesWritten, stringLength);
- }
- else
- {
- done = YES;
-
- // We need to scan the string to find the transactionID.
- if (transaction == NSNotFound)
- {
- NSLog(@"sent %@ without a transaction ID", command);
- continue;
- }
- lastWrittenTransaction_ = transaction;
- }
- }
- }
-
- // Log this trancation.
- LoggingController* logger = [(AppDelegate*)[NSApp delegate] loggingController];
- LogEntry* log = [logger recordSend:command];
- log.lastWrittenTransactionID = lastWrittenTransaction_;
- log.lastReadTransactionID = lastReadTransaction_;
-}
-
-- (void)handleResponse:(NSXMLDocument*)response
-{
- // Check and see if there's an error.
- NSArray* error = [[response rootElement] elementsForName:@"error"];
- if ([error count] > 0)
- {
- NSLog(@"Xdebug error: %@", error);
- [delegate errorEncountered:[[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue]];
- }
-
- if ([[[response rootElement] name] isEqualToString:@"init"])
- {
- [self initReceived:response];
- return;
- }
-
- NSString* callbackStr = [callTable_ objectForKey:[NSNumber numberWithInt:lastReadTransaction_]];
- if (callbackStr)
- {
- SEL callback = NSSelectorFromString(callbackStr);
- [self performSelector:callback withObject:response];
- }
-
- [self sendQueuedWrites];
-}
-
// Specific Response Handlers //////////////////////////////////////////////////
#pragma mark Response Handlers
[bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
}
-#pragma mark Private
-
-/**
- * This will send a command to the debugger engine. It will append the
- * transaction ID automatically. It accepts a NSString command along with a
- * a variable number of arguments to substitute into the command, a la
- * +[NSString stringWithFormat:]. Returns the transaction ID as a NSNumber.
- */
-- (NSNumber*)sendCommandWithCallback:(SEL)callback format:(NSString*)format, ...
-{
- // Collect varargs and format command.
- va_list args;
- va_start(args, format);
- NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
- va_end(args);
-
- NSNumber* callbackKey = [NSNumber numberWithInt:transactionID++];
- if (callback)
- [callTable_ setObject:NSStringFromSelector(callback) forKey:callbackKey];
-
- [self send:[NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey]];
-
- return callbackKey;
-}
-
-/**
- * Checks if there are unsent commands in the |queuedWrites_| queue and sends
- * them if it's OK to do so. This will not block.
- */
-- (void)sendQueuedWrites
-{
- if (!connected)
- return;
-
- [writeQueueLock_ lock];
- if (lastReadTransaction_ >= lastWrittenTransaction_ && [queuedWrites_ count] > 0)
- {
- NSString* command = [queuedWrites_ objectAtIndex:0];
-
- // We don't want to block because this is called from the main thread.
- // |-performSend:| busy waits when the stream is not ready. Bail out
- // before we do that becuase busy waiting is BAD.
- if (CFWriteStreamCanAcceptBytes(writeStream_))
- {
- [self performSend:command];
- [queuedWrites_ removeObjectAtIndex:0];
- }
- }
- [writeQueueLock_ unlock];
-}
-
/**
* Given a file path, this returns a file:// URI and escapes any spaces for the
* debugger engine.
return urlString;
}
-/**
- * Returns the transaction_id from an NSXMLDocument.
- */
-- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response
-{
- return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
-}
-
-/**
- * Scans a command string for the transaction ID component. If it is not found,
- * returns NSNotFound.
- */
-- (NSInteger)transactionIDFromCommand:(NSString*)command
-{
- NSRange occurrence = [command rangeOfString:@"-i "];
- if (occurrence.location == NSNotFound)
- return NSNotFound;
- NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
- return [transaction intValue];
-}
-
@end