Browse files

Lot of changes and new features:

- Geo module refactored
- Interval execution triggered by location updates
- Significant location changes added to check missing status
- Added NSLogger as logger framework
- And much much more...
  • Loading branch information...
1 parent 43b516c commit 9f0c9f0d579c931049641718f0a841ca16e4f60e Carlos Yaconi H committed Jan 21, 2011
View
21 AlarmModule.h
@@ -0,0 +1,21 @@
+//
+// AlarmModule.h
+// Prey
+//
+// Created by Carlos Yaconi on 19/10/2010.
+// Copyright 2010 Fork Ltd. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <AudioToolbox/AudioServices.h>
+#import "PreyModule.h"
+
+@interface AlarmModule : PreyModule {
+ CFURLRef soundFileURLRef;
+ SystemSoundID soundFileObject;
+}
+
+@property (readwrite) CFURLRef soundFileURLRef;
+@property (readonly) SystemSoundID soundFileObject;
+
+@end
View
37 AlarmModule.m
@@ -0,0 +1,37 @@
+//
+// AlarmModule.m
+// Prey
+//
+// Created by Carlos Yaconi on 19/10/2010.
+// Copyright 2010 Fork Ltd. All rights reserved.
+//
+
+#import "AlarmModule.h"
+
+
+@implementation AlarmModule
+
+@synthesize soundFileURLRef;
+@synthesize soundFileObject;
+
+- (void)main {
+ NSURL *tapSound = [[NSBundle mainBundle] URLForResource: @"siren" withExtension: @"mp3"];
+
+ // Store the URL as a CFURLRef instance
+ self.soundFileURLRef = (CFURLRef) [tapSound retain];
+
+ // Create a system sound object representing the sound file.
+ AudioServicesCreateSystemSoundID (soundFileURLRef,&soundFileObject);
+ AudioServicesDisposeSystemSoundID (soundFileObject);
+ CFRelease (soundFileURLRef);
+ LogMessageCompat(@"Playing the alarm now!");
+ AudioServicesPlaySystemSound (soundFileObject);
+
+ AudioServicesDisposeSystemSoundID (soundFileObject);
+}
+
+- (NSString *) getName {
+ return @"alarm";
+}
+
+@end
View
16 AlertModule.h
@@ -0,0 +1,16 @@
+//
+// AlertModule.h
+// Prey
+//
+// Created by Carlos Yaconi on 19/10/2010.
+// Copyright 2010 Fork Ltd. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "PreyModule.h"
+
+@interface AlertModule : PreyModule {
+
+}
+
+@end
View
25 AlertModule.m
@@ -0,0 +1,25 @@
+//
+// AlertModule.m
+// Prey
+//
+// Created by Carlos Yaconi on 19/10/2010.
+// Copyright 2010 Fork Ltd. All rights reserved.
+//
+
+#import "AlertModule.h"
+#import "PreyAppDelegate.h"
+
+
+@implementation AlertModule
+
+- (void)main {
+ PreyAppDelegate *appDelegate = (PreyAppDelegate*)[[UIApplication sharedApplication] delegate];
+ NSString *alertMessage = [self.configParms objectForKey:@"alert_message"];
+ [appDelegate showAlert:alertMessage];
+ [alertMessage release];
+}
+
+- (NSString *) getName {
+ return @"alert";
+}
+@end
View
23 AlertModuleController.h
@@ -0,0 +1,23 @@
+//
+// AlertModuleController.h
+// Prey
+//
+// Created by Carlos Yaconi on 19/10/2010.
+// Copyright 2010 Fork Ltd. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+
+@interface AlertModuleController : UIViewController {
+
+ UILabel *preyName;
+ UILabel *text;
+ NSString *textToShow;
+}
+
+@property (nonatomic, retain) IBOutlet UILabel *preyName;
+@property (nonatomic, retain) IBOutlet UILabel *text;
+@property (nonatomic, retain) NSString *textToShow;
+
+@end
View
64 AlertModuleController.m
@@ -0,0 +1,64 @@
+//
+// AlertModuleController.m
+// Prey
+//
+// Created by Carlos Yaconi on 19/10/2010.
+// Copyright 2010 Fork Ltd. All rights reserved.
+//
+
+#import "AlertModuleController.h"
+
+
+@implementation AlertModuleController
+
+@synthesize preyName, text, textToShow;
+
+/*
+ // The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
+ // Custom initialization
+ }
+ return self;
+}
+*/
+
+
+// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [[UIApplication sharedApplication] setStatusBarHidden:YES];
+ [preyName setFont:[UIFont fontWithName:@"large9" size:60]];
+ [text setText:textToShow];
+ LogMessageCompat(@"Text: %@",text.text);
+}
+
+
+/*
+// Override to allow orientations other than the default portrait orientation.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+ // Return YES for supported orientations
+ return (interfaceOrientation == UIInterfaceOrientationPortrait);
+}
+*/
+
+- (void)didReceiveMemoryWarning {
+ // Releases the view if it doesn't have a superview.
+ [super didReceiveMemoryWarning];
+
+ // Release any cached data, images, etc that aren't in use.
+}
+
+- (void)viewDidUnload {
+ [super viewDidUnload];
+ // Release any retained subviews of the main view.
+ // e.g. self.myOutlet = nil;
+}
+
+
+- (void)dealloc {
+ [super dealloc];
+}
+
+
+@end
View
460 AlertModuleController.xib
@@ -0,0 +1,460 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1024</int>
+ <string key="IBDocument.SystemVersion">10F569</string>
+ <string key="IBDocument.InterfaceBuilderVersion">804</string>
+ <string key="IBDocument.AppKitVersion">1038.29</string>
+ <string key="IBDocument.HIToolboxVersion">461.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="NS.object.0">123</string>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <integer value="1"/>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBProxyObject" id="372490531">
+ <string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBProxyObject" id="975951072">
+ <string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIView" id="191373211">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">274</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUILabel" id="922632844">
+ <reference key="NSNextResponder" ref="191373211"/>
+ <int key="NSvFlags">266</int>
+ <string key="NSFrame">{{20, 371}, {280, 69}}</string>
+ <reference key="NSSuperview" ref="191373211"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <int key="IBUIContentMode">4</int>
+ <bool key="IBUIUserInteractionEnabled">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <string key="IBUIText">Prey</string>
+ <object class="NSColor" key="IBUITextColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MSAxIDEAA</bytes>
+ <object class="NSColorSpace" key="NSCustomColorSpace" id="176713601">
+ <int key="NSID">1</int>
+ </object>
+ </object>
+ <object class="NSColor" key="IBUIHighlightedColor" id="933626152">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ </object>
+ <int key="IBUIBaselineAdjustment">1</int>
+ <float key="IBUIMinimumFontSize">10</float>
+ <int key="IBUITextAlignment">1</int>
+ <int key="IBUILineBreakMode">0</int>
+ </object>
+ <object class="IBUILabel" id="546349409">
+ <reference key="NSNextResponder" ref="191373211"/>
+ <int key="NSvFlags">258</int>
+ <string key="NSFrame">{{20, 102}, {280, 211}}</string>
+ <reference key="NSSuperview" ref="191373211"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <int key="IBUIContentMode">7</int>
+ <bool key="IBUIUserInteractionEnabled">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <string key="IBUIText">Label</string>
+ <object class="NSFont" key="IBUIFont">
+ <string key="NSName">Helvetica</string>
+ <double key="NSSize">20</double>
+ <int key="NSfFlags">16</int>
+ </object>
+ <object class="NSColor" key="IBUITextColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MSAxIDEAA</bytes>
+ <reference key="NSCustomColorSpace" ref="176713601"/>
+ </object>
+ <reference key="IBUIHighlightedColor" ref="933626152"/>
+ <int key="IBUIBaselineAdjustment">1</int>
+ <float key="IBUIMinimumFontSize">20</float>
+ <int key="IBUINumberOfLines">9</int>
+ <int key="IBUITextAlignment">1</int>
+ <int key="IBUILineBreakMode">0</int>
+ </object>
+ </object>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MCAwIDAAA</bytes>
+ </object>
+ <object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics">
+ <int key="IBUIStatusBarStyle">1</int>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">view</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="191373211"/>
+ </object>
+ <int key="connectionID">3</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">preyName</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="922632844"/>
+ </object>
+ <int key="connectionID">5</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">text</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="546349409"/>
+ </object>
+ <int key="connectionID">7</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <reference key="object" ref="0"/>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">1</int>
+ <reference key="object" ref="191373211"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="922632844"/>
+ <reference ref="546349409"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="372490531"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="975951072"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">4</int>
+ <reference key="object" ref="922632844"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">6</int>
+ <reference key="object" ref="546349409"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.CustomClassName</string>
+ <string>-2.CustomClassName</string>
+ <string>1.IBEditorWindowLastContentRect</string>
+ <string>1.IBPluginDependency</string>
+ <string>4.IBPluginDependency</string>
+ <string>4.IBViewBoundsToFrameTransform</string>
+ <string>6.IBPluginDependency</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>AlertModuleController</string>
+ <string>UIResponder</string>
+ <string>{{577, 1097}, {320, 480}}</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <object class="NSAffineTransform">
+ <bytes key="NSTransformStruct">P4AAAL+AAABBoAAAw9sAAA</bytes>
+ </object>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">7</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">AlertModuleController</string>
+ <string key="superclassName">UIViewController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>preyName</string>
+ <string>text</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>UILabel</string>
+ <string>UILabel</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>preyName</string>
+ <string>text</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">preyName</string>
+ <string key="candidateClassName">UILabel</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">text</string>
+ <string key="candidateClassName">UILabel</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">AlertModuleController.h</string>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSError.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSObject.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSThread.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURL.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UIAccessibility.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UINibLoading.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="557061388">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UIResponder.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UILabel</string>
+ <string key="superclassName">UIView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UILabel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIResponder</string>
+ <string key="superclassName">NSObject</string>
+ <reference key="sourceIdentifier" ref="557061388"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UISearchBar</string>
+ <string key="superclassName">UIView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UISearchBar.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UISearchDisplayController</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UISearchDisplayController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UITextField.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIView</string>
+ <string key="superclassName">UIResponder</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UIView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIViewController</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UINavigationController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIViewController</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UIPopoverController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIViewController</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UISplitViewController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIViewController</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UITabBarController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">UIViewController</string>
+ <string key="superclassName">UIResponder</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">UIKit.framework/Headers/UIViewController.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <integer value="1024" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <string key="IBDocument.LastKnownRelativeProjectPath">Prey.xcodeproj</string>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <string key="IBCocoaTouchPluginVersion">123</string>
+ </data>
+</archive>
View
226 Classes/LoggerClient.h
@@ -0,0 +1,226 @@
+/*
+ * LoggerClient.h
+ *
+ * version 1.0b10 2011-01-13
+ *
+ * Part of NSLogger (client side)
+ *
+ * BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Copyright (c) 2010 Florent Pillet <fpillet@gmail.com> All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 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. Neither the name of Florent
+ * Pillet nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "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 COPYRIGHT
+ * HOLDER OR CONTRIBUTORS 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 <unistd.h>
+#import <pthread.h>
+#import <libkern/OSAtomic.h>
+#import <Foundation/Foundation.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+#if !TARGET_OS_IPHONE
+#import <CoreServices/CoreServices.h>
+#endif
+
+// This define is here so that user application can test whether NSLogger Client is
+// being included in the project, and potentially configure their macros accordingly
+#define NSLOGGER_WAS_HERE 1
+
+// Set this to 0 if you absolutely NOT want any access to Cocoa (Objective-C, NS* calls)
+// We need a couple ones to reliably obtain the thread number and device information
+// Note that since we need NSAutoreleasePool when using Cocoa in the logger's worker thread,
+// we need to put Cocoa in multithreading mode. Also, ALLOW_COCOA_USE allows the client code
+// to use NSLog()-style message formatting (less verbose than CFShow()-style) through the
+// use of -[NSString stringWithFormat:arguments:]
+#define ALLOW_COCOA_USE 1
+
+/* -----------------------------------------------------------------
+ * Logger option flags & default options
+ * -----------------------------------------------------------------
+ */
+enum {
+ kLoggerOption_LogToConsole = 0x01,
+ kLoggerOption_BufferLogsUntilConnection = 0x02,
+ kLoggerOption_BrowseBonjour = 0x04,
+ kLoggerOption_BrowseOnlyLocalDomain = 0x08,
+ kLoggerOption_UseSSL = 0x10
+};
+
+#define LOGGER_DEFAULT_OPTIONS (kLoggerOption_BufferLogsUntilConnection | \
+ kLoggerOption_BrowseBonjour | \
+ kLoggerOption_BrowseOnlyLocalDomain | \
+ kLoggerOption_UseSSL)
+
+/* -----------------------------------------------------------------
+ * Structure defining a Logger
+ * -----------------------------------------------------------------
+ */
+typedef struct
+{
+ CFStringRef bufferFile; // If non-NULL, all buffering is done to the specified file instead of in-memory
+ CFStringRef host; // Viewer host to connect to (instead of using Bonjour)
+ UInt32 port; // port on the viewer host
+
+ CFMutableArrayRef bonjourServiceBrowsers; // Active service browsers
+ CFMutableArrayRef bonjourServices; // Services being tried
+ CFNetServiceBrowserRef bonjourDomainBrowser; // Domain browser
+
+ CFMutableArrayRef logQueue; // Message queue
+ pthread_mutex_t logQueueMutex;
+ pthread_cond_t logQueueEmpty;
+
+ pthread_t workerThread; // The worker thread responsible for Bonjour resolution, connection and logs transmission
+ CFRunLoopSourceRef messagePushedSource; // A message source that fires on the worker thread when messages are available for send
+ CFRunLoopSourceRef bufferFileChangedSource; // A message source that fires on the worker thread when the buffer file configuration changes
+
+ CFWriteStreamRef logStream; // The connected stream we're writing to
+ CFWriteStreamRef bufferWriteStream; // If bufferFile not NULL and we're not connected, points to a stream for writing log data
+ CFReadStreamRef bufferReadStream; // If bufferFile not NULL, points to a read stream that will be emptied prior to sending the rest of in-memory messages
+
+ SCNetworkReachabilityRef reachability; // The reachability object we use to determine when the target host becomes reachable
+ CFRunLoopTimerRef checkHostTimer; // A timer to regularly check connection to the defined host, along with reachability for added reliability
+
+ uint8_t *sendBuffer; // data waiting to be sent
+ NSUInteger sendBufferSize;
+ NSUInteger sendBufferUsed; // number of bytes of the send buffer currently in use
+ NSUInteger sendBufferOffset; // offset in sendBuffer to start sending at
+
+ int32_t messageSeq; // sequential message number (added to each message sent)
+
+ // settings
+ uint32_t options; // Flags, see enum above
+ CFStringRef bonjourServiceType; // leave NULL to use the default
+ CFStringRef bonjourServiceName; // leave NULL to use the first one available
+
+ // internal state
+ BOOL connected; // Set to YES once the write stream declares the connection open
+ volatile BOOL quit; // Set to YES to terminate the logger worker thread's runloop
+ BOOL incompleteSendOfFirstItem; // set to YES if we are sending the first item in the queue and it's bigger than what the buffer can hold
+} Logger;
+
+
+/* -----------------------------------------------------------------
+ * LOGGING FUNCTIONS
+ * -----------------------------------------------------------------
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Functions to set and get the default logger
+extern void LoggerSetDefaultLogger(Logger *aLogger);
+extern Logger *LoggerGetDefaultLogger();
+
+// Initialize a new logger, set as default logger if this is the first one
+// Options default to:
+// - logging to console = NO
+// - buffer until connection = YES
+// - browse Bonjour = YES
+// - browse only locally on Bonjour = YES
+extern Logger* LoggerInit();
+
+// Set logger options if you don't want the default options (see above)
+extern void LoggerSetOptions(Logger *logger, uint32_t options);
+
+// Set Bonjour logging names, so you can force the logger to use a specific service type
+// or direct logs to the machine on your network which publishes a specific name
+extern void LoggerSetupBonjour(Logger *logger, CFStringRef bonjourServiceType, CFStringRef bonjourServiceName);
+
+// Directly set the viewer host (hostname or IP address) and port we want to connect to. If set, LoggerStart() will
+// try to connect there first before trying Bonjour
+extern void LoggerSetViewerHost(Logger *logger, CFStringRef hostName, UInt32 port);
+
+
+// Configure the logger to use a local file for buffering, instead of memory.
+// - If you initially set a buffer file after logging started but while a logger connection
+// has not been acquired, the contents of the log queue will be written to the buffer file
+// the next time a logging function is called, or when LoggerStop() is called.
+// - If you want to change the buffering file after logging started, you should first
+// call LoggerStop() the call LoggerSetBufferFile(). Note that all logs stored in the previous
+// buffer file WON'T be transferred to the new file in this case.
+extern void LoggerSetBufferFile(Logger *logger, CFStringRef absolutePath);
+
+// Activate the logger, try connecting
+extern void LoggerStart(Logger *logger);
+
+//extern void LoggerConnectToHost(CFDataRef address, int port);
+
+// Deactivate and free the logger.
+extern void LoggerStop(Logger *logger);
+
+// Pause the current thread until all messages from the logger have been transmitted
+// this is useful to use before an assert() aborts your program. If waitForConnection is YES,
+// LoggerFlush() will block even if the client is not currently connected to the desktop
+// viewer. You should be using NO most of the time, but in some cases it can be useful.
+extern void LoggerFlush(Logger *logger, BOOL waitForConnection);
+
+/* Logging functions. Each function exists in four versions:
+ *
+ * - one without a Logger instance (uses default logger) and without filename/line/function (no F suffix)
+ * - one without a Logger instance but with filename/line/function (F suffix)
+ * - one with a Logger instance (use a specific Logger) and without filename/line/function (no F suffix)
+ * - one with a Logger instance (use a specific Logger) and with filename/line/function (F suffix)
+ *
+ * The exception being the single LogMessageCompat() function which is designed to be a drop-in replacement for NSLog()
+ *
+ */
+
+// Log a message, calling format compatible with NSLog
+extern void LogMessageCompat(NSString *format, ...);
+
+// Log a message. domain can be nil if default domain.
+extern void LogMessage(NSString *domain, int level, NSString *format, ...);
+extern void LogMessageF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...);
+extern void LogMessageTo(Logger *logger, NSString *domain, int level, NSString *format, ...);
+extern void LogMessageToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, ...);
+
+// Log a message. domain can be nil if default domain (versions with va_list format args instead of ...)
+extern void LogMessage_va(NSString *domain, int level, NSString *format, va_list args);
+extern void LogMessageF_va(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args);
+extern void LogMessageTo_va(Logger *logger, NSString *domain, int level, NSString *format, va_list args);
+extern void LogMessageToF_va(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSString *format, va_list args);
+
+// Send binary data to remote logger
+extern void LogData(NSString *domain, int level, NSData *data);
+extern void LogDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data);
+extern void LogDataTo(Logger *logger, NSString *domain, int level, NSData *data);
+extern void LogDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, NSData *data);
+
+// Send image data to remote logger
+extern void LogImageData(NSString *domain, int level, int width, int height, NSData *data);
+extern void LogImageDataF(const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data);
+extern void LogImageDataTo(Logger *logger, NSString *domain, int level, int width, int height, NSData *data);
+extern void LogImageDataToF(Logger *logger, const char *filename, int lineNumber, const char *functionName, NSString *domain, int level, int width, int height, NSData *data);
+
+// Mark the start of a block. This allows the remote logger to group blocks together
+extern void LogStartBlock(NSString *format, ...);
+extern void LogStartBlockTo(Logger *logger, NSString *format, ...);
+
+// Mark the end of a block
+extern void LogEndBlock();
+extern void LogEndBlockTo(Logger *logger);
+
+#ifdef __cplusplus
+};
+#endif
View
2,182 Classes/LoggerClient.m
@@ -0,0 +1,2182 @@
+/*
+ * LoggerClient.m
+ *
+ * version 1.0b10 2011-01-13
+ *
+ * Main implementation of the NSLogger client side code
+ * Part of NSLogger (client side)
+ *
+ * BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Copyright (c) 2010 Florent Pillet <fpillet@gmail.com> All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer. 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. Neither the name of Florent
+ * Pillet nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "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 COPYRIGHT
+ * HOLDER OR CONTRIBUTORS 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 <sys/time.h>
+#if TARGET_OS_MAC
+#import <sys/types.h>
+#import <sys/sysctl.h>
+#import <dlfcn.h>
+#endif
+#import <fcntl.h>
+
+#import "LoggerClient.h"
+#import "LoggerCommon.h"
+
+/* --------------------------------------------------------------------------------
+ * IMPLEMENTATION NOTES:
+ *
+ * The logger runs in a separate thread. It is written
+ * in straight C for maximum compatibility with all runtime environments
+ * (does not use the Objective-C runtime, only uses unix and CoreFoundation
+ * calls, except for get the thread name and device information, but these
+ * can be disabled by setting ALLOW_COCOA_USE to 0).
+ *
+ * It is suitable for use in both Cocoa and low-level code. It does not activate
+ * Cocoa multi-threading (no call to [NSThread detachNewThread...]). You can start
+ * logging very early (as soon as your code starts running), logs will be
+ * buffered and sent to the log viewer as soon as a connection is acquired.
+ * This makes the logger suitable for use in conditions where you usually
+ * don't have a connection to a remote machine yet (early wakeup, network
+ * down, etc).
+ *
+ * When you call one of the public logging functions, the logger is designed
+ * to return to your application as fast as possible. It enqueues logs to
+ * send for processing by its own thread, while your application keeps running.
+ *
+ * The logger does buffer logs while not connected to a desktop
+ * logger. It uses Bonjour to find a logger on the local network, and can
+ * optionally connect to a remote logger identified by an IP address / port
+ * or a Host Name / port.
+ *
+ * The logger can optionally output its log to the console, like NSLog().
+ *
+ * The logger can optionally buffer its logs to a file for which you specify the
+ * full path. Upon connection to the desktop viewer, the file contents are
+ * transmitted to the viewer prior to sending new logs. When the whole file
+ * content has been transmitted, it is emptied.
+ *
+ * Multiple loggers can coexist at the same time. You can perfectly use a
+ * logger for your debug traces, and another that connects remotely to help
+ * diagnostic issues while the application runs on your user's device.
+ *
+ * Using the logger's flexible packet format, you can customize logging by
+ * creating your own log types, and customize the desktop viewer to display
+ * runtime information panels for your application.
+ * --------------------------------------------------------------------------------
+ */
+
+/* Logger internal debug flags */
+// Set to 0 to disable internal debug completely
+// Set to 1 to activate console logs when running the logger itself
+// Set to 2 to see every logging call issued by the app, too
+#define LOGGER_DEBUG 0
+#ifdef NSLog
+ #undef NSLog
+#endif
+
+// Internal debugging stuff for the logger itself
+#if LOGGER_DEBUG
+ #define LOGGERDBG LoggerDbg
+ #if LOGGER_DEBUG > 1
+ #define LOGGERDBG2 LoggerDbg
+ #else
+ #define LOGGERDBG2(format, ...) do{}while(0)
+ #endif
+ // Internal logging function prototype
+ static void LoggerDbg(CFStringRef format, ...);
+#else
+ #define LOGGERDBG(format, ...) do{}while(0)
+ #define LOGGERDBG2(format, ...) do{}while(0)
+#endif
+
+/* Local prototypes */
+static void* LoggerWorkerThread(Logger *logger);
+static void LoggerWriteMoreData(Logger *logger);
+static void LoggerPushMessageToQueue(Logger *logger, CFDataRef message);
+
+// Bonjour management
+static void LoggerStartBonjourBrowsing(Logger *logger);
+static void LoggerStopBonjourBrowsing(Logger *logger);
+static BOOL LoggerBrowseBonjourForServices(Logger *logger, CFStringRef domainName);
+static void LoggerServiceBrowserCallBack(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError* error, void *info);
+
+// Reachability and reconnect timer
+static void LoggerStartReachabilityChecking(Logger *logger);
+static void LoggerStopReachabilityChecking(Logger *logger);
+static void LoggerReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);
+static void LoggerTimedReconnectCallback(CFRunLoopTimerRef timer, void *info);
+
+// Connection & stream management
+static void LoggerTryConnect(Logger *logger);
+static void LoggerWriteStreamCallback(CFWriteStreamRef ws, CFStreamEventType event, void* info);
+#if LOGGER_DEBUG
+static void LoggerReadStreamCallback(CFReadStreamRef ws, CFStreamEventType event, void* info);
+#endif
+
+// File buffering
+static void LoggerCreateBufferWriteStream(Logger *logger);
+static void LoggerCreateBufferReadStream(Logger *logger);
+static void LoggerEmptyBufferFile(Logger *logger);
+static void LoggerFileBufferingOptionsChanged(Logger *logger);
+static void LoggerFlushQueueToBufferStream(Logger *logger, BOOL firstEntryIsClientInfo);
+
+// Encoding functions
+static void LoggerPushClientInfoToFrontOfQueue(Logger *logger);
+static void LoggerMessageAddTimestampAndThreadID(CFMutableDataRef encoder);
+static void LogDataInternal(Logger *logger, NSString *domain, int level, NSData *data, int binaryOrImageType);
+
+static CFMutableDataRef LoggerMessageCreate();
+static void LoggerMessageUpdateDataHeader(CFMutableDataRef data);
+static void LoggerMessageAddInt16(CFMutableDataRef data, int16_t anInt, int key);
+static void LoggerMessageAddInt32(CFMutableDataRef data, int32_t anInt, int key);
+static void LoggerMessageAddInt64(CFMutableDataRef data, int64_t anInt, int key);
+static void LoggerMessageAddString(CFMutableDataRef data, CFStringRef aString, int key);
+static void LoggerMessageAddData(CFMutableDataRef data, CFDataRef theData, int key, int partType);
+static uint32_t LoggerMessageGetSeq(CFDataRef message);
+
+/* Static objects */
+static Logger* volatile sDefaultLogger = NULL;
+static pthread_mutex_t sDefaultLoggerMutex = PTHREAD_MUTEX_INITIALIZER;
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Default logger
+// -----------------------------------------------------------------------------
+void LoggerSetDefautLogger(Logger *defaultLogger)
+{
+ pthread_mutex_lock(&sDefaultLoggerMutex);
+ sDefaultLogger = defaultLogger;
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+}
+
+Logger *LoggerGetDefaultLogger()
+{
+ if (sDefaultLogger == NULL)
+ {
+ pthread_mutex_lock(&sDefaultLoggerMutex);
+ Logger *logger = LoggerInit();
+ if (sDefaultLogger == NULL)
+ {
+ sDefaultLogger = logger;
+ logger = NULL;
+ }
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+ if (logger != NULL)
+ LoggerStop(logger);
+ }
+ return sDefaultLogger;
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Initialization and setup
+// -----------------------------------------------------------------------------
+Logger *LoggerInit()
+{
+ LOGGERDBG(CFSTR("LoggerInit defaultLogger=%p"), sDefaultLogger);
+
+ Logger *logger = (Logger *)malloc(sizeof(Logger));
+ bzero(logger, sizeof(Logger));
+
+ logger->logQueue = CFArrayCreateMutable(NULL, 32, &kCFTypeArrayCallBacks);
+ pthread_mutex_init(&logger->logQueueMutex, NULL);
+ pthread_cond_init(&logger->logQueueEmpty, NULL);
+
+ logger->bonjourServiceBrowsers = CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
+ logger->bonjourServices = CFArrayCreateMutable(NULL, 4, &kCFTypeArrayCallBacks);
+
+ // for now we don't grow the send buffer, just use one page of memory which should be enouh
+ // (bigger messages will be sent separately)
+ logger->sendBuffer = (uint8_t *)malloc(4096);
+ logger->sendBufferSize = 4096;
+
+ logger->options = LOGGER_DEFAULT_OPTIONS;
+
+ logger->quit = NO;
+
+ // Set this logger as the default logger is none exist already
+ if (!pthread_mutex_trylock(&sDefaultLoggerMutex))
+ {
+ if (sDefaultLogger == NULL)
+ sDefaultLogger = logger;
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+ }
+
+ return logger;
+}
+
+void LoggerSetOptions(Logger *logger, uint32_t options)
+{
+ LOGGERDBG(CFSTR("LoggerSetOptions options=0x%08lx"), options);
+
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger != NULL)
+ logger->options = options;
+}
+
+void LoggerSetupBonjour(Logger *logger, CFStringRef bonjourServiceType, CFStringRef bonjourServiceName)
+{
+ LOGGERDBG(CFSTR("LoggerSetupBonjour serviceType=%@ serviceName=%@"), bonjourServiceType, bonjourServiceName);
+
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger != NULL)
+ {
+ if (bonjourServiceType != NULL)
+ CFRetain(bonjourServiceType);
+ if (bonjourServiceName != NULL)
+ CFRetain(bonjourServiceName);
+ if (logger->bonjourServiceType != NULL)
+ CFRelease(logger->bonjourServiceType);
+ if (logger->bonjourServiceName != NULL)
+ CFRelease(logger->bonjourServiceName);
+ logger->bonjourServiceType = bonjourServiceType;
+ logger->bonjourServiceName = bonjourServiceName;
+ }
+}
+
+void LoggerSetViewerHost(Logger *logger, CFStringRef hostName, UInt32 port)
+{
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger == NULL)
+ return;
+
+ if (logger->host != NULL)
+ {
+ CFRelease(logger->host);
+ logger->host = NULL;
+ }
+ if (hostName != NULL)
+ {
+ logger->host = CFStringCreateCopy(NULL, hostName);
+ logger->port = port;
+ }
+}
+
+void LoggerSetBufferFile(Logger *logger, CFStringRef absolutePath)
+{
+ if (logger == NULL)
+ {
+ logger = LoggerGetDefaultLogger();
+ if (logger == NULL)
+ return;
+ }
+
+ BOOL change = ((logger->bufferFile != NULL && absolutePath == NULL) ||
+ (logger->bufferFile == NULL && absolutePath != NULL) ||
+ (logger->bufferFile != NULL && absolutePath != NULL && CFStringCompare(logger->bufferFile, absolutePath, 0) != kCFCompareEqualTo));
+ if (change)
+ {
+ if (logger->bufferFile != NULL)
+ {
+ CFRelease(logger->bufferFile);
+ logger->bufferFile = NULL;
+ }
+ if (absolutePath != NULL)
+ logger->bufferFile = CFStringCreateCopy(NULL, absolutePath);
+ if (logger->bufferFileChangedSource != NULL)
+ CFRunLoopSourceSignal(logger->bufferFileChangedSource);
+ }
+}
+
+void LoggerStart(Logger *logger)
+{
+ // will do nothing if logger is already started
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+
+ if (logger->workerThread == NULL)
+ {
+ // Start the work thread which performs the Bonjour search,
+ // connects to the logging service and forwards the logs
+ LOGGERDBG(CFSTR("LoggerStart logger=%p"), logger);
+ pthread_create(&logger->workerThread, NULL, (void *(*)(void *))&LoggerWorkerThread, logger);
+ }
+}
+
+void LoggerStop(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerStop"));
+
+ pthread_mutex_lock(&sDefaultLoggerMutex);
+ if (logger == NULL || logger == sDefaultLogger)
+ {
+ logger = sDefaultLogger;
+ sDefaultLogger = NULL;
+ }
+ pthread_mutex_unlock(&sDefaultLoggerMutex);
+
+ if (logger != NULL)
+ {
+ if (logger->workerThread != NULL)
+ {
+ logger->quit = YES;
+ pthread_join(logger->workerThread, NULL);
+ }
+
+ CFRelease(logger->bonjourServiceBrowsers);
+ CFRelease(logger->bonjourServices);
+ free(logger->sendBuffer);
+ if (logger->host != NULL)
+ CFRelease(logger->host);
+ if (logger->bufferFile != NULL)
+ CFRelease(logger->bufferFile);
+ if (logger->bonjourServiceType != NULL)
+ CFRelease(logger->bonjourServiceType);
+ if (logger->bonjourServiceName != NULL)
+ CFRelease(logger->bonjourServiceName);
+
+ // to make sure potential errors are catched, set the whole structure
+ // to a value that will make code crash if it tries using pointers to it.
+ memset(logger, 0x55, sizeof(logger));
+
+ free(logger);
+ }
+}
+
+void LoggerFlush(Logger *logger, BOOL waitForConnection)
+{
+ // Special case: if nothing has ever been logged, don't bother
+ if (logger == NULL && sDefaultLogger == NULL)
+ return;
+ if (logger == NULL)
+ logger = LoggerGetDefaultLogger();
+ if (logger != NULL &&
+ pthread_self() != logger->workerThread &&
+ (logger->connected || logger->bufferFile != NULL || waitForConnection))
+ {
+ pthread_mutex_lock(&logger->logQueueMutex);
+ if (CFArrayGetCount(logger->logQueue) > 0)
+ pthread_cond_wait(&logger->logQueueEmpty, &logger->logQueueMutex);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ }
+}
+
+static void LoggerDbg(CFStringRef format, ...)
+{
+ // Internal debugging function
+ // (what do you think, that we use the Logger to debug itself ??)
+ if (format != NULL)
+ {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ va_list args;
+ va_start(args, format);
+ CFStringRef s = CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef)format, args);
+ va_end(args);
+ if (s != NULL)
+ {
+ CFShow(s);
+ CFRelease(s);
+ }
+ [pool drain];
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Main processing
+// -----------------------------------------------------------------------------
+static void *LoggerWorkerThread(Logger *logger)
+{
+ LOGGERDBG(CFSTR("Start LoggerWorkerThread"));
+
+ // Register thread with Garbage Collector on Mac OS X if we're running an OS version that has GC
+ void (*registerThreadWithCollector_fn)(void);
+ registerThreadWithCollector_fn = (void(*)(void)) dlsym(RTLD_NEXT, "objc_registerThreadWithCollector");
+ if (registerThreadWithCollector_fn)
+ (*registerThreadWithCollector_fn)();
+
+ // Create and get the runLoop for this thread
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ // Create the run loop source that signals when messages have been added to the runloop
+ // this will directly trigger a WriteMoreData() call, which will or won't write depending
+ // on whether we're connected and there's space available in the stream
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ context.info = logger;
+ context.perform = (void *)&LoggerWriteMoreData;
+ logger->messagePushedSource = CFRunLoopSourceCreate(NULL, 0, &context);
+ if (logger->messagePushedSource == NULL)
+ {
+ // Failing to create the runloop source for pushing messages is a major failure.
+ // This NSLog is intentional. We WANT console output in this case
+ NSLog(@"*** NSLogger: Worker thread failed creating runLoop source, switching to console logging.");
+ logger->options |= kLoggerOption_LogToConsole;
+ logger->workerThread = NULL;
+ return NULL;
+ }
+ CFRunLoopAddSource(runLoop, logger->messagePushedSource, kCFRunLoopDefaultMode);
+
+ // Create the buffering stream if needed
+ if (logger->bufferFile != NULL)
+ LoggerCreateBufferWriteStream(logger);
+
+ // Create the runloop source that lets us know when file buffering options change
+ context.perform = (void *)&LoggerFileBufferingOptionsChanged;
+ logger->bufferFileChangedSource = CFRunLoopSourceCreate(NULL, 0, &context);
+ if (logger->bufferFileChangedSource == NULL)
+ {
+ // This failure MUST be logged to console
+ NSLog(@"*** NSLogger Warning: failed creating a runLoop source for file buffering options change.");
+ }
+ else
+ CFRunLoopAddSource(runLoop, logger->bufferFileChangedSource, kCFRunLoopDefaultMode);
+
+ // Start Bonjour browsing, wait for remote logging service to be found
+ if (logger->host == NULL && (logger->options & kLoggerOption_BrowseBonjour))
+ {
+ LOGGERDBG(CFSTR("-> logger configured for Bonjour, no direct host set -- trying Bonjour first"));
+ LoggerStartBonjourBrowsing(logger);
+ }
+ else if (logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> logger configured with direct host, trying it first"));
+ LoggerTryConnect(logger);
+ }
+
+ // Run logging thread until LoggerStop() is called
+ while (!logger->quit)
+ {
+ int result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, true);
+ if (result == kCFRunLoopRunFinished || result == kCFRunLoopRunStopped)
+ break;
+
+ // Make sure we restart connection attempts if we get disconnected
+ if (!logger->connected &&
+ !CFArrayGetCount(logger->bonjourServices) &&
+ !CFArrayGetCount(logger->bonjourServiceBrowsers) &&
+ !CFArrayGetCount(logger->bonjourServices))
+ {
+ if (logger->options & kLoggerOption_BrowseBonjour)
+ LoggerStartBonjourBrowsing(logger);
+ else if (logger->host != NULL && logger->reachability == NULL && logger->checkHostTimer == NULL)
+ LoggerTryConnect(logger);
+ }
+ }
+
+ // Cleanup
+ if (logger->options & kLoggerOption_BrowseBonjour)
+ LoggerStopBonjourBrowsing(logger);
+ LoggerStopReachabilityChecking(logger);
+
+ if (logger->logStream != NULL)
+ {
+ CFWriteStreamSetClient(logger->logStream, 0, NULL, NULL);
+ CFWriteStreamClose(logger->logStream);
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+ }
+
+ if (logger->bufferWriteStream == NULL && logger->bufferFile != NULL)
+ {
+ // If there are messages in the queue and LoggerStop() was called and
+ // a buffer file was set just before LoggerStop() was called, flush
+ // the log queue to the buffer file
+ pthread_mutex_lock(&logger->logQueueMutex);
+ CFIndex outstandingMessages = CFArrayGetCount(logger->logQueue);
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ if (outstandingMessages)
+ LoggerCreateBufferWriteStream(logger);
+ }
+
+ if (logger->bufferWriteStream != NULL)
+ {
+ CFWriteStreamClose(logger->bufferWriteStream);
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+
+ if (logger->messagePushedSource != NULL)
+ {
+ CFRunLoopSourceInvalidate(logger->messagePushedSource);
+ CFRelease(logger->messagePushedSource);
+ logger->messagePushedSource = NULL;
+ }
+
+ if (logger->bufferFileChangedSource != NULL)
+ {
+ CFRunLoopSourceInvalidate(logger->bufferFileChangedSource);
+ CFRelease(logger->bufferFileChangedSource);
+ logger->bufferFileChangedSource = NULL;
+ }
+
+ // if the client ever tries to log again against us, make sure that logs at least
+ // go to console
+ logger->options |= kLoggerOption_LogToConsole;
+ logger->workerThread = NULL;
+ return NULL;
+}
+
+static CFStringRef LoggerCreateStringRepresentationFromBinaryData(CFDataRef data)
+{
+ CFMutableStringRef s = CFStringCreateMutable(NULL, 0);
+ unsigned int offset = 0;
+ unsigned int dataLen = (unsigned int)CFDataGetLength(data);
+ char buffer[1+6+16*3+1+16+1+1+1];
+ buffer[0] = '\0';
+ const unsigned char *q = (unsigned char *)CFDataGetBytePtr(data);
+ if (dataLen == 1)
+ CFStringAppend(s, CFSTR("Raw data, 1 byte:\n"));
+ else
+ CFStringAppendFormat(s, NULL, CFSTR("Raw data, %u bytes:\n"), dataLen);
+ while (dataLen)
+ {
+ int i, b = sprintf(buffer," %04x: ", offset);
+ for (i=0; i < 16 && i < dataLen; i++)
+ sprintf(&buffer[b+3*i], "%02x ", (int)q[i]);
+ for (int j=i; j < 16; j++)
+ strcat(buffer, " ");
+
+ b = strlen(buffer);
+ buffer[b++] = '\'';
+ for (i=0; i < 16 && i < dataLen; i++, q++)
+ {
+ if (*q >= 32 && *q < 128)
+ buffer[b++] = *q;
+ else
+ buffer[b++] = ' ';
+ }
+ for (int j=i; j < 16; j++)
+ buffer[b++] = ' ';
+ buffer[b++] = '\'';
+ buffer[b++] = '\n';
+ buffer[b] = 0;
+
+ CFStringRef bufferStr = CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)buffer, strlen(buffer), kCFStringEncodingISOLatin1, false, kCFAllocatorNull);
+ CFStringAppend(s, bufferStr);
+ CFRelease(bufferStr);
+
+ dataLen -= i;
+ offset += i;
+ }
+ return s;
+}
+
+static void LoggerLogToConsole(CFDataRef data)
+{
+ // Decode and log a message to the console. Doing this from the worker thread
+ // allow us to serialize logging, which is a benefit that NSLog() doesn't have.
+ // Only drawback is that we have to decode our own message, but that is a minor hassle.
+
+ struct timeval timestamp;
+ bzero(&timestamp, sizeof(timestamp));
+ int type = LOGMSG_TYPE_LOG, contentsType = PART_TYPE_STRING;
+ int imgWidth=0, imgHeight=0;
+ CFStringRef message = NULL;
+ CFStringRef thread = NULL;
+
+ // decode message contents
+ uint8_t *p = (uint8_t *)CFDataGetBytePtr(data) + 4;
+ uint16_t partCount;
+ memcpy(&partCount, p, 2);
+ partCount = ntohs(partCount);
+ p += 2;
+ while (partCount--)
+ {
+ uint8_t partKey = *p++;
+ uint8_t partType = *p++;
+ uint32_t partSize;
+ if (partType == PART_TYPE_INT16)
+ partSize = 2;
+ else if (partType == PART_TYPE_INT32)
+ partSize = 4;
+ else if (partType == PART_TYPE_INT64)
+ partSize = 8;
+ else
+ {
+ memcpy(&partSize, p, 4);
+ p += 4;
+ partSize = ntohl(partSize);
+ }
+ CFTypeRef part = NULL;
+ uint32_t value32 = 0;
+ uint64_t value64 = 0;
+ if (partSize > 0)
+ {
+ if (partType == PART_TYPE_STRING)
+ {
+ // trim whitespace and newline at both ends of the string
+ uint8_t *q = p;
+ uint32_t l = partSize;
+ while (l && (*q == ' ' || *q == '\t' || *q == '\n' || *q == '\r'))
+ q++, l--;
+ uint8_t *r = q + l - 1;
+ while (l && (*r == ' ' || *r == '\t' || *r == '\n' || *r == '\r'))
+ r--, l--;
+ part = CFStringCreateWithBytesNoCopy(NULL, q, l, kCFStringEncodingUTF8, false, kCFAllocatorNull);
+ }
+ else if (partType == PART_TYPE_BINARY)
+ {
+ part = CFDataCreateWithBytesNoCopy(NULL, p, partSize, kCFAllocatorNull);
+ }
+ else if (partType == PART_TYPE_IMAGE)
+ {
+ // ignore image data, we can't log it to console
+ }
+ else if (partType == PART_TYPE_INT32)
+ {
+ memcpy(&value32, p, 4);
+ value32 = ntohl(value32);
+ }
+ else if (partType == PART_TYPE_INT64)
+ {
+ memcpy(&value64, p, 8);
+ value64 = CFSwapInt64BigToHost(value64);
+ }
+ p += partSize;
+ }
+ switch (partKey)
+ {
+ case PART_KEY_MESSAGE_TYPE:
+ type = (int)value32;
+ break;
+ case PART_KEY_TIMESTAMP_S: // timestamp with seconds-level resolution
+ timestamp.tv_sec = (partType == PART_TYPE_INT64) ? (__darwin_time_t)value64 : (__darwin_time_t)value32;
+ break;
+ case PART_KEY_TIMESTAMP_MS: // millisecond part of the timestamp (optional)
+ timestamp.tv_usec = ((partType == PART_TYPE_INT64) ? (__darwin_suseconds_t)value64 : (__darwin_suseconds_t)value32) * 1000;
+ break;
+ case PART_KEY_TIMESTAMP_US: // microsecond part of the timestamp (optional)
+ timestamp.tv_usec = (partType == PART_TYPE_INT64) ? (__darwin_suseconds_t)value64 : (__darwin_suseconds_t)value32;
+ break;
+ case PART_KEY_THREAD_ID:
+ if (thread == NULL) // useless test, we know what we're doing but clang analyzer doesn't...
+ {
+ if (partType == PART_TYPE_INT32)
+ thread = CFStringCreateWithFormat(NULL, NULL, CFSTR("thread 0x%08x"), value32);
+ else if (partType == PART_TYPE_INT64)
+ thread = CFStringCreateWithFormat(NULL, NULL, CFSTR("thread 0x%qx"), value64);
+ else if (partType == PART_TYPE_STRING && part != NULL)
+ thread = CFRetain(part);
+ }
+ break;
+ case PART_KEY_MESSAGE:
+ if (part != NULL)
+ {
+ if (partType == PART_TYPE_STRING)
+ message = CFRetain(part);
+ else if (partType == PART_TYPE_BINARY)
+ message = LoggerCreateStringRepresentationFromBinaryData(part);
+ }
+ contentsType = partType;
+ break;
+ case PART_KEY_IMAGE_WIDTH:
+ imgWidth = (partType == PART_TYPE_INT32 ? (int)value32 : (int)value64);
+ break;
+ case PART_KEY_IMAGE_HEIGHT:
+ imgHeight = (partType == PART_TYPE_INT32 ? (int)value32 : (int)value64);
+ break;
+ default:
+ break;
+ }
+ if (part != NULL)
+ CFRelease(part);
+ }
+
+ // Prepare the final representation and log to console
+ CFMutableStringRef s = CFStringCreateMutable(NULL, 0);
+
+ char buf[32];
+ struct tm t;
+ gmtime_r(&timestamp.tv_sec, &t);
+ strftime(buf, sizeof(buf)-1, "%T", &t);
+ CFStringRef ts = CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)buf, strlen(buf), kCFStringEncodingASCII, false, kCFAllocatorNull);
+ CFStringAppend(s, ts);
+ CFRelease(ts);
+
+ if (contentsType == PART_TYPE_IMAGE)
+ message = CFStringCreateWithFormat(NULL, NULL, CFSTR("<image width=%d height=%d>"), imgWidth, imgHeight);
+
+ char threadNamePadding[16];
+ threadNamePadding[0] = 0;
+ if (CFStringGetLength(thread) < 16)
+ {
+ int n = 16 - CFStringGetLength(thread);
+ memset(threadNamePadding, ' ', n);
+ threadNamePadding[n] = 0;
+ }
+ CFStringAppendFormat(s, NULL, CFSTR(".%04d %s%@ | %@"),
+ (int)(timestamp.tv_usec / 1000),
+ threadNamePadding, thread,
+ message ? message : CFSTR(""));
+
+ if (thread != NULL)
+ CFRelease(thread);
+ if (message)
+ CFRelease(message);
+
+ if (type == LOGMSG_TYPE_LOG)
+ CFShow(s);
+
+ CFRelease(s);
+}
+
+static void LoggerWriteMoreData(Logger *logger)
+{
+ if (!logger->connected)
+ {
+ if (logger->options & kLoggerOption_LogToConsole)
+ {
+ pthread_mutex_lock(&logger->logQueueMutex);
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ LoggerLogToConsole((CFDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0));
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ }
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ pthread_cond_broadcast(&logger->logQueueEmpty);
+ }
+ else if (logger->bufferWriteStream != NULL)
+ {
+ LoggerFlushQueueToBufferStream(logger, NO);
+ }
+ return;
+ }
+
+ if (CFWriteStreamCanAcceptBytes(logger->logStream))
+ {
+ // prepare archived data with log queue contents, unblock the queue as soon as possible
+ CFMutableDataRef sendFirstItem = NULL;
+ if (logger->sendBufferUsed == 0)
+ {
+ // pull more data from the log queue
+ if (logger->bufferReadStream != NULL)
+ {
+ if (!CFReadStreamHasBytesAvailable(logger->bufferReadStream))
+ {
+ CFReadStreamClose(logger->bufferReadStream);
+ CFRelease(logger->bufferReadStream);
+ logger->bufferReadStream = NULL;
+ LoggerEmptyBufferFile(logger);
+ }
+ else
+ {
+ logger->sendBufferUsed = CFReadStreamRead(logger->bufferReadStream, logger->sendBuffer, logger->sendBufferSize);
+ }
+ }
+ else
+ {
+ pthread_mutex_lock(&logger->logQueueMutex);
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ CFDataRef d = (CFDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0);
+ CFIndex dsize = CFDataGetLength(d);
+ if ((logger->sendBufferUsed + dsize) > logger->sendBufferSize)
+ break;
+ memcpy(logger->sendBuffer + logger->sendBufferUsed, CFDataGetBytePtr(d), dsize);
+ logger->sendBufferUsed += dsize;
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ logger->incompleteSendOfFirstItem = NO;
+ }
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ }
+ if (logger->sendBufferUsed == 0)
+ {
+ // are we done yet?
+ pthread_mutex_lock(&logger->logQueueMutex);
+ if (CFArrayGetCount(logger->logQueue) == 0)
+ {
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ pthread_cond_broadcast(&logger->logQueueEmpty);
+ return;
+ }
+
+ // first item is too big to fit in a single packet, send it separately
+ sendFirstItem = (CFMutableDataRef)CFArrayGetValueAtIndex(logger->logQueue, 0);
+ logger->incompleteSendOfFirstItem = YES;
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ logger->sendBufferOffset = 0;
+ }
+ }
+
+ // send data over the socket. We try hard to be failsafe and if we have to send
+ // data in fragments, we make sure that in case a disconnect occurs we restart
+ // sending the whole message(s)
+ if (logger->sendBufferUsed != 0)
+ {
+ CFIndex written = CFWriteStreamWrite(logger->logStream,
+ logger->sendBuffer + logger->sendBufferOffset,
+ logger->sendBufferUsed - logger->sendBufferOffset);
+ if (written < 0)
+ {
+ // We'll get an event if the stream closes on error. Don't discard the data,
+ // it will be sent as soon as a connection is re-acquired.
+ return;
+ }
+ if ((logger->sendBufferOffset + written) < logger->sendBufferUsed)
+ {
+ // everything couldn't be sent at once
+ logger->sendBufferOffset += written;
+ }
+ else
+ {
+ logger->sendBufferUsed = 0;
+ logger->sendBufferOffset = 0;
+ }
+ }
+ else if (sendFirstItem)
+ {
+ CFIndex length = CFDataGetLength(sendFirstItem) - logger->sendBufferOffset;
+ CFIndex written = CFWriteStreamWrite(logger->logStream,
+ CFDataGetBytePtr(sendFirstItem) + logger->sendBufferOffset,
+ length);
+ if (written < 0)
+ {
+ // We'll get an event if the stream closes on error
+ return;
+ }
+ if (written < length)
+ {
+ // The output pipe is full, and the first item has not been sent completely
+ // We need to reduce the remaining data on the first item so it can be taken
+ // care of at the next iteration. We take advantage of the fact that each item
+ // in the queue is actually a mutable data block
+ // @@@ NOTE: IF WE GET DISCONNECTED WHILE DOING THIS, THINGS WILL GO WRONG
+ // NEED TO UPDATE THIS LOGIC
+ CFDataReplaceBytes((CFMutableDataRef)sendFirstItem, CFRangeMake(0, written), NULL, 0);
+ return;
+ }
+
+ // we are done sending the first item in the queue, remove it now
+ pthread_mutex_lock(&logger->logQueueMutex);
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ logger->incompleteSendOfFirstItem = NO;
+ pthread_mutex_unlock(&logger->logQueueMutex);
+ logger->sendBufferOffset = 0;
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark File buffering functions
+// -----------------------------------------------------------------------------
+static void LoggerCreateBufferWriteStream(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerCreateBufferWriteStream to file %@"), logger->bufferFile);
+ CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, logger->bufferFile, kCFURLPOSIXPathStyle, false);
+ if (fileURL != NULL)
+ {
+ // Create write stream to file
+ logger->bufferWriteStream = CFWriteStreamCreateWithFile(NULL, fileURL);
+ CFRelease(fileURL);
+ if (logger->bufferWriteStream != NULL)
+ {
+ // Set flag to append new data to buffer file
+ CFWriteStreamSetProperty(logger->bufferWriteStream, kCFStreamPropertyAppendToFile, kCFBooleanTrue);
+
+ // Open the buffer stream for writing
+ if (!CFWriteStreamOpen(logger->bufferWriteStream))
+ {
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+ else
+ {
+ // Write client info and flush the queue contents to buffer file
+ LoggerPushClientInfoToFrontOfQueue(logger);
+ LoggerFlushQueueToBufferStream(logger, YES);
+ }
+ }
+ }
+ if (logger->bufferWriteStream == NULL)
+ {
+ CFShow(CFSTR("NSLogger Warning: failed opening buffer file for writing:"));
+ CFShow(logger->bufferFile);
+ }
+}
+
+static void LoggerCreateBufferReadStream(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerCreateBufferReadStream from file %@"), logger->bufferFile);
+ CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, logger->bufferFile, kCFURLPOSIXPathStyle, false);
+ if (fileURL != NULL)
+ {
+ // Create read stream from file
+ logger->bufferReadStream = CFReadStreamCreateWithFile(NULL, fileURL);
+ CFRelease(fileURL);
+ if (logger->bufferReadStream != NULL)
+ {
+ if (!CFReadStreamOpen(logger->bufferReadStream))
+ {
+ CFRelease(logger->bufferReadStream);
+ logger->bufferReadStream = NULL;
+ }
+ }
+ }
+}
+
+static void LoggerEmptyBufferFile(Logger *logger)
+{
+ // completely remove the buffer file from storage
+ LOGGERDBG(CFSTR("LoggerEmptyBufferFile %@"), logger->bufferFile);
+ if (logger->bufferFile != NULL)
+ {
+ CFIndex bufferSize = 1 + CFStringGetLength(logger->bufferFile) * 3;
+ char *buffer = (char *)malloc(bufferSize);
+ if (buffer != NULL)
+ {
+ if (CFStringGetFileSystemRepresentation(logger->bufferFile, buffer, bufferSize))
+ {
+ // remove file
+ unlink(buffer);
+ }
+ free(buffer);
+ }
+ }
+}
+
+static void LoggerFileBufferingOptionsChanged(Logger *logger)
+{
+ // File buffering options changed:
+ // - close the current buffer file stream, if any
+ // - create a new one, if needed
+ LOGGERDBG(CFSTR("LoggerFileBufferingOptionsChanged bufferFile=%@"), logger->bufferFile);
+ if (logger->bufferWriteStream != NULL)
+ {
+ CFWriteStreamClose(logger->bufferWriteStream);
+ CFRelease(logger->bufferWriteStream);
+ logger->bufferWriteStream = NULL;
+ }
+ if (logger->bufferFile != NULL)
+ LoggerCreateBufferWriteStream(logger);
+}
+
+static void LoggerFlushQueueToBufferStream(Logger *logger, BOOL firstEntryIsClientInfo)
+{
+ LOGGERDBG(CFSTR("LoggerFlushQueueToBufferStream"));
+ pthread_mutex_lock(&logger->logQueueMutex);
+ if (logger->incompleteSendOfFirstItem)
+ {
+ // drop anything being sent
+ logger->sendBufferUsed = 0;
+ logger->sendBufferOffset = 0;
+ }
+ logger->incompleteSendOfFirstItem = NO;
+
+ // Write outstanding messages to the buffer file (streams don't detect disconnection
+ // until the next write, where we could lose one or more messages)
+ if (!firstEntryIsClientInfo && logger->sendBufferUsed)
+ CFWriteStreamWrite(logger->bufferWriteStream, logger->sendBuffer + logger->sendBufferOffset, logger->sendBufferUsed - logger->sendBufferOffset);
+
+ int n = 0;
+ while (CFArrayGetCount(logger->logQueue))
+ {
+ CFDataRef data = CFArrayGetValueAtIndex(logger->logQueue, 0);
+ CFIndex dataLength = CFDataGetLength(data);
+ CFIndex written = CFWriteStreamWrite(logger->bufferWriteStream, CFDataGetBytePtr(data), dataLength);
+ if (written != dataLength)
+ {
+ // couldn't write all data to file, maybe storage run out of space?
+ CFShow(CFSTR("NSLogger Error: failed flushing the whole queue to buffer file:"));
+ CFShow(logger->bufferFile);
+ break;
+ }
+ CFArrayRemoveValueAtIndex(logger->logQueue, 0);
+ if (n == 0 && firstEntryIsClientInfo && logger->sendBufferUsed)
+ {
+ // try hard: write any outstanding messages to the buffer file, after the client info
+ CFWriteStreamWrite(logger->bufferWriteStream, logger->sendBuffer + logger->sendBufferOffset, logger->sendBufferUsed - logger->sendBufferOffset);
+ }
+ n++;
+ }
+ logger->sendBufferUsed = 0;
+ logger->sendBufferOffset = 0;
+ pthread_mutex_unlock(&logger->logQueueMutex);
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Bonjour browsing
+// -----------------------------------------------------------------------------
+static void LoggerStartBonjourBrowsing(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerStartBonjourBrowsing"));
+
+ if (logger->options & kLoggerOption_BrowseOnlyLocalDomain)
+ {
+ LOGGERDBG(CFSTR("Logger configured to search only the local domain, searching for services on: local."));
+ if (!LoggerBrowseBonjourForServices(logger, CFSTR("local.")) && logger->host == NULL)
+ {
+ LOGGERDBG(CFSTR("*** Logger: could not browse for services in domain local., no remote host configured: reverting to console logging. ***"));
+ logger->options |= kLoggerOption_LogToConsole;
+ }
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("Logger configured to search all domains, browsing for domains first"));
+ CFNetServiceClientContext context = {0, (void *)logger, NULL, NULL, NULL};
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+ logger->bonjourDomainBrowser = CFNetServiceBrowserCreate(NULL, &LoggerServiceBrowserCallBack, &context);
+ CFNetServiceBrowserScheduleWithRunLoop(logger->bonjourDomainBrowser, runLoop, kCFRunLoopCommonModes);
+ if (!CFNetServiceBrowserSearchForDomains(logger->bonjourDomainBrowser, false, NULL))
+ {
+ // An error occurred, revert to console logging if there is no remote host
+ LOGGERDBG(CFSTR("*** Logger: could not browse for domains, reverting to console logging. ***"));
+ CFNetServiceBrowserUnscheduleFromRunLoop(logger->bonjourDomainBrowser, runLoop, kCFRunLoopCommonModes);
+ CFRelease(logger->bonjourDomainBrowser);
+ logger->bonjourDomainBrowser = NULL;
+ if (logger->host == NULL)
+ logger->options |= kLoggerOption_LogToConsole;
+ }
+ }
+}
+
+static void LoggerStopBonjourBrowsing(Logger *logger)
+{
+ LOGGERDBG(CFSTR("LoggerStopBonjourBrowsing"));
+
+ // stop browsing for domains
+ if (logger->bonjourDomainBrowser != NULL)
+ {
+ CFNetServiceBrowserStopSearch(logger->bonjourDomainBrowser, NULL);
+ CFNetServiceBrowserUnscheduleFromRunLoop(logger->bonjourDomainBrowser, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFNetServiceBrowserInvalidate(logger->bonjourDomainBrowser);
+ CFRelease(logger->bonjourDomainBrowser);
+ logger->bonjourDomainBrowser = NULL;
+ }
+
+ // stop browsing for services
+ for (CFIndex idx = 0; idx < CFArrayGetCount(logger->bonjourServiceBrowsers); idx++)
+ {
+ CFNetServiceBrowserRef browser = (CFNetServiceBrowserRef)CFArrayGetValueAtIndex(logger->bonjourServiceBrowsers, idx);
+ CFNetServiceBrowserStopSearch(browser, NULL);
+ CFNetServiceBrowserUnscheduleFromRunLoop(browser, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFNetServiceBrowserInvalidate(browser);
+ }
+ CFArrayRemoveAllValues(logger->bonjourServiceBrowsers);
+
+ // Forget all services
+ CFArrayRemoveAllValues(logger->bonjourServices);
+}
+
+static BOOL LoggerBrowseBonjourForServices(Logger *logger, CFStringRef domainName)
+{
+ BOOL result = NO;
+ CFNetServiceClientContext context = {0, (void *)logger, NULL, NULL, NULL};
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ CFNetServiceBrowserRef browser = CFNetServiceBrowserCreate(NULL, (CFNetServiceBrowserClientCallBack)&LoggerServiceBrowserCallBack, &context);
+ CFNetServiceBrowserScheduleWithRunLoop(browser, runLoop, kCFRunLoopCommonModes);
+ CFStreamError error;
+
+ // try to use the user-specfied service type if any, fallback on our
+ // default service type
+ CFStringRef serviceType = logger->bonjourServiceType;
+ if (serviceType == NULL)
+ {
+ if (logger->options & kLoggerOption_UseSSL)
+ serviceType = LOGGER_SERVICE_TYPE_SSL;
+ else
+ serviceType = LOGGER_SERVICE_TYPE;
+ }
+ if (!CFNetServiceBrowserSearchForServices(browser, domainName, serviceType, &error))
+ {
+ LOGGERDBG(CFSTR("Logger can't start search on domain: %@ (error %d)"), domainName, error.error);
+ CFNetServiceBrowserUnscheduleFromRunLoop(browser, runLoop, kCFRunLoopCommonModes);
+ CFNetServiceBrowserInvalidate(browser);
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("Logger started search for services of type %@ in domain %@"), serviceType, domainName);
+ CFArrayAppendValue(logger->bonjourServiceBrowsers, browser);
+ result = YES;
+ }
+ CFRelease(browser);
+ return result;
+}
+
+static void LoggerServiceBrowserCallBack (CFNetServiceBrowserRef browser,
+ CFOptionFlags flags,
+ CFTypeRef domainOrService,
+ CFStreamError* error,
+ void* info)
+{
+ LOGGERDBG(CFSTR("LoggerServiceBrowserCallback browser=%@ flags=0x%04x domainOrService=%@ error=%d"), browser, flags, domainOrService, error==NULL ? 0 : error->error);
+
+ Logger *logger = (Logger *)info;
+ assert(logger != NULL);
+
+ if (flags & kCFNetServiceFlagRemove)
+ {
+ if (!(flags & kCFNetServiceFlagIsDomain))
+ {
+ CFNetServiceRef service = (CFNetServiceRef)domainOrService;
+ for (CFIndex idx = 0; idx < CFArrayGetCount(logger->bonjourServices); idx++)
+ {
+ if (CFArrayGetValueAtIndex(logger->bonjourServices, idx) == service)
+ {
+ CFNetServiceUnscheduleFromRunLoop(service, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFNetServiceClientContext context = {0, NULL, NULL, NULL, NULL};
+ CFNetServiceSetClient(service, NULL, &context);
+ CFNetServiceCancel(service);
+ CFArrayRemoveValueAtIndex(logger->bonjourServices, idx);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (flags & kCFNetServiceFlagIsDomain)
+ {
+ // start searching for services in this domain
+ LoggerBrowseBonjourForServices(logger, (CFStringRef)domainOrService);
+ }
+ else
+ {
+ // a service has been found
+ LOGGERDBG(CFSTR("Logger found service: %@"), domainOrService);
+ CFNetServiceRef service = (CFNetServiceRef)domainOrService;
+ if (service != NULL)
+ {
+ // if the user has specified that Logger shall only connect to the specified
+ // Bonjour service name, check it now. This makes things easier in a teamwork
+ // environment where multiple instances of NSLogger viewer may run on the
+ // same network
+ if (logger->bonjourServiceName != NULL)
+ {
+ LOGGERDBG(CFSTR("-> looking for services of name %@"), logger->bonjourServiceName);
+ CFStringRef name = CFNetServiceGetName(service);
+ if (name == NULL || kCFCompareEqualTo != CFStringCompare(name, logger->bonjourServiceName, kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive))
+ {
+ LOGGERDBG(CFSTR("-> service name %@ does not match requested service name, ignoring."), name, logger->bonjourServiceName);
+ return;
+ }
+ }
+ CFArrayAppendValue(logger->bonjourServices, service);
+ LoggerTryConnect(logger);
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Reachability
+// -----------------------------------------------------------------------------
+static void LoggerStartReachabilityChecking(Logger *logger)
+{
+ if (logger->host != NULL && logger->reachability == NULL)
+ {
+ LOGGERDBG(CFSTR("Starting SCNetworkReachability to wait for host %@ to be reachable"), logger->host);
+
+ CFIndex length = CFStringGetLength(logger->host) * 3;
+ char *buffer = (char *)malloc(length + 1);
+ CFStringGetBytes(logger->host, CFRangeMake(0, CFStringGetLength(logger->host)), kCFStringEncodingUTF8, '?', false, (UInt8 *)buffer, length, &length);
+ buffer[length] = 0;
+
+ logger->reachability = SCNetworkReachabilityCreateWithName(NULL, buffer);
+
+ SCNetworkReachabilityContext context = {0, logger, NULL, NULL, NULL};
+ SCNetworkReachabilitySetCallback(logger->reachability, &LoggerReachabilityCallBack, &context);
+ SCNetworkReachabilityScheduleWithRunLoop(logger->reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+
+ free(buffer);
+
+ // Also start a timer that will try to reconnect every N seconds
+ if (logger->checkHostTimer == NULL)
+ {
+ CFRunLoopTimerContext context = {
+ .version = 0,
+ .info = logger,
+ .retain = NULL,
+ .release = NULL,
+ .copyDescription = NULL
+ };
+ logger->checkHostTimer = CFRunLoopTimerCreate(NULL,
+ CFAbsoluteTimeGetCurrent() + 5,
+ 5, // reconnect interval
+ 0,
+ 0,
+ &LoggerTimedReconnectCallback,
+ &context);
+ if (logger->checkHostTimer != NULL)
+ {
+ LOGGERDBG(CFSTR("Starting the TimedReconnect timer to regularly retry the connection"));
+ CFRunLoopAddTimer(CFRunLoopGetCurrent(), logger->checkHostTimer, kCFRunLoopCommonModes);
+ }
+ }
+ }
+}
+
+static void LoggerStopReachabilityChecking(Logger *logger)
+{
+ if (logger->reachability != NULL)
+ {
+ LOGGERDBG(CFSTR("Stopping SCNetworkReachability"));
+ SCNetworkReachabilityUnscheduleFromRunLoop(logger->reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ CFRelease(logger->reachability);
+ logger->reachability = NULL;
+ }
+ if (logger->checkHostTimer != NULL)
+ {
+ CFRunLoopTimerInvalidate(logger->checkHostTimer);
+ CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), logger->checkHostTimer, kCFRunLoopCommonModes);
+ CFRelease(logger->checkHostTimer);
+ logger->checkHostTimer = NULL;
+ }
+}
+
+static void LoggerReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
+{
+ Logger *logger = (Logger *)info;
+ assert(logger != NULL);
+ LOGGERDBG(CFSTR("LoggerReachabilityCallBack called with flags=0x%08lx"), flags);
+ if (flags & kSCNetworkReachabilityFlagsReachable)
+ {
+ // target host became reachable. If we have not other open connection,
+ // try direct connection to the host
+ if (logger->logStream == NULL && logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> host %@ became reachable, trying to connect."), logger->host);
+ LoggerTryConnect(logger);
+ }
+ }
+}
+
+static void LoggerTimedReconnectCallback(CFRunLoopTimerRef timer, void *info)
+{
+ Logger *logger = (Logger *)info;
+ assert(logger != NULL);
+ LOGGERDBG(CFSTR("LoggerTimedReconnectCallback"));
+ if (logger->logStream == NULL && logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> trying to reconnect to host %@"), logger->host);
+ LoggerTryConnect(logger);
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("-> timer not needed anymore, removing it form runloop"));
+ CFRunLoopTimerInvalidate(timer);
+ CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), logger->checkHostTimer, kCFRunLoopCommonModes);
+ CFRelease(timer);
+ logger->checkHostTimer = NULL;
+ }
+}
+
+// -----------------------------------------------------------------------------
+#pragma mark -
+#pragma mark Stream management
+// -----------------------------------------------------------------------------
+static BOOL LoggerConfigureAndOpenStream(Logger *logger)
+{
+ // configure and open stream
+ LOGGERDBG(CFSTR("LoggerConfigureAndOpenStream configuring and opening log stream"));
+ CFStreamClientContext context = {0, (void *)logger, NULL, NULL, NULL};
+ if (CFWriteStreamSetClient(logger->logStream,
+ (kCFStreamEventOpenCompleted |
+ kCFStreamEventCanAcceptBytes |
+ kCFStreamEventErrorOccurred |
+ kCFStreamEventEndEncountered),
+ &LoggerWriteStreamCallback,
+ &context))
+ {
+ if (logger->options & kLoggerOption_UseSSL)
+ {
+ // Configure stream to require a SSL connection
+ LOGGERDBG(CFSTR("-> configuring SSL"));
+ const void *SSLKeys[] = {
+ kCFStreamSSLLevel,
+ kCFStreamSSLValidatesCertificateChain,
+ kCFStreamSSLIsServer,
+ kCFStreamSSLPeerName
+ };
+ const void *SSLValues[] = {
+ kCFStreamSocketSecurityLevelNegotiatedSSL,
+ kCFBooleanFalse, // no certificate chain validation (we use a self-signed certificate)
+ kCFBooleanFalse, // not a server
+ kCFNull
+ };
+ CFDictionaryRef SSLDict = CFDictionaryCreate(NULL, SSLKeys, SSLValues, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFWriteStreamSetProperty(logger->logStream, kCFStreamPropertySSLSettings, SSLDict);
+ CFRelease(SSLDict);
+ }
+
+ CFWriteStreamScheduleWithRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+
+ if (CFWriteStreamOpen(logger->logStream))
+ {
+ LOGGERDBG(CFSTR("-> stream open attempt, waiting for open completion"));
+ return YES;
+ }
+
+ LOGGERDBG(CFSTR("-> stream open failed."));
+
+ CFWriteStreamSetClient(logger->logStream, kCFStreamEventNone, NULL, NULL);
+ if (CFWriteStreamGetStatus(logger->logStream) == kCFStreamStatusOpen)
+ CFWriteStreamClose(logger->logStream);
+ CFWriteStreamUnscheduleFromRunLoop(logger->logStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
+ }
+ else
+ {
+ LOGGERDBG(CFSTR("-> stream set client failed."));
+ }
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+ return NO;
+}
+
+static void LoggerTryConnect(Logger *logger)
+{
+ // Try connecting to the next address in the sConnectAttempts array
+ LOGGERDBG(CFSTR("LoggerTryConnect, %d services registered, current stream=%@"), CFArrayGetCount(logger->bonjourServices), logger->logStream);
+
+ // If we already have a connection established or being attempted, stop here
+ if (logger->logStream != NULL)
+ {
+ LOGGERDBG(CFSTR("-> another connection is opened or in progress, giving up for now"));
+ return;
+ }
+
+ // If there are discovered Bonjour services, try them now
+ while (CFArrayGetCount(logger->bonjourServices))
+ {
+ CFNetServiceRef service = (CFNetServiceRef)CFArrayGetValueAtIndex(logger->bonjourServices, 0);
+ LOGGERDBG(CFSTR("-> Trying to open write stream to service %@"), service);
+ CFStreamCreatePairWithSocketToNetService(NULL, service, NULL, &logger->logStream);
+ CFArrayRemoveValueAtIndex(logger->bonjourServices, 0);
+ if (logger->logStream == NULL)
+ {
+ // create pair failed
+ LOGGERDBG(CFSTR("-> failed."));
+ }
+ else if (LoggerConfigureAndOpenStream(logger))
+ {
+ // open is now in progress
+ return;
+ }
+ }
+
+ // If there is a host to directly connect to, try it now (this will happen before
+ // Bonjour kicks in, Bonjour being handled as a fallback solution if direct Host
+ // fails)
+ if (logger->host != NULL)
+ {
+ LOGGERDBG(CFSTR("-> Trying to open direct connection to host %@ port %u"), logger->host, logger->port);
+ CFStreamCreatePairWithSocketToHost(NULL, logger->host, logger->port, NULL, &logger->logStream);
+ if (logger->logStream == NULL)
+ {
+ // Create stream failed
+ LOGGERDBG(CFSTR("-> failed."));
+ if (logger->logStream != NULL)
+ {
+ CFRelease(logger->logStream);
+ logger->logStream = NULL;
+ }