Skip to content

Commit

Permalink
improve: optimize config refresh strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
yichengchen committed Apr 25, 2020
1 parent a0a246b commit 69e344b
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 90 deletions.
4 changes: 4 additions & 0 deletions ClashX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
4913C82321157D0200F6B87C /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4913C82221157D0200F6B87C /* Notification.swift */; };
492C4869210EE6B9004554A0 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492C4868210EE6B9004554A0 /* ApiRequest.swift */; };
492C4871210EF62E004554A0 /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492C4870210EF62E004554A0 /* ClashConfig.swift */; };
493A9F282453E60400D35296 /* ProxyDelayHistoryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493A9F272453E60400D35296 /* ProxyDelayHistoryMenu.swift */; };
493AEAE3221AE3420016FE98 /* AppVersionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493AEAE2221AE3420016FE98 /* AppVersionUtil.swift */; };
493AEAE5221AE7230016FE98 /* ProxyMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493AEAE4221AE7230016FE98 /* ProxyMenuItem.swift */; };
4949D154213242F600EF85E6 /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4949D153213242F600EF85E6 /* Paths.swift */; };
Expand Down Expand Up @@ -117,6 +118,7 @@
4913C82221157D0200F6B87C /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
492C4868210EE6B9004554A0 /* ApiRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = "<group>"; };
492C4870210EF62E004554A0 /* ClashConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashConfig.swift; sourceTree = "<group>"; };
493A9F272453E60400D35296 /* ProxyDelayHistoryMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyDelayHistoryMenu.swift; sourceTree = "<group>"; };
493AEAE2221AE3420016FE98 /* AppVersionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionUtil.swift; sourceTree = "<group>"; };
493AEAE4221AE7230016FE98 /* ProxyMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyMenuItem.swift; sourceTree = "<group>"; };
4949D153213242F600EF85E6 /* Paths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -310,6 +312,7 @@
495340B220DE68C300B0D3FF /* StatusItemView.swift */,
495340AF20DE5F7200B0D3FF /* StatusItemView.xib */,
F910AA23240134AF00116E95 /* ProxyGroupMenu.swift */,
493A9F272453E60400D35296 /* ProxyDelayHistoryMenu.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -666,6 +669,7 @@
4913C82321157D0200F6B87C /* Notification.swift in Sources */,
F9203A26236342820020D57D /* AppDelegate+..swift in Sources */,
499A485C22ED793C00F6C675 /* NSView+Nib.swift in Sources */,
493A9F282453E60400D35296 /* ProxyDelayHistoryMenu.swift in Sources */,
492C4871210EF62E004554A0 /* ClashConfig.swift in Sources */,
492C4869210EE6B9004554A0 /* ApiRequest.swift in Sources */,
49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */,
Expand Down
37 changes: 14 additions & 23 deletions ClashX/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {

self.proxyModeMenuItem.title = "\(NSLocalizedString("Proxy Mode", comment: "")) (\(config.mode.name))"

MenuItemFactory.refreshMenuItems()

if old?.port != config.port && ConfigManager.shared.proxyPortAutoSet {
SystemProxyManager.shared.enableProxy(port: config.port, socksPort: config.socketPort)
}
Expand All @@ -205,16 +203,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

}.disposed(by: disposeBag)

ConfigManager
.shared
.isRunningVariable
.asObservable()
.distinctUntilChanged()
.filter { $0 }
.bind { _ in
MenuItemFactory.refreshMenuItems()
}.disposed(by: disposeBag)
}

func checkOnlyOneClashX() {
Expand Down Expand Up @@ -277,16 +265,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}.disposed(by: disposeBag)
}

func updateProxyList() {
if ConfigManager.shared.isRunning {
MenuItemFactory.refreshMenuItems { [weak self] items in
self?.updateProxyList(withMenus: items)
}
} else {
updateProxyList(withMenus: [])
}
}

