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.
37 #import "LinkBackServer.h"
39 NSString* LinkBackPboardType = @"LinkBackData" ;
41 // LinkBack data keys. These are used in a LinkBack object, which is currently a dictionary. Do not depend on these values. They are public for testing purposes only.
42 NSString* LinkBackServerActionKey = @"serverActionKey" ;
43 NSString* LinkBackServerApplicationNameKey = @"serverAppName" ;
44 NSString* LinkBackServerNameKey = @"serverName" ;
45 NSString* LinkBackServerBundleIdentifierKey = @"bundleId" ;
46 NSString* LinkBackVersionKey = @"version" ;
47 NSString* LinkBackApplicationDataKey = @"appData" ;
48 NSString* LinkBackSuggestedRefreshKey = @"refresh" ;
49 NSString* LinkBackApplicationURLKey = @"ApplicationURL" ;
51 NSString* LinkBackEditActionName = @"_Edit" ;
52 NSString* LinkBackRefreshActionName = @"_Refresh" ;
54 // ...........................................................................
58 id MakeLinkBackData(NSString* serverName, id appData)
60 return [NSDictionary linkBackDataWithServerName: serverName appData: appData] ;
63 id LinkBackGetAppData(id LinkBackData)
65 return [LinkBackData linkBackAppData] ;
68 NSString* LinkBackUniqueItemKey()
70 static int counter = 0 ;
72 NSString* base = [[NSBundle mainBundle] bundleIdentifier] ;
73 unsigned long time = [NSDate timeIntervalSinceReferenceDate] ;
74 return [NSString stringWithFormat: @"%@%.8x.%.4x",base,time,counter++] ;
77 BOOL LinkBackDataBelongsToActiveApplication(id data)
79 return [data linkBackDataBelongsToActiveApplication] ;
82 NSString* LinkBackEditMultipleMenuTitle()
84 NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
85 NSString* ret = [bundle localizedStringForKey: @"_EditMultiple" value: @"Edit LinkBack Items" table: @"Localized"] ;
89 NSString* LinkBackEditNoneMenuTitle()
91 NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
92 NSString* ret = [bundle localizedStringForKey: @"_EditNone" value: @"Edit LinkBack Item" table: @"Localized"] ;
96 // ...........................................................................
97 // LinkBack Data Category
100 // Use these methods to create and access linkback data objects. You can also use the helper functions above.
102 @implementation NSDictionary (LinkBackData)
104 + (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData
106 return [self linkBackDataWithServerName: serverName appData: appData actionName: nil suggestedRefreshRate: 0];
109 + (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData suggestedRefreshRate:(NSTimeInterval)rate
111 return [self linkBackDataWithServerName: serverName appData: appData actionName: LinkBackRefreshActionName suggestedRefreshRate: rate] ;
114 + (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData actionName:(NSString*)action suggestedRefreshRate:(NSTimeInterval)rate ;
116 NSDictionary* appInfo = [[NSBundle mainBundle] infoDictionary] ;
118 NSMutableDictionary* ret = [[NSMutableDictionary alloc] init] ;
119 NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ;
120 NSString* url = [appInfo objectForKey: @"LinkBackApplicationURL"] ;
121 NSString* appName = [[NSProcessInfo processInfo] processName] ;
124 if (nil==serverName) [NSException raise: NSInvalidArgumentException format: @"LinkBack Data cannot be created without a server name."] ;
126 // callback information
127 [ret setObject: bundleId forKey: LinkBackServerBundleIdentifierKey];
128 [ret setObject: serverName forKey: LinkBackServerNameKey] ;
129 [ret setObject: version forKey: LinkBackVersionKey] ;
131 // additional information
132 if (appName) [ret setObject: appName forKey: LinkBackServerApplicationNameKey] ;
133 if (action) [ret setObject: action forKey: LinkBackServerActionKey] ;
134 if (appData) [ret setObject: appData forKey: LinkBackApplicationDataKey] ;
135 if (url) [ret setObject: url forKey: LinkBackApplicationURLKey] ;
136 [ret setObject: [NSNumber numberWithFloat: rate] forKey: LinkBackSuggestedRefreshKey] ;
138 return [ret autorelease] ;
141 - (BOOL)linkBackDataBelongsToActiveApplication
143 NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ;
144 NSString* dataId = [self objectForKey: LinkBackServerBundleIdentifierKey] ;
145 return (dataId && [dataId isEqualToString: bundleId]) ;
148 - (id)linkBackAppData
150 return [self objectForKey: LinkBackApplicationDataKey] ;
153 - (NSString*)linkBackSourceApplicationName
155 return [self objectForKey: LinkBackServerApplicationNameKey] ;
158 - (NSString*)linkBackActionName
160 NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
161 NSString* ret = [self objectForKey: LinkBackServerActionKey] ;
162 if (nil==ret) ret = LinkBackEditActionName ;
164 ret = [bundle localizedStringForKey: ret value: ret table: @"Localized"] ;
168 - (NSString*)linkBackEditMenuTitle
170 NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
171 NSString* appName = [self linkBackSourceApplicationName] ;
172 NSString* action = [self linkBackActionName] ;
173 NSString* ret = [bundle localizedStringForKey: @"_EditPattern" value: @"%@ in %@" table: @"Localized"] ;
174 ret = [NSString stringWithFormat: ret, action, appName] ;
178 - (NSString*)linkBackVersion
180 return [self objectForKey: LinkBackVersionKey] ;
183 - (NSTimeInterval)linkBackSuggestedRefreshRate
185 id obj = [self objectForKey: LinkBackSuggestedRefreshKey] ;
186 return (obj) ? [obj floatValue] : 0 ;
189 - (NSURL*)linkBackApplicationURL
191 id obj = [self objectForKey: LinkBackApplicationURLKey] ;
192 if (obj) obj = [NSURL URLWithString: obj] ;
198 // ...........................................................................
201 // one of these exists for each registered server name. This is the receiver of server requests.
203 // ...........................................................................
207 NSMutableDictionary* keyedLinkBacks = nil ;
209 @implementation LinkBack
213 static BOOL inited = NO ;
215 inited=YES; [super initialize] ;
216 keyedLinkBacks = [[NSMutableDictionary alloc] init] ;
219 + (LinkBack*)activeLinkBackForItemKey:(id)aKey
221 return [keyedLinkBacks objectForKey: aKey] ;
224 - (id)initServerWithClient: (LinkBack*)aLinkBack delegate: (id<LinkBackServerDelegate>)aDel
226 if ((self = [super init])) {
227 peer = [aLinkBack retain] ;
228 sourceName = [[peer sourceName] copy] ;
229 sourceApplicationName = [[peer sourceApplicationName] copy] ;
230 key = [[peer itemKey] copy] ;
233 [keyedLinkBacks setObject: self forKey: key] ;
239 - (id)initClientWithSourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)aDel itemKey:(NSString*)aKey ;
241 if ((self = [super init])) {
244 sourceName = [aName copy] ;
245 sourceApplicationName = [[NSProcessInfo processInfo] processName] ;
246 pboard = [[NSPasteboard pasteboardWithUniqueName] retain] ;
256 [sourceName release] ;
258 if (peer) [self closeLink] ;
261 if (!isServer) [pboard releaseGlobally] ; // client owns the pboard.
267 // ...........................................................................
268 // General Use methods
270 - (NSPasteboard*)pasteboard
275 - (id)representedObject
280 - (void)setRepresentedObject:(id)obj
287 - (NSString*)sourceName
292 - (NSString*)sourceApplicationName
294 return sourceApplicationName ;
302 // this method is called to initial a link closure from this side.
305 // inform peer of closure
307 [peer remoteCloseLink] ;
311 [keyedLinkBacks removeObjectForKey: [self itemKey]];
315 // this method is called whenever the link is about to be or has been closed by the other side.
316 - (void)remoteCloseLink
322 [keyedLinkBacks removeObjectForKey: [self itemKey]];
325 if (delegate) [delegate linkBackDidClose: self] ;
328 // ...........................................................................
329 // Server-side methods
331 + (BOOL)publishServerWithName:(NSString*)name delegate:(id<LinkBackServerDelegate>)del
333 return [LinkBackServer publishServerWithName: name delegate: del] ;
336 + (void)retractServerWithName:(NSString*)name
338 LinkBackServer* server = [LinkBackServer LinkBackServerWithName: name] ;
339 if (server) [server retract] ;
344 if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ;
345 [peer refreshEditWithPasteboardName: [pboard name]] ;
348 // FROM CLIENT LinkBack
349 - (void)requestEditWithPasteboardName:(bycopy NSString*)pboardName
351 // get the new pasteboard, if needed
352 if ((!pboard) || ![pboardName isEqualToString: [pboard name]]) pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ;
354 // pass onto delegate
355 [delegate performSelectorOnMainThread: @selector(linkBackClientDidRequestEdit:) withObject: self waitUntilDone: NO] ;
358 // ...........................................................................
359 // Client-Side Methods
361 + (LinkBack*)editLinkBackData:(id)data sourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)del itemKey:(NSString*)aKey
363 // if an active live link already exists, use that. Otherwise, create a new one.
364 LinkBack* ret = [keyedLinkBacks objectForKey: aKey] ;
367 BOOL ok = [data isKindOfClass: [NSDictionary class]] ;
370 // collect server contact information from data.
371 NSString* serverName = [data objectForKey: LinkBackServerNameKey] ;
372 NSString* serverId = [data objectForKey: LinkBackServerBundleIdentifierKey];
373 NSString* appName = [data linkBackSourceApplicationName] ;
374 NSURL* url = [data linkBackApplicationURL] ;
376 if ( !serverName || !serverId)
377 [NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ;
379 // create the live link object and try to connect to the server.
380 ret = [[LinkBack alloc] initClientWithSourceName: aName delegate: del itemKey: aKey] ;
382 if (![ret connectToServerWithName: serverName inApplication: serverId fallbackURL: url appName: appName]) {
387 [NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ;
391 // now with a live link in hand, request an edit
393 // if connected to server, publish data and inform server.
394 NSPasteboard* my_pboard = [ret pasteboard] ;
395 [my_pboard declareTypes: [NSArray arrayWithObject: LinkBackPboardType] owner: ret] ;
396 [my_pboard setPropertyList: data forType: LinkBackPboardType] ;
400 // if connection to server failed, return nil.
406 - (BOOL)connectToServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier fallbackURL:(NSURL*)url appName:(NSString*)appName
408 // get the LinkBackServer.
409 LinkBackServer* server = [LinkBackServer LinkBackServerWithName: aName inApplication: bundleIdentifier launchIfNeeded: YES fallbackURL: url appName: appName] ;
410 if (!server) return NO ; // failed to get server
412 peer = [[server initiateLinkBackFromClient: self] retain] ;
413 if (!peer) return NO ; // failed to initiate session
415 // if we connected, then add to the list of active keys
416 [keyedLinkBacks setObject: self forKey: [self itemKey]] ;
423 if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ;
424 [peer requestEditWithPasteboardName: [pboard name]] ;
427 // RECEIVED FROM SERVER
428 - (void)refreshEditWithPasteboardName:(bycopy NSString*)pboardName
430 // if pboard has changes, change to new pboard.
431 if (![pboardName isEqualToString: [pboard name]]) {
433 pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ;
437 [delegate performSelectorOnMainThread: @selector(linkBackServerDidSendEdit:) withObject: self waitUntilDone: NO] ;