5 // Created by Charles Jolley on Tue Jun 15 2004.
6 // Copyright (c) 2004, Nisus Software, Inc.
7 // All rights reserved.
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are met:
12 // Redistributions of source code must retain the above copyright notice,
13 // this list of conditions and the following disclaimer.
15 // Redistributions in binary form must reproduce the above copyright notice,
16 // this list of conditions and the following disclaimer in the documentation
17 // and/or other materials provided with the distribution.
19 // Neither the name of the Nisus Software, Inc. nor the names of its
20 // contributors may be used to endorse or promote products derived from this
21 // software without specific prior written permission.
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24 // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25 // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 #import "LinkBackServer.h"
39 NSString* MakeLinkBackServerName(NSString* bundleIdentifier, NSString* name)
41 return [bundleIdentifier stringByAppendingFormat: @":%@",name] ;
44 NSMutableDictionary* LinkBackServers = nil ;
46 @implementation LinkBackServer
50 static BOOL inited = NO ;
56 if (!LinkBackServers) LinkBackServers = [[NSMutableDictionary alloc] init];
59 + (LinkBackServer*)LinkBackServerWithName:(NSString*)aName
61 return [LinkBackServers objectForKey: aName] ;
64 + (BOOL)publishServerWithName:(NSString*)aName delegate:(id<LinkBackServerDelegate>)del
66 LinkBackServer* serv = [[LinkBackServer alloc] initWithName: aName delegate: del] ;
67 BOOL ret = [serv publish] ; // retains if successful
72 BOOL LinkBackServerIsSupported(NSString* name, id supportedServers)
75 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050)
80 NSString* curServer = supportedServers ;
82 // NOTE: supportedServers may be nil, an NSArray, or NSString.
83 if (supportedServers) {
84 if ([supportedServers isKindOfClass: [NSArray class]]) {
85 idx = [supportedServers count] ;
86 while((NO==ret) && (idx > 0)) {
87 curServer = [supportedServers objectAtIndex: --idx] ;
88 ret = [curServer isEqualToString: name] ;
90 } else ret = [curServer isEqualToString: name] ;
96 NSString* FindLinkBackServer(NSString* bundleIdentifier, NSString* serverName, NSString* dir, int level)
100 NSFileManager* fm = [NSFileManager defaultManager] ;
101 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050)
102 NSArray* contents = [fm contentsOfDirectoryAtPath: dir error: nil] ;
105 NSArray* contents = [fm directoryContentsAtPath: dir] ;
112 NSString* cbundleIdentifier ;
113 id supportedServers ;
115 NSLog(@"searching for %@ in folder: %@", serverName, dir) ;
117 // resolve any symlinks, expand tildes.
118 dir = [dir stringByStandardizingPath] ;
120 // find all .app bundles in the directory and test them.
121 idx = (contents) ? [contents count] : 0 ;
122 while((nil==ret) && (idx > 0)) {
123 cpath = [contents objectAtIndex: --idx] ;
125 if ([[cpath pathExtension] isEqualToString: @"app"]) {
126 cpath = [dir stringByAppendingPathComponent: cpath] ;
127 cbundle = [NSBundle bundleWithPath: cpath] ;
128 cbundleIdentifier = [cbundle bundleIdentifier] ;
130 if ([cbundleIdentifier isEqualToString: bundleIdentifier]) {
131 supportedServers = [[cbundle infoDictionary] objectForKey: @"LinkBackServer"] ;
132 ret= (LinkBackServerIsSupported(serverName, supportedServers)) ? cpath : nil ;
137 // if the app was not found, descend into non-app dirs. only descend 4 levels to avoid taking forever.
138 if ((nil==ret) && (level<4)) {
139 idx = (contents) ? [contents count] : 0 ;
140 while((nil==ret) && (idx > 0)) {
143 cpath = [contents objectAtIndex: --idx] ;
144 [fm fileExistsAtPath: cpath isDirectory: &isdir] ;
145 if (isdir && (![[cpath pathExtension] isEqualToString: @"app"])) {
146 cpath = [dir stringByAppendingPathComponent: cpath] ;
147 ret = FindLinkBackServer(bundleIdentifier, serverName, cpath, level+1) ;
155 void LinkBackRunAppNotFoundPanel(NSString* appName, NSURL* url)
160 NSBundle* b = [NSBundle bundleForClass: [LinkBack class]] ;
166 title = NSLocalizedStringFromTableInBundle(@"_AppNotFoundTitle", @"Localized", b, @"app not found title") ;
167 ok = NSLocalizedStringFromTableInBundle(@"_OK", @"Localized", b, @"ok") ;
169 msg = (url) ? NSLocalizedStringFromTableInBundle(@"_AppNotFoundMessageWithURL", @"Localized", b, @"app not found msg") : NSLocalizedStringFromTableInBundle(@"_AppNotFoundMessageNoURL", @"Localized", b, @"app not found msg") ;
171 urlstr = (url) ? NSLocalizedStringFromTableInBundle(@"_GetApplication", @"Localized", b, @"Get application") : nil ;
173 title = [NSString stringWithFormat: title, appName] ;
175 result = NSRunCriticalAlertPanel(title, @"%@", ok, urlstr, nil, msg) ;
176 if (NSAlertAlternateReturn == result) {
177 [[NSWorkspace sharedWorkspace] openURL: url] ;
181 + (LinkBackServer*)LinkBackServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier launchIfNeeded:(BOOL)flag fallbackURL:(NSURL*)url appName:(NSString*)appName
184 NSString* serverName = MakeLinkBackServerName(bundleIdentifier, aName) ;
186 NSTimeInterval tryMark ;
189 ret = [NSConnection rootProxyForConnectionWithRegisteredName: serverName host: nil] ;
191 // if launchIfNeeded, and the connection was not available, try to launch.
192 if((!ret) && (flag)) {
196 // first, try to find the app with the bundle identifier
197 appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier: bundleIdentifier] ;
198 linkBackServers = [[[NSBundle bundleWithPath: appPath] infoDictionary] objectForKey: @"LinkBackServer"] ;
199 appPath = (LinkBackServerIsSupported(aName, linkBackServers)) ? appPath : nil ;
201 // if the found app is not supported, we will need to search for the app ourselves.
202 if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"/Applications",0);
204 if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"~/Applications",0);
206 if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"/Network/Applications",0);
208 // if app path has been found, launch the app.
210 [[NSWorkspace sharedWorkspace] launchApplication: appName] ;
212 LinkBackRunAppNotFoundPanel(appName, url) ;
217 // if needed, try to connect.
218 // retry connection for a while if we did not succeed at first. This gives the app time to launch.
219 if (connect && (nil==ret)) {
220 tryMark = [NSDate timeIntervalSinceReferenceDate] ;
222 ret = [NSConnection rootProxyForConnectionWithRegisteredName: serverName host: nil] ;
223 } while ((!ret) && (([NSDate timeIntervalSinceReferenceDate]-tryMark)<10)) ;
227 // setup protocol and return
228 if (ret) [ret setProtocolForProxy: @protocol(LinkBackServer)] ;
232 - (id)initWithName:(NSString*)aName delegate:(id<LinkBackServerDelegate>)aDel
236 name = [aName copy] ;
246 if (listener) [self retract] ;
253 NSString* serverName = MakeLinkBackServerName([[NSBundle mainBundle] bundleIdentifier], name) ;
256 // create listener and connect
257 NSPort* port = [NSPort port] ;
258 listener = [NSConnection connectionWithReceivePort: port sendPort:port] ;
259 [listener setRootObject: self] ;
260 ret = [listener registerName: serverName] ;
262 // if successful, retain connection and add self to list of servers.
265 [LinkBackServers setObject: self forKey: name] ;
266 } else listener = nil ; // listener will dealloc on its own.
274 [listener invalidate] ;
279 [LinkBackServers removeObjectForKey: name] ;
282 - (LinkBack*)initiateLinkBackFromClient:(LinkBack*)clientLinkBack
284 LinkBack* ret = [[LinkBack alloc] initServerWithClient: clientLinkBack delegate: delegate] ;
286 // NOTE: we do not release because LinkBack will release itself when it the link closes. (caj)