mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-12-20 04:40:30 +00:00
441 lines
14 KiB
Objective-C
441 lines
14 KiB
Objective-C
//
|
|
// LinkBack.m
|
|
// LinkBack
|
|
//
|
|
// Created by Charles Jolley on Tue Jun 15 2004.
|
|
// Copyright (c) 2004, Nisus Software, Inc.
|
|
// All rights reserved.
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
//
|
|
// Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
//
|
|
// Neither the name of the Nisus Software, Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from this
|
|
// software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
|
|
#import "LinkBack.h"
|
|
#import "LinkBackServer.h"
|
|
|
|
NSString* LinkBackPboardType = @"LinkBackData" ;
|
|
|
|
// 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.
|
|
NSString* LinkBackServerActionKey = @"serverActionKey" ;
|
|
NSString* LinkBackServerApplicationNameKey = @"serverAppName" ;
|
|
NSString* LinkBackServerNameKey = @"serverName" ;
|
|
NSString* LinkBackServerBundleIdentifierKey = @"bundleId" ;
|
|
NSString* LinkBackVersionKey = @"version" ;
|
|
NSString* LinkBackApplicationDataKey = @"appData" ;
|
|
NSString* LinkBackSuggestedRefreshKey = @"refresh" ;
|
|
NSString* LinkBackApplicationURLKey = @"ApplicationURL" ;
|
|
|
|
NSString* LinkBackEditActionName = @"_Edit" ;
|
|
NSString* LinkBackRefreshActionName = @"_Refresh" ;
|
|
|
|
// ...........................................................................
|
|
// Support Functions
|
|
//
|
|
|
|
id MakeLinkBackData(NSString* serverName, id appData)
|
|
{
|
|
return [NSDictionary linkBackDataWithServerName: serverName appData: appData] ;
|
|
}
|
|
|
|
id LinkBackGetAppData(id LinkBackData)
|
|
{
|
|
return [LinkBackData linkBackAppData] ;
|
|
}
|
|
|
|
NSString* LinkBackUniqueItemKey()
|
|
{
|
|
static int counter = 0 ;
|
|
|
|
NSString* base = [[NSBundle mainBundle] bundleIdentifier] ;
|
|
unsigned long time = [NSDate timeIntervalSinceReferenceDate] ;
|
|
return [NSString stringWithFormat: @"%@%.8lx.%.4x",base,time,counter++] ;
|
|
}
|
|
|
|
BOOL LinkBackDataBelongsToActiveApplication(id data)
|
|
{
|
|
return [data linkBackDataBelongsToActiveApplication] ;
|
|
}
|
|
|
|
NSString* LinkBackEditMultipleMenuTitle()
|
|
{
|
|
NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
|
|
NSString* ret = [bundle localizedStringForKey: @"_EditMultiple" value: @"Edit LinkBack Items" table: @"Localized"] ;
|
|
return ret ;
|
|
}
|
|
|
|
NSString* LinkBackEditNoneMenuTitle()
|
|
{
|
|
NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
|
|
NSString* ret = [bundle localizedStringForKey: @"_EditNone" value: @"Edit LinkBack Item" table: @"Localized"] ;
|
|
return ret ;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// LinkBack Data Category
|
|
//
|
|
|
|
// Use these methods to create and access linkback data objects. You can also use the helper functions above.
|
|
|
|
@implementation NSDictionary (LinkBackData)
|
|
|
|
+ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData
|
|
{
|
|
return [self linkBackDataWithServerName: serverName appData: appData actionName: nil suggestedRefreshRate: (float)0];
|
|
}
|
|
|
|
+ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData suggestedRefreshRate:(NSTimeInterval)rate
|
|
{
|
|
return [self linkBackDataWithServerName: serverName appData: appData actionName: LinkBackRefreshActionName suggestedRefreshRate: rate] ;
|
|
}
|
|
|
|
+ (NSDictionary*)linkBackDataWithServerName:(NSString*)serverName appData:(id)appData actionName:(NSString*)action suggestedRefreshRate:(NSTimeInterval)rate
|
|
{
|
|
NSDictionary* appInfo = [[NSBundle mainBundle] infoDictionary] ;
|
|
|
|
NSMutableDictionary* ret = [[NSMutableDictionary alloc] init] ;
|
|
NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ;
|
|
NSString* url = [appInfo objectForKey: @"LinkBackApplicationURL"] ;
|
|
NSString* appName = [[NSProcessInfo processInfo] processName] ;
|
|
id version = @"A" ;
|
|
|
|
if (nil==serverName) [NSException raise: NSInvalidArgumentException format: @"LinkBack Data cannot be created without a server name."] ;
|
|
|
|
// callback information
|
|
[ret setObject: bundleId forKey: LinkBackServerBundleIdentifierKey];
|
|
[ret setObject: serverName forKey: LinkBackServerNameKey] ;
|
|
[ret setObject: version forKey: LinkBackVersionKey] ;
|
|
|
|
// additional information
|
|
if (appName) [ret setObject: appName forKey: LinkBackServerApplicationNameKey] ;
|
|
if (action) [ret setObject: action forKey: LinkBackServerActionKey] ;
|
|
if (appData) [ret setObject: appData forKey: LinkBackApplicationDataKey] ;
|
|
if (url) [ret setObject: url forKey: LinkBackApplicationURLKey] ;
|
|
[ret setObject: [NSNumber numberWithDouble: rate] forKey: LinkBackSuggestedRefreshKey] ;
|
|
|
|
return [ret autorelease] ;
|
|
}
|
|
|
|
- (BOOL)linkBackDataBelongsToActiveApplication
|
|
{
|
|
NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier] ;
|
|
NSString* dataId = [self objectForKey: LinkBackServerBundleIdentifierKey] ;
|
|
return (dataId && [dataId isEqualToString: bundleId]) ;
|
|
}
|
|
|
|
- (id)linkBackAppData
|
|
{
|
|
return [self objectForKey: LinkBackApplicationDataKey] ;
|
|
}
|
|
|
|
- (NSString*)linkBackSourceApplicationName
|
|
{
|
|
return [self objectForKey: LinkBackServerApplicationNameKey] ;
|
|
}
|
|
|
|
- (NSString*)linkBackActionName
|
|
{
|
|
NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
|
|
NSString* ret = [self objectForKey: LinkBackServerActionKey] ;
|
|
if (nil==ret) ret = LinkBackEditActionName ;
|
|
|
|
ret = [bundle localizedStringForKey: ret value: ret table: @"Localized"] ;
|
|
return ret ;
|
|
}
|
|
|
|
- (NSString*)linkBackEditMenuTitle
|
|
{
|
|
NSBundle* bundle = [NSBundle bundleForClass: [LinkBack class]] ;
|
|
NSString* appName = [self linkBackSourceApplicationName] ;
|
|
NSString* action = [self linkBackActionName] ;
|
|
NSString* ret = [bundle localizedStringForKey: @"_EditPattern" value: @"%@ in %@" table: @"Localized"] ;
|
|
ret = [NSString stringWithFormat: ret, action, appName] ;
|
|
return ret ;
|
|
}
|
|
|
|
- (NSString*)linkBackVersion
|
|
{
|
|
return [self objectForKey: LinkBackVersionKey] ;
|
|
}
|
|
|
|
- (NSTimeInterval)linkBackSuggestedRefreshRate
|
|
{
|
|
id obj = [self objectForKey: LinkBackSuggestedRefreshKey] ;
|
|
return (obj) ? [obj floatValue] : 0 ;
|
|
}
|
|
|
|
- (NSURL*)linkBackApplicationURL
|
|
{
|
|
id obj = [self objectForKey: LinkBackApplicationURLKey] ;
|
|
if (obj) obj = [NSURL URLWithString: obj] ;
|
|
return obj ;
|
|
}
|
|
|
|
@end
|
|
|
|
// ...........................................................................
|
|
// LinkBackServer
|
|
//
|
|
// one of these exists for each registered server name. This is the receiver of server requests.
|
|
|
|
// ...........................................................................
|
|
// LinkBack Class
|
|
//
|
|
|
|
NSMutableDictionary* keyedLinkBacks = nil ;
|
|
|
|
@implementation LinkBack
|
|
|
|
+ (void)initialize
|
|
{
|
|
static BOOL inited = NO ;
|
|
if (inited) return ;
|
|
inited=YES; [super initialize] ;
|
|
keyedLinkBacks = [[NSMutableDictionary alloc] init] ;
|
|
}
|
|
|
|
+ (LinkBack*)activeLinkBackForItemKey:(id)aKey
|
|
{
|
|
return [keyedLinkBacks objectForKey: aKey] ;
|
|
}
|
|
|
|
- (id)initServerWithClient: (LinkBack*)aLinkBack delegate: (id<LinkBackServerDelegate>)aDel
|
|
{
|
|
if ((self = [super init])) {
|
|
peer = [aLinkBack retain] ;
|
|
sourceName = [[peer sourceName] copy] ;
|
|
sourceApplicationName = [[peer sourceApplicationName] copy] ;
|
|
key = [[peer itemKey] copy] ;
|
|
isServer = YES ;
|
|
delegate = aDel ;
|
|
[keyedLinkBacks setObject: self forKey: key] ;
|
|
}
|
|
|
|
return self ;
|
|
}
|
|
|
|
- (id)initClientWithSourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)aDel itemKey:(NSString*)aKey
|
|
{
|
|
if ((self = [super init])) {
|
|
isServer = NO ;
|
|
delegate = aDel ;
|
|
sourceName = [aName copy] ;
|
|
sourceApplicationName = [[NSProcessInfo processInfo] processName] ;
|
|
pboard = [[NSPasteboard pasteboardWithUniqueName] retain] ;
|
|
key = [aKey copy] ;
|
|
}
|
|
|
|
return self ;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[repobj release] ;
|
|
[sourceName release] ;
|
|
|
|
if (peer) [self closeLink] ;
|
|
[peer release] ;
|
|
|
|
if (!isServer) [pboard releaseGlobally] ; // client owns the pboard.
|
|
[pboard release] ;
|
|
|
|
[super dealloc] ;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// General Use methods
|
|
|
|
- (NSPasteboard*)pasteboard
|
|
{
|
|
return pboard ;
|
|
}
|
|
|
|
- (id)representedObject
|
|
{
|
|
return repobj ;
|
|
}
|
|
|
|
- (void)setRepresentedObject:(id)obj
|
|
{
|
|
[obj retain] ;
|
|
[repobj release] ;
|
|
repobj = obj ;
|
|
}
|
|
|
|
- (NSString*)sourceName
|
|
{
|
|
return sourceName ;
|
|
}
|
|
|
|
- (NSString*)sourceApplicationName
|
|
{
|
|
return sourceApplicationName ;
|
|
}
|
|
|
|
- (NSString*)itemKey
|
|
{
|
|
return key ;
|
|
}
|
|
|
|
// this method is called to initial a link closure from this side.
|
|
- (void)closeLink
|
|
{
|
|
// inform peer of closure
|
|
if (peer) {
|
|
[peer remoteCloseLink] ;
|
|
[peer release] ;
|
|
peer = nil ;
|
|
[self release] ;
|
|
[keyedLinkBacks removeObjectForKey: [self itemKey]];
|
|
}
|
|
}
|
|
|
|
// this method is called whenever the link is about to be or has been closed by the other side.
|
|
- (oneway void)remoteCloseLink
|
|
{
|
|
if (peer) {
|
|
[peer release] ;
|
|
peer = nil ;
|
|
[self release] ;
|
|
[keyedLinkBacks removeObjectForKey: [self itemKey]];
|
|
}
|
|
|
|
if (delegate) [delegate linkBackDidClose: self] ;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// Server-side methods
|
|
//
|
|
+ (BOOL)publishServerWithName:(NSString*)name delegate:(id<LinkBackServerDelegate>)del
|
|
{
|
|
return [LinkBackServer publishServerWithName: name delegate: del] ;
|
|
}
|
|
|
|
+ (void)retractServerWithName:(NSString*)name
|
|
{
|
|
LinkBackServer* server = [LinkBackServer LinkBackServerWithName: name] ;
|
|
if (server) [server retract] ;
|
|
}
|
|
|
|
- (void)sendEdit
|
|
{
|
|
if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ;
|
|
[peer refreshEditWithPasteboardName: [pboard name]] ;
|
|
}
|
|
|
|
// FROM CLIENT LinkBack
|
|
- (void)requestEditWithPasteboardName:(bycopy NSString*)pboardName
|
|
{
|
|
// get the new pasteboard, if needed
|
|
if ((!pboard) || ![pboardName isEqualToString: [pboard name]]) pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ;
|
|
|
|
// pass onto delegate
|
|
[delegate performSelectorOnMainThread: @selector(linkBackClientDidRequestEdit:) withObject: self waitUntilDone: NO] ;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// Client-Side Methods
|
|
//
|
|
+ (LinkBack*)editLinkBackData:(id)data sourceName:(NSString*)aName delegate:(id<LinkBackClientDelegate>)del itemKey:(NSString*)aKey
|
|
{
|
|
// if an active live link already exists, use that. Otherwise, create a new one.
|
|
LinkBack* ret = [keyedLinkBacks objectForKey: aKey] ;
|
|
|
|
if(nil==ret) {
|
|
BOOL ok = [data isKindOfClass: [NSDictionary class]] ;
|
|
|
|
if (ok) {
|
|
// collect server contact information from data.
|
|
NSString* serverName = [data objectForKey: LinkBackServerNameKey] ;
|
|
NSString* serverId = [data objectForKey: LinkBackServerBundleIdentifierKey];
|
|
NSString* appName = [data linkBackSourceApplicationName] ;
|
|
NSURL* url = [data linkBackApplicationURL] ;
|
|
|
|
if ( !serverName || !serverId)
|
|
[NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ;
|
|
|
|
// create the live link object and try to connect to the server.
|
|
ret = [[LinkBack alloc] initClientWithSourceName: aName delegate: del itemKey: aKey] ;
|
|
|
|
if (![ret connectToServerWithName: serverName inApplication: serverId fallbackURL: url appName: appName]) {
|
|
[ret release] ;
|
|
ret = nil ;
|
|
}
|
|
} else {
|
|
[NSException raise: NSInvalidArgumentException format: @"LinkBackData is not of the correct format: %@", data] ;
|
|
}
|
|
}
|
|
|
|
// now with a live link in hand, request an edit
|
|
if (ret) {
|
|
// if connected to server, publish data and inform server.
|
|
NSPasteboard* my_pboard = [ret pasteboard] ;
|
|
[my_pboard declareTypes: [NSArray arrayWithObject: LinkBackPboardType] owner: ret] ;
|
|
[my_pboard setPropertyList: data forType: LinkBackPboardType] ;
|
|
|
|
[ret requestEdit] ;
|
|
|
|
// if connection to server failed, return nil.
|
|
}
|
|
|
|
return ret ;
|
|
}
|
|
|
|
- (BOOL)connectToServerWithName:(NSString*)aName inApplication:(NSString*)bundleIdentifier fallbackURL:(NSURL*)url appName:(NSString*)appName
|
|
{
|
|
// get the LinkBackServer.
|
|
LinkBackServer* server = [LinkBackServer LinkBackServerWithName: aName inApplication: bundleIdentifier launchIfNeeded: YES fallbackURL: url appName: appName] ;
|
|
if (!server) return NO ; // failed to get server
|
|
|
|
peer = [[server initiateLinkBackFromClient: self] retain] ;
|
|
if (!peer) return NO ; // failed to initiate session
|
|
|
|
// if we connected, then add to the list of active keys
|
|
[keyedLinkBacks setObject: self forKey: [self itemKey]] ;
|
|
|
|
return YES ;
|
|
}
|
|
|
|
- (void)requestEdit
|
|
{
|
|
if (!peer) [NSException raise: NSGenericException format: @"tried to request edit from a live link not connect to a server."] ;
|
|
[peer requestEditWithPasteboardName: [pboard name]] ;
|
|
}
|
|
|
|
// RECEIVED FROM SERVER
|
|
- (void)refreshEditWithPasteboardName:(bycopy NSString*)pboardName
|
|
{
|
|
// if pboard has changes, change to new pboard.
|
|
if (![pboardName isEqualToString: [pboard name]]) {
|
|
[pboard release] ;
|
|
pboard = [[NSPasteboard pasteboardWithName: pboardName] retain] ;
|
|
}
|
|
|
|
// inform delegate
|
|
[delegate performSelectorOnMainThread: @selector(linkBackServerDidSendEdit:) withObject: self waitUntilDone: NO] ;
|
|
}
|
|
|
|
@end
|