diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ccb674 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +xcuserdata/ +project.xcworkspace/ diff --git a/Doc/TODO.txt b/Doc/TODO.txt new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..260a79a --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ + + +Logentries logging for iOS +========================== + + +Main features +------------- + +* online/offline logging +* dictionary serialization +* secure TLS connection +* thread safety +* application lifecycle logging +* application crash logging with stack traces + +Installation +------------ + +Just add files from lelib group into your project. + +Simple example +-------------- + +```objectivec +#import "lelib.h" + +LELog* log = [LELog sharedInstance]; +log.token = @"f66815d1-702c-414b-8dcc-bb73de372584"; + +[log log:@"Hello World"]; +``` + +Early initialization +-------------------- + +The library automatically hooks up to the exception handler and logs unhandled +exceptions. This means that you should initialize the library as soon as +possible to log all exceptions. Insert following lines to main.m to log +exceptions even before application:didFinishLaunchingWithOptions: is invoked. + +```objectivec + +#import "lecore.h" + +int main(int argc, char * argv[]) +{ + @autoreleasepool { + + le_init(); + le_set_token("aaabacad-aeaf-4321-1234-abcdef012345"); + + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + +The token is stored in global variable. You don't have to setup token property +of LELog instance later. + +``` + +Quick questions +--------------- + +**Any dependencies?** No dependencies. The library uses standard Obj-C and POSIX C. + +**How to log an event?** Simply call `[log log:@"Hello world"];` + +**No network coverage?** Log entries are stored in a file and sent to Logentries when the network is back. + +**When app crashes?** If configured, the library logs information about the application crash with stack trace. + +**When app is forced to shut down by OS?** There is no way to log it. + diff --git a/demo/AppDelegate.h b/demo/AppDelegate.h new file mode 100644 index 0000000..5c81ec0 --- /dev/null +++ b/demo/AppDelegate.h @@ -0,0 +1,15 @@ +// +// AppDelegate.h +// demo +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/demo/AppDelegate.m b/demo/AppDelegate.m new file mode 100644 index 0000000..fd28bc6 --- /dev/null +++ b/demo/AppDelegate.m @@ -0,0 +1,46 @@ +// +// AppDelegate.m +// demo +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import "AppDelegate.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application +{ + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application +{ + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application +{ + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application +{ + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/demo/Base.lproj/Main.storyboard b/demo/Base.lproj/Main.storyboard new file mode 100644 index 0000000..b99208b --- /dev/null +++ b/demo/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/Images.xcassets/AppIcon.appiconset/Contents.json b/demo/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a396706 --- /dev/null +++ b/demo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/demo/Images.xcassets/LaunchImage.launchimage/Contents.json b/demo/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000..c79ebd3 --- /dev/null +++ b/demo/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/demo/ViewController.h b/demo/ViewController.h new file mode 100644 index 0000000..3f3ed96 --- /dev/null +++ b/demo/ViewController.h @@ -0,0 +1,13 @@ +// +// ViewController.h +// demo +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + +@end diff --git a/demo/ViewController.m b/demo/ViewController.m new file mode 100644 index 0000000..66f48c2 --- /dev/null +++ b/demo/ViewController.m @@ -0,0 +1,80 @@ +// +// ViewController.m +// demo +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import "ViewController.h" +#import "lelib.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)writeTimerFired:(NSTimer*)timer +{ + LELog* log = [LELog sharedInstance]; + [log log:timer.userInfo]; +} + +- (void)scheduleLog:(NSString*)message after:(NSTimeInterval)seconds +{ + [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(writeTimerFired:) userInfo:message repeats:NO]; +} + +- (void)logManyFired:(NSTimer*)timer +{ + static NSInteger counter = 1; + + if (counter > 10) return; + + LELog* log = [LELog sharedInstance]; + for (NSInteger i = 1; i < 10; i++) { + NSString* message = [NSString stringWithFormat:@"logging serie %ld index %ld", (long)counter, (long)i]; + [log log:message]; + } + + counter++; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + LELog* log = [LELog sharedInstance]; + log.token = @"f66815d1-702c-414b-8dcc-bb73de372584"; + log.logApplicationLifecycleNotifications = YES; + + +/* + // test exception logging handler + NSArray* x = [NSArray arrayWithObject:nil]; + NSLog(@"%@", x); + */ + +/* + // simple logging + [log log:@"test A"]; + [log log:@"test B"]; + [log log:@{@(123):@"test C"}]; + */ + + [self scheduleLog:@"test 10s" after:10]; + [self scheduleLog:@"test 20s" after:20]; + +/* + [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(logManyFired:) userInfo:nil repeats:YES]; +*/ + +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +@end diff --git a/demo/demo-Info.plist b/demo/demo-Info.plist new file mode 100644 index 0000000..0c75bc7 --- /dev/null +++ b/demo/demo-Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.logentries.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/demo/demo-Prefix.pch b/demo/demo-Prefix.pch new file mode 100644 index 0000000..82a2bb4 --- /dev/null +++ b/demo/demo-Prefix.pch @@ -0,0 +1,16 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#import + +#ifndef __IPHONE_5_0 +#warning "This project uses features only available in iOS SDK 5.0 and later." +#endif + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/demo/en.lproj/InfoPlist.strings b/demo/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/demo/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/demo/main.m b/demo/main.m new file mode 100644 index 0000000..4539a60 --- /dev/null +++ b/demo/main.m @@ -0,0 +1,22 @@ +// +// main.m +// demo +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +#import "AppDelegate.h" +#import "lecore.h" + +int main(int argc, char * argv[]) +{ + @autoreleasepool { + le_init(); + le_set_token("f66815d1-702c-414b-8dcc-bb73de372584"); + le_log("nazdar"); + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/demoTests/demoTests-Info.plist b/demoTests/demoTests-Info.plist new file mode 100644 index 0000000..5a32a6b --- /dev/null +++ b/demoTests/demoTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.logentries.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/demoTests/demoTests.m b/demoTests/demoTests.m new file mode 100644 index 0000000..24cadfb --- /dev/null +++ b/demoTests/demoTests.m @@ -0,0 +1,34 @@ +// +// demoTests.m +// demoTests +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +@interface demoTests : XCTestCase + +@end + +@implementation demoTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample +{ + XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); +} + +@end diff --git a/demoTests/en.lproj/InfoPlist.strings b/demoTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/demoTests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/lelib.xcodeproj/project.pbxproj b/lelib.xcodeproj/project.pbxproj new file mode 100644 index 0000000..989dc29 --- /dev/null +++ b/lelib.xcodeproj/project.pbxproj @@ -0,0 +1,676 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4B08F54A18436082003D813B /* LELog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B08F54918436082003D813B /* LELog.m */; }; + 4B08F55018436F10003D813B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB806A2181D3DF1003066E5 /* Foundation.framework */; }; + 4B08F55218436F10003D813B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B08F55118436F10003D813B /* CoreGraphics.framework */; }; + 4B08F55318436F10003D813B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB806B3181D3DF1003066E5 /* UIKit.framework */; }; + 4B08F55918436F10003D813B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4B08F55718436F10003D813B /* InfoPlist.strings */; }; + 4B08F55B18436F10003D813B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B08F55A18436F10003D813B /* main.m */; }; + 4B08F55F18436F10003D813B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B08F55E18436F10003D813B /* AppDelegate.m */; }; + 4B08F56218436F10003D813B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B08F56018436F10003D813B /* Main.storyboard */; }; + 4B08F56518436F10003D813B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B08F56418436F10003D813B /* ViewController.m */; }; + 4B08F56718436F10003D813B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B08F56618436F10003D813B /* Images.xcassets */; }; + 4B08F58018437007003D813B /* LELog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B08F54918436082003D813B /* LELog.m */; }; + 4B50E3E2187B5097007DCFE5 /* lecore.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B50E3E1187B5097007DCFE5 /* lecore.m */; }; + 4B50E3E3187B5097007DCFE5 /* lecore.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B50E3E1187B5097007DCFE5 /* lecore.m */; }; + 4BB806A3181D3DF1003066E5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB806A2181D3DF1003066E5 /* Foundation.framework */; }; + 4BB806A8181D3DF1003066E5 /* lelib.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4BB806A7181D3DF1003066E5 /* lelib.h */; }; + 4BB806B1181D3DF1003066E5 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB806B0181D3DF1003066E5 /* XCTest.framework */; }; + 4BB806B2181D3DF1003066E5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB806A2181D3DF1003066E5 /* Foundation.framework */; }; + 4BB806B4181D3DF1003066E5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB806B3181D3DF1003066E5 /* UIKit.framework */; }; + 4BB806B7181D3DF1003066E5 /* liblelib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8069F181D3DF1003066E5 /* liblelib.a */; }; + 4BB806BD181D3DF1003066E5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4BB806BB181D3DF1003066E5 /* InfoPlist.strings */; }; + 4BB806BF181D3DF1003066E5 /* lelibTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BB806BE181D3DF1003066E5 /* lelibTests.m */; }; + 4BBA0163184BDEE000AB5C9A /* LogFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBA0162184BDEE000AB5C9A /* LogFile.m */; }; + 4BBA0164184BDEEA00AB5C9A /* LogFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBA0162184BDEE000AB5C9A /* LogFile.m */; }; + 4BBA0167184BDEFC00AB5C9A /* LogFiles.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBA0166184BDEFC00AB5C9A /* LogFiles.m */; }; + 4BBA0168184BDEFC00AB5C9A /* LogFiles.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBA0166184BDEFC00AB5C9A /* LogFiles.m */; }; + 4BBB74FA184AC86F00B393BC /* LEBackgroundThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB74F9184AC86F00B393BC /* LEBackgroundThread.m */; }; + 4BBB74FC184AC89600B393BC /* LEBackgroundThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB74F9184AC86F00B393BC /* LEBackgroundThread.m */; }; + 4BE1B5C81897BF5C008DD96E /* LeNetworkStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BE1B5C71897BF5C008DD96E /* LeNetworkStatus.m */; }; + 4BE1B5C91897BF61008DD96E /* LeNetworkStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BE1B5C71897BF5C008DD96E /* LeNetworkStatus.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4BB806B5181D3DF1003066E5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4BB80697181D3DF1003066E5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4BB8069E181D3DF1003066E5; + remoteInfo = lelib; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4BB8069D181D3DF1003066E5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + 4BB806A8181D3DF1003066E5 /* lelib.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 4B08F54818436082003D813B /* LELog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LELog.h; sourceTree = ""; }; + 4B08F54918436082003D813B /* LELog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LELog.m; sourceTree = ""; }; + 4B08F54F18436F0F003D813B /* demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B08F55118436F10003D813B /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 4B08F55618436F10003D813B /* demo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "demo-Info.plist"; sourceTree = ""; }; + 4B08F55818436F10003D813B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 4B08F55A18436F10003D813B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 4B08F55C18436F10003D813B /* demo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "demo-Prefix.pch"; sourceTree = ""; }; + 4B08F55D18436F10003D813B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 4B08F55E18436F10003D813B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 4B08F56118436F10003D813B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 4B08F56318436F10003D813B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 4B08F56418436F10003D813B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 4B08F56618436F10003D813B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 4B08F57418436F10003D813B /* demoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "demoTests-Info.plist"; sourceTree = ""; }; + 4B08F57618436F10003D813B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 4B08F57818436F10003D813B /* demoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = demoTests.m; sourceTree = ""; }; + 4B50E3DD187B4D7C007DCFE5 /* lecore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lecore.h; sourceTree = ""; }; + 4B50E3E1187B5097007DCFE5 /* lecore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = lecore.m; sourceTree = ""; }; + 4BB8069F181D3DF1003066E5 /* liblelib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblelib.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 4BB806A2181D3DF1003066E5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 4BB806A6181D3DF1003066E5 /* lelib-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "lelib-Prefix.pch"; sourceTree = ""; }; + 4BB806A7181D3DF1003066E5 /* lelib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lelib.h; sourceTree = ""; }; + 4BB806AF181D3DF1003066E5 /* lelibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = lelibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4BB806B0181D3DF1003066E5 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 4BB806B3181D3DF1003066E5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 4BB806BA181D3DF1003066E5 /* lelibTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "lelibTests-Info.plist"; sourceTree = ""; }; + 4BB806BC181D3DF1003066E5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 4BB806BE181D3DF1003066E5 /* lelibTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = lelibTests.m; sourceTree = ""; }; + 4BB806C8181D3E69003066E5 /* TODO.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = TODO.txt; path = Doc/TODO.txt; sourceTree = SOURCE_ROOT; }; + 4BBA0161184BDEE000AB5C9A /* LogFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogFile.h; sourceTree = ""; }; + 4BBA0162184BDEE000AB5C9A /* LogFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LogFile.m; sourceTree = ""; }; + 4BBA0165184BDEFC00AB5C9A /* LogFiles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogFiles.h; sourceTree = ""; }; + 4BBA0166184BDEFC00AB5C9A /* LogFiles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LogFiles.m; sourceTree = ""; }; + 4BBB74F9184AC86F00B393BC /* LEBackgroundThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LEBackgroundThread.m; sourceTree = ""; }; + 4BBB74FB184AC88600B393BC /* LEBackgroundThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LEBackgroundThread.h; sourceTree = ""; }; + 4BE1B5C61897BF57008DD96E /* LeNetworkStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LeNetworkStatus.h; sourceTree = ""; }; + 4BE1B5C71897BF5C008DD96E /* LeNetworkStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LeNetworkStatus.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4B08F54C18436F0F003D813B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B08F55218436F10003D813B /* CoreGraphics.framework in Frameworks */, + 4B08F55318436F10003D813B /* UIKit.framework in Frameworks */, + 4B08F55018436F10003D813B /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BB8069C181D3DF1003066E5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BB806A3181D3DF1003066E5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BB806AC181D3DF1003066E5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BB806B7181D3DF1003066E5 /* liblelib.a in Frameworks */, + 4BB806B1181D3DF1003066E5 /* XCTest.framework in Frameworks */, + 4BB806B4181D3DF1003066E5 /* UIKit.framework in Frameworks */, + 4BB806B2181D3DF1003066E5 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4B08F55418436F10003D813B /* demo */ = { + isa = PBXGroup; + children = ( + 4B08F55D18436F10003D813B /* AppDelegate.h */, + 4B08F55E18436F10003D813B /* AppDelegate.m */, + 4B08F56018436F10003D813B /* Main.storyboard */, + 4B08F56318436F10003D813B /* ViewController.h */, + 4B08F56418436F10003D813B /* ViewController.m */, + 4B08F56618436F10003D813B /* Images.xcassets */, + 4B08F55518436F10003D813B /* Supporting Files */, + ); + path = demo; + sourceTree = ""; + }; + 4B08F55518436F10003D813B /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4B08F55618436F10003D813B /* demo-Info.plist */, + 4B08F55718436F10003D813B /* InfoPlist.strings */, + 4B08F55A18436F10003D813B /* main.m */, + 4B08F55C18436F10003D813B /* demo-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 4B08F57218436F10003D813B /* demoTests */ = { + isa = PBXGroup; + children = ( + 4B08F57818436F10003D813B /* demoTests.m */, + 4B08F57318436F10003D813B /* Supporting Files */, + ); + path = demoTests; + sourceTree = ""; + }; + 4B08F57318436F10003D813B /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4B08F57418436F10003D813B /* demoTests-Info.plist */, + 4B08F57518436F10003D813B /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 4BB80696181D3DF1003066E5 = { + isa = PBXGroup; + children = ( + 4BB806A4181D3DF1003066E5 /* lelib */, + 4BB806B8181D3DF1003066E5 /* lelibTests */, + 4B08F55418436F10003D813B /* demo */, + 4B08F57218436F10003D813B /* demoTests */, + 4BB806A1181D3DF1003066E5 /* Frameworks */, + 4BB806A0181D3DF1003066E5 /* Products */, + ); + sourceTree = ""; + }; + 4BB806A0181D3DF1003066E5 /* Products */ = { + isa = PBXGroup; + children = ( + 4BB8069F181D3DF1003066E5 /* liblelib.a */, + 4BB806AF181D3DF1003066E5 /* lelibTests.xctest */, + 4B08F54F18436F0F003D813B /* demo.app */, + ); + name = Products; + sourceTree = ""; + }; + 4BB806A1181D3DF1003066E5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4BB806A2181D3DF1003066E5 /* Foundation.framework */, + 4BB806B0181D3DF1003066E5 /* XCTest.framework */, + 4BB806B3181D3DF1003066E5 /* UIKit.framework */, + 4B08F55118436F10003D813B /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4BB806A4181D3DF1003066E5 /* lelib */ = { + isa = PBXGroup; + children = ( + 4BE1B5C71897BF5C008DD96E /* LeNetworkStatus.m */, + 4BE1B5C61897BF57008DD96E /* LeNetworkStatus.h */, + 4BBB74FB184AC88600B393BC /* LEBackgroundThread.h */, + 4BBB74F9184AC86F00B393BC /* LEBackgroundThread.m */, + 4BB806A7181D3DF1003066E5 /* lelib.h */, + 4B08F54818436082003D813B /* LELog.h */, + 4B08F54918436082003D813B /* LELog.m */, + 4BB806A5181D3DF1003066E5 /* Supporting Files */, + 4BBA0161184BDEE000AB5C9A /* LogFile.h */, + 4BBA0162184BDEE000AB5C9A /* LogFile.m */, + 4BBA0165184BDEFC00AB5C9A /* LogFiles.h */, + 4BBA0166184BDEFC00AB5C9A /* LogFiles.m */, + 4B50E3DD187B4D7C007DCFE5 /* lecore.h */, + 4B50E3E1187B5097007DCFE5 /* lecore.m */, + ); + path = lelib; + sourceTree = ""; + }; + 4BB806A5181D3DF1003066E5 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4BB806C8181D3E69003066E5 /* TODO.txt */, + 4BB806A6181D3DF1003066E5 /* lelib-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 4BB806B8181D3DF1003066E5 /* lelibTests */ = { + isa = PBXGroup; + children = ( + 4BB806BE181D3DF1003066E5 /* lelibTests.m */, + 4BB806B9181D3DF1003066E5 /* Supporting Files */, + ); + path = lelibTests; + sourceTree = ""; + }; + 4BB806B9181D3DF1003066E5 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4BB806BA181D3DF1003066E5 /* lelibTests-Info.plist */, + 4BB806BB181D3DF1003066E5 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4B08F54E18436F0F003D813B /* demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B08F57A18436F10003D813B /* Build configuration list for PBXNativeTarget "demo" */; + buildPhases = ( + 4B08F54B18436F0F003D813B /* Sources */, + 4B08F54C18436F0F003D813B /* Frameworks */, + 4B08F54D18436F0F003D813B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = demo; + productName = demo; + productReference = 4B08F54F18436F0F003D813B /* demo.app */; + productType = "com.apple.product-type.application"; + }; + 4BB8069E181D3DF1003066E5 /* lelib */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4BB806C2181D3DF1003066E5 /* Build configuration list for PBXNativeTarget "lelib" */; + buildPhases = ( + 4BB8069B181D3DF1003066E5 /* Sources */, + 4BB8069C181D3DF1003066E5 /* Frameworks */, + 4BB8069D181D3DF1003066E5 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = lelib; + productName = lelib; + productReference = 4BB8069F181D3DF1003066E5 /* liblelib.a */; + productType = "com.apple.product-type.library.static"; + }; + 4BB806AE181D3DF1003066E5 /* lelibTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4BB806C5181D3DF1003066E5 /* Build configuration list for PBXNativeTarget "lelibTests" */; + buildPhases = ( + 4BB806AB181D3DF1003066E5 /* Sources */, + 4BB806AC181D3DF1003066E5 /* Frameworks */, + 4BB806AD181D3DF1003066E5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4BB806B6181D3DF1003066E5 /* PBXTargetDependency */, + ); + name = lelibTests; + productName = lelibTests; + productReference = 4BB806AF181D3DF1003066E5 /* lelibTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4BB80697181D3DF1003066E5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = JLizard; + TargetAttributes = { + 4BB806AE181D3DF1003066E5 = { + TestTargetID = 4B08F54E18436F0F003D813B; + }; + }; + }; + buildConfigurationList = 4BB8069A181D3DF1003066E5 /* Build configuration list for PBXProject "lelib" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4BB80696181D3DF1003066E5; + productRefGroup = 4BB806A0181D3DF1003066E5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4BB8069E181D3DF1003066E5 /* lelib */, + 4BB806AE181D3DF1003066E5 /* lelibTests */, + 4B08F54E18436F0F003D813B /* demo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4B08F54D18436F0F003D813B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B08F56718436F10003D813B /* Images.xcassets in Resources */, + 4B08F55918436F10003D813B /* InfoPlist.strings in Resources */, + 4B08F56218436F10003D813B /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BB806AD181D3DF1003066E5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BB806BD181D3DF1003066E5 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4B08F54B18436F0F003D813B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B08F56518436F10003D813B /* ViewController.m in Sources */, + 4B08F58018437007003D813B /* LELog.m in Sources */, + 4BBA0164184BDEEA00AB5C9A /* LogFile.m in Sources */, + 4BBA0168184BDEFC00AB5C9A /* LogFiles.m in Sources */, + 4B08F55F18436F10003D813B /* AppDelegate.m in Sources */, + 4B50E3E3187B5097007DCFE5 /* lecore.m in Sources */, + 4B08F55B18436F10003D813B /* main.m in Sources */, + 4BE1B5C91897BF61008DD96E /* LeNetworkStatus.m in Sources */, + 4BBB74FC184AC89600B393BC /* LEBackgroundThread.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BB8069B181D3DF1003066E5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BBB74FA184AC86F00B393BC /* LEBackgroundThread.m in Sources */, + 4B08F54A18436082003D813B /* LELog.m in Sources */, + 4B50E3E2187B5097007DCFE5 /* lecore.m in Sources */, + 4BBA0167184BDEFC00AB5C9A /* LogFiles.m in Sources */, + 4BE1B5C81897BF5C008DD96E /* LeNetworkStatus.m in Sources */, + 4BBA0163184BDEE000AB5C9A /* LogFile.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4BB806AB181D3DF1003066E5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BB806BF181D3DF1003066E5 /* lelibTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4BB806B6181D3DF1003066E5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4BB8069E181D3DF1003066E5 /* lelib */; + targetProxy = 4BB806B5181D3DF1003066E5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 4B08F55718436F10003D813B /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 4B08F55818436F10003D813B /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 4B08F56018436F10003D813B /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 4B08F56118436F10003D813B /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 4B08F57518436F10003D813B /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 4B08F57618436F10003D813B /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 4BB806BB181D3DF1003066E5 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 4BB806BC181D3DF1003066E5 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4B08F57B18436F10003D813B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "demo/demo-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "demo/demo-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 4B08F57C18436F10003D813B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "demo/demo-Prefix.pch"; + INFOPLIST_FILE = "demo/demo-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + 4BB806C0181D3DF1003066E5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 4BB806C1181D3DF1003066E5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4BB806C3181D3DF1003066E5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DSTROOT = /tmp/lelib.dst; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "lelib/lelib-Prefix.pch"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 4BB806C4181D3DF1003066E5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DSTROOT = /tmp/lelib.dst; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "lelib/lelib-Prefix.pch"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 4BB806C6181D3DF1003066E5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/demo.app/demo"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "lelib/lelib-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "lelibTests/lelibTests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + 4BB806C7181D3DF1003066E5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/demo.app/demo"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "lelib/lelib-Prefix.pch"; + INFOPLIST_FILE = "lelibTests/lelibTests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4B08F57A18436F10003D813B /* Build configuration list for PBXNativeTarget "demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B08F57B18436F10003D813B /* Debug */, + 4B08F57C18436F10003D813B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4BB8069A181D3DF1003066E5 /* Build configuration list for PBXProject "lelib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BB806C0181D3DF1003066E5 /* Debug */, + 4BB806C1181D3DF1003066E5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4BB806C2181D3DF1003066E5 /* Build configuration list for PBXNativeTarget "lelib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BB806C3181D3DF1003066E5 /* Debug */, + 4BB806C4181D3DF1003066E5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4BB806C5181D3DF1003066E5 /* Build configuration list for PBXNativeTarget "lelibTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4BB806C6181D3DF1003066E5 /* Debug */, + 4BB806C7181D3DF1003066E5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4BB80697181D3DF1003066E5 /* Project object */; +} diff --git a/lelib/LEBackgroundThread.h b/lelib/LEBackgroundThread.h new file mode 100644 index 0000000..7a39bab --- /dev/null +++ b/lelib/LEBackgroundThread.h @@ -0,0 +1,28 @@ +// +// LEBackgroundThread.h +// lelib +// +// Created by Petr on 01.12.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + + +/* + This is a background thread which reads log files and sends it + to the server. You should not use this class directly. + */ +@interface LEBackgroundThread : NSThread + +/* + Initialization lock used to wait for background thread. + */ +@property (atomic, strong) NSCondition* initialized; + +/* + This method is invoked by le_poke() when new data are available. + */ +- (void)poke:(NSNumber*)lastLogFileNumber; + +@end \ No newline at end of file diff --git a/lelib/LEBackgroundThread.m b/lelib/LEBackgroundThread.m new file mode 100644 index 0000000..b741cf0 --- /dev/null +++ b/lelib/LEBackgroundThread.m @@ -0,0 +1,385 @@ +// +// LEBackgroundThread.m +// lelib +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import "LEBackgroundThread.h" +#import "lelib.h" +#import "LogFiles.h" +#import "LELog.h" +#import "LeNetworkStatus.h" + +#define LOGENTRIES_HOST @"data.logentries.com" +#define LOGENTRIES_PORT 10000 +#define LOGENTRIES_USE_TLS 0 + +#define RETRY_TIMEOUT 60.0 +#define KEEPALIVE_INTERVAL 3600.0 + + +@interface LEBackgroundThread() { + + uint8_t output_buffer[MAXIMUM_LOGENTRY_SIZE]; + size_t output_buffer_position; + size_t output_buffer_length; + long file_position; +} + +@property (nonatomic, assign) FILE* inputFile; +@property (nonatomic, strong) NSOutputStream* outputSocketStream; +@property (nonatomic, strong) NSTimer* retryTimer; +@property (nonatomic, strong) LeNetworkStatus* networkStatus; + +@property (nonatomic, strong) LogFile* currentLogFile; + +// when different from currentLogFile.orderNumber, try to finish sending of current log entry and move to the file +@property (nonatomic, assign) NSInteger lastLogFileNumber; + +// TRUE when last written character was '\n' +@property (nonatomic, assign) BOOL logentryCompleted; + +@end + +@implementation LEBackgroundThread + +- (void)initNetworkCommunication +{ + CFWriteStreamRef writeStream; + CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)LOGENTRIES_HOST, LOGENTRIES_PORT, NULL, &writeStream); + + self.outputSocketStream = (__bridge_transfer NSOutputStream *)writeStream; + +#if LOGENTRIES_USE_TLS + [self.outputSocketStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL + forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; +#endif + + self.outputSocketStream.delegate = self; + [self.outputSocketStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [self.outputSocketStream open]; +} + +- (void)checkConnection +{ + if (self.retryTimer) { + [self.retryTimer invalidate]; + self.retryTimer = nil; + } + + if (self.networkStatus) { + self.networkStatus.delegate = nil; + self.networkStatus = nil; + } + + [self check]; +} + +- (void)networkStatusDidChange:(LeNetworkStatus *)networkStatus +{ + if ([networkStatus connected]) { + LE_DEBUG(@"Network status available"); + [self checkConnection]; + } +} + +- (void)retryTimerFired:(NSTimer*)timer +{ + LE_DEBUG(@"Retry timer fired"); + [self checkConnection]; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode +{ + if (eventCode & NSStreamEventOpenCompleted) { + LE_DEBUG(@"Socket event NSStreamEventOpenCompleted"); + eventCode &= ~NSStreamEventOpenCompleted; + self.logentryCompleted = YES; + } + + if (eventCode & NSStreamEventErrorOccurred) { + LE_DEBUG(@"Socket event NSStreamEventErrorOccurred, scheduling retry timer"); + eventCode &= ~NSStreamEventErrorOccurred; + [self.outputSocketStream close]; + self.outputSocketStream = nil; + + self.networkStatus = [LeNetworkStatus new]; + self.networkStatus.delegate = self; + + self.retryTimer = [NSTimer scheduledTimerWithTimeInterval:RETRY_TIMEOUT target:self selector:@selector(retryTimerFired:) userInfo:nil repeats:NO]; + } + + if (eventCode & NSStreamEventHasSpaceAvailable) { + + LE_DEBUG(@"Socket event NSStreamEventHasSpaceAvailable"); + eventCode &= ~NSStreamEventHasSpaceAvailable; + + [self check]; + } + + if (eventCode) LE_DEBUG(@"Received event %x", (unsigned int)eventCode); +} + +- (void)readNextData +{ + output_buffer_position = 0; + + if (feof(self.inputFile)) clearerr(self.inputFile); // clears EOF indicator + size_t read = fread(output_buffer, 1, MAXIMUM_LOGENTRY_SIZE, self.inputFile); + if (!read) { + if (ferror(self.inputFile)) { + LE_DEBUG(@"Error reading logfile"); + } + return; + } + + output_buffer_length = read; +} + +// do we need to ove to another file, are we late? +- (BOOL)shouldSkipToAnotherFile +{ + NSInteger oldestInterrestingFileNumber = self.lastLogFileNumber - MAXIMUM_FILE_COUNT + 1; + return (self.currentLogFile.orderNumber < oldestInterrestingFileNumber); +} + +- (BOOL)openLogFile:(LogFile*)logFile +{ + LE_DEBUG(@"Will open file %ld", (long)logFile.orderNumber); + NSString* path = [logFile logPath]; + self.inputFile = fopen([path cStringUsingEncoding:NSUTF8StringEncoding], "r"); + if (!self.inputFile) { + LE_DEBUG(@"Failed to open log file."); + self.currentLogFile = nil; + return FALSE; + } + + file_position = logFile.bytesProcessed; + int r = fseek(self.inputFile, file_position, SEEK_SET); + if (r) { + LE_DEBUG(@"File seek error."); + file_position = 0; + } else { + LE_DEBUG(@"Seeked to position %ld", file_position); + } + + self.currentLogFile = logFile; + return TRUE; +} + +/* + Remove current file and move to another one given by self.lastFileLogNumber and self.currentLogFile + */ +- (BOOL)skip +{ + LE_DEBUG(@"Will skip, current file number is %ld", (long)self.currentLogFile.orderNumber); + output_buffer_length = 0; + output_buffer_position = 0; + fclose(self.inputFile); + [self.currentLogFile remove]; + + NSInteger next = self.currentLogFile.orderNumber + 1; + + // remove skipped files + while (next + MAXIMUM_FILE_COUNT <= self.lastLogFileNumber) { + + LogFile* logFileToDelete = [[LogFile alloc] initWithNumber:next]; + LE_DEBUG(@"Removing skipped file %ld", (long)logFileToDelete.orderNumber); + [logFileToDelete remove]; + next++; + } + + LogFile* logFile = [[LogFile alloc] initWithNumber:next]; + BOOL opened = [self openLogFile:logFile]; + + if (!opened) { + return FALSE; + } + + LE_DEBUG(@"Did skip, current file number is %ld", (long)self.currentLogFile.orderNumber); + return TRUE; +} + +- (void)check +{ + LE_DEBUG(@"Checking status"); + if (!self.currentLogFile) { + LE_DEBUG(@"Trying to open a log file"); + BOOL fixed = [self initializeInput]; + if (!fixed) { + LE_DEBUG(@"Can't open input file"); + return; + } + } + + if (self.logentryCompleted && [self shouldSkipToAnotherFile]) { + LE_DEBUG(@"Logentry completed and should skip to another file"); + BOOL skipped = [self skip]; + if (!skipped) { + LE_DEBUG(@"Can't skip to next input file"); + return; + } + } + + // check if there is something to send out + if (output_buffer_position >= output_buffer_length) { + + LE_DEBUG(@"Buffer empty, will read data"); + [self readNextData]; + LE_DEBUG(@"Read %ld bytes", (long)output_buffer_length); + + if (!output_buffer_length) { + + if (self.currentLogFile.orderNumber == self.lastLogFileNumber) { + LE_DEBUG(@"Nothing to do, finished"); + LE_DEBUG(@"|"); + return; + } + + LE_DEBUG(@"Skip to another file"); + [self skip]; + [self readNextData]; + if (!output_buffer_length) { + LE_DEBUG(@"Failed to read data from just opened file"); + return; + } + } + } + + + if (self.retryTimer) { + LE_DEBUG(@"Retry timer active"); + return; + } + + if (!self.outputSocketStream) { + [self initNetworkCommunication]; + } + + if ([self.outputSocketStream streamStatus] != NSStreamStatusOpen) { + LE_DEBUG(@"Stream not open yet"); + return; + } + + if (![self.outputSocketStream hasSpaceAvailable]) { + LE_DEBUG(@"No space available"); + return; + } + + NSUInteger maxLength = output_buffer_length - output_buffer_position; + + // truncate maxLength if we need to move to another file + if ([self shouldSkipToAnotherFile]) { + + NSUInteger i = 0; + while (i < maxLength) { + if (output_buffer[output_buffer_position + i] == '\n') { + maxLength = i + 1; + break; + } + i++; + } + } + + NSInteger written = [self.outputSocketStream write:output_buffer + output_buffer_position maxLength:maxLength]; + LE_DEBUG(@"Send out %ld bytes", (long)written); + if (written == -1) { + LE_DEBUG(@"write error occured %@", self.outputSocketStream.streamError); + return; + } +/* + for (int i = 0; i < written; i++) { + char c = output_buffer[output_buffer_position + i]; + LE_DEBUG(@"written '%c' (%02x)", c, c); + } + */ + + if (written > 0) { + self.logentryCompleted = output_buffer[output_buffer_position + written - 1] == '\n'; + }; + + if (self.logentryCompleted && [self shouldSkipToAnotherFile]) { + [self skip]; + return; + } + + // search for checkpoints + NSInteger searchIndex = written - 1; + while (searchIndex >= 0) { + char c = output_buffer[output_buffer_position + searchIndex]; + if (c == '\n') { + [self.currentLogFile markPosition:file_position + searchIndex + 1]; + break; + } + searchIndex--; + } + + file_position += written; + + output_buffer_position += written; + if (output_buffer_position >= output_buffer_length) { + output_buffer_length = 0; + output_buffer_position = 0; + + // check for another data to send out + LE_DEBUG(@"Buffer written, will check for another data"); + [self check]; + } +} + +- (void)keepaliveTimer:(NSTimer*)timer +{ + // does nothing, just keeps runloop running +} + +- (BOOL)initializeInput +{ + if (self.inputFile) return YES; + + LE_DEBUG(@"Opening input file"); + LogFiles* logFiles = [LogFiles new]; + + LogFile* logFile = [logFiles fileToRead]; + BOOL opened = [self openLogFile:logFile]; + return opened; +} + +- (void)initialize:(NSTimer*)timer +{ + [self.initialized lock]; + [self.initialized broadcast]; + [self.initialized unlock]; + self.initialized = nil; +} + +- (void)poke:(NSNumber*)fileOrderNumber +{ + self.lastLogFileNumber = [fileOrderNumber integerValue]; + [self check]; +} + +- (void)main +{ + @autoreleasepool { + NSRunLoop* runLoop = [NSRunLoop currentRunLoop]; + + // this timer will fire after runloop is ready + [NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(initialize:) userInfo:nil repeats:NO]; + + // the runloop needs an input source to keep it running, we will provide dummy timer + [NSTimer scheduledTimerWithTimeInterval:KEEPALIVE_INTERVAL target:self selector:@selector(keepaliveTimer:) userInfo:nil repeats:YES]; + + [runLoop run]; + } +} + +- (void)start +{ + self.initialized = [NSCondition new]; + [super start]; +} + + +@end diff --git a/lelib/LELog.h b/lelib/LELog.h new file mode 100644 index 0000000..93e761f --- /dev/null +++ b/lelib/LELog.h @@ -0,0 +1,47 @@ +// +// LELog.h +// lelib +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +/* Obj-C API */ + +@protocol LELoggableObject + +@optional + +- (NSString*)leDescription; + +@end + +@interface LELog : NSObject + ++ (LELog*)sharedInstance; + +/* + Appends space separated token to each log message. + */ +@property (atomic, copy) NSString* token; + +/* + When object implements LELoggableObject interface, it logs return value of + leDescription method. Otherwise, tries to log return value of standard + description method. + */ +- (void)log:(NSObject*)object; + +/* + Log UIApplicationDidFinishLaunchingNotification, UIApplicationDidBecomeActiveNotification, + UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification, + UIApplicationWillTerminateNotification. + */ +@property (nonatomic, assign) BOOL logApplicationLifecycleNotifications; + +@end + + + diff --git a/lelib/LELog.m b/lelib/LELog.m new file mode 100644 index 0000000..e9f38f7 --- /dev/null +++ b/lelib/LELog.m @@ -0,0 +1,181 @@ +// +// LELog.m +// lelib +// +// Created by Petr on 25/11/13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +#import "LELog.h" +#import "LEBackgroundThread.h" +#import "LogFiles.h" +#import "lelib.h" + + +extern LEBackgroundThread* backgroundThread; + +void le_write_string(NSString* string); +void le_poke(); + +extern dispatch_queue_t le_write_queue; +extern char* le_token; + +@implementation LELog + +- (id)init +{ + self = [super init]; + if (le_init()) return nil; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationWillEnterForegroundNotification object:nil]; + + le_poke(); + + return self; +} + +- (void)dealloc +{ + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; +} + +- (void)log:(NSObject*)object +{ + NSString* text = nil; + + if ([object respondsToSelector:@selector(leDescription)]) { + id leLoggableObject = (id)object; + text = [leLoggableObject leDescription]; + } else if ([object isKindOfClass:[NSString class]]) { + text = (NSString*)object; + } else { + text = [object description]; + } + + text = [text stringByReplacingOccurrencesOfString:@"\n" withString:@"\u2028"]; + + LE_DEBUG(@"%@", text); + + le_write_string(text); + le_poke(); +} + ++ (LELog*)sharedInstance +{ + static dispatch_once_t once; + static LELog* sharedInstance; + dispatch_once(&once, ^{ + sharedInstance = [LELog new]; + }); + return sharedInstance; +} + +- (void)setToken:(NSString *)token +{ + le_set_token([token cStringUsingEncoding:NSUTF8StringEncoding]); +} + +- (NSString*)token +{ + __block NSString* r = nil; + dispatch_sync(le_write_queue, ^{ + + if (!le_token || le_token[0]) { + r = nil; + } else { + r = [NSString stringWithUTF8String:le_token]; + } + }); + + return r; +} + +- (void)notificationReceived:(NSNotification*)notification +{ + if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) { + + if (self.logApplicationLifecycleNotifications) { + [self log:notification.name]; + } + + le_poke(); + + return; + } + + if ([notification.name isEqualToString:UIApplicationDidBecomeActiveNotification]) { + [self log:notification.name]; + return; + } + + if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) { + [self log:notification.name]; + return; + } + + if ([notification.name isEqualToString:UIApplicationDidFinishLaunchingNotification]) { + [self log:notification.name]; + return; + } + + if ([notification.name isEqualToString:UIApplicationWillTerminateNotification]) { + [self log:notification.name]; + return; + } + + if ([notification.name isEqualToString:UIApplicationDidEnterBackgroundNotification]) { + [self log:notification.name]; + return; + } + + if ([notification.name isEqualToString:UIApplicationDidReceiveMemoryWarningNotification]) { + [self log:notification.name]; + return; + } +} + +- (void)registerForNotifications +{ + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationDidBecomeActiveNotification object:nil]; + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationWillResignActiveNotification object:nil]; + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationDidFinishLaunchingNotification object:nil]; + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationWillTerminateNotification object:nil]; + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [center addObserver:self selector:@selector(notificationReceived:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; +} + +- (void)unregisterFromNotifications +{ + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [center removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + [center removeObserver:self name:UIApplicationDidFinishLaunchingNotification object:nil]; + [center removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; + [center removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [center removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; +} + +- (void)setLogApplicationLifecycleNotifications:(BOOL)logApplicationLifecycleNotifications +{ + @synchronized(self) { + + if (logApplicationLifecycleNotifications == _logApplicationLifecycleNotifications) return; + + _logApplicationLifecycleNotifications = logApplicationLifecycleNotifications; + + if (logApplicationLifecycleNotifications) { + [self registerForNotifications]; + } else { + [self unregisterFromNotifications]; + } + } +} + + +@end diff --git a/lelib/LeNetworkStatus.h b/lelib/LeNetworkStatus.h new file mode 100644 index 0000000..6722737 --- /dev/null +++ b/lelib/LeNetworkStatus.h @@ -0,0 +1,19 @@ + +#import + +@class LeNetworkStatus; + +@protocol LeNetworkStatusDelegete + +- (void)networkStatusDidChange:(LeNetworkStatus*)networkStatus; + +@end + +@interface LeNetworkStatus: NSObject + +@property (nonatomic, weak) id delegate; +- (BOOL)connected; + +@end + + diff --git a/lelib/LeNetworkStatus.m b/lelib/LeNetworkStatus.m new file mode 100644 index 0000000..5a1d003 --- /dev/null +++ b/lelib/LeNetworkStatus.m @@ -0,0 +1,93 @@ + +#import +#import + + +#import + +#import "LeNetworkStatus.h" + +@interface LeNetworkStatus () { + + SCNetworkReachabilityRef reachabilityRef; +} + +- (void)callback; + +@end + +@implementation LeNetworkStatus + +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ + LeNetworkStatus* networkStatus = (__bridge LeNetworkStatus*)info; + [networkStatus callback]; +} + +- (void)callback +{ + [self.delegate networkStatusDidChange:self]; +} + +- (void)start +{ + SCNetworkReachabilityContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + if (!SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) return; + SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); +} + +- (void)stop +{ + if (reachabilityRef == NULL) return; + SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); +} + +- (id)init +{ + self = [super init]; + if (!self) return nil; + + struct sockaddr_in addr; + bzero(&addr, sizeof(addr)); + addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + + reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)&addr); + if (reachabilityRef != NULL) [self start]; + + return self; +} + +- (void)dealloc +{ + [self stop]; + if (reachabilityRef != NULL) CFRelease(reachabilityRef); +} + +- (BOOL)networkStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) return NO; + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) return YES; + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) return YES; + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) return YES; + + return NO; +} + +- (BOOL)connected +{ + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return [self networkStatusForFlags:flags]; + } + + return YES; +} +@end diff --git a/lelib/LogFile.h b/lelib/LogFile.h new file mode 100644 index 0000000..1011211 --- /dev/null +++ b/lelib/LogFile.h @@ -0,0 +1,38 @@ +// +// LogFile.h +// lelib +// +// Created by Petr on 01.12.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +#define LOGFILE_BASENAME @"lelog" + +@interface LogFile : NSObject + +- (id)initWithNumber:(NSInteger)number; + +@property (nonatomic, assign) NSInteger orderNumber; +@property (nonatomic, assign) NSInteger bytesProcessed; + +- (NSString*)logPath; + +- (BOOL)remove; +- (void)changeOrderNumber:(NSInteger)newOrderNumber; + +/* + Write atomically position into mark file. + */ +- (void)markPosition:(NSInteger)position; + +// returns zero if the file does not exist +- (NSUInteger)size; + ++ (NSInteger)logFileNumber:(NSString*)filename; ++ (NSInteger)markFileNumber:(NSString*)filename; + + + +@end diff --git a/lelib/LogFile.m b/lelib/LogFile.m new file mode 100644 index 0000000..c0e185d --- /dev/null +++ b/lelib/LogFile.m @@ -0,0 +1,234 @@ +// +// LogFile.m +// lelib +// +// Created by Petr on 01.12.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import "LogFile.h" +#import "LogFiles.h" +#import "lelib.h" + +#define NAME_NUMBER_LENGTH 10 +#define LOG_EXTENSION @".log" +#define MARK_EXTENSION @".mark" + +@implementation LogFile + +/* + Pads file number with zeroes up tu NAME_NUMBER_LENGTH. + */ + ++ (NSString*)filename:(NSInteger)orderNumber +{ + NSString* number = [NSString stringWithFormat:@"%ld", (long)orderNumber]; + NSInteger space = NAME_NUMBER_LENGTH - [number length]; + NSMutableString* string = [NSMutableString stringWithCapacity:NAME_NUMBER_LENGTH]; + while (space > 0) { + [string appendString:@"0"]; + space--; + } + + [string appendString:number]; + return string; +} + ++ (NSString*)logFilename:(NSInteger)orderNumber +{ + return [[self filename:orderNumber] stringByAppendingString:LOG_EXTENSION]; +} + ++ (NSString*)markFilename:(NSInteger)orderNumber +{ + return [[self filename:orderNumber] stringByAppendingString:MARK_EXTENSION]; +} + ++ (NSString*)logPath:(NSInteger)orderNumber +{ + return [[LogFiles logsDirectory] stringByAppendingFormat:@"/%@", [self logFilename:orderNumber]]; +} + +- (NSString*)logPath +{ + return [[self class] logPath:self.orderNumber]; +} + ++ (NSString*)markPath:(NSInteger)orderNumber +{ + return [[LogFiles logsDirectory] stringByAppendingFormat:@"/%@", [self markFilename:orderNumber]]; +} + +- (NSString*)markPath +{ + return [[self class] markPath:self.orderNumber]; +} + +- (BOOL)remove +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSString* logPath = [self logPath]; + + NSError* error = nil; + BOOL deleted; + + deleted = [fileManager removeItemAtPath:logPath error:&error]; + if (!deleted) { + LE_DEBUG(@"Can't remove file at path '%@' with error %@", logPath, error); + return NO; + } + + NSString* markPath = [self markPath]; + if ([fileManager fileExistsAtPath:markPath]) { + error = nil; + deleted = [fileManager removeItemAtPath:markPath error:&error]; + if (!deleted) { + LE_DEBUG(@"Can't remove file at path '%@' with error %@", logPath, error); + } + } + + return YES; +} + +- (void)changeOrderNumber:(NSInteger)newOrderNumber +{ + NSString* oldLogPath = [[self class] logPath:self.orderNumber]; + NSString* oldMarkPath = [[self class] markPath:self.orderNumber]; + NSString* newLogPath = [[self class] logPath:newOrderNumber]; + NSString* newMarkPath = [[self class] markPath:newOrderNumber]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + + // it is neccessary to remove old mark (which should not exist, but we need to be sure) + if ([fileManager fileExistsAtPath:newMarkPath]) { + NSError* error = nil; + BOOL removed = [fileManager removeItemAtPath:newMarkPath error:&error]; + if (!removed) { + LE_DEBUG(@"Can't remove file at path '%@' with error %@", newMarkPath, error); + return; + } + } + + NSError* error = nil; + BOOL moved = [fileManager moveItemAtPath:oldLogPath toPath:newLogPath error:&error]; + + if (!moved) { + LE_DEBUG(@"Can't move file from path '%@' to path '%@' with error %@", oldLogPath, newLogPath, error); + return; + } + + self.orderNumber = newOrderNumber; //changed after rename + + if ([fileManager fileExistsAtPath:oldMarkPath]) { + + NSError* error = nil; + moved = [fileManager moveItemAtPath:oldMarkPath toPath:newMarkPath error:&error]; + + if (!moved) { + LE_DEBUG(@"Can't move file from path '%@' to path '%@' with error %@", oldMarkPath, newMarkPath, error); + } + } +} + +- (NSUInteger)size +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + + NSString* path = [self logPath]; + + if (![fileManager fileExistsAtPath:path]) return 0; + + NSError* error = nil; + NSDictionary *attributes = [fileManager attributesOfItemAtPath: path error: &error]; + if (!attributes) { + LE_DEBUG(@"Can't get file '%@' attributes with error %@", path, error); + } + UInt32 result = (UInt32)[attributes fileSize]; + return result; + +} + +- (void)loadMark +{ + NSString* path = [self markPath]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:path]) { + self.bytesProcessed = 0; + return; + } + + NSError* error = nil; + NSString* mark = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:&error]; + if (error) { + LE_DEBUG(@"Can't read mark file"); + self.bytesProcessed = 0; + return; + } + + self.bytesProcessed = [mark integerValue]; +} + +- (id)initWithNumber:(NSInteger)number +{ + self = [self init]; + if (!self) return nil; + + self.orderNumber = number; + [self loadMark]; + + return self; +} + ++ (NSInteger)filePrefix:(NSString*)filename +{ + NSUInteger index = 0; + NSUInteger length = [filename length]; + if (!length) return -1; // empty or nil filename + char c = [filename characterAtIndex:index++]; + while (index < length && '0' <= c && c <= '9') c = [filename characterAtIndex:index++]; + + // we need a dot fter digits + if (index == length) return -1; + if (c != '.') return -1; + + NSString* number = [filename substringToIndex:index]; + return [number integerValue]; +} + ++ (NSInteger)numberForFile:(NSString*)filename withExtension:(NSString*)extension +{ + NSInteger number = [self filePrefix:filename]; + if (number < 0) return -1; + + // we know that there is a dot + NSRange r = [filename rangeOfString:@"."]; + NSString* rest = [filename substringFromIndex:r.location]; + if (![rest isEqualToString:extension]) return -1; + return number; +} + ++ (NSInteger)logFileNumber:(NSString*)filename +{ + return [self numberForFile:filename withExtension:LOG_EXTENSION]; +} + ++ (NSInteger)markFileNumber:(NSString*)filename +{ + return [self numberForFile:filename withExtension:MARK_EXTENSION]; +} + +- (void)markPosition:(NSInteger)position +{ + NSString* markString = [NSString stringWithFormat:@"%ld", (long)position]; + NSString* markPath = [self markPath]; + NSError* error = nil; + BOOL r = [markString writeToFile:markPath atomically:YES encoding:NSASCIIStringEncoding error:&error]; + if (!r) { + LE_DEBUG(@"Error marking read position to file '%@'", error); + } + self.bytesProcessed = position; +} + + +@end diff --git a/lelib/LogFiles.h b/lelib/LogFiles.h new file mode 100644 index 0000000..f981223 --- /dev/null +++ b/lelib/LogFiles.h @@ -0,0 +1,29 @@ +// +// LogFiles.h +// lelib +// +// Created by Petr on 01.12.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import +#import "LogFile.h" + + +@interface LogFiles : NSObject + +@property (nonatomic, strong) NSMutableArray* logFiles; + ++ (NSString*)logsDirectory; + +/* + Check all files, remove files which are obsolete and rename valid files. + */ +- (void)consolidate; +- (LogFile*)fileToWrite; + +- (LogFile*)fileToRead; + +- (LogFile*)fileWithNumber:(NSInteger)number; + +@end diff --git a/lelib/LogFiles.m b/lelib/LogFiles.m new file mode 100644 index 0000000..925565d --- /dev/null +++ b/lelib/LogFiles.m @@ -0,0 +1,190 @@ +// +// LogFiles.m +// lelib +// +// Created by Petr on 01.12.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import "LogFiles.h" +#import "LELog.h" +#import "lelib.h" + + +#define CACHES_DIRECTORY_BASENAME @"logentries" + +@implementation LogFiles + ++ (NSString*)cachesDirectory +{ + NSArray* directories = [[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask]; + + if (![directories count]) { + LE_DEBUG(@"Could not find caches directory."); + return nil; + } + + NSURL* cachesDirectory = directories[0]; + return [cachesDirectory path]; +} + ++ (NSString*)logsDirectory +{ + return [[self cachesDirectory] stringByAppendingFormat:@"/%@", CACHES_DIRECTORY_BASENAME]; +} + +- (LogFile*)fileToWrite +{ + return [self.logFiles lastObject]; +} + +- (LogFile*)fileToRead +{ + if (![self.logFiles count]) return nil; + return [self.logFiles objectAtIndex:0]; +} + +- (LogFile*)fileWithNumber:(NSInteger)number +{ + for (LogFile* logFile in self.logFiles) { + if (logFile.orderNumber == number) return logFile; + } + + return nil; +} + +/* + Remove all mark files which does not have it's counterpart. + */ +- (void)consolidateMarks +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* error = nil; + NSArray* contents = [fileManager contentsOfDirectoryAtPath:[[self class] logsDirectory] error:&error]; + if (!contents) { + LE_DEBUG(@"Can't get contents of logs directory."); + return; + } + + for (NSString* filename in contents) { + NSInteger number = [LogFile markFileNumber:filename]; + if (number < 0) continue; // you are not my type + LogFile* logFile = [self fileWithNumber:number]; + if (!logFile) { + + NSError* error = nil; + NSString* path = [[[self class] logsDirectory] stringByAppendingFormat:@"/%@", filename]; + BOOL r = [fileManager removeItemAtPath:path error:&error]; + if (!r) { + LE_DEBUG(@"Can't remove file '%@' with error %@.", filename, error); + } + } + } +} + +- (void)consolidate +{ + // if there are no files yet add one + LogFile* lastLog = [self.logFiles lastObject]; + if (!lastLog) { + + LogFile* logFile = [LogFile new]; + logFile.orderNumber = 1; + [self.logFiles addObject:logFile]; + return; + } + + // if there is more than MAXIMUM_FILE_COUNT files remove first ones and rename the rest + NSUInteger count = [self.logFiles count]; + NSInteger orderNumber = MAXIMUM_FILE_COUNT - count; + if (orderNumber > 0) orderNumber = 0; + NSUInteger index = 0; + while (index < [self.logFiles count]) { + LogFile* logFile = self.logFiles[index]; + orderNumber++; + if (orderNumber <= 0) { + + BOOL removed = [logFile remove]; + if (removed) { + [self.logFiles removeObjectAtIndex:index]; + } + + continue; + } + + index++; + if (logFile.orderNumber == orderNumber) continue; + + [logFile changeOrderNumber:orderNumber]; + } + + [self consolidateMarks]; +} + +- (BOOL)createDirectory:(NSString*)path +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* error = nil; + BOOL created = [fileManager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:NO error:&error]; + + if (!created) { + LE_DEBUG(@"Can't create logentries directory '%@' with error %@", path, error); + } + return created; +} + +// if the directory does not exists, create it +- (BOOL)checkLogsDirectory +{ + NSString* logsDirectoryPath = [[self class] logsDirectory]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + BOOL isDirectory = NO; + if (![fileManager fileExistsAtPath:logsDirectoryPath isDirectory:&isDirectory]) { + return [self createDirectory:logsDirectoryPath]; + } else { + if (!isDirectory) { + LE_DEBUG(@"Can't create logentries directory '%@', file with same name already exists.", logsDirectoryPath); + return NO; + } else { + // directory already exists + return YES; + } + } +} + +- (id)init +{ + self = [super init]; + if (!self) return nil; + + if (![self checkLogsDirectory]) return nil; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* error = nil; + NSArray* contents = [fileManager contentsOfDirectoryAtPath:[[self class] logsDirectory] error:&error]; + if (!contents) { + LE_DEBUG(@"Can't get contents of logs directory."); + return nil; + } + + self.logFiles = [[NSMutableArray alloc] initWithCapacity:[contents count] + 1]; + + for (NSString* filename in contents) { + NSInteger number = [LogFile logFileNumber:filename]; + if (number < 0) continue; // you are not my type + LogFile* logFile = [[LogFile alloc] initWithNumber:number]; + [self.logFiles addObject:logFile]; + } + + [self.logFiles sortUsingComparator:^(LogFile* a, LogFile* b) { + + if (a.orderNumber < b.orderNumber) return NSOrderedAscending; + if (a.orderNumber > b.orderNumber) return NSOrderedDescending; + return NSOrderedSame; + }]; + + return self; +} + + +@end diff --git a/lelib/lecore.h b/lelib/lecore.h new file mode 100644 index 0000000..1d05219 --- /dev/null +++ b/lelib/lecore.h @@ -0,0 +1,26 @@ +// +// lecore.h +// lelib +// +// Created by Petr on 06.01.14. +// Copyright (c) 2014 JLizard. All rights reserved. +// + +#import + +#ifndef lelib_lecore_h +#define lelib_lecore_h + +#define TOKEN_LENGTH 36 +#define MAXIMUM_LOGENTRY_SIZE 8192 + +#define MAXIMUM_FILE_COUNT 3 +#define MAXIMUM_LOGFILE_SIZE (1024 * 1024) + +/* Pure C API */ + +int le_init(); +void le_log(const char* message); +void le_set_token(const char* token); + +#endif diff --git a/lelib/lecore.m b/lelib/lecore.m new file mode 100644 index 0000000..a16c42f --- /dev/null +++ b/lelib/lecore.m @@ -0,0 +1,230 @@ +// +// lecore.c +// lelib +// +// Created by Petr on 06.01.14. +// Copyright (c) 2014 JLizard. All rights reserved. +// + + + +#include "LEBackgroundThread.h" +#include "LogFiles.h" + +#include "lelib.h" + +LEBackgroundThread* backgroundThread; + +dispatch_queue_t le_write_queue; +char* le_token; + +static int logfile_descriptor; +static int logfile_size; +static int file_order_number; + +static char buffer[MAXIMUM_LOGENTRY_SIZE]; + +/* + Sets logfile_descriptor to -1 when fails, this means that all subsequent write attempts will fail + return 0 on success + */ +static int open_file(const char* path) +{ + mode_t mode = 0664; + + logfile_size = 0; + + logfile_descriptor = open(path, O_CREAT | O_WRONLY, mode); + if (logfile_descriptor < 0) { + LE_DEBUG(@"Unable to open log file."); + return 1; + } + + logfile_size = (int)lseek(logfile_descriptor, 0, SEEK_END); + if (logfile_size < 0) { + LE_DEBUG(@"Unable to seek at end of file."); + return 1; + } + + LE_DEBUG(@"log file %s opened", path); + return 0; +} + +void le_poke() +{ + if (!backgroundThread) { + backgroundThread = [LEBackgroundThread new]; + [backgroundThread start]; + + // if this is nil, initialization was already proceeded + NSCondition* initialized = backgroundThread.initialized; + + [initialized lock]; + [initialized wait]; + [initialized unlock]; + } + + [backgroundThread performSelector:@selector(poke:) onThread:backgroundThread withObject:@(file_order_number) waitUntilDone:NO modes:@[NSDefaultRunLoopMode]]; +} + +static void le_exception_handler(NSException *exception) +{ + NSString* message = [NSString stringWithFormat:@"Exception name=%@, reason=%@, userInfo=%@ addresses=%@ symbols=%@", [exception name], [exception reason], [exception userInfo], [exception callStackReturnAddresses], [exception callStackSymbols]]; + LE_DEBUG(@"%@", message); + message = [message stringByReplacingOccurrencesOfString:@"\n" withString:@"\u2028"]; + le_log([message cStringUsingEncoding:NSUTF8StringEncoding]); +} + +int le_init() +{ + static dispatch_once_t once; + + __block int r = 0; + + dispatch_once(&once, ^{ + + // pesimistic strategy + r = 1; + + le_write_queue = dispatch_queue_create("com.logentries.write", NULL); + + LogFiles* logFiles = [LogFiles new]; + if (!logFiles) { + LE_DEBUG(@"Error initializing logs directory."); + return; + } + + [logFiles consolidate]; + + LogFile* file = [logFiles fileToWrite]; + file_order_number = (int)file.orderNumber; + NSString* logFilePath = [file logPath]; + + const char* path = [logFilePath cStringUsingEncoding:NSASCIIStringEncoding]; + if (!path) { + LE_DEBUG(@"Invalid logfile path."); + return; + } + + if (open_file(path)) { + return; + }; + + r = 0; + + NSSetUncaughtExceptionHandler(&le_exception_handler); + + return; + }); + + return r; +} + +/* + Takes used_length characters from buffer, appends a space and token and writes in into log. Handles log rotation. + */ +static void write_buffer(size_t used_length) +{ + if (logfile_size + used_length > MAXIMUM_LOGFILE_SIZE) { + + close(logfile_descriptor); + file_order_number++; + + LogFile* logFile = [[LogFile alloc] initWithNumber:file_order_number]; + NSString* p = [logFile logPath]; + const char* path = [p cStringUsingEncoding:NSASCIIStringEncoding]; + + open_file(path); + } + + size_t written = write(logfile_descriptor, buffer, used_length); + if (written < used_length) { + LE_DEBUG(@"Could not write to log, no space left?"); + return; + } + + logfile_size += written; +} + +void le_log(const char* message) +{ + dispatch_sync(le_write_queue, ^{ + + size_t token_length = strlen(le_token); + size_t max_length = MAXIMUM_LOGENTRY_SIZE - token_length - 2; // minus token length, space separator and lf + + size_t length = strlen(message); + if (max_length < length) { + LE_DEBUG(@"Too large message, it will be truncated"); + length = max_length; + } + + memcpy(buffer, le_token, token_length); + buffer[token_length] = ' '; + memcpy(buffer + token_length + 1, message, length); + + size_t total_length = token_length + 1 + length; + buffer[total_length++] = '\n'; + + write_buffer(total_length); + le_poke(); + }); + +} + +void le_write_string(NSString* string) +{ + dispatch_sync(le_write_queue, ^{ + + NSUInteger tokenLength = strlen(le_token); + + NSUInteger maxLength = MAXIMUM_LOGENTRY_SIZE - tokenLength - 2; // minus token length, space separator and \n + if ([string length] > maxLength) { + LE_DEBUG(@"Too large message, it will be truncated"); + } + + memcpy(buffer, le_token, tokenLength); + buffer[tokenLength] = ' '; + + NSRange range = {.location = 0, .length = [string length]}; + + NSUInteger usedLength = 0; + BOOL r = [string getBytes:(buffer + tokenLength + 1) maxLength:maxLength usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionAllowLossy range:range remainingRange:NULL]; + + if (!r) { + LE_DEBUG(@"Error converting message characters."); + return; + } + + NSUInteger totalLength = tokenLength + 1 + usedLength; + buffer[totalLength++] = '\n'; + write_buffer((size_t)totalLength); + }); +} + +void le_set_token(const char* token) +{ + size_t length = strlen(token); + + if (length < TOKEN_LENGTH) { + LE_DEBUG(@"Invalid token length, it will not be used."); + return; + } + + if (length >= MAXIMUM_LOGENTRY_SIZE) { + LE_DEBUG(@"Token too large, it will not be used."); + return; + } + + char* buffer = malloc(strlen(token) + 1); + if (!buffer) { + LE_DEBUG(@"Can't allocate token buffer."); + return; + } + + strcpy(buffer, token); + + dispatch_sync(le_write_queue, ^{ + le_token = buffer; + }); +} diff --git a/lelib/lelib-Prefix.pch b/lelib/lelib-Prefix.pch new file mode 100644 index 0000000..eb2007e --- /dev/null +++ b/lelib/lelib-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/lelib/lelib.h b/lelib/lelib.h new file mode 100644 index 0000000..ad5e03a --- /dev/null +++ b/lelib/lelib.h @@ -0,0 +1,18 @@ +// +// lelib.h +// lelib +// +// Created by Petr on 27.10.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#define LE_DEBUG_LOGS 1 + +#if LE_DEBUG_LOGS +#define LE_DEBUG(...) NSLog(__VA_ARGS__) +#else +#define LE_DEBUG(...) +#endif + +#import "LELog.h" +#import "lecore.h" diff --git a/lelibTests/en.lproj/InfoPlist.strings b/lelibTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/lelibTests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/lelibTests/lelibTests-Info.plist b/lelibTests/lelibTests-Info.plist new file mode 100644 index 0000000..5a32a6b --- /dev/null +++ b/lelibTests/lelibTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.logentries.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/lelibTests/lelibTests.m b/lelibTests/lelibTests.m new file mode 100644 index 0000000..51a8945 --- /dev/null +++ b/lelibTests/lelibTests.m @@ -0,0 +1,43 @@ +// +// lelibTests.m +// lelibTests +// +// Created by Petr on 27.10.13. +// Copyright (c) 2013 JLizard. All rights reserved. +// + +#import + +#import "lelib.h" + +@interface lelibTests : XCTestCase + +@end + +@implementation lelibTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testLog +{ + LELog* log = [LELog sharedInstance]; + log.token = @"f66815d1-702c-414b-8dcc-bb73de372584"; + [log log:@"ahoj"]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + while ([runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); + + XCTFail(@"Test exited runloop"); +} + +@end