Write ProtocolClient, the layer that talks XML on top of a MessageQueue.
authorRobert Sesek <rsesek@bluestatic.org>
Tue, 18 Jun 2013 05:05:46 +0000 (01:05 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Tue, 18 Jun 2013 05:05:46 +0000 (01:05 -0400)
This is the other piece of the split NetworkConnection class.

MacGDBp.xcodeproj/project.pbxproj
Source/ProtocolClient.h [new file with mode: 0644]
Source/ProtocolClient.m [new file with mode: 0644]

index 3c91f765b1709cafb8fcaea0f287f37f2a51dcfb..b1fc55260506ae021acf287ff2c5e439aaf21599 100644 (file)
@@ -40,6 +40,7 @@
                1EC6965812BBC6A700A8D984 /* modp_b64.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6965212BBC6A700A8D984 /* modp_b64.cc */; };
                1EDA9CF812DD13B300596211 /* BSLineNumberRulerView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1EDA9CF712DD13B300596211 /* BSLineNumberRulerView.mm */; };
                1EEBE842176FEA80003622C3 /* MessageQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EEBE841176FEA80003622C3 /* MessageQueue.m */; };
+               1EEBE845176FFE04003622C3 /* ProtocolClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EEBE844176FFE04003622C3 /* ProtocolClient.m */; };
                1EEBFBE50D34C793008F835B /* Debugger.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1EEBFBE30D34C793008F835B /* Debugger.xib */; };
                1EEBFC2B0D358EBD008F835B /* StepIn.png in Resources */ = {isa = PBXBuildFile; fileRef = 1EEBFC2A0D358EBD008F835B /* StepIn.png */; };
                1EEBFC370D358F1B008F835B /* StepOut.png in Resources */ = {isa = PBXBuildFile; fileRef = 1EEBFC360D358F1B008F835B /* StepOut.png */; };
                1EDA9CF712DD13B300596211 /* BSLineNumberRulerView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BSLineNumberRulerView.mm; path = Source/BSLineNumberRulerView.mm; sourceTree = "<group>"; };
                1EEBE840176FEA80003622C3 /* MessageQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MessageQueue.h; path = Source/MessageQueue.h; sourceTree = "<group>"; };
                1EEBE841176FEA80003622C3 /* MessageQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MessageQueue.m; path = Source/MessageQueue.m; sourceTree = "<group>"; };
+               1EEBE843176FFE04003622C3 /* ProtocolClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProtocolClient.h; path = Source/ProtocolClient.h; sourceTree = "<group>"; };
+               1EEBE844176FFE04003622C3 /* ProtocolClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProtocolClient.m; path = Source/ProtocolClient.m; sourceTree = "<group>"; };
                1EEBFBE40D34C793008F835B /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/Debugger.xib; sourceTree = "<group>"; };
                1EEBFC2A0D358EBD008F835B /* StepIn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = StepIn.png; path = Icons/StepIn.png; sourceTree = "<group>"; };
                1EEBFC360D358F1B008F835B /* StepOut.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = StepOut.png; path = Icons/StepOut.png; sourceTree = "<group>"; };
                        children = (
                                1EEBE840176FEA80003622C3 /* MessageQueue.h */,
                                1EEBE841176FEA80003622C3 /* MessageQueue.m */,
+                               1EEBE843176FFE04003622C3 /* ProtocolClient.h */,
+                               1EEBE844176FFE04003622C3 /* ProtocolClient.m */,
                        );
                        name = Protocol;
                        sourceTree = "<group>";
                                1E108E40136CC8B9002E34E0 /* EvalController.m in Sources */,
                                1E109019136DD92D002E34E0 /* StripLineBreaksValueTransformer.m in Sources */,
                                1EEBE842176FEA80003622C3 /* MessageQueue.m in Sources */,
