diff --git a/Connection.xcodeproj/project.pbxproj b/Connection.xcodeproj/project.pbxproj index 3f146c054..60ca935fc 100644 --- a/Connection.xcodeproj/project.pbxproj +++ b/Connection.xcodeproj/project.pbxproj @@ -56,6 +56,8 @@ 22FEB6691680818800BB778B /* KMSTranscriptEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = 22FEB6671680818800BB778B /* KMSTranscriptEntry.m */; }; 271059521671334500E20511 /* DAVKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 27448C371458100D00EB086F /* DAVKit.framework */; }; 27105953167143D800E20511 /* CURLHandle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 220526E8165E96AA00A2BBC9 /* CURLHandle.framework */; }; + 273605911869A93600A32419 /* CK2Transcript.h in Headers */ = {isa = PBXBuildFile; fileRef = 2736058F1869A93600A32419 /* CK2Transcript.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 273605921869A93600A32419 /* CK2Transcript.m in Sources */ = {isa = PBXBuildFile; fileRef = 273605901869A93600A32419 /* CK2Transcript.m */; }; 27431CA01630381D00F6FB58 /* CK2FileProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 27431C9E1630381D00F6FB58 /* CK2FileProtocol.h */; }; 27431CA11630381D00F6FB58 /* CK2FileProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 27431C9F1630381D00F6FB58 /* CK2FileProtocol.m */; }; 2743E8091622E47600019979 /* CK2FileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2743E8071622E47600019979 /* CK2FileManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -340,6 +342,8 @@ 22FEB6671680818800BB778B /* KMSTranscriptEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KMSTranscriptEntry.m; sourceTree = ""; }; 22FEB6681680818800BB778B /* KMSTranscriptEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KMSTranscriptEntry.h; sourceTree = ""; }; 2702E4671459D0F50085BBC4 /* libssh2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssh2.dylib; path = CurlHandle/SFTP/libssh2.dylib; sourceTree = ""; }; + 2736058F1869A93600A32419 /* CK2Transcript.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2Transcript.h; sourceTree = ""; }; + 273605901869A93600A32419 /* CK2Transcript.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2Transcript.m; sourceTree = ""; }; 27431C9E1630381D00F6FB58 /* CK2FileProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileProtocol.h; sourceTree = ""; }; 27431C9F1630381D00F6FB58 /* CK2FileProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CK2FileProtocol.m; sourceTree = ""; }; 2743E8071622E47600019979 /* CK2FileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CK2FileManager.h; sourceTree = ""; }; @@ -529,6 +533,8 @@ 278D8B78167FF35D00622468 /* CK2Authentication.m */, 27993E8216FCB30D008DC1B0 /* CK2FileOperation.h */, 27993E8316FCB30D008DC1B0 /* CK2FileOperation.m */, + 2736058F1869A93600A32419 /* CK2Transcript.h */, + 273605901869A93600A32419 /* CK2Transcript.m */, ); name = Public; path = ConnectionKit; @@ -996,6 +1002,7 @@ 79CFD89509F704F400172CDD /* ConnectionKit.h in Headers */, 7983DF8E0B0C0FAC00F5078E /* CKTransferRecord.h in Headers */, 798313C90B0D67E000F5078E /* CKTransferProgressCell.h in Headers */, + 273605911869A93600A32419 /* CK2Transcript.h in Headers */, 27D03B421471787000FEA588 /* CKUploader.h in Headers */, 2743E8091622E47600019979 /* CK2FileManager.h in Headers */, 2790A8291627636E000C9D9F /* CK2Protocol.h in Headers */, @@ -1429,6 +1436,7 @@ 27F394F6162C162900944F43 /* CK2SFTPProtocol.m in Sources */, 27431CA11630381D00F6FB58 /* CK2FileProtocol.m in Sources */, 2288CD76165A99FC00F34E24 /* CK2WebDAVProtocol.m in Sources */, + 273605921869A93600A32419 /* CK2Transcript.m in Sources */, 27A2072C1671634800D8284D /* CK2CURLBasedProtocol.m in Sources */, 278D8B7A167FF35D00622468 /* CK2Authentication.m in Sources */, 27993E8516FCB30D008DC1B0 /* CK2FileOperation.m in Sources */, diff --git a/ConnectionKit/CK2CURLBasedProtocol.m b/ConnectionKit/CK2CURLBasedProtocol.m index 2ba2d837e..23d87c70f 100644 --- a/ConnectionKit/CK2CURLBasedProtocol.m +++ b/ConnectionKit/CK2CURLBasedProtocol.m @@ -112,6 +112,19 @@ - (BOOL)shouldEnumerateFilename:(NSString *)name options:(NSDirectoryEnumeration - (NSError*)processData:(NSMutableData*)data request:(NSURLRequest *)request url:(NSURL*)directoryURL path:(NSString*)directoryPath keys:(NSArray*)keys options:(NSDirectoryEnumerationOptions)mask { + NSString *listing = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (listing) + { + [self.client protocol:self appendStringToTranscript:listing isCommand:NO]; + } + else + { + listing = [[NSString alloc] initWithFormat:@"Unable to stringify listing: %@", data]; + [self.client protocol:self appendStringToTranscript:listing isCommand:NO]; + } + [listing release]; + + NSError* result = nil; // Process the data to make a directory listing @@ -685,26 +698,19 @@ - (void)transfer:(CURLTransfer *)transfer didCompleteWithError:(NSError *)error; - (void)transfer:(CURLTransfer *)transfer didReceiveDebugInformation:(NSString *)string ofType:(curl_infotype)type; { - CK2TranscriptType ckType; switch (type) { case CURLINFO_HEADER_IN: - ckType = CK2TranscriptHeaderIn; + [self.client protocol:self appendStringToTranscript:string isCommand:NO]; break; case CURLINFO_HEADER_OUT: - ckType = CK2TranscriptHeaderOut; - break; - - case CURLINFO_TEXT: - ckType = CK2TranscriptText; + [self.client protocol:self appendStringToTranscript:string isCommand:YES]; break; default: - return; + break; } - - [[self client] protocol:self appendString:string toTranscript:ckType]; } #pragma mark Customization diff --git a/ConnectionKit/CK2FileManager.h b/ConnectionKit/CK2FileManager.h index d1612624c..74bb4c702 100644 --- a/ConnectionKit/CK2FileManager.h +++ b/ConnectionKit/CK2FileManager.h @@ -432,21 +432,6 @@ typedef NS_ENUM(NSInteger, CK2AuthChallengeDisposition) { totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToSend; -typedef NS_ENUM(NSUInteger, CK2TranscriptType) { - CK2TranscriptText, - CK2TranscriptHeaderIn, - CK2TranscriptHeaderOut, -}; // deliberately aligned with curl_infotype for convenience - -/** - Reports received transcript info. - - @param manager The file manager. - @param info The received transcript line(s). Should end in a newline character. - @param transcript The type of transcript received. - */ -- (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; - /** * Sent as the last message related to a specific operation. Error may be * `nil`, which implies that no error occurred and this operation is finished. diff --git a/ConnectionKit/CK2FileOperation.m b/ConnectionKit/CK2FileOperation.m index 80bb35bcf..ba11442bf 100644 --- a/ConnectionKit/CK2FileOperation.m +++ b/ConnectionKit/CK2FileOperation.m @@ -8,6 +8,7 @@ #import "CK2FileOperation.h" #import "CK2Protocol.h" +#import "CK2Transcript.h" #import // so icon handling can use NSImage and NSWorkspace for now @@ -536,16 +537,12 @@ - (void)protocol:(CK2Protocol *)protocol didReceiveChallenge:(NSURLAuthenticatio // TODO: Cache credentials per protection space } -- (void)protocol:(CK2Protocol *)protocol appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; +- (void)protocol:(CK2Protocol *)protocol appendStringToTranscript:(NSString *)info isCommand:(BOOL)isCommand; { NSAssert(protocol == _protocol, @"Message received from unexpected protocol: %@ (should be %@)", protocol, _protocol); - // Pass straight onto delegate and trust it not to take too long handling it - // We used to dispatch off onto one of the global queues, but that does have the nasty downside of messages sometimes arriving out-of-order or concurrently - [self tryToMessageDelegateSelector:@selector(fileManager:appendString:toTranscript:) usingBlock:^(id delegate) { - [delegate fileManager:self.fileManager appendString:info toTranscript:transcript]; - }]; + [[CK2Transcript sharedTranscript] addEntryWithText:info isCommand:isCommand]; } - (void)protocol:(CK2Protocol *)protocol didDiscoverItemAtURL:(NSURL *)url; diff --git a/ConnectionKit/CK2OpenPanel.h b/ConnectionKit/CK2OpenPanel.h index ffdc6888f..fae4059ce 100644 --- a/ConnectionKit/CK2OpenPanel.h +++ b/ConnectionKit/CK2OpenPanel.h @@ -179,7 +179,6 @@ - (void)panelSelectionDidChange:(CK2OpenPanel *)sender; - (void)panel:(CK2OpenPanel *)sender didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential *))completionHandler; -- (void)panel:(CK2OpenPanel *)sender appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; - (void)panel:(CK2OpenPanel *)sender didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge __attribute((deprecated("implement -panel:didReceiveChallenge:completionHandler instead"))); diff --git a/ConnectionKit/CK2OpenPanelController.m b/ConnectionKit/CK2OpenPanelController.m index 210dd4b6f..c0b9a0689 100644 --- a/ConnectionKit/CK2OpenPanelController.m +++ b/ConnectionKit/CK2OpenPanelController.m @@ -1215,16 +1215,4 @@ - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)oper } } -- (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; -{ - id delegate; - - delegate = [[self openPanel] delegate]; - - if ([delegate respondsToSelector:@selector(panel:appendString:toTranscript:)]) - { - [delegate panel:[self openPanel] appendString:info toTranscript:transcript]; - } -} - @end diff --git a/ConnectionKit/CK2Protocol.h b/ConnectionKit/CK2Protocol.h index edb646053..b95012025 100644 --- a/ConnectionKit/CK2Protocol.h +++ b/ConnectionKit/CK2Protocol.h @@ -162,7 +162,7 @@ */ - (void)protocol:(CK2Protocol *)protocol didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(CK2AuthChallengeDisposition, NSURLCredential*))completionHandler; -- (void)protocol:(CK2Protocol *)protocol appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; +- (void)protocol:(CK2Protocol *)protocol appendStringToTranscript:(NSString *)info isCommand:(BOOL)isCommand; #pragma mark Operation-Specific diff --git a/ConnectionKit/CK2SFTPProtocol.m b/ConnectionKit/CK2SFTPProtocol.m index 2d448a0d8..4202cd7e2 100644 --- a/ConnectionKit/CK2SFTPProtocol.m +++ b/ConnectionKit/CK2SFTPProtocol.m @@ -53,6 +53,16 @@ + (NSString *)pathOfURLRelativeToHomeDirectory:(NSURL *)URL; #pragma mark Operations +- (id)initForEnumeratingDirectoryWithRequest:(NSURLRequest *)request includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask client:(id)client; +{ + if (self = [super initForEnumeratingDirectoryWithRequest:request includingPropertiesForKeys:keys options:mask client:client]) + { + NSString *path = [self.class pathOfURLRelativeToHomeDirectory:request.URL]; + _transcriptMessage = [[NSString alloc] initWithFormat:@"Listing %@", path]; + } + return self; +} + - (id)initForCreatingDirectoryWithRequest:(NSURLRequest *)request withIntermediateDirectories:(BOOL)createIntermediates openingAttributes:(NSDictionary *)attributes client:(id)client; { NSMutableURLRequest *mutableRequest = [request mutableCopy]; @@ -230,7 +240,7 @@ - (void)start; // Note what we're up to if (_transcriptMessage) { - [self.client protocol:self appendString:_transcriptMessage toTranscript:CK2TranscriptHeaderOut]; + [self.client protocol:self appendStringToTranscript:_transcriptMessage isCommand:YES]; [_transcriptMessage release]; _transcriptMessage = nil; } diff --git a/ConnectionKit/CK2Transcript.h b/ConnectionKit/CK2Transcript.h new file mode 100644 index 000000000..86764526a --- /dev/null +++ b/ConnectionKit/CK2Transcript.h @@ -0,0 +1,62 @@ +// +// CK2Transcript.h +// Connection +// +// Created by Mike on 24/12/2013. +// +// + +#import + + +/** + All safe to access from multiple threads. Be a bit wary that new entries might + arrive while removing existing ones though, if you have any UI code around it. + */ + + +@interface CK2TranscriptEntry : NSObject +{ + @private + NSString *_text; + BOOL _isCommand; +} + +@property(nonatomic, copy, readonly) NSString *text; +@property(nonatomic, readonly) BOOL isCommand; + +@end + + +@interface CK2Transcript : NSObject +{ + @private + NSMutableArray *_entries; + dispatch_queue_t _queue; +} + +#pragma mark Shared Transcript ++ (CK2Transcript *)sharedTranscript; + + +#pragma mark Retrieving Entries +- (NSArray *)entries; +- (NSUInteger)countOfEntries; +- (CK2TranscriptEntry *)entryAtIndex:(NSUInteger)index; + + +#pragma mark Adding Entries +- (void)addEntryWithText:(NSString *)text isCommand:(BOOL)command; + + +#pragma mark Removing Entries +- (void)removeAllEntries; + + +@end + + +/** + Posted on whichever thread modified the transcript + */ +extern NSString * const CK2TranscriptChangedNotification; diff --git a/ConnectionKit/CK2Transcript.m b/ConnectionKit/CK2Transcript.m new file mode 100644 index 000000000..0dbd7ca6a --- /dev/null +++ b/ConnectionKit/CK2Transcript.m @@ -0,0 +1,134 @@ +// +// CK2Transcript.m +// Connection +// +// Created by Mike on 24/12/2013. +// +// + +#import "CK2Transcript.h" + + +@implementation CK2TranscriptEntry + +- initWithText:(NSString *)text isCommand:(BOOL)isCommand; +{ + if (self = [self init]) + { + _text = [text copy]; + _isCommand = isCommand; + } + return self; +} + +- (void)dealloc; +{ + [_text release]; + [super dealloc]; +} + +@synthesize text = _text; +@synthesize isCommand = _isCommand; + +@end + + +@implementation CK2Transcript + ++ (CK2Transcript *)sharedTranscript; +{ + static CK2Transcript *transcript; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + transcript = [[CK2Transcript alloc] init]; + }); + return transcript; +} + +- init +{ + if (self = [super init]) + { + _queue = dispatch_queue_create("com.karelia.ConnectionKit.transcript", NULL); + } + return self; +} + +- (void)dealloc; +{ + [_entries release]; + dispatch_release(_queue); + + [super dealloc]; +} + +- (NSArray *)entries; +{ + __block NSArray *result; + dispatch_sync(_queue, ^{ + result = [_entries copy]; + }); + return [result autorelease]; +} + +- (NSUInteger)countOfEntries; +{ + __block NSUInteger result; + dispatch_sync(_queue, ^{ + result = _entries.count; + }); + return result; +} + +- (CK2TranscriptEntry *)entryAtIndex:(NSUInteger)index; +{ + __block CK2TranscriptEntry *result; + dispatch_sync(_queue, ^{ + result = [[_entries objectAtIndex:index] retain]; + }); + return [result autorelease]; +} + +- (void)addEntryWithText:(NSString *)text isCommand:(BOOL)command; +{ + dispatch_async(_queue, ^{ + + if (!_entries) + { + _entries = [[NSMutableArray alloc] init]; + + // Start off with details of the host machine + NSBundle *bundle = [NSBundle mainBundle]; + NSString *transcriptHeader = [NSString stringWithFormat: + @"%@ %@ (architecture unknown) Session Transcript [%@] (%@)", + [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey], + [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey], + [[NSProcessInfo processInfo] operatingSystemVersionString], + [NSDate date]]; + + CK2TranscriptEntry *entry = [[CK2TranscriptEntry alloc] initWithText:transcriptHeader isCommand:NO]; + [_entries addObject:entry]; + [entry release]; + } + + CK2TranscriptEntry *entry = [[CK2TranscriptEntry alloc] initWithText:text isCommand:command]; + [_entries addObject:entry]; + [entry release]; + }); + + [[NSNotificationCenter defaultCenter] postNotificationName:CK2TranscriptChangedNotification object:self]; +} + +- (void)removeAllEntries; +{ + dispatch_async(_queue, ^{ + [_entries removeAllObjects]; + }); + + [[NSNotificationCenter defaultCenter] postNotificationName:CK2TranscriptChangedNotification object:self]; +} + +@end + + +NSString * const CK2TranscriptChangedNotification = @"CK2TranscriptChanged"; diff --git a/ConnectionKit/CK2WebDAVProtocol.m b/ConnectionKit/CK2WebDAVProtocol.m index 6d12fd9fb..9cd5bdbe4 100644 --- a/ConnectionKit/CK2WebDAVProtocol.m +++ b/ConnectionKit/CK2WebDAVProtocol.m @@ -412,7 +412,7 @@ - (void)webDAVSession:(DAVSession *)session appendStringToTranscript:(NSString * // Tack on a newline to match libcurl output string = [string stringByAppendingString:@"\n"]; - [[self client] protocol:self appendString:string toTranscript:(sent ? CK2TranscriptHeaderOut : CK2TranscriptHeaderIn)]; + [[self client] protocol:self appendStringToTranscript:string isCommand:sent]; } #pragma mark - Utilities diff --git a/ConnectionKit/CKUploader.h b/ConnectionKit/CKUploader.h index 86d4041f9..2e6490f32 100644 --- a/ConnectionKit/CKUploader.h +++ b/ConnectionKit/CKUploader.h @@ -91,8 +91,6 @@ typedef NSUInteger CKUploadingOptions; @required - (void)uploader:(CKUploader *)uploader didBeginUploadToPath:(NSString *)path; -- (void)uploader:(CKUploader *)uploader appendString:(NSString *)string toTranscript:(CK2TranscriptType)transcript; - @optional - (void)uploader:(CKUploader *)uploader transferRecord:(CKTransferRecord *)record didWriteBodyData:(int64_t)bytesSent diff --git a/ConnectionKit/CKUploader.m b/ConnectionKit/CKUploader.m index f9f43a081..af5b5acc6 100644 --- a/ConnectionKit/CKUploader.m +++ b/ConnectionKit/CKUploader.m @@ -433,10 +433,5 @@ - (void)fileManager:(CK2FileManager *)manager operation:(CK2FileOperation *)oper } } -- (void)fileManager:(CK2FileManager *)manager appendString:(NSString *)info toTranscript:(CK2TranscriptType)transcript; -{ - [[self delegate] uploader:self appendString:info toTranscript:transcript]; -} - @end diff --git a/ConnectionKit/ConnectionKit.h b/ConnectionKit/ConnectionKit.h index b4baddc5f..784e50c21 100644 --- a/ConnectionKit/ConnectionKit.h +++ b/ConnectionKit/ConnectionKit.h @@ -31,6 +31,7 @@ #import #import #import +#import // Legacy #import