diff --git a/Evergreen/Resources/Evergreen.sdef b/Evergreen/Resources/Evergreen.sdef index 2cedc5478..56ab039db 100644 --- a/Evergreen/Resources/Evergreen.sdef +++ b/Evergreen/Resources/Evergreen.sdef @@ -13,6 +13,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Evergreen/Scriptability/Account+Scriptability.swift b/Evergreen/Scriptability/Account+Scriptability.swift index 4530dfea8..5f5d2822c 100644 --- a/Evergreen/Scriptability/Account+Scriptability.swift +++ b/Evergreen/Scriptability/Account+Scriptability.swift @@ -45,6 +45,11 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } + + @objc(isLocationRequiredToCreateForKey:) + func isLocationRequiredToCreate(forKey key:String) -> Bool { + return false; + } // MARK: --- Scriptable elements --- @@ -61,7 +66,6 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta return ScriptableFeed(feed, container:self) } - @objc(folders) var folders:NSArray { let folders = account.children.compactMap { $0 as? Folder } @@ -74,8 +78,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta let folders = account.children.compactMap { $0 as? Folder } guard let folder = folders.first(where:{$0.folderID == folderId}) else { return nil } return ScriptableFolder(folder, container:self) - } - + } // MARK: --- Scriptable properties --- diff --git a/Evergreen/Scriptability/AppDelegate+Scriptability.swift b/Evergreen/Scriptability/AppDelegate+Scriptability.swift index 47e3bf5e1..512f0801e 100644 --- a/Evergreen/Scriptability/AppDelegate+Scriptability.swift +++ b/Evergreen/Scriptability/AppDelegate+Scriptability.swift @@ -56,8 +56,18 @@ extension AppDelegate : AppDelegateAppleEvents { } } +class EvergreenCreateElementCommand : NSCreateCommand { + override func performDefaultImplementation() -> Any? { + let classDescription = self.createClassDescription + if (classDescription.className == "feed") { + return ScriptableFeed.handleCreateElement(command:self) + } + return nil + } +} + class EvergreenExistsCommand : NSExistsCommand { - + // cocoa default behavior doesn't work here, because of cases where we define an object's property // to be another object type. e.g., 'permalink of the current article' parses as // of of diff --git a/Evergreen/Scriptability/Feed+Scriptability.swift b/Evergreen/Scriptability/Feed+Scriptability.swift index 8a5ddad8b..24d89970a 100644 --- a/Evergreen/Scriptability/Feed+Scriptability.swift +++ b/Evergreen/Scriptability/Feed+Scriptability.swift @@ -7,6 +7,7 @@ // import Foundation +import RSParser import Account import Data @@ -27,6 +28,11 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine return (scriptObjectSpecifier) } + @objc(scriptingSpecifierDescriptor) + func scriptingSpecifierDescriptor() -> NSScriptObjectSpecifier { + return (self.objectSpecifier ?? NSScriptObjectSpecifier() ) + } + // MARK: --- ScriptingObject protocol --- var scriptingKey: String { @@ -47,6 +53,141 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } + + + // MARK: --- Create Element Handlers --- + + class func parsedFeedForURL(_ urlString:String, _ completionHandler: @escaping (_ parsedFeed: ParsedFeed?) -> Void) { + guard let url = URL(string: urlString) else { + completionHandler(nil) + return + } + InitialFeedDownloader.download(url) { (parsedFeed) in + completionHandler(parsedFeed) + } + } + + class func urlForNewFeed(arguments:[String:Any]) -> String? { + var url:String? + if let withDataParam = arguments["ObjectData"] { + if let objectDataDescriptor = withDataParam as? NSAppleEventDescriptor { + url = objectDataDescriptor.stringValue + } + } else if let withPropsParam = arguments["ObjectProperties"] as? [String:Any] { + url = withPropsParam["url"] as? String + } + return url + } + + class func accountAndFolderForNewFeed(appleEvent:NSAppleEventDescriptor?) -> (Account, Folder?) { + var account = AccountManager.shared.localAccount + var folder:Folder? = nil + if let appleEvent = appleEvent { + var descriptorToConsider:NSAppleEventDescriptor? + if let insertionLocationDescriptor = appleEvent.paramDescriptor(forKeyword:keyAEInsertHere) { + print("insertionLocation : \(insertionLocationDescriptor)") + // insertion location can be a typeObjectSpecifier, e.g. 'in account "Acct"' + // or a typeInsertionLocation, e.g. 'at end of folder " + if (insertionLocationDescriptor.descriptorType == "insl".FourCharCode()) { + descriptorToConsider = insertionLocationDescriptor.forKeyword("kobj".FourCharCode()) + } else if ( insertionLocationDescriptor.descriptorType == "obj ".FourCharCode()) { + descriptorToConsider = insertionLocationDescriptor + } + } else if let subjectDescriptor = appleEvent.attributeDescriptor(forKeyword:"subj".FourCharCode()) { + descriptorToConsider = subjectDescriptor + } + + if let descriptorToConsider = descriptorToConsider { + guard let newContainerSpecifier = NSScriptObjectSpecifier(descriptor:descriptorToConsider) else {return (account, folder)} + let newContainer = newContainerSpecifier.objectsByEvaluatingSpecifier + if let scriptableAccount = newContainer as? ScriptableAccount { + account = scriptableAccount.account + } else if let scriptableFolder = newContainer as? ScriptableFolder { + if let folderAccount = scriptableFolder.folder.account { + folder = scriptableFolder.folder + account = folderAccount + } + } + } + print("found account : \(account)") + print("found folder : \(folder)") + } + return (account, folder) + } + + class func handleCreateElement(command:NSCreateCommand) -> Any? { + let appleEventManager = NSAppleEventManager.shared() + if let receivers = command.receiversSpecifier { + print("receivers : \(receivers)") + } + if let evaluatedReceivers = command.evaluatedReceivers { + print("evaluatedReceivers : \(evaluatedReceivers)") + } + if let evaluatedArguments = command.evaluatedArguments { + print("evaluatedArguments : \(evaluatedArguments)") + } + if let directObject = command.directParameter { + print("directObject : \(directObject)") + } + if let appleEvent = command.appleEvent { // keyDirectObject + print("appleEvent : \(appleEvent)") + if let subjectDescriptor = appleEvent.attributeDescriptor(forKeyword:"subj".FourCharCode()) { + print("subjectDescriptor : \(subjectDescriptor)") + let subjectObjectSpecifier = NSScriptObjectSpecifier(descriptor:subjectDescriptor) + let subjects = subjectObjectSpecifier?.objectsByEvaluatingSpecifier + print("resolvedSubjects : \(subjects)") + } + } + let commandDescription = command.commandDescription + print("commandDescription : \(commandDescription)") + + let (account, folder) = self.accountAndFolderForNewFeed(appleEvent:command.appleEvent) + let scriptableAccount = ScriptableAccount(account) + guard let arguments = command.arguments else {return nil} + guard let newObjectClass = arguments["ObjectClass"] as? Int else {return nil} + guard (newObjectClass.FourCharCode() == "Feed".FourCharCode()) else {return nil} + guard let url = self.urlForNewFeed(arguments:arguments) else {return nil} + + if let existingFeed = account.existingFeed(withURL:url) { + return ScriptableFeed(existingFeed, container:scriptableAccount) + } + + // at this point, we have to download the feed and parse it. + // RS Parser does the callback for the download on the main thread + // because we can't wait here (on the main thread, maybe) for the callback, we have to return from this function + // generally, the means handling the appleEvent is over, but to prevent the apple event from returning + // we call suspendExecution here. When we get the callback, we can resume execution + command.suspendExecution() + + self.parsedFeedForURL(url, { (parsedFeedOptional) in + if let parsedFeed = parsedFeedOptional { + let titleFromFeed = parsedFeed.title + let titleFromArgs = arguments["name"] as? String + + guard let feed = account.createFeed(with: titleFromFeed, editedName: titleFromArgs, url: url) else { + command.resumeExecution(withResult:nil) + return + } + account.update(feed, with:parsedFeed, {}) + + // add the feed, puttin git in a folder if needed + if account.addFeed(feed, to: folder) { + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) + } + + let resolvedKeyDictionary = command.resolvedKeyDictionary + print("resolvedKeyDictionary : \(resolvedKeyDictionary)") + let scriptableFeed = ScriptableFeed(feed, container:ScriptableAccount(account)) + command.resumeExecution(withResult:scriptableFeed.objectSpecifier) + } else { + command.resumeExecution(withResult:nil) + } + }) + return nil + } + + + // MARK: --- Scriptable properties --- diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 76d32af9b..e37f5242a 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -257,6 +257,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, let feed = Feed(accountID: accountID, url: url, feedID: url) feed.name = name feed.editedName = editedName + return feed }