import Cocoa
import SwiftSoup
// S h o w a n i n f o r m a t i o n a l a l e r t
public func infoAlert ( _ message : String , _ extra : String ? = nil , style : NSAlert . Style = NSAlert . Style . informational ) {
let alert = NSAlert ( )
alert . messageText = message
if extra != nil { alert . informativeText = extra ! }
alert . alertStyle = style
alert . runModal ( )
}
// S h o w a n i n f o r m a t i o n a l a l e r t a n d t h e n q u i t
public func quitAlert ( _ message : String , _ extra : String ? = nil ) {
infoAlert ( message , " The application will now quit. " , style : NSAlert . Style . critical )
NSApp . terminate ( nil )
}
@NSApplicationMain
class AppDelegate : NSObject , NSApplicationDelegate {
var mainStoryboard : NSStoryboard !
var abtController : NSWindowController !
let macos_r_framework_dir = " /Library/Frameworks/R.framework/Versions " // W h e r e t h e o f f i c i a l R i n s t a l l s g o
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/ "
// G e t t h e b a r s e t u p
let statusItem = NSStatusBar . system . statusItem ( withLength : NSStatusItem . variableLength )
let statusMenu = NSMenu ( )
let quitItem = NSMenuItem ( title : NSLocalizedString ( " Quit " , comment : " Quit menu item " ) , action : #selector ( NSApp . terminate ) , keyEquivalent : " q " )
override init ( ) {
super . init ( )
statusMenu . delegate = self
// d i a l b y I c o n M a r k f r o m t h e N o u n P r o j e c t
statusItem . button ? . image = # imageLiteral ( resourceName : " RSwitch " )
statusItem . menu = statusMenu
mainStoryboard = NSStoryboard ( name : " Main " , bundle : nil )
abtController = ( mainStoryboard . instantiateController ( withIdentifier : " aboutPanelController " ) as ! NSWindowController )
}
func applicationDidFinishLaunching ( _ aNotification : Notification ) {
}
func applicationWillTerminate ( _ aNotification : Notification ) {
// I n s e r t c o d e h e r e t o t e a r d o w n a p p
}
// T h e c o r e w o r k e r f u n c t i o n . R e c e i v e s t h e b a s e n a m e o f t h e s e l e c t e d d i r e c t o r y
// t h e n r e m o v e s t h e c u r r e n t a l i a s a n d c r e a t e s t h e n e w o n e .
@objc func handleSwitch ( _ sender : NSMenuItem ? ) {
let fm = FileManager . default ;
let title = sender ? . title
do {
try fm . removeItem ( atPath : macos_r_framework_dir + " / " + " Current " )
} catch {
infoAlert ( " Failed to remove 'Current' alias " , macos_r_framework_dir + " / " + " Current " )
}
do {
try fm . createSymbolicLink (
at : NSURL ( fileURLWithPath : macos_r_framework_dir + " / " + " Current " ) as URL ,
withDestinationURL : NSURL ( fileURLWithPath : macos_r_framework_dir + " / " + title ! ) as URL
)
} catch {
infoAlert ( " Failed to create alias for " + macos_r_framework_dir + " / " + title ! )
}
}
// b r o w s e m a c O S d e v p a g e
@objc func browse_r_macos_dev_page ( _ sender : NSMenuItem ? ) {
let url = URL ( string : mac_r_project_url ) !
NSWorkspace . shared . open ( url )
}
// b r o w s e m a c O S d e v p a g e
@objc func browse_r_macos_cran_page ( _ sender : NSMenuItem ? ) {
let url = URL ( string : macos_cran_url ) !
NSWorkspace . shared . open ( url )
}
// b r o w s e m a c O S d e v p a g e
@objc func browse_r_sig_mac_page ( _ sender : NSMenuItem ? ) {
let url = URL ( string : r_sig_mac_url ) !
NSWorkspace . shared . open ( url )
}
// b r o w s e R S t u d i o m a c O S D a i l i e s
@objc func browse_rstudio_mac_dailies_page ( _ sender : NSMenuItem ? ) {
let url = URL ( string : rstudio_dailies_url ) !
NSWorkspace . shared . open ( url )
}
// S h o w a b o u t d i a l o g
@objc func about ( _ sender : NSMenuItem ? ) {
abtController . showWindow ( self )
}
// S h o w t h e f r a m e w o r k d i r i n a n e w F i n d e r w i n d o w
@objc func openFrameworksDir ( _ sender : NSMenuItem ? ) {
NSWorkspace . shared . openFile ( macos_r_framework_dir , withApplication : " Finder " )
}
// D o w n l o a d l a t e s t r s t u d i o d a i l y b u i l d
@objc func download_latest_rstudio ( _ sender : NSMenuItem ? ) {
let url = URL ( string : rstudio_dailies_url )
do {
let html = try String . init ( contentsOf : url ! )
let document = try SwiftSoup . parse ( html )
let link = try document . select ( " td > a " ) . first !
let href = try link . attr ( " href " )
let dlurl = URL ( string : href ) !
let dldir = FileManager . default . urls ( for : . downloadsDirectory , in : . userDomainMask ) . first !
var dlfile = dldir
dlfile . appendPathComponent ( dlurl . lastPathComponent )
if ( FileManager . default . fileExists ( atPath : dlfile . relativePath ) ) {
infoAlert ( " A local copy of the latest RStudio daily already exists. Please remove or rename it if you wish to re-download it. " )
} else {
let task = URLSession . shared . downloadTask ( with : dlurl ) { ( tempURL , response , error ) in
if let tempURL = tempURL , error = = nil {
if ( ( response as ? HTTPURLResponse ) ? . statusCode = = 200 ) {
do {
try FileManager . default . copyItem ( at : tempURL , to : dlfile )
NSWorkspace . shared . openFile ( dldir . path , withApplication : " Finder " )
DispatchQueue . main . async { infoAlert ( " Download of latest RStudio daily ( " + dlurl . lastPathComponent + " ) successful. " ) }
} catch {
DispatchQueue . main . async { infoAlert ( " Error downloading and saving latest RStudio daily. " ) }
}
} else {
DispatchQueue . main . async { infoAlert ( " Latest RStudio daily not found. " ) }
}
} else {
DispatchQueue . main . async { infoAlert ( " Error downloading latest RStudio daily. " ) }
}
}
task . resume ( )
}
} catch {
DispatchQueue . main . async { infoAlert ( " Error downloading latrest RStudio daily. " ) }
}
}
// D o w n l o a d l a t e s t r - d e v e l t a r b a l l
@objc func download_latest_tarball ( _ sender : NSMenuItem ? ) {
let url = URL ( string : " https://mac.r-project.org/el-capitan/R-devel/R-devel-el-capitan-sa-x86_64.tar.gz " ) !
let dldir = FileManager . default . urls ( for : . downloadsDirectory , in : . userDomainMask ) . first !
var dlfile = dldir
dlfile . appendPathComponent ( " R-devel-el-capitan-sa-x86_64.tar.gz " )
if ( FileManager . default . fileExists ( atPath : dlfile . relativePath ) ) {
infoAlert ( " R-devel tarball already exists. Please remove or rename it before downloading. " )
} else {
let task = URLSession . shared . downloadTask ( with : url ) { ( tempURL , response , error ) in
if let tempURL = tempURL , error = = nil {
if ( ( response as ? HTTPURLResponse ) ? . statusCode = = 200 ) {
do {
try FileManager . default . copyItem ( at : tempURL , to : dlfile )
NSWorkspace . shared . openFile ( dldir . path , withApplication : " Finder " )
DispatchQueue . main . async { infoAlert ( " Download of latest r-devel successful. " ) }
} catch {
DispatchQueue . main . async { infoAlert ( " Error downloading and saving latest r-devel . " ) }
}
} else {
DispatchQueue . main . async { infoAlert ( " Latest r-devel file not found. " ) }
}
} else {
DispatchQueue . main . async { infoAlert ( " Error downloading latest r-devel . " ) }
}
}
task . resume ( )
}
}
}
extension AppDelegate : NSMenuDelegate {
func menuWillOpen ( _ menu : NSMenu ) {
if ( menu != self . statusMenu ) { return }
// c l e a r t h e m e n u
menu . removeAllItems ( )
// a d d s e l e c t i o n t o o p e n f r a m e w o r k s d i r i n F i n d e r
menu . addItem ( NSMenuItem ( title : " Open R Frameworks Directory " , action : #selector ( openFrameworksDir ) , keyEquivalent : " " ) )
menu . addItem ( NSMenuItem . separator ( ) )
// p o p u l a t e i n s t a l l e d v e r s i o n s
let fm = FileManager . default
var targetPath : String ? = nil
do {
// g e t s a d i r e c t o r y l i s t i n g
let entries = try fm . contentsOfDirectory ( atPath : macos_r_framework_dir )
// r e t r i e v e s a l l v e r s i o n s ( e x c l u d e s h i d d e n f i l e s a n d t h e C u r r e n t a l i a s
let versions = entries . sorted ( ) . filter { ! ( $0 . hasPrefix ( " . " ) ) && ! ( $0 = = " Current " ) }
let hasCurrent = entries . filter { $0 = = " Current " }
// i f t h e r e w a s a C u r r e n t a l i a s ( p r b l y s h l d a l e r t i f n o t )
if ( hasCurrent . count > 0 ) {
// g e t w h e r e C u r r e n t p o i n t s t o
let furl = NSURL ( fileURLWithPath : macos_r_framework_dir + " / " + " Current " )
if ( furl . fileReferenceURL ( ) != nil ) {
do {
let fdat = try NSURL ( resolvingAliasFileAt : furl as URL , options : [ ] )
targetPath = fdat . lastPathComponent !
} catch {
targetPath = furl . path
}
}
// p o p u l a t e m e n u i t e m s w i t h a l l i n s t a l l e d R v e r s i o n s , e n s u r i n g w e
// p u t a c h e c k b o x n e x t t o t h e o n e t h a t i s C u r r e n t
var i = 1
for version in versions {
let keynum = ( i < 10 ) ? String ( i ) : " "
let item = NSMenuItem ( title : version , action : #selector ( handleSwitch ) , keyEquivalent : keynum )
item . isEnabled = true
if ( version = = targetPath ) { item . state = NSControl . StateValue . on }
item . representedObject = version
menu . addItem ( item )
i = i + 1
}
}
} catch {
quitAlert ( " Failed to list contents of R framework directory. You either do not have R installed or have incorrect permissions set on " + macos_r_framework_dir )
}
// A d d i t e m s t o d o w n l o a d l a t e s t r - d e v e l t a r b a l l a n d l a t e s t m a c O S d a i l y
menu . addItem ( NSMenuItem . separator ( ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " Download latest R-devel tarball " , comment : " Download latest tarball item " ) , action : #selector ( download_latest_tarball ) , keyEquivalent : " " ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " Download latest RStudio daily build " , comment : " Download latest RStudio item " ) , action : #selector ( download_latest_rstudio ) , keyEquivalent : " " ) )
// A d d i t e m s t o o p e n v a r i o s u R f o r m a c O S p a g e s
menu . addItem ( NSMenuItem . separator ( ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " Open R for macOS Developers Page… " , comment : " Open macOS Dev Page item " ) , action : #selector ( browse_r_macos_dev_page ) , keyEquivalent : " " ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " Open R for macOS CRAN Page… " , comment : " Open macOS CRAN Page item " ) , action : #selector ( browse_r_macos_cran_page ) , keyEquivalent : " " ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " Open R-SIG-Mac Archives Page… " , comment : " Open R-SIG-Mac Page item " ) , action : #selector ( browse_r_sig_mac_page ) , keyEquivalent : " " ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " Open RStudio macOS Dailies Page… " , comment : " Open RStudio macOS Dailies Page item " ) , action : #selector ( browse_rstudio_mac_dailies_page ) , keyEquivalent : " " ) )
// A d d a A b o u t i t e m
menu . addItem ( NSMenuItem . separator ( ) )
menu . addItem ( NSMenuItem ( title : NSLocalizedString ( " About RSwitch… " , comment : " About menu item " ) , action : #selector ( about ) , keyEquivalent : " " ) )
// A d d a Q u i t i t e m
menu . addItem ( NSMenuItem . separator ( ) )
menu . addItem ( quitItem )
}
}