mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge branch 'extension-point'
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Secrets
|
||||
|
||||
public enum ArticleExtractorState {
|
||||
case ready
|
||||
|
||||
@@ -235,6 +235,25 @@ blockquote {
|
||||
max-height: 1em;
|
||||
}
|
||||
|
||||
/* Twitter */
|
||||
|
||||
.twitterAvatar {
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.twitterUsername {
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.twitterTimestamp {
|
||||
font-size: 66%;
|
||||
}
|
||||
|
||||
/*Block ads and junk*/
|
||||
|
||||
iframe[src*="feedads"],
|
||||
|
||||
60
Shared/ExtensionPoints/ExtensionPoint.swift
Normal file
60
Shared/ExtensionPoints/ExtensionPoint.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// ExtensionPoint.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 4/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import RSCore
|
||||
|
||||
protocol ExtensionPoint {
|
||||
|
||||
static var isSinglton: Bool { get }
|
||||
static var isDeveloperBuildRestricted: Bool { get }
|
||||
static var title: String { get }
|
||||
static var templateImage: RSImage { get }
|
||||
static var description: NSAttributedString { get }
|
||||
|
||||
var title: String { get }
|
||||
var extensionPointID: ExtensionPointIdentifer { get }
|
||||
|
||||
}
|
||||
|
||||
extension ExtensionPoint {
|
||||
|
||||
var templateImage: RSImage {
|
||||
return extensionPointID.extensionPointType.templateImage
|
||||
}
|
||||
|
||||
var description: NSAttributedString {
|
||||
return extensionPointID.extensionPointType.description
|
||||
}
|
||||
|
||||
static func makeAttrString(_ text: String) -> NSMutableAttributedString {
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.alignment = .center
|
||||
|
||||
#if os(macOS)
|
||||
let attrs = [
|
||||
NSAttributedString.Key.paragraphStyle: paragraphStyle,
|
||||
NSAttributedString.Key.font: NSFont.systemFont(ofSize: NSFont.systemFontSize),
|
||||
NSAttributedString.Key.foregroundColor: NSColor.textColor
|
||||
]
|
||||
#else
|
||||
let attrs = [
|
||||
NSAttributedString.Key.paragraphStyle: paragraphStyle,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body),
|
||||
NSAttributedString.Key.foregroundColor: UIColor.label
|
||||
]
|
||||
#endif
|
||||
|
||||
return NSMutableAttributedString(string: text, attributes: attrs)
|
||||
}
|
||||
|
||||
}
|
||||
85
Shared/ExtensionPoints/ExtensionPointIdentifer.swift
Normal file
85
Shared/ExtensionPoints/ExtensionPointIdentifer.swift
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// ExtensionPointIdentifer.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 4/8/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
enum ExtensionPointIdentifer: Hashable {
|
||||
#if os(macOS)
|
||||
case marsEdit
|
||||
case microblog
|
||||
#endif
|
||||
case twitter(String)
|
||||
|
||||
var extensionPointType: ExtensionPoint.Type {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
return SendToMarsEditCommand.self
|
||||
case .microblog:
|
||||
return SendToMicroBlogCommand.self
|
||||
#endif
|
||||
case .twitter:
|
||||
return TwitterFeedProvider.self
|
||||
}
|
||||
}
|
||||
|
||||
public var userInfo: [AnyHashable: AnyHashable] {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
return [
|
||||
"type": "marsEdit"
|
||||
]
|
||||
case .microblog:
|
||||
return [
|
||||
"type": "microblog"
|
||||
]
|
||||
#endif
|
||||
case .twitter(let screenName):
|
||||
return [
|
||||
"type": "twitter",
|
||||
"screenName": screenName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public init?(userInfo: [AnyHashable: AnyHashable]) {
|
||||
guard let type = userInfo["type"] as? String else { return nil }
|
||||
|
||||
switch type {
|
||||
#if os(macOS)
|
||||
case "marsEdit":
|
||||
self = ExtensionPointIdentifer.marsEdit
|
||||
case "microblog":
|
||||
self = ExtensionPointIdentifer.microblog
|
||||
#endif
|
||||
case "twitter":
|
||||
guard let screenName = userInfo["screenName"] as? String else { return nil }
|
||||
self = ExtensionPointIdentifer.twitter(screenName)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
hasher.combine("marsEdit")
|
||||
case .microblog:
|
||||
hasher.combine("microblog")
|
||||
#endif
|
||||
case .twitter(let screenName):
|
||||
hasher.combine("twitter")
|
||||
hasher.combine(screenName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
142
Shared/ExtensionPoints/ExtensionPointManager.swift
Normal file
142
Shared/ExtensionPoints/ExtensionPointManager.swift
Normal file
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// ExtensionPointManager.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 4/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import RSCore
|
||||
import OAuthSwift
|
||||
|
||||
public extension Notification.Name {
|
||||
static let ActiveExtensionPointsDidChange = Notification.Name(rawValue: "ActiveExtensionPointsDidChange")
|
||||
}
|
||||
|
||||
final class ExtensionPointManager: FeedProviderManagerDelegate {
|
||||
|
||||
static let shared = ExtensionPointManager()
|
||||
|
||||
var activeExtensionPoints = [ExtensionPointIdentifer: ExtensionPoint]()
|
||||
let possibleExtensionPointTypes: [ExtensionPoint.Type]
|
||||
var availableExtensionPointTypes: [ExtensionPoint.Type] {
|
||||
|
||||
let activeExtensionPointTypes = activeExtensionPoints.keys.compactMap({ ObjectIdentifier($0.extensionPointType) })
|
||||
var available = [ExtensionPoint.Type]()
|
||||
for possibleExtensionPointType in possibleExtensionPointTypes {
|
||||
if !(AppDefaults.isDeveloperBuild && possibleExtensionPointType.isDeveloperBuildRestricted) {
|
||||
if possibleExtensionPointType.isSinglton {
|
||||
if !activeExtensionPointTypes.contains(ObjectIdentifier(possibleExtensionPointType)) {
|
||||
available.append(possibleExtensionPointType)
|
||||
}
|
||||
} else {
|
||||
available.append(possibleExtensionPointType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
|
||||
}
|
||||
|
||||
var activeSendToCommands: [SendToCommand] {
|
||||
return activeExtensionPoints.values.compactMap({ return $0 as? SendToCommand })
|
||||
}
|
||||
|
||||
var activeFeedProviders: [FeedProvider] {
|
||||
return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider })
|
||||
}
|
||||
|
||||
init() {
|
||||
#if os(macOS)
|
||||
#if DEBUG
|
||||
possibleExtensionPointTypes = [SendToMarsEditCommand.self, SendToMicroBlogCommand.self, TwitterFeedProvider.self]
|
||||
#else
|
||||
possibleExtensionPointTypes = [SendToMarsEditCommand.self, SendToMicroBlogCommand.self, TwitterFeedProvider.self]
|
||||
#endif
|
||||
#else
|
||||
#if DEBUG
|
||||
possibleExtensionPointTypes = [TwitterFeedProvider.self]
|
||||
#else
|
||||
possibleExtensionPointTypes = [TwitterFeedProvider.self]
|
||||
#endif
|
||||
#endif
|
||||
loadExtensionPoints()
|
||||
}
|
||||
|
||||
func activateExtensionPoint(_ extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess? = nil) {
|
||||
if let extensionPoint = self.extensionPoint(for: extensionPointType, tokenSuccess: tokenSuccess) {
|
||||
activeExtensionPoints[extensionPoint.extensionPointID] = extensionPoint
|
||||
saveExtensionPointIDs()
|
||||
}
|
||||
}
|
||||
|
||||
func deactivateExtensionPoint(_ extensionPointID: ExtensionPointIdentifer) {
|
||||
activeExtensionPoints[extensionPointID] = nil
|
||||
saveExtensionPointIDs()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension ExtensionPointManager {
|
||||
|
||||
func loadExtensionPoints() {
|
||||
if let extensionPointUserInfos = AppDefaults.activeExtensionPointIDs {
|
||||
for extensionPointUserInfo in extensionPointUserInfos {
|
||||
if let extensionPointID = ExtensionPointIdentifer(userInfo: extensionPointUserInfo) {
|
||||
activeExtensionPoints[extensionPointID] = extensionPoint(for: extensionPointID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveExtensionPointIDs() {
|
||||
AppDefaults.activeExtensionPointIDs = activeExtensionPoints.keys.map({ $0.userInfo })
|
||||
NotificationCenter.default.post(name: .ActiveExtensionPointsDidChange, object: nil, userInfo: nil)
|
||||
}
|
||||
|
||||
func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?) -> ExtensionPoint? {
|
||||
switch extensionPointType {
|
||||
#if os(macOS)
|
||||
case is SendToMarsEditCommand.Type:
|
||||
return SendToMarsEditCommand()
|
||||
case is SendToMicroBlogCommand.Type:
|
||||
return SendToMicroBlogCommand()
|
||||
#endif
|
||||
case is TwitterFeedProvider.Type:
|
||||
if let tokenSuccess = tokenSuccess {
|
||||
return TwitterFeedProvider(tokenSuccess: tokenSuccess)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
assertionFailure("Unrecognized Extension Point Type.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? {
|
||||
switch extensionPointID {
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
return SendToMarsEditCommand()
|
||||
case .microblog:
|
||||
return SendToMicroBlogCommand()
|
||||
#endif
|
||||
case .twitter(let screenName):
|
||||
return TwitterFeedProvider(screenName: screenName)
|
||||
}
|
||||
}
|
||||
|
||||
func feedProviderMatching(_ offered: URLComponents, ability: FeedProviderAbility) -> FeedProvider? {
|
||||
for extensionPoint in activeExtensionPoints.values {
|
||||
if let feedProvider = extensionPoint as? FeedProvider, feedProvider.ability(offered) == ability {
|
||||
return feedProvider
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,10 +10,28 @@ import AppKit
|
||||
import RSCore
|
||||
import Articles
|
||||
|
||||
final class SendToMarsEditCommand: SendToCommand {
|
||||
|
||||
let title = "MarsEdit"
|
||||
final class SendToMarsEditCommand: ExtensionPoint, SendToCommand {
|
||||
|
||||
static var isSinglton = true
|
||||
static var isDeveloperBuildRestricted = false
|
||||
static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit")
|
||||
static var templateImage = AppAssets.extensionPointMarsEdit
|
||||
static var description: NSAttributedString = {
|
||||
let attrString = SendToMarsEditCommand.makeAttrString("This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.")
|
||||
let range = NSRange(location: 81, length: 8)
|
||||
attrString.beginEditing()
|
||||
attrString.addAttribute(NSAttributedString.Key.link, value: "https://red-sweater.com/marsedit/", range: range)
|
||||
attrString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.systemBlue, range: range)
|
||||
attrString.endEditing()
|
||||
return attrString
|
||||
}()
|
||||
|
||||
let extensionPointID = ExtensionPointIdentifer.marsEdit
|
||||
|
||||
var title: String {
|
||||
return extensionPointID.extensionPointType.title
|
||||
}
|
||||
|
||||
var image: NSImage? {
|
||||
return appToUse()?.icon ?? nil
|
||||
}
|
||||
@@ -12,10 +12,29 @@ import RSCore
|
||||
|
||||
// Not undoable.
|
||||
|
||||
final class SendToMicroBlogCommand: SendToCommand {
|
||||
final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand {
|
||||
|
||||
let title = "Micro.blog"
|
||||
static var isSinglton = true
|
||||
static var isDeveloperBuildRestricted = false
|
||||
static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog")
|
||||
static var templateImage = AppAssets.extensionPointMicroblog
|
||||
static var description: NSAttributedString = {
|
||||
let attrString = SendToMicroBlogCommand.makeAttrString("This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.")
|
||||
let range = NSRange(location: 81, length: 10)
|
||||
attrString.beginEditing()
|
||||
attrString.addAttribute(NSAttributedString.Key.link, value: "https://micro.blog", range: range)
|
||||
attrString.addAttribute(NSAttributedString.Key.foregroundColor, value: NSColor.systemBlue, range: range)
|
||||
attrString.endEditing()
|
||||
return attrString
|
||||
|
||||
}()
|
||||
|
||||
let extensionPointID = ExtensionPointIdentifer.microblog
|
||||
|
||||
var title: String {
|
||||
return extensionPointID.extensionPointType.title
|
||||
}
|
||||
|
||||
var image: NSImage? {
|
||||
return microBlogApp.icon
|
||||
}
|
||||
30
Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift
Normal file
30
Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// TwitterFeedProvider+Extensions.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 4/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
extension TwitterFeedProvider: ExtensionPoint {
|
||||
|
||||
static var isSinglton = false
|
||||
static var isDeveloperBuildRestricted = true
|
||||
static var title = NSLocalizedString("Twitter", comment: "Twitter")
|
||||
static var templateImage = AppAssets.extensionPointTwitter
|
||||
static var description: NSAttributedString = {
|
||||
return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.")
|
||||
}()
|
||||
|
||||
var extensionPointID: ExtensionPointIdentifer {
|
||||
return ExtensionPointIdentifer.twitter(screenName)
|
||||
}
|
||||
|
||||
var title: String {
|
||||
return "@\(screenName)"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,10 +28,11 @@ struct CacheCleaner {
|
||||
let tempDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
||||
let faviconsFolderURL = tempDir.appendingPathComponent("Favicons")
|
||||
let imagesFolderURL = tempDir.appendingPathComponent("Images")
|
||||
let feedURLToIconURL = tempDir.appendingPathComponent("FeedURLToIconURLCache.plist")
|
||||
let homePageToIconURL = tempDir.appendingPathComponent("HomePageToIconURLCache.plist")
|
||||
let homePagesWithNoIconURL = tempDir.appendingPathComponent("HomePagesWithNoIconURLCache.plist")
|
||||
|
||||
for tempItem in [faviconsFolderURL, imagesFolderURL, homePageToIconURL, homePagesWithNoIconURL] {
|
||||
for tempItem in [faviconsFolderURL, imagesFolderURL, feedURLToIconURL, homePageToIconURL, homePagesWithNoIconURL] {
|
||||
do {
|
||||
os_log(.info, log: self.log, "Removing cache file: %@", tempItem.absoluteString)
|
||||
try FileManager.default.removeItem(at: tempItem)
|
||||
@@ -24,6 +24,14 @@ public final class WebFeedIconDownloader {
|
||||
|
||||
private let imageDownloader: ImageDownloader
|
||||
|
||||
private var feedURLToIconURLCache = [String: String]()
|
||||
private var feedURLToIconURLCachePath: String
|
||||
private var feedURLToIconURLCacheDirty = false {
|
||||
didSet {
|
||||
queueSaveFeedURLToIconURLCacheIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private var homePageToIconURLCache = [String: String]()
|
||||
private var homePageToIconURLCachePath: String
|
||||
private var homePageToIconURLCacheDirty = false {
|
||||
@@ -47,11 +55,13 @@ public final class WebFeedIconDownloader {
|
||||
private var urlsInProgress = Set<String>()
|
||||
private var cache = [WebFeed: IconImage]()
|
||||
private var waitingForFeedURLs = [String: WebFeed]()
|
||||
|
||||
|
||||
init(imageDownloader: ImageDownloader, folder: String) {
|
||||
self.imageDownloader = imageDownloader
|
||||
self.feedURLToIconURLCachePath = (folder as NSString).appendingPathComponent("FeedURLToIconURLCache.plist")
|
||||
self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist")
|
||||
self.homePagesWithNoIconURLCachePath = (folder as NSString).appendingPathComponent("HomePagesWithNoIconURLCache.plist")
|
||||
loadFeedURLToIconURLCache()
|
||||
loadHomePageToIconURLCache()
|
||||
loadHomePagesWithNoIconURLCache()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
|
||||
@@ -82,22 +92,51 @@ public final class WebFeedIconDownloader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let iconURL = feed.iconURL {
|
||||
icon(forURL: iconURL, feed: feed) { (image) in
|
||||
|
||||
func checkFeedIconURL() {
|
||||
if let iconURL = feed.iconURL {
|
||||
icon(forURL: iconURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
} else {
|
||||
checkHomePageURL()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkHomePageURL()
|
||||
}
|
||||
}
|
||||
|
||||
if let feedProviderURL = feedURLToIconURLCache[feed.url] {
|
||||
self.icon(forURL: feedProviderURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
else {
|
||||
checkHomePageURL()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if let components = URLComponents(string: feed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
|
||||
feedProvider.iconURL(components) { result in
|
||||
switch result {
|
||||
case .success(let feedProviderURL):
|
||||
self.feedURLToIconURLCache[feed.url] = feedProviderURL
|
||||
self.feedURLToIconURLCacheDirty = true
|
||||
self.icon(forURL: feedProviderURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
}
|
||||
case .failure:
|
||||
checkFeedIconURL()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkFeedIconURL()
|
||||
}
|
||||
else {
|
||||
checkHomePageURL()
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -110,6 +149,12 @@ public final class WebFeedIconDownloader {
|
||||
_ = icon(for: feed)
|
||||
}
|
||||
|
||||
@objc func saveFeedURLToIconURLCacheIfNeeded() {
|
||||
if feedURLToIconURLCacheDirty {
|
||||
saveFeedURLToIconURLCache()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func saveHomePageToIconURLCacheIfNeeded() {
|
||||
if homePageToIconURLCacheDirty {
|
||||
saveHomePageToIconURLCache()
|
||||
@@ -200,6 +245,15 @@ private extension WebFeedIconDownloader {
|
||||
homePagesWithNoIconURLCacheDirty = true
|
||||
}
|
||||
|
||||
func loadFeedURLToIconURLCache() {
|
||||
let url = URL(fileURLWithPath: feedURLToIconURLCachePath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
return
|
||||
}
|
||||
let decoder = PropertyListDecoder()
|
||||
feedURLToIconURLCache = (try? decoder.decode([String: String].self, from: data)) ?? [String: String]()
|
||||
}
|
||||
|
||||
func loadHomePageToIconURLCache() {
|
||||
let url = URL(fileURLWithPath: homePageToIconURLCachePath)
|
||||
guard let data = try? Data(contentsOf: url) else {
|
||||
@@ -219,6 +273,10 @@ private extension WebFeedIconDownloader {
|
||||
homePagesWithNoIconURLCache = Set(decoded)
|
||||
}
|
||||
|
||||
func queueSaveFeedURLToIconURLCacheIfNeeded() {
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
}
|
||||
@@ -227,6 +285,20 @@ private extension WebFeedIconDownloader {
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func saveFeedURLToIconURLCache() {
|
||||
feedURLToIconURLCacheDirty = false
|
||||
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
let url = URL(fileURLWithPath: feedURLToIconURLCachePath)
|
||||
do {
|
||||
let data = try encoder.encode(feedURLToIconURLCache)
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func saveHomePageToIconURLCache() {
|
||||
homePageToIconURLCacheDirty = false
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// Generated by Secrets.swift.gyb
|
||||
%{
|
||||
import os
|
||||
|
||||
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET']
|
||||
|
||||
def chunks(seq, size):
|
||||
return (seq[i:(i + size)] for i in range(0, len(seq), size))
|
||||
|
||||
def encode(string, salt):
|
||||
bytes = string.encode("UTF-8")
|
||||
return [ord(bytes[i]) ^ salt[i % len(salt)] for i in range(0, len(bytes))]
|
||||
|
||||
def snake_to_camel(snake_str):
|
||||
components = snake_str.split('_')
|
||||
return components[0].lower() + ''.join(x.title() for x in components[1:])
|
||||
|
||||
salt = [ord(byte) for byte in os.urandom(64)]
|
||||
}%
|
||||
public enum Secrets {
|
||||
% for secret in secrets:
|
||||
|
||||
public static var ${snake_to_camel(secret)}: String {
|
||||
let encoded: [UInt8] = [
|
||||
% for chunk in chunks(encode(os.environ.get(secret) or "", salt), 8):
|
||||
${"".join(["0x%02x, " % byte for byte in chunk])}
|
||||
% end
|
||||
]
|
||||
|
||||
return decode(encoded, salt: salt)
|
||||
}
|
||||
% end
|
||||
|
||||
%{
|
||||
# custom example: static let myVariable = "${os.environ.get('MY_CUSTOM_VARIABLE')}"
|
||||
}%
|
||||
}
|
||||
|
||||
private extension Secrets {
|
||||
|
||||
private static let salt: [UInt8] = [
|
||||
% for chunk in chunks(salt, 8):
|
||||
${"".join(["0x%02x, " % byte for byte in chunk])}
|
||||
% end
|
||||
]
|
||||
|
||||
private static func decode(_ encoded: [UInt8], salt: [UInt8]) -> String {
|
||||
String(decoding: encoded.enumerated().map { (offset, element) in
|
||||
element ^ salt[offset % salt.count]
|
||||
}, as: UTF8.self)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user