Revision: 29194 http://sourceforge.net/p/bibdesk/svn/29194 Author: hofman Date: 2025-04-25 16:30:07 +0000 (Fri, 25 Apr 2025) Log Message: ----------- Add search group server for SRU. This is a rerplacement protocol for z39.50 for libraries. Not yet tested and not yet possible to choose in UI.
Modified Paths: -------------- trunk/bibdesk/BDSKSearchGroup.h trunk/bibdesk/BDSKSearchGroup.m trunk/bibdesk/BDSKSearchGroupSheetController.h trunk/bibdesk/BDSKSearchGroupSheetController.m trunk/bibdesk/BDSKServerInfo.h trunk/bibdesk/BDSKServerInfo.m trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj Added Paths: ----------- trunk/bibdesk/BDSKSRUGroupServer.h trunk/bibdesk/BDSKSRUGroupServer.m Added: trunk/bibdesk/BDSKSRUGroupServer.h =================================================================== --- trunk/bibdesk/BDSKSRUGroupServer.h (rev 0) +++ trunk/bibdesk/BDSKSRUGroupServer.h 2025-04-25 16:30:07 UTC (rev 29194) @@ -0,0 +1,64 @@ +// +// BDSKSRUGroupServer.h +// BibDesk +// +// Created by Christiaan Hofmanon 25/04/2025. +/* + This software is Copyright (c) 2025 + Christiaan Hofman. 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 Christiaan Hofman nor the names of any + 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 <Cocoa/Cocoa.h> +#import "BDSKSearchGroup.h" +#import "BDSKDownloader.h" + +NS_ASSUME_NONNULL_BEGIN + +@class BDSKServerInfo; + +@interface BDSKSRUGroupServer : NSObject <BDSKSearchGroupServer, BDSKDownloadDelegate> { + __weak id<BDSKSearchGroup> group; + BDSKServerInfo *serverInfo; + NSString *searchTerm; + BDSKDownload *download; + BOOL failedDownload; + BOOL needsReset; + NSInteger availableResults; + NSInteger fetchedResults; + NSInteger requestedResults; + NSInteger limitResults; + NSInteger downloadState; + NSString *errorMessage; +} + +@end + +NS_ASSUME_NONNULL_END Added: trunk/bibdesk/BDSKSRUGroupServer.m =================================================================== --- trunk/bibdesk/BDSKSRUGroupServer.m (rev 0) +++ trunk/bibdesk/BDSKSRUGroupServer.m 2025-04-25 16:30:07 UTC (rev 29194) @@ -0,0 +1,330 @@ +// +// BDSKSRUGroupServer.m +// BibDesk +// +// Created by Christiaan Hofmanon 25/04/2025. +/* + This software is Copyright (c) 2025 + Christiaan Hofman. 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 Christiaan Hofman nor the names of any + 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 "BDSKSRUGroupServer.h" +#import "BDSKServerInfo.h" +#import "BDSKStringParser.h" +#import "BDSKMARCParser.h" +#import "NSString_BDSKExtensions.h" +#import "NSURL_BDSKExtensions.h" + +#define MAX_RESULTS 100 +#define MAX_TOTAL_RESULTS 1000 + +enum { BDSKIdleState, BDSKSearchState, BDSKFetchState }; + +@interface BDSKSRUGroupServer () + +@property (nonatomic, copy) NSString *searchTerm; + +@property (nonatomic, copy) NSString *errorMessage; + +- (void)resetSearch; +- (void)fetch; + +- (void)startDownloadFromURL:(NSURL *)theURL; + +@end + +#pragma mark - + +@implementation BDSKSRUGroupServer + +@synthesize searchTerm, errorMessage; + +- (instancetype)initWithGroup:(id<BDSKSearchGroup>)aGroup serverInfo:(BDSKServerInfo *)info { + self = [super init]; + if (self) { + group = aGroup; + serverInfo = [info copy]; + searchTerm = nil; + failedDownload = NO; + needsReset = NO; + availableResults = 0; + fetchedResults = 0; + limitResults = 0; + requestedResults = 0; + download = nil; + downloadState = BDSKIdleState; + errorMessage = nil; + } + return self; +} + +#pragma mark BDSKSearchGroupServer protocol + +- (NSString *)type { return BDSKSearchGroupSRU; } + +- (void)reset { + if ([self isRetrieving]) + [self terminate]; + availableResults = 0; + fetchedResults = 0; +} + +- (void)terminate { + [download cancel]; + download = nil; + downloadState = BDSKIdleState; +} + +- (void)retrieveWithSearchTerm:(NSString *)aSearchTerm { + if ([[[self baseComponents] URL] canConnect]) { + if ([[self searchTerm] isEqualToString:aSearchTerm] == NO || needsReset) { + [self setSearchTerm:aSearchTerm]; + [self resetSearch]; + } else if ([self isRetrieving] == NO) { + limitResults = MIN(availableResults, fetchedResults + MAX_TOTAL_RESULTS); + [self fetch]; + } + } else { + failedDownload = YES; + [self setErrorMessage:NSLocalizedString(@"Unable to connect to server", @"error when pubmed connection fails")]; + } +} + +- (BDSKServerInfo *)serverInfo { return serverInfo; } + +- (void)setServerInfo:(BDSKServerInfo *)info { + if(serverInfo != info){ + serverInfo = [info copy]; + needsReset = YES; + } +} + +- (NSInteger)numberOfFetchedResults { return fetchedResults; } + +- (NSInteger)numberOfAvailableResults { return availableResults; } + +- (BOOL)failedDownload { return failedDownload; } + +- (BOOL)isRetrieving { return BDSKIdleState != downloadState; } + +- (NSFormatter *)searchStringFormatter { return nil; } + +#pragma mark URLs + +static inline NSString *escapeHost(NSString *string) { + return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]; +} + +static NSString *escapeDatabase(NSString *string) { + static NSCharacterSet *URLDatabaseOrNameAllowedCharacterSet = nil; + if (URLDatabaseOrNameAllowedCharacterSet == nil) { + NSMutableCharacterSet *tmpSet = [[NSCharacterSet URLPathAllowedCharacterSet] mutableCopy]; + [tmpSet removeCharactersInString:@"/"]; + URLDatabaseOrNameAllowedCharacterSet = [tmpSet copy]; + } + return [string stringByAddingPercentEncodingWithAllowedCharacters:URLDatabaseOrNameAllowedCharacterSet]; +} + +- (NSURLComponents *)baseComponents { + NSString *host = [[self serverInfo] host]; + NSInteger port = [[[self serverInfo] port] integerValue]; + NSString *database = [[self serverInfo] database]; + NSURLComponents *components = [[NSURLComponents alloc] init]; + [components setScheme:@"http"]; + [components setPercentEncodedHost:escapeHost(host)]; + if (port > 0) + [components setPort:[NSNumber numberWithInteger:port]]; + [components setPercentEncodedPath:[@"/" stringByAppendingString:escapeDatabase(database)]]; + return components; +} + +- (NSURL *)searchURL { + NSURLComponents *components = [self baseComponents]; + NSMutableArray *query = [NSMutableArray array]; + NSString *version = [[[self serverInfo] options] objectForKey:@"version"] ?: @"1.1"; + [query addObject:[@"version=" stringByAppendingString:version]]; + [query addObject:@"operation=searchRetrieve"]; + [query addObject:[@"query=" stringByAppendingString:[[self searchTerm] stringByAddingPercentEscapesForQueryTerm]]]; + [components setPercentEncodedQuery:[query componentsJoinedByString:@"&"]]; + return [components URL]; +} + +- (NSURL *)fetchURL { + NSURLComponents *components = [self baseComponents]; + NSMutableArray *query = [NSMutableArray array]; + NSString *version = [[[self serverInfo] options] objectForKey:@"version"] ?: @"1.1"; + [query addObject:[@"version=" stringByAppendingString:version]]; + [query addObject:@"operation=searchRetrieve"]; + [query addObject:[@"query=%@" stringByAppendingString:[[self searchTerm] stringByAddingPercentEscapesForQueryTerm]]]; + [query addObject:[NSString stringWithFormat:@"startRecord=%ld", 1 + [self numberOfFetchedResults]]]; + [query addObject:[NSString stringWithFormat:@"maximumRecords=%ld", MIN([self numberOfAvailableResults] - [self numberOfFetchedResults], MAX_RESULTS)]]; + [components setPercentEncodedQuery:[query componentsJoinedByString:@"&"]]; + return [components URL]; +} + +#pragma mark Search methods + +- (void)resetSearch { + [self reset]; + + if (NO == [NSString isEmptyString:[self searchTerm]]) { + // get the initial XML document with our search parameters in it + NSURL *initialURL = [self searchURL]; + BDSKPRECONDITION(initialURL); + + downloadState = BDSKSearchState; + [self startDownloadFromURL:initialURL]; + needsReset = NO; + } +} + +- (void)fetch { + if ([self numberOfAvailableResults] <= [self numberOfFetchedResults]) { + downloadState = BDSKIdleState; + [group addPublications:@[]]; + return; + } + + NSRange returnRange = NSMakeRange([self numberOfFetchedResults], MIN([self numberOfAvailableResults] - [self numberOfFetchedResults], MAX_RESULTS)); + + NSURL *theURL = [self fetchURL]; + BDSKPOSTCONDITION(theURL); + + requestedResults = returnRange.length; + + downloadState = BDSKFetchState; + [self startDownloadFromURL:theURL]; +} + +#pragma mark Downloading + +- (void)download:(BDSKDownload *)aDownload didCompleteWithError:(NSError *)error { + if (error) { + // calling -cancel may call this delegate method + if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) + return; + + downloadState = BDSKIdleState; + failedDownload = YES; + [self setErrorMessage:[error localizedDescription]]; + + download = nil; + + // redraw + [group addPublications:nil]; + return; + } + + failedDownload = NO; + NSError *presentableError; + NSData *data = [download data]; + + download = nil; + + switch (downloadState) { + case BDSKSearchState: + { + // okay to reset state before calling -fetch + downloadState = BDSKIdleState; + + // parse the result opf the search + NSXMLDocument *document = nil; + if (data) + document = [[NSXMLDocument alloc] initWithData:data options:NSXMLNodeOptionsNone error:NULL]; + + if (nil != document) { + NSXMLElement *root = [document rootElement]; + + // we need to extract WebEnv, Count, and QueryKey to construct our final URL + NSString *countString = [[[root nodesForXPath:@"/zs:searchRetrieveResponse[1]/zs:numberOfRecords[1]" error:NULL] lastObject] stringValue]; + + availableResults = [countString integerValue]; + limitResults = MIN(availableResults, fetchedResults + MAX_TOTAL_RESULTS); + + [self fetch]; + + } else { + + // no document, or zero length data from the server + failedDownload = YES; + [self setErrorMessage:NSLocalizedString(@"Unable to connect to server", @"")]; + + } + + break; + } + case BDSKFetchState: + { + + NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSArray *pubs = [BDSKMARCParser itemsFromString:string error:&presentableError]; + + // the number of returned results is not always equal to what we requested + // fetchedResults should track the requests to get the correct offset + fetchedResults += requestedResults; + + if (nil == pubs) { + failedDownload = YES; + [self setErrorMessage:[presentableError localizedDescription]]; + // set before addPublications: + downloadState = BDSKIdleState; + [group addPublications:nil]; + } + else if (limitResults > [self numberOfFetchedResults]) { + [group addPublications:pubs]; + [self fetch]; + } + else { + // set before addPublications: + downloadState = BDSKIdleState; + [group addPublications:pubs]; + } + break; + } + case BDSKIdleState: + break; + default: + [NSException raise:NSInternalInconsistencyException format:@"Unhandled case %ld", (long)downloadState]; + break; + } +} + +- (NSWindow *)downloadWindowForAuthenticationSheet:(BDSKDownload *)download { + return [group windowForSheetForObject:self]; +} + +- (void)startDownloadFromURL:(NSURL *)theURL { + NSURLRequest *request = [NSURLRequest requestWithURL:theURL]; + [download cancel]; + download = [[BDSKDownloader sharedDownloader] startDataDownloadWithRequest:request delegate:self]; +} + +@end Modified: trunk/bibdesk/BDSKSearchGroup.h =================================================================== --- trunk/bibdesk/BDSKSearchGroup.h 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/BDSKSearchGroup.h 2025-04-25 16:30:07 UTC (rev 29194) @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *BDSKSearchGroupEntrez; +extern NSString *BDSKSearchGroupSRU; extern NSString *BDSKSearchGroupZoom; extern NSString *BDSKSearchGroupISI; extern NSString *BDSKSearchGroupDBLP; Modified: trunk/bibdesk/BDSKSearchGroup.m =================================================================== --- trunk/bibdesk/BDSKSearchGroup.m 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/BDSKSearchGroup.m 2025-04-25 16:30:07 UTC (rev 29194) @@ -45,6 +45,7 @@ #import "BDSKServerInfo.h" #import "BDSKISIGroupServer.h" #import "BDSKDBLPGroupServer.h" +#import "BDSKSRUGroupServer.h" #import "BDSKGroup+Scripting.h" #import "BibItem.h" #import "BDSKStringConstants.h" @@ -54,6 +55,7 @@ #import "NSFileManager_BDSKExtensions.h" NSString *BDSKSearchGroupEntrez = @"entrez"; +NSString *BDSKSearchGroupSRU = @"sru"; NSString *BDSKSearchGroupZoom = @"zoom"; NSString *BDSKSearchGroupISI = @"isi"; NSString *BDSKSearchGroupDBLP = @"dblp"; @@ -386,6 +388,11 @@ aName = value; } else if ([key isEqualToString:@"database"]) { aDatabase = value; + } else if ([key isEqualToString:@"type"]) { + if ([value isCaseInsensitiveEqual:BDSKSearchGroupSRU]) + aType = BDSKSearchGroupSRU; + else if ([value isCaseInsensitiveEqual:BDSKSearchGroupZoom]) + aType = BDSKSearchGroupZoom; } else { if ([key isEqualToString:@"removeDiacritics"] || [key isEqualToString:@"lite"]) { if ([value boolValue] == NO) continue; @@ -405,6 +412,9 @@ [dictionary setValue:aHost forKey:@"host"]; [dictionary setValue:aPort forKey:@"port"]; [dictionary setValue:options forKey:@"options"]; + } else if ([aType isEqualToString:BDSKSearchGroupSRU]) { + [dictionary setValue:aHost forKey:@"host"]; + [dictionary setValue:aPort forKey:@"port"]; } else if ([aType isEqualToString:BDSKSearchGroupISI] && [options count] > 0) { [dictionary setValue:options forKey:@"options"]; } @@ -423,6 +433,8 @@ serverClass = [BDSKISIGroupServer class]; else if ([aType isEqualToString:BDSKSearchGroupDBLP]) serverClass = [BDSKDBLPGroupServer class]; + else if ([aType isEqualToString:BDSKSearchGroupSRU]) + serverClass = [BDSKSRUGroupServer class]; else BDSKASSERT_NOT_REACHED("unknown search group type"); return [[serverClass alloc] initWithGroup:group serverInfo:info]; Modified: trunk/bibdesk/BDSKSearchGroupSheetController.h =================================================================== --- trunk/bibdesk/BDSKSearchGroupSheetController.h 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/BDSKSearchGroupSheetController.h 2025-04-25 16:30:07 UTC (rev 29194) @@ -112,7 +112,9 @@ @property (nonatomic, getter=isEditable) BOOL editable; @property (nonatomic, readonly, getter=isZoom) BOOL zoom; @property (nonatomic, readonly, getter=isISI) BOOL ISI; +@property (nonatomic, readonly, getter=isSRU) BOOL SRU; @property (nonatomic, readonly, getter=isZoomOrISI) BOOL zoomOrISI; +@property (nonatomic, readonly, getter=isZoomOrSRU) BOOL zoomOrSRU; @property (nonatomic, strong) NSString *type; Modified: trunk/bibdesk/BDSKSearchGroupSheetController.m =================================================================== --- trunk/bibdesk/BDSKSearchGroupSheetController.m 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/BDSKSearchGroupSheetController.m 2025-04-25 16:30:07 UTC (rev 29194) @@ -54,13 +54,13 @@ @implementation BDSKSearchGroupSheetController @synthesize serverPopup, nameField, addressField, portField, databaseField, passwordField, userField, syntaxPopup, encodingComboBox, removeDiacriticsButton, liteButton, editButton, addRemoveButton, serverView, revealButton, okButton, cancelButton, bottomConstraint, objectController, custom, editable; -@dynamic zoom, ISI, zoomOrISI, typeTag, databases, serverInfo, undoManager; +@dynamic zoom, ISI, SRU, zoomOrISI, zoomOrSRU, typeTag, databases, serverInfo, undoManager; + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([[NSSet setWithObjects:@"type", nil] containsObject:key]) keyPaths = [keyPaths setByAddingObjectsFromSet:[NSSet setWithObjects:@"serverInfo", nil]]; - else if ([[NSSet setWithObjects:@"typeTag", @"zoom", @"ISI", @"zoomOrISI", @"databases", nil] containsObject:key]) + else if ([[NSSet setWithObjects:@"typeTag", @"zoom", @"ISI", @"SRU", @"zoomOrISI", @"zoomOrSRU", @"databases", nil] containsObject:key]) keyPaths = [keyPaths setByAddingObjectsFromSet:[NSSet setWithObjects:@"type", nil]]; return keyPaths; } @@ -314,8 +314,12 @@ - (BOOL)isISI { return [serverInfo isISI]; } +- (BOOL)isSRU { return [serverInfo isSRU]; } + - (BOOL)isZoomOrISI { return [serverInfo isZoom] || [serverInfo isISI]; } +- (BOOL)isZoomOrSRU { return [serverInfo isZoom] || [serverInfo isSRU]; } + - (BDSKServerInfo *)serverInfo { return serverInfo; } - (void)setServerInfo:(BDSKServerInfo *)info; @@ -345,6 +349,7 @@ case BDSKServerTypeZoom: [self setType:BDSKSearchGroupZoom]; break; case BDSKServerTypeISI: [self setType:BDSKSearchGroupISI]; break; case BDSKServerTypeDBLP: [self setType:BDSKSearchGroupDBLP]; break; + case BDSKServerTypeSRU: [self setType:BDSKSearchGroupSRU]; break; default: BDSKASSERT_NOT_REACHED("Unknown search type tag"); } } @@ -359,6 +364,7 @@ return entrezDatabases; } case BDSKServerTypeZoom: + case BDSKServerTypeSRU: return @[]; case BDSKServerTypeISI: { Modified: trunk/bibdesk/BDSKServerInfo.h =================================================================== --- trunk/bibdesk/BDSKServerInfo.h 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/BDSKServerInfo.h 2025-04-25 16:30:07 UTC (rev 29194) @@ -44,7 +44,8 @@ BDSKServerTypeEntrez, BDSKServerTypeZoom, BDSKServerTypeDBLP, - BDSKServerTypeISI + BDSKServerTypeISI, + BDSKServerTypeSRU }; @class BDSKReadWriteLock; @@ -86,6 +87,7 @@ @property (nonatomic, readonly, getter=isZoom) BOOL zoom; @property (nonatomic, readonly, getter=isISI) BOOL ISI; @property (nonatomic, readonly, getter=isDBLP) BOOL DBLP; +@property (nonatomic, readonly, getter=isSRU) BOOL SRU; @property (nonatomic, readonly) BDSKServerType serverType; Modified: trunk/bibdesk/BDSKServerInfo.m =================================================================== --- trunk/bibdesk/BDSKServerInfo.m 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/BDSKServerInfo.m 2025-04-25 16:30:07 UTC (rev 29194) @@ -81,7 +81,7 @@ @implementation BDSKServerInfo @synthesize type, name, database; -@dynamic dictionaryValue, host, port, username, recordSyntax, resultEncoding, queryConfig, removeDiacritics, lite, options, entrez, zoom, ISI, DBLP, serverType, URLValue; +@dynamic dictionaryValue, host, port, username, recordSyntax, resultEncoding, queryConfig, removeDiacritics, lite, options, entrez, zoom, ISI, DBLP, SRU, serverType, URLValue; + (BOOL)accessInstanceVariablesDirectly { return NO; } @@ -88,12 +88,13 @@ + (instancetype)defaultServerInfoWithType:(NSString *)aType; { BOOL isZoom = [aType isEqualToString:BDSKSearchGroupZoom]; + BOOL isSRU = [aType isEqualToString:BDSKSearchGroupSRU]; return [[[self class] alloc] initWithType:aType name:DEFAULT_NAME database:DEFAULT_DATABASE - host:isZoom ? DEFAULT_HOST : nil - port:isZoom ? DEFAULT_PORT : nil + host:isZoom || isSRU ? DEFAULT_HOST : nil + port:isZoom || isSRU ? DEFAULT_PORT : nil options:isZoom ? @{} : nil]; } @@ -188,9 +189,12 @@ isEqualOrBothNil([self database], [other database]) == NO) isEqual = NO; else if ([self isZoom]) - isEqual = isEqualOrBothNil([self host], [other host]) && - isEqualOrBothNil([self port], [(BDSKServerInfo *)other port]) && + isEqual = isEqualOrBothNil([self host], [other host]) && + isEqualOrBothNil([self port], [(BDSKServerInfo *)other port]) && isEqualOrBothEmpty(options, [(BDSKServerInfo *)other options]); + else if ([self isSRU]) + isEqual = isEqualOrBothNil([self host], [other host]) && + isEqualOrBothNil([self port], [(BDSKServerInfo *)other port]); else if ([self isISI]) isEqual = isEqualOrBothEmpty(options, [(BDSKServerInfo *)other options]); return isEqual; @@ -199,7 +203,7 @@ - (NSUInteger)hash { NSUInteger prime = 31; NSUInteger hash = prime * [[self type] hash] + [[self database] hash]; - if ([self isZoom]) { + if ([self isZoom] || [self isSRU]) { hash = prime * hash + [[self host] hash]; hash = prime * hash + [[self port] hash]; } @@ -220,6 +224,9 @@ [info setValue:[self host] forKey:HOST_KEY]; [info setValue:[self port] forKey:PORT_KEY]; [info setValue:[self options] forKey:OPTIONS_KEY]; + } else if ([self isSRU]) { + [info setValue:[self host] forKey:HOST_KEY]; + [info setValue:[self port] forKey:PORT_KEY]; } else if ([self isISI] && [options count] > 0) { [info setValue:[self options] forKey:OPTIONS_KEY]; } @@ -226,9 +233,9 @@ return info; } -- (NSString *)host { return [self isZoom] ? host : nil; } +- (NSString *)host { return [self isZoom] || [self isSRU] ? host : nil; } -- (NSString *)port { return [self isZoom] ? port : nil; } +- (NSString *)port { return [self isZoom] || [self isSRU] ? port : nil; } - (NSString *)username { return [options objectForKey:USERNAME_KEY]; } @@ -250,6 +257,7 @@ - (BOOL)isZoom { return [[self type] isEqualToString:BDSKSearchGroupZoom]; } - (BOOL)isISI { return [[self type] isEqualToString:BDSKSearchGroupISI]; } - (BOOL)isDBLP { return [[self type] isEqualToString:BDSKSearchGroupDBLP]; } +- (BOOL)isSRU { return [[self type] isEqualToString:BDSKSearchGroupSRU]; } - (BDSKServerType)serverType { if ([self isEntrez]) @@ -260,6 +268,8 @@ return BDSKServerTypeISI; if ([self isDBLP]) return BDSKServerTypeDBLP; + if ([self isSRU]) + return BDSKServerTypeSRU; BDSKASSERT_NOT_REACHED("Unknown search type"); return BDSKServerTypeEntrez; } @@ -337,7 +347,7 @@ NSString *username = [self username]; if (username) [components setPercentEncodedUser:escapeUser(username)]; - if ([self isZoom]) { + if ([self isZoom] || [self isSRU]) { [components setPercentEncodedHost:escapeHost([self host])]; [components setPort:[NSNumber numberWithInteger:[[self port] integerValue]]]; } else { @@ -357,6 +367,8 @@ [components setPercentEncodedQuery:[query componentsJoinedByString:@"&"]]; } else if ([self isISI] && [self isLite]) { [components setPercentEncodedQuery:@"lite=1"]; + } else if ([self isSRU]) { + [components setPercentEncodedQuery:@"type=sru"]; } NSURL *url = [components URL]; return url; @@ -396,6 +408,11 @@ [self setPort:DEFAULT_PORT]; password = nil; options = [[NSMutableDictionary alloc] init]; + } else if ([self isSRU]) { + if (host == nil) + [self setHost:DEFAULT_HOST]; + if (port == nil) + [self setPort:DEFAULT_PORT]; } else { password = nil; options = nil; @@ -495,7 +512,7 @@ - (BOOL)validateHost:(id *)value error:(NSError **)error { NSString *string = *value; - if ([self isZoom]) { + if ([self isZoom] || [self isSRU]) { NSRange range = [string rangeOfString:@"://"]; if(range.location != NSNotFound){ // ZOOM gets confused when the host has a protocol Modified: trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib =================================================================== --- trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/Base.lproj/BDSKSearchGroupSheet.xib 2025-04-25 16:30:07 UTC (rev 29194) @@ -130,7 +130,7 @@ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> </textFieldCell> <connections> - <binding destination="-2" name="hidden" keyPath="zoom" id="960"> + <binding destination="-2" name="hidden" keyPath="zoomOrSRU" id="mrZ-fQ-Vvk"> <dictionary key="options"> <string key="NSValueTransformerName">NSNegateBoolean</string> </dictionary> @@ -145,7 +145,7 @@ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> </textFieldCell> <connections> - <binding destination="-2" name="hidden" keyPath="zoom" id="958"> + <binding destination="-2" name="hidden" keyPath="zoomOrSRU" id="fKC-PK-0y5"> <dictionary key="options"> <string key="NSValueTransformerName">NSNegateBoolean</string> </dictionary> @@ -276,6 +276,11 @@ <connections> <accessibilityConnection property="title" destination="191" id="525"/> <binding destination="-2" name="enabled" keyPath="editable" id="468"/> + <binding destination="-2" name="hidden" keyPath="zoomOrSRU" previousBinding="979" id="zmn-bc-vkQ"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSNegateBoolean</string> + </dictionary> + </binding> <binding destination="569" name="value" keyPath="selection.port" id="578"> <dictionary key="options"> <integer key="NSConditionallySetsEditable" value="0"/> @@ -291,11 +296,6 @@ <integer key="NSNullPlaceholder" value="-1"/> </dictionary> </binding> - <binding destination="-2" name="hidden" keyPath="zoom" previousBinding="979" id="980"> - <dictionary key="options"> - <string key="NSValueTransformerName">NSNegateBoolean</string> - </dictionary> - </binding> <outlet property="nextKeyView" destination="201" id="293"/> </connections> </textField> @@ -376,6 +376,11 @@ <connections> <accessibilityConnection property="title" destination="196" id="524"/> <binding destination="-2" name="enabled" keyPath="editable" id="480"/> + <binding destination="-2" name="hidden" keyPath="zoomOrSRU" previousBinding="977" id="XIN-vT-kNF"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSNegateBoolean</string> + </dictionary> + </binding> <binding destination="569" name="value" keyPath="selection.host" id="576"> <dictionary key="options"> <integer key="NSConditionallySetsEditable" value="0"/> @@ -391,11 +396,6 @@ <integer key="NSNullPlaceholder" value="-1"/> </dictionary> </binding> - <binding destination="-2" name="hidden" keyPath="zoom" previousBinding="977" id="978"> - <dictionary key="options"> - <string key="NSValueTransformerName">NSNegateBoolean</string> - </dictionary> - </binding> <outlet property="nextKeyView" destination="198" id="212"/> </connections> </textField> Modified: trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj =================================================================== --- trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj 2025-04-24 15:59:23 UTC (rev 29193) +++ trunk/bibdesk/Bibdesk.xcodeproj/project.pbxproj 2025-04-25 16:30:07 UTC (rev 29194) @@ -469,6 +469,8 @@ CE3B5E7D09CEDE470017D339 /* BDSKMacroResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3B5E7B09CEDE470017D339 /* BDSKMacroResolver.m */; }; CE3B682B09D1B0190017D339 /* BDSKImagePopUpButton.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3B682709D1B0190017D339 /* BDSKImagePopUpButton.m */; }; CE3B682D09D1B0190017D339 /* BDSKImagePopUpButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3B682909D1B0190017D339 /* BDSKImagePopUpButtonCell.m */; }; + CE3D27C82DBB85AA002EA644 /* BDSKSRUGroupServer.h in Headers */ = {isa = PBXBuildFile; fileRef = CE3D27C62DBB85AA002EA644 /* BDSKSRUGroupServer.h */; }; + CE3D27C92DBB85AA002EA644 /* BDSKSRUGroupServer.m in Sources */ = {isa = PBXBuildFile; fileRef = CE3D27C72DBB85AA002EA644 /* BDSKSRUGroupServer.m */; }; CE4241360D0EAE0B00F824E7 /* BDSKEditorTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE4241340D0EAE0B00F824E7 /* BDSKEditorTableView.m */; }; CE4241450D0EAEFE00F824E7 /* BDSKEditorTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = CE4241430D0EAEFE00F824E7 /* BDSKEditorTextField.m */; }; CE424A450D0F123500F824E7 /* BDSKCompletionServerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = CE3A0BD50B1634D500233208 /* BDSKCompletionServerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1545,6 +1547,8 @@ CE3B682709D1B0190017D339 /* BDSKImagePopUpButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BDSKImagePopUpButton.m; sourceTree = "<group>"; }; CE3B682809D1B0190017D339 /* BDSKImagePopUpButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BDSKImagePopUpButtonCell.h; sourceTree = "<group>"; }; CE3B682909D1B0190017D339 /* BDSKImagePopUpButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BDSKImagePopUpButtonCell.m; sourceTree = "<group>"; }; + CE3D27C62DBB85AA002EA644 /* BDSKSRUGroupServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BDSKSRUGroupServer.h; sourceTree = "<group>"; }; + CE3D27C72DBB85AA002EA644 /* BDSKSRUGroupServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BDSKSRUGroupServer.m; sourceTree = "<group>"; }; CE3D8DA0125E69BB00AE0232 /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/BibTeXKeys.strings; sourceTree = "<group>"; }; CE3D8DA5125E69BB00AE0232 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = de; path = de.lproj/Credits.rtf; sourceTree = "<group>"; }; CE3D8DA9125E69BB00AE0232 /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; }; @@ -3171,6 +3175,8 @@ F94DB0F70B3E2FA1006F37A2 /* BDSKSearchGroup.m */, F92EF32109E6242100A244D0 /* BDSKSharedGroup.m */, CEFDBDBC0AEA86BA009EE99D /* BDSKSmartGroup.m */, + CE3D27C62DBB85AA002EA644 /* BDSKSRUGroupServer.h */, + CE3D27C72DBB85AA002EA644 /* BDSKSRUGroupServer.m */, CEFDBDC40AEA86F0009EE99D /* BDSKStaticGroup.m */, F9F5ECD00AE5E7C8007EBB31 /* BDSKURLGroup.m */, 4575382B0B70170D00C0E49B /* BDSKWebGroup.m */, @@ -3762,6 +3768,7 @@ CE2A09F9224599E100A8F31C /* BDSKFileContentSearchController.h in Headers */, CE1C94E926A9CDFB00EF17E8 /* BDSKGroupTextFieldCell.h in Headers */, CE2A0A46224599F600A8F31C /* BDSKPreferenceWindow.h in Headers */, + CE3D27C82DBB85AA002EA644 /* BDSKSRUGroupServer.h in Headers */, CE2A0A9722459A3600A8F31C /* BDSKTypeTemplate.h in Headers */, CE2A0A3A224599F600A8F31C /* BDSKOwnerProtocol.h in Headers */, CE2A0A4C224599F600A8F31C /* BDSKPubMedXMLParser.h in Headers */, @@ -5022,6 +5029,7 @@ CE2F056911B517E9001B0AE0 /* BDSKOpenAccessoryViewController.m in Sources */, CEFEF0ED11C23F1F0050350D /* BDSKCompletionServer.m in Sources */, CEFE21321259E6CB00DAD553 /* BDSKSearchGroupServerManager.m in Sources */, + CE3D27C92DBB85AA002EA644 /* BDSKSRUGroupServer.m in Sources */, CE044A4512667EB500CE55C4 /* BDSKDownloadManager.m in Sources */, CEF7F42712743BCC00B20881 /* BDSKWebView.m in Sources */, CE03C0CB127D751D00F62F51 /* BDSKWebViewModalDialogController.m in Sources */, This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. _______________________________________________ Bibdesk-commit mailing list Bibdesk-commit@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/bibdesk-commit