diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift
index 79b625d..2456cdb 100644
--- a/ShadowsocksX-NG/AppDelegate.swift
+++ b/ShadowsocksX-NG/AppDelegate.swift
@@ -365,6 +365,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
}
@IBAction func updateSubscribe(_ sender: NSMenuItem) {
+ SubscribeManager.instance.updateAllServerFromSubscribe()
}
@IBAction func updateSubscribeAtLaunch(_ sender: NSMenuItem) {
diff --git a/ShadowsocksX-NG/Subscribe.swift b/ShadowsocksX-NG/Subscribe.swift
index ed65ba5..ab44661 100644
--- a/ShadowsocksX-NG/Subscribe.swift
+++ b/ShadowsocksX-NG/Subscribe.swift
@@ -16,6 +16,7 @@ class Subscribe: NSObject{
var maxCount = -1 // -1 is not limited
var groupName = ""
var token = ""
+ var cache = ""
var profileMgr: ServerProfileManager!
@@ -55,6 +56,35 @@ class Subscribe: NSObject{
func getMaxCount() -> Int {
return maxCount
}
+ static func fromDictionary(_ data:[String:AnyObject]) -> Subscribe {
+ var feed:String = ""
+ var group:String = ""
+ var token:String = ""
+ var maxCount:Int = -1
+ for (key, value) in data {
+ switch key {
+ case "feed":
+ feed = value as! String
+ case "group":
+ group = value as! String
+ case "token":
+ token = value as! String
+ case "maxCount":
+ maxCount = value as! Int
+ default:
+ print("")
+ }
+ }
+ return Subscribe.init(initUrlString: feed, initGroupName: group, initToken: token, initMaxCount: maxCount)
+ }
+ static func toDictionary(_ data: Subscribe) -> [String: AnyObject] {
+ var ret : [String: AnyObject] = [:]
+ ret["feed"] = data.subscribeFeed as AnyObject
+ ret["group"] = data.groupName as AnyObject
+ ret["token"] = data.token as AnyObject
+ ret["maxCount"] = data.maxCount as AnyObject
+ return ret
+ }
fileprivate func sendRequest(url: String, options: Any, callback: @escaping (String) -> Void) {
let headers: HTTPHeaders = [
// "Authorization": "Basic U2hhZG93c29ja1gtTkctUg==",
@@ -76,8 +106,8 @@ class Subscribe: NSObject{
}
}
func setMaxCount() {
- sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
-
+
+ func getMaxFromRes(resString: String) {
let maxCountReg = "MAX=[0-9]+"
let decodeRes = decode64(resString)!
let range = decodeRes.range(of: maxCountReg, options: .regularExpression)
@@ -88,12 +118,19 @@ class Subscribe: NSObject{
else{
self.maxCount = -1
}
-
+ }
+
+ if cache != "" {
+ return getMaxFromRes(resString: cache)
+ }
+ sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
+ getMaxFromRes(resString: resString)
+ self.cache = resString
})
}
func updateServerFromFeed(){
- if (!isActive){ return }
- sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
+
+ func updateServerHandler(resString: String) {
let decodeRes = decode64(resString)!
let ssrregexp = "ssr://([A-Za-z0-9_-]+)"
let urls = splitor(url: decodeRes, regexp: ssrregexp)
@@ -108,6 +145,15 @@ class Subscribe: NSObject{
}
self.profileMgr.save()
(NSApplication.shared().delegate as! AppDelegate).updateServersMenu()
+ }
+
+ if (!isActive){ return }
+ if cache != "" {
+ return updateServerHandler(resString: cache)
+ }
+ sendRequest(url: self.subscribeFeed, options: "", callback: { resString in
+ updateServerHandler(resString: resString)
+ self.cache = resString
})
}
func feedValidator() -> Bool{
@@ -117,13 +163,31 @@ class Subscribe: NSObject{
fileprivate func pushNotification(){
}
- fileprivate func isExisted() -> Bool{
+ fileprivate func isExisted(profile: ServerProfile) -> Bool{
// using to url judge
// if existed update
+ for (_, value) in profileMgr.profiles.enumerated() {
+ let ret = value.serverHost == profile.serverHost
+ if ret {
+ return ret
+ }
+ }
return false
}
- fileprivate func isDuplicated() -> Bool{
+ fileprivate func isDuplicated(profile: ServerProfile) -> Bool{
// if duplicated skip
+ for (_, value) in profileMgr.profiles.enumerated() {
+ let ret = value.serverHost == profile.serverHost
+ && value.password == profile.password
+ && value.serverPort == profile.serverPort
+ && value.ssrProtocol == profile.ssrProtocol
+ && value.ssrObfs == profile.ssrObfs
+ && value.ssrObfsParam == value.ssrObfsParam
+ && value.ssrProtocolParam == value.ssrProtocolParam
+ if ret {
+ return ret
+ }
+ }
return false
}
fileprivate func isSame(source: Subscribe, target: Subscribe) -> Bool {
diff --git a/ShadowsocksX-NG/SubscribeManager.swift b/ShadowsocksX-NG/SubscribeManager.swift
index 80b2d9a..758b6f5 100644
--- a/ShadowsocksX-NG/SubscribeManager.swift
+++ b/ShadowsocksX-NG/SubscribeManager.swift
@@ -12,14 +12,21 @@ class SubscribeManager:NSObject{
static let instance:SubscribeManager = SubscribeManager()
var subscribes:[Subscribe]
+ var subscribesDefault : [[String: AnyObject]]
let defaults = UserDefaults.standard
fileprivate override init() {
- subscribes = defaults.array(forKey: "Subscribes") as! [Subscribe]
+ subscribes = []
+ subscribesDefault = [[:]]
+ if let subscribesDefault = defaults.array(forKey: "Subscribes") {
+ for value in subscribesDefault{
+ subscribes.append(Subscribe.fromDictionary(value as! [String : AnyObject]))
+ }
+ }
}
func addSubscribe(oneSubscribe: Subscribe) -> Bool {
subscribes.append(oneSubscribe)
- defaults.set(subscribes, forKey: "Subscribes")
+ defaults.set(subscribesToDefaults(data: subscribes), forKey: "Subscribes")
return true
}
func deleteSubscribe(toDeleteSubscribe: Subscribe) -> Bool {
@@ -28,10 +35,26 @@ class SubscribeManager:NSObject{
subscribes.remove(at: index)
}
})
- defaults.set(subscribes, forKey: "Subscribes")
+ defaults.set(subscribesToDefaults(data: subscribes), forKey: "Subscribes")
return true
}
+ fileprivate func subscribesToDefaults(data: [Subscribe]) -> [[String: AnyObject]]{
+ var ret : [[String: AnyObject]] = []
+ for value in data {
+ ret.append(Subscribe.toDictionary(value))
+ }
+ return ret
+ }
+ fileprivate func DefaultsToSubscribes(data:[[String: AnyObject]]) -> [Subscribe] {
+ var ret : [Subscribe] = []
+ for value in data{
+ ret.append(Subscribe.fromDictionary(value))
+ }
+ return ret
+ }
func updateAllServerFromSubscribe(){
-
+ subscribes.forEach{ value in
+ value.updateServerFromFeed()
+ }
}
}
diff --git a/ShadowsocksX-NG/SubscribePreferenceWindowController.swift b/ShadowsocksX-NG/SubscribePreferenceWindowController.swift
index 6de3f0b..0a2d486 100644
--- a/ShadowsocksX-NG/SubscribePreferenceWindowController.swift
+++ b/ShadowsocksX-NG/SubscribePreferenceWindowController.swift
@@ -8,7 +8,8 @@
import Cocoa
-class SubscribePreferenceWindowController: NSWindowController {
+class SubscribePreferenceWindowController: NSWindowController
+ , NSTableViewDataSource, NSTableViewDelegate {
@IBOutlet weak var FeedLabel: NSTextField!
@IBOutlet weak var OKButton: NSButton!
@@ -17,22 +18,25 @@ class SubscribePreferenceWindowController: NSWindowController {
@IBOutlet weak var TokenTextField: NSTextField!
@IBOutlet weak var GroupTextField: NSTextField!
@IBOutlet weak var MaxCountTextField: NSTextField!
+ @IBOutlet weak var SubscribeTableView: NSTableView!
+
var sbMgr: SubscribeManager!
var defaults: UserDefaults!
+ let tableViewDragType: String = "subscribe.host"
+ var editingSubscribe: Subscribe!
override func windowDidLoad() {
super.windowDidLoad()
- // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
- // TODO load stored subscribes and prepare the manager
sbMgr = SubscribeManager.instance
defaults = UserDefaults.standard
+ SubscribeTableView.reloadData()
}
override func awakeFromNib() {
-// profilesTableView.register(forDraggedTypes: [tableViewDragType])
-// profilesTableView.allowsMultipleSelection = true
+ SubscribeTableView.register(forDraggedTypes: [tableViewDragType])
+ SubscribeTableView.allowsMultipleSelection = true
}
@IBAction func onOk(_ sender: NSButton) {
@@ -42,14 +46,167 @@ class SubscribePreferenceWindowController: NSWindowController {
_ = sbMgr.addSubscribe(oneSubscribe: subscribe)
}
subscribe.updateServerFromFeed()
+ SubscribeTableView.reloadData()
window?.performClose(self)
}
- func bindSubscribe(index: Int){
- if index >= 0 && index <= SubscribeManager.instance.subscribes.count {
- var editSubscribe = SubscribeManager.instance.subscribes[index]
- FeedTextField.bind("value", to: editSubscribe, withKeyPath: "subscribeFeed", options: [NSContinuouslyUpdatesValueBindingOption: true])
+ func bindSubscribe(_ index:Int) {
+ if index >= 0 && index < sbMgr.subscribes.count {
+ editingSubscribe = sbMgr.subscribes[index]
+
+ FeedTextField.bind("value", to: editingSubscribe, withKeyPath: "subscribeFeed", options: [NSContinuouslyUpdatesValueBindingOption: true])
+ TokenTextField.bind("value", to: editingSubscribe, withKeyPath: "token", options: [NSContinuouslyUpdatesValueBindingOption: true])
+ GroupTextField.bind("value", to: editingSubscribe, withKeyPath: "groupName", options: [NSContinuouslyUpdatesValueBindingOption: true])
+ MaxCountTextField.bind("value", to: editingSubscribe, withKeyPath: "maxCount", options: [NSContinuouslyUpdatesValueBindingOption: true])
+
+ } else {
+ editingSubscribe = nil
+ FeedTextField.unbind("value")
+ TokenTextField.unbind("value")
+ GroupTextField.unbind("value")
+ MaxCountTextField.unbind("value")
}
}
+ func getDataAtRow(_ index:Int) -> String {
+ return sbMgr.subscribes[index].groupName
+ }
+
+ // MARK: For NSTableViewDataSource
+
+ func numberOfRows(in tableView: NSTableView) -> Int {
+ if let mgr = sbMgr {
+ return mgr.subscribes.count
+ }
+ return 0
+ }
+
+ func tableView(_ tableView: NSTableView
+ , objectValueFor tableColumn: NSTableColumn?
+ , row: Int) -> Any? {
+
+ let title = getDataAtRow(row)
+
+ if tableColumn?.identifier == "main" {
+ if title != "" {return title}
+ else {return "S"}
+ } else if tableColumn?.identifier == "status" {
+ return NSImage(named: "menu_icon")
+ }
+ return ""
+ }
+
+ // MARK: Drag & Drop reorder rows
+
+ func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
+ let item = NSPasteboardItem()
+ item.setString(String(row), forType: tableViewDragType)
+ return item
+ }
+
+ func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int
+ , proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
+ if dropOperation == .above {
+ return .move
+ }
+ return NSDragOperation()
+ }
+
+ func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo
+ , row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
+ if let mgr = sbMgr {
+ var oldIndexes = [Int]()
+ info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
+ if let str = ($0.0.item as! NSPasteboardItem).string(forType: self.tableViewDragType), let index = Int(str) {
+ oldIndexes.append(index)
+ }
+ }
+
+ var oldIndexOffset = 0
+ var newIndexOffset = 0
+
+ // For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
+ // You may want to move rows in your content array and then call `tableView.reloadData()` instead.
+ tableView.beginUpdates()
+ for oldIndex in oldIndexes {
+ if oldIndex < row {
+ let o = mgr.subscribes.remove(at: oldIndex + oldIndexOffset)
+ mgr.subscribes.insert(o, at:row - 1)
+ tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
+ oldIndexOffset -= 1
+ } else {
+ let o = mgr.subscribes.remove(at: oldIndex)
+ mgr.subscribes.insert(o, at:row + newIndexOffset)
+ tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
+ newIndexOffset += 1
+ }
+ }
+ tableView.endUpdates()
+
+ return true
+ }
+ return false
+ }
+
+ //--------------------------------------------------
+ // For NSTableViewDelegate
+
+ func tableView(_ tableView: NSTableView
+ , shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
+ return false
+ }
+
+ func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
+ if row < 0 {
+ editingSubscribe = nil
+ return true
+ }
+// if editingSubscribe != nil {
+// if !editingSubscribe.isValid() {
+// return false
+// }
+// }
+
+ return true
+ }
+
+ func tableViewSelectionDidChange(_ notification: Notification) {
+ if SubscribeTableView.selectedRow >= 0 {
+ bindSubscribe(SubscribeTableView.selectedRow)
+ if (SubscribeTableView.selectedRowIndexes.count > 1){
+// duplicateProfileButton.isEnabled = false
+ } else {
+// duplicateProfileButton.isEnabled = true
+ }
+ } else {
+ if !sbMgr.subscribes.isEmpty {
+ let index = IndexSet(integer: sbMgr.subscribes.count - 1)
+ SubscribeTableView.selectRowIndexes(index, byExtendingSelection: false)
+ }
+ }
+ }
+
+ func shakeWindows(){
+ let numberOfShakes:Int = 8
+ let durationOfShake:Float = 0.5
+ let vigourOfShake:Float = 0.05
+
+ let frame:CGRect = (window?.frame)!
+ let shakeAnimation = CAKeyframeAnimation()
+
+ let shakePath = CGMutablePath()
+
+ shakePath.move(to: CGPoint(x:NSMinX(frame), y:NSMinY(frame)))
+
+ for _ in 1...numberOfShakes{
+ shakePath.addLine(to: CGPoint(x: NSMinX(frame) - frame.size.width * CGFloat(vigourOfShake), y: NSMinY(frame)))
+ shakePath.addLine(to: CGPoint(x: NSMinX(frame) + frame.size.width * CGFloat(vigourOfShake), y: NSMinY(frame)))
+ }
+
+ shakePath.closeSubpath()
+ shakeAnimation.path = shakePath
+ shakeAnimation.duration = CFTimeInterval(durationOfShake)
+ window?.animations = ["frameOrigin":shakeAnimation]
+ window?.animator().setFrameOrigin(window!.frame.origin)
+ }
}
diff --git a/ShadowsocksX-NG/SubscribePreferenceWindowController.xib b/ShadowsocksX-NG/SubscribePreferenceWindowController.xib
index 3c23cdd..26bd490 100644
--- a/ShadowsocksX-NG/SubscribePreferenceWindowController.xib
+++ b/ShadowsocksX-NG/SubscribePreferenceWindowController.xib
@@ -13,6 +13,7 @@
+
@@ -36,32 +37,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -131,18 +106,19 @@
-
+
-
+
+
+
-
+
-
@@ -162,33 +138,90 @@ DQ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
+
+
+
+
+
+
+
-
+
+
+
-
+
-
-
+
-