Skip to content

Commit

Permalink
Merge pull request #16 from andrea-prearo/data-writer
Browse files Browse the repository at this point in the history
Create DataWriter as a base class for JSONWriter
  • Loading branch information
Geek-1001 authored May 3, 2017
2 parents 2dcb61c + 1263a5e commit a799eaf
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 131 deletions.
24 changes: 16 additions & 8 deletions Clue.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
objects = {

/* Begin PBXBuildFile section */
AF15549B1EB568F1005D1046 /* DataWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF15549A1EB568F1005D1046 /* DataWriter.swift */; };
AF5314621EB018E8005A5146 /* JSONWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF53145F1EB018E8005A5146 /* JSONWriter.swift */; };
AF5314631EB018E8005A5146 /* JSONWriterError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5314601EB018E8005A5146 /* JSONWriterError.swift */; };
AF5314671EB01931005A5146 /* JSONWriterErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5314651EB01931005A5146 /* JSONWriterErrorTests.swift */; };
AF5314631EB018E8005A5146 /* DataWriterError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5314601EB018E8005A5146 /* DataWriterError.swift */; };
AF5314671EB01931005A5146 /* DataWriterErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5314651EB01931005A5146 /* DataWriterErrorTests.swift */; };
AF5314681EB01931005A5146 /* JSONWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF5314661EB01931005A5146 /* JSONWriterTests.swift */; };
AF67568A1EB674380057CA08 /* DataWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF6756891EB674380057CA08 /* DataWriterTests.swift */; };
AF79EA561EAFCD42002231B9 /* Clue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E272DD821CD5223300F1FECA /* Clue.framework */; };
AF79EA571EAFCD42002231B9 /* Clue.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E272DD821CD5223300F1FECA /* Clue.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E20F23401D30F94700654690 /* CLUInfoModule.h in Headers */ = {isa = PBXBuildFile; fileRef = E20F233F1D30F94700654690 /* CLUInfoModule.h */; };
Expand Down Expand Up @@ -201,10 +203,12 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
AF15549A1EB568F1005D1046 /* DataWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataWriter.swift; sourceTree = "<group>"; };
AF53145F1EB018E8005A5146 /* JSONWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONWriter.swift; sourceTree = "<group>"; };
AF5314601EB018E8005A5146 /* JSONWriterError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONWriterError.swift; sourceTree = "<group>"; };
AF5314651EB01931005A5146 /* JSONWriterErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONWriterErrorTests.swift; sourceTree = "<group>"; };
AF5314601EB018E8005A5146 /* DataWriterError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataWriterError.swift; sourceTree = "<group>"; };
AF5314651EB01931005A5146 /* DataWriterErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataWriterErrorTests.swift; sourceTree = "<group>"; };
AF5314661EB01931005A5146 /* JSONWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONWriterTests.swift; sourceTree = "<group>"; };
AF6756891EB674380057CA08 /* DataWriterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataWriterTests.swift; sourceTree = "<group>"; };
E20F233F1D30F94700654690 /* CLUInfoModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUInfoModule.h; sourceTree = "<group>"; };
E20F23411D30FAB100654690 /* CLUDeviceInfoModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLUDeviceInfoModule.h; sourceTree = "<group>"; };
E20F23421D30FAB100654690 /* CLUDeviceInfoModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLUDeviceInfoModule.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -676,7 +680,8 @@
E2A30C8F1CED0BF800A48C61 /* CLUVideoWriter.h */,
E2A30C901CED0BF800A48C61 /* CLUVideoWriter.m */,
AF53145F1EB018E8005A5146 /* JSONWriter.swift */,
AF5314601EB018E8005A5146 /* JSONWriterError.swift */,
AF15549A1EB568F1005D1046 /* DataWriter.swift */,
AF5314601EB018E8005A5146 /* DataWriterError.swift */,
);
path = Writers;
sourceTree = "<group>";
Expand All @@ -697,7 +702,8 @@
isa = PBXGroup;
children = (
E2AD27951E729B200062FE3D /* CLUVideoWriterTests.m */,
AF5314651EB01931005A5146 /* JSONWriterErrorTests.swift */,
AF6756891EB674380057CA08 /* DataWriterTests.swift */,
AF5314651EB01931005A5146 /* DataWriterErrorTests.swift */,
AF5314661EB01931005A5146 /* JSONWriterTests.swift */,
);
path = Writers;
Expand Down Expand Up @@ -1044,7 +1050,7 @@
E2CDA6A81D3905D60079F784 /* sha1.c in Sources */,
AF5314621EB018E8005A5146 /* JSONWriter.swift in Sources */,
E232BA931CF830DC00F5DB52 /* UILabel+CLUViewRecordableAdditions.m in Sources */,
AF5314631EB018E8005A5146 /* JSONWriterError.swift in Sources */,
AF5314631EB018E8005A5146 /* DataWriterError.swift in Sources */,
E29CE2AA1CF7994C00C27384 /* CLUViewStructureModule.m in Sources */,
E2CDA6981D3905D60079F784 /* aeskey.c in Sources */,
E2CDA6B31D3905D60079F784 /* zip.c in Sources */,
Expand All @@ -1067,6 +1073,7 @@
E2ABF6481D424531006646B3 /* CLURecordIndicatorView.m in Sources */,
E232BA8F1CF821D400F5DB52 /* UIView+CLUViewRecordableAdditions.m in Sources */,
E23891401D1BDC3C00D688CD /* NSURLResponse+CLUNetworkAdditions.m in Sources */,
AF15549B1EB568F1005D1046 /* DataWriter.swift in Sources */,
E28CC0961CEE619000C9DFD9 /* ClueController.m in Sources */,
E2ABF64C1D425108006646B3 /* CLURecordIndicatorViewManager.m in Sources */,
E21840D51CFCD7F80053422C /* UIImageView+CLUViewRecordableAdditions.m in Sources */,
Expand All @@ -1087,14 +1094,15 @@
files = (
E2CEADBA1E8FFE8700CEF6AE /* CLUReportFileManagerTests.m in Sources */,
E241F46B1E7BDE000075EF2C /* CLUExceptionAdditions.m in Sources */,
AF67568A1EB674380057CA08 /* DataWriterTests.swift in Sources */,
E241F4671E7B39DE0075EF2C /* CLUErrorNetworkAdditions.m in Sources */,
E24E2D711E74C1AF00804E13 /* CLUUIViewAdditionsTests.m in Sources */,
E241F4631E7B333A0075EF2C /* CLUTextFieldViewRecordableAdditions.m in Sources */,
E28258BF1E7ABCDF000DAAB4 /* CLULabelRecordableAdditions.m in Sources */,
E24E6CD01EAA13C100A2F0C1 /* CLUNSMutableDictionaryUtilsAdditionsTests.m in Sources */,
E241F4691E7B3D9A0075EF2C /* CLUCommunicationNetworkAdditions.m in Sources */,
E241F4651E7B37770075EF2C /* CLUTouchUserInteractionAdditions.m in Sources */,
AF5314671EB01931005A5146 /* JSONWriterErrorTests.swift in Sources */,
AF5314671EB01931005A5146 /* DataWriterErrorTests.swift in Sources */,
E2C604991E80782900A0918D /* CLUTouchTests.m in Sources */,
E24E2D6E1E74899600804E13 /* CLUGeneralGestureRecognizerTests.m in Sources */,
E241F46E1E7BE12D0075EF2C /* CLUObserveModuleTests.m in Sources */,
Expand Down
9 changes: 4 additions & 5 deletions Clue/Classes/Protocols/CLUInfoModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
//

#import <Foundation/Foundation.h>

@class JSONWriter;
#import "CLUWritable.h"

/**
`CLUInfoModule` protocol describe info modules (like Device Info module or Exception module), static one-time modules which needs to write their data only once during recording.
Expand All @@ -20,12 +19,12 @@
@required

/**
Initialize info module with specific `JSONWriter` instance. So module will be able to record/write required information.
Initialize info module with specific writer which implements `CLUWritable` protocol. So module will be able to record/write required information.
@param writer `JSONWriter` instance responsible for actual writing information to some specific file
@param writer Writer object which implements `CLUWritable` protocol. Responsible for actual writing information to some specific file
@return New instance of info module
*/
- (instancetype)initWithWriter:(JSONWriter *)writer;
- (instancetype)initWithWriter:(id <CLUWritable>)writer;

/**
Record actual information once and cleanup everything
Expand Down
101 changes: 101 additions & 0 deletions Clue/Classes/Writers/DataWriter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// DataWriter.swift
// Clue
//
// Created by Andrea Prearo on 4/29/17.
// Copyright © 2017 Ahmed Sulaiman. All rights reserved.
//

import Foundation

/// The `DataWriter` class encapsulates the details of writing data to a stream.
public class DataWriter: NSObject {
let outputStream: OutputStream
var currentError: DataWriterError?

/// The current error.
public var error: DataWriterError? {
if let currentError = currentError {
return currentError
}
guard let streamError = outputStream.streamError else {
return nil
}
return DataWriterError.error(streamError)
}

/// Initializes an output stream.
///
/// - Parameter outputURL: The URL for the output stream.
/// - Returns: An initialized output stream for writing to a specified URL.
public init?(outputURL: URL) {
guard let outputStream = OutputStream(url: outputURL, append: true) else {
return nil
}
self.outputStream = outputStream
super.init()
self.outputStream.delegate = self
}

deinit {
finishWriting()
}

/// Appends data to the stream.
///
/// - Parameter data: The data to be written.
/// - Returns: The number of bytes that were written. In case the return value
/// is zero, the `error` property will contain the current error.
@discardableResult
public func append(data: Data) -> Int {
if !isReadyForWriting() {
startWriting()
}
let bytes = data.withUnsafeBytes { outputStream.write($0, maxLength: data.count) }
guard bytes > 0 else {
handleStreamError()
return bytes
}

return bytes
}
}

// MARK: - DataWriter + StreamDelegate
extension DataWriter: StreamDelegate {
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.errorOccurred:
handleStreamError()
default:
return
}
}
}

// MARK: - DataWriter + CLUWritable
extension DataWriter: CLUWritable {
public func isReadyForWriting() -> Bool {
return outputStream.streamStatus == .open
}

public func startWriting() {
if outputStream.streamStatus != .open {
outputStream.open()
}
}

public func finishWriting() {
if outputStream.streamStatus != .closed {
outputStream.close()
}
}
}

// MARK: - Internal Methods
extension DataWriter {
func handleStreamError() {
let error = outputStream.streamError ?? currentError
print("Stream error: \(String(describing: error?.localizedDescription))")
}
}
66 changes: 66 additions & 0 deletions Clue/Classes/Writers/DataWriterError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// DataWriterError.swift
// Clue
//
// Created by Andrea Prearo on 4/25/17.
// Copyright © 2017 Ahmed Sulaiman. All rights reserved.
//

import Foundation

/// An error that can be returned from a `DataWriter` instance.
///
/// - error: Internal error.
/// - failure: Internal failure.
/// - invalidData: Invalid data.
/// - invalidJSON: Invalid JSON content.
/// - unknown: Unknown error.
public enum DataWriterError: Error {
case error(Error)
case failure(NSError)
case invalidData(Data)
case invalidJSON(Any)
case unknown
}

extension DataWriterError: Equatable {}

public func == (lhs: DataWriterError, rhs: DataWriterError) -> Bool {
switch lhs {
case .error(let error):
switch rhs {
case .error(let error2):
return String(describing: error) == String(describing: error2)
default:
return false
}
case .failure(let error):
switch rhs {
case .failure(let error2):
return error == error2
default:
return false
}
case .invalidData(let data):
switch rhs {
case .invalidData(let data2):
return data == data2
default:
return false
}
case .invalidJSON(let json):
switch rhs {
case .invalidJSON(let json2):
return String(describing: json) == String(describing: json2)
default:
return false
}
case .unknown:
switch rhs {
case .unknown:
return true
default:
return false
}
}
}
79 changes: 13 additions & 66 deletions Clue/Classes/Writers/JSONWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,17 @@

import Foundation

public class JSONWriter: NSObject {
fileprivate let outputStream: OutputStream
fileprivate var currentError: JSONWriterError?

public var error: JSONWriterError? {
return currentError
}

public init?(outputURL: URL) {
guard let outputStream = OutputStream(url: outputURL, append: true) else {
return nil
}
self.outputStream = outputStream
super.init()
self.outputStream.delegate = self
}

deinit {
finishWriting()
}

/// The `JSONWriter` class encapsulates the details of writing JSON data to a stream.
public class JSONWriter: DataWriter {
/// Appends JSON content.
///
/// - Parameter json: The JSON content to append.
/// - Returns: The number of bytes that were appended. In case the return value
/// is zero, the `error` property will contain the current error.
@discardableResult
public func append(json: Any) -> Int {
guard JSONSerialization.isValidJSONObject(json) else {
currentError = JSONWriterError.invalidObject(json)
currentError = DataWriterError.invalidJSON(json)
handleStreamError()
return 0
}
Expand All @@ -43,66 +29,27 @@ public class JSONWriter: NSObject {
let bytes = JSONSerialization.writeJSONObject(json, to: outputStream, options: [], error: &error)
guard bytes > 0 else {
if let error = error {
currentError = JSONWriterError.failure(error)
currentError = DataWriterError.failure(error)
} else {
currentError = JSONWriterError.unknown
currentError = DataWriterError.unknown
}
handleStreamError()
return bytes
}

let lineSeparator = "\n"
guard let stringData = lineSeparator.data(using: .utf8) else {
currentError = JSONWriterError.invalidObject(lineSeparator)
currentError = DataWriterError.invalidJSON(lineSeparator)
handleStreamError()
return bytes
}
let lineSeparatorBytes = stringData.withUnsafeBytes { outputStream.write($0, maxLength: stringData.count) }
let lineSeparatorBytes = append(data: stringData)
if lineSeparatorBytes != 1 {
currentError = JSONWriterError.unknown
currentError = DataWriterError.unknown
handleStreamError()
return bytes
}

return bytes + lineSeparatorBytes
}
}

// MARK: - JSONWriter + StreamDelegate
extension JSONWriter: StreamDelegate {
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.errorOccurred:
handleStreamError()
default:
return
}
}
}

// MARK: - JSONWriter + CLUWritable
extension JSONWriter: CLUWritable {
public func isReadyForWriting() -> Bool {
return outputStream.streamStatus == .open
}

public func startWriting() {
if outputStream.streamStatus != .open {
outputStream.open()
}
}

public func finishWriting() {
if outputStream.streamStatus != .closed {
outputStream.close()
}
}
}

// MARK: - Private Methods
fileprivate extension JSONWriter {
func handleStreamError() {
let error = outputStream.streamError ?? currentError
print("Stream error: \(String(describing: error?.localizedDescription))")
}
}
Loading

0 comments on commit a799eaf

Please sign in to comment.