func updateProxyList(withMenus menus: [NSMenuItem]) {
let startIndex = statusMenu.items.firstIndex(of: separatorLineTop)! + 1
let endIndex = statusMenu.items.firstIndex(of: sepatatorLineEndProxySelect)!
Expand Down Expand Up @@ -396,6 +374,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
ConfigManager.selectConfigName = newConfigName
}
self.selectProxyGroupWithMemory()
MenuItemFactory.recreateProxyMenuItems()
NotificationCenter.default.post(name: .reloadDashboard, object: nil)
}
}
Expand Down Expand Up @@ -737,10 +716,22 @@ extension AppDelegate {

extension AppDelegate: NSMenuDelegate {
func menuNeedsUpdate(_ menu: NSMenu) {
updateProxyList()
MenuItemFactory.refreshExistingMenuItems()
updateConfigFiles()
syncConfig()
}

func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {
menu.items.forEach {
($0.view as? ProxyGroupMenuHighlightDelegate)?.highlight(item: item)
}
}

func menuDidClose(_ menu: NSMenu) {
menu.items.forEach {
($0.view as? ProxyGroupMenuHighlightDelegate)?.highlight(item: nil)
}
}
}

// MARK: URL Scheme
Expand Down
1 change: 0 additions & 1 deletion ClashX/General/Managers/ClashResourceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import AppKit
import Foundation

class ClashResourceManager {

static func check() -> Bool {
checkConfigDir()
checkMMDB()
Expand Down
134 changes: 82 additions & 52 deletions ClashX/General/Managers/MenuItemFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import SwiftyJSON

class MenuItemFactory {
private static var cachedProxyMenuItem: [NSMenuItem]?
private static var cachedProxyData: ClashProxyResp?

private static var showSpeedTestItemAtTop: Bool = UserDefaults.standard.object(forKey: "kShowSpeedTestItemAtTop") as? Bool ?? AppDelegate.isAboveMacOS14 {
didSet {
UserDefaults.standard.set(showSpeedTestItemAtTop, forKey: "kShowSpeedTestItemAtTop")
Expand All @@ -26,15 +28,67 @@ class MenuItemFactory {

// MARK: - Public

static func refreshMenuItems(completionHandler: (([NSMenuItem]) -> Void)? = nil) {
if ConfigManager.shared.currentConfig?.mode == .direct {
completionHandler?([])
return
static func refreshExistingMenuItems() {
let previousInfo = cachedProxyData
getMergedProxyData {
info in
if info?.proxiesMap.keys != previousInfo?.proxiesMap.keys {
// force update menu
refreshMenuItems(mergedData: info)
return
}

for proxy in info?.proxies ?? [] {
NotificationCenter.default.post(name: .proxyUpdate(for: proxy.name), object: proxy, userInfo: nil)
}
}
}

static func recreateProxyMenuItems() {
getMergedProxyData {
proxyInfo in
refreshMenuItems(mergedData: proxyInfo)
}
if let cached = cachedProxyMenuItem {
completionHandler?(cached)
}

static func refreshMenuItems(mergedData proxyInfo: ClashProxyResp?) {
guard let proxyInfo = proxyInfo else { return }
var menuItems = [NSMenuItem]()
for proxy in proxyInfo.proxyGroups {
var menu: NSMenuItem?
switch proxy.type {
case .select: menu = generateSelectorMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
case .urltest, .fallback: menu = generateUrlTestFallBackMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
case .loadBalance:
menu = generateLoadBalanceMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
case .relay:
menu = generateListOnlyMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
default: continue
}

if let menu = menu {
menuItems.append(menu)
menu.isEnabled = true
}
}
let items = Array(menuItems.reversed())
updateProxyList(withMenus: items)
}

static func generateSwitchConfigMenuItems() -> [NSMenuItem] {
var items = [NSMenuItem]()
for config in ConfigManager.getConfigFilesList() {
let item = NSMenuItem(title: config, action: #selector(MenuItemFactory.actionSelectConfig(sender:)), keyEquivalent: "")
item.target = MenuItemFactory.self
item.state = ConfigManager.selectConfigName == config ? .on : .off
items.append(item)
}
return items
}

// MARK: - Private

private static func getMergedProxyData(complete: ((ClashProxyResp?) -> Void)? = nil) {
let group = DispatchGroup()
group.enter()
group.enter()
Expand All @@ -45,31 +99,12 @@ class MenuItemFactory {
group.notify(queue: .main) {
guard let proxyInfo = proxyInfo, let proxyprovider = provider else {
assertionFailure()
complete?(nil)
return
}
proxyInfo.updateProvider(proxyprovider)

var menuItems = [NSMenuItem]()
for proxy in proxyInfo.proxyGroups {
var menu: NSMenuItem?
switch proxy.type {
case .select: menu = self.generateSelectorMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
case .urltest, .fallback: menu = generateUrlTestFallBackMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
case .loadBalance:
menu = generateLoadBalanceMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
case .relay:
menu = generateListOnlyMenuItem(proxyGroup: proxy, proxyInfo: proxyInfo)
default: continue
}

if let menu = menu {
menuItems.append(menu)
menu.isEnabled = true
}
}
let items = Array(menuItems.reversed())
cachedProxyMenuItem = items
completionHandler?(items)
cachedProxyData = proxyInfo
complete?(proxyInfo)
}

ApiRequest.requestProxyProviderList {
Expand All @@ -85,18 +120,22 @@ class MenuItemFactory {
}
}

static func generateSwitchConfigMenuItems() -> [NSMenuItem] {
var items = [NSMenuItem]()
for config in ConfigManager.getConfigFilesList() {
let item = NSMenuItem(title: config, action: #selector(MenuItemFactory.actionSelectConfig(sender:)), keyEquivalent: "")
item.target = MenuItemFactory.self
item.state = ConfigManager.selectConfigName == config ? .on : .off
items.append(item)
// MARK: Updaters

static func updateProxyList(withMenus menus: [NSMenuItem]) {
let app = AppDelegate.shared
let startIndex = app.statusMenu.items.firstIndex(of: app.separatorLineTop)! + 1
let endIndex = app.statusMenu.items.firstIndex(of: app.sepatatorLineEndProxySelect)!
app.sepatatorLineEndProxySelect.isHidden = menus.count == 0
for _ in 0..<endIndex - startIndex {
app.statusMenu.removeItem(at: startIndex)
}
for each in menus {
app.statusMenu.insertItem(each, at: startIndex)
}
return items
}

// MARK: - Private
// MARK: Generators

private static func generateSelectorMenuItem(proxyGroup: ClashProxy,
proxyInfo: ClashProxyResp) -> NSMenuItem? {
Expand Down Expand Up @@ -157,9 +196,7 @@ class MenuItemFactory {
proxyMenuItem.state = .on
}

if let historyMenu = generateHistoryMenu(proxy) {
proxyMenuItem.submenu = historyMenu
}
proxyMenuItem.submenu = ProxyDelayHistoryMenu(proxy: proxy)

submenu.addItem(proxyMenuItem)
}
Expand All @@ -182,15 +219,6 @@ class MenuItemFactory {
(menu as? ProxyGroupMenu)?.add(delegate: speedTestItem)
}

private static func generateHistoryMenu(_ proxy: ClashProxy) -> NSMenu? {
let historyMenu = NSMenu(title: "")
for his in proxy.history.reversed() {
historyMenu.addItem(
NSMenuItem(title: "\(his.dateDisplay) \(his.delayDisplay)", action: nil, keyEquivalent: ""))
}
return historyMenu.items.count > 0 ? historyMenu : nil
}

private static func generateLoadBalanceMenuItem(proxyGroup: ClashProxy, proxyInfo: ClashProxyResp) -> NSMenuItem? {
let proxyMap = proxyInfo.proxiesMap

Expand Down Expand Up @@ -265,13 +293,15 @@ extension MenuItemFactory {
@objc static func optionSpeedtestMenuItemTap(sender: NSMenuItem) {
showSpeedTestItemAtTop = !showSpeedTestItemAtTop
updateSpeedtestMenuItemStatus(sender)
refreshMenuItems()
refreshExistingMenuItems()
recreateProxyMenuItems()
}

@objc static func optionUseViewRenderMenuItemTap(sender: NSMenuItem) {
useViewToRenderProxy = !useViewToRenderProxy
updateUseViewRenderMenuItem(sender)
refreshMenuItems()
refreshExistingMenuItems()
recreateProxyMenuItems()
}
}

Expand All @@ -297,7 +327,7 @@ extension MenuItemFactory {
// terminal Connections for this group
ConnectionManager.closeConnection(for: proxyGroup)
// refresh menu items
MenuItemFactory.refreshMenuItems()
MenuItemFactory.refreshExistingMenuItems()
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions ClashX/Macro/Notification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ extension Notification.Name {
static let reloadDashboard = Notification.Name("kReloadDashboard")
static let systemNetworkStatusIPUpdate = Notification.Name("systemNetworkStatusIPUpdate")
static let systemNetworkStatusDidChange = Notification.Name("kSystemNetworkStatusDidChange")

static func proxyUpdate(for name: ClashProxyName) -> Notification.Name {
return Notification.Name("kProxyUpdate_\(name)")
}
}
2 changes: 2 additions & 0 deletions ClashX/Models/ClashProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class ClashProxySpeedHistory: Codable {
lazy var dateDisplay: String = {
return hisDateFormaterInstance.shared.formater.string(from: time)
}()

lazy var displayString: String = "\(dateDisplay) \(delayDisplay)"
}

class ClashProxy: Codable {
Expand Down
61 changes: 61 additions & 0 deletions ClashX/Views/ProxyDelayHistoryMenu.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// ProxyDelayHistoryMenu.swift
// ClashX
//
// Created by yicheng on 2020/4/25.
// Copyright © 2020 west2online. All rights reserved.
//

import Cocoa
import FlexibleDiff

class ProxyDelayHistoryMenu: NSMenu {
var currentHistory: [ClashProxySpeedHistory]?

init(proxy: ClashProxy) {
super.init(title: "")
updateHistoryMenu(proxy: proxy)
NotificationCenter.default.addObserver(self, selector: #selector(proxyInfoDidUpdate(note:)), name: .proxyUpdate(for: proxy.name), object: nil)
}

required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

deinit {
NotificationCenter.default.removeObserver(self)
}

@objc private func proxyInfoDidUpdate(note: Notification) {
guard let info = note.object as? ClashProxy else { return }
updateHistoryMenu(proxy: info)
}

private func updateHistoryMenu(proxy: ClashProxy) {
let historys = Array(proxy.history.reversed())
let change = Changeset(previous: currentHistory, current: historys, identifier: { $0.time })
currentHistory = historys
if change.moves.count == 0 && change.mutations.count == 0 {
change.removals.reversed().forEach { idx in
removeItem(at: idx)
}
change.inserts.forEach { idx in
let his = historys[idx]
let item = NSMenuItem(title: his.displayString, action: nil, keyEquivalent: "")
insertItem(item, at: idx)
}
} else {
historys.map { his in
NSMenuItem(title: his.displayString, action: nil, keyEquivalent: "")
}.forEach { item in
addItem(item)
}
}
}
}

extension ClashProxySpeedHistory: Equatable {
static func == (lhs: ClashProxySpeedHistory, rhs: ClashProxySpeedHistory) -> Bool {
return lhs.displayString == rhs.displayString
}
}
Loading

0 comments on commit 69e344b

Please sign in to comment.