Stop spamming the CPU with -[MessageQueue dequeueAndSendBlocks].
[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
24 - (id)initWithDelegate:(NSObject<ProtocolClientDelegate>*)delegate {
25 if ((self = [super init])) {
26 _delegate = delegate;
27 _delegateThread = [NSThread currentThread];
28 _lock = [[NSRecursiveLock alloc] init];
29 }
30 return self;
31 }
32
33 - (void)dealloc {
34 [_lock release];
35 [super dealloc];
36 }
37
38 - (BOOL)isConnected {
39 return [_messageQueue isConnected];
40 }
41
42 - (void)connectOnPort:(NSUInteger)port {
43 assert(!_messageQueue);
44 _messageQueue = [[MessageQueue alloc] initWithPort:port delegate:self];
45 [_messageQueue connect];
46 }
47
48 - (void)disconnect {
49 [_messageQueue disconnect];
50 }
51
52 - (NSNumber*)sendCommandWithFormat:(NSString*)format, ... {
53 // Collect varargs and format command.
54 va_list args;
55 va_start(args, format);
56 NSString* command = [[NSString alloc] initWithFormat:format arguments:args];
57 va_end(args);
58
59 NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++];
60 NSString* taggedCommand = [NSString stringWithFormat:@"%@ -i %@", [command autorelease], callbackKey];
61
62 assert(_messageQueue);
63 [_messageQueue sendMessage:taggedCommand];
64 return callbackKey;
65 }
66
67 - (NSNumber*)sendCustomCommandWithFormat:(NSString*)format, ... {
68 // Collect varargs and format command.
69 va_list args;
70 va_start(args, format);
71 NSString* command = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
72 va_end(args);
73
74 NSNumber* callbackKey = [NSNumber numberWithInt:_nextID++];
75 NSString* taggedCommand = [command stringByReplacingOccurrencesOfString:@"{txn}"
76 withString:[callbackKey stringValue]];
77
78 [_messageQueue sendMessage:taggedCommand];
79 return callbackKey;
80 }
81
82 - (NSInteger)transactionIDFromResponse:(NSXMLDocument*)response {
83 return [[[[response rootElement] attributeForName:@"transaction_id"] stringValue] intValue];
84 }
85
86 - (NSInteger)transactionIDFromCommand:(NSString*)command {
87 NSRange occurrence = [command rangeOfString:@"-i "];
88 if (occurrence.location == NSNotFound)
89 return NSNotFound;
90 NSString* transaction = [command substringFromIndex:occurrence.location + occurrence.length];
91 return [transaction intValue];
92 }
93
94 + (NSString*)escapedFilePathURI:(NSString*)path {
95 // Custon GDBp paths are fine.
96 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
97 return path;
98
99 // Create a temporary URL that will escape all the nasty characters.
100 NSURL* url = [NSURL fileURLWithPath:path];
101 NSString* urlString = [url absoluteString];
102
103 // Remove the host because this is a file:// URL;
104 NSString* host = [url host];
105 if (host)
106 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
107
108 // Escape % for use in printf-style NSString formatters.
109 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
110 return urlString;
111 }
112
113 // MessageQueueDelegate ////////////////////////////////////////////////////////
114
115 - (void)messageQueue:(MessageQueue*)queue error:(NSError*)error {
116 NSLog(@"error = %@", error);
117 }
118
119 - (void)messageQueueDidConnect:(MessageQueue*)queue {
120 [_lock lock];
121 _nextID = 0;
122 _lastReadID = 0;
123 _lastWrittenID = 0;
124 [_lock unlock];
125
126 [_delegate debuggerEngineConnected:self];
127 }
128
129 - (void)messageQueueDidDisconnect:(MessageQueue*)queue {
130 [_messageQueue release];
131 _messageQueue = nil;
132 [_delegate debuggerEngineDisconnected:self];
133 }
134
135 // Callback for when a message has been sent.
136 - (void)messageQueue:(MessageQueue*)queue didSendMessage:(NSString*)message
137 {
138 NSInteger tag = [self transactionIDFromCommand:message];
139 [_lock lock];
140 _lastWrittenID = tag;
141 [_lock unlock];
142
143 LoggingController* logger = [[AppDelegate instance] loggingController];
144 LogEntry* entry = [LogEntry newSendEntry:message];
145 entry.lastReadTransactionID = _lastReadID;
146 entry.lastWrittenTransactionID = _lastWrittenID;
147 [logger recordEntry:entry];
148 }
149
150 // Callback with the message content when one has been receieved.
151 - (void)messageQueue:(MessageQueue*)queue didReceiveMessage:(NSString*)message
152 {
153 LoggingController* logger = [[AppDelegate instance] loggingController];
154 LogEntry* entry = [LogEntry newReceiveEntry:message];
155 entry.lastReadTransactionID = _lastReadID;
156 entry.lastWrittenTransactionID = _lastWrittenID;
157 [logger recordEntry:entry];
158
159 // Test if we can convert it into an NSXMLDocument.
160 NSError* error = nil;
161 NSXMLDocument* xml = [[NSXMLDocument alloc] initWithXMLString:message
162 options:NSXMLDocumentTidyXML
163 error:&error];
164 if (error) {
165 [self messageQueue:queue error:error];
166 return;
167 }
168
169 // Validate the transaction.
170 NSInteger transaction = [self transactionIDFromResponse:xml];
171 if (transaction < _lastReadID) {
172 NSLog(@"Transaction #%d is out of date (lastRead = %d). Dropping packet: %@",
173 transaction, _lastReadID, message);
174 return;
175 }
176 if (transaction != _lastWrittenID) {
177 NSLog(@"Transaction #%d received out of order. lastRead = %d, lastWritten = %d. Continuing.",
178 transaction, _lastReadID, _lastWrittenID);
179 }
180
181 _lastReadID = transaction;
182 entry.lastReadTransactionID = _lastReadID;
183
184 [_delegate debuggerEngine:self receivedMessage:xml];
185 }
186
187 @end