Ticket #1803: DPAgent.m

File DPAgent.m, 12.6 KB (added by davidkarl@…, 20 years ago)

patch to fix

Line 
1//
2//  DPAgent.h
3//  DarwinPorts
4//
5/*
6 Copyright (c) 2003 Apple Computer, Inc.
7 All rights reserved.
8 
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions
11 are met:
12 1. Redistributions of source code must retain the above copyright
13    notice, this list of conditions and the following disclaimer.
14 2. Redistributions in binary form must reproduce the above copyright
15    notice, this list of conditions and the following disclaimer in the
16    documentation and/or other materials provided with the distribution.
17 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
18    may be used to endorse or promote products derived from this software
19    without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#import "DPAgent.h"
35#import "DPObject.h"
36
37// package info
38static NSString *DPPackageName = @"darwinports";
39static NSString *DPPackageVersion = @"1.0";
40static NSString *DPPackageInit = @"dportinit";
41
42// commands
43static NSString *DPSearchCommand = @"dportsearch";
44static NSString *DPOpenCommand = @"dportopen";
45static NSString *DPExecCommand = @"dportexec";
46static NSString *DPCloseCommand = @"dportclose";
47
48// arguments
49static NSString *DPAnyPortArgument = @".+";
50
51// results
52static NSString *DPYesResult = @"1";
53static NSString *DPNoResult = @"0";
54
55// ui
56static NSString *DPUIPuts = @"ui_puts";
57
58
59@implementation DPAgent
60/*
61    Serves as a bridge between the GUI app and the tcl interpreter.   Uses distributed objects to communicate with a delegte (GUI front-end).  All values exposed by the APIs to this class are generic objective-c data-types and not TCL DPObjects... clients talking to this class should not know or care that there's a tcl interpreter hiding underneath.   This class should also be the place to encapsulate all knowledge about the internal structure/constants of the ports system so that clients do not need to know about those details.
62    This class is multi-threaded but all port operations are currently serialized (see more comments on this below)\
63    The agent does not cache any information but rather always calls the tcl engine to talk to the ports collection and get the most recent information.   Caching should (and is) performed in the application front-end.   
64*/
65
66/** Init and clean-up **/
67
68
69- (id) init
70{
71   
72    if (self = [super init])
73    {
74
75        _interpLock = [[NSLock alloc] init];
76        _ports = [[NSMutableDictionary alloc] init];
77       
78        // configure our tcl interpreter
79        _interp = [[DPInterp alloc] init];
80        [_interp loadPackage: DPPackageName version: DPPackageVersion usingCommand: DPPackageInit];
81        [_interp redirectCommand: [DPObject objectWithString: DPUIPuts] toObject: self];
82
83        // configure our d.o. connection
84        _connection = [NSConnection defaultConnection];
85        [_connection setRootObject: self];
86        [_connection enableMultipleThreads];
87                [_connection setDelegate: self];
88        if ([_connection registerName: @"DPAgent"] == NO)
89        {
90            NSLog(@"Couldn't register server on this host.");
91            exit(0);
92        }
93
94        [[NSNotificationCenter defaultCenter] addObserver:self
95            selector: @selector(connectionDidDie:)
96            name: NSConnectionDidDieNotification
97            object: _connection];
98 
99    }
100    return self;
101
102}
103
104
105- (void) dealloc
106{
107    [_interp release];
108    [_interpLock release];
109    [super dealloc];
110}
111
112
113/** D.O. connection management **/
114
115
116- (BOOL) connection: (NSConnection *)parentConnection shouldMakeNewConnection:(NSConnection *)newConnection
117{
118    /*
119     * Ensure that connectionDidDie: is called if newConnection
120     * dies without terminate being called
121     */
122    [[NSNotificationCenter defaultCenter] addObserver:self
123        selector: @selector(connectionDidDie:)
124        name: NSConnectionDidDieNotification
125        object: newConnection];
126    return YES;
127}
128
129
130- (void) connectionDidDie: (id)connection
131{
132    exit(0);
133}
134
135
136- (oneway void) terminate
137{
138    [self performSelector: @selector(connectionDidDie:) withObject: self afterDelay: 0.0];
139}
140
141
142
143/** Port operations **/
144
145
146- (bycopy NSData *) portsData
147/*
148    Returns a serialized property list describing the ports collection
149*/
150{
151   
152    Tcl_Obj **objv;
153    int count = 0, i=0;
154    DPObject *result;
155    NSString *error;
156    NSData *portsData;
157   
158    [_interpLock lock];   
159   
160    [_ports removeAllObjects];
161    result = [_interp evaluateCommand: [DPObject objectWithString: DPSearchCommand]
162        withObject: [DPObject objectWithString: DPAnyPortArgument]];
163    Tcl_ListObjGetElements(NULL, [result tclObj], &count, &objv);
164
165    while (++i < count)
166    {   
167
168        NSMutableDictionary *portDict = [NSMutableDictionary dictionary];
169        Tcl_Obj **innerobjs;
170        int innercount = 0, j = 0;   
171        Tcl_ListObjGetElements(NULL, objv[i++], &innercount, &innerobjs);
172
173        while (j < innercount)
174        {
175
176            DPObject *keyObject = [DPObject objectWithTclObj: innerobjs[j++]];
177            DPObject *valueObject = [DPObject objectWithTclObj: innerobjs[j++]];
178            NSString *key = [keyObject stringValue];
179            id value;
180           
181            if ([key isEqualToString: DPCategoriesKey] ||
182                [key isEqualToString: DPMaintainersKey])
183            {
184                NSEnumerator *enm = [[[valueObject stringValue] componentsSeparatedByString: @" "] objectEnumerator];
185                NSString *component;
186                value = [NSMutableArray array];
187                while (component = [enm nextObject])
188                {
189                    if (![value containsObject: component])
190                    {
191                        [value addObject: component];
192                    }
193                }
194   
195            }
196            else if ([key rangeOfString: DPDependsKey].location != NSNotFound)
197            {
198                NSEnumerator *dependencyEnm = [[[valueObject stringValue] componentsSeparatedByString: @" "] objectEnumerator];
199                NSString *component;
200                value = [NSMutableArray array];
201                while (component = [dependencyEnm nextObject])
202                {
203                    if ([component length] > 0)
204                                        {
205                                                NSString *dependencyName = [[component componentsSeparatedByString: @":"] objectAtIndex: 2];
206                                                if (![value containsObject: dependencyName])
207                                                {
208                                                        [value addObject: dependencyName];
209                                                }
210                    }
211                }
212                key = DPDependsKey;
213            }
214            else
215            {
216                value = [valueObject stringValue];
217            }
218            [portDict setObject: value forKey: key];
219
220        }
221        [_ports setObject: portDict forKey: [portDict objectForKey: DPNameKey]];
222    }
223    [_interpLock unlock];   
224
225    // we serialize the data before returning it so that we can pass a deep copy of
226    // the entire dictionary back in a single D.O. exchange.   if we just pass
227    // a copy of _ports back then the GUI app is still forced to query objects
228    // stored in the dict via d.o. proxies - which is dirt slow when refreshing
229    // the outline view in the GUI   
230    portsData = [NSPropertyListSerialization dataFromPropertyList: _ports
231        format: NSPropertyListXMLFormat_v1_0
232        errorDescription: &error];
233
234    return portsData;
235   
236}
237
238
239- (oneway void) executeTarget: (NSString *)target forPortName: (NSString *)portName
240/*
241    Detaches a new thread to perform the specified target on the specified port...
242*/
243{
244    NSDictionary *op = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
245        portName, @"portName",
246        [DPObject objectWithString: target], @"target",
247        [DPObject objectWithString: [[_ports objectForKey: portName] objectForKey: DPPortURLKey]], @"url",
248        nil];
249    [NSThread detachNewThreadSelector: sel_getUid("_threadForOperation:")
250        toTarget: self
251        withObject: op];
252}
253
254
255/** Execution thread methods */
256
257/*
258    Everything beyond this point code executes in secondary threads.  I've made a conscious effort to not share any objects (except for the _interpLock) between the primary thread and the secondary threads to minimize the need to worry about thread-safe data sharing between threads.
259    Even though we have a separate thread for each operation all operations are currently serialized using the _interpLock.   If we wish to allow multiple simultaneous operations in the future then each thread will have to instantiate it's own interpreter (and the gui will also have to be made more flexible to sort out messages coming back from multiple simultaneous operations).   
260    If we allow multiple truly simultaneous operations we also need to think about situations such as what happens if you start installing Port A and Port B both of which have a dependency on C?  Does C get installed twice?  Or even worse what if you start installing port A which depends on port C and simultaneously start uninstalling C?  etc.   For now easier to sidestep the whole issue by serializing everything.
261*/
262
263
264- (void) _threadForOperation: (NSMutableDictionary *)op
265{
266
267    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];   
268
269    // establish a separate connection for communication from this thread
270    // back to the PortsManager.app
271    NSConnection *connection = [NSConnection connectionWithRegisteredName: @"PMApp" host: nil];
272    id <DPDelegateProtocol> delegate = [connection rootProxy];
273    [connection enableMultipleThreads];   
274    [(NSDistantObject *)delegate setProtocolForProxy:@protocol(DPDelegateProtocol)];
275    [[NSNotificationCenter defaultCenter] addObserver:self
276        selector: @selector(connectionDidDie:)
277        name: NSConnectionDidDieNotification
278        object: connection];
279    [[[NSThread currentThread] threadDictionary] setObject: delegate forKey: @"delegate"];
280   
281    if ([delegate shouldPerformTarget: [[op objectForKey: @"target"] stringValue] forPortName: [op objectForKey: @"portName"]])
282    {
283        DPObject *result = nil;
284        [delegate willPerformTarget: [[op objectForKey: @"target"] stringValue] forPortName: [op objectForKey: @"portName"]];
285        [_interpLock lock]; // this can block for a long time if another op is in progress
286        _currentOp = op;
287        DPObject *workName = [_interp evaluateCommand: [DPObject objectWithString: DPOpenCommand]
288            withObject: [op objectForKey: @"url"]];
289        if ([_interp succeeded])
290        {
291            result = [_interp evaluateCommand: [DPObject objectWithString: DPExecCommand]
292                withObjects: workName : [op objectForKey: @"target"]];
293            if ([_interp succeeded])
294            {
295                result = [_interp evaluateCommand: [DPObject objectWithString: DPCloseCommand] withObject: workName];           
296            }
297        }
298        _currentOp = nil;
299        [_interpLock unlock];
300        [delegate didPerformTarget: [[op objectForKey: @"target"] stringValue] forPortName: [op objectForKey: @"portName"] withResult: [result stringValue]];
301    }
302
303    // Be sure to unregister for NSConnectionDidDieNotification before
304    // auto-releasing the connection object
305    [[NSNotificationCenter defaultCenter] removeObserver:self
306        name: NSConnectionDidDieNotification
307        object: connection];
308
309    [op release];
310    [pool release];
311
312}
313
314
315- (DPObject *) ui_puts: (NSArray *)array
316{
317    NSDictionary *message = [[array objectAtIndex: 1] dictionaryValue];
318    if (message == nil)
319        return [DPObject objectWithString: DPNoResult];
320
321    NSString *data = [message objectForKey: @"data"];
322    NSString *priority = [message objectForKey: @"priority"];
323    if (data == nil || priority == nil)
324        return [DPObject objectWithString: DPNoResult];
325
326    id delegate = [[[NSThread currentThread] threadDictionary] objectForKey: @"delegate"];
327    [delegate displayMessage: [NSString stringWithFormat: @"%@%@", data, @"\n"]
328        withPriority: priority
329        forPortName: [_currentOp objectForKey: @"portName"]];       
330    return [DPObject objectWithString: DPYesResult];
331}
332
333
334
335@end