Replace -[ProtocolClient sendCustomCommandWithFormat:...] with a block-based
[macgdbp.git] / Source / ProtocolClient.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2013, Blue Static <http://www.bluestatic.org>
4 *
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.
8 *
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.
12 *
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
15 */
16
17 #import "ProtocolClient.h"
18
19 #import "AppDelegate.h"
20 #import "LoggingController.h"
21
22 @implementation ProtocolClient {
23 // The object responsible for the actual communication with the debug server.
24 MessageQueue* _messageQueue;
25
26 // The delegate of this class, which receives high-level messages about the
27 // state of the debugger.
28 id<ProtocolClientDelegate> _delegate; // weak
29
30 // A map between transaction ID and handler block for that message.
31 NSMutableDictionary<NSNumber*, ProtocolClientMessageHandler>* _dispatchTable;
32
33 // The next transaction ID to assign.
34 int _nextID;
35
36 // Records the last read and written transaction IDs. These are only used in
37 // creating LogEntry objects.
38 NSInteger _lastReadID;
39 NSInteger _lastWrittenID;
40 }
41
42 - (id)initWithDelegate:(id<ProtocolClientDelegate>)delegate {
43 if ((self = [super init])) {
44 _delegate = delegate;
45 _dispatchTable = [[NSMutableDictionary alloc] init];
46 }
47 return self;
48 }
49
50 - (void)dealloc {
51 [_dispatchTable release];
52 [super dealloc];
53 }
54
55 - (BOOL)isConnected {
56 return [_messageQueue isConnected];
57 }
58
59 - (void)connectOnPort:(NSUInteger)port {
60 assert(!_messageQueue);
61 _messageQueue = [[MessageQueue alloc] initWithPort:port delegate:self];
62 [_messageQueue connect];
63 }
64
65 - (void)disconnect {
66 [_messageQueue disconnect];
67 }
68
69 - (NSNumber*)sendCommandWithFormat:(NSString*)format, ... {
70 // Collect varargs and format command.
71 va_list args;
72 va_start(args, format);
73 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
74 va_end(args);
75
76 NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++];
77 NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey];
78
79 assert(_messageQueue);
80 [_messageQueue sendMessage:taggedCommand];
81 return callbackKey;
82 }
83
84 - (void)sendCommandWithFormat:(NSString*)format
85 handler:(ProtocolClientMessageHandler)handler, ... {
86 // Collect varargs and format command.
87 va_list args;
88 va_start(args, handler);
89 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
90 va_end(args);
91
92 int transaction = _nextID++;
93 NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %d", [command autorelease], transaction];
94
95 assert(_messageQueue);
96 [_dispatchTable setObject:[[handler copy] autorelease] forKey:@(transaction)];
97 [_messageQueue sendMessage:taggedCommand];
98 }
99
100 - (void)sendCustomCommandWithFormat:(NSString*)format
101 handler:(ProtocolClientMessageHandler)handler, ... {
102 // Collect varargs and format command.
103 va_list args;
104 va_start(args, handler);
105 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
106 va_end(args);
107
108 int transaction = _nextID++;
109 NSString* taggedCommand =
110 [command stringByReplacingOccurrencesOfString:@"{txn}"
111 withString:[NSString stringWithFormat:@"%d", transaction]];
112
113 assert(_messageQueue);
114 [_dispatchTable setObject:[[handler copy] autorelease] forKey:@(transaction)];
115 [_messageQueue sendMessage:taggedCommand];
116 }
117
118
119 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response {
120 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
121 }
122
123 - (NSInteger)transactionIDFromCommand:(NSString*)command {
124 NSRange occurrence = [command rangeOfString:@"-i "];
125 if (occurrence.location == NSNotFound)
126 return NSNotFound;
127 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
128 return [transaction intValue];
129 }
130
131 + (NSString*)escapedFilePathURI:(NSString*)path {
132 // Custon GDBp paths are fine.
133 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
134 return path;
135
136 // Create a temporary URL that will escape all the nasty characters.
137 NSURL* url = [NSURL fileURLWithPath:path];
138 NSString* urlString = [url absoluteString];
139
140 // Remove the host because this is a file:// URL;
141 NSString* host = [url host];
142 if (host)
143 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
144
145 // Escape % for use in printf-style NSString formatters.
146 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
147 return urlString;
148 }
149
150 // MessageQueueDelegate ////////////////////////////////////////////////////////
151
152 - (void)messageQueue:(MessageQueue*)queue error:(NSError*)error {
153 NSLog(@"error = %@", error);
154 }
155
156 - (void)messageQueueDidConnect:(MessageQueue*)queue {
157 _nextID = 0;
158 _lastReadID = 0;
159 _lastWrittenID = 0;
160
161 [_delegate debuggerEngineConnected:self];
162 }
163
164 - (void)messageQueueDidDisconnect:(MessageQueue*)queue {
165 [_messageQueue release];
166 _messageQueue = nil;
167 [_dispatchTable removeAllObjects];
168 [_delegate debuggerEngineDisconnected:self];
169 }
170
171 // Callback for when a message has been sent.
172 - (void)messageQueue:(MessageQueue*)queue didSendMessage:(NSString*)message {
173 NSInteger tag = [self transactionIDFromCommand:message];
174 _lastWrittenID = tag;
175
176 LoggingController* logger = [[AppDelegate instance] loggingController];
177 LogEntry* entry = [LogEntry newSendEntry:message];
178 entry.lastReadTransactionID = _lastReadID;
179 entry.lastWrittenTransactionID = _lastWrittenID;
180 [logger recordEntry:entry];
181 }
182
183 // Callback with the message content when one has been receieved.
184 - (void)messageQueue:(MessageQueue*)queue didReceiveMessage:(NSString*)message {
185 // Record this message in the transaction log.
186 LoggingController* logger = [[AppDelegate instance] loggingController];
187 LogEntry* entry = [LogEntry newReceiveEntry:message];
188 entry.lastReadTransactionID = _lastReadID;
189 entry.lastWrittenTransactionID = _lastWrittenID;
190 [logger recordEntry:entry];
191
192 // Parse the XML and test for errors.
193 NSError* error = nil;
194 NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message
195 options:NSXMLDocumentTidyXML
196 error:&error];
197 if (error) {
198 [self messageQueue:queue error:error];
199 return;
200 }
201 int transactionID = [self transactionIDFromResponse:xml];
202
203 _lastReadID = transactionID;
204 entry.lastReadTransactionID = _lastReadID;
205
206 if ([[[xml rootElement] elementsForName:@"error"] count] > 0) {
207 // Handle back-end errors.
208 [_delegate protocolClient:self receivedErrorMessage:xml];
209 } else if ([[[xml rootElement] name] isEqualToString:@"init"]) {
210 // Handle the initial connection message.
211 [_delegate protocolClient:self receivedInitialMessage:xml];
212 } else {
213 // Dispatch the handler for the message.
214 ProtocolClientMessageHandler handler = [_dispatchTable objectForKey:@(transactionID)];
215 if (handler) {
216 handler(xml);
217 [_dispatchTable removeObjectForKey:@(transactionID)];
218 } else {
219 // TODO(rsesek): Remove this path once the backend rewrite is complete.
220 [_delegate debuggerEngine:self receivedMessage:xml];
221 }
222 }
223 }
224
225 @end