Revision: 26846 http://sourceforge.net/p/bibdesk/svn/26846 Author: hofman Date: 2021-09-11 17:59:22 +0000 (Sat, 11 Sep 2021) Log Message: ----------- Move alias and bookmark based linked file to separate subclasses of BDSKLinkedAliasFile. Redirect (un)archivers to the correct classes. Implementation based methods, such as URL and reference updating, are done in the subclasses, generic methods in BDSKLinkedAliasFile. Let bookmarked based class use path based fileURL as primary reference, and the file reference URL as secondary to find back moved files or notice replacements.
Modified Paths: -------------- trunk/bibdesk/BDSKLinkedFile.m trunk/bibdesk/BibDocument.m Modified: trunk/bibdesk/BDSKLinkedFile.m =================================================================== --- trunk/bibdesk/BDSKLinkedFile.m 2021-09-11 14:09:28 UTC (rev 26845) +++ trunk/bibdesk/BDSKLinkedFile.m 2021-09-11 17:59:22 UTC (rev 26846) @@ -74,15 +74,6 @@ #pragma mark - -@interface BDSKFileRef : NSObject { - const FSRef *fsRef; -} -- (id)initWithFSRef:(FSRef *)aRef; -@property (nonatomic, readonly) const FSRef *fsRef; -@end - -#pragma mark - - // Private placeholder subclass @interface BDSKPlaceholderLinkedFile : BDSKLinkedFile @@ -94,7 +85,6 @@ @interface BDSKLinkedAliasFile : BDSKLinkedFile { id alias; // can be a BDSKAlias or bookmark NSData - id fileRef; // can be a BDSKFileRef or a file reference NSURL NSString *relativePath; NSURL *fileURL; BOOL isInitial; @@ -102,13 +92,24 @@ BOOL hasSkimNotesNeedsUpdate; id delegate; } +- (void)updateFileURL; +- (NSData *)copyAliasDataRelativeToPath:(NSString *)newBasePath; +@end +#pragma mark - + +@interface BDSKAliasLinkedFile : BDSKLinkedAliasFile { + const FSRef *fileRef; +} - (void)updateFileRef; -- (void)updateFileURL; +@end -- (NSData *)copyAliasDataRelativeToPath:(NSString *)newBasePath; +#pragma mark - + +@interface BDSKBookmarkLinkedFile : BDSKLinkedAliasFile { + NSURL *fileRefURL; +} - (NSData *)copyBookmarkDataRelativeToPath:(NSString *)newBasePath; - @end #pragma mark - @@ -139,6 +140,10 @@ saveRelativePathOnly = [[NSUserDefaults standardUserDefaults] boolForKey:BDSKSaveLinkedFilesAsRelativePathOnlyKey]; saveArchivedData = [[NSUserDefaults standardUserDefaults] boolForKey:BDSKSaveLinkedFilesAsArchivedDataKey]; wantsBookmark = saveArchivedData == NO && [[NSUserDefaults standardUserDefaults] boolForKey:BDSKSaveLinkedFilesAsBookmarkDataKey]; + + NSString *fileClassName = wantsBookmark ? @"BDSKBookmarkLinkedFile" : @"BDSKAliasLinkedFile"; + [NSKeyedUnarchiver setClass:NSClassFromString(fileClassName) forClassName:@"BDSKLinkedAliasFile"]; + [NSUnarchiver decodeClassName:@"BDSKLinkedAliasFile" asClassName:fileClassName]; } + (id)allocWithZone:(NSZone *)aZone { @@ -246,7 +251,7 @@ - (id)initWithURL:(NSURL *)aURL delegate:(id<BDSKLinkedFileDelegate>)aDelegate { if([aURL isFileURL]) - return (id)[[BDSKLinkedAliasFile alloc] initWithURL:aURL delegate:aDelegate]; + return (id)[(wantsBookmark ? [BDSKBookmarkLinkedFile alloc] : [BDSKAliasLinkedFile alloc]) initWithURL:aURL delegate:aDelegate]; else if (aURL) return (id)[[BDSKLinkedURL alloc] initWithURL:aURL delegate:aDelegate]; else @@ -254,7 +259,7 @@ } - (id)initWithBase64String:(NSString *)base64String delegate:(id<BDSKLinkedFileDelegate>)aDelegate { - return (id)[[BDSKLinkedAliasFile alloc] initWithBase64String:base64String delegate:aDelegate]; + return (id)[(wantsBookmark ? [BDSKBookmarkLinkedFile alloc] : [BDSKAliasLinkedFile alloc]) initWithBase64String:base64String delegate:aDelegate]; } - (id)initWithURLString:(NSString *)aString { @@ -287,7 +292,6 @@ } else { self = [super init]; if (self) { - fileRef = nil; // this is updated lazily, as we don't know the base path at this point alias = [anAlias retain]; relativePath = [relPath copy]; delegate = aDelegate; @@ -301,24 +305,8 @@ } - (id)initWithURL:(NSURL *)aURL delegate:(id<BDSKLinkedFileDelegate>)aDelegate { - BDSKASSERT([aURL isFileURL]); - - NSString *aPath = [aURL path]; - - BDSKASSERT(nil != aPath); - - NSString *basePath = [aDelegate basePathForLinkedFile:self]; - NSString *relPath = basePath ? [aPath relativePathFromPath:basePath] : nil; - id anAlias; - if (wantsBookmark) - anAlias = BDSKCreateBookmarkDataFromURL(aURL); - else - anAlias = [BDSKAlias newWithPath:aPath basePath:basePath]; - self = [self initWithAlias:anAlias relativePath:relPath delegate:aDelegate]; - [anAlias release]; - if (self && basePath) - [self updateFileRef]; - return self; + BDSKASSERT_NOT_REACHED("Attempt to initialize BDSKLinkedAliasFile directly with a URL"); + return nil; } - (id)initWithBase64String:(NSString *)base64String delegate:(id<BDSKLinkedFileDelegate>)aDelegate { @@ -385,6 +373,11 @@ } - (id)initWithCoder:(NSCoder *)coder { + if ([self isMemberOfClass:[BDSKLinkedAliasFile class]]) { + BDSKASSERT_NOT_REACHED("Attempt to decode a BDSKLinkedAliasFile instance"); + [self release]; + self = wantsBookmark ? [BDSKBookmarkLinkedFile alloc] : [BDSKAliasLinkedFile alloc]; + } id anAlias = nil; NSString *relPath = nil; if ([coder allowsKeyedCoding]) { @@ -404,33 +397,23 @@ - (void)encodeWithCoder:(NSCoder *)coder { // make sure the fileRef is valid [self updateFileURL]; - NSData *data = nil; - NSString *basePath = [delegate basePathForLinkedFile:self]; if ([coder allowsKeyedCoding]) { - if (wantsBookmark) { - data = [self copyBookmarkDataRelativeToPath:basePath]; - if (data) - [coder encodeObject:alias forKey:BOOKMARK_KEY]; - else if ([alias isKindOfClass:[BDSKAlias class]] && (data = [alias copyData])) - [coder encodeObject:data forKey:ALIASDATA_KEY]; - } else { - data = [self copyAliasDataRelativeToPath:basePath]; - if (data) - [coder encodeObject:data forKey:ALIASDATA_KEY]; - else if ([alias isKindOfClass:[NSData class]]) - [coder encodeObject:alias forKey:BOOKMARK_KEY]; - } + // the alias or bookmark data is set by the subclasses [coder encodeObject:relativePath forKey:RELATIVEPATH_KEY]; } else { - data = [self copyAliasDataRelativeToPath:basePath]; + NSData *data = [self copyAliasDataRelativeToPath:[delegate basePathForLinkedFile:self]]; [coder encodeObject:data]; [coder encodeObject:relativePath]; + [data release]; } - [data release]; } +// always encode subclasses as BDSKLinkedAliasFile +- (Class)classForKeyedArchiver { return [BDSKLinkedAliasFile class]; } +- (Class)classForArchiver { return [BDSKLinkedAliasFile class]; } +- (Class)classForPortCoder { return [BDSKLinkedAliasFile class]; } + - (void)dealloc { - BDSKDESTROY(fileRef); BDSKDESTROY(alias); BDSKDESTROY(relativePath); BDSKDESTROY(fileURL); @@ -440,21 +423,11 @@ - (id)copyWithZone:(NSZone *)aZone { // make sure the fileRef is valid [self updateFileURL]; - id anAlias = nil; - if (wantsBookmark) { - anAlias = [self copyBookmarkDataRelativeToPath:[delegate basePathForLinkedFile:self]]; - if (anAlias == nil && [alias isKindOfClass:[BDSKAlias class]]) { - NSData *data = [alias copyData]; - anAlias = [BDSKAlias newWithData:data]; - [data release]; - } - } else { - NSData *data = [self copyAliasDataRelativeToPath:[delegate basePathForLinkedFile:self]]; - anAlias = [BDSKAlias newWithData:data]; - [data release]; - if (anAlias == nil && [alias isKindOfClass:[NSData class]]) - anAlias = [alias copy]; - } + NSData *data = [self copyAliasDataRelativeToPath:[delegate basePathForLinkedFile:self]]; + id anAlias = [BDSKAlias newWithData:data]; + [data release]; + if (anAlias == nil && [alias isKindOfClass:[NSData class]]) + anAlias = [alias copy]; self = [[[self class] allocWithZone:aZone] initWithAlias:anAlias relativePath:relativePath delegate:delegate]; [anAlias release]; return self; @@ -499,25 +472,179 @@ } } -- (NSURL *)newPathURL { - if (fileRef == nil) - return nil; - else if (wantsBookmark) - return (NSURL *)CFURLCreateFilePathURL(kCFAllocatorDefault, (CFURLRef)fileRef, NULL); +// this is really implemented by the subclasses +- (void)updateFileURL { + if (fileURL == nil && relativePath) { + NSString *basePath = [delegate basePathForLinkedFile:self]; + if (basePath) + fileURL = [[NSURL alloc] initFileURLWithPath:[relativePath isAbsolutePath] ? relativePath : [[basePath stringByAppendingPathComponent:relativePath] stringByStandardizingPath]]; + } +} + +- (NSURL *)URL { + [self updateFileURL]; + return fileURL; +} + +- (NSURL *)displayURL { + NSURL *displayURL = [self URL]; + if (displayURL == nil && relativePath) { + NSString *basePath = [delegate basePathForLinkedFile:self]; + displayURL = basePath ? [NSURL URLWithString:[relativePath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]] relativeToURL:[NSURL fileURLWithPath:basePath isDirectory:YES]] : [NSURL fileURLWithPath:relativePath isDirectory:NO]; + } + return displayURL; +} + +- (BOOL)hasSkimNotes { + if (hasSkimNotesNeedsUpdate) { + hasSkimNotes = [[fileURL SkimNotes] count] > 0; + hasSkimNotesNeedsUpdate = NO; + } + return hasSkimNotes; +} + +- (NSData *)copyAliasDataRelativeToPath:(NSString *)basePath { + BDSKAlias *anAlias = NULL; + NSData *data = nil; + + if (fileURL) { + anAlias = [BDSKAlias newWithPath:[fileURL path] basePath:basePath]; + } else if (relativePath && basePath) { + NSString *path = [relativePath isAbsolutePath] ? relativePath : [[basePath stringByAppendingPathComponent:relativePath] stringByStandardizingPath]; + anAlias = [BDSKAlias newWithPath:path basePath:basePath]; + } + if (anAlias != NULL) { + data = [anAlias copyData]; + [anAlias release]; + } else if ([alias isKindOfClass:[BDSKAlias class]]) { + data = [alias copyData]; + } + + return data; +} + +- (NSData *)copyDataRelativeToPath:(NSString *)newBasePath isBookmark:(BOOL *)isBookmark { + NSData *data = [self copyAliasDataRelativeToPath:newBasePath]; + if (data) { + *isBookmark = NO; + } else if ([alias isKindOfClass:[NSData class]]) { + data = [alias retain]; + *isBookmark = YES; + } + return data; +} + +- (NSString *)stringRelativeToPath:(NSString *)newBasePath { + BOOL noAlias = saveRelativePathOnly && newBasePath != nil; + if (newBasePath == nil) + newBasePath = [delegate basePathForLinkedFile:self]; + NSDictionary *dictionary = nil; + // this will make sure the fileURL is valid + NSString *path = [self path]; + path = path && newBasePath ? [path relativePathFromPath:newBasePath] : relativePath; + if (noAlias == NO || path == nil) { + BOOL isBookmark = NO; + NSData *data = [self copyDataRelativeToPath:newBasePath isBookmark:&isBookmark]; + if (data) { + dictionary = [NSDictionary dictionaryWithObjectsAndKeys:data, (isBookmark ? BOOKMARK_KEY : ALIASDATA_KEY), path, RELATIVEPATH_KEY, nil]; + [data release]; + } + } + if (dictionary == nil) + dictionary = [NSDictionary dictionaryWithObjectsAndKeys:path, RELATIVEPATH_KEY, nil]; + if (saveArchivedData) + return [[NSKeyedArchiver archivedDataWithRootObject:dictionary] base64String]; else - return BDSKCreateURLFromFSRef([fileRef fsRef]); + return [[NSPropertyListSerialization dataWithPropertyList:dictionary format:NSPropertyListBinaryFormat_v1_0 options:0 error:NULL] base64String]; } +// This is called when the file is added (new or initially), +// the document URL changes, or with aPath != nil after an autofile +// implemented by the subclasses +- (void)updateWithPath:(NSString *)aPath {} + +- (void)updateHasSkimNotes { + hasSkimNotesNeedsUpdate = YES; + if (isInitial == NO) + [delegate performSelector:@selector(linkedFileURLChanged:) withObject:self afterDelay:0.0]; +} + +@end + +#pragma mark - + +@implementation BDSKAliasLinkedFile + +- (id)initWithURL:(NSURL *)aURL delegate:(id<BDSKLinkedFileDelegate>)aDelegate { + BDSKASSERT([aURL isFileURL]); + + NSString *aPath = [aURL path]; + + BDSKASSERT(nil != aPath); + + NSString *basePath = [aDelegate basePathForLinkedFile:self]; + NSString *relPath = basePath ? [aPath relativePathFromPath:basePath] : nil; + id anAlias = [BDSKAlias newWithPath:aPath basePath:basePath]; + self = [self initWithAlias:anAlias relativePath:relPath delegate:aDelegate]; + [anAlias release]; + if (self && basePath) + [self updateFileRef]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + if ([coder allowsKeyedCoding]) { + NSData *data = [self copyAliasDataRelativeToPath:[delegate basePathForLinkedFile:self]]; + if (data) + [coder encodeObject:data forKey:ALIASDATA_KEY]; + else if ([alias isKindOfClass:[NSData class]]) + [coder encodeObject:alias forKey:BOOKMARK_KEY]; + [data release]; + } +} + +- (void)dealloc { + BDSKZONEDESTROY(fileRef); + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)aZone { + // make sure the fileRef is valid + [self updateFileURL]; + NSData *data = [self copyAliasDataRelativeToPath:[delegate basePathForLinkedFile:self]]; + id anAlias = [BDSKAlias newWithData:data]; + [data release]; + if (anAlias == nil && [alias isKindOfClass:[NSData class]]) + anAlias = [alias copy]; + self = [[[self class] allocWithZone:aZone] initWithAlias:anAlias relativePath:relativePath delegate:delegate]; + [anAlias release]; + return self; +} + +- (void)setFileRef:(FSRef *)aRef { + BDSKZONEDESTROY(fileRef); + if (aRef != NULL) { + FSRef *newRef = (FSRef *)NSZoneMalloc([self zone], sizeof(FSRef)); + if (newRef) { + bcopy(aRef, newRef, sizeof(FSRef)); + fileRef = newRef; + } else { + [self release]; + self = nil; + } + } +} + - (void)updateAliasWithBaseRef:(FSRef *)baseRef { BDSKASSERT(fileRef != nil); BDSKASSERT(baseRef != NULL); - BDSKASSERT(wantsBookmark == NO); // update the alias if ([alias isKindOfClass:[BDSKAlias class]]) { - [alias updateWithFSRef:[fileRef fsRef] baseRef:baseRef]; + [alias updateWithFSRef:fileRef baseRef:baseRef]; } else { - BDSKAlias *anAlias = [BDSKAlias newWithFSRef:[fileRef fsRef] baseRef:baseRef]; + BDSKAlias *anAlias = [BDSKAlias newWithFSRef:fileRef baseRef:baseRef]; if (anAlias) { BDSKDESTROY(alias); alias = anAlias; @@ -525,130 +652,55 @@ } } -- (void)updateBookmark { - BDSKASSERT(fileURL != nil); - BDSKASSERT(wantsBookmark); - - // update the bookmark - NSData *data = BDSKCreateBookmarkDataFromURL(fileURL ?: [fileRef filePathURL]); - if (data) { - [alias release]; - alias = data; - } -} - - (void)updateFileRef { NSString *basePath = [delegate basePathForLinkedFile:self]; BOOL shouldUpdate = NO; - if (wantsBookmark) { - // using bookmark and NSURL - if (fileRef == nil) { - NSURL *refURL = nil; - - if (fileURL) { - refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)fileURL, NULL); - shouldUpdate = basePath != nil && refURL != nil; - } - - if (refURL == nil && basePath && relativePath) { - NSURL *aURL = [NSURL fileURLWithPath:[relativePath isAbsolutePath] ? relativePath : [basePath stringByAppendingPathComponent:relativePath] isDirectory:NO]; - refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)aURL, NULL); - shouldUpdate = refURL != nil; - } - - if (refURL == nil) { - if ([alias isKindOfClass:[NSData class]]) { - NSURL *aURL = (NSURL *)CFURLCreateByResolvingBookmarkData(kCFAllocatorDefault, (CFDataRef)alias, kCFURLBookmarkResolutionWithoutUIMask | kCFURLBookmarkResolutionWithoutMountingMask, NULL, NULL, (Boolean *)&shouldUpdate, NULL); - if (aURL) { - refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)aURL, NULL); - [aURL release]; - } - shouldUpdate = shouldUpdate && basePath != nil && refURL != nil; - } else if ([alias isKindOfClass:[BDSKAlias class]]) { - FSRef aRef, baseRef; - BOOL ignored, hasBaseRef = BDSKPathToFSRef(basePath, &baseRef); - if ([alias getFSRef:&aRef baseRef:hasBaseRef ? &baseRef : NULL shouldUpdate:&ignored]){ - NSURL *aURL = BDSKCreateURLFromFSRef(&aRef); - if (aURL) { - refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)aURL, NULL); - [aURL release]; - } - } - shouldUpdate = hasBaseRef && refURL != nil; - } - } - - if (refURL != nil) { - [fileRef release]; - fileRef = refURL; - } - } else if (relativePath == nil) { - shouldUpdate = basePath != nil; + FSRef baseRef; + BOOL hasBaseRef = BDSKPathToFSRef(basePath, &baseRef); + + if (fileRef == nil) { + FSRef aRef; + BOOL hasRef = NO; + + if (fileURL) { + hasRef = BDSKPathToFSRef([fileURL path], &aRef); + shouldUpdate = hasBaseRef && hasRef; } - if ((shouldUpdate || fileURL == nil) && fileRef != nil) { - NSURL *aURL = [self newPathURL]; - if (aURL != nil) { - if (fileURL == nil) - fileURL = [aURL retain]; - if (shouldUpdate) { - [self updateBookmark]; - [self setRelativePath:[aURL path] fromPath:basePath]; - } - [aURL release]; - } + if (hasRef == NO && hasBaseRef && relativePath) { + NSString *path = [relativePath isAbsolutePath] ? relativePath : [basePath stringByAppendingPathComponent:relativePath]; + shouldUpdate = hasRef = BDSKPathToFSRef(path, &aRef); } - } else { - // using AliasHandle and FSRef - FSRef baseRef; - BOOL hasBaseRef = BDSKPathToFSRef(basePath, &baseRef); - - if (fileRef == nil) { - FSRef aRef; - BOOL hasRef = NO; - - if (fileURL) { - hasRef = BDSKPathToFSRef([fileURL path], &aRef); + if (hasRef == NO) { + if ([alias isKindOfClass:[BDSKAlias class]]) { + hasRef = [alias getFSRef:&aRef baseRef:hasBaseRef ? &baseRef : NULL shouldUpdate:&shouldUpdate]; + shouldUpdate = (shouldUpdate || relativePath == nil) && hasBaseRef && hasRef; + } else if ([alias isKindOfClass:[NSData class]]) { + NSURL *aURL = [NSURL URLByResolvingBookmarkData:alias options:NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:NULL error:NULL]; + hasRef = BDSKPathToFSRef([aURL path], &aRef); shouldUpdate = hasBaseRef && hasRef; } - - if (hasRef == NO && hasBaseRef && relativePath) { - NSString *path = [relativePath isAbsolutePath] ? relativePath : [basePath stringByAppendingPathComponent:relativePath]; - shouldUpdate = hasRef = BDSKPathToFSRef(path, &aRef); - } - - if (hasRef == NO) { - if ([alias isKindOfClass:[BDSKAlias class]]) { - hasRef = [alias getFSRef:&aRef baseRef:hasBaseRef ? &baseRef : NULL shouldUpdate:&shouldUpdate]; - shouldUpdate = (shouldUpdate || relativePath == nil) && hasBaseRef && hasRef; - } else if ([alias isKindOfClass:[NSData class]]) { - NSURL *aURL = [NSURL URLByResolvingBookmarkData:alias options:NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:NULL error:NULL]; - hasRef = BDSKPathToFSRef([aURL path], &aRef); - shouldUpdate = hasBaseRef && hasRef; - } - } - - if (hasRef) { - [fileRef release]; - fileRef = [[BDSKFileRef alloc] initWithFSRef:&aRef]; - } - } else if (relativePath == nil) { - shouldUpdate = hasBaseRef; } - if ((shouldUpdate || fileURL == nil) && fileRef != nil) { - NSURL *aURL = [self newPathURL]; - if (aURL != nil) { - if (fileURL == nil) - fileURL = [aURL retain]; - if (shouldUpdate) { - [self updateAliasWithBaseRef:&baseRef]; - [self setRelativePath:[aURL path] fromPath:basePath]; - } - [aURL release]; + if (hasRef) { + [self setFileRef:&aRef]; + } + } else if (relativePath == nil) { + shouldUpdate = hasBaseRef; + } + + if ((shouldUpdate || fileURL == nil) && fileRef != nil) { + NSURL *aURL = BDSKCreateURLFromFSRef(fileRef); + if (aURL != nil) { + if (fileURL == nil) + fileURL = [aURL retain]; + if (shouldUpdate) { + [self updateAliasWithBaseRef:&baseRef]; + [self setRelativePath:[aURL path] fromPath:basePath]; } + [aURL release]; } } } @@ -658,51 +710,31 @@ NSURL *aURL = nil; if (fileRef != nil) { - aURL = [self newPathURL]; + aURL = BDSKCreateURLFromFSRef(fileRef); if (aURL == nil) // fileRef was invalid, try to update it - BDSKDESTROY(fileRef); + BDSKZONEDESTROY(fileRef); } if (aURL == nil) { // fileRef was nil or invalid [self updateFileRef]; - aURL = [self newPathURL]; + aURL = BDSKCreateURLFromFSRef(fileRef); } if ([aURL isEqual:fileURL] == NO && (aURL != nil || hadFileURL)) { - if (wantsBookmark) { - // using bookmark and NSURL - NSURL *refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)fileURL, NULL); - if (refURL) { - // the file was replaced, reference the replacement rather than the moved file - // this is what Dropbox does with file updates - [fileRef release]; - fileRef = refURL; - [aURL release]; - aURL = [self newPathURL]; - NSString *basePath = [delegate basePathForLinkedFile:self]; - if (basePath) { - [self updateBookmark]; - [self setRelativePath:[aURL path] fromPath:basePath]; - } + FSRef aRef; + if (BDSKPathToFSRef([fileURL path], &aRef)) { + // the file was replaced, reference the replacement rather than the moved file + // this is what Dropbox does with file updates + [self setFileRef:&aRef]; + [aURL release]; + aURL =BDSKCreateURLFromFSRef(fileRef); + NSString *basePath = [delegate basePathForLinkedFile:self]; + FSRef baseRef; + if (BDSKPathToFSRef(basePath, &baseRef)) { + [self updateAliasWithBaseRef:&baseRef]; + [self setRelativePath:[aURL path] fromPath:basePath]; } - } else { - // using AliasHandle and FSRef - FSRef aRef; - if (BDSKPathToFSRef([fileURL path], &aRef)) { - // the file was replaced, reference the replacement rather than the moved file - // this is what Dropbox does with file updates - [fileRef release]; - fileRef = [[BDSKFileRef alloc] initWithFSRef:&aRef]; - [aURL release]; - aURL = [self newPathURL]; - NSString *basePath = [delegate basePathForLinkedFile:self]; - FSRef baseRef; - if (BDSKPathToFSRef(basePath, &baseRef)) { - [self updateAliasWithBaseRef:&baseRef]; - [self setRelativePath:[aURL path] fromPath:basePath]; - } - } } [self setFileURL:aURL]; } @@ -710,38 +742,14 @@ isInitial = NO; } -- (NSURL *)URL { - [self updateFileURL]; - return fileURL; -} - -- (NSURL *)displayURL { - NSURL *displayURL = [self URL]; - if (displayURL == nil && relativePath) { - NSString *basePath = [delegate basePathForLinkedFile:self]; - displayURL = basePath ? [NSURL URLWithString:[relativePath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]] relativeToURL:[NSURL fileURLWithPath:basePath isDirectory:YES]] : [NSURL fileURLWithPath:relativePath isDirectory:NO]; - } - return displayURL; -} - -- (BOOL)hasSkimNotes { - if (hasSkimNotesNeedsUpdate) { - hasSkimNotes = [[fileURL SkimNotes] count] > 0; - hasSkimNotesNeedsUpdate = NO; - } - return hasSkimNotes; -} - - (NSData *)copyAliasDataRelativeToPath:(NSString *)basePath { BDSKAlias *anAlias = NULL; NSData *data = nil; - if (wantsBookmark && fileURL) { - anAlias = [BDSKAlias newWithPath:[fileURL path] basePath:basePath]; - } else if (wantsBookmark == NO && fileRef) { + if (fileRef) { FSRef baseRef; BOOL hasBaseRef = BDSKPathToFSRef(basePath, &baseRef); - anAlias = [BDSKAlias newWithFSRef:[fileRef fsRef] baseRef:hasBaseRef ? &baseRef : NULL]; + anAlias = [BDSKAlias newWithFSRef:fileRef baseRef:hasBaseRef ? &baseRef : NULL]; } else if (relativePath && basePath) { NSString *path = [relativePath isAbsolutePath] ? relativePath : [[basePath stringByAppendingPathComponent:relativePath] stringByStandardizingPath]; anAlias = [BDSKAlias newWithPath:path basePath:basePath]; @@ -756,59 +764,8 @@ return data; } -- (NSData *)copyBookmarkDataRelativeToPath:(NSString *)basePath { - NSData *data = nil; - - if (fileURL) { - data = BDSKCreateBookmarkDataFromURL(fileURL); - } else if (relativePath && basePath) { - NSURL *aURL = [NSURL fileURLWithPath:[relativePath isAbsolutePath] ? relativePath : [[basePath stringByAppendingPathComponent:relativePath] stringByStandardizingPath]]; - data = BDSKCreateBookmarkDataFromURL(aURL); - } - if (data == nil && [alias isKindOfClass:[NSData class]]) - data = [alias copy]; - - return data; -} - -- (NSString *)stringRelativeToPath:(NSString *)newBasePath { - BOOL noAlias = saveRelativePathOnly && newBasePath != nil; - if (newBasePath == nil) - newBasePath = [delegate basePathForLinkedFile:self]; - // this will make sure the fileRef is valid - NSString *path = [self path]; - NSData *data = nil; - NSString *dataKey = ALIASDATA_KEY; - path = path && newBasePath ? [path relativePathFromPath:newBasePath] : relativePath; - if (noAlias == NO || path == nil) { - if (wantsBookmark) { - data = [self copyBookmarkDataRelativeToPath:newBasePath]; - if (data) - dataKey = BOOKMARK_KEY; - else if ([alias isKindOfClass:[BDSKAlias class]]) - data = [alias copyData]; - } else { - data = [self copyAliasDataRelativeToPath:newBasePath]; - if (data == nil && [alias isKindOfClass:[NSData class]]) { - data = [alias retain]; - dataKey = BOOKMARK_KEY; - } - } - } - NSDictionary *dictionary = data ? [NSDictionary dictionaryWithObjectsAndKeys:data, dataKey, path, RELATIVEPATH_KEY, nil] : [NSDictionary dictionaryWithObjectsAndKeys:path, RELATIVEPATH_KEY, nil]; - [data release]; - if (saveArchivedData) - return [[NSKeyedArchiver archivedDataWithRootObject:dictionary] base64String]; - else - return [[NSPropertyListSerialization dataWithPropertyList:dictionary format:NSPropertyListBinaryFormat_v1_0 options:0 error:NULL] base64String]; -} - -- (void)setAliasOrBookmarkWithPath:(NSString *)aPath basePath:(NSString *)basePath { - id anAlias = nil; - if (wantsBookmark) - anAlias = BDSKCreateBookmarkDataFromURL([NSURL fileURLWithPath:aPath]); - else - anAlias = [BDSKAlias newWithPath:aPath basePath:basePath]; +- (void)setAliasWithPath:(NSString *)aPath basePath:(NSString *)basePath { + id anAlias = [BDSKAlias newWithPath:aPath basePath:basePath]; if (anAlias != nil) { id saveAlias = alias; alias = anAlias; @@ -832,29 +789,24 @@ // this does the updating if possible [self updateFileRef]; } else { - NSURL *aURL = [self newPathURL]; + NSURL *aURL = BDSKCreateURLFromFSRef(fileRef); if (aURL != nil) { // if the path has changed, updating will be done below if (basePath && (aPath == nil || [[aURL path] isEqualToString:aPath])) { - if (wantsBookmark) { - [self updateBookmark]; + FSRef baseRef; + if (BDSKPathToFSRef(basePath, &baseRef)) { + [self updateAliasWithBaseRef:&baseRef]; [self setRelativePath:[aURL path] fromPath:basePath]; - } else { - FSRef baseRef; - if (BDSKPathToFSRef(basePath, &baseRef)) { - [self updateAliasWithBaseRef:&baseRef]; - [self setRelativePath:[aURL path] fromPath:basePath]; - } } } [aURL release]; } else { // the fileRef was invalid, reset it and update - BDSKDESTROY(fileRef); + BDSKZONEDESTROY(fileRef); [self updateFileRef]; if (fileRef == nil && aPath) { // this can happen after an auto file to a volume, as the file is actually not moved but copied - [self setAliasOrBookmarkWithPath:aPath basePath:basePath]; + [self setAliasWithPath:aPath basePath:basePath]; if (basePath) [self setRelativePath:aPath fromPath:basePath]; } @@ -864,63 +816,238 @@ NSString *path = [self path]; if ([path isEqualToString:aPath] == NO) { BOOL needsUpdate = YES; - if (wantsBookmark) { - // using bookmark and NSURL - NSURL *refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:aPath], NULL); - if (refURL) { - NSURL *aURL = (NSURL *)CFURLCreateFilePathURL(kCFAllocatorDefault, (CFURLRef)refURL, NULL); - if ([path isEqualToString:[aURL path]]) { - [refURL release]; - needsUpdate = NO; - } else { - [fileRef release]; - fileRef = refURL; - [self setFileURL:aURL]; - } - [aURL release]; + FSRef aRef; + if (BDSKPathToFSRef(aPath, &aRef)) { + NSURL *aURL = BDSKCreateURLFromFSRef(&aRef); + if ([path isEqualToString:[aURL path]]) { + needsUpdate = NO; + } else { + [self setFileRef:&aRef]; + [self setFileURL:aURL]; } - if (needsUpdate) { - if (basePath) { - [self updateBookmark]; - [self setRelativePath:aPath fromPath:basePath]; - } else { - [self setAliasOrBookmarkWithPath:aPath basePath:basePath]; - } + [aURL release]; + } + if (needsUpdate) { + FSRef baseRef; + if (BDSKPathToFSRef(basePath, &baseRef)) { + [self updateAliasWithBaseRef:&baseRef]; + [self setRelativePath:aPath fromPath:basePath]; + } else { + [self setAliasWithPath:aPath basePath:basePath]; } - } else { - // using AliasHandle and FSRef - FSRef aRef; - if (BDSKPathToFSRef(aPath, &aRef)) { - NSURL *aURL = BDSKCreateURLFromFSRef(&aRef); - if ([path isEqualToString:[aURL path]]) { - needsUpdate = NO; - } else { - [fileRef release]; - fileRef = [[BDSKFileRef alloc] initWithFSRef:&aRef]; - [self setFileURL:aURL]; - } - [aURL release]; + } + } + } +} + +@end + +#pragma mark - + +@implementation BDSKBookmarkLinkedFile + +- (id)initWithURL:(NSURL *)aURL delegate:(id<BDSKLinkedFileDelegate>)aDelegate { + BDSKASSERT([aURL isFileURL]); + + NSString *aPath = [aURL path]; + + BDSKASSERT(nil != aPath); + + NSString *basePath = [aDelegate basePathForLinkedFile:self]; + NSString *relPath = basePath ? [aPath relativePathFromPath:basePath] : nil; + id anAlias = BDSKCreateBookmarkDataFromURL(aURL); + self = [self initWithAlias:anAlias relativePath:relPath delegate:aDelegate]; + [anAlias release]; + if (self && basePath) + [self updateFileURL]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + if ([coder allowsKeyedCoding]) { + NSData *data = [self copyBookmarkDataRelativeToPath:[delegate basePathForLinkedFile:self]]; + if (data) + [coder encodeObject:alias forKey:BOOKMARK_KEY]; + else if ([alias isKindOfClass:[BDSKAlias class]] && (data = [alias copyData])) + [coder encodeObject:data forKey:ALIASDATA_KEY]; + [data release]; + } +} + +- (void)dealloc { + BDSKDESTROY(fileRefURL); + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)aZone { + // make sure the fileRef is valid + [self updateFileURL]; + id anAlias = [self copyBookmarkDataRelativeToPath:[delegate basePathForLinkedFile:self]]; + if (anAlias == nil && [alias isKindOfClass:[BDSKAlias class]]) { + NSData *data = [alias copyData]; + anAlias = [BDSKAlias newWithData:data]; + [data release]; + } + self = [[[self class] allocWithZone:aZone] initWithAlias:anAlias relativePath:relativePath delegate:delegate]; + [anAlias release]; + return self; +} + +- (void)updateFileURL { + NSString *basePath = nil; + BOOL shouldUpdate = NO; + NSURL *aURL = nil; + NSURL *refURL = nil; + + if (fileURL) { + refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)fileURL, NULL); + + if (refURL == nil) { + // fileURL was invalid, try to update it + BDSKDESTROY(fileURL); + } else if ([fileRefURL isEqual:refURL] == NO) { + // file was replaced + [fileRefURL release]; + fileRefURL = refURL; + hasSkimNotesNeedsUpdate = YES; + if (isInitial) + [delegate linkedFileURLChanged:self]; + basePath = [delegate basePathForLinkedFile:self]; + shouldUpdate = YES; + } else { + BDSKDESTROY(refURL); + if (relativePath == nil) { + basePath = [delegate basePathForLinkedFile:self]; + shouldUpdate = YES; + } + } + } + + if (fileURL == nil) { + + if (fileRefURL) { + aURL = (NSURL *)CFURLCreateFilePathURL(kCFAllocatorDefault, (CFURLRef)fileRefURL, NULL); + shouldUpdate = aURL != nil; + } + + basePath = [delegate basePathForLinkedFile:self]; + + if (aURL == nil && basePath && relativePath) { + aURL = [[NSURL alloc] initFileURLWithPath:[relativePath isAbsolutePath] ? relativePath : [basePath stringByAppendingPathComponent:relativePath] isDirectory:NO]; + refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)aURL, NULL); + shouldUpdate = refURL != nil; + } + + if (refURL == nil && [alias isKindOfClass:[NSData class]]) { + aURL = (NSURL *)CFURLCreateByResolvingBookmarkData(kCFAllocatorDefault, (CFDataRef)alias, kCFURLBookmarkResolutionWithoutUIMask | kCFURLBookmarkResolutionWithoutMountingMask, NULL, NULL, (Boolean *)&shouldUpdate, NULL); + if (aURL) + refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)aURL, NULL); + shouldUpdate = shouldUpdate && aURL != nil; + } else if (aURL == nil && [alias isKindOfClass:[BDSKAlias class]]) { + FSRef aRef, baseRef; + BOOL ignored, hasBaseRef = BDSKPathToFSRef(basePath, &baseRef); + if ([alias getFSRef:&aRef baseRef:hasBaseRef ? &baseRef : NULL shouldUpdate:&ignored]){ + aURL = BDSKCreateURLFromFSRef(&aRef); + if (aURL) + refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)aURL, NULL); + } + shouldUpdate = aURL != nil; + } + + if (aURL) { + [self setFileURL:aURL]; + if (refURL) { + [fileRefURL release]; + fileRefURL = refURL; + } + [aURL release]; + } else { + BDSKDESTROY(fileRefURL); + } + } + + if (shouldUpdate) { + NSData *data = BDSKCreateBookmarkDataFromURL(fileURL ?: [fileRefURL filePathURL]); + if (data) { + [alias release]; + alias = data; + } + if (basePath) + [self setRelativePath:[fileURL path] fromPath:basePath]; + } +} + +- (NSData *)copyBookmarkDataRelativeToPath:(NSString *)basePath { + NSData *data = nil; + + if (fileURL) { + data = BDSKCreateBookmarkDataFromURL(fileURL); + } else if (relativePath && basePath) { + NSURL *aURL = [NSURL fileURLWithPath:[relativePath isAbsolutePath] ? relativePath : [[basePath stringByAppendingPathComponent:relativePath] stringByStandardizingPath]]; + data = BDSKCreateBookmarkDataFromURL(aURL); + } + if (data == nil && [alias isKindOfClass:[NSData class]]) + data = [alias copy]; + + return data; +} + +- (NSData *)copyDataRelativeToPath:(NSString *)newBasePath isBookmark:(BOOL *)isBookmark { + NSData *data = [self copyBookmarkDataRelativeToPath:newBasePath]; + if (data) { + *isBookmark = YES; + } else if ([alias isKindOfClass:[BDSKAlias class]]) { + data = [alias copyData]; + *isBookmark = NO; + } + return data; +} + +// This is called when the file is added (new or initially), +// the document URL changes, or with aPath != nil after an autofile +- (void)updateWithPath:(NSString *)aPath { + NSString *basePath = [delegate basePathForLinkedFile:self]; + + if (aPath) { + // this implicitly calls updateFileURL + NSString *path = [self path]; + if ([path isEqualToString:aPath] == NO) { + // this can happen when auto filing to a different volume, which copies the file + BOOL needsUpdate = YES; + NSURL *refURL = (NSURL *)CFURLCreateFileReferenceURL(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:aPath], NULL); + if (refURL) { + NSURL *aURL = (NSURL *)CFURLCreateFilePathURL(kCFAllocatorDefault, (CFURLRef)refURL, NULL); + if ([path isEqualToString:[aURL path]]) { + [refURL release]; + needsUpdate = NO; + } else { + [fileRefURL release]; + fileRefURL = refURL; + [self setFileURL:aURL]; } - if (needsUpdate) { - FSRef baseRef; - if (BDSKPathToFSRef(basePath, &baseRef)) { - [self updateAliasWithBaseRef:&baseRef]; - [self setRelativePath:aPath fromPath:basePath]; - } else { - [self setAliasOrBookmarkWithPath:aPath basePath:basePath]; - } + [aURL release]; + } + if (needsUpdate) { + NSData *data = BDSKCreateBookmarkDataFromURL(fileURL ?: [fileRefURL filePathURL]); + if (data) { + [alias release]; + alias = data; } + if (basePath) + [self setRelativePath:aPath fromPath:basePath]; } } + } else { + // this does the updating of the bookmark and URLs if needed + [self updateFileURL]; + if (fileURL && basePath) { + // the basePath may have changed, which may be noted by updateFileURL + [self setRelativePath:[fileURL path] fromPath:basePath]; + } } } -- (void)updateHasSkimNotes { - hasSkimNotesNeedsUpdate = YES; - if (isInitial == NO) - [delegate performSelector:@selector(linkedFileURLChanged:) withObject:self afterDelay:0.0]; -} - @end #pragma mark - @@ -1155,36 +1282,3 @@ #pragma clang diagnostic pop @end - -#pragma mark - - -@implementation BDSKFileRef - -@synthesize fsRef; - -- (id)initWithFSRef:(FSRef *)aRef { - if (aRef == NULL) { - [self release]; - self = nil; - } else { - self = [super init]; - if (self) { - FSRef *newRef = (FSRef *)NSZoneMalloc([self zone], sizeof(FSRef)); - if (newRef) { - bcopy(aRef, newRef, sizeof(FSRef)); - fsRef = newRef; - } else { - [self release]; - self = nil; - } - } - } - return self; -} - -- (void)dealloc { - BDSKZONEDESTROY(fsRef); - [super dealloc]; -} - -@end Modified: trunk/bibdesk/BibDocument.m =================================================================== --- trunk/bibdesk/BibDocument.m 2021-09-11 14:09:28 UTC (rev 26845) +++ trunk/bibdesk/BibDocument.m 2021-09-11 17:59:22 UTC (rev 26846) @@ -190,6 +190,12 @@ @synthesize documentWindow, tableView, splitView, mainBox, mainView, controlContentView, statusBar, groupOutlineView, groupSplitView, groupActionButton, groupAddButton, groupButtonView, groupFieldMenu, sidePreviewTabView, sidePreviewTextView, sideFileView, sidePreviewButton, sidePreviewButtonView, bottomPreviewTabView, bottomPreviewTextView, bottomFileView, bottomPreviewButton, actionMenuButton, groupActionMenuButton, searchField, groupMenu, actionMenu, alternateCopyMenu, sharingMenu, publications, shownPublications, groups, documentInfo, macroResolver; @dynamic mainWindowSetupDictionaryFromExtendedAttributes, mainDocument, atomData, MODSData, endNoteData, wordXMLData, numberOfSelectedPubs, numberOfClickedOrSelectedPubs, selectedPublications, clickedOrSelectedPublications, singleSelectedPublication, selectedFileURLs, clickedOrSelectedFileURLs, selectedRemoteURLs, clickedOrSelectedRemoteURLs, documentStringEncoding, sharingServices; ++ (void)initialize { + BDSKINITIALIZE; + // make sure the unarchiver redirects are installed + [BDSKLinkedFile class]; +} + + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"displayName"]) 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