From 2d3b7e2173c672dca5d97d9a5c8ab4217652c442 Mon Sep 17 00:00:00 2001 From: Zeke Chin Date: Tue, 14 Jul 2015 12:55:44 -0700 Subject: [PATCH] AppRTCDemo file logging. Adds logging macros to log logs to a file. Undeletes CircularFileStream for that purpose. BUG= R=jiayl@webrtc.org, pbos@webrtc.org Review URL: https://codereview.webrtc.org/1217473011 . Cr-Commit-Position: refs/heads/master@{#9582} --- talk/app/webrtc/objc/RTCFileLogger.mm | 230 ++++++++++++++++++ talk/app/webrtc/objc/public/RTCFileLogger.h | 72 ++++++ talk/examples/objc/.clang-format | 1 + talk/examples/objc/AppRTCDemo/ARDAppClient.m | 30 ++- .../objc/AppRTCDemo/ARDAppEngineClient.m | 11 +- talk/examples/objc/AppRTCDemo/ARDSDPUtils.m | 9 +- .../objc/AppRTCDemo/ARDSignalingMessage.m | 5 +- .../objc/AppRTCDemo/ARDWebSocketChannel.m | 23 +- .../objc/AppRTCDemo/RTCICECandidate+JSON.m | 4 +- .../objc/AppRTCDemo/common/ARDLogging.h | 88 +++++++ .../objc/AppRTCDemo/common/ARDLogging.mm | 68 ++++++ .../AppRTCDemo/{ => common}/ARDUtilities.h | 0 .../AppRTCDemo/{ => common}/ARDUtilities.m | 9 +- .../objc/AppRTCDemo/ios/ARDAppDelegate.m | 2 + .../ios/ARDVideoCallViewController.m | 9 +- talk/libjingle.gyp | 2 + talk/libjingle_examples.gyp | 32 ++- webrtc/base/BUILD.gn | 2 + webrtc/base/base.gyp | 1 + webrtc/base/bitbuffer_unittest.cc | 2 + webrtc/base/stream.cc | 107 ++++++++ webrtc/base/stream.h | 33 +++ webrtc/base/stream_unittest.cc | 160 ++++++++++++ 23 files changed, 854 insertions(+), 46 deletions(-) create mode 100644 talk/app/webrtc/objc/RTCFileLogger.mm create mode 100644 talk/app/webrtc/objc/public/RTCFileLogger.h create mode 120000 talk/examples/objc/.clang-format create mode 100644 talk/examples/objc/AppRTCDemo/common/ARDLogging.h create mode 100644 talk/examples/objc/AppRTCDemo/common/ARDLogging.mm rename talk/examples/objc/AppRTCDemo/{ => common}/ARDUtilities.h (100%) rename talk/examples/objc/AppRTCDemo/{ => common}/ARDUtilities.m (93%) diff --git a/talk/app/webrtc/objc/RTCFileLogger.mm b/talk/app/webrtc/objc/RTCFileLogger.mm new file mode 100644 index 0000000000..b474d7a5c6 --- /dev/null +++ b/talk/app/webrtc/objc/RTCFileLogger.mm @@ -0,0 +1,230 @@ +/* + * libjingle + * Copyright 2015 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "RTCFileLogger.h" + +#include "webrtc/base/checks.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/stream.h" + +NSString *const kDefaultLogFileName = @"webrtc.log"; +NSUInteger const kDefaultMaxFileSize = 10 * 1024 * 1024; // 10MB. + +namespace rtc { + +class CircularFileStreamLogSink : public LogSink { + public: + // Creates a log sink that writes to the given stream. This log sink takes + // ownership of |stream|. + CircularFileStreamLogSink(CircularFileStream *stream) { + DCHECK(stream); + _stream.reset(stream); + } + + ~CircularFileStreamLogSink() override {} + + void OnLogMessage(const std::string &message) override { + if (_stream) { + _stream->WriteAll(message.data(), message.size(), nullptr, nullptr); + } + } + + CircularFileStream *GetStream() { return _stream.get(); } + + private: + scoped_ptr _stream; +}; + +} // namespace rtc + +@implementation RTCFileLogger { + BOOL _hasStarted; + NSString *_filePath; + NSUInteger _maxFileSize; + rtc::scoped_ptr _logSink; +} + +@synthesize severity = _severity; + +- (instancetype)init { + NSArray *paths = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirPath = [paths firstObject]; + NSString *defaultFilePath = + [documentsDirPath stringByAppendingPathComponent:kDefaultLogFileName]; + return [self initWithFilePath:defaultFilePath + maxFileSize:kDefaultMaxFileSize]; +} + +- (instancetype)initWithFilePath:(NSString *)filePath + maxFileSize:(NSUInteger)maxFileSize { + NSParameterAssert(filePath.length); + NSParameterAssert(maxFileSize); + if (self = [super init]) { + _filePath = filePath; + _maxFileSize = maxFileSize; + _severity = kRTCFileLoggerSeverityInfo; + } + return self; +} + +- (void)dealloc { + [self stop]; +} + +- (void)start { + if (_hasStarted) { + return; + } + rtc::scoped_ptr stream; + stream.reset(new rtc::CircularFileStream(_maxFileSize)); + _logSink.reset(new rtc::CircularFileStreamLogSink(stream.release())); + int error = 0; + if (!_logSink->GetStream()->Open(_filePath.UTF8String, "wb", &error)) { + LOG(LS_ERROR) << "Failed to open log file at path: " + << _filePath.UTF8String + << " Error: " + << error; + _logSink.reset(); + return; + } + // TODO(tkchin): Log thead info on iOS, currently this doesn't do anything. + rtc::LogMessage::LogThreads(true); + rtc::LogMessage::LogTimestamps(true); + rtc::LogMessage::AddLogToStream(_logSink.get(), [self rtcSeverity]); + _hasStarted = YES; +} + +- (void)stop { + if (!_hasStarted) { + return; + } + DCHECK(_logSink); + rtc::LogMessage::RemoveLogToStream(_logSink.get()); + _hasStarted = NO; + + // Read the ordered version of the log. + NSData *logData = [self reorderedLogData]; + NSError *error = nil; + // Write the ordered version back to disk. + if (![logData writeToFile:_filePath + options:NSDataWritingAtomic + error:&error]) { + LOG(LS_ERROR) << "Failed to rewrite log to disk at path: " + << _filePath.UTF8String; + if (error) { + LOG(LS_ERROR) << "Error: " << error.localizedDescription.UTF8String; + } + } else { + // If we succeeded in writing to disk we don't need to hold on to the + // stream anymore. + _logSink.reset(); + } +} + +- (NSData *)logData { + if (_hasStarted) { + return nil; + } + if (!_logSink.get()) { + // If there isn't a previously used stream just return contents of file. + return [[self class] contentsOfFileAtPath:_filePath]; + } + return [self reorderedLogData]; +} + +#pragma mark - Private + ++ (NSData *)contentsOfFileAtPath:(NSString *)path { + NSError *error = nil; + NSData *contents = [NSData dataWithContentsOfFile:path + options:0 + error:&error]; + if (error) { + LOG(LS_ERROR) << "Failed to read contents of file at path: " + << path.UTF8String + << " Error: " + << error.localizedDescription.UTF8String; + return nil; + } + return contents; +} + +- (NSData *)reorderedLogData { + if (_hasStarted || !_logSink.get()) { + return nil; + } + // We have a stream we used for writing in memory and we're not writing. The + // stream has a pointer to where the log boundary is so it can reorder the + // log correctly. We just need to reopen the file in read mode. + int error = 0; + rtc::CircularFileStream *stream = _logSink->GetStream(); + if (!stream->Open(_filePath.UTF8String, "r", &error)) { + LOG(LS_ERROR) << "Failed to open log file at path: " + << _filePath.UTF8String + << " Error: " + << error; + return nil; + } + size_t logSize = 0; + size_t bytesRead = 0; + error = 0; + if (!stream->GetSize(&logSize)) { + LOG(LS_ERROR) << "Failed to get log file size."; + return nil; + } + // Allocate memory using malloc so we can pass it direcly to NSData without + // copying. + rtc::scoped_ptr buffer(static_cast(malloc(logSize))); + if (stream->ReadAll(buffer.get(), logSize, &bytesRead, &error) + != rtc::SR_SUCCESS) { + LOG(LS_ERROR) << "Failed to read log file at path: " + << _filePath.UTF8String + << " Error: " + << error; + } + DCHECK_LE(bytesRead, logSize); + // NSData takes ownership of the bytes and frees it on dealloc. + return [NSData dataWithBytesNoCopy:buffer.release() + length:bytesRead]; +} + +- (rtc::LoggingSeverity)rtcSeverity { + switch (_severity) { + case kRTCFileLoggerSeverityVerbose: + return rtc::LS_VERBOSE; + case kRTCFileLoggerSeverityInfo: + return rtc::LS_INFO; + case kRTCFileLoggerSeverityWarning: + return rtc::LS_WARNING; + case kRTCFileLoggerSeverityError: + return rtc::LS_ERROR; + } +} + +@end diff --git a/talk/app/webrtc/objc/public/RTCFileLogger.h b/talk/app/webrtc/objc/public/RTCFileLogger.h new file mode 100644 index 0000000000..5c311b711a --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCFileLogger.h @@ -0,0 +1,72 @@ +/* + * libjingle + * Copyright 2015 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 + +// TODO(tkchin): Move this to a common location. +#ifndef NS_DESIGNATED_INITIALIZER +#define NS_DESIGNATED_INITIALIZER +#endif + +typedef NS_ENUM(NSUInteger, RTCFileLoggerSeverity) { + kRTCFileLoggerSeverityVerbose, + kRTCFileLoggerSeverityInfo, + kRTCFileLoggerSeverityWarning, + kRTCFileLoggerSeverityError +}; + +// This class intercepts WebRTC logs and saves them to a file. The file size +// will not exceed the given maximum bytesize. When the maximum bytesize is +// reached logs from the beginning and the end are preserved while the middle +// section is overwritten instead. +// This class is not threadsafe. +@interface RTCFileLogger : NSObject + +// The severity level to capture. The default is kRTCFileLoggerSeverityInfo. +@property(nonatomic, assign) RTCFileLoggerSeverity severity; + +// Default constructor provides default settings for file path and file size. +- (instancetype)init; + +- (instancetype)initWithFilePath:(NSString *)filePath + maxFileSize:(NSUInteger)maxFileSize + NS_DESIGNATED_INITIALIZER; + +// Starts writing WebRTC logs to file if not already started. Overwrites any +// existing file. +- (void)start; + +// Stops writing WebRTC logs to file. Rewrites the log file as required to +// reorder logs because logs may be disordered due to use of +// rtc::CircularFileStream. This method is also called on dealloc. +- (void)stop; + +// Returns the current contents of the log file. Returns nil if start has been +// called without a stop, or if there is no data. +- (NSData *)logData; + +@end diff --git a/talk/examples/objc/.clang-format b/talk/examples/objc/.clang-format new file mode 120000 index 0000000000..ce43d52283 --- /dev/null +++ b/talk/examples/objc/.clang-format @@ -0,0 +1 @@ +../../app/webrtc/objc/.clang-format \ No newline at end of file diff --git a/talk/examples/objc/AppRTCDemo/ARDAppClient.m b/talk/examples/objc/AppRTCDemo/ARDAppClient.m index ac99ca2997..5b905c6e65 100644 --- a/talk/examples/objc/AppRTCDemo/ARDAppClient.m +++ b/talk/examples/objc/AppRTCDemo/ARDAppClient.m @@ -30,6 +30,7 @@ #if defined(WEBRTC_IOS) #import "RTCAVFoundationVideoSource.h" #endif +#import "RTCFileLogger.h" #import "RTCICEServer.h" #import "RTCMediaConstraints.h" #import "RTCMediaStream.h" @@ -41,6 +42,7 @@ #import "ARDAppEngineClient.h" #import "ARDCEODTURNClient.h" #import "ARDJoinResponse.h" +#import "ARDLogging.h" #import "ARDMessageResponse.h" #import "ARDSDPUtils.h" #import "ARDSignalingMessage.h" @@ -65,7 +67,9 @@ static NSInteger const kARDAppClientErrorSetSDP = -4; static NSInteger const kARDAppClientErrorInvalidClient = -5; static NSInteger const kARDAppClientErrorInvalidRoom = -6; -@implementation ARDAppClient +@implementation ARDAppClient { + RTCFileLogger *_fileLogger; +} @synthesize delegate = _delegate; @synthesize state = _state; @@ -131,6 +135,8 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; _factory = [[RTCPeerConnectionFactory alloc] init]; _messageQueue = [NSMutableArray array]; _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]]; + _fileLogger = [[RTCFileLogger alloc] init]; + [_fileLogger start]; } - (void)dealloc { @@ -156,7 +162,7 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers, NSError *error) { if (error) { - NSLog(@"Error retrieving TURN servers: %@", error); + ARDLog("Error retrieving TURN servers: %@", error.localizedDescription); } ARDAppClient *strongSelf = weakSelf; [strongSelf.iceServers addObjectsFromArray:turnServers]; @@ -175,12 +181,12 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; NSError *joinError = [[strongSelf class] errorForJoinResultType:response.result]; if (joinError) { - NSLog(@"Failed to join room:%@ on room server.", roomId); + ARDLog(@"Failed to join room:%@ on room server.", roomId); [strongSelf disconnect]; [strongSelf.delegate appClient:strongSelf didError:joinError]; return; } - NSLog(@"Joined room:%@ on room server.", roomId); + ARDLog(@"Joined room:%@ on room server.", roomId); strongSelf.roomId = response.roomId; strongSelf.clientId = response.clientId; strongSelf.isInitiator = response.isInitiator; @@ -272,13 +278,13 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; - (void)peerConnection:(RTCPeerConnection *)peerConnection signalingStateChanged:(RTCSignalingState)stateChanged { - NSLog(@"Signaling state changed: %d", stateChanged); + ARDLog(@"Signaling state changed: %d", stateChanged); } - (void)peerConnection:(RTCPeerConnection *)peerConnection addedStream:(RTCMediaStream *)stream { dispatch_async(dispatch_get_main_queue(), ^{ - NSLog(@"Received %lu video tracks and %lu audio tracks", + ARDLog(@"Received %lu video tracks and %lu audio tracks", (unsigned long)stream.videoTracks.count, (unsigned long)stream.audioTracks.count); if (stream.videoTracks.count) { @@ -290,17 +296,17 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; - (void)peerConnection:(RTCPeerConnection *)peerConnection removedStream:(RTCMediaStream *)stream { - NSLog(@"Stream was removed."); + ARDLog(@"Stream was removed."); } - (void)peerConnectionOnRenegotiationNeeded: (RTCPeerConnection *)peerConnection { - NSLog(@"WARNING: Renegotiation needed but unimplemented."); + ARDLog(@"WARNING: Renegotiation needed but unimplemented."); } - (void)peerConnection:(RTCPeerConnection *)peerConnection iceConnectionChanged:(RTCICEConnectionState)newState { - NSLog(@"ICE state changed: %d", newState); + ARDLog(@"ICE state changed: %d", newState); dispatch_async(dispatch_get_main_queue(), ^{ [_delegate appClient:self didChangeConnectionState:newState]; }); @@ -308,7 +314,7 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; - (void)peerConnection:(RTCPeerConnection *)peerConnection iceGatheringChanged:(RTCICEGatheringState)newState { - NSLog(@"ICE gathering state changed: %d", newState); + ARDLog(@"ICE gathering state changed: %d", newState); } - (void)peerConnection:(RTCPeerConnection *)peerConnection @@ -333,7 +339,7 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; error:(NSError *)error { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { - NSLog(@"Failed to create session description. Error: %@", error); + ARDLog(@"Failed to create session description. Error: %@", error); [self disconnect]; NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Failed to create session description.", @@ -362,7 +368,7 @@ static NSInteger const kARDAppClientErrorInvalidRoom = -6; didSetSessionDescriptionWithError:(NSError *)error { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { - NSLog(@"Failed to set session description. Error: %@", error); + ARDLog(@"Failed to set session description. Error: %@", error); [self disconnect]; NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Failed to set session description.", diff --git a/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m index 760a171503..0b33325c89 100644 --- a/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m +++ b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m @@ -28,6 +28,7 @@ #import "ARDAppEngineClient.h" #import "ARDJoinResponse.h" +#import "ARDLogging.h" #import "ARDMessageResponse.h" #import "ARDSignalingMessage.h" #import "ARDUtilities.h" @@ -57,7 +58,7 @@ static NSInteger const kARDAppEngineClientErrorBadResponse = -1; NSString *urlString = [NSString stringWithFormat:kARDRoomServerJoinFormat, roomId]; NSURL *roomURL = [NSURL URLWithString:urlString]; - NSLog(@"Joining room:%@ on room server.", roomId); + ARDLog(@"Joining room:%@ on room server.", roomId); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:roomURL]; request.HTTPMethod = @"POST"; __weak ARDAppEngineClient *weakSelf = self; @@ -101,7 +102,7 @@ static NSInteger const kARDAppEngineClientErrorBadResponse = -1; [NSString stringWithFormat: kARDRoomServerMessageFormat, roomId, clientId]; NSURL *url = [NSURL URLWithString:urlString]; - NSLog(@"C->RS POST: %@", message); + ARDLog(@"C->RS POST: %@", message); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = data; @@ -147,19 +148,19 @@ static NSInteger const kARDAppEngineClientErrorBadResponse = -1; NSError *error = nil; // We want a synchronous request so that we know that we've left the room on // room server before we do any further work. - NSLog(@"C->RS: BYE"); + ARDLog(@"C->RS: BYE"); [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error) { - NSLog(@"Error leaving room %@ on room server: %@", + ARDLog(@"Error leaving room %@ on room server: %@", roomId, error.localizedDescription); if (completionHandler) { completionHandler(error); } return; } - NSLog(@"Left room:%@ on room server.", roomId); + ARDLog(@"Left room:%@ on room server.", roomId); if (completionHandler) { completionHandler(nil); } diff --git a/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m b/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m index 157d6fc1f6..481b6ce18f 100644 --- a/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m +++ b/talk/examples/objc/AppRTCDemo/ARDSDPUtils.m @@ -27,6 +27,7 @@ #import "ARDSDPUtils.h" +#import "ARDLogging.h" #import "RTCSessionDescription.h" @implementation ARDSDPUtils @@ -42,7 +43,7 @@ NSMutableArray *lines = [NSMutableArray arrayWithArray: [sdpString componentsSeparatedByString:lineSeparator]]; - int mLineIndex = -1; + NSInteger mLineIndex = -1; NSString *codecRtpMap = nil; // a=rtpmap: / // [/] @@ -70,11 +71,11 @@ } } if (mLineIndex == -1) { - NSLog(@"No m=video line, so can't prefer %@", codec); + ARDLog(@"No m=video line, so can't prefer %@", codec); return description; } if (!codecRtpMap) { - NSLog(@"No rtpmap for %@", codec); + ARDLog(@"No rtpmap for %@", codec); return description; } NSArray *origMLineParts = @@ -98,7 +99,7 @@ [lines replaceObjectAtIndex:mLineIndex withObject:newMLine]; } else { - NSLog(@"Wrong SDP media description format: %@", lines[mLineIndex]); + ARDLog(@"Wrong SDP media description format: %@", lines[mLineIndex]); } NSString *mangledSdpString = [lines componentsJoinedByString:lineSeparator]; return [[RTCSessionDescription alloc] initWithType:description.type diff --git a/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m b/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m index e9fb09aeef..dc00e8f77c 100644 --- a/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m +++ b/talk/examples/objc/AppRTCDemo/ARDSignalingMessage.m @@ -27,6 +27,7 @@ #import "ARDSignalingMessage.h" +#import "ARDLogging.h" #import "ARDUtilities.h" #import "RTCICECandidate+JSON.h" #import "RTCSessionDescription+JSON.h" @@ -52,7 +53,7 @@ static NSString const *kARDSignalingMessageTypeKey = @"type"; + (ARDSignalingMessage *)messageFromJSONString:(NSString *)jsonString { NSDictionary *values = [NSDictionary dictionaryWithJSONString:jsonString]; if (!values) { - NSLog(@"Error parsing signaling message JSON."); + ARDLog(@"Error parsing signaling message JSON."); return nil; } @@ -71,7 +72,7 @@ static NSString const *kARDSignalingMessageTypeKey = @"type"; } else if ([typeString isEqualToString:@"bye"]) { message = [[ARDByeMessage alloc] init]; } else { - NSLog(@"Unexpected type: %@", typeString); + ARDLog(@"Unexpected type: %@", typeString); } return message; } diff --git a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m index 27dd25d2c2..f25e9d08bb 100644 --- a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m +++ b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m @@ -27,6 +27,7 @@ #import "ARDWebSocketChannel.h" +#import "ARDLogging.h" #import "ARDUtilities.h" #import "SRWebSocket.h" @@ -57,7 +58,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; _delegate = delegate; _socket = [[SRWebSocket alloc] initWithURL:url]; _socket.delegate = self; - NSLog(@"Opening WebSocket."); + ARDLog(@"Opening WebSocket."); [_socket open]; } return self; @@ -104,12 +105,12 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; NSString *messageString = [[NSString alloc] initWithData:messageJSONObject encoding:NSUTF8StringEncoding]; - NSLog(@"C->WSS: %@", messageString); + ARDLog(@"C->WSS: %@", messageString); [_socket send:messageString]; } else { NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSLog(@"C->WSS POST: %@", dataString); + ARDLog(@"C->WSS POST: %@", dataString); NSString *urlString = [NSString stringWithFormat:@"%@/%@/%@", [_restURL absoluteString], _roomId, _clientId]; @@ -126,7 +127,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; return; } [_socket close]; - NSLog(@"C->WSS DELETE rid:%@ cid:%@", _roomId, _clientId); + ARDLog(@"C->WSS DELETE rid:%@ cid:%@", _roomId, _clientId); NSString *urlString = [NSString stringWithFormat:@"%@/%@/%@", [_restURL absoluteString], _roomId, _clientId]; @@ -140,7 +141,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; #pragma mark - SRWebSocketDelegate - (void)webSocketDidOpen:(SRWebSocket *)webSocket { - NSLog(@"WebSocket connection opened."); + ARDLog(@"WebSocket connection opened."); self.state = kARDSignalingChannelStateOpen; if (_roomId.length && _clientId.length) { [self registerWithCollider]; @@ -154,24 +155,24 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; options:0 error:nil]; if (![jsonObject isKindOfClass:[NSDictionary class]]) { - NSLog(@"Unexpected message: %@", jsonObject); + ARDLog(@"Unexpected message: %@", jsonObject); return; } NSDictionary *wssMessage = jsonObject; NSString *errorString = wssMessage[kARDWSSMessageErrorKey]; if (errorString.length) { - NSLog(@"WSS error: %@", errorString); + ARDLog(@"WSS error: %@", errorString); return; } NSString *payload = wssMessage[kARDWSSMessagePayloadKey]; ARDSignalingMessage *signalingMessage = [ARDSignalingMessage messageFromJSONString:payload]; - NSLog(@"WSS->C: %@", payload); + ARDLog(@"WSS->C: %@", payload); [_delegate channel:self didReceiveMessage:signalingMessage]; } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { - NSLog(@"WebSocket error: %@", error); + ARDLog(@"WebSocket error: %@", error); self.state = kARDSignalingChannelStateError; } @@ -179,7 +180,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { - NSLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d", + ARDLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d", (long)code, reason, wasClean); NSParameterAssert(_state != kARDSignalingChannelStateError); self.state = kARDSignalingChannelStateClosed; @@ -204,7 +205,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; error:nil]; NSString *messageString = [[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding]; - NSLog(@"Registering on WSS for rid:%@ cid:%@", _roomId, _clientId); + ARDLog(@"Registering on WSS for rid:%@ cid:%@", _roomId, _clientId); // Registration can fail if server rejects it. For example, if the room is // full. [_socket send:messageString]; diff --git a/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m b/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m index c15d633405..01d666d82e 100644 --- a/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m +++ b/talk/examples/objc/AppRTCDemo/RTCICECandidate+JSON.m @@ -27,6 +27,8 @@ #import "RTCICECandidate+JSON.h" +#import "ARDLogging.h" + static NSString const *kRTCICECandidateTypeKey = @"type"; static NSString const *kRTCICECandidateTypeValue = @"candidate"; static NSString const *kRTCICECandidateMidKey = @"id"; @@ -56,7 +58,7 @@ static NSString const *kRTCICECandidateSdpKey = @"candidate"; options:NSJSONWritingPrettyPrinted error:&error]; if (error) { - NSLog(@"Error serializing JSON: %@", error); + ARDLog(@"Error serializing JSON: %@", error); return nil; } return data; diff --git a/talk/examples/objc/AppRTCDemo/common/ARDLogging.h b/talk/examples/objc/AppRTCDemo/common/ARDLogging.h new file mode 100644 index 0000000000..dfb31d8539 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/common/ARDLogging.h @@ -0,0 +1,88 @@ +/* + * libjingle + * Copyright 2015 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 + +// We route all logging through the WebRTC logger. By doing this we will get +// both app and WebRTC logs in the same place, which we can then route to a +// file if we need to. A side effect of this is that we get severity for free. +typedef NS_ENUM(NSInteger, ARDLogSeverity) { + kARDLogSeverityVerbose, + kARDLogSeverityInfo, + kARDLogSeverityWarning, + kARDLogSeverityError, +}; + +#if defined(__cplusplus) +extern "C" void ARDLogToWebRTCLogger(ARDLogSeverity severity, + NSString *logString); +extern "C" NSString *ARDFileName(const char *filePath); +extern "C" void ARDLogInit(); +#else +// Logs |logString| to the WebRTC logger at the given severity. +extern void ARDLogToWebRTCLogger(ARDLogSeverity severity, NSString *logString); +// Returns the filename with the path prefix removed. +extern NSString *ARDFileName(const char *filePath); +// Initializes the correct logging levels. This should be called once on app +// startup. +extern void ARDLogInit(); +#endif + +#define ARDLogString(format, ...) \ + [NSString stringWithFormat:@"(%@:%d %s): " format, \ + ARDFileName(__FILE__), \ + __LINE__, \ + __FUNCTION__, \ + ##__VA_ARGS__] + +#define ARDLogEx(severity, format, ...) \ + do { \ + NSString *logString = ARDLogString(format, ##__VA_ARGS__); \ + ARDLogToWebRTCLogger(severity, logString); \ + } while (false) + +#define ARDLogVerbose(format, ...) \ + ARDLogEx(kARDLogSeverityVerbose, format, ##__VA_ARGS__) \ + +#define ARDLogInfo(format, ...) \ + ARDLogEx(kARDLogSeverityInfo, format, ##__VA_ARGS__) \ + +#define ARDLogWarning(format, ...) \ + ARDLogEx(kARDLogSeverityWarning, format, ##__VA_ARGS__) \ + +#define ARDLogError(format, ...) \ + ARDLogEx(kARDLogSeverityError, format, ##__VA_ARGS__) \ + +#ifdef _DEBUG +#define ARDLogDebug(format, ...) ARDLogInfo(format, ##__VA_ARGS__) +#else +#define ARDLogDebug(format, ...) \ + do { \ + } while (false) +#endif + +#define ARDLog(format, ...) ARDLogInfo(format, ##__VA_ARGS__) diff --git a/talk/examples/objc/AppRTCDemo/common/ARDLogging.mm b/talk/examples/objc/AppRTCDemo/common/ARDLogging.mm new file mode 100644 index 0000000000..7bd773d770 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/common/ARDLogging.mm @@ -0,0 +1,68 @@ +/* + * libjingle + * Copyright 2015 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "ARDLogging.h" + +#include "webrtc/base/logging.h" + +void ARDLogInit() { +#ifndef _DEBUG + // In debug builds the default level is LS_INFO and in non-debug builds it is + // disabled. Continue to log to console in non-debug builds, but only + // warnings and errors. + rtc::LogMessage::LogToDebug(rtc::LS_WARNING); +#endif +} + +void ARDLogToWebRTCLogger(ARDLogSeverity severity, NSString *logString) { + if (logString.length) { + const char* utf8String = logString.UTF8String; + switch (severity) { + case kARDLogSeverityVerbose: + LOG(LS_VERBOSE) << utf8String; + break; + case kARDLogSeverityInfo: + LOG(LS_INFO) << utf8String; + break; + case kARDLogSeverityWarning: + LOG(LS_WARNING) << utf8String; + break; + case kARDLogSeverityError: + LOG(LS_ERROR) << utf8String; + break; + } + } +} + +NSString *ARDFileName(const char *filePath) { + NSString *nsFilePath = + [[NSString alloc] initWithBytesNoCopy:const_cast(filePath) + length:strlen(filePath) + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + return nsFilePath.lastPathComponent; +} diff --git a/talk/examples/objc/AppRTCDemo/ARDUtilities.h b/talk/examples/objc/AppRTCDemo/common/ARDUtilities.h similarity index 100% rename from talk/examples/objc/AppRTCDemo/ARDUtilities.h rename to talk/examples/objc/AppRTCDemo/common/ARDUtilities.h diff --git a/talk/examples/objc/AppRTCDemo/ARDUtilities.m b/talk/examples/objc/AppRTCDemo/common/ARDUtilities.m similarity index 93% rename from talk/examples/objc/AppRTCDemo/ARDUtilities.m rename to talk/examples/objc/AppRTCDemo/common/ARDUtilities.m index 781e786aa4..066ee0294c 100644 --- a/talk/examples/objc/AppRTCDemo/ARDUtilities.m +++ b/talk/examples/objc/AppRTCDemo/common/ARDUtilities.m @@ -25,6 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#import "ARDLogging.h" #import "ARDUtilities.h" @implementation NSDictionary (ARDUtilites) @@ -36,7 +37,7 @@ NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (error) { - NSLog(@"Error parsing JSON: %@", error.localizedDescription); + ARDLog(@"Error parsing JSON: %@", error.localizedDescription); } return dict; } @@ -46,7 +47,7 @@ NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (error) { - NSLog(@"Error parsing JSON: %@", error.localizedDescription); + ARDLog(@"Error parsing JSON: %@", error.localizedDescription); } return dict; } @@ -84,7 +85,7 @@ NSData *data, NSError *error) { if (error) { - NSLog(@"Error posting data: %@", error.localizedDescription); + ARDLog(@"Error posting data: %@", error.localizedDescription); if (completionHandler) { completionHandler(NO, data); } @@ -95,7 +96,7 @@ NSString *serverResponse = data.length > 0 ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil; - NSLog(@"Received bad response: %@", serverResponse); + ARDLog(@"Received bad response: %@", serverResponse); if (completionHandler) { completionHandler(NO, data); } diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m b/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m index 2a7a1551c3..352ade60c7 100644 --- a/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m +++ b/talk/examples/objc/AppRTCDemo/ios/ARDAppDelegate.m @@ -27,6 +27,7 @@ #import "ARDAppDelegate.h" +#import "ARDLogging.h" #import "ARDMainViewController.h" #import "RTCPeerConnectionFactory.h" @@ -43,6 +44,7 @@ [_window makeKeyAndVisible]; ARDMainViewController *viewController = [[ARDMainViewController alloc] init]; _window.rootViewController = viewController; + ARDLogInit(); return YES; } diff --git a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m index 2f07c7a4cc..149beef467 100644 --- a/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m +++ b/talk/examples/objc/AppRTCDemo/ios/ARDVideoCallViewController.m @@ -30,6 +30,7 @@ #import "RTCAVFoundationVideoSource.h" #import "ARDAppClient.h" +#import "ARDLogging.h" #import "ARDVideoCallView.h" @interface ARDVideoCallViewController () ="10.8")', { 'targets': [ - { 'target_name': 'apprtc_signaling', + { + 'target_name': 'apprtc_common', + 'type': 'static_library', + 'sources': [ + 'examples/objc/AppRTCDemo/common/ARDLogging.h', + 'examples/objc/AppRTCDemo/common/ARDLogging.mm', + 'examples/objc/AppRTCDemo/common/ARDUtilities.h', + 'examples/objc/AppRTCDemo/common/ARDUtilities.m', + ], + 'include_dirs': [ + 'examples/objc/AppRTCDemo/common', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'examples/objc/AppRTCDemo/common', + ], + }, + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'MACOSX_DEPLOYMENT_TARGET' : '10.8', + }, + }], + ], + }, + { + 'target_name': 'apprtc_signaling', 'type': 'static_library', 'dependencies': [ + 'apprtc_common', 'libjingle.gyp:libjingle_peerconnection_objc', 'socketrocket', ], @@ -179,8 +206,6 @@ 'examples/objc/AppRTCDemo/ARDSignalingMessage.h', 'examples/objc/AppRTCDemo/ARDSignalingMessage.m', 'examples/objc/AppRTCDemo/ARDTURNClient.h', - 'examples/objc/AppRTCDemo/ARDUtilities.h', - 'examples/objc/AppRTCDemo/ARDUtilities.m', 'examples/objc/AppRTCDemo/ARDWebSocketChannel.h', 'examples/objc/AppRTCDemo/ARDWebSocketChannel.m', 'examples/objc/AppRTCDemo/RTCICECandidate+JSON.h', @@ -217,6 +242,7 @@ 'product_name': 'AppRTCDemo', 'mac_bundle': 1, 'dependencies': [ + 'apprtc_common', 'apprtc_signaling', ], 'conditions': [ diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn index 60bed6e53b..2535a641c4 100644 --- a/webrtc/base/BUILD.gn +++ b/webrtc/base/BUILD.gn @@ -49,6 +49,8 @@ config("nss_config") { config("ios_config") { libs = [ + "CFNetwork.framework", + #"Foundation.framework", # Already included in //build/config:default_libs. "Security.framework", "SystemConfiguration.framework", diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp index 7bfa3e36ab..b11f4de206 100644 --- a/webrtc/base/base.gyp +++ b/webrtc/base/base.gyp @@ -588,6 +588,7 @@ 'all_dependent_settings': { 'xcode_settings': { 'OTHER_LDFLAGS': [ + '-framework CFNetwork', '-framework Foundation', '-framework Security', '-framework SystemConfiguration', diff --git a/webrtc/base/bitbuffer_unittest.cc b/webrtc/base/bitbuffer_unittest.cc index 89d3524677..c47f92c59d 100644 --- a/webrtc/base/bitbuffer_unittest.cc +++ b/webrtc/base/bitbuffer_unittest.cc @@ -166,10 +166,12 @@ TEST(BitBufferTest, SetOffsetValues) { // Disable death test on Android because it relies on fork() and doesn't play // nicely. +#if defined(GTEST_HAS_DEATH_TEST) #if !defined(WEBRTC_ANDROID) // Passing a NULL out parameter is death. EXPECT_DEATH(buffer.GetCurrentOffset(&byte_offset, NULL), ""); #endif +#endif } uint64 GolombEncoded(uint32 val) { diff --git a/webrtc/base/stream.cc b/webrtc/base/stream.cc index e22c3d8aa4..3e3afa040d 100644 --- a/webrtc/base/stream.cc +++ b/webrtc/base/stream.cc @@ -517,6 +517,113 @@ void FileStream::DoClose() { fclose(file_); } +CircularFileStream::CircularFileStream(size_t max_size) + : max_write_size_(max_size), + position_(0), + marked_position_(max_size / 2), + last_write_position_(0), + read_segment_(READ_LATEST), + read_segment_available_(0) { +} + +bool CircularFileStream::Open(const std::string& filename, + const char* mode, + int* error) { + if (!FileStream::Open(filename.c_str(), mode, error)) + return false; + + if (strchr(mode, "r") != NULL) { // Opened in read mode. + // Check if the buffer has been overwritten and determine how to read the + // log in time sequence. + size_t file_size; + GetSize(&file_size); + if (file_size == position_) { + // The buffer has not been overwritten yet. Read 0 .. file_size + read_segment_ = READ_LATEST; + read_segment_available_ = file_size; + } else { + // The buffer has been over written. There are three segments: The first + // one is 0 .. marked_position_, which is the marked earliest log. The + // second one is position_ .. file_size, which is the middle log. The + // last one is marked_position_ .. position_, which is the latest log. + read_segment_ = READ_MARKED; + read_segment_available_ = marked_position_; + last_write_position_ = position_; + } + + // Read from the beginning. + position_ = 0; + SetPosition(position_); + } + + return true; +} + +StreamResult CircularFileStream::Read(void* buffer, + size_t buffer_len, + size_t* read, + int* error) { + if (read_segment_available_ == 0) { + size_t file_size; + switch (read_segment_) { + case READ_MARKED: // Finished READ_MARKED and start READ_MIDDLE. + read_segment_ = READ_MIDDLE; + position_ = last_write_position_; + SetPosition(position_); + GetSize(&file_size); + read_segment_available_ = file_size - position_; + break; + + case READ_MIDDLE: // Finished READ_MIDDLE and start READ_LATEST. + read_segment_ = READ_LATEST; + position_ = marked_position_; + SetPosition(position_); + read_segment_available_ = last_write_position_ - position_; + break; + + default: // Finished READ_LATEST and return EOS. + return rtc::SR_EOS; + } + } + + size_t local_read; + if (!read) + read = &local_read; + + size_t to_read = std::min(buffer_len, read_segment_available_); + rtc::StreamResult result = + rtc::FileStream::Read(buffer, to_read, read, error); + if (result == rtc::SR_SUCCESS) { + read_segment_available_ -= *read; + position_ += *read; + } + return result; +} + +StreamResult CircularFileStream::Write(const void* data, + size_t data_len, + size_t* written, + int* error) { + if (position_ >= max_write_size_) { + ASSERT(position_ == max_write_size_); + position_ = marked_position_; + SetPosition(position_); + } + + size_t local_written; + if (!written) + written = &local_written; + + size_t to_eof = max_write_size_ - position_; + size_t to_write = std::min(data_len, to_eof); + rtc::StreamResult result = + rtc::FileStream::Write(data, to_write, written, error); + if (result == rtc::SR_SUCCESS) { + position_ += *written; + } + return result; +} + /////////////////////////////////////////////////////////////////////////////// // MemoryStream /////////////////////////////////////////////////////////////////////////////// diff --git a/webrtc/base/stream.h b/webrtc/base/stream.h index b3317663b5..f67c704aa5 100644 --- a/webrtc/base/stream.h +++ b/webrtc/base/stream.h @@ -418,6 +418,39 @@ class FileStream : public StreamInterface { DISALLOW_COPY_AND_ASSIGN(FileStream); }; +// A stream that caps the output at a certain size, dropping content from the +// middle of the logical stream and maintaining equal parts of the start/end of +// the logical stream. +class CircularFileStream : public FileStream { + public: + explicit CircularFileStream(size_t max_size); + + bool Open(const std::string& filename, const char* mode, int* error) override; + StreamResult Read(void* buffer, + size_t buffer_len, + size_t* read, + int* error) override; + StreamResult Write(const void* data, + size_t data_len, + size_t* written, + int* error) override; + + private: + enum ReadSegment { + READ_MARKED, // Read 0 .. marked_position_ + READ_MIDDLE, // Read position_ .. file_size + READ_LATEST, // Read marked_position_ .. position_ if the buffer was + // overwritten or 0 .. position_ otherwise. + }; + + size_t max_write_size_; + size_t position_; + size_t marked_position_; + size_t last_write_position_; + ReadSegment read_segment_; + size_t read_segment_available_; +}; + /////////////////////////////////////////////////////////////////////////////// // MemoryStream is a simple implementation of a StreamInterface over in-memory // data. Data is read and written at the current seek position. Reads return diff --git a/webrtc/base/stream_unittest.cc b/webrtc/base/stream_unittest.cc index 86eb722c2a..4d5066ab55 100644 --- a/webrtc/base/stream_unittest.cc +++ b/webrtc/base/stream_unittest.cc @@ -8,7 +8,9 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "webrtc/base/fileutils.h" #include "webrtc/base/gunit.h" +#include "webrtc/base/pathutils.h" #include "webrtc/base/stream.h" #include "webrtc/test/testsupport/gtest_disable.h" @@ -370,4 +372,162 @@ TEST(FifoBufferTest, WriteOffsetAndReadOffset) { EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL)); } +class CircularFileStreamTest : public ::testing::Test { + protected: + static size_t const kMaxSize = 12; + + CircularFileStreamTest() : is_open_(false), stream_(kMaxSize) { + Pathname temp_dir; + if (Filesystem::GetAppTempFolder(&temp_dir)) { + logfile_name_ = + Filesystem::TempFilename(temp_dir, "CircularFileStreamTest"); + } + } + + virtual void SetUp() { + int error = -1; + is_open_ = stream_.Open(logfile_name_, "wb", &error); + } + + virtual void TearDown() { + if (!Filesystem::IsAbsent(logfile_name_)) { + Filesystem::DeleteFile(logfile_name_); + } + } + + bool is_open_; + CircularFileStream stream_; + std::string logfile_name_; +}; + +TEST_F(CircularFileStreamTest, ReadWriteWithinCapacity) { + EXPECT_TRUE(is_open_); + // Write contents. + const uint8_t bytes[] = {1, 2, 3, 4, 5, 6}; + size_t written = 0; + int error = 0; + EXPECT_EQ(SR_SUCCESS, stream_.Write(bytes, sizeof(bytes), &written, &error)); + EXPECT_EQ(0, error); + EXPECT_EQ(written, sizeof(bytes)); + stream_.Close(); + + // Check file contents. + uint8_t content_bytes[sizeof(bytes)] = {}; + scoped_ptr content_stream( + Filesystem::OpenFile(logfile_name_, "r")); + size_t num_content_bytes_read = 0; + EXPECT_TRUE(content_stream); + error = 0; + EXPECT_EQ(SR_SUCCESS, + content_stream->Read(content_bytes, sizeof(content_bytes), + &num_content_bytes_read, &error)); + EXPECT_EQ(sizeof(content_bytes), num_content_bytes_read); + ASSERT_EQ(sizeof(content_bytes), sizeof(bytes)); + EXPECT_EQ(0, memcmp(content_bytes, bytes, sizeof(content_bytes))); + + // Check read result. + error = 0; + size_t file_size = 0; + EXPECT_TRUE(stream_.Open(logfile_name_, "r", &error)); + EXPECT_TRUE(stream_.GetSize(&file_size)); + EXPECT_EQ(0, error); + EXPECT_EQ(sizeof(bytes), file_size); + scoped_ptr read_bytes(new uint8_t[file_size]); + size_t num_read_bytes = 0; + error = 0; + EXPECT_EQ(SR_SUCCESS, stream_.ReadAll(read_bytes.get(), file_size, + &num_read_bytes, &error)); + EXPECT_EQ(sizeof(bytes), num_read_bytes); + EXPECT_EQ(0, memcmp(bytes, read_bytes.get(), file_size)); +} + +TEST_F(CircularFileStreamTest, ReadWriteAtCapacity) { + EXPECT_TRUE(is_open_); + // Write contents. + const uint8_t bytes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + size_t written = 0; + int error = 0; + EXPECT_EQ(SR_SUCCESS, stream_.Write(bytes, sizeof(bytes), &written, &error)); + EXPECT_EQ(0, error); + EXPECT_EQ(written, sizeof(bytes)); + stream_.Close(); + + // Check file contents. + uint8_t content_bytes[sizeof(bytes)] = {}; + scoped_ptr content_stream( + Filesystem::OpenFile(logfile_name_, "r")); + size_t num_content_bytes_read = 0; + EXPECT_TRUE(content_stream); + error = 0; + EXPECT_EQ(SR_SUCCESS, + content_stream->Read(content_bytes, sizeof(content_bytes), + &num_content_bytes_read, &error)); + EXPECT_EQ(sizeof(content_bytes), num_content_bytes_read); + ASSERT_EQ(sizeof(content_bytes), sizeof(bytes)); + EXPECT_EQ(0, memcmp(content_bytes, bytes, sizeof(content_bytes))); + + // Check read result. + error = 0; + size_t file_size = 0; + EXPECT_TRUE(stream_.Open(logfile_name_, "r", &error)); + EXPECT_TRUE(stream_.GetSize(&file_size)); + EXPECT_EQ(0, error); + EXPECT_EQ(sizeof(bytes), file_size); + scoped_ptr read_bytes(new uint8_t[file_size]); + size_t num_read_bytes = 0; + error = 0; + EXPECT_EQ(SR_SUCCESS, stream_.ReadAll(read_bytes.get(), file_size, + &num_read_bytes, &error)); + EXPECT_EQ(sizeof(bytes), num_read_bytes); + EXPECT_EQ(0, memcmp(bytes, read_bytes.get(), file_size)); +} + +TEST_F(CircularFileStreamTest, ReadWriteOverCapacity) { + EXPECT_TRUE(is_open_); + // Write contents. + const uint8_t bytes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + size_t written = 0; + int error = 0; + EXPECT_EQ(SR_SUCCESS, + stream_.WriteAll(bytes, sizeof(bytes), &written, &error)); + EXPECT_EQ(0, error); + EXPECT_EQ(written, sizeof(bytes)); + stream_.Close(); + + // Check file contents. + uint8_t content_bytes[kMaxSize] = {}; + scoped_ptr content_stream( + Filesystem::OpenFile(logfile_name_, "r")); + size_t num_content_bytes_read = 0; + EXPECT_TRUE(content_stream); + error = 0; + EXPECT_EQ(SR_SUCCESS, + content_stream->Read(content_bytes, sizeof(content_bytes), + &num_content_bytes_read, &error)); + EXPECT_EQ(sizeof(content_bytes), num_content_bytes_read); + const uint8_t expected_content_bytes[] = { + 1, 2, 3, 4, 5, 6, 13, 14, 15, 10, 11, 12}; + ASSERT_EQ(sizeof(content_bytes), sizeof(expected_content_bytes)); + EXPECT_EQ( + 0, memcmp(expected_content_bytes, content_bytes, sizeof(content_bytes))); + + // Check read result. + error = 0; + size_t file_size = 0; + EXPECT_TRUE(stream_.Open(logfile_name_, "r", &error)); + EXPECT_TRUE(stream_.GetSize(&file_size)); + EXPECT_EQ(0, error); + EXPECT_EQ(sizeof(content_bytes), file_size); + scoped_ptr read_bytes(new uint8_t[file_size]); + size_t num_read_bytes = 0; + error = 0; + EXPECT_EQ(SR_SUCCESS, stream_.ReadAll(read_bytes.get(), file_size, + &num_read_bytes, &error)); + + const uint8_t expected_read_bytes[] = { + 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 15}; + EXPECT_EQ(sizeof(expected_read_bytes), num_read_bytes); + EXPECT_EQ(0, memcmp(expected_read_bytes, read_bytes.get(), file_size)); +} + } // namespace rtc