diff --git a/NEWS.md b/NEWS.md index a792a24..1cd90c6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# 1.7.0 + +- Added checks for network availability + # 1.4.0 - Use Notification Center vs alerts for everything but super-fatal errors diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index 657c8f8..a450f32 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -30,14 +30,14 @@ 4F000B9DE332C7D2340B760411896249 /* Pods-RSwitch-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-RSwitch-umbrella.h"; sourceTree = ""; }; 574603E2D7597AEEB79587D7C3889AA1 /* Just.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Just.xcconfig; sourceTree = ""; }; 5A65C21E7E55D2FB16F3F2767B0063C7 /* Just-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Just-dummy.m"; sourceTree = ""; }; - 5D52FFF81A1E26634DAD54B2BE7CBA99 /* Pods_RSwitch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_RSwitch.framework; path = "Pods-RSwitch.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5D52FFF81A1E26634DAD54B2BE7CBA99 /* Pods_RSwitch.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RSwitch.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 614B6A9B3F94B8D815819AA921D78D95 /* Just-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Just-prefix.pch"; sourceTree = ""; }; 6848EDAB880F7E2D2B180B0D25F67504 /* Just.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Just.modulemap; sourceTree = ""; }; 6B076A7E6A9514F7BC8853C054606452 /* Just-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Just-umbrella.h"; sourceTree = ""; }; 6B5F1D02B43D15E9D5752784305E1A5D /* Pods-RSwitch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-RSwitch.debug.xcconfig"; sourceTree = ""; }; 7B194431FBE08FAA54D43A7D8C27124C /* Pods-RSwitch-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-RSwitch-Info.plist"; sourceTree = ""; }; - 9A212383B73C76EDF23FB6126214CAB4 /* Just.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Just.framework; path = Just.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9A212383B73C76EDF23FB6126214CAB4 /* Just.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Just.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; ACB677C58AE332368BBEB1AF0A6918B1 /* Pods-RSwitch-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-RSwitch-acknowledgements.plist"; sourceTree = ""; }; B39AAB51B4D348DDE0A772879510BD57 /* Pods-RSwitch-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-RSwitch-frameworks.sh"; sourceTree = ""; }; B8F0FBF810ECE65109C95E0C8BBB893F /* Just.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Just.swift; path = Sources/Just/Just.swift; sourceTree = ""; }; @@ -131,7 +131,6 @@ B8F0FBF810ECE65109C95E0C8BBB893F /* Just.swift */, 46872DBB8C34D63DE630A92868138E93 /* Support Files */, ); - name = Just; path = Just; sourceTree = ""; }; @@ -228,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1100; - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1120; }; buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 10.0"; @@ -236,6 +235,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; productRefGroup = 623DD1C81238D9DB831E4CF39661DBBE /* Products */; @@ -363,7 +363,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 574603E2D7597AEEB79587D7C3889AA1 /* Just.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -461,7 +460,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 574603E2D7597AEEB79587D7C3889AA1 /* Just.xcconfig */; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -500,7 +498,6 @@ baseConfigurationReference = D722B0A61F22FDFBDEF11F24016B4D1D /* Pods-RSwitch.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -540,7 +537,6 @@ baseConfigurationReference = 6B5F1D02B43D15E9D5752784305E1A5D /* Pods-RSwitch.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; diff --git a/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Just.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Just.xcscheme index 35289a1..d9f5e24 100644 --- a/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Just.xcscheme +++ b/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Just.xcscheme @@ -1,17 +1,17 @@ + buildForArchiving = "YES" + buildForAnalyzing = "YES"> - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + - - + debugDocumentVersioning = "YES"> diff --git a/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Pods-RSwitch.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Pods-RSwitch.xcscheme index 0bee195..27dd05b 100644 --- a/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Pods-RSwitch.xcscheme +++ b/Pods/Pods.xcodeproj/xcuserdata/hrbrmstr.xcuserdatad/xcschemes/Pods-RSwitch.xcscheme @@ -1,6 +1,6 @@ - + - + @@ -839,29 +839,47 @@ DQ - + - -RSwitch v1.5.1 + +Cg + + + + + + + + + RSwitch v1.6.0 Copyright © 2019 Bob Rudis MIT Licensed - - + + + + + + + +Cg + + + - + @@ -872,7 +890,7 @@ MIT Licensed Cgo - + @@ -883,7 +901,7 @@ String+Version by DragonCherry ProcInfo by Patrick Wardle Just by Daniel Duan - + diff --git a/RSwitch/Info.plist b/RSwitch/Info.plist index 628a752..b2af5c6 100644 --- a/RSwitch/Info.plist +++ b/RSwitch/Info.plist @@ -25,7 +25,7 @@ LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright © 2019 Bob Rudis. All rights reserved. MIT Licensed + Copyright © 2020 Bob Rudis. All rights reserved. MIT Licensed NSMainStoryboardFile Main NSPrincipalClass diff --git a/RSwitch/Swift/AppDelegate.swift b/RSwitch/Swift/AppDelegate.swift index 551f659..c08d6c0 100644 --- a/RSwitch/Swift/AppDelegate.swift +++ b/RSwitch/Swift/AppDelegate.swift @@ -27,10 +27,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSToolbarDelegate { } @objc func performRStudioCheck(_ sender: NSObject) { - let v = RStudioUtils.latestVersionNumber() - if (!Preferences.lastVersionNotified.isVersion(equalTo: v)) { - Preferences.lastVersionNotified = v - notifyUser(title: "New RStudio Daily version available", text: ("Version: " + v)) + if (currentReachabilityStatus != .notReachable) { + let v = RStudioUtils.latestVersionNumber() + if (!Preferences.lastVersionNotified.isVersion(equalTo: v)) { + Preferences.lastVersionNotified = v + notifyUserWithDL(title: "New RStudio Daily version available", text: ("Version: " + v)) + } } } @@ -73,7 +75,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSToolbarDelegate { } DockIcon.standard.setVisibility(Preferences.showDockIcon) - + } func applicationDidFinishLaunching(_ aNotification: Notification) { @@ -89,7 +91,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSToolbarDelegate { newSessController = (mainStoryboard.instantiateController(withIdentifier: "newSessPanel") as! NSWindowController) sess = RStudioServerSessionManager() - + + FileAssociationUtils.getHandlers(); + FileAssociationUtils.setHandlers(); + FileAssociationUtils.getHandlers(); + timer = Timer.scheduledTimer( timeInterval: 3600, target: self, diff --git a/RSwitch/Swift/Notify.swift b/RSwitch/Swift/Notify.swift index 339f67e..4bf5c70 100644 --- a/RSwitch/Swift/Notify.swift +++ b/RSwitch/Swift/Notify.swift @@ -10,8 +10,27 @@ import Foundation import Cocoa extension AppDelegate : NSUserNotificationCenterDelegate { + -func notifyUser(title: String? = nil, subtitle: String? = nil, text: String? = nil) -> Void { + func notifyUserWithDL(title: String? = nil, subtitle: String? = nil, text: String? = nil) -> Void { + + let notification = NSUserNotification() + + notification.title = title + notification.subtitle = subtitle + notification.informativeText = text + notification.hasActionButton = true + notification.actionButtonTitle = "Download" + + notification.soundName = NSUserNotificationDefaultSoundName + + NSUserNotificationCenter.default.delegate = self + NSUserNotificationCenter.default.deliver(notification) + + } + + + func notifyUser(title: String? = nil, subtitle: String? = nil, text: String? = nil) -> Void { let notification = NSUserNotification() @@ -30,4 +49,8 @@ func notifyUser(title: String? = nil, subtitle: String? = nil, text: String? = n return(true) } + func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { + print(notification) + } + } diff --git a/RSwitch/Swift/RStudioServerSessionWebViewController.swift b/RSwitch/Swift/RStudioServerSessionWebViewController.swift index a95249a..cd5e4ff 100644 --- a/RSwitch/Swift/RStudioServerSessionWebViewController.swift +++ b/RSwitch/Swift/RStudioServerSessionWebViewController.swift @@ -50,6 +50,29 @@ class RstudioServerSessionWebViewController: NSViewController, NSWindowDelegate extension RstudioServerSessionWebViewController: WKUIDelegate { + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + + NSLog("PANELING!") + print("PANELING!") + + + let openPanel = NSOpenPanel() + + openPanel.canChooseFiles = true + openPanel.allowsMultipleSelection = false + openPanel.canChooseDirectories = false + openPanel.canCreateDirectories = false + openPanel.beginSheetModal(for:self.view.window!) { (response) in + if (response == NSApplication.ModalResponse.OK) { + completionHandler([openPanel.url!]) + } else { + completionHandler(nil) + } + openPanel.close() + } + + } + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if navigationAction.targetFrame == nil { diff --git a/RSwitch/Swift/Utils/Associations.swift b/RSwitch/Swift/Utils/Associations.swift new file mode 100644 index 0000000..2e72319 --- /dev/null +++ b/RSwitch/Swift/Utils/Associations.swift @@ -0,0 +1,59 @@ +// +// Associations.swift +// RSwitch +// +// Created by hrbrmstr on 2/11/20. +// Copyright © 2020 Bob Rudis. All rights reserved. +// + +import Foundation +import AppKit +import ApplicationServices + +class FileAssociationUtils { + + public static func getHandlers() { + + let workspace = NSWorkspace.shared; + + let setResR : String = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, "R" as CFString, nil)?.takeRetainedValue() as String? ?? ""; + NSLog("UTI of .R extension: " + setResR); + + let handlerR : String = LSCopyDefaultRoleHandlerForContentType(setResR as CFString, LSRolesMask.all)?.takeRetainedValue() as String? ?? ""; + NSLog("Bundle ID of handler for .R files is: [" + handlerR + "]"); + + let rAppUrl : URL = (workspace.urlForApplication(withBundleIdentifier: handlerR))!.appendingPathComponent("Contents/Info.plist"); + NSLog("The Info.plist for the app that handles .R files is: " + rAppUrl.absoluteString); + + let rAppDict : NSDictionary = NSDictionary(contentsOf: rAppUrl)!; + NSLog("The name of the app that handles .R files is: " + (rAppDict.object(forKey: "CFBundleName") as! String)); + + let setResRmd : String = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, "Rmd" as CFString, nil)?.takeRetainedValue() as String? ?? ""; + NSLog("UTI of .Rmd extension: " + setResRmd); + + let handlerRmd : String = LSCopyDefaultRoleHandlerForContentType(setResRmd as CFString, LSRolesMask.all)?.takeRetainedValue() as String? ?? ""; + NSLog("Bundle ID of handler for .Rmd files is: [" + handlerRmd + "]"); + + let rmdAppUrl : URL = (workspace.urlForApplication(withBundleIdentifier: handlerRmd))!.appendingPathComponent("Contents/Info.plist"); + NSLog("The Info.plist for the app that handles .Rmd files is: " + rmdAppUrl.absoluteString); + + let rmdAppDict : NSDictionary = NSDictionary(contentsOf: rmdAppUrl)!; + NSLog("The name of the app that handles .Rmd files is: " + (rmdAppDict.object(forKey: "CFBundleName") as! String)); + + } + + public static func setHandlers() { + + let setResR : String = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, "R" as CFString, nil)?.takeRetainedValue() as String? ?? ""; + NSLog("UTI of .R extension: " + setResR); + + LSSetDefaultRoleHandlerForContentType(setResR as CFString, LSRolesMask.all, "org.rstudio.RStudio" as CFString); + + let setResRmd : String = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, "Rmd" as CFString, nil)?.takeRetainedValue() as String? ?? ""; + NSLog("UTI of .Rmd extension: " + setResRmd); + + LSSetDefaultRoleHandlerForContentType(setResRmd as CFString, LSRolesMask.all, "org.rstudio.RStudio" as CFString); + + } + +} diff --git a/RSwitch/Swift/Utils/Reachable.swift b/RSwitch/Swift/Utils/Reachable.swift new file mode 100644 index 0000000..3eba2e9 --- /dev/null +++ b/RSwitch/Swift/Utils/Reachable.swift @@ -0,0 +1,51 @@ +// +// Reachable.swift +// RSwitch +// +// Created by hrbrmstr on 2/11/20. +// Copyright © 2020 Bob Rudis. All rights reserved. +// + +import Foundation +import SystemConfiguration + +protocol Utilities {} +extension NSObject: Utilities { + enum ReachabilityStatus { + case notReachable + case reachableViaWWAN + case reachableViaWiFi + } + + var currentReachabilityStatus: ReachabilityStatus { + + var zeroAddress = sockaddr_in() + zeroAddress.sin_len = UInt8(MemoryLayout.size) + zeroAddress.sin_family = sa_family_t(AF_INET) + guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { + SCNetworkReachabilityCreateWithAddress(nil, $0) + } + }) else { + return .notReachable + } + + var flags: SCNetworkReachabilityFlags = [] + if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) { + return .notReachable + } + + if flags.contains(.reachable) == false { + // The target host is not reachable. + return .notReachable + } else if flags.contains(.connectionRequired) == false { + // If the target host is reachable and no connection is required then we'll assume that you're on Wi-Fi... + return .reachableViaWiFi + } else if (flags.contains(.connectionOnDemand) == true || flags.contains(.connectionOnTraffic) == true) && flags.contains(.interventionRequired) == false { + // The connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs and no [user] intervention is needed + return .reachableViaWiFi + } else { + return .notReachable + } + } +} diff --git a/RSwitch/Swift/plotPopupViewController.swift b/RSwitch/Swift/plotPopupViewController.swift index 36173d4..5bea61d 100644 --- a/RSwitch/Swift/plotPopupViewController.swift +++ b/RSwitch/Swift/plotPopupViewController.swift @@ -28,10 +28,17 @@ class plotPopupViewController: NSViewController { view.addSubview(webView) } + func loadWebView(urlIn: String) { urlPath = urlIn + + NSLog(urlPath) + + // Check for "/export/" + // If export, then get bring up a Save Panel and then download the file to that location + if let url = URL(string: urlPath) { let urlRequest = URLRequest(url: url) webView.load(urlRequest) @@ -50,12 +57,37 @@ extension plotPopupViewController: WKUIDelegate { func webViewDidClose(_ webView: WKWebView) { self.view.window?.close() } + + + func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) { + + NSLog("savePanel!") + + let savePanel = NSSavePanel() + + savePanel.canCreateDirectories = true + savePanel.beginSheetModal(for:self.view.window!) { (response) in + if (response == NSApplication.ModalResponse.OK) { + completionHandler([savePanel.url!]) + } else { + completionHandler(nil) + } + savePanel.close() + } + + } } extension plotPopupViewController: WKNavigationDelegate { + open func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + print("DID START") } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + print("DID FINISH") } + } +