Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create DataWriter as a base class for JSONWriter #16

Merged
merged 5 commits into from
May 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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