+                               1EEBE845176FFE04003622C3 /* ProtocolClient.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
diff --git a/Source/ProtocolClient.h b/Source/ProtocolClient.h
new file mode 100644 (file)
index 0000000..982edad
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * MacGDBp
+ * Copyright (c) 2013, 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 <Foundation/Foundation.h>
+
+#import "MessageQueue.h"
+
+@protocol ProtocolClientDelegate;
+
+// ProtocolClient sends string commands to a DBGP <http://www.xdebug.org/docs-dbgp.php>
+// debugger engine and receives XML packets in response. This class ensures
+// proper sequencing of the messages.
+@interface ProtocolClient : NSObject<MessageQueueDelegate> {
+ @private
+  MessageQueue* _messageQueue;
+
+  NSRecursiveLock* _lock;
+  NSInteger _nextID;
+
+  NSInteger _lastReadID;
+  NSInteger _lastWrittenID;
+
+  NSObject<ProtocolClientDelegate>* _delegate;
+  NSThread* _delegateThread;
+}
+
+- (id)initWithDelegate:(NSObject<ProtocolClientDelegate>*)delegate;
+
+- (BOOL)isConnected;
+
+- (void)connectOnPort:(NSUInteger)port;
+- (void)disconnect;
+
+// This sends the given command format to the debugger. This method is thread
+// safe and schedules the request on the |runLoop_|.
+- (NSNumber*)sendCommandWithFormat:(NSString*)format, ...;
+
+// Sends a command to the debugger. The command must have a substring |{txn}|
+// within it, which will be replaced with the transaction ID. Use this if
+// |-sendCommandWithFormat:|'s insertion of the transaction ID is incorrect.
+- (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ...;
+
+- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response;
+- (NSInteger)transactionIDFromCommand:(NSString*)command;
+
+@end
+
+// Delegate ////////////////////////////////////////////////////////////////////
+
+// All methods of the protocol client are dispatched to the thread on which the
+// ProtocolClient was created.
+@protocol ProtocolClientDelegate
+- (void)debuggerEngineConnected:(ProtocolClient*)client;
+- (void)debuggerEngineDisconnected:(ProtocolClient*)client;
+
+- (void)debuggerEngine:(ProtocolClient*)client receivedMessage:(NSXMLDocument*)message;
+@end
diff --git a/Source/ProtocolClient.m b/Source/ProtocolClient.m
new file mode 100644 (file)
index 0000000..f8d386c
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * MacGDBp
+ * Copyright (c) 2013, 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 "ProtocolClient.h"
+
+@interface ProtocolClient (Private)
+- (void)postReceivedMessage:(NSXMLDocument*)message;
+@end
+
+@implementation ProtocolClient
+
+- (id)initWithDelegate:(NSObject<ProtocolClientDelegate>*)delegate {
+  if ((self = [super init])) {
+    _delegate = delegate;
+    _delegateThread = [NSThread currentThread];
+    _lock = [[NSRecursiveLock alloc] init];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [_lock release];
+  [super dealloc];
+}
+
+- (BOOL)isConnected {
+  return [_messageQueue isConnected];
+}
+
+- (void)connectOnPort:(NSUInteger)port {
+  assert(!_messageQueue);
+  _messageQueue = [[MessageQueue alloc] initWithPort:port delegate:self];
+  [_messageQueue connect];
+}
+
+- (void)disconnect {
+  [_messageQueue disconnect];
+}
+
+- (NSNumber*)sendCommandWithFormat:(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:_nextID++];
+  NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey];
+
+  [_messageQueue sendMessage:taggedCommand];
+  return callbackKey;
+}
+
+- (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ... {
+  // Collect varargs and format command.
+  va_list args;
+  va_start(args, format);
+  NSString* command = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
+  va_end(args);
+
+  NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++];
+  NSString* taggedCommand = [command stringByReplacingOccurrencesOfString:@"{txn}"
+                                                               withString:[callbackKey stringValue]];
+
+  [_messageQueue sendMessage:taggedCommand];
+  return callbackKey;
+}
+
+- (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response {
+  return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
+}
+
+- (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];
+}
+
+// MessageQueueDelegate ////////////////////////////////////////////////////////
+
+- (void)messageQueueError:(NSError*)error {
+  NSLog(@"error = %@", error);
+}
+
+- (void)clientDidConnect:(MessageQueue*)queue {
+  [_lock lock];
+  _nextID = 0;
+  _lastReadID = 0;
+  _lastWrittenID = 0;
+  [_lock unlock];
+
+  [_delegate performSelector:@selector(debuggerEngineConnected:)
+                    onThread:_delegateThread
+                  withObject:self
+               waitUntilDone:NO];
+}
+
+- (void)clientDidDisconnect:(MessageQueue*)queue {
+  [_delegate performSelector:@selector(debuggerEngineDisconnected:)
+                    onThread:_delegateThread
+                  withObject:self
+               waitUntilDone:NO];
+}
+
+// If the write stream is ready, the delegate controls whether or not the next
+// pending message should be sent via the result of this method.
+- (BOOL)shouldSendMessage {
+  [_lock lock];
+  BOOL r = _lastReadID >= _lastWrittenID;
+  [_lock unlock];
+  return r;
+}
+
+// Callback for when a message has been sent.
+- (void)didSendMessage:(NSString*)message {
+  NSInteger tag = [self transactionIDFromCommand:message];
+  [_lock lock];
+  _lastWrittenID = tag;
+  [_lock unlock];
+}
+
+// Callback with the message content when one has been receieved.
+- (void)didReceiveMessage:(NSString*)message {
+  // Test if we can convert it into an NSXMLDocument.
+  NSError* error = nil;
+  NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message
+                                                        options:NSXMLDocumentTidyXML
+                                                          error:&error];
+  if (error) {
+    [self messageQueueError:error];
+    return;
+  }
+
+  // Validate the transaction.
+  NSInteger transaction = [self transactionIDFromResponse:xml];
+  if (transaction < _lastReadID) {
+    NSLog(@"Transaction #%d is out of date (lastRead = %d). Dropping packet: %@",
+          transaction, _lastReadID, message);
+    return;
+  }
+  if (transaction != _lastWrittenID) {
+    NSLog(@"Transaction #%d received out of order. lastRead = %d, lastWritten = %d. Continuing.",
+          transaction, _lastReadID, _lastWrittenID);
+  }
+
+  _lastReadID = transaction;
+
+  [self performSelector:@selector(postReceivedMessage:)
+               onThread:_delegateThread
+             withObject:xml
+          waitUntilDone:NO];
+}
+
+// Private /////////////////////////////////////////////////////////////////////
+
+- (void)postReceivedMessage:(NSXMLDocument*)message {
+  [_delegate debuggerEngine:self receivedMessage:message];
+}
+
+@end