NetNewsWire/Mac/Scriptability/Feed+Scriptability.swift

184 lines
6.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Feed+Scriptability.swift
// NetNewsWire
//
// Created by Olof Hellman on 1/10/18.
// Copyright © 2018 Olof Hellman. All rights reserved.
//
import Foundation
import Parser
import Account
import Articles
@objc(ScriptableFeed)
@objcMembers class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
let feed:Feed
let container:ScriptingObjectContainer
init (_ feed:Feed, container:ScriptingObjectContainer) {
self.feed = feed
self.container = container
}
@objc(objectSpecifier)
override var objectSpecifier: NSScriptObjectSpecifier? {
let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject:self)
return (scriptObjectSpecifier)
}
@objc(scriptingSpecifierDescriptor)
func scriptingSpecifierDescriptor() -> NSScriptObjectSpecifier {
return (self.objectSpecifier ?? NSScriptObjectSpecifier() )
}
// MARK: --- ScriptingObject protocol ---
var scriptingKey: String {
return "feeds"
}
// MARK: --- UniqueIdScriptingObject protocol ---
// I am not sure if account should prefer to be specified by name or by ID
// but in either case it seems like the accountID would be used as the keydata, so I chose ID
@objc(uniqueId)
@MainActor var scriptingUniqueId:Any {
return feed.feedID
}
// MARK: --- ScriptingObjectContainer protocol ---
var scriptingClassDescription: NSScriptClassDescription {
return self.classDescription as! NSScriptClassDescription
}
func deleteElement(_ element:ScriptingObject) {
}
// MARK: --- handle NSCreateCommand ---
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
}
@MainActor class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed {
let scriptableAccount = ScriptableAccount(account)
if let folder = folder {
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
return ScriptableFeed(feed, container:scriptableFolder)
} else {
return ScriptableFeed(feed, container:scriptableAccount)
}
}
@MainActor class func handleCreateElement(command:NSCreateCommand) -> Any? {
guard command.isCreateCommand(forClass:"Feed") else { return nil }
guard let arguments = command.arguments else {return nil}
let titleFromArgs = command.property(forKey:"name") as? String
let (account, folder) = command.accountAndFolderForNewChild()
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
if let existingFeed = account.existingFeed(withURL:url) {
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
}
let container: Container = folder != nil ? folder! : account
// We need to download the feed and parse it.
// RSParser does the callback for the download on the main thread.
// Because we can't wait here (on the main thread) for the callback, we have to return from this function.
// Generally, returning from an AppleEvent handler function means that handling the Apple event is over,
// but we dont yet have the result of the event yet, so we prevent the Apple event from returning by calling
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
command.suspendExecution()
Task { @MainActor in
do {
let feed = try await account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true)
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
} catch {
command.resumeExecution(withResult:nil)
}
}
return nil
}
// MARK: --- Scriptable properties ---
@objc(url)
@MainActor var url:String {
return self.feed.url
}
@objc(name)
@MainActor var name:String {
return self.feed.name ?? ""
}
@objc(homePageURL)
@MainActor var homePageURL:String {
return self.feed.homePageURL ?? ""
}
@objc(iconURL)
@MainActor var iconURL:String {
return self.feed.iconURL ?? ""
}
@objc(faviconURL)
@MainActor var faviconURL:String {
return self.feed.faviconURL ?? ""
}
@objc(opmlRepresentation)
var opmlRepresentation:String {
return self.feed.OPMLString(indentLevel:0)
}
// MARK: --- scriptable elements ---
@objc(authors)
@MainActor var authors:NSArray {
let feedAuthors = feed.authors ?? []
return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
}
@objc(valueInAuthorsWithUniqueID:)
@MainActor func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? {
guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil }
return ScriptableAuthor(author, container:self)
}
// @objc(articles)
// var articles:NSArray {
// let feedArticles = (try? feed.fetchArticles()) ?? Set<Article>()
// // the articles are a set, use the sorting algorithm from the viewer
// let sortedArticles = feedArticles.sorted(by:{
// return $0.logicalDatePublished > $1.logicalDatePublished
// })
// return sortedArticles.map { ScriptableArticle($0, container:self) } as NSArray
// }
// @objc(valueInArticlesWithUniqueID:)
// func valueInArticles(withUniqueID id:String) -> ScriptableArticle? {
// let articles = (try? feed.fetchArticles()) ?? Set<Article>()
// guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil }
// return ScriptableArticle(article, container:self)
// }
}