2017-05-22 22:00:45 +02:00
//
// A p p D e l e g a t e . s w i f t
// E v e r g r e e n
//
2017-05-27 19:43:27 +02:00
// C r e a t e d b y B r e n t S i m m o n s o n 7 / 1 1 / 1 5 .
// C o p y r i g h t © 2 0 1 5 R a n c h e r o S o f t w a r e , L L C . A l l r i g h t s r e s e r v e d .
2017-05-22 22:00:45 +02:00
//
import Cocoa
2017-05-27 19:43:27 +02:00
import DB5
2017-09-17 21:22:15 +02:00
import Data
2017-05-27 19:43:27 +02:00
import RSTextDrawing
import RSTree
2017-05-27 22:37:50 +02:00
import RSWeb
2017-09-18 02:12:42 +02:00
import Account
2017-11-14 03:33:23 +01:00
import RSCore
2017-05-27 19:43:27 +02:00
2017-11-15 06:31:17 +01:00
var appDelegate : AppDelegate !
2017-05-22 22:00:45 +02:00
@NSApplicationMain
2017-11-19 22:57:42 +01:00
class AppDelegate : NSObject , NSApplicationDelegate , NSUserInterfaceValidations , UnreadCountProvider {
2017-05-22 22:00:45 +02:00
2017-11-25 06:39:59 +01:00
var currentTheme : VSTheme !
var faviconDownloader : FaviconDownloader !
2017-11-26 05:12:53 +01:00
var imageDownloader : ImageDownloader !
2017-11-26 22:16:32 +01:00
var authorAvatarDownloader : AuthorAvatarDownloader !
2017-11-27 04:57:45 +01:00
var feedIconDownloader : FeedIconDownloader !
2017-11-25 06:39:59 +01:00
var appName : String !
2017-11-19 21:46:29 +01:00
var pseudoFeeds = [ PseudoFeed ] ( )
2017-05-27 19:43:27 +02:00
var unreadCount = 0 {
didSet {
2017-10-06 03:12:58 +02:00
if unreadCount != oldValue {
dockBadge . update ( )
2017-11-19 22:57:42 +01:00
postUnreadCountDidChangeNotification ( )
2017-10-06 03:12:58 +02:00
}
2017-05-27 19:43:27 +02:00
}
}
2017-11-25 06:39:59 +01:00
private let windowControllers = NSMutableArray ( )
private var preferencesWindowController : NSWindowController ?
private var mainWindowController : NSWindowController ?
private var readerWindows = [ NSWindowController ] ( )
private var feedListWindowController : NSWindowController ?
private var dinosaursWindowController : DinosaursWindowController ?
private var addFeedController : AddFeedController ?
private var addFolderWindowController : AddFolderWindowController ?
private var keyboardShortcutsWindowController : WebViewWindowController ?
private var inspectorWindowController : InspectorWindowController ?
private var logWindowController : LogWindowController ?
private var panicButtonWindowController : PanicButtonWindowController ?
private let log = Log ( )
private let themeLoader = VSThemeLoader ( )
private let appNewsURLString = " https://ranchero.com/evergreen/feed.json "
private let dockBadge = DockBadge ( )
2017-05-27 19:43:27 +02:00
override init ( ) {
NSWindow . allowsAutomaticWindowTabbing = false
super . init ( )
2017-10-06 03:01:18 +02:00
dockBadge . appDelegate = self
2017-10-19 03:37:45 +02:00
NotificationCenter . default . addObserver ( self , selector : #selector ( unreadCountDidChange ( _ : ) ) , name : . UnreadCountDidChange , object : nil )
2017-11-15 06:31:17 +01:00
appDelegate = self
}
2017-11-15 22:13:40 +01:00
// MARK: - A P I
2017-11-15 06:31:17 +01:00
func logMessage ( _ message : String , type : LogItem . ItemType ) {
2017-11-26 01:15:17 +01:00
#if DEBUG
print ( " logMessage: \( message ) - \( type ) " )
#endif
2017-11-15 06:31:17 +01:00
let logItem = LogItem ( type : type , message : message )
log . add ( logItem )
}
func logDebugMessage ( _ message : String ) {
logMessage ( message , type : . debug )
2017-05-27 19:43:27 +02:00
}
2017-11-15 22:13:40 +01:00
func showAddFolderSheetOnWindow ( _ window : NSWindow ) {
addFolderWindowController = AddFolderWindowController ( )
addFolderWindowController ! . runSheetOnWindow ( window )
}
2017-11-20 00:45:20 +01:00
func markOlderArticlesAsRead ( with window : NSWindow ) {
2017-11-17 03:23:07 +01:00
panicButtonWindowController = PanicButtonWindowController ( )
panicButtonWindowController ! . runSheetOnWindow ( window )
}
2017-11-20 01:28:26 +01:00
func markEverywhereAsRead ( with window : NSWindow ) {
let alert = NSAlert ( )
alert . messageText = NSLocalizedString ( " Mark All Articles as Read Everywhere? " , comment : " Mark Everywhere alert messageText " )
alert . informativeText = NSLocalizedString ( " This will mark every single article as read. All of them. The unread count will be zero. \n \n Note: this operation cannot be undone. " , comment : " Mark Everywhere informativeText. " )
alert . addButton ( withTitle : NSLocalizedString ( " Mark All Articles as Read " , comment : " Mark Everywhere alert button. " ) )
alert . addButton ( withTitle : NSLocalizedString ( " Don’ t Mark as Read " , comment : " Mark Everywhere alert button. " ) )
alert . beginSheetModal ( for : window ) { ( returnCode ) in
if returnCode = = . alertFirstButtonReturn {
self . markEverywhereAsRead ( )
}
}
}
func markEverywhereAsRead ( ) {
AccountManager . shared . accounts . forEach { $0 . markEverywhereAsRead ( ) }
}
2017-09-19 07:00:35 +02:00
// MARK: - N S A p p l i c a t i o n D e l e g a t e
2017-05-27 19:43:27 +02:00
func applicationDidFinishLaunching ( _ note : Notification ) {
2017-11-25 06:39:59 +01:00
appName = Bundle . main . infoDictionary ! [ " CFBundleExecutable " ] ! as ! String
2017-09-23 21:17:14 +02:00
let isFirstRun = AppDefaults . shared . isFirstRun
2017-11-26 01:10:19 +01:00
if isFirstRun {
logDebugMessage ( " Is first run. " )
}
2017-09-24 21:24:44 +02:00
let localAccount = AccountManager . shared . localAccount
2017-09-27 06:43:40 +02:00
DefaultFeedsImporter . importIfNeeded ( isFirstRun , account : localAccount )
2017-05-27 19:43:27 +02:00
currentTheme = themeLoader . defaultTheme
2017-11-19 21:46:29 +01:00
2017-11-25 20:14:06 +01:00
let tempDirectory = NSTemporaryDirectory ( )
2017-11-25 22:47:26 +01:00
let cacheFolder = ( tempDirectory as NSString ) . appendingPathComponent ( " com.ranchero.evergreen " )
2017-11-26 05:12:53 +01:00
2017-11-25 22:47:26 +01:00
let faviconsFolder = ( cacheFolder as NSString ) . appendingPathComponent ( " Favicons " )
2017-11-25 20:14:06 +01:00
let faviconsFolderURL = URL ( fileURLWithPath : faviconsFolder )
try ! FileManager . default . createDirectory ( at : faviconsFolderURL , withIntermediateDirectories : true , attributes : nil )
2017-11-25 06:39:59 +01:00
faviconDownloader = FaviconDownloader ( folder : faviconsFolder )
2017-11-26 05:12:53 +01:00
let imagesFolder = ( cacheFolder as NSString ) . appendingPathComponent ( " Images " )
let imagesFolderURL = URL ( fileURLWithPath : imagesFolder )
try ! FileManager . default . createDirectory ( at : imagesFolderURL , withIntermediateDirectories : true , attributes : nil )
imageDownloader = ImageDownloader ( folder : imagesFolder )
2017-11-26 22:16:32 +01:00
authorAvatarDownloader = AuthorAvatarDownloader ( imageDownloader : imageDownloader )
2017-11-27 04:57:45 +01:00
feedIconDownloader = FeedIconDownloader ( imageDownloader : imageDownloader )
2017-11-20 00:40:02 +01:00
let todayFeed = SmartFeed ( delegate : TodayFeedDelegate ( ) )
let unreadFeed = UnreadFeed ( )
let starredFeed = SmartFeed ( delegate : StarredFeedDelegate ( ) )
pseudoFeeds = [ todayFeed , unreadFeed , starredFeed ]
2017-09-24 21:24:44 +02:00
2017-05-27 19:43:27 +02:00
createAndShowMainWindow ( )
#if RELEASE
DispatchQueue . main . async {
self . refreshAll ( self )
}
#endif
NSAppleEventManager . shared ( ) . setEventHandler ( self , andSelector : #selector ( AppDelegate . getURL ( _ : _ : ) ) , forEventClass : AEEventClass ( kInternetEventClass ) , andEventID : AEEventID ( kAEGetURL ) )
2017-10-19 03:37:45 +02:00
2017-11-26 22:16:32 +01:00
NotificationCenter . default . addObserver ( self , selector : #selector ( feedSettingDidChange ( _ : ) ) , name : . FeedSettingDidChange , object : nil )
2017-10-19 03:37:45 +02:00
DispatchQueue . main . async {
self . unreadCount = AccountManager . shared . unreadCount
}
2017-05-27 19:43:27 +02:00
}
func applicationShouldHandleReopen ( _ sender : NSApplication , hasVisibleWindows flag : Bool ) -> Bool {
if ( ! flag ) {
createAndShowMainWindow ( )
}
return false
}
func applicationDidResignActive ( _ notification : Notification ) {
RSSingleLineRenderer . emptyCache ( )
RSMultiLineRenderer . emptyCache ( )
TimelineCellData . emptyCache ( )
timelineEmptyCaches ( )
}
// MARK: G e t U R L A p p l e E v e n t
2017-09-18 02:12:42 +02:00
@objc func getURL ( _ event : NSAppleEventDescriptor , _ withReplyEvent : NSAppleEventDescriptor ) {
2017-05-22 22:00:45 +02:00
2017-05-27 19:43:27 +02:00
guard let urlString = event . paramDescriptor ( forKeyword : keyDirectObject ) ? . stringValue else {
return
}
2017-05-22 22:00:45 +02:00
2017-05-27 19:43:27 +02:00
let normalizedURLString = urlString . rs_normalizedURL ( )
if ! normalizedURLString . rs_stringMayBeURL ( ) {
return
}
DispatchQueue . main . async {
self . addFeed ( normalizedURLString )
}
2017-05-22 22:00:45 +02:00
}
2017-05-27 19:43:27 +02:00
// MARK: N o t i f i c a t i o n s
2017-10-19 03:37:45 +02:00
@objc func unreadCountDidChange ( _ note : Notification ) {
2017-05-27 19:43:27 +02:00
2017-10-19 03:37:45 +02:00
if note . object is AccountManager {
unreadCount = AccountManager . shared . unreadCount
}
2017-05-27 19:43:27 +02:00
}
2017-11-26 22:16:32 +01:00
@objc func feedSettingDidChange ( _ note : Notification ) {
guard let feed = note . object as ? Feed else {
return
}
let _ = faviconDownloader . favicon ( for : feed )
}
2017-05-27 19:43:27 +02:00
// MARK: M a i n W i n d o w
func windowControllerWithName ( _ storyboardName : String ) -> NSWindowController {
2017-09-18 02:12:42 +02:00
let storyboard = NSStoryboard ( name : NSStoryboard . Name ( rawValue : storyboardName ) , bundle : nil )
2017-05-27 19:43:27 +02:00
return storyboard . instantiateInitialController ( ) ! as ! NSWindowController
}
func createAndShowMainWindow ( ) {
if mainWindowController = = nil {
2017-11-14 22:18:25 +01:00
mainWindowController = createReaderWindow ( )
2017-05-27 19:43:27 +02:00
}
mainWindowController ! . showWindow ( self )
}
// MARK: N S U s e r I n t e r f a c e V a l i d a t i o n s
func validateUserInterfaceItem ( _ item : NSValidatedUserInterfaceItem ) -> Bool {
if item . action = = #selector ( refreshAll ( _ : ) ) {
2017-09-23 21:17:14 +02:00
return ! AccountManager . shared . refreshInProgress
2017-05-27 19:43:27 +02:00
}
if item . action = = #selector ( addAppNews ( _ : ) ) {
2017-09-23 21:17:14 +02:00
return ! AccountManager . shared . anyAccountHasFeedWithURL ( appNewsURLString )
2017-05-27 19:43:27 +02:00
}
return true
}
// MARK: A d d F e e d
func addFeed ( _ urlString : String ? , _ name : String ? = nil ) {
2017-05-22 22:00:45 +02:00
2017-05-27 19:43:27 +02:00
createAndShowMainWindow ( )
addFeedController = AddFeedController ( hostWindow : mainWindowController ! . window ! )
addFeedController ? . showAddFeedSheet ( urlString , name )
}
2017-09-23 21:17:14 +02:00
// MARK: - A c t i o n s
2017-05-27 19:43:27 +02:00
2017-11-14 22:18:25 +01:00
@IBAction func newReaderWindow ( _ sender : Any ? ) {
let readerWindow = createReaderWindow ( )
readerWindows += [ readerWindow ]
readerWindow . showWindow ( self )
}
2017-05-27 19:43:27 +02:00
@IBAction func showPreferences ( _ sender : AnyObject ) {
if preferencesWindowController = = nil {
preferencesWindowController = windowControllerWithName ( " Preferences " )
}
preferencesWindowController ! . showWindow ( self )
}
@IBAction func showMainWindow ( _ sender : AnyObject ) {
createAndShowMainWindow ( )
}
@IBAction func refreshAll ( _ sender : AnyObject ) {
2017-09-24 21:24:44 +02:00
AccountManager . shared . refreshAll ( )
2017-05-27 19:43:27 +02:00
}
@IBAction func showAddFeedWindow ( _ sender : AnyObject ) {
addFeed ( nil )
}
@IBAction func showAddFolderWindow ( _ sender : AnyObject ) {
createAndShowMainWindow ( )
2017-11-15 22:13:40 +01:00
showAddFolderSheetOnWindow ( mainWindowController ! . window ! )
2017-05-27 19:43:27 +02:00
}
@IBAction func showFeedList ( _ sender : AnyObject ) {
if feedListWindowController = = nil {
feedListWindowController = windowControllerWithName ( " FeedList " )
}
feedListWindowController ! . showWindow ( self )
}
2017-11-12 20:57:51 +01:00
@IBAction func showDinosaursWindow ( _ sender : Any ? ) {
if dinosaursWindowController = = nil {
dinosaursWindowController = DinosaursWindowController ( )
}
dinosaursWindowController ! . showWindow ( self )
}
2017-11-14 03:33:23 +01:00
@IBAction func showKeyboardShortcutsWindow ( _ sender : Any ? ) {
if keyboardShortcutsWindowController = = nil {
keyboardShortcutsWindowController = WebViewWindowController ( title : NSLocalizedString ( " Keyboard Shortcuts " , comment : " window title " ) )
let htmlFile = Bundle ( for : type ( of : self ) ) . path ( forResource : " KeyboardShortcuts " , ofType : " html " ) !
keyboardShortcutsWindowController ? . displayContents ( of : htmlFile )
}
keyboardShortcutsWindowController ! . showWindow ( self )
}
2017-11-16 07:33:35 +01:00
@IBAction func toggleInspectorWindow ( _ sender : Any ? ) {
if inspectorWindowController = = nil {
inspectorWindowController = InspectorWindowController ( )
}
if inspectorWindowController ! . isOpen {
inspectorWindowController ! . window ! . performClose ( self )
}
else {
inspectorWindowController ! . showWindow ( self )
}
}
2017-11-16 07:40:49 +01:00
@IBAction func showLogWindow ( _ sender : Any ? ) {
if logWindowController = = nil {
logWindowController = LogWindowController ( title : " Errors " , log : log )
}
logWindowController ! . showWindow ( self )
}
2017-05-27 19:43:27 +02:00
@IBAction func importOPMLFromFile ( _ sender : AnyObject ) {
let panel = NSOpenPanel ( )
panel . canDownloadUbiquitousContents = true
panel . canResolveUbiquitousConflicts = true
panel . canChooseFiles = true
panel . allowsMultipleSelection = false
panel . canChooseDirectories = false
panel . resolvesAliases = true
panel . allowedFileTypes = [ " opml " ]
panel . allowsOtherFileTypes = false
let result = panel . runModal ( )
2017-09-23 21:17:14 +02:00
if result = = NSApplication . ModalResponse . OK , let url = panel . url {
DispatchQueue . main . async {
2017-10-05 22:28:39 +02:00
do {
try OPMLImporter . parseAndImport ( fileURL : url , account : AccountManager . shared . localAccount )
}
catch let error as NSError {
NSApplication . shared . presentError ( error )
}
2017-05-27 19:43:27 +02:00
}
}
}
2017-09-23 21:17:14 +02:00
2017-05-27 20:33:31 +02:00
@IBAction func importOPMLFromURL ( _ sender : AnyObject ) {
}
2017-05-27 19:43:27 +02:00
@IBAction func exportOPML ( _ sender : AnyObject ) {
let panel = NSSavePanel ( )
panel . allowedFileTypes = [ " opml " ]
panel . allowsOtherFileTypes = false
panel . prompt = NSLocalizedString ( " Export OPML " , comment : " Export OPML " )
panel . title = NSLocalizedString ( " Export OPML " , comment : " Export OPML " )
panel . nameFieldLabel = NSLocalizedString ( " Export to: " , comment : " Export OPML " )
panel . message = NSLocalizedString ( " Choose a location for the exported OPML file. " , comment : " Export OPML " )
panel . isExtensionHidden = false
panel . nameFieldStringValue = " MySubscriptions.opml "
let result = panel . runModal ( )
2017-10-07 21:00:47 +02:00
if result = = NSApplication . ModalResponse . OK , let url = panel . url {
2017-09-23 21:17:14 +02:00
DispatchQueue . main . async {
2017-09-24 21:24:44 +02:00
let opmlString = AccountManager . shared . localAccount . OPMLString ( indentLevel : 0 )
2017-09-23 21:17:14 +02:00
do {
try opmlString . write ( to : url , atomically : true , encoding : String . Encoding . utf8 )
}
catch let error as NSError {
NSApplication . shared . presentError ( error )
2017-05-27 19:43:27 +02:00
}
}
}
}
2017-09-23 21:17:14 +02:00
2017-05-27 19:43:27 +02:00
@IBAction func addAppNews ( _ sender : AnyObject ) {
2017-09-23 21:17:14 +02:00
if AccountManager . shared . anyAccountHasFeedWithURL ( appNewsURLString ) {
2017-05-27 19:43:27 +02:00
return
}
addFeed ( appNewsURLString , " Evergreen News " )
}
2017-05-27 22:37:50 +02:00
@IBAction func openWebsite ( _ sender : AnyObject ) {
2017-10-06 03:12:58 +02:00
Browser . open ( " //ranchero.com/evergreen/ " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
@IBAction func openRepository ( _ sender : AnyObject ) {
2017-10-06 03:12:58 +02:00
Browser . open ( " https://github.com/brentsimmons/Evergreen " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
@IBAction func openBugTracker ( _ sender : AnyObject ) {
2017-10-06 03:12:58 +02:00
Browser . open ( " https://github.com/brentsimmons/Evergreen/issues " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
@IBAction func showHelp ( _ sender : AnyObject ) {
2017-10-06 03:12:58 +02:00
Browser . open ( " https://ranchero.com/evergreen/help/1.0/ " , inBackground : false )
2017-05-27 22:37:50 +02:00
}
2017-11-17 03:23:07 +01:00
2017-11-20 00:45:20 +01:00
@IBAction func markOlderArticlesAsRead ( _ sender : Any ? ) {
2017-11-17 03:23:07 +01:00
createAndShowMainWindow ( )
2017-11-20 00:45:20 +01:00
markOlderArticlesAsRead ( with : mainWindowController ! . window ! )
2017-11-17 03:23:07 +01:00
}
2017-11-20 01:28:26 +01:00
@IBAction func markEverywhereAsRead ( _ sender : Any ? ) {
createAndShowMainWindow ( )
markEverywhereAsRead ( with : mainWindowController ! . window ! )
}
2017-11-20 07:39:13 +01:00
@IBAction func debugDropConditionalGetInfo ( _ sender : Any ? ) {
#if DEBUG
2017-11-25 20:14:06 +01:00
AccountManager . shared . accounts . forEach { $0 . debugDropConditionalGetInfo ( ) }
2017-11-20 07:39:13 +01:00
#endif
}
2017-05-27 19:43:27 +02:00
}
2017-11-14 22:18:25 +01:00
private extension AppDelegate {
2017-05-22 22:00:45 +02:00
2017-11-14 22:18:25 +01:00
func createReaderWindow ( ) -> NSWindowController {
return windowControllerWithName ( " MainWindow " )
}
}