From 149c9b9dc7a6fe42a98d680daaa9df30e1c19580 Mon Sep 17 00:00:00 2001 From: Marco Filetti Date: Wed, 26 Apr 2017 15:38:38 +0300 Subject: [PATCH] swift 3 update + Sierra compatibility --- .gitmodules | 6 + JustUsed.xcodeproj/project.pbxproj | 229 +-- JustUsed/Base.lproj/Main.storyboard | 73 +- JustUsed/DiMe Data/CalendarEvent.swift | 34 +- JustUsed/DiMe Data/DesktopEvent.swift | 24 +- JustUsed/DiMe Data/DiMeData.swift | 76 +- .../DocumentInformationElement.swift | 83 +- JustUsed/DiMe Data/Event.swift | 22 +- JustUsed/DiMe Data/HistoryManager.swift | 171 +-- JustUsed/DiMe Data/Location.swift | 8 +- JustUsed/DiMe Data/Person.swift | 65 +- .../CLGeoCoder+getDescription.swift | 8 +- JustUsed/Extensions/NSDate+Extensions.swift | 20 +- JustUsed/Extensions/NSURL+getMime.swift | 12 +- .../Extensions/PDFDocument+Extensions.swift | 238 ++- JustUsed/Extensions/String+Extensions.swift | 86 +- JustUsed/Info.plist | 4 +- JustUsed/Libraries/SwiftyJSON.swift | 1353 ----------------- JustUsed/Libraries/XCGLogger.swift | 893 ----------- JustUsed/Model/AppDelegate.swift | 52 +- JustUsed/Model/AppSingleton.swift | 31 +- JustUsed/Model/CalendarTracker.swift | 88 +- JustUsed/Model/ChromeHistoryFetcher.swift | 40 +- JustUsed/Model/FirefoxHistoryFetcher.swift | 66 +- JustUsed/Model/GenericBrowserHistory.swift | 80 +- JustUsed/Model/LocationSingleton.swift | 11 +- JustUsed/Model/RecentDocumentsTracker.swift | 8 +- JustUsed/Model/RecentPlistTracker.swift | 55 +- JustUsed/Model/SafariHistoryFetcher.swift | 50 +- JustUsed/Model/SpotlightDocumentTracker.swift | 52 +- JustUsed/REST/CrossRefSession.swift | 49 + JustUsed/REST/DiMePusher.swift | 85 ++ JustUsed/REST/DiMeSession.swift | 236 +++ .../UI/DiMePreferencesViewController.swift | 50 +- JustUsed/UI/ViewController.swift | 52 +- JustUsed/Utils/JustUsedConstants.swift | 8 +- JustUsedTests/JustUsedTests.swift | 2 +- SwiftyJSON | 1 + SwiftyJSON.swift | 1343 ---------------- XCGLogger | 1 + 40 files changed, 1298 insertions(+), 4467 deletions(-) create mode 100644 .gitmodules delete mode 100755 JustUsed/Libraries/SwiftyJSON.swift delete mode 100755 JustUsed/Libraries/XCGLogger.swift create mode 100644 JustUsed/REST/CrossRefSession.swift create mode 100644 JustUsed/REST/DiMePusher.swift create mode 100644 JustUsed/REST/DiMeSession.swift create mode 160000 SwiftyJSON delete mode 100755 SwiftyJSON.swift create mode 160000 XCGLogger diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..440e890 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "XCGLogger"] + path = XCGLogger + url = https://github.com/DaveWoodCom/XCGLogger.git +[submodule "SwiftyJSON"] + path = SwiftyJSON + url = https://github.com/SwiftyJSON/SwiftyJSON.git diff --git a/JustUsed.xcodeproj/project.pbxproj b/JustUsed.xcodeproj/project.pbxproj index 514573f..4f60cd3 100644 --- a/JustUsed.xcodeproj/project.pbxproj +++ b/JustUsed.xcodeproj/project.pbxproj @@ -7,13 +7,16 @@ objects = { /* Begin PBXBuildFile section */ + A90E8A1F1EB0CB52009A860E /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90E8A1E1EB0CB52009A860E /* SwiftyJSON.swift */; }; + A90E8A3F1EB0CDA1009A860E /* XCGLogger.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A90E8A321EB0CD41009A860E /* XCGLogger.framework */; }; + A90E8A4F1EB0DBCB009A860E /* CrossRefSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90E8A4B1EB0DBCB009A860E /* CrossRefSession.swift */; }; + A90E8A511EB0DBCB009A860E /* DiMePusher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90E8A4D1EB0DBCB009A860E /* DiMePusher.swift */; }; + A90E8A521EB0DBCB009A860E /* DiMeSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90E8A4E1EB0DBCB009A860E /* DiMeSession.swift */; }; A914066E1C0348EF00C3C2E0 /* SpotlightDocumentTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914066D1C0348EF00C3C2E0 /* SpotlightDocumentTracker.swift */; }; A91406731C036D5500C3C2E0 /* FirefoxHistoryFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91406721C036D5500C3C2E0 /* FirefoxHistoryFetcher.swift */; }; A92411E71C69FAC900DD6449 /* CalendarTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92411E61C69FAC900DD6449 /* CalendarTracker.swift */; }; A92411EF1C69FB3F00DD6449 /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92411EE1C69FB3F00DD6449 /* CalendarEvent.swift */; }; A92411F11C69FD8F00DD6449 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92411F01C69FD8F00DD6449 /* Person.swift */; }; - A92411F31C69FDCB00DD6449 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92411F21C69FDCB00DD6449 /* SwiftyJSON.swift */; }; - A92574591BCF8FF9004866B4 /* XCGLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92574581BCF8FF9004866B4 /* XCGLogger.swift */; }; A925745B1BCF901D004866B4 /* JustUsedConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A925745A1BCF901D004866B4 /* JustUsedConstants.swift */; }; A92574621BCF906A004866B4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A925745D1BCF906A004866B4 /* AppDelegate.swift */; }; A92574631BCF906A004866B4 /* AppSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A925745E1BCF906A004866B4 /* AppSingleton.swift */; }; @@ -37,7 +40,6 @@ A9BD83C01C6B512600D62A61 /* CLGeoCoder+getDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BD83BF1C6B512600D62A61 /* CLGeoCoder+getDescription.swift */; }; A9BD83C21C6B516100D62A61 /* EKCalendar+getCompositeName.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BD83C11C6B516100D62A61 /* EKCalendar+getCompositeName.swift */; }; A9D4A2D51B7B9B3400EC1D68 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = A9D4A2D41B7B9B3400EC1D68 /* libsqlite3.dylib */; }; - A9D894201BD00EED00314475 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9F5077D1BC7B8DE00275C6C /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A9F3807B1C199E38009251AB /* GenericBrowserHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F3807A1C199E38009251AB /* GenericBrowserHistory.swift */; }; A9F507881BC7BA4B00275C6C /* DiMePreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F507861BC7BA4B00275C6C /* DiMePreferencesViewController.swift */; }; A9F507891BC7BA4B00275C6C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F507871BC7BA4B00275C6C /* ViewController.swift */; }; @@ -50,68 +52,68 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - A9438DE21B7A3F0400F1A54B /* PBXContainerItemProxy */ = { + A90E8A2F1EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9438DC71B7A3F0300F1A54B /* Project object */; - proxyType = 1; - remoteGlobalIDString = A9438DCE1B7A3F0400F1A54B; - remoteInfo = JustUsed; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 55E3EE4519D76F280068C3A7; + remoteInfo = "XCGLogger (iOS)"; }; - A9D894211BD00EED00314475 /* PBXContainerItemProxy */ = { + A90E8A311EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = 4DD67C0A1A5C55C900ED2280; - remoteInfo = "Alamofire OSX"; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 554DF41719D76FE7005708BE; + remoteInfo = "XCGLogger (OS X)"; }; - A9F380821C199E39009251AB /* PBXContainerItemProxy */ = { + A90E8A331EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 4CF626EF1BA7CB3E0011A099; - remoteInfo = "Alamofire tvOS"; + remoteGlobalIDString = 70CB94201B99E728007802FF; + remoteInfo = "XCGLogger (watchOS)"; }; - A9F380841C199E39009251AB /* PBXContainerItemProxy */ = { + A90E8A351EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 4CF626F81BA7CB3E0011A099; - remoteInfo = "Alamofire tvOS Tests"; + remoteGlobalIDString = 5515A3DF1BA119FA0047BA31; + remoteInfo = "XCGLogger (tvOS)"; }; - A9F5077A1BC7B8DE00275C6C /* PBXContainerItemProxy */ = { + A90E8A371EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; proxyType = 2; - remoteGlobalIDString = F8111E3319A95C8B0040E7D1; - remoteInfo = "Alamofire iOS"; + remoteGlobalIDString = 55E3EE5019D76F280068C3A7; + remoteInfo = XCGLoggerTests_iOS; }; - A9F5077C1BC7B8DE00275C6C /* PBXContainerItemProxy */ = { + A90E8A391EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 4DD67C0B1A5C55C900ED2280; - remoteInfo = "Alamofire OSX"; + remoteGlobalIDString = 55BB1F011B79DC7500709779; + remoteInfo = XCGLoggerTests_OSX; }; - A9F5077E1BC7B8DE00275C6C /* PBXContainerItemProxy */ = { + A90E8A3B1EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; proxyType = 2; - remoteGlobalIDString = E4202FE01B667AA100C997FB; - remoteInfo = "Alamofire watchOS"; + remoteGlobalIDString = 558EEB851D6BAC61006A9C7A; + remoteInfo = XCGLoggerTests_watchOS; }; - A9F507801BC7B8DE00275C6C /* PBXContainerItemProxy */ = { + A90E8A3D1EB0CD41009A860E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + containerPortal = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; proxyType = 2; - remoteGlobalIDString = F8111E3E19A95C8B0040E7D1; - remoteInfo = "Alamofire iOS Tests"; + remoteGlobalIDString = 558EEB921D6BAC66006A9C7A; + remoteInfo = XCGLoggerTests_tvOS; }; - A9F507821BC7B8DE00275C6C /* PBXContainerItemProxy */ = { + A9438DE21B7A3F0400F1A54B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = F829C6B21A7A94F100A2CD59; - remoteInfo = "Alamofire OSX Tests"; + containerPortal = A9438DC71B7A3F0300F1A54B /* Project object */; + proxyType = 1; + remoteGlobalIDString = A9438DCE1B7A3F0400F1A54B; + remoteInfo = JustUsed; }; /* End PBXContainerItemProxy section */ @@ -122,7 +124,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - A9D894201BD00EED00314475 /* Alamofire.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -130,13 +131,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + A90E8A1E1EB0CB52009A860E /* SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftyJSON.swift; path = SwiftyJSON/Source/SwiftyJSON.swift; sourceTree = SOURCE_ROOT; }; + A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = XCGLogger.xcodeproj; path = XCGLogger/Sources/XCGLogger.xcodeproj; sourceTree = SOURCE_ROOT; }; + A90E8A4B1EB0DBCB009A860E /* CrossRefSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossRefSession.swift; sourceTree = ""; }; + A90E8A4D1EB0DBCB009A860E /* DiMePusher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiMePusher.swift; sourceTree = ""; }; + A90E8A4E1EB0DBCB009A860E /* DiMeSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiMeSession.swift; sourceTree = ""; }; A914066D1C0348EF00C3C2E0 /* SpotlightDocumentTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpotlightDocumentTracker.swift; sourceTree = ""; }; A91406721C036D5500C3C2E0 /* FirefoxHistoryFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirefoxHistoryFetcher.swift; sourceTree = ""; }; A92411E61C69FAC900DD6449 /* CalendarTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarTracker.swift; sourceTree = ""; }; A92411EE1C69FB3F00DD6449 /* CalendarEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarEvent.swift; sourceTree = ""; }; A92411F01C69FD8F00DD6449 /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = ""; }; - A92411F21C69FDCB00DD6449 /* SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyJSON.swift; sourceTree = ""; }; - A92574581BCF8FF9004866B4 /* XCGLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCGLogger.swift; sourceTree = ""; }; A925745A1BCF901D004866B4 /* JustUsedConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JustUsedConstants.swift; sourceTree = ""; }; A925745C1BCF9047004866B4 /* JustUsed-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JustUsed-Bridging-Header.h"; sourceTree = ""; }; A925745D1BCF906A004866B4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -172,7 +176,6 @@ A9BD83C11C6B516100D62A61 /* EKCalendar+getCompositeName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EKCalendar+getCompositeName.swift"; sourceTree = ""; }; A9D4A2D41B7B9B3400EC1D68 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; A9F3807A1C199E38009251AB /* GenericBrowserHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericBrowserHistory.swift; sourceTree = ""; }; - A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire.xcodeproj; path = Alamofire/Alamofire.xcodeproj; sourceTree = ""; }; A9F507861BC7BA4B00275C6C /* DiMePreferencesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiMePreferencesViewController.swift; sourceTree = ""; }; A9F507871BC7BA4B00275C6C /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; A9F5078A1BC7BF1300275C6C /* HistoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = ""; }; @@ -188,6 +191,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A90E8A3F1EB0CDA1009A860E /* XCGLogger.framework in Frameworks */, A9D4A2D51B7B9B3400EC1D68 /* libsqlite3.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -202,6 +206,31 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A90E8A251EB0CD41009A860E /* Products */ = { + isa = PBXGroup; + children = ( + A90E8A301EB0CD41009A860E /* XCGLogger.framework */, + A90E8A321EB0CD41009A860E /* XCGLogger.framework */, + A90E8A341EB0CD41009A860E /* XCGLogger.framework */, + A90E8A361EB0CD41009A860E /* XCGLogger.framework */, + A90E8A381EB0CD41009A860E /* XCGLoggerTests_iOS.xctest */, + A90E8A3A1EB0CD41009A860E /* XCGLoggerTests (OS X).xctest */, + A90E8A3C1EB0CD41009A860E /* XCGLoggerTests_watchOS.xctest */, + A90E8A3E1EB0CD41009A860E /* XCGLoggerTests_tvOS.xctest */, + ); + name = Products; + sourceTree = ""; + }; + A90E8A4A1EB0DBA4009A860E /* REST */ = { + isa = PBXGroup; + children = ( + A90E8A4B1EB0DBCB009A860E /* CrossRefSession.swift */, + A90E8A4D1EB0DBCB009A860E /* DiMePusher.swift */, + A90E8A4E1EB0DBCB009A860E /* DiMeSession.swift */, + ); + path = REST; + sourceTree = ""; + }; A925744C1BCF8EAA004866B4 /* Model */ = { isa = PBXGroup; children = ( @@ -224,10 +253,10 @@ A92574521BCF8EC3004866B4 /* Libraries */ = { isa = PBXGroup; children = ( + A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */, A92574691BCF9190004866B4 /* fmdb */, A9D4A2D41B7B9B3400EC1D68 /* libsqlite3.dylib */, - A92574581BCF8FF9004866B4 /* XCGLogger.swift */, - A92411F21C69FDCB00DD6449 /* SwiftyJSON.swift */, + A90E8A1E1EB0CB52009A860E /* SwiftyJSON.swift */, ); name = Libraries; path = JustUsed/Libraries; @@ -264,7 +293,6 @@ A9438DC61B7A3F0300F1A54B = { isa = PBXGroup; children = ( - A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */, A9438DD11B7A3F0400F1A54B /* JustUsed */, A9438DE41B7A3F0400F1A54B /* JustUsedTests */, A9438DD01B7A3F0400F1A54B /* Products */, @@ -287,6 +315,7 @@ A92574531BCF8F69004866B4 /* Utils */, A92574521BCF8EC3004866B4 /* Libraries */, A925744C1BCF8EAA004866B4 /* Model */, + A90E8A4A1EB0DBA4009A860E /* REST */, A9F5076C1BC7B6F000275C6C /* DiMe Data */, A9F507851BC7B9CE00275C6C /* UI */, A9438DD81B7A3F0400F1A54B /* Images.xcassets */, @@ -349,20 +378,6 @@ path = "JustUsed/DiMe Data"; sourceTree = SOURCE_ROOT; }; - A9F507731BC7B8DE00275C6C /* Products */ = { - isa = PBXGroup; - children = ( - A9F5077B1BC7B8DE00275C6C /* Alamofire.framework */, - A9F507811BC7B8DE00275C6C /* Alamofire iOS Tests.xctest */, - A9F5077D1BC7B8DE00275C6C /* Alamofire.framework */, - A9F507831BC7B8DE00275C6C /* Alamofire OSX Tests.xctest */, - A9F380831C199E39009251AB /* Alamofire.framework */, - A9F380851C199E39009251AB /* Alamofire tvOS Tests.xctest */, - A9F5077F1BC7B8DE00275C6C /* Alamofire.framework */, - ); - name = Products; - sourceTree = ""; - }; A9F507851BC7B9CE00275C6C /* UI */ = { isa = PBXGroup; children = ( @@ -389,7 +404,6 @@ buildRules = ( ); dependencies = ( - A9D894221BD00EED00314475 /* PBXTargetDependency */, ); name = JustUsed; productName = JustUsed; @@ -422,15 +436,17 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = HIIT; TargetAttributes = { A9438DCE1B7A3F0400F1A54B = { CreatedOnToolsVersion = 6.4; DevelopmentTeam = JYV3R5X7JQ; + LastSwiftMigration = 0830; }; A9438DE01B7A3F0400F1A54B = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 0830; TestTargetID = A9438DCE1B7A3F0400F1A54B; }; }; @@ -448,8 +464,8 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = A9F507731BC7B8DE00275C6C /* Products */; - ProjectRef = A9F507721BC7B8DE00275C6C /* Alamofire.xcodeproj */; + ProductGroup = A90E8A251EB0CD41009A860E /* Products */; + ProjectRef = A90E8A241EB0CD41009A860E /* XCGLogger.xcodeproj */; }, ); projectRoot = ""; @@ -461,53 +477,60 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - A9F380831C199E39009251AB /* Alamofire.framework */ = { + A90E8A301EB0CD41009A860E /* XCGLogger.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; - path = Alamofire.framework; - remoteRef = A9F380821C199E39009251AB /* PBXContainerItemProxy */; + path = XCGLogger.framework; + remoteRef = A90E8A2F1EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - A9F380851C199E39009251AB /* Alamofire tvOS Tests.xctest */ = { + A90E8A321EB0CD41009A860E /* XCGLogger.framework */ = { isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = "Alamofire tvOS Tests.xctest"; - remoteRef = A9F380841C199E39009251AB /* PBXContainerItemProxy */; + fileType = wrapper.framework; + path = XCGLogger.framework; + remoteRef = A90E8A311EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - A9F5077B1BC7B8DE00275C6C /* Alamofire.framework */ = { + A90E8A341EB0CD41009A860E /* XCGLogger.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; - path = Alamofire.framework; - remoteRef = A9F5077A1BC7B8DE00275C6C /* PBXContainerItemProxy */; + path = XCGLogger.framework; + remoteRef = A90E8A331EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - A9F5077D1BC7B8DE00275C6C /* Alamofire.framework */ = { + A90E8A361EB0CD41009A860E /* XCGLogger.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; - path = Alamofire.framework; - remoteRef = A9F5077C1BC7B8DE00275C6C /* PBXContainerItemProxy */; + path = XCGLogger.framework; + remoteRef = A90E8A351EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - A9F5077F1BC7B8DE00275C6C /* Alamofire.framework */ = { + A90E8A381EB0CD41009A860E /* XCGLoggerTests_iOS.xctest */ = { isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = Alamofire.framework; - remoteRef = A9F5077E1BC7B8DE00275C6C /* PBXContainerItemProxy */; + fileType = wrapper.cfbundle; + path = XCGLoggerTests_iOS.xctest; + remoteRef = A90E8A371EB0CD41009A860E /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A90E8A3A1EB0CD41009A860E /* XCGLoggerTests (OS X).xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "XCGLoggerTests (OS X).xctest"; + remoteRef = A90E8A391EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - A9F507811BC7B8DE00275C6C /* Alamofire iOS Tests.xctest */ = { + A90E8A3C1EB0CD41009A860E /* XCGLoggerTests_watchOS.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = "Alamofire iOS Tests.xctest"; - remoteRef = A9F507801BC7B8DE00275C6C /* PBXContainerItemProxy */; + path = XCGLoggerTests_watchOS.xctest; + remoteRef = A90E8A3B1EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - A9F507831BC7B8DE00275C6C /* Alamofire OSX Tests.xctest */ = { + A90E8A3E1EB0CD41009A860E /* XCGLoggerTests_tvOS.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = "Alamofire OSX Tests.xctest"; - remoteRef = A9F507821BC7B8DE00275C6C /* PBXContainerItemProxy */; + path = XCGLoggerTests_tvOS.xctest; + remoteRef = A90E8A3D1EB0CD41009A860E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ @@ -540,9 +563,9 @@ A9BD83BE1C6B50F900D62A61 /* String+Extensions.swift in Sources */, A92574631BCF906A004866B4 /* AppSingleton.swift in Sources */, A9F507921BC7BF5900275C6C /* DocumentInformationElement.swift in Sources */, - A92411F31C69FDCB00DD6449 /* SwiftyJSON.swift in Sources */, A9BD83BC1C6B50BA00D62A61 /* NSDate+Extensions.swift in Sources */, A92574761BCF9190004866B4 /* FMDatabaseAdditions.m in Sources */, + A90E8A4F1EB0DBCB009A860E /* CrossRefSession.swift in Sources */, A9F507881BC7BA4B00275C6C /* DiMePreferencesViewController.swift in Sources */, A9F507901BC7BF5900275C6C /* DesktopEvent.swift in Sources */, A92411EF1C69FB3F00DD6449 /* CalendarEvent.swift in Sources */, @@ -550,11 +573,11 @@ A92574651BCF906A004866B4 /* SafariHistoryFetcher.swift in Sources */, A92411F11C69FD8F00DD6449 /* Person.swift in Sources */, A92574781BCF9190004866B4 /* FMDatabaseQueue.m in Sources */, + A90E8A521EB0DBCB009A860E /* DiMeSession.swift in Sources */, A9F507891BC7BA4B00275C6C /* ViewController.swift in Sources */, A914066E1C0348EF00C3C2E0 /* SpotlightDocumentTracker.swift in Sources */, A9F3807B1C199E38009251AB /* GenericBrowserHistory.swift in Sources */, A92411E71C69FAC900DD6449 /* CalendarTracker.swift in Sources */, - A92574591BCF8FF9004866B4 /* XCGLogger.swift in Sources */, A91406731C036D5500C3C2E0 /* FirefoxHistoryFetcher.swift in Sources */, A92574641BCF906A004866B4 /* LocationSingleton.swift in Sources */, A9BD83BA1C6B508600D62A61 /* NSURL+getMime.swift in Sources */, @@ -569,7 +592,9 @@ A9F507911BC7BF5900275C6C /* DiMeData.swift in Sources */, A925745B1BCF901D004866B4 /* JustUsedConstants.swift in Sources */, A9647CDB1C03853D00485837 /* ChromeHistoryFetcher.swift in Sources */, + A90E8A1F1EB0CB52009A860E /* SwiftyJSON.swift in Sources */, A92C0ACD1BE401BC00F93915 /* RecentPlistTracker.swift in Sources */, + A90E8A511EB0DBCB009A860E /* DiMePusher.swift in Sources */, A92574751BCF9190004866B4 /* FMDatabase.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -590,11 +615,6 @@ target = A9438DCE1B7A3F0400F1A54B /* JustUsed */; targetProxy = A9438DE21B7A3F0400F1A54B /* PBXContainerItemProxy */; }; - A9D894221BD00EED00314475 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "Alamofire OSX"; - targetProxy = A9D894211BD00EED00314475 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -623,8 +643,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -668,8 +690,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -688,17 +712,19 @@ MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; A9438DEC1B7A3F0400F1A54B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Developer ID Application"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + DEVELOPMENT_TEAM = JYV3R5X7JQ; INFOPLIST_FILE = JustUsed/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -706,17 +732,19 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_OBJC_BRIDGING_HEADER = "JustUsed/Utils/JustUsed-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Debug; }; A9438DED1B7A3F0400F1A54B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Developer ID Application"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + DEVELOPMENT_TEAM = JYV3R5X7JQ; INFOPLIST_FILE = JustUsed/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -724,6 +752,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_OBJC_BRIDGING_HEADER = "JustUsed/Utils/JustUsed-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -744,6 +773,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "hiit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JustUsed.app/Contents/MacOS/JustUsed"; }; name = Debug; @@ -761,6 +791,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "hiit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JustUsed.app/Contents/MacOS/JustUsed"; }; name = Release; diff --git a/JustUsed/Base.lproj/Main.storyboard b/JustUsed/Base.lproj/Main.storyboard index ab3a071..6b4b423 100644 --- a/JustUsed/Base.lproj/Main.storyboard +++ b/JustUsed/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - - + + - + + @@ -647,24 +648,6 @@ - - - - - - - - - - - - - - - - - - @@ -754,7 +737,6 @@ - @@ -866,7 +848,6 @@ - @@ -888,7 +869,7 @@ - + @@ -903,7 +884,7 @@ - + @@ -911,7 +892,7 @@ - + @@ -933,13 +914,13 @@ - + - + @@ -955,7 +936,7 @@ - + @@ -989,8 +970,6 @@ - - - + - + @@ -1173,7 +1152,6 @@ - @@ -1204,7 +1182,7 @@ - + @@ -1234,23 +1212,18 @@ - - - + - - - - + - + @@ -1266,8 +1239,6 @@ - - diff --git a/JustUsed/DiMe Data/CalendarEvent.swift b/JustUsed/DiMe Data/CalendarEvent.swift index c474d14..479f59b 100644 --- a/JustUsed/DiMe Data/CalendarEvent.swift +++ b/JustUsed/DiMe Data/CalendarEvent.swift @@ -29,12 +29,12 @@ import Contacts /// Represents an calendar event, as understood by dime class CalendarEvent: Event { - private(set) var participants: [Person] = [Person]() - private(set) var name: String - private(set) var calendar: String - private(set) var location: Location? - private(set) var locString: String? - private(set) var notes: String? + fileprivate(set) var participants: [Person] = [Person]() + fileprivate(set) var name: String + fileprivate(set) var calendar: String + fileprivate(set) var location: Location? + fileprivate(set) var locString: String? + fileprivate(set) var notes: String? let id: String override var hash: Int { get { @@ -62,7 +62,7 @@ class CalendarEvent: Event { self.calendar = event.calendar.compositeName self.notes = event.notes if #available(OSX 10.11, *) { - if let structLoc = event.structuredLocation, clloc = structLoc.geoLocation { + if let structLoc = event.structuredLocation, let clloc = structLoc.geoLocation { location = Location(fromCLLocation: clloc) } } @@ -72,14 +72,14 @@ class CalendarEvent: Event { if event.hasAttendees, let attendees = event.attendees { for attendee in attendees { - if let name = attendee.name, part = Person(fromString: name) { + if let name = attendee.name, let part = Person(fromString: name) { // if possible, fetch more data for this person if #available(OSX 10.11, *) { - if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized { + if CNContactStore.authorizationStatus(for: .contacts) == .authorized { do { let store: CNContactStore = AppSingleton.contactStore as! CNContactStore let predicate = attendee.contactPredicate - let contacts = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: [CNContactEmailAddressesKey,CNContactMiddleNameKey, CNContactGivenNameKey, CNContactFamilyNameKey]) + let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: [CNContactEmailAddressesKey as CNKeyDescriptor,CNContactMiddleNameKey as CNKeyDescriptor, CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor]) // put in data from the first returned contact if contacts.count >= 1 { part.firstName = contacts[0].givenName @@ -88,7 +88,7 @@ class CalendarEvent: Event { if midName != "" { part.middleNames = [midName] } - part.email = (contacts[0].emailAddresses[0].value as! String) + part.email = (contacts[0].emailAddresses[0].value as String) } } catch { AppSingleton.log.error("Error while fetching an individual contact for \(self.name):\n\(error)") @@ -117,7 +117,7 @@ class CalendarEvent: Event { if participants.count > 0 { self.participants = [Person]() for participant in participants { - self.participants.append(Person(fromJson: participant)) + self.participants.append(Person(fromDime: participant)) } } } @@ -128,22 +128,22 @@ class CalendarEvent: Event { super.init() - let start = NSDate(timeIntervalSince1970: NSTimeInterval(json["start"].intValue / 1000)) - let end = NSDate(timeIntervalSince1970: NSTimeInterval(json["end"].intValue / 1000)) + let start = Date(timeIntervalSince1970: TimeInterval(json["start"].intValue / 1000)) + let end = Date(timeIntervalSince1970: TimeInterval(json["end"].intValue / 1000)) setStart(start) setEnd(end) } /// getDict for calendar is overridden to update return value with internal state. - override func getDict() -> [String : AnyObject] { + override func getDict() -> [String : Any] { var retDict = theDictionary // fetch current values // update values retDict["calendar"] = calendar retDict["name"] = name if participants.count > 0 { - var partArray = [[String: AnyObject]]() + var partArray = [[String: Any]]() for participant in participants { partArray.append(participant.getDict()) } @@ -170,7 +170,7 @@ class CalendarEvent: Event { } /// Compares two calendar events, which are equal only if all their fields are equal - override func isEqual(object: AnyObject?) -> Bool { + override func isEqual(_ object: Any?) -> Bool { if let otherEvent = object as? CalendarEvent { if name != otherEvent.name || calendar != otherEvent.calendar || notes != otherEvent.notes { return false diff --git a/JustUsed/DiMe Data/DesktopEvent.swift b/JustUsed/DiMe Data/DesktopEvent.swift index 0cc4ff7..aefb70e 100644 --- a/JustUsed/DiMe Data/DesktopEvent.swift +++ b/JustUsed/DiMe Data/DesktopEvent.swift @@ -27,28 +27,28 @@ import Foundation /// Used to send the first-time file opening event class DesktopEvent: Event { - init(infoElem: DocumentInformationElement, ofType type: TrackingType, withDate date: NSDate, andLocation location: Location?) { + init(infoElem: DocumentInformationElement, ofType type: TrackingType, withDate date: Date, andLocation location: Location?) { super.init() - theDictionary["targettedResource"] = infoElem.getDict() + theDictionary["targettedResource"] = infoElem.getDict() as AnyObject switch type { - case .Spotlight: - theDictionary["actor"] = "JustUsed_Spotlight" - case let .Browser(browser): - theDictionary["actor"] = "JustUsed_\(browser)" + case .spotlight: + theDictionary["actor"] = "JustUsed_Spotlight" as AnyObject + case let .browser(browser): + theDictionary["actor"] = "JustUsed_\(browser)" as AnyObject } - theDictionary["start"] = JustUsedConstants.diMeDateFormatter.stringFromDate(date) + theDictionary["start"] = JustUsedConstants.diMeDateFormatter.string(from: date) if let loc = location { - theDictionary["location"] = loc.getDict() + theDictionary["location"] = loc.getDict() as AnyObject } - theDictionary["@type"] = "DesktopEvent" - theDictionary["type"] = "http://www.hiit.fi/ontologies/dime/#DesktopEvent" + theDictionary["@type"] = "DesktopEvent" as AnyObject + theDictionary["type"] = "http://www.hiit.fi/ontologies/dime/#DesktopEvent" as AnyObject } } enum TrackingType { - case Browser(BrowserType) - case Spotlight + case browser(BrowserType) + case spotlight } diff --git a/JustUsed/DiMe Data/DiMeData.swift b/JustUsed/DiMe Data/DiMeData.swift index 3613024..41b8def 100644 --- a/JustUsed/DiMe Data/DiMeData.swift +++ b/JustUsed/DiMe Data/DiMeData.swift @@ -24,29 +24,83 @@ import Foundation -/// Marks classes and structs that can return themselves in a dictionary -/// where all keys are strings and values can be used in a JSON -protocol Dictionariable { - - /// Returns itself in a JSON-Serializable dict - func getDict() -> [String: AnyObject] -} +import Foundation /// This class is made for subclassing. It represents data common to all dime objects (see /dime-server/src/main/java/fi/hiit/dime/data/DiMeData.java in the dime project). class DiMeBase: NSObject, Dictionariable { /// Main dictionary storing all data /// - /// **Important**: all sublasses must set these two keys, in order to be decoded by dime: + /// **Important**: endpoint classes (Event, InformationElement as subclasses) must set these two keys, in order to be decoded by dime: /// - @type /// - type - var theDictionary = [String: AnyObject]() + var theDictionary = [String : Any]() override init() { super.init() } - func getDict() -> [String : AnyObject] { + /// Simply returns the dictionary. Can be overridden by subclasses that want + /// to edit the dictionary before sending it. + func getDict() -> [String : Any] { return theDictionary } -} \ No newline at end of file +} + +/// Represents a simple range with a start and end value +struct DiMeRange: Dictionariable, Equatable { + var min: NSNumber + var max: NSNumber + + /// Returns min and max in a dict + func getDict() -> [String : Any] { + var retDict = [String : Any]() + retDict["min"] = min + retDict["max"] = max + return retDict + } +} + +func == (lhs: DiMeRange, rhs: DiMeRange) -> Bool { + return lhs.max == rhs.max && + lhs.min == rhs.min +} + +/// Marks classes and structs that can return themselves in a dictionary +/// where all keys are strings and values can be used in a JSON +protocol Dictionariable { + + /// Returns itself in a dict + func getDict() -> [String : Any] +} + +/// Allows collections of dictionariable types to return themselves as array of dicts +extension Sequence where Iterator.Element: Dictionariable { + + /// Returns itself as an array of dicts + func asDictArray() -> [[String : Any]] { + return self.reduce([[String : Any]](), {$0 + [$1.getDict()]}) + } +} + +extension NSSize: Dictionariable { + /// Returns width and height in a dictionary with their values as + /// numbers (both as JSONableItem enums). + func getDict() -> [String : Any] { + var retDict = [String : Any]() + retDict["height"] = self.height + retDict["width"] = self.width + return retDict + } +} + +extension NSPoint: Dictionariable { + /// Returns x and y in a dictionary with their values as + /// numbers (both as JSONableItem enums). + func getDict() -> [String : Any] { + var retDict = [String : Any]() + retDict["x"] = self.x + retDict["y"] = self.y + return retDict + } +} diff --git a/JustUsed/DiMe Data/DocumentInformationElement.swift b/JustUsed/DiMe Data/DocumentInformationElement.swift index d1533a7..45fb57e 100644 --- a/JustUsed/DiMe Data/DocumentInformationElement.swift +++ b/JustUsed/DiMe Data/DocumentInformationElement.swift @@ -38,7 +38,7 @@ class DocumentInformationElement: DiMeBase { } set(title) { if let t = title { - theDictionary["title"] = t + theDictionary["title"] = t as AnyObject } } } @@ -49,23 +49,28 @@ class DocumentInformationElement: DiMeBase { init(fromSafariHist histItem: BrowserHistItem) { super.init() - theDictionary["appId"] = "JustUsed_\(histItem.url.sha1())" - theDictionary["mimeType"] = "text/html" - theDictionary["uri"] = histItem.url + theDictionary["appId"] = "JustUsed_\(histItem.url.sha1())" as AnyObject + theDictionary["mimeType"] = "text/html" as AnyObject + theDictionary["uri"] = histItem.url as AnyObject if let title = histItem.title { - theDictionary["title"] = title + theDictionary["title"] = title as AnyObject } // attempt to fetch plain text from url - if let url = NSURL(string: histItem.url), urlData = NSData(contentsOfURL: url), atString = NSAttributedString(HTML: urlData, documentAttributes: nil) { - theDictionary["plainTextContent"] = atString.string - theDictionary["contentHash"] = atString.string.sha1() + if let url = URL(string: histItem.url), let urlData = try? Data(contentsOf: url) { + do { + let atString = try NSAttributedString(data: urlData, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil) + theDictionary["plainTextContent"] = atString.string + theDictionary["contentHash"] = atString.string.sha1() + } catch { + AppSingleton.log.warning("Failed to convert url contents to string: \(error)") + } } // set dime-required fields - theDictionary["@type"] = "Document" - theDictionary["type"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#HtmlDocument" - theDictionary["isStoredAs"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#RemoteDataObject" + theDictionary["@type"] = "Document" as AnyObject + theDictionary["type"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#HtmlDocument" as AnyObject + theDictionary["isStoredAs"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#RemoteDataObject" as AnyObject } /// Creates a document from a Spotlight history element @@ -74,24 +79,24 @@ class DocumentInformationElement: DiMeBase { var id: String // check if the histItem contains plain text, if so use for hash and set id - let mt: NSString = histItem.mime - if mt.substringToIndex(4) == "text" { + let mt: NSString = histItem.mime as NSString + if mt.substring(to: 4) == "text" { do { - let plainText: NSString = try String(contentsOfFile: histItem.path) + let plainText: NSString = try String(contentsOfFile: histItem.path) as NSString let plainTextString: String = plainText as String id = plainTextString.sha1() - theDictionary["plainTextContent"] = plainTextString + theDictionary["plainTextContent"] = plainTextString as AnyObject } catch (let exception) { id = histItem.path.sha1() AppSingleton.log.error("Error while fetching plain text from \(histItem.path): \(exception)") } } else if mt == "application/pdf" { // attempt to fetch plain text from pdf - let docUrl = NSURL(fileURLWithPath: histItem.path) - if let pdfDoc = PDFDocument(URL: docUrl), plainString = pdfDoc.getText() { + let docUrl = URL(fileURLWithPath: histItem.path) + if let pdfDoc = PDFDocument(url: docUrl), let plainString = pdfDoc.getText() { id = plainString.sha1() - theDictionary["contentHash"] = plainString.sha1() - theDictionary["plainTextContent"] = plainString + theDictionary["contentHash"] = plainString.sha1() as AnyObject + theDictionary["plainTextContent"] = plainString as AnyObject } else { id = histItem.path.sha1() } @@ -99,55 +104,55 @@ class DocumentInformationElement: DiMeBase { id = histItem.path.sha1() } - theDictionary["appId"] = "JustUsed_\(id)" + theDictionary["appId"] = "JustUsed_\(id)" as AnyObject // set everything else apart from plain text and id - theDictionary["mimeType"] = histItem.mime - theDictionary["uri"] = "file://" + histItem.path - theDictionary["title"] = NSURL(fileURLWithPath: histItem.path).lastPathComponent! + theDictionary["mimeType"] = histItem.mime as AnyObject + theDictionary["uri"] = "file://" + histItem.path as AnyObject + theDictionary["title"] = URL(fileURLWithPath: histItem.path).lastPathComponent as AnyObject // set dime-required fields - theDictionary["@type"] = "Document" - theDictionary["type"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#Document" - theDictionary["isStoredAs"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#LocalFileDataObject" + theDictionary["@type"] = "Document" as AnyObject + theDictionary["type"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#Document" as AnyObject + theDictionary["isStoredAs"] = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/#LocalFileDataObject" as AnyObject } /// Converts to scientific document by updating dictionary using crossref metadata. Also accepts keywords (from pdf's metadata). func convertToSciDoc(fromCrossRef json: JSON, keywords: [String]?) { // dime-required - theDictionary["@type"] = "ScientificDocument" - theDictionary["type"] = "http://www.hiit.fi/ontologies/dime/#ScientificDocument" + theDictionary["@type"] = "ScientificDocument" as AnyObject + theDictionary["type"] = "http://www.hiit.fi/ontologies/dime/#ScientificDocument" as AnyObject - if let status = json["status"].string where status == "ok" { + if let status = json["status"].string, status == "ok" { if let title = json["message"]["title"][0].string { - theDictionary["title"] = title + theDictionary["title"] = title as AnyObject } if let keywords = keywords { - theDictionary["keywords"] = keywords + theDictionary["keywords"] = keywords as AnyObject } if let subj = json["message"]["container-title"][0].string { - theDictionary["booktitle"] = subj + theDictionary["booktitle"] = subj as AnyObject } if let auths = json["message"]["author"].array { theDictionary["authors"] = auths.flatMap({Person(fromCrossRef: $0)?.getDict()}) } if let doi = json["message"]["DOI"].string { - theDictionary["doi"] = doi + theDictionary["doi"] = doi as AnyObject } if let year = json["message"]["issued"]["date-parts"][0][0].int { - theDictionary["year"] = year + theDictionary["year"] = year as AnyObject } - if let ps = json["message"]["page"].string, words = ps.words() { - theDictionary["firstPage"] = Int(words[0]) + if let ps = json["message"]["page"].string, let words = ps.words() { + theDictionary["firstPage"] = Int(words[0]) as AnyObject if words.count > 1 { - theDictionary["lastPage"] = Int(words[1]) + theDictionary["lastPage"] = Int(words[1]) as AnyObject } } if let publisher = json["message"]["publisher"].string { - theDictionary["publisher"] = publisher + theDictionary["publisher"] = publisher as AnyObject } if let volume = json["message"]["volume"].string { - theDictionary["volume"] = Int(volume) + theDictionary["volume"] = Int(volume) as AnyObject } } } diff --git a/JustUsed/DiMe Data/Event.swift b/JustUsed/DiMe Data/Event.swift index de88d00..be5c520 100644 --- a/JustUsed/DiMe Data/Event.swift +++ b/JustUsed/DiMe Data/Event.swift @@ -33,24 +33,24 @@ class Event: DiMeBase { super.init() // Make creation date - theDictionary["start"] = JustUsedConstants.diMeDateFormatter.stringFromDate(NSDate()) - if let hostname = NSHost.currentHost().name { - theDictionary["origin"] = hostname + theDictionary["start"] = JustUsedConstants.diMeDateFormatter.string(from: Date()) + if let hostname = Host.current().name { + theDictionary["origin"] = hostname as AnyObject } // set dime-required fields (can be overwritten by subclasses) - theDictionary["actor"] = "JustUsed" - theDictionary["@type"] = "Event" - theDictionary["type"] = "http://www.hiit.fi/ontologies/dime/#Event" + theDictionary["actor"] = "JustUsed" as AnyObject + theDictionary["@type"] = "Event" as AnyObject + theDictionary["type"] = "http://www.hiit.fi/ontologies/dime/#Event" as AnyObject } /// Set an end date for this item (otherwise, won't be submitted) - func setEnd(endDate: NSDate) { - theDictionary["end"] = JustUsedConstants.diMeDateFormatter.stringFromDate(endDate) + func setEnd(_ endDate: Date) { + theDictionary["end"] = JustUsedConstants.diMeDateFormatter.string(from: endDate) } /// Set a start date for this item (updates old value) - func setStart(endDate: NSDate) { - theDictionary["start"] = JustUsedConstants.diMeDateFormatter.stringFromDate(endDate) + func setStart(_ endDate: Date) { + theDictionary["start"] = JustUsedConstants.diMeDateFormatter.string(from: endDate) } -} \ No newline at end of file +} diff --git a/JustUsed/DiMe Data/HistoryManager.swift b/JustUsed/DiMe Data/HistoryManager.swift index d10ca62..24c9fe3 100644 --- a/JustUsed/DiMe Data/HistoryManager.swift +++ b/JustUsed/DiMe Data/HistoryManager.swift @@ -28,11 +28,13 @@ // See https://github.com/HIIT/PeyeDF/wiki/Data-Format for more information import Foundation -import Alamofire import Quartz class HistoryManager: NSObject { + /// Set to true to prevent automatic connection checks + static var forceDisconnect = false + /// Returns a shared instance of this class. This is the designed way of accessing the history manager. static let sharedManager = HistoryManager() @@ -40,116 +42,25 @@ class HistoryManager: NSObject { static let kConnectionCheckTime = 5.0 /// Is true if there is a connection to DiMe, and can be used - private var dimeAvailable: Bool = false + fileprivate var dimeAvailable: Bool = false // MARK: - Initialization override init() { super.init() - let connectTimer = NSTimer(timeInterval: HistoryManager.kConnectionCheckTime, target: self, selector: #selector(connectionTimerCheck(_:)), userInfo: nil, repeats: true) - NSRunLoop.currentRunLoop().addTimer(connectTimer, forMode: NSRunLoopCommonModes) + let connectTimer = Timer(timeInterval: HistoryManager.kConnectionCheckTime, target: self, selector: #selector(connectionTimerCheck(_:)), userInfo: nil, repeats: true) + RunLoop.current.add(connectTimer, forMode: RunLoopMode.commonModes) } /// Callback for connection timer - func connectionTimerCheck(aTimer: NSTimer) { - dimeConnect() - } - - // MARK: - External functions - - /// Returns true if dime is available - func isDiMeAvailable() -> Bool { - return dimeAvailable - } - - /// Attempts to connect to dime. Sends a notification if we succeeded / failed - func dimeConnect() { - let server_url: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerURL) as! String - let user: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerUserName) as! String - let password: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerPassword) as! String - - let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)! - let base64Credentials = credentialData.base64EncodedStringWithOptions([]) - - let headers = ["Authorization": "Basic \(base64Credentials)"] - - let dictionaryObject = ["test": "test"] - - Alamofire.request(Alamofire.Method.POST, server_url + "/ping", parameters: dictionaryObject, encoding: Alamofire.ParameterEncoding.JSON, headers: headers).responseJSON { - response in - if response.result.isFailure { - // connection failed - self.dimeConnectState(false) - } else { - // succesfully connected - self.dimeConnectState(true) - } + func connectionTimerCheck(_ aTimer: Timer) { + if !HistoryManager.forceDisconnect { + DiMeSession.dimeConnect() } } - /// Disconnects from dime - func dimeDisconnect() { - self.dimeConnectState(false) - } - - // MARK: - Internal functions - - /// Connection to dime successful / failed - private func dimeConnectState(success: Bool) { - if !success { - self.dimeAvailable = false - NSNotificationCenter.defaultCenter().postNotificationName(JustUsedConstants.diMeConnectionNotification, object: self, userInfo: nil) - } else { - // succesfully connected - self.dimeAvailable = true - NSNotificationCenter.defaultCenter().postNotificationName(JustUsedConstants.diMeConnectionNotification, object: self, userInfo: nil) - } - } - - /// Send the given dictionary to DiMe (assumed to be in correct form due to the use of public callers of this method) - /// If given, calls the success block if request succeeded. - private func sendToDiMe(dimeData: DiMeBase, successBlock: (Void -> Void)? = nil) { - - if dimeAvailable { - - let server_url: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerURL) as! String - let user: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerUserName) as! String - let password: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerPassword) as! String - - let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)! - let base64Credentials = credentialData.base64EncodedStringWithOptions([]) - - let headers = ["Authorization": "Basic \(base64Credentials)"] - - let options = NSJSONWritingOptions.PrettyPrinted + // MARK: - External functions - do { - try NSJSONSerialization.dataWithJSONObject(dimeData.getDict(), options: options) - } catch { - AppSingleton.log.error("Error while deserializing json! This should never happen. \(error)") - return - } - - Alamofire.request(Alamofire.Method.POST, server_url + "/data/event", parameters: dimeData.getDict(), encoding: Alamofire.ParameterEncoding.JSON, headers: headers).responseJSON { - response in - if response.result.isFailure { - self.dimeConnectState(false) - AppSingleton.log.error("Failure when submitting data to dime:\n\(response.result.error!)") - } else { - let jres = JSON(response.result.value!) - // check if there is an "error" in the response. If so, log it, otherwise report success - if jres["error"] != nil { - AppSingleton.log.error("DiMe reported error:\n\(jres["error"].stringValue)") - if let mes = jres["message"].string { - AppSingleton.log.error("DiMe's error message:\n\(mes)") - } - } else { - successBlock?() - } - } - } - } - } } // MARK: - Protocol implementations @@ -157,26 +68,26 @@ class HistoryManager: NSObject { /// Protocol implementations for browser and document history updates extension HistoryManager: RecentDocumentUpdateDelegate, BrowserHistoryUpdateDelegate { - func newHistoryItems(newURLs: [BrowserHistItem]) { + func newHistoryItems(_ newURLs: [BrowserHistItem]) { for newURL in newURLs { - let sendingToBrowser = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefSendSafariHistory) as! Bool + let sendingToBrowser = UserDefaults.standard.value(forKey: JustUsedConstants.prefSendSafariHistory) as! Bool if !newURL.excludedFromDiMe && sendingToBrowser { let infoElem = DocumentInformationElement(fromSafariHist: newURL) - let event = DesktopEvent(infoElem: infoElem, ofType: TrackingType.Browser(newURL.browser), withDate: newURL.date, andLocation: newURL.location) - sendToDiMe(event) + let event = DesktopEvent(infoElem: infoElem, ofType: TrackingType.browser(newURL.browser), withDate: newURL.date, andLocation: newURL.location) + DiMePusher.sendToDiMe(event) } } } - func newRecentDocument(newItem: RecentDocItem) { + func newRecentDocument(_ newItem: RecentDocItem) { // do all fetching on the utility queue (especially since pdfDoc.getMetadata() blocks) - dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) { + DispatchQueue.global(qos: DispatchQoS.QoSClass.utility).async { let infoElem = DocumentInformationElement(fromRecentDoc: newItem) if infoElem.isPdf { - let docUrl = NSURL(fileURLWithPath: newItem.path) - if let pdfDoc = PDFDocument(URL: docUrl) { + let docUrl = URL(fileURLWithPath: newItem.path) + if let pdfDoc = PDFDocument(url: docUrl) { // try to get metadata from crossref, otherwise get title from pdf's metadata, and as a last resort guess it - if let json = pdfDoc.getMetadata() { + if let json = pdfDoc.autoCrossref() { infoElem.convertToSciDoc(fromCrossRef: json, keywords: pdfDoc.getKeywordsAsArray()) } else if let tit = pdfDoc.getTitle() { infoElem.title = tit @@ -185,49 +96,9 @@ extension HistoryManager: RecentDocumentUpdateDelegate, BrowserHistoryUpdateDele } } } - let event = DesktopEvent(infoElem: infoElem, ofType: TrackingType.Spotlight, withDate: newItem.lastAccessDate, andLocation: newItem.location) - self.sendToDiMe(event) + let event = DesktopEvent(infoElem: infoElem, ofType: TrackingType.spotlight, withDate: newItem.lastAccessDate, andLocation: newItem.location) + DiMePusher.sendToDiMe(event) } } } - -/// Protocol implementations for calendar updating -extension HistoryManager: CalendarHistoryDelegate { - - func fetchCalendarEvents(block: [CalendarEvent] -> Void) { - - if dimeAvailable { - - let server_url: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerURL) as! String - let user: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerUserName) as! String - let password: String = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefDiMeServerPassword) as! String - - let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)! - let base64Credentials = credentialData.base64EncodedStringWithOptions([]) - - let headers = ["Authorization": "Basic \(base64Credentials)"] - - Alamofire.request(.GET, server_url + "/data/events?type=http://www.hiit.fi/ontologies/dime/%23CalendarEvent", headers: headers).responseJSON { - response in - if response.result.isFailure { - AppSingleton.log.error("Failure when retrieving calendar events:\n\(response.result.error!)") - } else { - let eventsPack = JSON(response.result.value!) - var retVal = [CalendarEvent]() - if let events = eventsPack.array { - for ev in events { - retVal.append(CalendarEvent(fromJSON: ev)) - } - } - block(retVal) - } - } - - } - } - - func sendCalendarEvent(newEvent: CalendarEvent, successBlock: Void -> Void) { - sendToDiMe(newEvent, successBlock: successBlock) - } -} \ No newline at end of file diff --git a/JustUsed/DiMe Data/Location.swift b/JustUsed/DiMe Data/Location.swift index bf37623..15f6988 100644 --- a/JustUsed/DiMe Data/Location.swift +++ b/JustUsed/DiMe Data/Location.swift @@ -80,8 +80,8 @@ struct Location: Dictionariable, Equatable, Hashable { } /// Returns itself in a (json-able) dict - func getDict() -> [String: AnyObject] { - var retDict = [String: AnyObject]() + func getDict() -> [String: Any] { + var retDict = [String: Any]() retDict["latitude"] = latitude retDict["longitude"] = longitude @@ -104,9 +104,9 @@ struct Location: Dictionariable, Equatable, Hashable { } func ==(rhs: Location, lhs: Location) -> Bool { - if let ralt = rhs.altitude, lalt = lhs.altitude { + if let ralt = rhs.altitude, let lalt = lhs.altitude { return ralt == lalt && rhs.latitude == lhs.latitude && rhs.longitude == lhs.longitude } else { return rhs.latitude == lhs.latitude && rhs.longitude == lhs.longitude } -} \ No newline at end of file +} diff --git a/JustUsed/DiMe Data/Person.swift b/JustUsed/DiMe Data/Person.swift index f1316f0..3a9b3f8 100644 --- a/JustUsed/DiMe Data/Person.swift +++ b/JustUsed/DiMe Data/Person.swift @@ -43,20 +43,7 @@ class Person: DiMeBase { } outVal += lastName return outVal - } } - - /// Person's hash is based on the hash of all its fields, xorred together - override var hash: Int { get { - var outH = firstName.hash - outH ^= lastName.hash - for mn in middleNames { - outH ^= mn.hash - } - if let em = email { - outH ^= em.hash - } - return outH - } } + } } /// Generates a person from a string. If there is a comma in the string, it is assumed that the first name after the comma, otherwise first name is the first non-whitespace separated string, and last name is the last. Middle names are assumed to all come after the first name if there was a comma, between first and last if there is no comma. /// **Fails (returns nil) if the string could not be parsed.** @@ -74,7 +61,7 @@ class Person: DiMeBase { // check if there are middle names in the following part if spl[1].containsChar(" ") { var resplitted = spl[1].split(" ") - self.firstName = resplitted!.removeAtIndex(0) + self.firstName = resplitted!.remove(at: 0) if resplitted!.count > 0 { for remName in resplitted! { middleNames.append(remName) @@ -111,21 +98,20 @@ class Person: DiMeBase { /// Crossref has an array of dicts with "given", "family" keys. /// "family" values can contain middle names separated by " " init?(fromCrossRef json: JSON) { - email = nil // crossref doesn't support emails super.init() - guard let fnamesS = json["given"].string, lname = json["family"].string, + guard let fnamesS = json["given"].string, let lname = json["family"].string, var fnames = fnamesS.split(" ") - where fnames.count >= 1 else { + , fnames.count >= 1 else { AppSingleton.log.warning("Couldn't parse author with dictionary: \(json)") return nil } - self.firstName = fnames.removeAtIndex(0) + self.firstName = fnames.remove(at: 0) self.lastName = lname self.middleNames = fnames } /// Creates a person from dime's json - init(fromJson json: JSON) { + init(fromDime json: JSON) { self.firstName = json["firstName"].stringValue self.lastName = json["lastName"].stringValue if let midnames = json["middleNames"].array { @@ -138,41 +124,16 @@ class Person: DiMeBase { } } - override func getDict() -> [String : AnyObject] { - var retDict = theDictionary - retDict["firstName"] = firstName - retDict["lastName"] = lastName + override func getDict() -> [String : Any] { + theDictionary["firstName"] = firstName + theDictionary["lastName"] = lastName if middleNames.count > 0 { - retDict["middleNames"] = middleNames + theDictionary["middleNames"] = middleNames } if let em = self.email { - retDict["emailAccount"] = em + theDictionary["emailAccount"] = em } - return retDict + return theDictionary } - - override func isEqual(object: AnyObject?) -> Bool { - if let otherPerson = object as? Person { - if self.firstName != otherPerson.firstName { - return false - } - if self.lastName != otherPerson.lastName { - return false - } - if self.middleNames != otherPerson.middleNames { - return false - } - if let em1 = self.email, em2 = otherPerson.email { - if em1 != em2 { - return false - } - } - - return true - } else { - return false - } - } - -} \ No newline at end of file +} diff --git a/JustUsed/Extensions/CLGeoCoder+getDescription.swift b/JustUsed/Extensions/CLGeoCoder+getDescription.swift index f5cf9e8..6d5f38f 100644 --- a/JustUsed/Extensions/CLGeoCoder+getDescription.swift +++ b/JustUsed/Extensions/CLGeoCoder+getDescription.swift @@ -28,13 +28,13 @@ extension CLGeocoder { /// Asynchronously creates a Location object by using a CLLocation and /// reversing its location. Calls the given block with a Location that includes description. - func getDescription(fromLoc inLoc: CLLocation, block: (describedLocation: Location) -> Void) { + func getDescription(fromLoc inLoc: CLLocation, block: @escaping (_ describedLocation: Location) -> Void) { self.reverseGeocodeLocation(inLoc) { placemarkA, error in var outLoc = Location(fromCLLocation: inLoc) if let error = error { - outLoc.descriptionLine = "** Error reversing: \(error.description)" + outLoc.descriptionLine = "** Error reversing: \(error)" } else { let placemark = placemarkA![0] var builtString = "" @@ -54,9 +54,9 @@ extension CLGeocoder { outLoc.descriptionLine = builtString } - block(describedLocation: outLoc) + block(outLoc) } } -} \ No newline at end of file +} diff --git a/JustUsed/Extensions/NSDate+Extensions.swift b/JustUsed/Extensions/NSDate+Extensions.swift index 2e92614..4407325 100644 --- a/JustUsed/Extensions/NSDate+Extensions.swift +++ b/JustUsed/Extensions/NSDate+Extensions.swift @@ -22,7 +22,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -extension NSDate { +extension Date { /// Number of ms since 1/1/1970. var unixTime_ms: Int { get { @@ -31,7 +31,7 @@ extension NSDate { } /// Creates a date from a unix time in microsec - convenience init(fromUnixTime_μs μs: Int) { + init(fromUnixTime_μs μs: Int) { self.init(timeIntervalSince1970: Double(μs) / 1000000) } @@ -42,7 +42,7 @@ extension NSDate { } /// Creates a date from a ldap timestamp. - convenience init(fromLdapTime lt: Int) { + init(fromLdapTime lt: Int) { let unixtime_s = Double(lt)/1000000-11644473600 self.init(timeIntervalSince1970: unixtime_s) } @@ -56,19 +56,19 @@ extension NSDate { /// Returns the current time in a short format, e.g. 16:30.45 /// Use this to pass dates to DiMe static func shortTime() -> String { - let currentDate = NSDate() - let dsf = NSDateFormatter() + let currentDate = Date() + let dsf = DateFormatter() dsf.dateFormat = "HH:mm.ss" - return dsf.stringFromDate(currentDate) + return dsf.string(from: currentDate) } /// Returns an NSDate that representing this date plus the given offset. /// e.g. NSDate().yearOffset(2) represents two years from now. - func yearOffset(year: Int) -> NSDate { - let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)! - let addComponents = NSDateComponents() + func yearOffset(_ year: Int) -> Date { + let calendar = Calendar(identifier: Calendar.Identifier.gregorian) + var addComponents = DateComponents() addComponents.year = year - return calendar.dateByAddingComponents(addComponents, toDate: self, options: .MatchStrictly)! + return (calendar as NSCalendar).date(byAdding: addComponents, to: self, options: .matchStrictly)! } } diff --git a/JustUsed/Extensions/NSURL+getMime.swift b/JustUsed/Extensions/NSURL+getMime.swift index 1e19aa7..34b4f9c 100644 --- a/JustUsed/Extensions/NSURL+getMime.swift +++ b/JustUsed/Extensions/NSURL+getMime.swift @@ -24,23 +24,23 @@ import Foundation -extension NSURL { +extension URL { /// Get mime type func getMime() -> String? { var mime: String? - let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, self.pathExtension!, nil) + let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, self.pathExtension as CFString, nil) let MIMEType = UTTypeCopyPreferredTagWithClass(UTI!.takeRetainedValue(), kUTTagClassMIMEType) var isDir = ObjCBool(false) if let mimet = MIMEType { mime = mimet.takeRetainedValue() as String - } else if NSFileManager.defaultManager().fileExistsAtPath(self.path!, isDirectory: &isDir) { - if isDir { + } else if FileManager.default.fileExists(atPath: self.path, isDirectory: &isDir) { + if isDir.boolValue { mime = "application/x-directory" } else { // if the file exists but it's not a directory and has no known mime type var foundEncoding: UInt = 0 - if let _ = try? NSString(contentsOfURL: self, usedEncoding: &foundEncoding) { + if let _ = try? NSString(contentsOf: self, usedEncoding: &foundEncoding) { mime = "text/plain" } else { mime = "application/octet-stream" @@ -49,4 +49,4 @@ extension NSURL { } return mime } -} \ No newline at end of file +} diff --git a/JustUsed/Extensions/PDFDocument+Extensions.swift b/JustUsed/Extensions/PDFDocument+Extensions.swift index 6d84b25..5f09316 100644 --- a/JustUsed/Extensions/PDFDocument+Extensions.swift +++ b/JustUsed/Extensions/PDFDocument+Extensions.swift @@ -24,35 +24,9 @@ import Foundation import Quartz -import Alamofire extension PDFDocument { - - /// Returns the string corresponding to the block with the largest font on the first page. - /// Returns nil if no information could be found or if two or more blocks have the same largest size. - func guessTitle() -> String? { - let astring = pageAtIndex(0).attributedString() - - let fullRange = NSMakeRange(0, astring.length) - - var textInfo = [(size: CGFloat, range: NSRange)]() - - astring.enumerateAttribute(NSFontAttributeName, inRange: fullRange, options: NSAttributedStringEnumerationOptions()) { - obj, range, stop in - if let font = obj as? NSFont { - textInfo.append(size: font.pointSize, range: range) - } - } - textInfo.sortInPlace({$0.size > $1.size}) - - if textInfo.count >= 2 && textInfo[0].size > textInfo[1].size { - return (astring.string as NSString).substringWithRange(textInfo[0].range) - } else { - return nil - } - } - /// Returns all keywords in an array, useful for DiMe. /// Keywords can be separated by ";" or "," func getKeywordsAsArray() -> [String]? { @@ -75,9 +49,84 @@ extension PDFDocument { } } + /// Returns all authors as an array of person, useful for DiMe. + /// Authors can be only separated by ";" + func getAuthorsAsArray() -> [Person]? { + guard let auths = getAuthor() else { + return nil + } + + var retVal = [Person]() + if auths.containsChar(";") { + if let splitStr = auths.split(";") { + for subStr in splitStr { + if let newPerson = Person(fromString: subStr) { + retVal.append(newPerson) + } + } + } + } else { + if let newPerson = Person(fromString: auths) { + retVal.append(newPerson) + } + } + + if retVal.count > 0 { + return retVal + } else { + return nil + } + } + + /// Returns a trimmed plain text of the data contained in the document, nil not present + /// - Warning: Takes time and memory for big documents, better to asynchronise this + func getText() -> String? { + var trimmedText = string + trimmedText = trimmedText!.replacingOccurrences(of: "\u{fffc}", with: "") + trimmedText = trimmedText!.trimmingCharacters(in: CharacterSet.whitespaces) // get trimmed version of all text + trimmedText = trimmedText!.trimmingCharacters(in: CharacterSet.newlines) // trim newlines + trimmedText = trimmedText!.trimmingCharacters(in: CharacterSet.whitespaces) // trim again + if trimmedText!.characters.count > 5 { // we assume the document does contain useful text if there are more than 5 characters remaining + return trimmedText + } else { + return nil + } + } + + /// Gets the title from the document metadata, returns nil if not present + func getTitle() -> String? { + let docAttrib = documentAttributes + if let title: String = docAttrib![PDFDocumentTitleAttribute] as? String , title.trimmed().characters.count > 0 { + return title.trimmed() + } else { + return nil + } + } + + /// Gets the author(s) from the document metadata, returns nil if not present + func getAuthor() -> String? { + let docAttrib = documentAttributes + if let author: Any = docAttrib![PDFDocumentAuthorAttribute] { + return (author as! String) + } else { + return nil + } + } + + /// Gets the subject from the document metadata, returns nil if not present + func getSubject() -> String? { + let docAttrib = documentAttributes + if let subject: Any = docAttrib![PDFDocumentSubjectAttribute] { + return (subject as! String) + } else { + return nil + } + } + + /// Gets the keywods from the document metadata, returns nil if not present func getKeywords() -> String? { - let docAttrib = documentAttributes() - if let keywords: AnyObject = docAttrib[PDFDocumentKeywordsAttribute] { + let docAttrib = documentAttributes + if let keywords: Any = docAttrib![PDFDocumentKeywordsAttribute] { // some times keywords are in an array // other times keywords are all contained in the first element of the array as a string // other times they are a string @@ -105,23 +154,32 @@ extension PDFDocument { } } - /// Attempts to retrieve metadata for the given pdf. - /// Returns nil if it couln't be found - /// - Attention: Blocks while waiting for an answer from crossref, don't use on main thread. - func getMetadata() -> JSON? { + // MARK: - Auto-Metadata + + /// **Synchronously** attempt to auto-set metadata using crossref. + /// - Attention: Do not call this from main thread (blocks while waiting for an answer). + /// - Returns: The json found with crossref, or nil if the operation failed. + func autoCrossref() -> JSON? { + + guard !Thread.isMainThread else { + AppSingleton.log.error("Attempted to call on the main thread, aborting") + return nil + } + // Try to find doi var _doi: String? = nil - guard let pageString = self.pageAtIndex(0).string() else { + guard let pageString = self.page(at: 0)!.string else { return nil } + let doiSearches = ["doi ", "doi:"] for doiS in doiSearches { - let _range = pageString.rangeOfString(doiS, options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil, locale: nil) + let range = pageString.range(of: doiS, options: NSString.CompareOptions.caseInsensitive, range: nil, locale: nil) - if let r = _range, last = r.last { - let s = pageString.substringFromIndex(last.advancedBy(1)).trimmed() - if let doiChunk = s.firstChunk() where doiChunk.characters.count >= 5 { + if let upperBound = range?.upperBound { + let s = pageString.substring(from: upperBound).trimmed() + if let doiChunk = s.firstChunk() , doiChunk.characters.count >= 5 { _doi = doiChunk break } @@ -133,53 +191,99 @@ extension PDFDocument { return nil } - var json: JSON? - let sema = dispatch_semaphore_create(0) + var foundJson: JSON? + let sema = DispatchSemaphore(value: 0) - Alamofire.request(.GET, "http://api.crossref.org/works/\(doi)").responseJSON() { - response in - if let resp = response.result.value where response.result.isSuccess { - let _json = JSON(resp) - if let status = _json["status"].string where status == "ok" { - json = _json + CrossRefSession.fetch(doi: doi) { + json in + if let json = json { + if let status = json["status"].string , status == "ok" { + if let title = json["message"]["title"][0].string { + self.setTitle(title) + } + if let subj = json["message"]["container-title"][0].string { + self.setSubject(subj) + } + if let auths = json["message"]["author"].array { + let authString = auths.map({$0["given"].stringValue + " " + $0["family"].stringValue}).joined(separator: "; ") + self.setAuthor(authString) + } + foundJson = json } } - dispatch_semaphore_signal(sema) + sema.signal() } - - let waitTime = dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Float(NSEC_PER_SEC))) - if dispatch_semaphore_wait(sema, waitTime) != 0 { + // wait five seconds + let waitTime = DispatchTime.now() + 5.0 + if sema.wait(timeout: waitTime) == .timedOut { AppSingleton.log.warning("Crossref request timed out") } - return json - + return foundJson } - /// Returns a trimmed plain text of the data contained in the document, nil not preset - func getText() -> String? { + /// Returns the string corresponding to the block with the largest font on the first page. + /// Returns nil if no information could be found or if two or more blocks have the same largest size. + func guessTitle() -> String? { + let astring = page(at: 0)!.attributedString - var trimmedText = string() - trimmedText = trimmedText.stringByReplacingOccurrencesOfString("\u{fffc}", withString: "") - trimmedText = trimmedText.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) // get trimmed version of all text - trimmedText = trimmedText.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet()) // trim newlines - trimmedText = trimmedText.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) // trim again - if trimmedText.characters.count > 5 { // we assume the document does contain useful text if there are more than 5 characters remaining - return trimmedText + let fullRange = NSMakeRange(0, astring!.length) + + var textInfo = [(size: CGFloat, range: NSRange)]() + + astring!.enumerateAttribute(NSFontAttributeName, in: fullRange, options: NSAttributedString.EnumerationOptions()) { + obj, range, stop in + if let font = obj as? NSFont { + textInfo.append(size: font.pointSize, range: range) + } + } + + textInfo.sort(by: {$0.size > $1.size}) + + if textInfo.count >= 2 && textInfo[0].size > textInfo[1].size { + return (astring!.string as NSString).substring(with: textInfo[0].range) } else { return nil } } - /// Gets the title from the document metadata, returns nil if not present - func getTitle() -> String? { - let docAttrib = documentAttributes() - if let title: String = docAttrib[PDFDocumentTitleAttribute] as? String where title.trimmed().characters.count > 0 { - return title.trimmed() - } else { + // MARK: - Setters + + func setTitle(_ newTitle: String) { + var docAttrib = documentAttributes + docAttrib![PDFDocumentTitleAttribute] = newTitle + documentAttributes = docAttrib + } + + func setSubject(_ newSubject: String) { + var docAttrib = documentAttributes + docAttrib![PDFDocumentSubjectAttribute] = newSubject + documentAttributes = docAttrib + } + + func setAuthor(_ newAuthor: String) { + var docAttrib = documentAttributes + docAttrib![PDFDocumentAuthorAttribute] = newAuthor + documentAttributes = docAttrib + } + + func setKeywords(_ newKeywords: String) { + var docAttrib = documentAttributes + docAttrib![PDFDocumentKeywordsAttribute] = newKeywords + documentAttributes = docAttrib + } + + /** + Get the page at the specified index (unlike the PDFKit function, returns nil + if the index is out of bounds, but logs a warning). + */ + public func getPage(atIndex index: Int) -> PDFPage? { + if index < 0 || index >= self.pageCount { + AppSingleton.log.warning("Attempted to retrieve a page at index \(index), while the document has \(self.pageCount) pages.") return nil + } else { + return self.page(at: index) } } - } diff --git a/JustUsed/Extensions/String+Extensions.swift b/JustUsed/Extensions/String+Extensions.swift index 61c7291..7a54b0c 100644 --- a/JustUsed/Extensions/String+Extensions.swift +++ b/JustUsed/Extensions/String+Extensions.swift @@ -26,13 +26,25 @@ import Foundation extension String { + /// Returns true if the string contains any of the strings given in the array. + /// Case insensitive search. + func containsAny(strings: [String]) -> Bool { + for s2 in strings { + if self.range(of: s2, options: .caseInsensitive) != nil { + return true + } + } + return false + } + /// Returns an array of the words that compose the string (skipping space and other punctuation). Returns nil if no words were found. func words() -> [String]? { - let range = self.startIndex ..< self.endIndex var words = [String]() - self.enumerateSubstringsInRange(range, options: NSStringEnumerationOptions.ByWords) { substring, _, _, _ in + self.enumerateSubstrings(in: self.startIndex ..< self.endIndex, + options: NSString.EnumerationOptions.byWords) + { substring, _, _, _ in if let s = substring { words.append(s) } @@ -62,23 +74,57 @@ extension String { } } + /// Removes the character(s) from this string + mutating func removeChars(_ theChars: [Character]) { + self = String(characters.filter({!theChars.contains($0)})) + } + + /// Removes the character(s) and returns a new string + + func withoutChars(_ theChars: [Character]) -> String { + return String(self.characters.filter({!theChars.contains($0)})) + } + + /// Dumps a string to a file in the temporary directory. + /// The title will be prefixed to the name of the output file (before the date/time). + /// Returns url of the written-to file (if successful, otherwise nil). + func dumpToTemp(_ title: String) -> URL? { + // Date formatter similar to XCGLogger + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.dateFormat = "_yyyy-MM-dd_HH:mm:ss.SSS" + let dateString = dateFormatter.string(from: Date()) + let outFilename = title + dateString + ".txt" + + + var tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(Bundle.main.bundleIdentifier!) + tempURL = tempURL.appendingPathComponent(outFilename) + + do { + try self.write(to: tempURL, atomically: true, encoding: String.Encoding.utf8) + return tempURL + } catch { + return nil + } + } + /// Returns SHA1 digest for this string func sha1() -> String { - let data = self.dataUsingEncoding(NSUTF8StringEncoding)! - var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0) - CC_SHA1(data.bytes, CC_LONG(data.length), &digest) + let data = self.data(using: String.Encoding.utf8)! + var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) + CC_SHA1((data as NSData).bytes, CC_LONG(data.count), &digest) let hexBytes = digest.map { String(format: "%02hhx", $0) } - return hexBytes.joinWithSeparator("") + return hexBytes.joined(separator: "") } /// Trims whitespace and newlines using foundation func trimmed() -> String { - let nss: NSString = self - return nss.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + let nss: NSString = self as NSString + return nss.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) } /// Checks if this string contains the given character - func containsChar(theChar: Character) -> Bool { + func containsChar(_ theChar: Character) -> Bool { for c in self.characters { if c == theChar { return true @@ -87,16 +133,27 @@ extension String { return false } + /// Counts occurrences of char within this string + func countOfChar(_ theChar: Character) -> Int { + var count = 0 + for c in self.characters { + if c == theChar { + count += 1 + } + } + return count + } + /// Splits the string, trimming whitespaces, between the given characters (note: slow for very long strings) - func split(theChar: Character) -> [String]? { + func split(_ theChar: Character) -> [String]? { var outVal = [String]() var remainingString = self while remainingString.containsChar(theChar) { var outString = "" - var nextChar = remainingString.removeAtIndex(remainingString.startIndex) + var nextChar = remainingString.remove(at: remainingString.startIndex) while nextChar != theChar { outString.append(nextChar) - nextChar = remainingString.removeAtIndex(remainingString.startIndex) + nextChar = remainingString.remove(at: remainingString.startIndex) } if !outString.trimmed().isEmpty { outVal.append(outString.trimmed()) @@ -113,9 +170,8 @@ extension String { } /// Skips the first x characters - func skipPrefix(nOfChars: Int) -> String { - return self.substringFromIndex(self.startIndex.advancedBy(nOfChars)) + func skipPrefix(_ nOfChars: Int) -> String { + return self.substring(from: self.characters.index(self.startIndex, offsetBy: nOfChars)) } } - diff --git a/JustUsed/Info.plist b/JustUsed/Info.plist index ddc21ab..c07429e 100644 --- a/JustUsed/Info.plist +++ b/JustUsed/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.2 + 1.2 CFBundleSignature ???? CFBundleVersion - 6 + 7 LSApplicationCategoryType LSMinimumSystemVersion diff --git a/JustUsed/Libraries/SwiftyJSON.swift b/JustUsed/Libraries/SwiftyJSON.swift deleted file mode 100755 index 100f30f..0000000 --- a/JustUsed/Libraries/SwiftyJSON.swift +++ /dev/null @@ -1,1353 +0,0 @@ -// SwiftyJSON.swift -// -// Copyright (c) 2014 Ruoyu Fu, Pinglin Tang -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation - -// MARK: - Error - -///Error domain -public let ErrorDomain: String! = "SwiftyJSONErrorDomain" - -///Error code -public let ErrorUnsupportedType: Int! = 999 -public let ErrorIndexOutOfBounds: Int! = 900 -public let ErrorWrongType: Int! = 901 -public let ErrorNotExist: Int! = 500 -public let ErrorInvalidJSON: Int! = 490 - -// MARK: - JSON Type - -/** -JSON's type definitions. - -See http://tools.ietf.org/html/rfc7231#section-4.3 -*/ -public enum Type :Int{ - - case Number - case String - case Bool - case Array - case Dictionary - case Null - case Unknown -} - -// MARK: - JSON Base - -public struct JSON { - - /** - Creates a JSON using the data. - - - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary - - parameter opt: The JSON serialization reading options. `.AllowFragments` by default. - - parameter error: error The NSErrorPointer used to return the error. `nil` by default. - - - returns: The created JSON - */ - public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) { - do { - let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt) - self.init(object) - } catch let aError as NSError { - if error != nil { - error.memory = aError - } - self.init(NSNull()) - } - } - - /** - Creates a JSON using the object. - - - parameter object: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. - - - returns: The created JSON - */ - public init(_ object: AnyObject) { - self.object = object - } - - /** - Creates a JSON from a [JSON] - - - parameter jsonArray: A Swift array of JSON objects - - - returns: The created JSON - */ - public init(_ jsonArray:[JSON]) { - self.init(jsonArray.map { $0.object }) - } - - /** - Creates a JSON from a [String: JSON] - - - parameter jsonDictionary: A Swift dictionary of JSON objects - - - returns: The created JSON - */ - public init(_ jsonDictionary:[String: JSON]) { - var dictionary = [String: AnyObject]() - for (key, json) in jsonDictionary { - dictionary[key] = json.object - } - self.init(dictionary) - } - - /// Private object - private var rawArray: [AnyObject] = [] - private var rawDictionary: [String : AnyObject] = [:] - private var rawString: String = "" - private var rawNumber: NSNumber = 0 - private var rawNull: NSNull = NSNull() - /// Private type - private var _type: Type = .Null - /// prviate error - private var _error: NSError? = nil - - /// Object in JSON - public var object: AnyObject { - get { - switch self.type { - case .Array: - return self.rawArray - case .Dictionary: - return self.rawDictionary - case .String: - return self.rawString - case .Number: - return self.rawNumber - case .Bool: - return self.rawNumber - default: - return self.rawNull - } - } - set { - _error = nil - switch newValue { - case let number as NSNumber: - if number.isBool { - _type = .Bool - } else { - _type = .Number - } - self.rawNumber = number - case let string as String: - _type = .String - self.rawString = string - case _ as NSNull: - _type = .Null - case let array as [AnyObject]: - _type = .Array - self.rawArray = array - case let dictionary as [String : AnyObject]: - _type = .Dictionary - self.rawDictionary = dictionary - default: - _type = .Unknown - _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"]) - } - } - } - - /// json type - public var type: Type { get { return _type } } - - /// Error in JSON - public var error: NSError? { get { return self._error } } - - /// The static null json - @available(*, unavailable, renamed="null") - public static var nullJSON: JSON { get { return null } } - public static var null: JSON { get { return JSON(NSNull()) } } -} - -// MARK: - CollectionType, SequenceType, Indexable -extension JSON : Swift.CollectionType, Swift.SequenceType, Swift.Indexable { - - public typealias Generator = JSONGenerator - - public typealias Index = JSONIndex - - public var startIndex: JSON.Index { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.rawArray.startIndex) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex) - default: - return JSONIndex() - } - } - - public var endIndex: JSON.Index { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.rawArray.endIndex) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex) - default: - return JSONIndex() - } - } - - public subscript (position: JSON.Index) -> JSON.Generator.Element { - switch self.type { - case .Array: - return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!])) - case .Dictionary: - let (key, value) = self.rawDictionary[position.dictionaryIndex!] - return (key, JSON(value)) - default: - return ("", JSON.null) - } - } - - /// If `type` is `.Array` or `.Dictionary`, return `array.empty` or `dictonary.empty` otherwise return `false`. - public var isEmpty: Bool { - get { - switch self.type { - case .Array: - return self.rawArray.isEmpty - case .Dictionary: - return self.rawDictionary.isEmpty - default: - return true - } - } - } - - /// If `type` is `.Array` or `.Dictionary`, return `array.count` or `dictonary.count` otherwise return `0`. - public var count: Int { - switch self.type { - case .Array: - return self.rawArray.count - case .Dictionary: - return self.rawDictionary.count - default: - return 0 - } - } - - public func underestimateCount() -> Int { - switch self.type { - case .Array: - return self.rawArray.underestimateCount() - case .Dictionary: - return self.rawDictionary.underestimateCount() - default: - return 0 - } - } - - /** - If `type` is `.Array` or `.Dictionary`, return a generator over the elements like `Array` or `Dictionary`, otherwise return a generator over empty. - - - returns: Return a *generator* over the elements of JSON. - */ - public func generate() -> JSON.Generator { - return JSON.Generator(self) - } -} - -public struct JSONIndex: ForwardIndexType, _Incrementable, Equatable, Comparable { - - let arrayIndex: Int? - let dictionaryIndex: DictionaryIndex? - - let type: Type - - init(){ - self.arrayIndex = nil - self.dictionaryIndex = nil - self.type = .Unknown - } - - init(arrayIndex: Int) { - self.arrayIndex = arrayIndex - self.dictionaryIndex = nil - self.type = .Array - } - - init(dictionaryIndex: DictionaryIndex) { - self.arrayIndex = nil - self.dictionaryIndex = dictionaryIndex - self.type = .Dictionary - } - - public func successor() -> JSONIndex { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.arrayIndex!.successor()) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor()) - default: - return JSONIndex() - } - } -} - -public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex == rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex == rhs.dictionaryIndex - default: - return false - } -} - -public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex < rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex < rhs.dictionaryIndex - default: - return false - } -} - -public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex <= rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex <= rhs.dictionaryIndex - default: - return false - } -} - -public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex >= rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex >= rhs.dictionaryIndex - default: - return false - } -} - -public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex > rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex > rhs.dictionaryIndex - default: - return false - } -} - -public struct JSONGenerator : GeneratorType { - - public typealias Element = (String, JSON) - - private let type: Type - private var dictionayGenerate: DictionaryGenerator? - private var arrayGenerate: IndexingGenerator<[AnyObject]>? - private var arrayIndex: Int = 0 - - init(_ json: JSON) { - self.type = json.type - if type == .Array { - self.arrayGenerate = json.rawArray.generate() - }else { - self.dictionayGenerate = json.rawDictionary.generate() - } - } - - public mutating func next() -> JSONGenerator.Element? { - switch self.type { - case .Array: - if let o = self.arrayGenerate!.next() { - self.arrayIndex += 1 - return (String(self.arrayIndex), JSON(o)) - } else { - return nil - } - case .Dictionary: - if let (k, v): (String, AnyObject) = self.dictionayGenerate!.next() { - return (k, JSON(v)) - } else { - return nil - } - default: - return nil - } - } -} - -// MARK: - Subscript - -/** -* To mark both String and Int can be used in subscript. -*/ -public protocol JSONSubscriptType {} - -extension Int: JSONSubscriptType {} - -extension String: JSONSubscriptType {} - -extension JSON { - - /// If `type` is `.Array`, return json which's object is `array[index]`, otherwise return null json with error. - private subscript(index index: Int) -> JSON { - get { - if self.type != .Array { - var r = JSON.null - r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"]) - return r - } else if index >= 0 && index < self.rawArray.count { - return JSON(self.rawArray[index]) - } else { - var r = JSON.null - r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"]) - return r - } - } - set { - if self.type == .Array { - if self.rawArray.count > index && newValue.error == nil { - self.rawArray[index] = newValue.object - } - } - } - } - - /// If `type` is `.Dictionary`, return json which's object is `dictionary[key]` , otherwise return null json with error. - private subscript(key key: String) -> JSON { - get { - var r = JSON.null - if self.type == .Dictionary { - if let o = self.rawDictionary[key] { - r = JSON(o) - } else { - r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"]) - } - } else { - r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"]) - } - return r - } - set { - if self.type == .Dictionary && newValue.error == nil { - self.rawDictionary[key] = newValue.object - } - } - } - - /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. - private subscript(sub sub: JSONSubscriptType) -> JSON { - get { - if sub is String { - return self[key:sub as! String] - } else { - return self[index:sub as! Int] - } - } - set { - if sub is String { - self[key:sub as! String] = newValue - } else { - self[index:sub as! Int] = newValue - } - } - } - - /** - Find a json in the complex data structuresby using the Int/String's array. - - - parameter path: The target json's path. Example: - - let json = JSON[data] - let path = [9,"list","person","name"] - let name = json[path] - - The same as: let name = json[9]["list"]["person"]["name"] - - - returns: Return a json found by the path or a null json with error - */ - public subscript(path: [JSONSubscriptType]) -> JSON { - get { - return path.reduce(self) { $0[sub: $1] } - } - set { - switch path.count { - case 0: - return - case 1: - self[sub:path[0]].object = newValue.object - default: - var aPath = path; aPath.removeAtIndex(0) - var nextJSON = self[sub: path[0]] - nextJSON[aPath] = newValue - self[sub: path[0]] = nextJSON - } - } - } - - /** - Find a json in the complex data structuresby using the Int/String's array. - - - parameter path: The target json's path. Example: - - let name = json[9,"list","person","name"] - - The same as: let name = json[9]["list"]["person"]["name"] - - - returns: Return a json found by the path or a null json with error - */ - public subscript(path: JSONSubscriptType...) -> JSON { - get { - return self[path] - } - set { - self[path] = newValue - } - } -} - -// MARK: - LiteralConvertible - -extension JSON: Swift.StringLiteralConvertible { - - public init(stringLiteral value: StringLiteralType) { - self.init(value) - } - - public init(extendedGraphemeClusterLiteral value: StringLiteralType) { - self.init(value) - } - - public init(unicodeScalarLiteral value: StringLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.IntegerLiteralConvertible { - - public init(integerLiteral value: IntegerLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.BooleanLiteralConvertible { - - public init(booleanLiteral value: BooleanLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.FloatLiteralConvertible { - - public init(floatLiteral value: FloatLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.DictionaryLiteralConvertible { - - public init(dictionaryLiteral elements: (String, AnyObject)...) { - self.init(elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in - var d = dictionary - d[element.0] = element.1 - return d - }) - } -} - -extension JSON: Swift.ArrayLiteralConvertible { - - public init(arrayLiteral elements: AnyObject...) { - self.init(elements) - } -} - -extension JSON: Swift.NilLiteralConvertible { - - public init(nilLiteral: ()) { - self.init(NSNull()) - } -} - -// MARK: - Raw - -extension JSON: Swift.RawRepresentable { - - public init?(rawValue: AnyObject) { - if JSON(rawValue).type == .Unknown { - return nil - } else { - self.init(rawValue) - } - } - - public var rawValue: AnyObject { - return self.object - } - - public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData { - guard NSJSONSerialization.isValidJSONObject(self.object) else { - throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"]) - } - - return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt) - } - - public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? { - switch self.type { - case .Array, .Dictionary: - do { - let data = try self.rawData(options: opt) - return NSString(data: data, encoding: encoding) as? String - } catch _ { - return nil - } - case .String: - return self.rawString - case .Number: - return self.rawNumber.stringValue - case .Bool: - return self.rawNumber.boolValue.description - case .Null: - return "null" - default: - return nil - } - } -} - -// MARK: - Printable, DebugPrintable - -extension JSON: Swift.Printable, Swift.DebugPrintable { - - public var description: String { - if let string = self.rawString(options:.PrettyPrinted) { - return string - } else { - return "unknown" - } - } - - public var debugDescription: String { - return description - } -} - -// MARK: - Array - -extension JSON { - - //Optional [JSON] - public var array: [JSON]? { - get { - if self.type == .Array { - return self.rawArray.map{ JSON($0) } - } else { - return nil - } - } - } - - //Non-optional [JSON] - public var arrayValue: [JSON] { - get { - return self.array ?? [] - } - } - - //Optional [AnyObject] - public var arrayObject: [AnyObject]? { - get { - switch self.type { - case .Array: - return self.rawArray - default: - return nil - } - } - set { - if let array = newValue { - self.object = array - } else { - self.object = NSNull() - } - } - } -} - -// MARK: - Dictionary - -extension JSON { - - //Optional [String : JSON] - public var dictionary: [String : JSON]? { - if self.type == .Dictionary { - return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, AnyObject)) -> [String : JSON] in - var d = dictionary - d[element.0] = JSON(element.1) - return d - } - } else { - return nil - } - } - - //Non-optional [String : JSON] - public var dictionaryValue: [String : JSON] { - return self.dictionary ?? [:] - } - - //Optional [String : AnyObject] - public var dictionaryObject: [String : AnyObject]? { - get { - switch self.type { - case .Dictionary: - return self.rawDictionary - default: - return nil - } - } - set { - if let v = newValue { - self.object = v - } else { - self.object = NSNull() - } - } - } -} - -// MARK: - Bool - -extension JSON: Swift.BooleanType { - - //Optional bool - public var bool: Bool? { - get { - switch self.type { - case .Bool: - return self.rawNumber.boolValue - default: - return nil - } - } - set { - if newValue != nil { - self.object = NSNumber(bool: newValue!) - } else { - self.object = NSNull() - } - } - } - - //Non-optional bool - public var boolValue: Bool { - get { - switch self.type { - case .Bool, .Number, .String: - return self.object.boolValue - default: - return false - } - } - set { - self.object = NSNumber(bool: newValue) - } - } -} - -// MARK: - String - -extension JSON { - - //Optional string - public var string: String? { - get { - switch self.type { - case .String: - return self.object as? String - default: - return nil - } - } - set { - if newValue != nil { - self.object = NSString(string:newValue!) - } else { - self.object = NSNull() - } - } - } - - //Non-optional string - public var stringValue: String { - get { - switch self.type { - case .String: - return self.object as! String - case .Number: - return self.object.stringValue - case .Bool: - return (self.object as! Bool).description - default: - return "" - } - } - set { - self.object = NSString(string:newValue) - } - } -} - -// MARK: - Number -extension JSON { - - //Optional number - public var number: NSNumber? { - get { - switch self.type { - case .Number, .Bool: - return self.rawNumber - default: - return nil - } - } - set { - self.object = newValue ?? NSNull() - } - } - - //Non-optional number - public var numberValue: NSNumber { - get { - switch self.type { - case .String: - let decimal = NSDecimalNumber(string: self.object as? String) - if decimal == NSDecimalNumber.notANumber() { // indicates parse error - return NSDecimalNumber.zero() - } - return decimal - case .Number, .Bool: - return self.object as! NSNumber - default: - return NSNumber(double: 0.0) - } - } - set { - self.object = newValue - } - } -} - -//MARK: - Null -extension JSON { - - public var null: NSNull? { - get { - switch self.type { - case .Null: - return self.rawNull - default: - return nil - } - } - set { - self.object = NSNull() - } - } - public func isExists() -> Bool{ - if let errorValue = error where errorValue.code == ErrorNotExist{ - return false - } - return true - } -} - -//MARK: - URL -extension JSON { - - //Optional URL - public var URL: NSURL? { - get { - switch self.type { - case .String: - if let encodedString_ = self.rawString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) { - return NSURL(string: encodedString_) - } else { - return nil - } - default: - return nil - } - } - set { - self.object = newValue?.absoluteString ?? NSNull() - } - } -} - -// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 - -extension JSON { - - public var double: Double? { - get { - return self.number?.doubleValue - } - set { - if newValue != nil { - self.object = NSNumber(double: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var doubleValue: Double { - get { - return self.numberValue.doubleValue - } - set { - self.object = NSNumber(double: newValue) - } - } - - public var float: Float? { - get { - return self.number?.floatValue - } - set { - if newValue != nil { - self.object = NSNumber(float: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var floatValue: Float { - get { - return self.numberValue.floatValue - } - set { - self.object = NSNumber(float: newValue) - } - } - - public var int: Int? { - get { - return self.number?.longValue - } - set { - if newValue != nil { - self.object = NSNumber(integer: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var intValue: Int { - get { - return self.numberValue.integerValue - } - set { - self.object = NSNumber(integer: newValue) - } - } - - public var uInt: UInt? { - get { - return self.number?.unsignedLongValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedLong: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uIntValue: UInt { - get { - return self.numberValue.unsignedLongValue - } - set { - self.object = NSNumber(unsignedLong: newValue) - } - } - - public var int8: Int8? { - get { - return self.number?.charValue - } - set { - if newValue != nil { - self.object = NSNumber(char: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int8Value: Int8 { - get { - return self.numberValue.charValue - } - set { - self.object = NSNumber(char: newValue) - } - } - - public var uInt8: UInt8? { - get { - return self.number?.unsignedCharValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedChar: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt8Value: UInt8 { - get { - return self.numberValue.unsignedCharValue - } - set { - self.object = NSNumber(unsignedChar: newValue) - } - } - - public var int16: Int16? { - get { - return self.number?.shortValue - } - set { - if newValue != nil { - self.object = NSNumber(short: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int16Value: Int16 { - get { - return self.numberValue.shortValue - } - set { - self.object = NSNumber(short: newValue) - } - } - - public var uInt16: UInt16? { - get { - return self.number?.unsignedShortValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedShort: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt16Value: UInt16 { - get { - return self.numberValue.unsignedShortValue - } - set { - self.object = NSNumber(unsignedShort: newValue) - } - } - - public var int32: Int32? { - get { - return self.number?.intValue - } - set { - if newValue != nil { - self.object = NSNumber(int: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int32Value: Int32 { - get { - return self.numberValue.intValue - } - set { - self.object = NSNumber(int: newValue) - } - } - - public var uInt32: UInt32? { - get { - return self.number?.unsignedIntValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedInt: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt32Value: UInt32 { - get { - return self.numberValue.unsignedIntValue - } - set { - self.object = NSNumber(unsignedInt: newValue) - } - } - - public var int64: Int64? { - get { - return self.number?.longLongValue - } - set { - if newValue != nil { - self.object = NSNumber(longLong: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int64Value: Int64 { - get { - return self.numberValue.longLongValue - } - set { - self.object = NSNumber(longLong: newValue) - } - } - - public var uInt64: UInt64? { - get { - return self.number?.unsignedLongLongValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedLongLong: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt64Value: UInt64 { - get { - return self.numberValue.unsignedLongLongValue - } - set { - self.object = NSNumber(unsignedLongLong: newValue) - } - } -} - -//MARK: - Comparable -extension JSON : Swift.Comparable {} - -public func ==(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber == rhs.rawNumber - case (.String, .String): - return lhs.rawString == rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): - return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): - return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): - return true - default: - return false - } -} - -public func <=(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber <= rhs.rawNumber - case (.String, .String): - return lhs.rawString <= rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): - return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): - return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): - return true - default: - return false - } -} - -public func >=(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber >= rhs.rawNumber - case (.String, .String): - return lhs.rawString >= rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): - return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): - return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): - return true - default: - return false - } -} - -public func >(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber > rhs.rawNumber - case (.String, .String): - return lhs.rawString > rhs.rawString - default: - return false - } -} - -public func <(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber < rhs.rawNumber - case (.String, .String): - return lhs.rawString < rhs.rawString - default: - return false - } -} - -private let trueNumber = NSNumber(bool: true) -private let falseNumber = NSNumber(bool: false) -private let trueObjCType = String.fromCString(trueNumber.objCType) -private let falseObjCType = String.fromCString(falseNumber.objCType) - -// MARK: - NSNumber: Comparable - -extension NSNumber { - var isBool:Bool { - get { - let objCType = String.fromCString(self.objCType) - if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType) - || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){ - return true - } else { - return false - } - } - } -} - -public func ==(lhs: NSNumber, rhs: NSNumber) -> Bool { - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) == NSComparisonResult.OrderedSame - } -} - -public func !=(lhs: NSNumber, rhs: NSNumber) -> Bool { - return !(lhs == rhs) -} - -public func <(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) == NSComparisonResult.OrderedAscending - } -} - -public func >(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) == NSComparisonResult.OrderedDescending - } -} - -public func <=(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) != NSComparisonResult.OrderedDescending - } -} - -public func >=(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) != NSComparisonResult.OrderedAscending - } -} diff --git a/JustUsed/Libraries/XCGLogger.swift b/JustUsed/Libraries/XCGLogger.swift deleted file mode 100755 index a09d569..0000000 --- a/JustUsed/Libraries/XCGLogger.swift +++ /dev/null @@ -1,893 +0,0 @@ -// -// XCGLogger.swift -// XCGLogger: https://github.com/DaveWoodCom/XCGLogger -// -// Created by Dave Wood on 2014-06-06. -// Copyright (c) 2014 Dave Wood, Cerebral Gardens. -// Some rights reserved: https://github.com/DaveWoodCom/XCGLogger/blob/master/LICENSE.txt -// - -import Foundation -#if os(OSX) - import AppKit -#elseif os(iOS) || os(tvOS) || os(watchOS) - import UIKit -#endif - -// MARK: - XCGLogDetails -// - Data structure to hold all info about a log message, passed to log destination classes -public struct XCGLogDetails { - public var logLevel: XCGLogger.LogLevel - public var date: NSDate - public var logMessage: String - public var functionName: String - public var fileName: String - public var lineNumber: Int - - public init(logLevel: XCGLogger.LogLevel, date: NSDate, logMessage: String, functionName: String, fileName: String, lineNumber: Int) { - self.logLevel = logLevel - self.date = date - self.logMessage = logMessage - self.functionName = functionName - self.fileName = fileName - self.lineNumber = lineNumber - } -} - -// MARK: - XCGLogDestinationProtocol -// - Protocol for output classes to conform to -public protocol XCGLogDestinationProtocol: CustomDebugStringConvertible { - var owner: XCGLogger {get set} - var identifier: String {get set} - var outputLogLevel: XCGLogger.LogLevel {get set} - - func processLogDetails(logDetails: XCGLogDetails) - func processInternalLogDetails(logDetails: XCGLogDetails) // Same as processLogDetails but should omit function/file/line info - func isEnabledForLogLevel(logLevel: XCGLogger.LogLevel) -> Bool -} - -// MARK: - XCGBaseLogDestination -// - A base class log destination that doesn't actually output the log anywhere and is intented to be subclassed -public class XCGBaseLogDestination: XCGLogDestinationProtocol, CustomDebugStringConvertible { - // MARK: - Properties - public var owner: XCGLogger - public var identifier: String - public var outputLogLevel: XCGLogger.LogLevel = .Debug - - public var showLogIdentifier: Bool = false - public var showFunctionName: Bool = true - public var showThreadName: Bool = false - public var showFileName: Bool = true - public var showLineNumber: Bool = true - public var showLogLevel: Bool = true - public var showDate: Bool = true - - // MARK: - CustomDebugStringConvertible - public var debugDescription: String { - get { - return "\(extractClassName(self)): \(identifier) - LogLevel: \(outputLogLevel) showLogIdentifier: \(showLogIdentifier) showFunctionName: \(showFunctionName) showThreadName: \(showThreadName) showLogLevel: \(showLogLevel) showFileName: \(showFileName) showLineNumber: \(showLineNumber) showDate: \(showDate)" - } - } - - // MARK: - Life Cycle - public init(owner: XCGLogger, identifier: String = "") { - self.owner = owner - self.identifier = identifier - } - - // MARK: - Methods to Process Log Details - public func processLogDetails(logDetails: XCGLogDetails) { - var extendedDetails: String = "" - - if showDate { - var formattedDate: String = logDetails.date.description - if let dateFormatter = owner.dateFormatter { - formattedDate = dateFormatter.stringFromDate(logDetails.date) - } - - extendedDetails += "\(formattedDate) " - } - - if showLogLevel { - extendedDetails += "[\(logDetails.logLevel)] " - } - - if showLogIdentifier { - extendedDetails += "[\(owner.identifier)] " - } - - if showThreadName { - if NSThread.isMainThread() { - extendedDetails += "[main] " - } - else { - if let threadName = NSThread.currentThread().name where !threadName.isEmpty { - extendedDetails += "[" + threadName + "] " - } - else if let queueName = String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) where !queueName.isEmpty { - extendedDetails += "[" + queueName + "] " - } - else { - extendedDetails += "[" + String(format:"%p", NSThread.currentThread()) + "] " - } - } - } - - if showFileName { - extendedDetails += "[" + (logDetails.fileName as NSString).lastPathComponent + (showLineNumber ? ":" + String(logDetails.lineNumber) : "") + "] " - } - else if showLineNumber { - extendedDetails += "[" + String(logDetails.lineNumber) + "] " - } - - if showFunctionName { - extendedDetails += "\(logDetails.functionName) " - } - - output(logDetails, text: "\(extendedDetails)> \(logDetails.logMessage)") - } - - public func processInternalLogDetails(logDetails: XCGLogDetails) { - var extendedDetails: String = "" - - if showDate { - var formattedDate: String = logDetails.date.description - if let dateFormatter = owner.dateFormatter { - formattedDate = dateFormatter.stringFromDate(logDetails.date) - } - - extendedDetails += "\(formattedDate) " - } - - if showLogLevel { - extendedDetails += "[\(logDetails.logLevel)] " - } - - if showLogIdentifier { - extendedDetails += "[\(owner.identifier)] " - } - - output(logDetails, text: "\(extendedDetails)> \(logDetails.logMessage)") - } - - // MARK: - Misc methods - public func isEnabledForLogLevel (logLevel: XCGLogger.LogLevel) -> Bool { - return logLevel >= self.outputLogLevel - } - - // MARK: - Methods that must be overriden in subclasses - public func output(logDetails: XCGLogDetails, text: String) { - // Do something with the text in an overridden version of this method - precondition(false, "Must override this") - } -} - -// MARK: - XCGConsoleLogDestination -// - A standard log destination that outputs log details to the console -public class XCGConsoleLogDestination: XCGBaseLogDestination { - // MARK: - Properties - public var logQueue: dispatch_queue_t? = nil - public var xcodeColors: [XCGLogger.LogLevel: XCGLogger.XcodeColor]? = nil - - // MARK: - Misc Methods - public override func output(logDetails: XCGLogDetails, text: String) { - - let outputClosure = { - let adjustedText: String - if let xcodeColor = (self.xcodeColors ?? self.owner.xcodeColors)[logDetails.logLevel] where self.owner.xcodeColorsEnabled { - adjustedText = "\(xcodeColor.format())\(text)\(XCGLogger.XcodeColor.reset)" - } - else { - adjustedText = text - } - - print("\(adjustedText)") - } - - if let logQueue = logQueue { - dispatch_async(logQueue, outputClosure) - } - else { - outputClosure() - } - } -} - -// MARK: - XCGNSLogDestination -// - A standard log destination that outputs log details to the console using NSLog instead of println -public class XCGNSLogDestination: XCGBaseLogDestination { - // MARK: - Properties - public var logQueue: dispatch_queue_t? = nil - public var xcodeColors: [XCGLogger.LogLevel: XCGLogger.XcodeColor]? = nil - - public override var showDate: Bool { - get { - return false - } - set { - // ignored, NSLog adds the date, so we always want showDate to be false in this subclass - } - } - - // MARK: - Misc Methods - public override func output(logDetails: XCGLogDetails, text: String) { - - let outputClosure = { - let adjustedText: String - if let xcodeColor = (self.xcodeColors ?? self.owner.xcodeColors)[logDetails.logLevel] where self.owner.xcodeColorsEnabled { - adjustedText = "\(xcodeColor.format())\(text)\(XCGLogger.XcodeColor.reset)" - } - else { - adjustedText = text - } - - NSLog("%@", adjustedText) - } - - if let logQueue = logQueue { - dispatch_async(logQueue, outputClosure) - } - else { - outputClosure() - } - } -} - -// MARK: - XCGFileLogDestination -// - A standard log destination that outputs log details to a file -public class XCGFileLogDestination: XCGBaseLogDestination { - // MARK: - Properties - public var logQueue: dispatch_queue_t? = nil - private var writeToFileURL: NSURL? = nil { - didSet { - openFile() - } - } - private var logFileHandle: NSFileHandle? = nil - - // MARK: - Life Cycle - public init(owner: XCGLogger, writeToFile: AnyObject, identifier: String = "") { - super.init(owner: owner, identifier: identifier) - - if writeToFile is NSString { - writeToFileURL = NSURL.fileURLWithPath(writeToFile as! String) - } - else if writeToFile is NSURL { - writeToFileURL = writeToFile as? NSURL - } - else { - writeToFileURL = nil - } - - openFile() - } - - deinit { - // close file stream if open - closeFile() - } - - // MARK: - File Handling Methods - private func openFile() { - if logFileHandle != nil { - closeFile() - } - - if let writeToFileURL = writeToFileURL, - let path = writeToFileURL.path { - - NSFileManager.defaultManager().createFileAtPath(path, contents: nil, attributes: nil) - do { - logFileHandle = try NSFileHandle(forWritingToURL: writeToFileURL) - } - catch let error as NSError { - owner._logln("Attempt to open log file for writing failed: \(error.localizedDescription)", logLevel: .Error) - logFileHandle = nil - return - } - - owner.logAppDetails(self) - - let logDetails = XCGLogDetails(logLevel: .Info, date: NSDate(), logMessage: "XCGLogger writing to log to: \(writeToFileURL)", functionName: "", fileName: "", lineNumber: 0) - owner._logln(logDetails.logMessage, logLevel: logDetails.logLevel) - processInternalLogDetails(logDetails) - } - } - - private func closeFile() { - logFileHandle?.closeFile() - logFileHandle = nil - } - - // MARK: - Misc Methods - public override func output(logDetails: XCGLogDetails, text: String) { - - let outputClosure = { - if let encodedData = "\(text)\n".dataUsingEncoding(NSUTF8StringEncoding) { - self.logFileHandle?.writeData(encodedData) - } - } - - if let logQueue = logQueue { - dispatch_async(logQueue, outputClosure) - } - else { - outputClosure() - } - } -} - -// MARK: - XCGLogger -// - The main logging class -public class XCGLogger: CustomDebugStringConvertible { - // MARK: - Constants - public struct Constants { - public static let defaultInstanceIdentifier = "com.cerebralgardens.xcglogger.defaultInstance" - public static let baseConsoleLogDestinationIdentifier = "com.cerebralgardens.xcglogger.logdestination.console" - public static let nslogDestinationIdentifier = "com.cerebralgardens.xcglogger.logdestination.console.nslog" - public static let baseFileLogDestinationIdentifier = "com.cerebralgardens.xcglogger.logdestination.file" - public static let logQueueIdentifier = "com.cerebralgardens.xcglogger.queue" - public static let nsdataFormatterCacheIdentifier = "com.cerebralgardens.xcglogger.nsdataFormatterCache" - public static let versionString = "3.3" - } - public typealias constants = Constants // Preserve backwards compatibility: Constants should be capitalized since it's a type - - // MARK: - Enums - public enum LogLevel: Int, Comparable, CustomStringConvertible { - case Verbose - case Debug - case Info - case Warning - case Error - case Severe - case None - - public var description: String { - switch self { - case .Verbose: - return "Verbose" - case .Debug: - return "Debug" - case .Info: - return "Info" - case .Warning: - return "Warning" - case .Error: - return "Error" - case .Severe: - return "Severe" - case .None: - return "None" - } - } - } - - public struct XcodeColor { - public static let escape = "\u{001b}[" - public static let resetFg = "\u{001b}[fg;" - public static let resetBg = "\u{001b}[bg;" - public static let reset = "\u{001b}[;" - - public var fg: (Int, Int, Int)? = nil - public var bg: (Int, Int, Int)? = nil - - public func format() -> String { - guard fg != nil || bg != nil else { - // neither set, return reset value - return XcodeColor.reset - } - - var format: String = "" - - if let fg = fg { - format += "\(XcodeColor.escape)fg\(fg.0),\(fg.1),\(fg.2);" - } - else { - format += XcodeColor.resetFg - } - - if let bg = bg { - format += "\(XcodeColor.escape)bg\(bg.0),\(bg.1),\(bg.2);" - } - else { - format += XcodeColor.resetBg - } - - return format - } - - public init(fg: (Int, Int, Int)? = nil, bg: (Int, Int, Int)? = nil) { - self.fg = fg - self.bg = bg - } - -#if os(OSX) - public init(fg: NSColor, bg: NSColor? = nil) { - if let fgColorSpaceCorrected = fg.colorUsingColorSpaceName(NSCalibratedRGBColorSpace) { - self.fg = (Int(fgColorSpaceCorrected.redComponent * 255), Int(fgColorSpaceCorrected.greenComponent * 255), Int(fgColorSpaceCorrected.blueComponent * 255)) - } - else { - self.fg = nil - } - - if let bg = bg, - let bgColorSpaceCorrected = bg.colorUsingColorSpaceName(NSCalibratedRGBColorSpace) { - - self.bg = (Int(bgColorSpaceCorrected.redComponent * 255), Int(bgColorSpaceCorrected.greenComponent * 255), Int(bgColorSpaceCorrected.blueComponent * 255)) - } - else { - self.bg = nil - } - } -#elseif os(iOS) || os(tvOS) || os(watchOS) - public init(fg: UIColor, bg: UIColor? = nil) { - var redComponent: CGFloat = 0 - var greenComponent: CGFloat = 0 - var blueComponent: CGFloat = 0 - var alphaComponent: CGFloat = 0 - - fg.getRed(&redComponent, green: &greenComponent, blue: &blueComponent, alpha:&alphaComponent) - self.fg = (Int(redComponent * 255), Int(greenComponent * 255), Int(blueComponent * 255)) - if let bg = bg { - bg.getRed(&redComponent, green: &greenComponent, blue: &blueComponent, alpha:&alphaComponent) - self.bg = (Int(redComponent * 255), Int(greenComponent * 255), Int(blueComponent * 255)) - } - else { - self.bg = nil - } - } -#endif - - public static let red: XcodeColor = { - return XcodeColor(fg: (255, 0, 0)) - }() - - public static let green: XcodeColor = { - return XcodeColor(fg: (0, 255, 0)) - }() - - public static let blue: XcodeColor = { - return XcodeColor(fg: (0, 0, 255)) - }() - - public static let black: XcodeColor = { - return XcodeColor(fg: (0, 0, 0)) - }() - - public static let white: XcodeColor = { - return XcodeColor(fg: (255, 255, 255)) - }() - - public static let lightGrey: XcodeColor = { - return XcodeColor(fg: (211, 211, 211)) - }() - - public static let darkGrey: XcodeColor = { - return XcodeColor(fg: (169, 169, 169)) - }() - - public static let orange: XcodeColor = { - return XcodeColor(fg: (255, 165, 0)) - }() - - public static let whiteOnRed: XcodeColor = { - return XcodeColor(fg: (255, 255, 255), bg: (255, 0, 0)) - }() - - public static let darkGreen: XcodeColor = { - return XcodeColor(fg: (0, 128, 0)) - }() - } - - // MARK: - Properties (Options) - public var identifier: String = "" - public var outputLogLevel: LogLevel = .Debug { - didSet { - for index in 0 ..< logDestinations.count { - logDestinations[index].outputLogLevel = outputLogLevel - } - } - } - - public var xcodeColorsEnabled: Bool = false - public var xcodeColors: [XCGLogger.LogLevel: XCGLogger.XcodeColor] = [ - .Verbose: .lightGrey, - .Debug: .darkGrey, - .Info: .blue, - .Warning: .orange, - .Error: .red, - .Severe: .whiteOnRed - ] - - // MARK: - Properties - public class var logQueue: dispatch_queue_t { - struct Statics { - static var logQueue = dispatch_queue_create(XCGLogger.Constants.logQueueIdentifier, nil) - } - - return Statics.logQueue - } - - private var _dateFormatter: NSDateFormatter? = nil - public var dateFormatter: NSDateFormatter? { - get { - if _dateFormatter != nil { - return _dateFormatter - } - - let defaultDateFormatter = NSDateFormatter() - defaultDateFormatter.locale = NSLocale.currentLocale() - defaultDateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" - _dateFormatter = defaultDateFormatter - - return _dateFormatter - } - set { - _dateFormatter = newValue - } - } - - public var logDestinations: Array = [] - - // MARK: - Life Cycle - public init(identifier: String = "", includeDefaultDestinations: Bool = true) { - self.identifier = identifier - - // Check if XcodeColors is installed and enabled - if let xcodeColors = NSProcessInfo.processInfo().environment["XcodeColors"] { - xcodeColorsEnabled = xcodeColors == "YES" - } - - if includeDefaultDestinations { - // Setup a standard console log destination - addLogDestination(XCGConsoleLogDestination(owner: self, identifier: XCGLogger.Constants.baseConsoleLogDestinationIdentifier)) - } - } - - // MARK: - Default instance - public class func defaultInstance() -> XCGLogger { - struct Statics { - static let instance: XCGLogger = XCGLogger(identifier: XCGLogger.Constants.defaultInstanceIdentifier) - } - - return Statics.instance - } - - // MARK: - Setup methods - public class func setup(logLevel: LogLevel = .Debug, showLogIdentifier: Bool = false, showFunctionName: Bool = true, showThreadName: Bool = false, showLogLevel: Bool = true, showFileNames: Bool = true, showLineNumbers: Bool = true, showDate: Bool = true, writeToFile: AnyObject? = nil, fileLogLevel: LogLevel? = nil) { - defaultInstance().setup(logLevel, showLogIdentifier: showLogIdentifier, showFunctionName: showFunctionName, showThreadName: showThreadName, showLogLevel: showLogLevel, showFileNames: showFileNames, showLineNumbers: showLineNumbers, showDate: showDate, writeToFile: writeToFile) - } - - public func setup(logLevel: LogLevel = .Debug, showLogIdentifier: Bool = false, showFunctionName: Bool = true, showThreadName: Bool = false, showLogLevel: Bool = true, showFileNames: Bool = true, showLineNumbers: Bool = true, showDate: Bool = true, writeToFile: AnyObject? = nil, fileLogLevel: LogLevel? = nil) { - outputLogLevel = logLevel; - - if let standardConsoleLogDestination = logDestination(XCGLogger.Constants.baseConsoleLogDestinationIdentifier) as? XCGConsoleLogDestination { - standardConsoleLogDestination.showLogIdentifier = showLogIdentifier - standardConsoleLogDestination.showFunctionName = showFunctionName - standardConsoleLogDestination.showThreadName = showThreadName - standardConsoleLogDestination.showLogLevel = showLogLevel - standardConsoleLogDestination.showFileName = showFileNames - standardConsoleLogDestination.showLineNumber = showLineNumbers - standardConsoleLogDestination.showDate = showDate - standardConsoleLogDestination.outputLogLevel = logLevel - } - - logAppDetails() - - if let writeToFile: AnyObject = writeToFile { - // We've been passed a file to use for logging, set up a file logger - let standardFileLogDestination: XCGFileLogDestination = XCGFileLogDestination(owner: self, writeToFile: writeToFile, identifier: XCGLogger.Constants.baseFileLogDestinationIdentifier) - - standardFileLogDestination.showLogIdentifier = showLogIdentifier - standardFileLogDestination.showFunctionName = showFunctionName - standardFileLogDestination.showThreadName = showThreadName - standardFileLogDestination.showLogLevel = showLogLevel - standardFileLogDestination.showFileName = showFileNames - standardFileLogDestination.showLineNumber = showLineNumbers - standardFileLogDestination.showDate = showDate - standardFileLogDestination.outputLogLevel = fileLogLevel ?? logLevel - - addLogDestination(standardFileLogDestination) - } - } - - // MARK: - Logging methods - public class func logln(@autoclosure closure: () -> String?, logLevel: LogLevel = .Debug, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(logLevel, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func logln(logLevel: LogLevel = .Debug, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(logLevel, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func logln(@autoclosure closure: () -> String?, logLevel: LogLevel = .Debug, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(logLevel, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func logln(logLevel: LogLevel = .Debug, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - var logDetails: XCGLogDetails? = nil - for logDestination in self.logDestinations { - if (logDestination.isEnabledForLogLevel(logLevel)) { - if logDetails == nil { - if let logMessage = closure() { - logDetails = XCGLogDetails(logLevel: logLevel, date: NSDate(), logMessage: logMessage, functionName: functionName, fileName: fileName, lineNumber: lineNumber) - } - else { - break - } - } - - logDestination.processLogDetails(logDetails!) - } - } - } - - public class func exec(logLevel: LogLevel = .Debug, closure: () -> () = {}) { - self.defaultInstance().exec(logLevel, closure: closure) - } - - public func exec(logLevel: LogLevel = .Debug, closure: () -> () = {}) { - if (!isEnabledForLogLevel(logLevel)) { - return - } - - closure() - } - - public func logAppDetails(selectedLogDestination: XCGLogDestinationProtocol? = nil) { - let date = NSDate() - - var buildString = "" - if let infoDictionary = NSBundle.mainBundle().infoDictionary { - if let CFBundleShortVersionString = infoDictionary["CFBundleShortVersionString"] as? String { - buildString = "Version: \(CFBundleShortVersionString) " - } - if let CFBundleVersion = infoDictionary["CFBundleVersion"] as? String { - buildString += "Build: \(CFBundleVersion) " - } - } - - let processInfo: NSProcessInfo = NSProcessInfo.processInfo() - let XCGLoggerVersionNumber = XCGLogger.Constants.versionString - - let logDetails: Array = [XCGLogDetails(logLevel: .Info, date: date, logMessage: "\(processInfo.processName) \(buildString)PID: \(processInfo.processIdentifier)", functionName: "", fileName: "", lineNumber: 0), - XCGLogDetails(logLevel: .Info, date: date, logMessage: "XCGLogger Version: \(XCGLoggerVersionNumber) - LogLevel: \(outputLogLevel)", functionName: "", fileName: "", lineNumber: 0)] - - for logDestination in (selectedLogDestination != nil ? [selectedLogDestination!] : logDestinations) { - for logDetail in logDetails { - if !logDestination.isEnabledForLogLevel(.Info) { - continue; - } - - logDestination.processInternalLogDetails(logDetail) - } - } - } - - // MARK: - Convenience logging methods - // MARK: * Verbose - public class func verbose(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(.Verbose, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func verbose(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(.Verbose, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func verbose(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(.Verbose, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func verbose(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.logln(.Verbose, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - // MARK: * Debug - public class func debug(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(.Debug, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func debug(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(.Debug, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func debug(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(.Debug, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func debug(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.logln(.Debug, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - // MARK: * Info - public class func info(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(.Info, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func info(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(.Info, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func info(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(.Info, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func info(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.logln(.Info, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - // MARK: * Warning - public class func warning(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(.Warning, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func warning(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(.Warning, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func warning(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(.Warning, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func warning(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.logln(.Warning, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - // MARK: * Error - public class func error(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(.Error, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func error(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(.Error, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func error(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(.Error, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func error(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.logln(.Error, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - // MARK: * Severe - public class func severe(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.defaultInstance().logln(.Severe, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public class func severe(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.defaultInstance().logln(.Severe, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func severe(@autoclosure closure: () -> String?, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) { - self.logln(.Severe, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - public func severe(functionName: String = #function, fileName: String = #file, lineNumber: Int = #line, @noescape closure: () -> String?) { - self.logln(.Severe, functionName: functionName, fileName: fileName, lineNumber: lineNumber, closure: closure) - } - - // MARK: - Exec Methods - // MARK: * Verbose - public class func verboseExec(closure: () -> () = {}) { - self.defaultInstance().exec(XCGLogger.LogLevel.Verbose, closure: closure) - } - - public func verboseExec(closure: () -> () = {}) { - self.exec(XCGLogger.LogLevel.Verbose, closure: closure) - } - - // MARK: * Debug - public class func debugExec(closure: () -> () = {}) { - self.defaultInstance().exec(XCGLogger.LogLevel.Debug, closure: closure) - } - - public func debugExec(closure: () -> () = {}) { - self.exec(XCGLogger.LogLevel.Debug, closure: closure) - } - - // MARK: * Info - public class func infoExec(closure: () -> () = {}) { - self.defaultInstance().exec(XCGLogger.LogLevel.Info, closure: closure) - } - - public func infoExec(closure: () -> () = {}) { - self.exec(XCGLogger.LogLevel.Info, closure: closure) - } - - // MARK: * Warning - public class func warningExec(closure: () -> () = {}) { - self.defaultInstance().exec(XCGLogger.LogLevel.Warning, closure: closure) - } - - public func warningExec(closure: () -> () = {}) { - self.exec(XCGLogger.LogLevel.Warning, closure: closure) - } - - // MARK: * Error - public class func errorExec(closure: () -> () = {}) { - self.defaultInstance().exec(XCGLogger.LogLevel.Error, closure: closure) - } - - public func errorExec(closure: () -> () = {}) { - self.exec(XCGLogger.LogLevel.Error, closure: closure) - } - - // MARK: * Severe - public class func severeExec(closure: () -> () = {}) { - self.defaultInstance().exec(XCGLogger.LogLevel.Severe, closure: closure) - } - - public func severeExec(closure: () -> () = {}) { - self.exec(XCGLogger.LogLevel.Severe, closure: closure) - } - - // MARK: - Misc methods - public func isEnabledForLogLevel (logLevel: XCGLogger.LogLevel) -> Bool { - return logLevel >= self.outputLogLevel - } - - public func logDestination(identifier: String) -> XCGLogDestinationProtocol? { - for logDestination in logDestinations { - if logDestination.identifier == identifier { - return logDestination - } - } - - return nil - } - - public func addLogDestination(logDestination: XCGLogDestinationProtocol) -> Bool { - let existingLogDestination: XCGLogDestinationProtocol? = self.logDestination(logDestination.identifier) - if existingLogDestination != nil { - return false - } - - logDestinations.append(logDestination) - return true - } - - public func removeLogDestination(logDestination: XCGLogDestinationProtocol) { - removeLogDestination(logDestination.identifier) - } - - public func removeLogDestination(identifier: String) { - logDestinations = logDestinations.filter({$0.identifier != identifier}) - } - - // MARK: - Private methods - private func _logln(logMessage: String, logLevel: LogLevel = .Debug) { - - var logDetails: XCGLogDetails? = nil - for logDestination in self.logDestinations { - if (logDestination.isEnabledForLogLevel(logLevel)) { - if logDetails == nil { - logDetails = XCGLogDetails(logLevel: logLevel, date: NSDate(), logMessage: logMessage, functionName: "", fileName: "", lineNumber: 0) - } - - logDestination.processInternalLogDetails(logDetails!) - } - } - } - - // MARK: - DebugPrintable - public var debugDescription: String { - get { - var description: String = "\(extractClassName(self)): \(identifier) - logDestinations: \r" - for logDestination in logDestinations { - description += "\t \(logDestination.debugDescription)\r" - } - - return description - } - } -} - -// Implement Comparable for XCGLogger.LogLevel -public func < (lhs:XCGLogger.LogLevel, rhs:XCGLogger.LogLevel) -> Bool { - return lhs.rawValue < rhs.rawValue -} - -func extractClassName(someObject: Any) -> String { - return (someObject is Any.Type) ? "\(someObject)" : "\(someObject.dynamicType)" -} diff --git a/JustUsed/Model/AppDelegate.swift b/JustUsed/Model/AppDelegate.swift index 14483b1..ada99d7 100644 --- a/JustUsed/Model/AppDelegate.swift +++ b/JustUsed/Model/AppDelegate.swift @@ -36,14 +36,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { // let browserManager = BrowserHistoryManager() let recentDocTracker: RecentDocumentsTracker = { - if AppSingleton.isElCapitan { + if AppSingleton.aboveYosemite { return SpotlightDocumentTracker() } else { return RecentPlistTracker() } }() - let calendarTracker = CalendarTracker(calendarDelegate: HistoryManager.sharedManager) + let calendarTracker = CalendarTracker() // Data sources to display tables in GUI let browHistoryDataSource = BrowserTrackerDataSource() @@ -52,28 +52,28 @@ class AppDelegate: NSObject, NSApplicationDelegate { /// Popover that will be displayed while clicking on menubar button let popover = NSPopover() - let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSSquareStatusItemLength) + let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength) - func applicationDidFinishLaunching(aNotification: NSNotification) { + func applicationDidFinishLaunching(_ aNotification: Notification) { // Set default preferences var defaultPrefs = [String: AnyObject]() - defaultPrefs[JustUsedConstants.prefDiMeServerURL] = "http://localhost:8080/api" - defaultPrefs[JustUsedConstants.prefDiMeServerUserName] = "Test1" - defaultPrefs[JustUsedConstants.prefDiMeServerPassword] = "123456" + defaultPrefs[JustUsedConstants.prefDiMeServerURL] = "http://localhost:8080/api" as AnyObject + defaultPrefs[JustUsedConstants.prefDiMeServerUserName] = "Test1" as AnyObject + defaultPrefs[JustUsedConstants.prefDiMeServerPassword] = "123456" as AnyObject let defaultExcludeDomains = ["localhost", "talkgadget.google.com"] let defaultExcludeCalendars: [String] = [] - defaultPrefs[JustUsedConstants.prefExcludeCalendars] = defaultExcludeCalendars - defaultPrefs[JustUsedConstants.prefExcludeDomains] = defaultExcludeDomains - defaultPrefs[JustUsedConstants.prefSendPlainTexts] = 1 - defaultPrefs[JustUsedConstants.prefSendSafariHistory] = 0 - NSUserDefaults.standardUserDefaults().registerDefaults(defaultPrefs) - NSUserDefaults.standardUserDefaults().synchronize() + defaultPrefs[JustUsedConstants.prefExcludeCalendars] = defaultExcludeCalendars as AnyObject + defaultPrefs[JustUsedConstants.prefExcludeDomains] = defaultExcludeDomains as AnyObject + defaultPrefs[JustUsedConstants.prefSendPlainTexts] = 1 as AnyObject + defaultPrefs[JustUsedConstants.prefSendSafariHistory] = 0 as AnyObject + UserDefaults.standard.register(defaults: defaultPrefs) + UserDefaults.standard.synchronize() // Starts dime - HistoryManager.sharedManager.dimeConnect() - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(diMeConnectionChanged(_:)), name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) + NotificationCenter.default.addObserver(self, selector: #selector(diMeConnectionChanged(_:)), name: JustUsedConstants.diMeConnectionNotification, object: nil) + DiMeSession.dimeConnect() if let _ = LocationSingleton.getCurrentLocation() { // just fetch nothing to initialise location } @@ -82,10 +82,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { button.action = #selector(togglePopover(_:)) } let storyboard = NSStoryboard(name: "Main", bundle: nil) - popover.behavior = NSPopoverBehavior.Transient + popover.behavior = NSPopoverBehavior.transient // View controller and its delegation - self.viewController = (storyboard.instantiateControllerWithIdentifier("View Controller") as! ViewController) + self.viewController = (storyboard.instantiateController(withIdentifier: "View Controller") as! ViewController) viewController!.setSources(spoHistoryDataSource, browserSource: browHistoryDataSource) popover.contentViewController = self.viewController! recentDocTracker.addRecentDocumentUpdateDelegate(self.viewController!) @@ -107,26 +107,26 @@ class AppDelegate: NSObject, NSApplicationDelegate { } /// Updates itself when connection is lost / resumed - @objc private func diMeConnectionChanged(notification: NSNotification?) { - statusItem.button!.appearsDisabled = !HistoryManager.sharedManager.isDiMeAvailable() + @objc fileprivate func diMeConnectionChanged(_ notification: Notification?) { + statusItem.button!.appearsDisabled = !DiMeSession.dimeAvailable } - func applicationWillTerminate(aNotification: NSNotification) { - NSNotificationCenter.defaultCenter().removeObserver(self, name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) + func applicationWillTerminate(_ aNotification: Notification) { + NotificationCenter.default.removeObserver(self, name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) } - func showPopover(sender: AnyObject?) { + func showPopover(_ sender: AnyObject?) { if let button = statusItem.button { - popover.showRelativeToRect(button.bounds, ofView: button, preferredEdge: NSRectEdge.MinY) + popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) } } - func closePopover(sender: AnyObject?) { + func closePopover(_ sender: AnyObject?) { popover.performClose(sender) } - func togglePopover(sender: AnyObject?) { - if popover.shown { + func togglePopover(_ sender: AnyObject?) { + if popover.isShown { closePopover(sender) } else { showPopover(sender) diff --git a/JustUsed/Model/AppSingleton.swift b/JustUsed/Model/AppSingleton.swift index 11318c3..098619e 100644 --- a/JustUsed/Model/AppSingleton.swift +++ b/JustUsed/Model/AppSingleton.swift @@ -25,51 +25,52 @@ import Foundation import Cocoa import Contacts +import XCGLogger class AppSingleton { /// Returns true if OS X version is greater than 10.10 - static let isElCapitan = NSProcessInfo.processInfo().operatingSystemVersion.majorVersion == 10 && - NSProcessInfo.processInfo().operatingSystemVersion.minorVersion == 11 + static let aboveYosemite = ProcessInfo.processInfo.operatingSystemVersion.majorVersion == 10 && + ProcessInfo.processInfo.operatingSystemVersion.minorVersion >= 11 /// The contact store used to fetch contacts (to fill out calendar events). /// Is nil if we can't / are not allowed to access it. /// If allowed, the object is not nil and should be cast to a CNContactStore (only for /// el capitan and above). - static private(set) var contactStore: AnyObject? = AppSingleton.initiateContactsRequest() + static fileprivate(set) var contactStore: AnyObject? = AppSingleton.initiateContactsRequest() static let log = AppSingleton.createLog() - static private(set) var logsURL = NSURL() + static fileprivate(set) var logsURL: URL? /// Ref to filemanager for convenience - static let fileManager = NSFileManager.defaultManager() + static let fileManager = FileManager.default static func createLog() -> XCGLogger { let dateFormat = "Y'-'MM'-'d'T'HH':'mm':'ssZ" // date format for string appended to log - let dateFormatter = NSDateFormatter() + let dateFormatter = DateFormatter() dateFormatter.dateFormat = dateFormat - let appString = dateFormatter.stringFromDate(NSDate()) + let appString = dateFormatter.string(from: Date()) var firstLine: String = "Log directory succesfully created / present" - let tempURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSBundle.mainBundle().bundleIdentifier!) + let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(Bundle.main.bundleIdentifier!) do { - try NSFileManager.defaultManager().createDirectoryAtURL(tempURL, withIntermediateDirectories: true, attributes: nil) + try FileManager.default.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil) } catch { firstLine = "Error creating log directory: \(error)" } AppSingleton.logsURL = tempURL - let logFilePath = tempURL.URLByAppendingPathComponent("XCGLog_\(appString).log") - let newLog = XCGLogger.defaultInstance() - newLog.setup(.Debug, showThreadName: true, showLogLevel: true, showFileNames: true, showLineNumbers: true, writeToFile: logFilePath, fileLogLevel: .Debug) + let logFilePath = tempURL.appendingPathComponent("XCGLog_\(appString).log") + let newLog = XCGLogger.default + newLog.setup(level: .debug, showThreadName: true, showLevel: true, showFileNames: true, showLineNumbers: true, writeToFile: logFilePath, fileLevel: .debug) newLog.debug(firstLine) return newLog } /// Returns true if we are on el capitan (or another supported platform) /// and we can access the user's contacts - private static func initiateContactsRequest() -> AnyObject? { + fileprivate static func initiateContactsRequest() -> AnyObject? { if #available(OSX 10.11, *) { let store = CNContactStore() - store.requestAccessForEntityType(.Contacts) { + store.requestAccess(for: .contacts) { granted, error in if let err = error { AppSingleton.log.error("Error while accessing contact store:\n\(err)") @@ -81,4 +82,4 @@ class AppSingleton { } } -} \ No newline at end of file +} diff --git a/JustUsed/Model/CalendarTracker.swift b/JustUsed/Model/CalendarTracker.swift index e66520f..f96e06f 100644 --- a/JustUsed/Model/CalendarTracker.swift +++ b/JustUsed/Model/CalendarTracker.swift @@ -25,58 +25,41 @@ import Foundation import EventKit -/// Implementers of this method give ways of asynchronously getting a list events -/// and a means to submit new ones (e.g. DiMe). -protocol CalendarHistoryDelegate: class { - - /// Asynchronously retrieves all CalendarEvents currently saved in the external history (e.g. currently in dime) - func fetchCalendarEvents(block: [CalendarEvent] -> Void) - - /// Asynchronously submits a new event to the history (e.g. send to dime). - /// (Must make sure that this was not retrieved eariler in fetchCalendarEvents). - func sendCalendarEvent(newEvent: CalendarEvent, successBlock: Void -> Void) -} - /// Tracks calendars event this way: it checks for events that are stored in the calendar /// ±24 hours from now. Sends all of them, if not in the calendar exclusion list, to dime, updating /// old events. Repeats this procedure every hour, or every time the calendar is modified externally. /// Also does a check 90 seconds from initialisation. -public class CalendarTracker { +open class CalendarTracker { /// How often we look for events - public static let kInterval: NSTimeInterval = 60 * 60 // one hour + open static let kInterval: TimeInterval = 60 * 60 // one hour /// When looking for events in the past, cover this time interval - public static let kLookBack: NSTimeInterval = 24 * 60 * 60 // 24 hours back + open static let kLookBack: TimeInterval = 24 * 60 * 60 // 24 hours back /// When looking for events in the future, cover this time interval - public static let kLookAhead: NSTimeInterval = 24 * 60 * 60 // 24 hours ahead + open static let kLookAhead: TimeInterval = 24 * 60 * 60 // 24 hours ahead - private let store = EKEventStore() + fileprivate let store = EKEventStore() /// If the user granted access to the calendar, this becomes true - private(set) var hasAccess: Bool = false - - /// Where are new events fetched from or sent - var calendarDelegate: CalendarHistoryDelegate + fileprivate(set) var hasAccess: Bool = false /// Creates a new calendar tracker, which uses the given object to fetch / update /// calendar events. - init(calendarDelegate: CalendarHistoryDelegate) { - self.calendarDelegate = calendarDelegate - store.requestAccessToEntityType(.Event) { + init() { + store.requestAccess(to: .event) { (granted, error) in if granted { self.hasAccess = true // check calendar when an event is modified - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.submitCurrentEvents(_:)), name: EKEventStoreChangedNotification, object: self.store) + NotificationCenter.default.addObserver(self, selector: #selector(self.submitCurrentEvents(_:)), name: NSNotification.Name.EKEventStoreChanged, object: self.store) // check calendar regularly - NSTimer.scheduledTimerWithTimeInterval(CalendarTracker.kInterval, target: self, selector: #selector(self.submitCurrentEvents(_:)), userInfo: nil, repeats: true) + Timer.scheduledTimer(timeInterval: CalendarTracker.kInterval, target: self, selector: #selector(self.submitCurrentEvents(_:)), userInfo: nil, repeats: true) // fetch the current events in 90 seconds, to allow dime to come online and the user to set preferences - let callTime = dispatch_time(DISPATCH_TIME_NOW, - Int64(90 * Double(NSEC_PER_SEC))) - dispatch_after(callTime, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) { + let callTime = DispatchTime.now() + Double(Int64(90 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.global(qos: DispatchQoS.QoSClass.background).asyncAfter(deadline: callTime) { self.submitCurrentEvents(nil) } } @@ -94,7 +77,7 @@ public class CalendarTracker { func calendarNames() -> [String]? { if hasAccess { var retVal = [String]() - for cal in store.calendarsForEntityType(.Event) { + for cal in store.calendars(for: .event) { retVal.append("\(cal.source.title).\(cal.title)") } return retVal @@ -105,8 +88,8 @@ public class CalendarTracker { /// Sets the exclude value for a given calendar. Returns an updated exclude list (only /// (calendars which actually exist can be excluded). Updates NSUserDefaults. - func setExcludeValue(exclude exclude: Bool, calendar: String) -> [String] { - var currentExcludes = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefExcludeCalendars) as! [String] + func setExcludeValue(exclude: Bool, calendar: String) -> [String] { + var currentExcludes = UserDefaults.standard.value(forKey: JustUsedConstants.prefExcludeCalendars) as! [String] let _calendars = calendarNames() guard let calendars = _calendars else { return [] @@ -119,8 +102,8 @@ public class CalendarTracker { } } else { if currentExcludes.contains(calendar) { - let i = currentExcludes.indexOf(calendar)! - currentExcludes.removeAtIndex(i) + let i = currentExcludes.index(of: calendar)! + currentExcludes.remove(at: i) } } @@ -132,8 +115,8 @@ public class CalendarTracker { } } - NSUserDefaults.standardUserDefaults().setValue(actualExcludes, forKey: JustUsedConstants.prefExcludeCalendars) - NSUserDefaults.standardUserDefaults().synchronize() + UserDefaults.standard.setValue(actualExcludes, forKey: JustUsedConstants.prefExcludeCalendars) + UserDefaults.standard.synchronize() return actualExcludes } @@ -142,7 +125,7 @@ public class CalendarTracker { /// calendar is excluded /// Returns nil if there are no valid calendars func getExcludeCalendars() -> [String: Bool]? { - let currentExcludes = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefExcludeCalendars) as! [String] + let currentExcludes = UserDefaults.standard.value(forKey: JustUsedConstants.prefExcludeCalendars) as! [String] let _calendars = calendarNames() guard let calendars = _calendars else { return nil @@ -157,32 +140,31 @@ public class CalendarTracker { /// Submits calendar events to dime. /// - parameter dataMine: if true, looks for all possible events two years before and after now. - func submitEvents(dataMine dataMine: Bool = false) { + func submitEvents(dataMine: Bool = false) { guard let excludeCalendars = getExcludeCalendars() else { return } - let sinceDate: NSDate - let thenDate: NSDate + let sinceDate: Date + let thenDate: Date if dataMine { - sinceDate = NSDate().yearOffset(-2) - thenDate = NSDate().yearOffset(2) + sinceDate = Date().yearOffset(-2) + thenDate = Date().yearOffset(2) } else { - let sinceTime: NSTimeInterval = CalendarTracker.kLookBack - let thenTime: NSTimeInterval = CalendarTracker.kLookAhead - sinceDate = NSDate().dateByAddingTimeInterval(-sinceTime) - thenDate = NSDate().dateByAddingTimeInterval(+thenTime) + let sinceTime: TimeInterval = CalendarTracker.kLookBack + let thenTime: TimeInterval = CalendarTracker.kLookAhead + sinceDate = Date().addingTimeInterval(-sinceTime) + thenDate = Date().addingTimeInterval(+thenTime) } - var fetchCalendars = store.calendarsForEntityType(.Event) + var fetchCalendars = store.calendars(for: .event) fetchCalendars = fetchCalendars.filter({excludeCalendars[$0.compositeName] == false}) - let predicate = store.predicateForEventsWithStartDate(sinceDate, endDate: thenDate, calendars: fetchCalendars) - let allEvents = store.eventsMatchingPredicate(predicate) + let predicate = store.predicateForEvents(withStart: sinceDate, end: thenDate, calendars: fetchCalendars) + let allEvents = store.events(matching: predicate) for event in allEvents { let calEvent = CalendarEvent(fromEKEvent: event) - calendarDelegate.sendCalendarEvent(calEvent) { - } + DiMePusher.sendToDiMe(calEvent) } } @@ -190,8 +172,8 @@ public class CalendarTracker { /// Submits events which just-passed. Current event is defined as the latest event that started from kLookBack until kLookAhead. /// - parameter hitObject: Whatever is calling this (notification or timer) - @objc private func submitCurrentEvents(hitObject: AnyObject?) { + @objc fileprivate func submitCurrentEvents(_ hitObject: AnyObject?) { submitEvents() } -} \ No newline at end of file +} diff --git a/JustUsed/Model/ChromeHistoryFetcher.swift b/JustUsed/Model/ChromeHistoryFetcher.swift index 2a889e6..a39e799 100644 --- a/JustUsed/Model/ChromeHistoryFetcher.swift +++ b/JustUsed/Model/ChromeHistoryFetcher.swift @@ -27,15 +27,15 @@ import Cocoa class ChromeHistoryFetcher: BrowserHistoryFetcher { - private(set) var lastHistoryEntry: NSDate - var lastDBFileUpdate: NSDate + fileprivate(set) var lastHistoryEntry: Date + var lastDBFileUpdate: Date let browserType: BrowserType = .Safari required init?() { // initializes dates and performs first history check to update them - lastHistoryEntry = NSDate() - lastDBFileUpdate = NSDate.distantPast() // Initialise to be as early as possible. + lastHistoryEntry = Date() + lastDBFileUpdate = Date.distantPast // Initialise to be as early as possible. // If no valid urls exist, fail initialization if getDBURLs().count == 0 { @@ -43,51 +43,51 @@ class ChromeHistoryFetcher: BrowserHistoryFetcher { } // initialization succeeded, do first history check - historyCheck() + _ = historyCheck() } - func getNewHistoryItemsFromDB(dbPath: String) -> [BrowserHistItem] { + func getNewHistoryItemsFromDB(_ dbPath: String) -> [BrowserHistItem] { // Perform database read var new_urls = [BrowserHistItem]() let db = FMDatabase(path: dbPath) - db.open() + db?.open() let lastTime = self.lastHistoryEntry.ldapTime let urls_query = "SELECT url, title, last_visit_time FROM urls WHERE last_visit_time > ? ORDER BY last_visit_time asc" - if let urls_result = db.executeQuery(urls_query, withArgumentsInArray: ["\(lastTime)"]) { + if let urls_result = db?.executeQuery(urls_query, withArgumentsIn: ["\(lastTime)"]) { while urls_result.next() { let urls_dict = urls_result.resultDictionary() - let url = urls_dict["url"] as! String - let title = urls_dict["title"] as? String - let visit_time = urls_dict["last_visit_time"] as! Int - let visit_date = NSDate(fromLdapTime: visit_time) + let url = urls_dict!["url"] as! String + let title = urls_dict!["title"] as! String + let visit_time = urls_dict!["last_visit_time"] as! Int + let visit_date = Date(fromLdapTime: visit_time) self.lastHistoryEntry = visit_date let location = LocationSingleton.getCurrentLocation() new_urls.append(BrowserHistItem(browser: .Chrome, date: visit_date, url: url, title: title, location: location)) } } - db.close() + db?.close() return new_urls } /// Chrome implementation: return "History" - func getDBURLs() -> [NSURL] { - let appSupportDir = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0] + func getDBURLs() -> [URL] { + let appSupportDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] - let chromeDefaultDir = appSupportDir.URLByAppendingPathComponent("Google/Chrome/Default") + let chromeDefaultDir = appSupportDir.appendingPathComponent("Google/Chrome/Default") let filenames: [String] = ["History"] - var retVal = [NSURL]() + var retVal = [URL]() for filename in filenames { - retVal.append(chromeDefaultDir.URLByAppendingPathComponent(filename)) + retVal.append(chromeDefaultDir.appendingPathComponent(filename)) } // If History does not exist, assume chrome is not being used - if !AppSingleton.fileManager.fileExistsAtPath(retVal[0].path!) { - return [NSURL]() + if !AppSingleton.fileManager.fileExists(atPath: retVal[0].path) { + return [URL]() } return retVal diff --git a/JustUsed/Model/FirefoxHistoryFetcher.swift b/JustUsed/Model/FirefoxHistoryFetcher.swift index 35dd7b7..8402c38 100644 --- a/JustUsed/Model/FirefoxHistoryFetcher.swift +++ b/JustUsed/Model/FirefoxHistoryFetcher.swift @@ -27,74 +27,74 @@ import Cocoa class FirefoxHistoryFetcher: BrowserHistoryFetcher { - private(set) var lastHistoryEntry: NSDate - var lastDBFileUpdate: NSDate + fileprivate(set) var lastHistoryEntry: Date + var lastDBFileUpdate: Date let browserType: BrowserType = .Safari /// Keeping firefox's db folder here since it changes from user to user - private let dbFolder: NSURL + fileprivate let dbFolder: URL! required init?() { // initializes dates and performs first history check to update them - lastHistoryEntry = NSDate() - lastDBFileUpdate = NSDate.distantPast() // Initialise to be as early as possible. + lastHistoryEntry = Date() + lastDBFileUpdate = Date.distantPast // Initialise to be as early as possible. // if firefox db folder can't be found, fail initialization if let fdbf = FirefoxHistoryFetcher.getFirefoxDBFolder() { dbFolder = fdbf } else { - dbFolder = NSURL() return nil } // initialization succeeded, do first history check - historyCheck() + _ = historyCheck() } - func getNewHistoryItemsFromDB(dbPath: String) -> [BrowserHistItem] { + func getNewHistoryItemsFromDB(_ dbPath: String) -> [BrowserHistItem] { // Perform database read var new_urls = [BrowserHistItem]() let db = FMDatabase(path: dbPath) - db.open() + db?.open() let lastTime = self.lastHistoryEntry.unixTime_μs let visits_query = "SELECT url, title, last_visit_date FROM moz_places WHERE last_visit_date > ? ORDER BY last_visit_date asc" - if let visits_result = db.executeQuery(visits_query, withArgumentsInArray: ["\(lastTime)"]) { + if let visits_result = db?.executeQuery(visits_query, withArgumentsIn: ["\(lastTime)"]) { while visits_result.next() { let visits_dict = visits_result.resultDictionary() - let visit_url = visits_dict["url"] as! String - let visit_title = visits_dict["title"] as? String - let visit_time = visits_dict["last_visit_date"] as! Int - let visit_date = NSDate(fromUnixTime_μs: visit_time) + let visit_url = visits_dict!["url"]! as! String + let visit_title = visits_dict!["title"] as? String + let visit_time = visits_dict!["last_visit_date"] as! Int + let visit_date = Date(fromUnixTime_μs: visit_time) self.lastHistoryEntry = visit_date let location = LocationSingleton.getCurrentLocation() new_urls.append(BrowserHistItem(browser: .Firefox, date: visit_date, url: visit_url, title: visit_title, location: location)) } } - db.close() + db?.close() return new_urls } /// Firefox implementation: return places.sqlite and places.sqlite-wal - func getDBURLs() -> [NSURL] { + func getDBURLs() -> [URL] { let filenames: [String] = ["places.sqlite", "places.sqlite-wal", "places.sqlite-shm"] - var retVal = [NSURL]() + + var retVal = [URL]() for filename in filenames { - retVal.append(dbFolder.URLByAppendingPathComponent(filename)) + retVal.append(dbFolder.appendingPathComponent(filename)) } // If places.sqlite does not exist, assume Firefox is not being used - if !AppSingleton.fileManager.fileExistsAtPath(retVal[0].path!) { - return [NSURL]() + if !AppSingleton.fileManager.fileExists(atPath: retVal[0].path) { + return [URL]() } // filter by keeping only existing paths - retVal = retVal.filter({AppSingleton.fileManager.fileExistsAtPath($0.path!)}) + retVal = retVal.filter({AppSingleton.fileManager.fileExists(atPath: $0.path)}) return retVal } @@ -106,27 +106,27 @@ class FirefoxHistoryFetcher: BrowserHistoryFetcher { /// This is usually ~/Application\ Support/Firefox/Profiles/.default. /// /// - returns: The url in which firefox's files are present, nil if nothing could be found - static func getFirefoxDBFolder() -> NSURL? { - let appSupportDir = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0] - let firefoxProfilesDir = appSupportDir.URLByAppendingPathComponent("Firefox/Profiles") - let profilesEnumerator = NSFileManager.defaultManager().enumeratorAtURL(firefoxProfilesDir, includingPropertiesForKeys: [NSURLContentModificationDateKey, NSURLIsDirectoryKey], options: .SkipsSubdirectoryDescendants, errorHandler: nil) + static func getFirefoxDBFolder() -> URL? { + let appSupportDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + let firefoxProfilesDir = appSupportDir.appendingPathComponent("Firefox/Profiles") + let profilesEnumerator = FileManager.default.enumerator(at: firefoxProfilesDir, includingPropertiesForKeys: [URLResourceKey.contentModificationDateKey, URLResourceKey.isDirectoryKey], options: .skipsSubdirectoryDescendants, errorHandler: nil) - var newestDate = NSDate.distantPast() - var newestURL: NSURL? + var newestDate = Date.distantPast + var newestURL: URL? // cycle through all files in firefox's profiles for element in profilesEnumerator! { - let elURL = element as! NSURL + let elURL = element as! URL var inVal: AnyObject? do { // only consider if it a directory - try elURL.getResourceValue(&inVal, forKey: NSURLIsDirectoryKey) + try (elURL as NSURL).getResourceValue(&inVal, forKey: URLResourceKey.isDirectoryKey) if let isDir = inVal as? Bool { if isDir { // check modification time, if newer than newest set URL - try elURL.getResourceValue(&inVal, forKey: NSURLContentModificationDateKey) - if let fileDate = inVal as? NSDate { - if fileDate.compare(newestDate) == .OrderedDescending { + try (elURL as NSURL).getResourceValue(&inVal, forKey: URLResourceKey.contentModificationDateKey) + if let fileDate = inVal as? Date { + if fileDate.compare(newestDate) == .orderedDescending { newestURL = elURL newestDate = fileDate } @@ -139,4 +139,4 @@ class FirefoxHistoryFetcher: BrowserHistoryFetcher { } return newestURL } -} \ No newline at end of file +} diff --git a/JustUsed/Model/GenericBrowserHistory.swift b/JustUsed/Model/GenericBrowserHistory.swift index 0ab05e5..5324175 100644 --- a/JustUsed/Model/GenericBrowserHistory.swift +++ b/JustUsed/Model/GenericBrowserHistory.swift @@ -32,19 +32,19 @@ import CoreLocation protocol BrowserHistoryUpdateDelegate { /// Notifies an update to history items. Passed a parameter is the new history item - func newHistoryItems(newURLs: [BrowserHistItem]) + func newHistoryItems(_ newURLs: [BrowserHistItem]) } /// A new safari history item will be returned as this to the delegate struct BrowserHistItem: Equatable { let browser: BrowserType - let date: NSDate + let date: Date let url: String let title: String? let location: Location? let excludedFromDiMe: Bool - init(browser: BrowserType, date: NSDate, url: String, title: String?, location: Location?) { + init(browser: BrowserType, date: Date, url: String, title: String?, location: Location?) { self.browser = browser self.date = date self.url = url @@ -52,9 +52,9 @@ struct BrowserHistItem: Equatable { self.location = location // Set excluded property if url's domain is in exclude list - if let url = NSURL(string: self.url), domain = url.host { - let excludeDomains = NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefExcludeDomains) as! [String] - let filteredDomains = excludeDomains.filter({domain.rangeOfString($0) != nil}) + if let url = URL(string: self.url), let domain = url.host { + let excludeDomains = UserDefaults.standard.value(forKey: JustUsedConstants.prefExcludeDomains) as! [String] + let filteredDomains = excludeDomains.filter({domain.range(of: $0) != nil}) if filteredDomains.count > 0 { excludedFromDiMe = true } else { @@ -67,7 +67,7 @@ struct BrowserHistItem: Equatable { } func ==(lhs: BrowserHistItem, rhs: BrowserHistItem) -> Bool { - return lhs.url == rhs.url && lhs.date.compare(rhs.date) == NSComparisonResult.OrderedSame && lhs.title == rhs.title + return lhs.url == rhs.url && lhs.date.compare(rhs.date) == ComparisonResult.orderedSame && lhs.title == rhs.title } /// Each history fetcher should represent one of these browser types (e.g. SafariHistoryFetcher represents the BrowserType.Safari enum) @@ -91,43 +91,43 @@ protocol BrowserHistoryFetcher: class { /// The last time an history item was retrieved. History items found in the db with a date newer than this /// are retrieved and this date is updated. - var lastHistoryEntry: NSDate { get } + var lastHistoryEntry: Date { get } /// The last time that the db files on disk were modified. If no change happened since this date, it is /// assumed no new history items exist. - var lastDBFileUpdate: NSDate { get set } + var lastDBFileUpdate: Date { get set } /// Returns paths for all valid database history files for this browser. /// **The first one should be the main database file** (e.g. .db file). If the returned list is empty, /// it is assumed the given browser is not being used. - func getDBURLs() -> [NSURL] + func getDBURLs() -> [URL] /// Returns all history items found in the database which have a date greater than the specified one. /// If the returned list is empty, no new items were found. **Called by extension**. /// Must close db files properly. - func getNewHistoryItemsFromDB(dbPath: String) -> [BrowserHistItem] + func getNewHistoryItemsFromDB(_ dbPath: String) -> [BrowserHistItem] } extension BrowserHistoryFetcher { /// Returns the latest time that a database file was modified - func latestDBTime() -> NSDate { - var dates = [NSDate]() + func latestDBTime() -> Date { + var dates = [Date]() for fileUrl in getDBURLs() { var inVal: AnyObject? do { - try fileUrl.getResourceValue(&inVal, forKey: NSURLContentModificationDateKey) + try (fileUrl as NSURL).getResourceValue(&inVal, forKey: URLResourceKey.contentModificationDateKey) } catch { - AppSingleton.log.error("Something went wrong while reading db file '\(fileUrl.path ?? "")': \(error)") + AppSingleton.log.error("Something went wrong while reading db file '\(fileUrl.path)': \(error)") } - if let fileDate = inVal as? NSDate { + if let fileDate = inVal as? Date { dates.append(fileDate) } } - dates.sortInPlace({ $0.compare($1) == NSComparisonResult.OrderedDescending }) + dates.sort(by: { $0.compare($1) == ComparisonResult.orderedDescending }) return dates[0] } @@ -140,39 +140,39 @@ extension BrowserHistoryFetcher { var allPaths = [String]() // paths will be put here // Create temporary directory and delete previous temporary file (if present) - let tempURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("hiit.JustUsed") + let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("hiit.JustUsed") do { - try fileManager.createDirectoryAtURL(tempURL, withIntermediateDirectories: true, attributes: nil) + try fileManager.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil) //AppSingleton.log.debug("directory created succesfully") } catch { - AppSingleton.log.error("Error while creating temp folder at \(tempURL.path ?? "<>"): \(error)") + AppSingleton.log.error("Error while creating temp folder at \(tempURL.path): \(error)") return nil } // Copy files for dbPathURL in getDBURLs() { let filenameURL = dbPathURL - let tempDataFileURL = tempURL.URLByAppendingPathComponent("\(browserType)_copied_\(filenameURL.lastPathComponent!)") - let tempDataFile = tempDataFileURL.path! + let tempDataFileURL = tempURL.appendingPathComponent("\(browserType)_copied_\(filenameURL.lastPathComponent)") + let tempDataFile = tempDataFileURL.path allPaths.append(tempDataFile) - if fileManager.fileExistsAtPath(tempDataFile) { + if fileManager.fileExists(atPath: tempDataFile) { //AppSingleton.log.debug("file exists already") do { - try fileManager.removeItemAtURL(tempDataFileURL) + try fileManager.removeItem(at: tempDataFileURL) } catch { - AppSingleton.log.error("Error while removing temp file \(tempURL.path ?? "<>"): \(error)") + AppSingleton.log.error("Error while removing temp file \(tempURL.path): \(error)") return nil } } // Copy database - if fileManager.fileExistsAtPath(dbPathURL.path!) { + if fileManager.fileExists(atPath: dbPathURL.path) { do { - try fileManager.copyItemAtURL(dbPathURL, toURL: tempDataFileURL) + try fileManager.copyItem(at: dbPathURL, to: tempDataFileURL) //AppSingleton.log.debug("file created") } catch { - AppSingleton.log.error("Error while copying file \(dbPathURL.path ?? ""): \(error)") + AppSingleton.log.error("Error while copying file \(dbPathURL.path): \(error)") } } } @@ -186,7 +186,7 @@ extension BrowserHistoryFetcher { // We proceed only if the date database was modified is newer than the last date let dbTime = latestDBTime() - if dbTime.compare(lastDBFileUpdate) == NSComparisonResult.OrderedDescending { + if dbTime.compare(lastDBFileUpdate) == ComparisonResult.orderedDescending { lastDBFileUpdate = dbTime } else { // return empty list if nothing was updated @@ -202,7 +202,7 @@ extension BrowserHistoryFetcher { // Remove temporary files for filePath in paths { do { - try AppSingleton.fileManager.removeItemAtPath(filePath) + try AppSingleton.fileManager.removeItem(atPath: filePath) //AppSingleton.log.debug("succesfully removed") } catch { AppSingleton.log.error("Something went wrong while removing old database \(filePath): \(error)") @@ -225,35 +225,35 @@ extension BrowserHistoryFetcher { class BrowserHistoryManager: NSObject { /// Valid BrowserHistoryFetcher will be added to this array and repeatedly asked to retrieve new history items - private var historyFetchers = [BrowserHistoryFetcher]() + fileprivate var historyFetchers = [BrowserHistoryFetcher]() /// Update delegates which are interested in new history items (e.g. dime) are kept in this list. - private var updateDelegates = [BrowserHistoryUpdateDelegate]() + fileprivate var updateDelegates = [BrowserHistoryUpdateDelegate]() /// Checks for an update to the database file(s) every times this amount of seconds passes static let kCheckTimeInterval = 10.0 /// Timer used for checking database file(s) - private lazy var checkTimer: NSTimer = { - return NSTimer(timeInterval: BrowserHistoryManager.kCheckTimeInterval, target: self, selector: #selector(timerFire(_:)), userInfo: nil, repeats: true) + fileprivate lazy var checkTimer: Timer = { + return Timer(timeInterval: BrowserHistoryManager.kCheckTimeInterval, target: self, selector: #selector(timerFire(_:)), userInfo: nil, repeats: true) }() override init() { super.init() - NSRunLoop.currentRunLoop().addTimer(checkTimer, forMode: NSRunLoopCommonModes) + RunLoop.current.add(checkTimer, forMode: RunLoopMode.commonModes) } /// Adds support for a browser fetcher. Can pass initializer directly. - func addFetcher(newFetcher: BrowserHistoryFetcher?) { + func addFetcher(_ newFetcher: BrowserHistoryFetcher?) { if let fetcher = newFetcher { historyFetchers.append(fetcher) } } - @objc func timerFire(timer: NSTimer) { + @objc func timerFire(_ timer: Timer) { var newItems = [BrowserHistItem]() for fetcher in historyFetchers { - newItems.appendContentsOf(fetcher.historyCheck()) + newItems.append(contentsOf: fetcher.historyCheck()) } if newItems.count > 0 { for updateDelegate in updateDelegates { @@ -263,8 +263,8 @@ class BrowserHistoryManager: NSObject { } /// A delegate adds itself using this in order to receive updates regarding new history urls - func addUpdateDelegate(updateDelegate: BrowserHistoryUpdateDelegate) { + func addUpdateDelegate(_ updateDelegate: BrowserHistoryUpdateDelegate) { updateDelegates.append(updateDelegate) } -} \ No newline at end of file +} diff --git a/JustUsed/Model/LocationSingleton.swift b/JustUsed/Model/LocationSingleton.swift index 3ae726b..83bf9c4 100644 --- a/JustUsed/Model/LocationSingleton.swift +++ b/JustUsed/Model/LocationSingleton.swift @@ -28,7 +28,7 @@ import CoreLocation /// Keeps track of the location controller, of which we should have only one instance at a time class LocationSingleton { - private static let _locationController = LocationController() + fileprivate static let _locationController = LocationController() static func getCurrentLocation() -> Location? { return LocationSingleton._locationController.location @@ -50,7 +50,7 @@ class LocationController: NSObject, CLLocationManagerDelegate { let authStat = CLLocationManager.authorizationStatus() switch authStat { - case .Denied, .Restricted: + case .denied, .restricted: authorised = false default: authorised = true @@ -68,12 +68,13 @@ class LocationController: NSObject, CLLocationManagerDelegate { } - func locationManager(manager: CLLocationManager, didUpdateLocations locations: [AnyObject]) { - if let loc = locations[0] as? CLLocation { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + if locations.count >= 1 { + let loc = locations[0] geoMan.getDescription(fromLoc: loc) { describedLocation in self.location = describedLocation } } } -} \ No newline at end of file +} diff --git a/JustUsed/Model/RecentDocumentsTracker.swift b/JustUsed/Model/RecentDocumentsTracker.swift index 373ddfa..7fa0ee5 100644 --- a/JustUsed/Model/RecentDocumentsTracker.swift +++ b/JustUsed/Model/RecentDocumentsTracker.swift @@ -28,7 +28,7 @@ import Cocoa /// New recent document items are represented by this struct struct RecentDocItem: Equatable { /// Date that this item was last accessed - var lastAccessDate: NSDate + var lastAccessDate: Date /// Path of this file on disk let path: String /// Location when this file was last opened, if available @@ -47,7 +47,7 @@ func ==(lhs:RecentDocItem, rhs: RecentDocItem) -> Bool { protocol RecentDocumentUpdateDelegate { /// Tells the delegate that new data is available - func newRecentDocument(newItem: RecentDocItem) + func newRecentDocument(_ newItem: RecentDocItem) } /// This class should be subclassed by all items that find recent documents, such as SpotlightTracker or RecentPlistTracker @@ -55,11 +55,11 @@ class RecentDocumentsTracker: NSObject { internal var recentDocumentUpdateDelegates = [RecentDocumentUpdateDelegate]() - func addRecentDocumentUpdateDelegate(newRecentDocDelegate: RecentDocumentUpdateDelegate) { + func addRecentDocumentUpdateDelegate(_ newRecentDocDelegate: RecentDocumentUpdateDelegate) { recentDocumentUpdateDelegates.append(newRecentDocDelegate) } required override init() { super.init() } -} \ No newline at end of file +} diff --git a/JustUsed/Model/RecentPlistTracker.swift b/JustUsed/Model/RecentPlistTracker.swift index 77fd2da..129ad47 100644 --- a/JustUsed/Model/RecentPlistTracker.swift +++ b/JustUsed/Model/RecentPlistTracker.swift @@ -29,13 +29,13 @@ import Foundation class RecentPlistTracker: RecentDocumentsTracker { /// Convenience file manager - private var fm = NSFileManager.defaultManager() + fileprivate var fm = FileManager.default /// Plists' last modification date is stored in this dictionary - private var allPlists = [NSURL: NSDate]() + fileprivate var allPlists = [URL: Date]() /// Files are tracked every this amount of seconds - private let kPlistCheckTime = 5.0 + fileprivate let kPlistCheckTime = 5.0 required init() { super.init() @@ -46,18 +46,18 @@ class RecentPlistTracker: RecentDocumentsTracker { } // Start timer - let checkTimer = NSTimer(timeInterval: kPlistCheckTime, target: self, selector: #selector(timerHit(_:)), userInfo: nil, repeats: true) - NSRunLoop.currentRunLoop().addTimer(checkTimer, forMode: NSRunLoopCommonModes) + let checkTimer = Timer(timeInterval: kPlistCheckTime, target: self, selector: #selector(timerHit(_:)), userInfo: nil, repeats: true) + RunLoop.current.add(checkTimer, forMode: RunLoopMode.commonModes) } /// Check if any recent document plist has been modified since last time the timer hit - @objc private func timerHit(theTimer: NSTimer) { + @objc fileprivate func timerHit(_ theTimer: Timer) { for tuple in getAllPlists() { // if previous item exists, check date, otherwise add it if let previousModDate = allPlists[tuple.sflUrl] { // check if the just found modification date is more recent than the last found one - if tuple.modDate.compare(previousModDate) == NSComparisonResult.OrderedDescending { + if tuple.modDate.compare(previousModDate) == ComparisonResult.orderedDescending { allPlists[tuple.sflUrl] = tuple.modDate if let newItem = fetchMostRecentDoc(fromFile: tuple.sflUrl, date: tuple.modDate) { for upDel in recentDocumentUpdateDelegates { @@ -81,39 +81,42 @@ class RecentPlistTracker: RecentDocumentsTracker { /// - parameter fromFile: The path of the file on disk /// - parameter date: The date on which the recend document was added /// - returns: A spotlight hist item representing the most recent opened document in the sfl - private func fetchMostRecentDoc(fromFile filePath: NSURL, date: NSDate) -> RecentDocItem? { + fileprivate func fetchMostRecentDoc(fromFile filePath: URL, date: Date) -> RecentDocItem? { - guard let sfl = NSDictionary(contentsOfURL: filePath), - recents = sfl["RecentDocuments"], - objects = recents["CustomListItems"], - mostrecentpossiblebook = objects[0]["Bookmark"] as? NSData, - abookdict = NSURL.resourceValuesForKeys([NSURLPathKey], fromBookmarkData: mostrecentpossiblebook) + guard let sfl = NSDictionary(contentsOf: filePath), + let recents = sfl["RecentDocuments"] as? [String: Any], + let objects = recents["CustomListItems"] as? [[String: Any]], + let mostrecentpossiblebook = objects[0]["Bookmark"] as? Data, + let abookdict = URL.resourceValues(forKeys: [URLResourceKey.pathKey], fromBookmarkData: mostrecentpossiblebook) else { return nil } - let path = abookdict[NSURLPathKey]! as! String - let docUrl = NSURL(fileURLWithPath: path) - let rangeOfLSSharedFileList = filePath.lastPathComponent!.rangeOfString(".LSSharedFileList") - let docSource = filePath.lastPathComponent!.substringToIndex(rangeOfLSSharedFileList!.startIndex) + guard let path = abookdict.path else { + AppSingleton.log.error("Failed to find path from file: \(filePath.path)") + return nil + } + let docUrl = URL(fileURLWithPath: path) + let rangeOfLSSharedFileList = filePath.lastPathComponent.range(of: ".LSSharedFileList") + let docSource = filePath.lastPathComponent.substring(to: rangeOfLSSharedFileList!.lowerBound) let location = LocationSingleton.getCurrentLocation() - return RecentDocItem(lastAccessDate: date, path: docUrl.path!, location: location, mime: docUrl.getMime()!, source: docSource) + return RecentDocItem(lastAccessDate: date, path: docUrl.path, location: location, mime: docUrl.getMime()!, source: docSource) } /// Returns all plist files related to recents documents and their last modification date in a tuple - private func getAllPlists() -> [(sflUrl: NSURL, modDate: NSDate)] { - var retVal = [(sflUrl: NSURL, modDate: NSDate)]() + fileprivate func getAllPlists() -> [(sflUrl: URL, modDate: Date)] { + var retVal = [(sflUrl: URL, modDate: Date)]() - let preferencesDir = NSURL(fileURLWithPath: NSHomeDirectory()).URLByAppendingPathComponent("Library/Preferences") + let preferencesDir = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Preferences") do { - let allPrefs = try fm.contentsOfDirectoryAtURL(preferencesDir, includingPropertiesForKeys: nil, options: NSDirectoryEnumerationOptions()) - let sfls = allPrefs.filter({$0.lastPathComponent!.rangeOfString("LSSharedFileList.plist") != nil}) + let allPrefs = try fm.contentsOfDirectory(at: preferencesDir, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions()) + let sfls = allPrefs.filter({$0.lastPathComponent.range(of: "LSSharedFileList.plist") != nil}) for sfl in sfls { var inVal: AnyObject? do { - try sfl.getResourceValue(&inVal, forKey: NSURLContentModificationDateKey) - if let fileDate = inVal as? NSDate { + try (sfl as NSURL).getResourceValue(&inVal, forKey: URLResourceKey.contentModificationDateKey) + if let fileDate = inVal as? Date { retVal.append((sfl, fileDate)) } } catch let error as NSError { @@ -128,4 +131,4 @@ class RecentPlistTracker: RecentDocumentsTracker { return retVal } -} \ No newline at end of file +} diff --git a/JustUsed/Model/SafariHistoryFetcher.swift b/JustUsed/Model/SafariHistoryFetcher.swift index ed2670b..17703ae 100644 --- a/JustUsed/Model/SafariHistoryFetcher.swift +++ b/JustUsed/Model/SafariHistoryFetcher.swift @@ -30,15 +30,15 @@ import CoreLocation /// Makes a copy for reading if file has been updated. Checks every x seconds (kSafariHistoryCheckTime) class SafariHistoryFetcher: BrowserHistoryFetcher { - private(set) var lastHistoryEntry: NSDate - var lastDBFileUpdate: NSDate + fileprivate(set) var lastHistoryEntry: Date + var lastDBFileUpdate: Date let browserType: BrowserType = .Safari required init?() { // initializes dates and performs first history check to update them - lastHistoryEntry = NSDate() - lastDBFileUpdate = NSDate.distantPast() // Initialise to be as early as possible. + lastHistoryEntry = Date() + lastDBFileUpdate = Date.distantPast // Initialise to be as early as possible. // If no valid urls exist, fail initialization if getDBURLs().count == 0 { @@ -46,60 +46,60 @@ class SafariHistoryFetcher: BrowserHistoryFetcher { } // initialization succeeded, do first history check - historyCheck() + _ = historyCheck() } - func getNewHistoryItemsFromDB(dbPath: String) -> [BrowserHistItem] { + func getNewHistoryItemsFromDB(_ dbPath: String) -> [BrowserHistItem] { // Perform database read var new_urls = [BrowserHistItem]() let db = FMDatabase(path: dbPath) - db.open() + db?.open() let lastTime = self.lastHistoryEntry.timeIntervalSinceReferenceDate as Double let visits_query = "SELECT history_item, visit_time, title FROM history_visits WHERE visit_time > ? ORDER BY visit_time asc" - if let visits_result = db.executeQuery(visits_query, withArgumentsInArray: ["\(lastTime)"]) { + if let visits_result = db?.executeQuery(visits_query, withArgumentsIn: ["\(lastTime)"]) { while visits_result.next() { let visits_dict = visits_result.resultDictionary() - let visit_id = visits_dict["history_item"] as! NSNumber - let visit_title = visits_dict["title"] as? String - let visit_time = visits_dict["visit_time"] as! NSNumber - let visit_date = NSDate(timeIntervalSinceReferenceDate: visit_time as NSTimeInterval) + let visit_id = visits_dict?["history_item"] as! NSNumber + let visit_title = visits_dict?["title"] as? String + let visit_time = visits_dict?["visit_time"] as! NSNumber + let visit_date = Date(timeIntervalSinceReferenceDate: visit_time as! TimeInterval) self.lastHistoryEntry = visit_date let item_query = "SELECT url FROM history_items WHERE id = ?" - let item_result = db.executeQuery(item_query, withArgumentsInArray: [visit_id]) - while item_result.next() { - let item_dict = item_result.resultDictionary() - let item_url = item_dict["url"] as! String + let item_result = db?.executeQuery(item_query, withArgumentsIn: [visit_id]) + while (item_result?.next())! { + let item_dict = item_result?.resultDictionary() + let item_url = item_dict?["url"] as! String let location = LocationSingleton.getCurrentLocation() new_urls.append(BrowserHistItem(browser: .Safari, date: visit_date, url: item_url, title: visit_title, location: location)) } } } - db.close() + db?.close() return new_urls } /// Safari implementation: gets path of both .db, .db-wal and .db-shm files - func getDBURLs() -> [NSURL] { - let safariLibURL = NSURL(fileURLWithPath: NSHomeDirectory()).URLByAppendingPathComponent("Library/Safari") + func getDBURLs() -> [URL] { + let safariLibURL = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Safari") let filenames: [String] = ["History.db", "History.db-wal", "History.db-shm"] - var retVal = [NSURL]() + var retVal = [URL]() for filename in filenames { - retVal.append(safariLibURL.URLByAppendingPathComponent(filename)) + retVal.append(safariLibURL.appendingPathComponent(filename)) } // If History.db does not exist, assume Safari is not being used - if !AppSingleton.fileManager.fileExistsAtPath(retVal[0].path!) { - return [NSURL]() + if !AppSingleton.fileManager.fileExists(atPath: retVal[0].path) { + return [URL]() } // filter by keeping only existing paths - retVal = retVal.filter({AppSingleton.fileManager.fileExistsAtPath($0.path!)}) + retVal = retVal.filter({AppSingleton.fileManager.fileExists(atPath: $0.path)}) return retVal } -} \ No newline at end of file +} diff --git a/JustUsed/Model/SpotlightDocumentTracker.swift b/JustUsed/Model/SpotlightDocumentTracker.swift index fed9063..76690a4 100644 --- a/JustUsed/Model/SpotlightDocumentTracker.swift +++ b/JustUsed/Model/SpotlightDocumentTracker.swift @@ -34,7 +34,7 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { dynamic var query: NSMetadataQuery? /// Stores all recent documents found. Items are stored in order, so item 0 in this list correponds to the first item found after starting the application - private var allItems = [RecentDocItem]() + fileprivate var allItems = [RecentDocItem]() required init() { @@ -42,18 +42,18 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { query = NSMetadataQuery() - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(queryUpdated(_:)), name: NSMetadataQueryDidUpdateNotification, object: query) + NotificationCenter.default.addObserver(self, selector: #selector(queryUpdated(_:)), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: query) query?.searchScopes = [NSMetadataQueryUserHomeScope] - let startDate = NSDate() + let startDate = Date() let predicateFormat = "kMDItemFSContentChangeDate >= %@" var predicateToRun = NSPredicate(format: predicateFormat, argumentArray: [startDate]) // Now, we don't want to include email messages in the result set, so add in an AND that excludes them let emailExclusionPredicate = NSPredicate(format: "(kMDItemContentType != 'com.apple.mail.emlx') && (kMDItemContentType != 'public.vcard')", argumentArray: nil) - if AppSingleton.isElCapitan { + if AppSingleton.aboveYosemite { // Only look for files that end in .sfl let extensionPredicate = NSPredicate(format: "%K ENDSWITH %@", NSMetadataItemFSNameKey, ".sfl") @@ -82,19 +82,19 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { } query?.predicate = predicateToRun - query?.startQuery() + query?.start() } - @objc func queryUpdated(notification: NSNotification) { - query?.enumerateResultsUsingBlock(updateBlock) + @objc func queryUpdated(_ notification: Notification) { + query?.enumerateResults(updateBlock) } - func updateBlock(input: AnyObject!, index: Int, boolPoint: UnsafeMutablePointer) { + func updateBlock(_ input: Any, index: Int, boolPoint: UnsafeMutablePointer) { let inputVal = input as! NSMetadataItem - guard let path = inputVal.valueForKey(kMDItemPath as String), - date = inputVal.valueForKey(NSMetadataItemFSContentChangeDateKey as String), - newHistItem = fetchMostRecentDoc(fromFile: NSURL(fileURLWithPath: path as! String), - date: date as! NSDate) + guard let path = inputVal.value(forKey: kMDItemPath as String), + let date = inputVal.value(forKey: NSMetadataItemFSContentChangeDateKey as String), + let newHistItem = fetchMostRecentDoc(fromFile: URL(fileURLWithPath: path as! String), + date: date as! Date) else { return } @@ -104,12 +104,12 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { } allItems.append(newHistItem) } else { - let previousItemIndex = allItems.indexOf(newHistItem)! + let previousItemIndex = allItems.index(of: newHistItem)! // Only re-add items if first time that it was opened was before kMinSeconds from now - let shiftedDate = NSDate().dateByAddingTimeInterval(-kMinSeconds) + let shiftedDate = Date().addingTimeInterval(-kMinSeconds) let previousDate = allItems[previousItemIndex].lastAccessDate - if shiftedDate.compare(previousDate) == NSComparisonResult.OrderedDescending { - allItems[previousItemIndex].lastAccessDate = NSDate() + if shiftedDate.compare(previousDate as Date) == ComparisonResult.orderedDescending { + allItems[previousItemIndex].lastAccessDate = Date() for delegate in recentDocumentUpdateDelegates { delegate.newRecentDocument(newHistItem) } @@ -122,9 +122,9 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { /// - parameter fromFile: The path of the sfl file on disk /// - parameter date: The date on which the recend document was added /// - returns: A spotlight hist item representing the most recent opened document in the sfl - private func fetchMostRecentDoc(fromFile filePath: NSURL, date: NSDate) -> RecentDocItem? { + fileprivate func fetchMostRecentDoc(fromFile filePath: URL, date: Date) -> RecentDocItem? { - guard let sfl = NSDictionary(contentsOfURL: filePath), objects = sfl["$objects"] as? [AnyObject] else { + guard let sfl = NSDictionary(contentsOf: filePath), let objects = sfl["$objects"] as? [AnyObject] else { return nil } @@ -133,7 +133,7 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { var i = 0 while i < objects.count { // seek order (int) which comes before bookmark - if let odict = objects[i] as? NSDictionary, order = odict["order"] { + if let odict = objects[i] as? NSDictionary, let order = odict["order"] { if let cnt = order as? Int { // seek bookmark and associate it to previously found order @@ -141,9 +141,9 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { i += 1 - if let possiblebook = objects[i] as? NSData, abookdict = NSURL.resourceValuesForKeys([NSURLPathKey], fromBookmarkData: possiblebook) { + if let possiblebook = objects[i] as? Data, let abookdict = URL.resourceValues(forKeys: [URLResourceKey.pathKey], fromBookmarkData: possiblebook) { - tuples.append((count: cnt, path: abookdict[NSURLPathKey]! as! String)) + tuples.append((count: cnt, path: abookdict.path!)) break } } @@ -153,15 +153,15 @@ class SpotlightDocumentTracker: RecentDocumentsTracker { } if tuples.count > 0 { // sort tuples by count in ascending order, take first item - tuples = tuples.sort {$0.0 < $1.0} + tuples = tuples.sorted {$0.0 < $1.0} // most recent document URL - let docUrl = NSURL(fileURLWithPath: tuples[0].path) + let docUrl = URL(fileURLWithPath: tuples[0].path) // get application name by removing extension - let docSource = filePath.URLByDeletingPathExtension!.lastPathComponent! + let docSource = filePath.deletingPathExtension().lastPathComponent let location = LocationSingleton.getCurrentLocation() - return RecentDocItem(lastAccessDate: date, path: docUrl.path!, location: location, mime: docUrl.getMime()!, source: docSource) + return RecentDocItem(lastAccessDate: date, path: docUrl.path, location: location, mime: docUrl.getMime()!, source: docSource) } else { return nil } } -} \ No newline at end of file +} diff --git a/JustUsed/REST/CrossRefSession.swift b/JustUsed/REST/CrossRefSession.swift new file mode 100644 index 0000000..ffdda2a --- /dev/null +++ b/JustUsed/REST/CrossRefSession.swift @@ -0,0 +1,49 @@ +// +// Copyright (c) 2015 Aalto University +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +import Foundation + +class CrossRefSession { + fileprivate static let urlSession = URLSession(configuration: .default) + + /// Fetches metedata for a given doi using CrossRef, and calls a callback with the result + /// (nil if failed) + static func fetch(doi: String, callback: @escaping (JSON?) -> Void) { + guard let url = URL(string: "http://api.crossref.org/works/\(doi)") else { + AppSingleton.log.error("Error while creating crossref url") + callback(nil) + return + } + let urlRequest = URLRequest(url: url, timeoutInterval: 10) + urlSession.dataTask(with: urlRequest) { + data, response, error in + if let data = data, error == nil { + callback(JSON(data: data)) + } else { + callback(nil) + AppSingleton.log.error("Failed to fetch crossref data for \(doi): \(error!)") + } + }.resume() + } +} diff --git a/JustUsed/REST/DiMePusher.swift b/JustUsed/REST/DiMePusher.swift new file mode 100644 index 0000000..8a1b286 --- /dev/null +++ b/JustUsed/REST/DiMePusher.swift @@ -0,0 +1,85 @@ +// +// Copyright (c) 2015 Aalto University +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +import Foundation + +/// Points to an endpoint, with the associated string used in the url for the request +enum DiMeEndpoint: String { + case Event = "event" + case InformationElement = "informationelement" +} + +/// This class is used as a convenience to snd data to dime +class DiMePusher { + + /// Send the given data to dime + /// - parameter callback: When done calls the callback where the first parameter is a boolean (true if successful) and + /// the second the id of the returned item (nil if couldn't be found, or operation failed) + static func sendToDiMe(_ dimeData: DiMeBase, callback: ((Bool, Int?) -> Void)? = nil) { + guard DiMeSession.dimeAvailable else { + callback?(false, nil) + return + } + + let endPoint: DiMeEndpoint + switch dimeData { + case is Event: + endPoint = .Event + case is DocumentInformationElement: + endPoint = .InformationElement + default: + return + } + + do { + // attempt to translate json + let options = JSONSerialization.WritingOptions.prettyPrinted + + try JSONSerialization.data(withJSONObject: dimeData.getDict(), options: options) + + // assume json conversion was a success, hence send to dime + let server_url = DiMeSession.dimeUrl + DiMeSession.push(urlString: server_url + "/data/\(endPoint.rawValue)", jsonDict: dimeData.getDict()) { + json, _ in + if let json = json { + if let error = json["error"].string { + AppSingleton.log.error("DiMe reply to submission contains error:\n\(error)") + if let message = json["message"].string { + AppSingleton.log.error("DiMe's error message:\n\(message)") + } + callback?(false, nil) + } else { + // assume submission was a success, call callback (if any) with returned id + callback?(true, json["id"].int) + } + } + } + } catch { + AppSingleton.log.error("Error while serializing json - no data sent:\n\(error)") + callback?(false, nil) + } + + } + +} diff --git a/JustUsed/REST/DiMeSession.swift b/JustUsed/REST/DiMeSession.swift new file mode 100644 index 0000000..94658f2 --- /dev/null +++ b/JustUsed/REST/DiMeSession.swift @@ -0,0 +1,236 @@ +// +// Copyright (c) 2015 Aalto University +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +import Foundation + +enum RESTError: Error { + case invalidUrl + case notFound + /// We were asked to block the main thread, which is invalid. + case waitOnMain + /// Error otherwise undefined (check dime logs) + case dimeError(String) +} + +/// Contains configurations for the DiMe API using the native macOS URL Loading System +class DiMeSession { + + /// Is true if there is a connection to DiMe, and can be used + private(set) static var dimeAvailable: Bool = false { didSet { + NotificationCenter.default.post(name: JustUsedConstants.diMeConnectionNotification, object: self, userInfo: ["available": dimeAvailable]) + } } + + /// Returns dime server url + static var dimeUrl: String = { + return UserDefaults.standard.object(forKey: JustUsedConstants.prefDiMeServerURL) as! String + }() + + /// Returns HTTP headers used for DiMe connection + static var dimeHeaders: [String: String] { get { + let user: String = UserDefaults.standard.object(forKey: JustUsedConstants.prefDiMeServerUserName) as! String + let password: String = UserDefaults.standard.object(forKey: JustUsedConstants.prefDiMeServerPassword) as! String + + let credentialData = "\(user):\(password)".data(using: String.Encoding.utf8)! + let base64Credentials = credentialData.base64EncodedString(options: []) + + return ["Authorization": "Basic \(base64Credentials)"] + } } + + /// Shared url session used to push / fetch data + fileprivate static var sharedSession: URLSession = URLSession(configuration: getConfiguration()) { willSet { + sharedSession.finishTasksAndInvalidate() + } } + + /// Updates the configuration (in case username and password change, for example) + static func getConfiguration() -> URLSessionConfiguration { + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = DiMeSession.dimeHeaders + configuration.timeoutIntervalForRequest = 4 // seconds + configuration.timeoutIntervalForResource = 4 + return configuration + } + + /// Fetches a given URL using dime and calls back the given function with a json (if successful). + /// Calls back with an error, if an error was given. + static func fetch(urlString: String, callback: @escaping (JSON?, Error?) -> Void) { + guard let url = URL(string: urlString) else { + callback(nil, RESTError.invalidUrl) + return + } + DiMeSession.sharedSession.dataTask(with: url) { + data, response, error in + if let data = data, error == nil { + callback(JSON(data: data), nil) + } else if let error = error { + callback(nil, error) + } else { + callback(nil, nil) + } + }.resume() + } + + /// Fetches a given URL using dime and calls back the given function with a json (if successful). + /// Calls back with an error, if an error was given. + /// - Attention: Do not call from main thread + static func fetch_sync(urlString: String) -> (json: JSON?, error: Error?) { + + guard !Thread.isMainThread else { + AppSingleton.log.error("Called from main thread, exiting") + return (nil, RESTError.waitOnMain) + } + + guard let url = URL(string: urlString) else { + return(nil, RESTError.invalidUrl) + } + + var retVal: (JSON?, Error?) = (nil, nil) + let dGroup = DispatchGroup() + + dGroup.enter() + DiMeSession.sharedSession.dataTask(with: url) { + data, response, error in + if let data = data, error == nil { + retVal = (JSON(data: data), nil) + } else if let error = error { + retVal = (nil, error) + } else { + retVal = (nil, nil) + } + dGroup.leave() + }.resume() + + if dGroup.wait(timeout: DispatchTime.now() + 10.0) == .timedOut { + AppSingleton.log.error("Synchronous request fetch timeout") + } + + return retVal + } + + /// Pushes a given dictionary (representing a json entry) to dime. + /// Calls back the callback with the response from dime (which should mirror the pushed data). + static func push(urlString: String, jsonDict: [String: Any], callback: @escaping (JSON?, Error?) -> Void) { + guard let url = URL(string: urlString) else { + callback(false, RESTError.invalidUrl) + return + } + do { + var urlRequest = URLRequest(url: url, timeoutInterval: 5.0) + urlRequest.httpMethod = "POST" + urlRequest.httpBody = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted) + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") + DiMeSession.sharedSession.dataTask(with: urlRequest) { + data, _, error in + if let error = error { + AppSingleton.log.error("Error while uploading json: \(error)") + callback(nil, error) + } else if let data = data { + callback(JSON(data: data), nil) + } else { + callback(nil, nil) + } + }.resume() + } catch { + AppSingleton.log.error("Failed to convert to json: \(error)") + } + } + + /// **Synchronously** submits a delete http request for the given url. + /// Returns a non-nil error in case operation didn't succeed. + /// - Attention: do not call from the main thread. + @discardableResult + static func delete_sync(urlString: String) -> Error? { + guard !Thread.isMainThread else { + AppSingleton.log.error("Called from main thread, exiting") + return RESTError.waitOnMain + } + + guard let url = URL(string: urlString) else { + return RESTError.invalidUrl + } + + var urlRequest = URLRequest(url: url, timeoutInterval: 5.0) + urlRequest.httpMethod = "DELETE" + + let dGroup = DispatchGroup() + + var returnedError: Error? = nil + + dGroup.enter() + DiMeSession.sharedSession.dataTask(with: urlRequest) { + _, response, error in + if let foundError = error { + returnedError = foundError + } else { + if let httpResponse = response as? HTTPURLResponse { + if httpResponse.statusCode != 204 { + returnedError = RESTError.dimeError("Code \(httpResponse.statusCode)") + } + } else { + AppSingleton.log.error("Failed to convert url response to http url response") + } + } + dGroup.leave() + }.resume() + + if dGroup.wait(timeout: DispatchTime.now() + 10.0) == .timedOut { + AppSingleton.log.error("Synchronous request fetch timeout") + } + + return returnedError + } + + /// Attempts to connect to dime. Sends a notification if we succeeded / failed. + /// Also calls the given callback with a boolean (which is true if operation succeeded). + static func dimeConnect(_ callback: ((Bool, Error?) -> Void)? = nil) { + + let server_url = DiMeSession.dimeUrl + + DiMeSession.fetch(urlString: server_url + "/ping") { + json, error in + if let json = json, error == nil, let response = json["message"].string, response == "pong" { + dimeAvailable = true + callback?(true, nil) + } else { + var returnedError: Error? = nil + // connection failed + if let error = error { + AppSingleton.log.error("Error while connecting to (pinging) DiMe. Error message:\n\(error)") + returnedError = error + } else if let jsonError = json?["error"].string { + AppSingleton.log.error("DiMe Connection error: \(jsonError)") + returnedError = RESTError.dimeError(jsonError) + } else { + AppSingleton.log.error("Error while connecting to (pinging) DiMe. No error returned.") + } + callback?(false, returnedError) + dimeAvailable = false + } + } + } + + static func dimeDisconnect() { + dimeAvailable = false + } +} diff --git a/JustUsed/UI/DiMePreferencesViewController.swift b/JustUsed/UI/DiMePreferencesViewController.swift index 407c91a..e6915e2 100644 --- a/JustUsed/UI/DiMePreferencesViewController.swift +++ b/JustUsed/UI/DiMePreferencesViewController.swift @@ -41,27 +41,27 @@ class DiMePreferencesViewController: NSViewController { /// Create view and programmatically set-up bindings override func viewDidLoad() { super.viewDidLoad() - calendarExcludeTable.setDataSource(calendarExcludeDelegate) - calendarExcludeTable.setDelegate(calendarExcludeDelegate) + calendarExcludeTable.dataSource = calendarExcludeDelegate + calendarExcludeTable.delegate = calendarExcludeDelegate - let options: [String: AnyObject] = ["NSContinuouslyUpdatesValue": true] + let options: [String: AnyObject] = ["NSContinuouslyUpdatesValue": true as AnyObject] - urlField.bind("value", toObject: NSUserDefaultsController.sharedUserDefaultsController(), withKeyPath: "values." + JustUsedConstants.prefDiMeServerURL, options: options) + urlField.bind("value", to: NSUserDefaultsController.shared(), withKeyPath: "values." + JustUsedConstants.prefDiMeServerURL, options: options) - usernameField.bind("value", toObject: NSUserDefaultsController.sharedUserDefaultsController(), withKeyPath: "values." + JustUsedConstants.prefDiMeServerUserName, options: options) + usernameField.bind("value", to: NSUserDefaultsController.shared(), withKeyPath: "values." + JustUsedConstants.prefDiMeServerUserName, options: options) - passwordField.bind("value", toObject: NSUserDefaultsController.sharedUserDefaultsController(), withKeyPath: "values." + JustUsedConstants.prefDiMeServerPassword, options: options) + passwordField.bind("value", to: NSUserDefaultsController.shared(), withKeyPath: "values." + JustUsedConstants.prefDiMeServerPassword, options: options) // the following will set // (PeyeConstants.prefSendEventOnFocusSwitch) // to optional int 0 when off and nonzero (1) when on - sendPlainTextCell.bind("value", toObject: NSUserDefaultsController.sharedUserDefaultsController(), withKeyPath: "values." + JustUsedConstants.prefSendPlainTexts, options: options) + sendPlainTextCell.bind("value", to: NSUserDefaultsController.shared(), withKeyPath: "values." + JustUsedConstants.prefSendPlainTexts, options: options) // similar set here // Browser history disabled in favour of extension // sendSafariHistCell.bind("value", toObject: NSUserDefaultsController.sharedUserDefaultsController(), withKeyPath: "values." + JustUsedConstants.prefSendSafariHistory, options: options) - logsPathLabel.stringValue = AppSingleton.logsURL.path ?? "" + logsPathLabel.stringValue = AppSingleton.logsURL?.path ?? "" } @@ -70,54 +70,54 @@ class DiMePreferencesViewController: NSViewController { @IBOutlet weak var domainsTable: NSTableView! @IBOutlet weak var newDomainField: NSTextField! - @IBAction func removeButtonPress(sender: NSButton) { + @IBAction func removeButtonPress(_ sender: NSButton) { if domainsTable.selectedRow != -1 { - userDefaultsAC.removeObjectAtArrangedObjectIndex(domainsTable.selectedRow) + userDefaultsAC.remove(atArrangedObjectIndex: domainsTable.selectedRow) } } - @IBAction func addButtonPress(sender: NSButton) { - let newVal = newDomainField.stringValue.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + @IBAction func addButtonPress(_ sender: NSButton) { + let newVal = newDomainField.stringValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if newVal.characters.count > 0 { let cont = userDefaultsAC.content as! [String] - if cont.indexOf(newVal) == nil { + if cont.index(of: newVal) == nil { userDefaultsAC.addObject(newVal) } newDomainField.stringValue = "" } } - @IBAction func calendarDataMine(sender: NSButton) { + @IBAction func calendarDataMine(_ sender: NSButton) { let myAl = NSAlert() myAl.messageText = "Attention: this will fetch ALL events, ± 2 years from now (unless they belong to an excluded calendar). Are you sure?" - myAl.addButtonWithTitle("Yes") - myAl.addButtonWithTitle("No") - myAl.beginSheetModalForWindow(self.view.window!) { + myAl.addButton(withTitle: "Yes") + myAl.addButton(withTitle: "No") + myAl.beginSheetModal(for: self.view.window!, completionHandler: { response in if response == NSAlertFirstButtonReturn { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) { - let appDel = NSApplication.sharedApplication().delegate! as! AppDelegate + DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async { + let appDel = NSApplication.shared().delegate! as! AppDelegate appDel.calendarTracker.submitEvents(dataMine: true) } } - } + }) } } class CalendarExcludeDelegate: NSObject, NSTableViewDataSource, NSTableViewDelegate { let calendarTracker: CalendarTracker = { - let appDel = NSApplication.sharedApplication().delegate! as! AppDelegate + let appDel = NSApplication.shared().delegate! as! AppDelegate return appDel.calendarTracker }() - @objc func numberOfRowsInTableView(tableView: NSTableView) -> Int { + @objc func numberOfRows(in tableView: NSTableView) -> Int { return calendarTracker.calendarNames()!.count } - @objc func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? { + @objc func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { if tableColumn!.identifier == "calExclTableCheck" { let cal = calendarTracker.calendarNames()![row] return calendarTracker.getExcludeCalendars()![cal] @@ -126,10 +126,10 @@ class CalendarExcludeDelegate: NSObject, NSTableViewDataSource, NSTableViewDeleg } } - @objc func tableView(tableView: NSTableView, setObjectValue object: AnyObject?, forTableColumn tableColumn: NSTableColumn?, row: Int) { + @objc func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { if tableColumn!.identifier == "calExclTableCheck" { let exclude = object! as! Bool - calendarTracker.setExcludeValue(exclude: exclude, calendar: calendarTracker.calendarNames()![row]) + _ = calendarTracker.setExcludeValue(exclude: exclude, calendar: calendarTracker.calendarNames()![row]) tableView.reloadData() } } diff --git a/JustUsed/UI/ViewController.swift b/JustUsed/UI/ViewController.swift index d0e1716..aa8fcd8 100644 --- a/JustUsed/UI/ViewController.swift +++ b/JustUsed/UI/ViewController.swift @@ -51,7 +51,7 @@ class ViewController: NSViewController, RecentDocumentUpdateDelegate, BrowserHis } override func viewWillAppear() { - if (NSUserDefaults.standardUserDefaults().valueForKey(JustUsedConstants.prefSendSafariHistory) as! Bool) { + if (UserDefaults.standard.value(forKey: JustUsedConstants.prefSendSafariHistory) as! Bool) { fileTableWidth.constant = fileTableWidthForBoth } else { fileTableWidth.constant = self.view.bounds.width - 40 @@ -59,36 +59,36 @@ class ViewController: NSViewController, RecentDocumentUpdateDelegate, BrowserHis } override func viewDidAppear() { - NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(dimeConnectionChanged(_:)), name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) + NotificationCenter.default.addObserver(self, selector: #selector(dimeConnectionChanged(_:)), name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) updateDiMeStatus() - fileTable.setDataSource(spotlightSource) - browserTable.setDataSource(browserSource) + fileTable.dataSource = spotlightSource + browserTable.dataSource = browserSource } override func viewDidDisappear() { - NSNotificationCenter.defaultCenter().removeObserver(self, name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) + NotificationCenter.default.removeObserver(self, name: JustUsedConstants.diMeConnectionNotification, object: HistoryManager.sharedManager) } /// Must call this function to set-up delegates and data sources - func setSources(spotlightSource: RecentDocDataSource, browserSource: BrowserTrackerDataSource) { + func setSources(_ spotlightSource: RecentDocDataSource, browserSource: BrowserTrackerDataSource) { self.spotlightSource = spotlightSource self.browserSource = browserSource } - func newRecentDocument(newItem: RecentDocItem) { + func newRecentDocument(_ newItem: RecentDocItem) { spotlightSource!.addData(newItem) fileTable?.reloadData() } - func newHistoryItems(newURLs: [BrowserHistItem]) { + func newHistoryItems(_ newURLs: [BrowserHistItem]) { browserSource!.insertNewData(newURLs) browserTable?.reloadData() } /// Checks dime status and updates view accordingly - private func updateDiMeStatus() { - if HistoryManager.sharedManager.isDiMeAvailable() { + fileprivate func updateDiMeStatus() { + if DiMeSession.dimeAvailable { statusImage.image = NSImage(named: "NSStatusAvailable") statusButton.tag = kTagDisconnect statusButton.title = "Disconnect" @@ -101,20 +101,22 @@ class ViewController: NSViewController, RecentDocumentUpdateDelegate, BrowserHis } } - @IBAction func connectButtonPress(sender: NSButton) { + @IBAction func connectButtonPress(_ sender: NSButton) { if sender.tag == kTagDisconnect { - HistoryManager.sharedManager.dimeDisconnect() + HistoryManager.forceDisconnect = true + DiMeSession.dimeDisconnect() } else if sender.tag == kTagConnect { - HistoryManager.sharedManager.dimeConnect() + HistoryManager.forceDisconnect = false + DiMeSession.dimeConnect() } } - @objc private func dimeConnectionChanged(notification: NSNotification) { + @objc fileprivate func dimeConnectionChanged(_ notification: Notification) { updateDiMeStatus() } - @IBAction func quitButtonPress(sender: NSButton) { - let delegate = NSApplication.sharedApplication().delegate! as! AppDelegate + @IBAction func quitButtonPress(_ sender: NSButton) { + let delegate = NSApplication.shared().delegate! as! AppDelegate delegate.quit() } } @@ -124,7 +126,7 @@ class BrowserTrackerDataSource: NSObject, NSTableViewDataSource { var allHistory = [BrowserHistItem]() // Insert data avoiding duplicates - func insertNewData(newURLs: [BrowserHistItem]) { + func insertNewData(_ newURLs: [BrowserHistItem]) { for newUrl in newURLs { if !allHistory.contains(newUrl) { allHistory.append(newUrl) @@ -133,15 +135,15 @@ class BrowserTrackerDataSource: NSObject, NSTableViewDataSource { } /// MARK: - Table data source - func numberOfRowsInTableView(aTableView: NSTableView) -> Int { + func numberOfRows(in aTableView: NSTableView) -> Int { return allHistory.count } - func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? { + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { if tableColumn!.identifier == JustUsedConstants.kBHistoryDate { let date = allHistory[row].date - return date.descriptionWithLocale(NSLocale.currentLocale()) + return date.description(with: Locale.current) } else if tableColumn!.identifier == JustUsedConstants.kBHistoryBrowser { return allHistory[row].browser.rawValue } else if tableColumn!.identifier == JustUsedConstants.kBHistoryTitle { @@ -173,8 +175,8 @@ class RecentDocDataSource: NSObject, NSTableViewDataSource { var locations = [String]() var mimes = [String]() - func addData(newItem: RecentDocItem) { - lutimes.append(newItem.lastAccessDate.descriptionWithLocale(NSLocale.currentLocale())) + func addData(_ newItem: RecentDocItem) { + lutimes.append(newItem.lastAccessDate.description(with: Locale.current)) lupaths.append(newItem.path) sources.append(newItem.source) if let locString = newItem.location?.descriptionLine { @@ -187,11 +189,11 @@ class RecentDocDataSource: NSObject, NSTableViewDataSource { /// MARK: Static table data source - func numberOfRowsInTableView(aTableView: NSTableView) -> Int { + func numberOfRows(in aTableView: NSTableView) -> Int { return lutimes.count } - func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? { + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { if tableColumn!.identifier == JustUsedConstants.kLastUsedDateTitle { return lutimes[row] } else if tableColumn!.identifier == JustUsedConstants.kPathTitle { @@ -207,4 +209,4 @@ class RecentDocDataSource: NSObject, NSTableViewDataSource { } } -} \ No newline at end of file +} diff --git a/JustUsed/Utils/JustUsedConstants.swift b/JustUsed/Utils/JustUsedConstants.swift index 5c92dd6..01bf05a 100644 --- a/JustUsed/Utils/JustUsedConstants.swift +++ b/JustUsed/Utils/JustUsedConstants.swift @@ -69,7 +69,7 @@ class JustUsedConstants { /// String notifying that something changed in the dime connection. /// Calls to HistoryManager can verify what is the current status of dime /// using isDimeAvailable(). - static let diMeConnectionNotification = "hiit.JustUsed.diMeConnectionChange" + static let diMeConnectionNotification = Notification.Name("hiit.JustUsed.diMeConnectionChange") // MARK: - General constants @@ -87,11 +87,11 @@ class JustUsedConstants { // MARK: - Static functions - private static func makeDateFormatter() -> NSDateFormatter { - let dateFormatter = NSDateFormatter() + fileprivate static func makeDateFormatter() -> DateFormatter { + let dateFormatter = DateFormatter() dateFormatter.dateFormat = JustUsedConstants.diMeDateFormat return dateFormatter } } - \ No newline at end of file + diff --git a/JustUsedTests/JustUsedTests.swift b/JustUsedTests/JustUsedTests.swift index a5c6bb4..b875fcf 100644 --- a/JustUsedTests/JustUsedTests.swift +++ b/JustUsedTests/JustUsedTests.swift @@ -44,7 +44,7 @@ class JustUsedTests: XCTestCase { func testPerformanceExample() { // This is an example of a performance test case. - self.measureBlock() { + self.measure() { // Put the code you want to measure the time of here. } } diff --git a/SwiftyJSON b/SwiftyJSON new file mode 160000 index 0000000..dadbfcf --- /dev/null +++ b/SwiftyJSON @@ -0,0 +1 @@ +Subproject commit dadbfcffd5f51e2b488a26e83f188d96755e5393 diff --git a/SwiftyJSON.swift b/SwiftyJSON.swift deleted file mode 100755 index 61423a3..0000000 --- a/SwiftyJSON.swift +++ /dev/null @@ -1,1343 +0,0 @@ -// SwiftyJSON.swift -// -// Copyright (c) 2014 Ruoyu Fu, Pinglin Tang -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation - -// MARK: - Error - -///Error domain -public let ErrorDomain: String! = "SwiftyJSONErrorDomain" - -///Error code -public let ErrorUnsupportedType: Int! = 999 -public let ErrorIndexOutOfBounds: Int! = 900 -public let ErrorWrongType: Int! = 901 -public let ErrorNotExist: Int! = 500 - -// MARK: - JSON Type - -/** -JSON's type definitions. - -See http://tools.ietf.org/html/rfc7231#section-4.3 -*/ -public enum Type :Int{ - - case Number - case String - case Bool - case Array - case Dictionary - case Null - case Unknown -} - -// MARK: - JSON Base - -public struct JSON { - - /** - Creates a JSON using the data. - - - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary - - parameter opt: The JSON serialization reading options. `.AllowFragments` by default. - - parameter error: error The NSErrorPointer used to return the error. `nil` by default. - - - returns: The created JSON - */ - public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) { - do { - let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt) - self.init(object) - } catch let aError as NSError { - if error != nil { - error.memory = aError - } - self.init(NSNull()) - } - } - - /** - Creates a JSON using the object. - - - parameter object: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. - - - returns: The created JSON - */ - public init(_ object: AnyObject) { - self.object = object - } - - /** - Creates a JSON from a [JSON] - - - parameter jsonArray: A Swift array of JSON objects - - - returns: The created JSON - */ - public init(_ jsonArray:[JSON]) { - self.init(jsonArray.map { $0.object }) - } - - /** - Creates a JSON from a [String: JSON] - - :param: jsonDictionary A Swift dictionary of JSON objects - - :returns: The created JSON - */ - public init(_ jsonDictionary:[String: JSON]) { - var dictionary = [String: AnyObject]() - for (key, json) in jsonDictionary { - dictionary[key] = json.object - } - self.init(dictionary) - } - - /// Private object - private var rawArray: [AnyObject] = [] - private var rawDictionary: [String : AnyObject] = [:] - private var rawString: String = "" - private var rawNumber: NSNumber = 0 - private var rawNull: NSNull = NSNull() - /// Private type - private var _type: Type = .Null - /// prviate error - private var _error: NSError? = nil - - /// Object in JSON - public var object: AnyObject { - get { - switch self.type { - case .Array: - return self.rawArray - case .Dictionary: - return self.rawDictionary - case .String: - return self.rawString - case .Number: - return self.rawNumber - case .Bool: - return self.rawNumber - default: - return self.rawNull - } - } - set { - _error = nil - switch newValue { - case let number as NSNumber: - if number.isBool { - _type = .Bool - } else { - _type = .Number - } - self.rawNumber = number - case let string as String: - _type = .String - self.rawString = string - case _ as NSNull: - _type = .Null - case let array as [AnyObject]: - _type = .Array - self.rawArray = array - case let dictionary as [String : AnyObject]: - _type = .Dictionary - self.rawDictionary = dictionary - default: - _type = .Unknown - _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"]) - } - } - } - - /// json type - public var type: Type { get { return _type } } - - /// Error in JSON - public var error: NSError? { get { return self._error } } - - /// The static null json - @available(*, unavailable, renamed="null") - public static var nullJSON: JSON { get { return null } } - public static var null: JSON { get { return JSON(NSNull()) } } -} - -// MARK: - CollectionType, SequenceType, Indexable -extension JSON : Swift.CollectionType, Swift.SequenceType, Swift.Indexable { - - public typealias Generator = JSONGenerator - - public typealias Index = JSONIndex - - public var startIndex: JSON.Index { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.rawArray.startIndex) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex) - default: - return JSONIndex() - } - } - - public var endIndex: JSON.Index { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.rawArray.endIndex) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex) - default: - return JSONIndex() - } - } - - public subscript (position: JSON.Index) -> JSON.Generator.Element { - switch self.type { - case .Array: - return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!])) - case .Dictionary: - let (key, value) = self.rawDictionary[position.dictionaryIndex!] - return (key, JSON(value)) - default: - return ("", JSON.null) - } - } - - /// If `type` is `.Array` or `.Dictionary`, return `array.empty` or `dictonary.empty` otherwise return `false`. - public var isEmpty: Bool { - get { - switch self.type { - case .Array: - return self.rawArray.isEmpty - case .Dictionary: - return self.rawDictionary.isEmpty - default: - return true - } - } - } - - /// If `type` is `.Array` or `.Dictionary`, return `array.count` or `dictonary.count` otherwise return `0`. - public var count: Int { - switch self.type { - case .Array: - return self.rawArray.count - case .Dictionary: - return self.rawDictionary.count - default: - return 0 - } - } - - public func underestimateCount() -> Int { - switch self.type { - case .Array: - return self.rawArray.underestimateCount() - case .Dictionary: - return self.rawDictionary.underestimateCount() - default: - return 0 - } - } - - /** - If `type` is `.Array` or `.Dictionary`, return a generator over the elements like `Array` or `Dictionary`, otherwise return a generator over empty. - - - returns: Return a *generator* over the elements of JSON. - */ - public func generate() -> JSON.Generator { - return JSON.Generator(self) - } -} - -public struct JSONIndex: ForwardIndexType, _Incrementable, Equatable, Comparable { - - let arrayIndex: Int? - let dictionaryIndex: DictionaryIndex? - - let type: Type - - init(){ - self.arrayIndex = nil - self.dictionaryIndex = nil - self.type = .Unknown - } - - init(arrayIndex: Int) { - self.arrayIndex = arrayIndex - self.dictionaryIndex = nil - self.type = .Array - } - - init(dictionaryIndex: DictionaryIndex) { - self.arrayIndex = nil - self.dictionaryIndex = dictionaryIndex - self.type = .Dictionary - } - - public func successor() -> JSONIndex { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.arrayIndex!.successor()) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor()) - default: - return JSONIndex() - } - } -} - -public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex == rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex == rhs.dictionaryIndex - default: - return false - } -} - -public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex < rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex < rhs.dictionaryIndex - default: - return false - } -} - -public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex <= rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex <= rhs.dictionaryIndex - default: - return false - } -} - -public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex >= rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex >= rhs.dictionaryIndex - default: - return false - } -} - -public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex > rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex > rhs.dictionaryIndex - default: - return false - } -} - -public struct JSONGenerator : GeneratorType { - - public typealias Element = (String, JSON) - - private let type: Type - private var dictionayGenerate: DictionaryGenerator? - private var arrayGenerate: IndexingGenerator<[AnyObject]>? - private var arrayIndex: Int = 0 - - init(_ json: JSON) { - self.type = json.type - if type == .Array { - self.arrayGenerate = json.rawArray.generate() - }else { - self.dictionayGenerate = json.rawDictionary.generate() - } - } - - public mutating func next() -> JSONGenerator.Element? { - switch self.type { - case .Array: - if let o = self.arrayGenerate!.next() { - return (String(self.arrayIndex++), JSON(o)) - } else { - return nil - } - case .Dictionary: - if let (k, v): (String, AnyObject) = self.dictionayGenerate!.next() { - return (k, JSON(v)) - } else { - return nil - } - default: - return nil - } - } -} - -// MARK: - Subscript - -/** -* To mark both String and Int can be used in subscript. -*/ -public protocol JSONSubscriptType {} - -extension Int: JSONSubscriptType {} - -extension String: JSONSubscriptType {} - -extension JSON { - - /// If `type` is `.Array`, return json which's object is `array[index]`, otherwise return null json with error. - private subscript(index index: Int) -> JSON { - get { - if self.type != .Array { - var r = JSON.null - r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"]) - return r - } else if index >= 0 && index < self.rawArray.count { - return JSON(self.rawArray[index]) - } else { - var r = JSON.null - r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"]) - return r - } - } - set { - if self.type == .Array { - if self.rawArray.count > index && newValue.error == nil { - self.rawArray[index] = newValue.object - } - } - } - } - - /// If `type` is `.Dictionary`, return json which's object is `dictionary[key]` , otherwise return null json with error. - private subscript(key key: String) -> JSON { - get { - var r = JSON.null - if self.type == .Dictionary { - if let o = self.rawDictionary[key] { - r = JSON(o) - } else { - r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"]) - } - } else { - r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"]) - } - return r - } - set { - if self.type == .Dictionary && newValue.error == nil { - self.rawDictionary[key] = newValue.object - } - } - } - - /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. - private subscript(sub sub: JSONSubscriptType) -> JSON { - get { - if sub is String { - return self[key:sub as! String] - } else { - return self[index:sub as! Int] - } - } - set { - if sub is String { - self[key:sub as! String] = newValue - } else { - self[index:sub as! Int] = newValue - } - } - } - - /** - Find a json in the complex data structuresby using the Int/String's array. - - - parameter path: The target json's path. Example: - - let json = JSON[data] - let path = [9,"list","person","name"] - let name = json[path] - - The same as: let name = json[9]["list"]["person"]["name"] - - - returns: Return a json found by the path or a null json with error - */ - public subscript(path: [JSONSubscriptType]) -> JSON { - get { - return path.reduce(self) { $0[sub: $1] } - } - set { - switch path.count { - case 0: - return - case 1: - self[sub:path[0]].object = newValue.object - default: - var aPath = path; aPath.removeAtIndex(0) - var nextJSON = self[sub: path[0]] - nextJSON[aPath] = newValue - self[sub: path[0]] = nextJSON - } - } - } - - /** - Find a json in the complex data structuresby using the Int/String's array. - - - parameter path: The target json's path. Example: - - let name = json[9,"list","person","name"] - - The same as: let name = json[9]["list"]["person"]["name"] - - - returns: Return a json found by the path or a null json with error - */ - public subscript(path: JSONSubscriptType...) -> JSON { - get { - return self[path] - } - set { - self[path] = newValue - } - } -} - -// MARK: - LiteralConvertible - -extension JSON: Swift.StringLiteralConvertible { - - public init(stringLiteral value: StringLiteralType) { - self.init(value) - } - - public init(extendedGraphemeClusterLiteral value: StringLiteralType) { - self.init(value) - } - - public init(unicodeScalarLiteral value: StringLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.IntegerLiteralConvertible { - - public init(integerLiteral value: IntegerLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.BooleanLiteralConvertible { - - public init(booleanLiteral value: BooleanLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.FloatLiteralConvertible { - - public init(floatLiteral value: FloatLiteralType) { - self.init(value) - } -} - -extension JSON: Swift.DictionaryLiteralConvertible { - - public init(dictionaryLiteral elements: (String, AnyObject)...) { - self.init(elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in - var d = dictionary - d[element.0] = element.1 - return d - }) - } -} - -extension JSON: Swift.ArrayLiteralConvertible { - - public init(arrayLiteral elements: AnyObject...) { - self.init(elements) - } -} - -extension JSON: Swift.NilLiteralConvertible { - - public init(nilLiteral: ()) { - self.init(NSNull()) - } -} - -// MARK: - Raw - -extension JSON: Swift.RawRepresentable { - - public init?(rawValue: AnyObject) { - if JSON(rawValue).type == .Unknown { - return nil - } else { - self.init(rawValue) - } - } - - public var rawValue: AnyObject { - return self.object - } - - public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData { - return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt) - } - - public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? { - switch self.type { - case .Array, .Dictionary: - do { - let data = try self.rawData(options: opt) - return NSString(data: data, encoding: encoding) as? String - } catch _ { - return nil - } - case .String: - return self.rawString - case .Number: - return self.rawNumber.stringValue - case .Bool: - return self.rawNumber.boolValue.description - case .Null: - return "null" - default: - return nil - } - } -} - -// MARK: - Printable, DebugPrintable - -extension JSON: Swift.Printable, Swift.DebugPrintable { - - public var description: String { - if let string = self.rawString(options:.PrettyPrinted) { - return string - } else { - return "unknown" - } - } - - public var debugDescription: String { - return description - } -} - -// MARK: - Array - -extension JSON { - - //Optional [JSON] - public var array: [JSON]? { - get { - if self.type == .Array { - return self.rawArray.map{ JSON($0) } - } else { - return nil - } - } - } - - //Non-optional [JSON] - public var arrayValue: [JSON] { - get { - return self.array ?? [] - } - } - - //Optional [AnyObject] - public var arrayObject: [AnyObject]? { - get { - switch self.type { - case .Array: - return self.rawArray - default: - return nil - } - } - set { - if let array = newValue { - self.object = array - } else { - self.object = NSNull() - } - } - } -} - -// MARK: - Dictionary - -extension JSON { - - //Optional [String : JSON] - public var dictionary: [String : JSON]? { - if self.type == .Dictionary { - return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, AnyObject)) -> [String : JSON] in - var d = dictionary - d[element.0] = JSON(element.1) - return d - } - } else { - return nil - } - } - - //Non-optional [String : JSON] - public var dictionaryValue: [String : JSON] { - return self.dictionary ?? [:] - } - - //Optional [String : AnyObject] - public var dictionaryObject: [String : AnyObject]? { - get { - switch self.type { - case .Dictionary: - return self.rawDictionary - default: - return nil - } - } - set { - if let v = newValue { - self.object = v - } else { - self.object = NSNull() - } - } - } -} - -// MARK: - Bool - -extension JSON: Swift.BooleanType { - - //Optional bool - public var bool: Bool? { - get { - switch self.type { - case .Bool: - return self.rawNumber.boolValue - default: - return nil - } - } - set { - if newValue != nil { - self.object = NSNumber(bool: newValue!) - } else { - self.object = NSNull() - } - } - } - - //Non-optional bool - public var boolValue: Bool { - get { - switch self.type { - case .Bool, .Number, .String: - return self.object.boolValue - default: - return false - } - } - set { - self.object = NSNumber(bool: newValue) - } - } -} - -// MARK: - String - -extension JSON { - - //Optional string - public var string: String? { - get { - switch self.type { - case .String: - return self.object as? String - default: - return nil - } - } - set { - if newValue != nil { - self.object = NSString(string:newValue!) - } else { - self.object = NSNull() - } - } - } - - //Non-optional string - public var stringValue: String { - get { - switch self.type { - case .String: - return self.object as! String - case .Number: - return self.object.stringValue - case .Bool: - return (self.object as! Bool).description - default: - return "" - } - } - set { - self.object = NSString(string:newValue) - } - } -} - -// MARK: - Number -extension JSON { - - //Optional number - public var number: NSNumber? { - get { - switch self.type { - case .Number, .Bool: - return self.rawNumber - default: - return nil - } - } - set { - self.object = newValue ?? NSNull() - } - } - - //Non-optional number - public var numberValue: NSNumber { - get { - switch self.type { - case .String: - let scanner = NSScanner(string: self.object as! String) - if scanner.scanDouble(nil){ - if (scanner.atEnd) { - return NSNumber(double:(self.object as! NSString).doubleValue) - } - } - return NSNumber(double: 0.0) - case .Number, .Bool: - return self.object as! NSNumber - default: - return NSNumber(double: 0.0) - } - } - set { - self.object = newValue - } - } -} - -//MARK: - Null -extension JSON { - - public var null: NSNull? { - get { - switch self.type { - case .Null: - return self.rawNull - default: - return nil - } - } - set { - self.object = NSNull() - } - } -} - -//MARK: - URL -extension JSON { - - //Optional URL - public var URL: NSURL? { - get { - switch self.type { - case .String: - if let encodedString_ = self.rawString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) { - return NSURL(string: encodedString_) - } else { - return nil - } - default: - return nil - } - } - set { - self.object = newValue?.absoluteString ?? NSNull() - } - } -} - -// MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 - -extension JSON { - - public var double: Double? { - get { - return self.number?.doubleValue - } - set { - if newValue != nil { - self.object = NSNumber(double: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var doubleValue: Double { - get { - return self.numberValue.doubleValue - } - set { - self.object = NSNumber(double: newValue) - } - } - - public var float: Float? { - get { - return self.number?.floatValue - } - set { - if newValue != nil { - self.object = NSNumber(float: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var floatValue: Float { - get { - return self.numberValue.floatValue - } - set { - self.object = NSNumber(float: newValue) - } - } - - public var int: Int? { - get { - return self.number?.longValue - } - set { - if newValue != nil { - self.object = NSNumber(integer: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var intValue: Int { - get { - return self.numberValue.integerValue - } - set { - self.object = NSNumber(integer: newValue) - } - } - - public var uInt: UInt? { - get { - return self.number?.unsignedLongValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedLong: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uIntValue: UInt { - get { - return self.numberValue.unsignedLongValue - } - set { - self.object = NSNumber(unsignedLong: newValue) - } - } - - public var int8: Int8? { - get { - return self.number?.charValue - } - set { - if newValue != nil { - self.object = NSNumber(char: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int8Value: Int8 { - get { - return self.numberValue.charValue - } - set { - self.object = NSNumber(char: newValue) - } - } - - public var uInt8: UInt8? { - get { - return self.number?.unsignedCharValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedChar: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt8Value: UInt8 { - get { - return self.numberValue.unsignedCharValue - } - set { - self.object = NSNumber(unsignedChar: newValue) - } - } - - public var int16: Int16? { - get { - return self.number?.shortValue - } - set { - if newValue != nil { - self.object = NSNumber(short: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int16Value: Int16 { - get { - return self.numberValue.shortValue - } - set { - self.object = NSNumber(short: newValue) - } - } - - public var uInt16: UInt16? { - get { - return self.number?.unsignedShortValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedShort: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt16Value: UInt16 { - get { - return self.numberValue.unsignedShortValue - } - set { - self.object = NSNumber(unsignedShort: newValue) - } - } - - public var int32: Int32? { - get { - return self.number?.intValue - } - set { - if newValue != nil { - self.object = NSNumber(int: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int32Value: Int32 { - get { - return self.numberValue.intValue - } - set { - self.object = NSNumber(int: newValue) - } - } - - public var uInt32: UInt32? { - get { - return self.number?.unsignedIntValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedInt: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt32Value: UInt32 { - get { - return self.numberValue.unsignedIntValue - } - set { - self.object = NSNumber(unsignedInt: newValue) - } - } - - public var int64: Int64? { - get { - return self.number?.longLongValue - } - set { - if newValue != nil { - self.object = NSNumber(longLong: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var int64Value: Int64 { - get { - return self.numberValue.longLongValue - } - set { - self.object = NSNumber(longLong: newValue) - } - } - - public var uInt64: UInt64? { - get { - return self.number?.unsignedLongLongValue - } - set { - if newValue != nil { - self.object = NSNumber(unsignedLongLong: newValue!) - } else { - self.object = NSNull() - } - } - } - - public var uInt64Value: UInt64 { - get { - return self.numberValue.unsignedLongLongValue - } - set { - self.object = NSNumber(unsignedLongLong: newValue) - } - } -} - -//MARK: - Comparable -extension JSON : Swift.Comparable {} - -public func ==(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber == rhs.rawNumber - case (.String, .String): - return lhs.rawString == rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): - return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): - return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): - return true - default: - return false - } -} - -public func <=(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber <= rhs.rawNumber - case (.String, .String): - return lhs.rawString <= rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): - return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): - return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): - return true - default: - return false - } -} - -public func >=(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber >= rhs.rawNumber - case (.String, .String): - return lhs.rawString >= rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): - return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): - return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): - return true - default: - return false - } -} - -public func >(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber > rhs.rawNumber - case (.String, .String): - return lhs.rawString > rhs.rawString - default: - return false - } -} - -public func <(lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.Number, .Number): - return lhs.rawNumber < rhs.rawNumber - case (.String, .String): - return lhs.rawString < rhs.rawString - default: - return false - } -} - -private let trueNumber = NSNumber(bool: true) -private let falseNumber = NSNumber(bool: false) -private let trueObjCType = String.fromCString(trueNumber.objCType) -private let falseObjCType = String.fromCString(falseNumber.objCType) - -// MARK: - NSNumber: Comparable - -extension NSNumber: Swift.Comparable { - var isBool:Bool { - get { - let objCType = String.fromCString(self.objCType) - if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType) - || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){ - return true - } else { - return false - } - } - } -} - -public func ==(lhs: NSNumber, rhs: NSNumber) -> Bool { - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) == NSComparisonResult.OrderedSame - } -} - -public func !=(lhs: NSNumber, rhs: NSNumber) -> Bool { - return !(lhs == rhs) -} - -public func <(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) == NSComparisonResult.OrderedAscending - } -} - -public func >(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) == NSComparisonResult.OrderedDescending - } -} - -public func <=(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) != NSComparisonResult.OrderedDescending - } -} - -public func >=(lhs: NSNumber, rhs: NSNumber) -> Bool { - - switch (lhs.isBool, rhs.isBool) { - case (false, true): - return false - case (true, false): - return false - default: - return lhs.compare(rhs) != NSComparisonResult.OrderedAscending - } -} diff --git a/XCGLogger b/XCGLogger new file mode 160000 index 0000000..31aa5c1 --- /dev/null +++ b/XCGLogger @@ -0,0 +1 @@ +Subproject commit 31aa5c1dd068f6550772e34ba01d667a1b5ff2de