diff --git a/Podfile b/Podfile index 0219666..1cd04fe 100644 --- a/Podfile +++ b/Podfile @@ -1,11 +1,9 @@ -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' +platform :osx, '10.14' target 'RSwitch' do - # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! - # Pods for RSwitch pod 'SwiftSoup' end diff --git a/Podfile.lock b/Podfile.lock index aaeac53..ad11e35 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -11,6 +11,6 @@ SPEC REPOS: SPEC CHECKSUMS: SwiftSoup: 230991361c5ba249d140161860a3864a28b63860 -PODFILE CHECKSUM: 31d048fe80d57b70a2af7f59ae745d8fd46a1fad +PODFILE CHECKSUM: d5db68a23a6fb36f3d94717563c9d5199d9f3bdf COCOAPODS: 1.7.5 diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock index aaeac53..ad11e35 100644 --- a/Pods/Manifest.lock +++ b/Pods/Manifest.lock @@ -11,6 +11,6 @@ SPEC REPOS: SPEC CHECKSUMS: SwiftSoup: 230991361c5ba249d140161860a3864a28b63860 -PODFILE CHECKSUM: 31d048fe80d57b70a2af7f59ae745d8fd46a1fad +PODFILE CHECKSUM: d5db68a23a6fb36f3d94717563c9d5199d9f3bdf COCOAPODS: 1.7.5 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index ade8fe5..a3af964 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -396,7 +396,7 @@ LastUpgradeCheck = 1100; }; buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 10.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( diff --git a/RSwitch.xcodeproj/project.pbxproj b/RSwitch.xcodeproj/project.pbxproj index 9135df2..e7f739a 100644 --- a/RSwitch.xcodeproj/project.pbxproj +++ b/RSwitch.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 01073F0F2311AE2E007162C9 /* String+Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01073F0E2311AE2E007162C9 /* String+Version.swift */; }; 0178970D230ED25100F8F5BC /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0178970C230ED25100F8F5BC /* AboutViewController.swift */; }; 01F3EF0C230E635300DF5DF9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3EF0B230E635300DF5DF9 /* AppDelegate.swift */; }; 01F3EF0E230E635300DF5DF9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3EF0D230E635300DF5DF9 /* ViewController.swift */; }; @@ -16,6 +17,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 01073F0E2311AE2E007162C9 /* String+Version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Version.swift"; sourceTree = ""; }; 0178970C230ED25100F8F5BC /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 01F3EF08230E635300DF5DF9 /* RSwitch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RSwitch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 01F3EF0B230E635300DF5DF9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -66,6 +68,7 @@ 01F3EF0F230E635500DF5DF9 /* Assets.xcassets */, 01F3EF11230E635500DF5DF9 /* Main.storyboard */, 0178970C230ED25100F8F5BC /* AboutViewController.swift */, + 01073F0E2311AE2E007162C9 /* String+Version.swift */, 01F3EF14230E635500DF5DF9 /* Info.plist */, ); path = RSwitch; @@ -205,6 +208,7 @@ 0178970D230ED25100F8F5BC /* AboutViewController.swift in Sources */, 01F3EF0E230E635300DF5DF9 /* ViewController.swift in Sources */, 01F3EF0C230E635300DF5DF9 /* AppDelegate.swift in Sources */, + 01073F0F2311AE2E007162C9 /* String+Version.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/RSwitch/AppDelegate.swift b/RSwitch/AppDelegate.swift index 9161f17..e23c404 100644 --- a/RSwitch/AppDelegate.swift +++ b/RSwitch/AppDelegate.swift @@ -21,14 +21,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele var mainStoryboard: NSStoryboard! var abtController: NSWindowController! - + let macos_r_framework_dir = "/Library/Frameworks/R.framework/Versions" // Where the official R installs go - let mac_r_project_url = "https://mac.r-project.org/" - let macos_cran_url = "https://cran.rstudio.org/bin/macosx/" - let r_sig_mac_url = "https://stat.ethz.ch/pipermail/r-sig-mac/" - let rstudio_dailies_url = "https://dailies.rstudio.com/rstudio/oss/mac/" - let latest_rstudio_dailies_url = "https://www.rstudio.org/download/latest/daily/desktop/mac/RStudio-latest.dmg" - let browse_r_admin_macos_url = "https://cran.rstudio.org/doc/manuals/R-admin.html#Installing-R-under-macOS" + + struct app_urls { + static let mac_r_project = "https://mac.r-project.org/" + static let macos_cran = "https://cran.rstudio.org/bin/macosx/" + static let r_sig_mac = "https://stat.ethz.ch/pipermail/r-sig-mac/" + static let rstudio_dailies = "https://dailies.rstudio.com/rstudio/oss/mac/" + static let latest_rstudio_dailies = "https://www.rstudio.org/download/latest/daily/desktop/mac/RStudio-latest.dmg" + static let browse_r_admin_macos = "https://cran.rstudio.org/doc/manuals/R-admin.html#Installing-R-under-macOS" + static let version_check = "https://rud.is/rswitch/releases/current-version.txt" + static let releases = "https://git.rud.is/hrbrmstr/RSwitch/releases" + } // Get the bar setup let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) @@ -54,6 +59,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele rdevel_enabled = true rstudio_enabled = true + + URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) } @@ -111,32 +118,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele // browse macOS dev page @objc func browse_r_macos_dev_page(_ sender: NSMenuItem?) { - let url = URL(string: mac_r_project_url)! + let url = URL(string: app_urls.mac_r_project)! NSWorkspace.shared.open(url) } // browse macOS dev page @objc func browse_r_macos_cran_page(_ sender: NSMenuItem?) { - let url = URL(string: macos_cran_url)! + let url = URL(string: app_urls.macos_cran)! NSWorkspace.shared.open(url) } // browse macOS dev page @objc func browse_r_sig_mac_page(_ sender: NSMenuItem?) { - let url = URL(string: r_sig_mac_url)! + let url = URL(string: app_urls.r_sig_mac)! NSWorkspace.shared.open(url) } // browse RStudio macOS Dailies @objc func browse_rstudio_mac_dailies_page(_ sender: NSMenuItem?) { - let url = URL(string: rstudio_dailies_url)! + let url = URL(string: app_urls.rstudio_dailies)! NSWorkspace.shared.open(url) } // browse R Install/Admin macOS section @objc func browse_r_admin_macos_page(_ sender: NSMenuItem?) { - let url = URL(string: browse_r_admin_macos_url)! + let url = URL(string: app_urls.browse_r_admin_macos)! NSWorkspace.shared.open(url) } @@ -159,13 +166,35 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele @objc func launchRApp(_ sender: NSMenuItem?) { NSWorkspace.shared.launchApplication("R.app") } + + // Launch R.app + @objc func checkForUpdate(_ sender: NSMenuItem?) { + + let url = URL(string: app_urls.version_check) + + do { + URLCache.shared.removeAllCachedResponses() + var version = try String.init(contentsOf: url!) + version = version.trimmingCharacters(in: .whitespacesAndNewlines) + if (version.isVersion(greaterThan: Bundle.main.releaseVersionNumber!)) { + let url = URL(string: app_urls.releases) + NSWorkspace.shared.open(url!) + } else { + self.notifyUser(title: "RSwitch", text: "You are running the latest version of RSwitch.") + } + } catch { + self.notifyUser(title: "Action failed", subtitle: "Update check", text: "Error: \(error)") + } + + + } // Download latest rstudio daily build @objc func download_latest_rstudio(_ sender: NSMenuItem?) { self.rstudio_enabled = false - let url = URL(string: rstudio_dailies_url) + let url = URL(string: app_urls.rstudio_dailies) do { @@ -300,6 +329,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } +extension Bundle { + var releaseVersionNumber: String? { + return infoDictionary?["CFBundleShortVersionString"] as? String + } + var buildVersionNumber: String? { + return infoDictionary?["CFBundleVersion"] as? String + } + var releaseVersionNumberPretty: String { + return "v\(releaseVersionNumber ?? "1.0.0")" + } +} + extension AppDelegate: NSMenuDelegate { func menuWillOpen(_ menu: NSMenu) { @@ -384,6 +425,10 @@ extension AppDelegate: NSMenuDelegate { menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem(title: NSLocalizedString("Launch R GUI", comment: "Launch R GUI item"), action: #selector(launchRApp), keyEquivalent: "")) menu.addItem(NSMenuItem(title: NSLocalizedString("Launch RStudio", comment: "Launch RStudio item"), action: #selector(launchRStudio), keyEquivalent: "")) + + // Add a About item + menu.addItem(NSMenuItem.separator()) + menu.addItem(NSMenuItem(title: NSLocalizedString("Check for update…", comment: "Check for update item"), action: #selector(checkForUpdate), keyEquivalent: "")) // Add a About item menu.addItem(NSMenuItem.separator()) diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-1x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-1x.png index d021997..1b26e40 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-1x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-1x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-2x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-2x.png index 145390c..39c9a5f 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-2x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-128-2x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-1x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-1x.png index fa9291b..25aae47 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-1x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-1x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-2x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-2x.png index 2af2020..7c4e508 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-2x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-16-2x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-1x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-1x.png index 08473dc..d2990c1 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-1x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-1x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-2x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-2x.png index c20fff3..044d0ca 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-2x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-256-2x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-1x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-1x.png index 16a2f22..59ce2e4 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-1x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-1x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-2x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-2x.png index 24a3223..a54a7e3 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-2x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-32-2x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-1x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-1x.png index 833be35..65951c4 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-1x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-1x.png differ diff --git a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-2x.png b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-2x.png index 37d4947..487ec72 100644 Binary files a/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-2x.png and b/RSwitch/Assets.xcassets/AppIcon.appiconset/rs-512-2x.png differ diff --git a/RSwitch/Base.lproj/Main.storyboard b/RSwitch/Base.lproj/Main.storyboard index fece9aa..9aa02bf 100644 --- a/RSwitch/Base.lproj/Main.storyboard +++ b/RSwitch/Base.lproj/Main.storyboard @@ -754,11 +754,11 @@ MIT Licensed - + - + diff --git a/RSwitch/String+Version.swift b/RSwitch/String+Version.swift new file mode 100644 index 0000000..312bb6e --- /dev/null +++ b/RSwitch/String+Version.swift @@ -0,0 +1,42 @@ +// +// String+Version.swift +// Pods +// +// Created by DragonCherry on 5/11/17. +// +// + +import Foundation + +extension String { + + /// Inner comparison utility to handle same versions with different length. (Ex: "1.0.0" & "1.0") + private func compare(toVersion targetVersion: String) -> ComparisonResult { + + let versionDelimiter = "." + var result: ComparisonResult = .orderedSame + var versionComponents = components(separatedBy: versionDelimiter) + var targetComponents = targetVersion.components(separatedBy: versionDelimiter) + let spareCount = versionComponents.count - targetComponents.count + + if spareCount == 0 { + result = compare(targetVersion, options: .numeric) + } else { + let spareZeros = repeatElement("0", count: abs(spareCount)) + if spareCount > 0 { + targetComponents.append(contentsOf: spareZeros) + } else { + versionComponents.append(contentsOf: spareZeros) + } + result = versionComponents.joined(separator: versionDelimiter) + .compare(targetComponents.joined(separator: versionDelimiter), options: .numeric) + } + return result + } + + public func isVersion(equalTo targetVersion: String) -> Bool { return compare(toVersion: targetVersion) == .orderedSame } + public func isVersion(greaterThan targetVersion: String) -> Bool { return compare(toVersion: targetVersion) == .orderedDescending } + public func isVersion(greaterThanOrEqualTo targetVersion: String) -> Bool { return compare(toVersion: targetVersion) != .orderedAscending } + public func isVersion(lessThan targetVersion: String) -> Bool { return compare(toVersion: targetVersion) == .orderedAscending } + public func isVersion(lessThanOrEqualTo targetVersion: String) -> Bool { return compare(toVersion: targetVersion) != .orderedDescending } +}