Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -554,11 +554,12 @@ return ret; } - (OFData *)stream: (OF_KINDOF(OFStream *))stream - didWriteData: (OFData *)data + didWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding bytesWritten: (size_t)bytesWritten exception: (id)exception { if (exception != nil) { if ([exception isKindOfClass: [OFWriteFailedException class]] && @@ -605,16 +606,11 @@ * We do not use the socket's write buffer in case we need to resend * the entire request (e.g. in case a keep-alive connection timed out). */ @try { - OFString *requestString = constructRequestString(_request); - OFData *requestData = [OFData - dataWithItems: [requestString UTF8String] - count: [requestString UTF8StringLength]]; - - [sock asyncWriteData: requestData]; + [sock asyncWriteString: constructRequestString(_request)]; } @catch (id e) { [self raiseException: e]; return; } } Index: src/OFRunLoop+Private.h ================================================================== --- src/OFRunLoop+Private.h +++ src/OFRunLoop+Private.h @@ -54,10 +54,16 @@ mode: (of_run_loop_mode_t)mode delegate: (id )delegate; + (void)of_addAsyncWriteForStream: (OFStream *) stream data: (OFData *)data + mode: (of_run_loop_mode_t)mode + delegate: (id )delegate; ++ (void)of_addAsyncWriteForStream: (OFStream *) + stream + string: (OFString *)string + encoding: (of_string_encoding_t)encoding mode: (of_run_loop_mode_t)mode delegate: (id )delegate; + (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)socket mode: (of_run_loop_mode_t)mode delegate: (id ) @@ -96,11 +102,17 @@ block: (of_stream_async_read_line_block_t)block; + (void)of_addAsyncWriteForStream: (OFStream *) stream data: (OFData *)data mode: (of_run_loop_mode_t)mode - block: (of_stream_async_write_block_t)block; + block: (of_stream_async_write_data_block_t)block; ++ (void)of_addAsyncWriteForStream: (OFStream *) + stream + string: (OFString *)string + encoding: (of_string_encoding_t)encoding + mode: (of_run_loop_mode_t)mode + block: (of_stream_async_write_string_block_t)block; + (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)socket mode: (of_run_loop_mode_t)mode block: (of_tcp_socket_async_accept_block_t) block; + (void)of_addAsyncReceiveForUDPSocket: (OFUDPSocket *)socket Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -111,17 +111,29 @@ # endif of_string_encoding_t _encoding; } @end -@interface OFRunLoop_WriteQueueItem: OFRunLoop_QueueItem +@interface OFRunLoop_WriteDataQueueItem: OFRunLoop_QueueItem +{ +@public +# ifdef OF_HAVE_BLOCKS + of_stream_async_write_data_block_t _block; +# endif + OFData *_data; + size_t _writtenLength; +} +@end + +@interface OFRunLoop_WriteStringQueueItem: OFRunLoop_QueueItem { @public # ifdef OF_HAVE_BLOCKS - of_stream_async_write_block_t _block; + of_stream_async_write_string_block_t _block; # endif - OFData *_data; + OFString *_string; + of_string_encoding_t _encoding; size_t _writtenLength; } @end @interface OFRunLoop_ConnectQueueItem: OFRunLoop_QueueItem @@ -248,12 +260,11 @@ { /* * Retain the queue so that it doesn't disappear from us because the * handler called -[cancelAsyncRequests]. */ - OFList OF_GENERIC(OF_KINDOF(OFRunLoop_WriteQueueItem *)) *queue = - [[_writeQueues objectForKey: object] retain]; + OFList *queue = [[_writeQueues objectForKey: object] retain]; assert(queue != nil); @try { if (![[queue firstObject] handleObject: object]) { @@ -444,11 +455,11 @@ [super dealloc]; } # endif @end -@implementation OFRunLoop_WriteQueueItem +@implementation OFRunLoop_WriteDataQueueItem - (bool)handleObject: (id)object { size_t length; id exception = nil; size_t dataLength = [_data count] * [_data itemSize]; @@ -511,10 +522,84 @@ { [_data release]; # ifdef OF_HAVE_BLOCKS [_block release]; # endif + + [super dealloc]; +} +@end + +@implementation OFRunLoop_WriteStringQueueItem +- (bool)handleObject: (id)object +{ + size_t length; + id exception = nil; + size_t cStringLength = [_string cStringLengthWithEncoding: _encoding]; + OFString *newString, *oldString; + + @try { + const char *cString = [_string cStringWithEncoding: _encoding]; + + length = [object writeBuffer: cString + _writtenLength + length: cStringLength - _writtenLength]; + } @catch (id e) { + length = 0; + exception = e; + } + + _writtenLength += length; + + if (_writtenLength != cStringLength && exception == nil) + return true; + +# ifdef OF_HAVE_BLOCKS + if (_block != NULL) { + newString = _block(object, _string, _encoding, _writtenLength, + exception); + + if (newString == nil) + return false; + + oldString = _string; + _string = [newString copy]; + [oldString release]; + + _writtenLength = 0; + return true; + } else { +# endif + if (![_delegate respondsToSelector: @selector(stream: + didWriteString:encoding:bytesWritten:exception:)]) + return false; + + newString = [_delegate stream: object + didWriteString: _string + encoding: _encoding + bytesWritten: _writtenLength + exception: exception]; + + if (newString == nil) + return false; + + oldString = _string; + _string = [newString copy]; + [oldString release]; + + _writtenLength = 0; + return true; +# ifdef OF_HAVE_BLOCKS + } +# endif +} + +- (void)dealloc +{ + [_string release]; +# ifdef OF_HAVE_BLOCKS + [_block release]; +# endif [super dealloc]; } @end @@ -787,15 +872,29 @@ stream data: (OFData *)data mode: (of_run_loop_mode_t)mode delegate: (id )delegate { - ADD_WRITE(OFRunLoop_WriteQueueItem, stream, mode, { + ADD_WRITE(OFRunLoop_WriteDataQueueItem, stream, mode, { queueItem->_delegate = [delegate retain]; queueItem->_data = [data copy]; }) } + ++ (void)of_addAsyncWriteForStream: (OFStream *) + stream + string: (OFString *)string + encoding: (of_string_encoding_t)encoding + mode: (of_run_loop_mode_t)mode + delegate: (id )delegate +{ + ADD_WRITE(OFRunLoop_WriteStringQueueItem, stream, mode, { + queueItem->_delegate = [delegate retain]; + queueItem->_string = [string copy]; + queueItem->_encoding = encoding; + }) +} + (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)stream mode: (of_run_loop_mode_t)mode delegate: (id ) delegate @@ -885,17 +984,31 @@ + (void)of_addAsyncWriteForStream: (OFStream *) stream data: (OFData *)data mode: (of_run_loop_mode_t)mode - block: (of_stream_async_write_block_t)block + block: (of_stream_async_write_data_block_t)block { - ADD_WRITE(OFRunLoop_WriteQueueItem, stream, mode, { + ADD_WRITE(OFRunLoop_WriteDataQueueItem, stream, mode, { queueItem->_data = [data copy]; queueItem->_block = [block copy]; }) } + ++ (void)of_addAsyncWriteForStream: (OFStream *) + stream + string: (OFString *)string + encoding: (of_string_encoding_t)encoding + mode: (of_run_loop_mode_t)mode + block: (of_stream_async_write_string_block_t)block +{ + ADD_WRITE(OFRunLoop_WriteStringQueueItem, stream, mode, { + queueItem->_string = [string copy]; + queueItem->_encoding = encoding; + queueItem->_block = [block copy]; + }) +} + (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)stream mode: (of_run_loop_mode_t)mode block: (of_tcp_socket_async_accept_block_t)block { Index: src/OFStream.h ================================================================== --- src/OFStream.h +++ src/OFStream.h @@ -77,13 +77,30 @@ * asynchronous write if no exception was encountered. * @param exception An exception which occurred while writing or `nil` on * success * @return The data to repeat the write with or nil if it should not repeat */ -typedef OFData *_Nullable (^of_stream_async_write_block_t)( +typedef OFData *_Nullable (^of_stream_async_write_data_block_t)( OF_KINDOF(OFStream *) stream, OFData *_Nonnull data, size_t bytesWritten, id _Nullable exception); + +/*! + * @brief A block which is called when a string was written asynchronously to a + * stream. + * + * @param string The string which was written to the stream + * @param bytesWritten The number of bytes which have been written. This + * matches the length of the specified data on the + * asynchronous write if no exception was encountered. + * @param encoding The encoding in which the string was written + * @param exception An exception which occurred while writing or `nil` on + * success + * @return The string to repeat the write with or nil if it should not repeat + */ +typedef OFString *_Nullable (^of_stream_async_write_string_block_t)( + OF_KINDOF(OFStream *) stream, OFString *_Nonnull string, + of_string_encoding_t encoding, size_t bytesWritten, id _Nullable exception); #endif /*! * @protocol OFStreamDelegate OFStream.h ObjFW/OFStream.h * @@ -134,10 +151,29 @@ */ - (nullable OFData *)stream: (OF_KINDOF(OFStream *))stream didWriteData: (OFData *)data bytesWritten: (size_t)bytesWritten exception: (nullable id)exception; + +/*! + * @brief This method is called when a string was written asynchronously to a + * stream. + * + * @param stream The stream to which a string was written + * @param string The string which was written to the stream + * @param encoding The encoding in which the string was written + * @param bytesWritten The number of bytes which have been written. This + * matches the length of the specified data on the + * asynchronous write if no exception was encountered. + * @param exception An exception that occurred while writing, or nil on success + * @return The string to repeat the write with or nil if it should not repeat + */ +- (nullable OFString *)stream: (OF_KINDOF(OFStream *))stream + didWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + bytesWritten: (size_t)bytesWritten + exception: (nullable id)exception; @end /*! * @class OFStream OFStream.h ObjFW/OFStream.h * @@ -972,10 +1008,50 @@ * @param runLoopMode The run loop mode in which to perform the async write */ - (void)asyncWriteData: (OFData *)data runLoopMode: (of_run_loop_mode_t)runLoopMode; +/*! + * @brief Asynchronously writes a string in UTF-8 encoding into the stream. + * + * @note The stream must conform to @ref OFReadyForWritingObserving in order + * for this to work! + * + * @param string The string which is written into the stream + */ +- (void)asyncWriteString: (OFString *)string; + +/*! + * @brief Asynchronously writes a string in the specified encoding into the + * stream. + * + * @note The stream must conform to @ref OFReadyForWritingObserving in order + * for this to work! + * + * @param string The string which is written into the stream + * @param encoding The encoding in which the string should be written to the + * stream + */ +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding; + +/*! + * @brief Asynchronously writes a string in the specified encoding into the + * stream. + * + * @note The stream must conform to @ref OFReadyForWritingObserving in order + * for this to work! + * + * @param string The string which is written into the stream + * @param encoding The encoding in which the string should be written to the + * stream + * @param runLoopMode The run loop mode in which to perform the async write + */ +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + runLoopMode: (of_run_loop_mode_t)runLoopMode; + # ifdef OF_HAVE_BLOCKS /*! * @brief Asynchronously writes data into the stream. * * @note The stream must conform to @ref OFReadyForWritingObserving in order @@ -985,11 +1061,11 @@ * @param block The block to call when the data has been written. It should * return the data for the next write with the same callback or * nil if it should not repeat. */ - (void)asyncWriteData: (OFData *)data - block: (of_stream_async_write_block_t)block; + block: (of_stream_async_write_data_block_t)block; /*! * @brief Asynchronously writes data into the stream. * * @note The stream must conform to @ref OFReadyForWritingObserving in order @@ -1001,11 +1077,63 @@ * return the data for the next write with the same callback or * nil if it should not repeat. */ - (void)asyncWriteData: (OFData *)data runLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_stream_async_write_block_t)block; + block: (of_stream_async_write_data_block_t)block; + +/*! + * @brief Asynchronously writes a string into the stream. + * + * @note The stream must conform to @ref OFReadyForWritingObserving in order + * for this to work! + * + * @param string The string which is written into the stream + * @param block The block to call when the string has been written. It should + * return the string for the next write with the same callback or + * nil if it should not repeat. + */ +- (void)asyncWriteString: (OFString *)string + block: (of_stream_async_write_string_block_t)block; + +/*! + * @brief Asynchronously writes a string in the specified encoding into the + * stream. + * + * @note The stream must conform to @ref OFReadyForWritingObserving in order + * for this to work! + * + * @param string The string which is written into the stream + * @param encoding The encoding in which the string should be written to the + * stream + * @param block The block to call when the string has been written. It should + * return the string for the next write with the same callback or + * nil if it should not repeat. + */ +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + block: (of_stream_async_write_string_block_t)block; + +/*! + * @brief Asynchronously writes a string in the specified encoding into the + * stream. + * + * @note The stream must conform to @ref OFReadyForWritingObserving in order + * for this to work! + * + * @param string The string which is written into the stream + * @param encoding The encoding in which the string should be written to the + * stream + * @param runLoopMode The run loop mode in which to perform the async write + * @param block The block to call when the string has been written. It should + * return the string for the next write with the same callback or + * nil if it should not repeat. + */ +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_stream_async_write_string_block_t)block; # endif #endif /*! * @brief Writes a uint8_t into the stream. Index: src/OFStream.m ================================================================== --- src/OFStream.m +++ src/OFStream.m @@ -1181,32 +1181,95 @@ [OFRunLoop of_addAsyncWriteForStream: stream data: data mode: runLoopMode delegate: _delegate]; } + +- (void)asyncWriteString: (OFString *)string +{ + [self asyncWriteString: string + encoding: OF_STRING_ENCODING_UTF_8 + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding +{ + [self asyncWriteString: string + encoding: encoding + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + OFStream *stream = + (OFStream *)self; + + [OFRunLoop of_addAsyncWriteForStream: stream + string: string + encoding: encoding + mode: runLoopMode + delegate: _delegate]; +} # ifdef OF_HAVE_BLOCKS - (void)asyncWriteData: (OFData *)data - block: (of_stream_async_write_block_t)block + block: (of_stream_async_write_data_block_t)block { [self asyncWriteData: data runLoopMode: of_run_loop_mode_default block: block]; } - (void)asyncWriteData: (OFData *)data runLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_stream_async_write_block_t)block + block: (of_stream_async_write_data_block_t)block { OFStream *stream = (OFStream *)self; [OFRunLoop of_addAsyncWriteForStream: stream data: data mode: runLoopMode block: block]; } + +- (void)asyncWriteString: (OFString *)string + block: (of_stream_async_write_string_block_t)block +{ + [self asyncWriteString: string + encoding: OF_STRING_ENCODING_UTF_8 + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + block: (of_stream_async_write_string_block_t)block +{ + [self asyncWriteString: string + encoding: encoding + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncWriteString: (OFString *)string + encoding: (of_string_encoding_t)encoding + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_stream_async_write_string_block_t)block +{ + OFStream *stream = + (OFStream *)self; + + [OFRunLoop of_addAsyncWriteForStream: stream + string: string + encoding: encoding + mode: runLoopMode + block: block]; +} # endif #endif - (void)writeInt8: (uint8_t)int8 {