Remove CircularFileStream / replace it with CallSessionFileRotatingStream.

BUG=4838, 4839

Review URL: https://codereview.webrtc.org/1245143005

Cr-Commit-Position: refs/heads/master@{#9628}
diff --git a/talk/app/webrtc/objc/RTCFileLogger.mm b/talk/app/webrtc/objc/RTCFileLogger.mm
index b474d7a..3080ebc 100644
--- a/talk/app/webrtc/objc/RTCFileLogger.mm
+++ b/talk/app/webrtc/objc/RTCFileLogger.mm
@@ -28,45 +28,19 @@
 #import "RTCFileLogger.h"
 
 #include "webrtc/base/checks.h"
+#include "webrtc/base/filerotatingstream.h"
 #include "webrtc/base/logging.h"
+#include "webrtc/base/logsinks.h"
 #include "webrtc/base/scoped_ptr.h"
-#include "webrtc/base/stream.h"
 
-NSString *const kDefaultLogFileName = @"webrtc.log";
+NSString *const kDefaultLogDirName = @"webrtc_logs";
 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<CircularFileStream> _stream;
-};
-
-} // namespace rtc
-
 @implementation RTCFileLogger {
   BOOL _hasStarted;
-  NSString *_filePath;
+  NSString *_dirPath;
   NSUInteger _maxFileSize;
-  rtc::scoped_ptr<rtc::CircularFileStreamLogSink> _logSink;
+  rtc::scoped_ptr<rtc::CallSessionFileRotatingLogSink> _logSink;
 }
 
 @synthesize severity = _severity;
@@ -75,18 +49,34 @@
   NSArray *paths = NSSearchPathForDirectoriesInDomains(
       NSDocumentDirectory, NSUserDomainMask, YES);
   NSString *documentsDirPath = [paths firstObject];
-  NSString *defaultFilePath =
-      [documentsDirPath stringByAppendingPathComponent:kDefaultLogFileName];
-  return [self initWithFilePath:defaultFilePath
-                    maxFileSize:kDefaultMaxFileSize];
+  NSString *defaultDirPath =
+      [documentsDirPath stringByAppendingPathComponent:kDefaultLogDirName];
+  return [self initWithDirPath:defaultDirPath
+                   maxFileSize:kDefaultMaxFileSize];
 }
 
-- (instancetype)initWithFilePath:(NSString *)filePath
-                     maxFileSize:(NSUInteger)maxFileSize {
-  NSParameterAssert(filePath.length);
+- (instancetype)initWithDirPath:(NSString *)dirPath
+                    maxFileSize:(NSUInteger)maxFileSize {
+  NSParameterAssert(dirPath.length);
   NSParameterAssert(maxFileSize);
   if (self = [super init]) {
-    _filePath = filePath;
+    BOOL isDir = NO;
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    if ([fileManager fileExistsAtPath:dirPath isDirectory:&isDir]) {
+      if (!isDir) {
+        // Bail if something already exists there.
+        return nil;
+      }
+    } else {
+      if (![fileManager createDirectoryAtPath:dirPath
+                  withIntermediateDirectories:NO
+                                   attributes:nil
+                                        error:nil]) {
+        // Bail if we failed to create a directory.
+        return nil;
+      }
+    }
+    _dirPath = dirPath;
     _maxFileSize = maxFileSize;
     _severity = kRTCFileLoggerSeverityInfo;
   }
@@ -101,19 +91,14 @@
   if (_hasStarted) {
     return;
   }
-  rtc::scoped_ptr<rtc::CircularFileStream> 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(new rtc::CallSessionFileRotatingLogSink(_dirPath.UTF8String,
+                                                         _maxFileSize));
+  if (!_logSink->Init()) {
+    LOG(LS_ERROR) << "Failed to open log files at path: "
+                  << _dirPath.UTF8String;
     _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]);
@@ -127,93 +112,35 @@
   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();
-  }
+  _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];
+  NSMutableData* logData = [NSMutableData data];
+  rtc::scoped_ptr<rtc::CallSessionFileRotatingStream> stream(
+      new rtc::CallSessionFileRotatingStream(_dirPath.UTF8String));
+  if (!stream->Open()) {
+    return logData;
   }
-  return [self reorderedLogData];
+  size_t bufferSize = 0;
+  if (!stream->GetSize(&bufferSize) || bufferSize == 0) {
+    return logData;
+  }
+  size_t read = 0;
+  // Allocate memory using malloc so we can pass it direcly to NSData without
+  // copying.
+  rtc::scoped_ptr<uint8_t[]> buffer(static_cast<uint8_t*>(malloc(bufferSize)));
+  stream->ReadAll(buffer.get(), bufferSize, &read, nullptr);
+  logData = [[NSMutableData alloc] initWithBytesNoCopy:buffer.release()
+                                                length:read];
+  return logData;
 }
 
 #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<uint8_t[]> buffer(static_cast<uint8_t*>(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: