]> git.lyx.org Git - lyx.git/blob - src/support/linkback/LinkBackServer.m
Fix glitch in revert_biblatex_chicago
[lyx.git] / src / support / linkback / LinkBackServer.m
1 //
2 //  LinkBackServer.m
3 //  LinkBack
4 //
5 //  Created by Charles Jolley on Tue Jun 15 2004.
6 //  Copyright (c) 2004, Nisus Software, 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 are met:
11 //
12 //  Redistributions of source code must retain the above copyright notice,
13 //  this list of conditions and the following disclaimer.
14 //
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.
18 //
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.
22 //
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.
34 //
35
36 #import "LinkBackServer.h"
37 #import "LinkBack.h"
38
39 NSString* MakeLinkBackServerName(NSString* bundleIdentifier, NSString* name)
40 {
41     return [bundleIdentifier stringByAppendingFormat: @":%@",name] ;
42 }
43
44 NSMutableDictionary* LinkBackServers = nil ;
45
46 @implementation LinkBackServer
47
48 + (void)initialize
49 {
50     static BOOL inited = NO ;
51     if (inited) return ;
52
53     [super initialize] ;
54     inited = YES ;
55
56     if (!LinkBackServers) LinkBackServers = [[NSMutableDictionary alloc] init];
57 }
58
59 + (LinkBackServer*)LinkBackServerWithName:(NSString*)aName
60 {
61     return [LinkBackServers objectForKey: aName] ;
62 }
63
64 + (BOOL)publishServerWithName:(NSString*)aName delegate:(id<LinkBackServerDelegate>)del
65 {
66     LinkBackServer* serv = [[LinkBackServer alloc] initWithName: aName delegate: del] ;
67     BOOL ret = [serv publish] ; // retains if successful
68     [serv release] ;
69     return ret ;
70 }
71
72 BOOL LinkBackServerIsSupported(NSString* name, id supportedServers)
73 {
74         BOOL ret = NO ;
75 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050)
76         NSUInteger idx ;
77 #else
78         int idx ;
79 #endif
80         NSString* curServer = supportedServers ;
81
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] ;
89                         }
90                 } else ret = [curServer isEqualToString: name] ;
91         }
92
93         return ret ;
94 }
95
96 NSString* FindLinkBackServer(NSString* bundleIdentifier, NSString* serverName, NSString* dir, int level)
97 {
98         NSString* ret = nil ;
99
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] ;
103         NSUInteger idx ;
104 #else
105         NSArray* contents = [fm directoryContentsAtPath: dir] ;
106         int idx ;
107 #endif
108
109         // working info
110         NSString* cpath ;
111         NSBundle* cbundle ;
112         NSString* cbundleIdentifier ;
113         id supportedServers ;
114
115         NSLog(@"searching for %@ in folder: %@", serverName, dir) ;
116
117         // resolve any symlinks, expand tildes.
118         dir = [dir stringByStandardizingPath] ;
119
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] ;
124
125                 if ([[cpath pathExtension] isEqualToString: @"app"]) {
126                         cpath = [dir stringByAppendingPathComponent: cpath] ;
127                         cbundle = [NSBundle bundleWithPath: cpath] ;
128                         cbundleIdentifier = [cbundle bundleIdentifier] ;
129
130                         if ([cbundleIdentifier isEqualToString: bundleIdentifier]) {
131                                 supportedServers = [[cbundle infoDictionary] objectForKey: @"LinkBackServer"] ;
132                                 ret= (LinkBackServerIsSupported(serverName, supportedServers)) ? cpath : nil ;
133                         }
134                 }
135         }
136
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)) {
141                         BOOL isdir ;
142
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) ;
148                         }
149                 }
150         }
151
152         return ret ;
153 }
154
155 void LinkBackRunAppNotFoundPanel(NSString* appName, NSURL* url)
156 {
157
158         // strings for panel
159         NSBundle* b = [NSBundle bundleForClass: [LinkBack class]] ;
160         NSString* title ;
161         NSString* msg ;
162         NSString* ok ;
163         NSString* urlstr ;
164
165         title = NSLocalizedStringFromTableInBundle(@"_AppNotFoundTitle", @"Localized", b, @"app not found title") ;
166         ok = NSLocalizedStringFromTableInBundle(@"_OK", @"Localized", b, @"ok") ;
167
168         msg = (url) ? NSLocalizedStringFromTableInBundle(@"_AppNotFoundMessageWithURL", @"Localized", b, @"app not found msg") : NSLocalizedStringFromTableInBundle(@"_AppNotFoundMessageNoURL", @"Localized", b, @"app not found msg") ;
169
170         urlstr = (url) ? NSLocalizedStringFromTableInBundle(@"_GetApplication", @"Localized", b, @"Get application") : nil ;
171
172         title = [NSString stringWithFormat: title, appName] ;
173
174         NSAlert* alert = [[NSAlert alloc] init];
175         [alert setAlertStyle:NSAlertStyleCritical];
176         [alert setMessageText:title];
177         [alert setInformativeText:[NSString stringWithFormat:@"%@", msg]];
178         [alert addButtonWithTitle:ok];
179         [alert addButtonWithTitle:urlstr];
180         [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSModalResponse returnCode) {
181                 if (returnCode == NSAlertSecondButtonReturn)
182                                 [[NSWorkspace sharedWorkspace] openURL: url] ;
183         }];
184 }
185
186 + (LinkBackServer*)LinkBackServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier launchIfNeeded:(BOOL)flag fallbackURL:(NSURL*)url appName:(NSString*)appName
187 {
188         BOOL connect = YES ;
189         NSString* serverName = MakeLinkBackServerName(bundleIdentifier, aName) ;
190     id ret = nil ;
191         NSTimeInterval tryMark ;
192
193         // Try to connect
194         ret = [NSConnection rootProxyForConnectionWithRegisteredName: serverName host: nil] ;
195
196     // if launchIfNeeded, and the connection was not available, try to launch.
197         if((!ret) && (flag)) {
198                 NSString* appPath ;
199                 id linkBackServers ;
200
201                 // first, try to find the app with the bundle identifier
202                 appPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier: bundleIdentifier] ;
203                 linkBackServers = [[[NSBundle bundleWithPath: appPath] infoDictionary] objectForKey: @"LinkBackServer"] ;
204                 appPath = (LinkBackServerIsSupported(aName, linkBackServers)) ? appPath : nil ;
205
206                 // if the found app is not supported, we will need to search for the app ourselves.
207                 if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"/Applications",0);
208
209                 if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"~/Applications",0);
210
211                 if (nil==appPath) appPath = FindLinkBackServer(bundleIdentifier, aName, @"/Network/Applications",0);
212
213                 // if app path has been found, launch the app.
214                 if (appPath) {
215                         [[NSWorkspace sharedWorkspace] launchApplication: appName] ;
216                 } else {
217                         LinkBackRunAppNotFoundPanel(appName, url) ;
218                         connect = NO ;
219                 }
220         }
221
222     // if needed, try to connect.
223         // retry connection for a while if we did not succeed at first.  This gives the app time to launch.
224         if (connect && (nil==ret)) {
225                 tryMark = [NSDate timeIntervalSinceReferenceDate] ;
226                 do {
227                         ret = [NSConnection rootProxyForConnectionWithRegisteredName: serverName host: nil] ;
228                 } while ((!ret) && (([NSDate timeIntervalSinceReferenceDate]-tryMark)<10)) ;
229
230         }
231
232         // setup protocol and return
233     if (ret) [ret setProtocolForProxy: @protocol(LinkBackServer)] ;
234     return ret ;
235 }
236
237 - (id)initWithName:(NSString*)aName delegate:(id<LinkBackServerDelegate>)aDel
238 {
239     self = [super init];
240     if (self) {
241         name = [aName copy] ;
242         delegate = aDel ;
243         listener = nil ;
244     }
245
246     return self ;
247 }
248
249 - (void)dealloc
250 {
251     if (listener) [self retract] ;
252     [name release] ;
253     [super dealloc] ;
254 }
255
256 - (BOOL)publish
257 {
258     NSString* serverName = MakeLinkBackServerName([[NSBundle mainBundle] bundleIdentifier], name) ;
259     BOOL ret = YES ;
260
261     // create listener and connect
262     NSPort* port = [NSPort port] ;
263     listener = [NSConnection connectionWithReceivePort: port sendPort:port] ;
264     [listener setRootObject: self] ;
265     ret = [listener registerName: serverName] ;
266
267     // if successful, retain connection and add self to list of servers.
268     if (ret) {
269         [listener retain] ;
270         [LinkBackServers setObject: self forKey: name] ;
271     } else listener = nil ; // listener will dealloc on its own.
272
273     return ret ;
274 }
275
276 - (void)retract
277 {
278     if (listener) {
279         [listener invalidate] ;
280         [listener release] ;
281         listener = nil ;
282     }
283
284     [LinkBackServers removeObjectForKey: name] ;
285 }
286
287 - (LinkBack*)initiateLinkBackFromClient:(LinkBack*)clientLinkBack
288 {
289     LinkBack* ret = [[LinkBack alloc] initServerWithClient: clientLinkBack delegate: delegate] ;
290
291     // NOTE: we do not release because LinkBack will release itself when it the link closes. (caj)
292
293     return ret ;
294 }
295
296 @end