diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index faa9ed8..e1c8c0c 100755 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -35,6 +35,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele @IBOutlet weak var serversMenuItem: NSMenuItem! @IBOutlet var showQRCodeMenuItem: NSMenuItem! @IBOutlet var scanQRCodeMenuItem: NSMenuItem! + @IBOutlet var showBunchJsonExampleFileItem: NSMenuItem! + @IBOutlet var importBunchJsonFileItem: NSMenuItem! + @IBOutlet var exportAllServerProfileItem: NSMenuItem! @IBOutlet var serversPreferencesMenuItem: NSMenuItem! @IBOutlet weak var lanchAtLoginMenuItem: NSMenuItem! @@ -255,7 +258,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele ScanQRCodeOnScreen() } - @IBAction func toggleLaunghAtLogin(_ sender: NSMenuItem) { + @IBAction func showBunchJsonExampleFile(sender: NSMenuItem) { + ServerProfileManager.showExampleConfigFile() + } + + @IBAction func importBunchJsonFile(sender: NSMenuItem) { + ServerProfileManager.instance.importConfigFile() + //updateServersMenu()//not working + } + + @IBAction func exportAllServerProfile(sender: NSMenuItem) { + ServerProfileManager.instance.exportConfigFile() + } + + @IBAction func toggleLaunghAtLogin(sender: NSMenuItem) { launchAtLoginController.launchAtLogin = !launchAtLoginController.launchAtLogin; updateLaunchAtLoginMenu() } @@ -457,6 +473,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele let showQRItem = showQRCodeMenuItem let scanQRItem = scanQRCodeMenuItem let preferencesItem = serversPreferencesMenuItem + let showBunch = showBunchJsonExampleFileItem + let importBuntch = importBunchJsonFileItem + let exportAllServer = exportAllServerProfileItem var i = 0 for p in mgr.profiles { @@ -483,6 +502,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } serversMenuItem.submenu?.addItem(showQRItem!) serversMenuItem.submenu?.addItem(scanQRItem!) + serversMenuItem.submenu?.addItem(showBunch!) + serversMenuItem.submenu?.addItem(importBuntch!) + serversMenuItem.submenu?.addItem(exportAllServer!) serversMenuItem.submenu?.addItem(NSMenuItem.separator()) serversMenuItem.submenu?.addItem(preferencesItem!) } diff --git a/ShadowsocksX-NG/Base.lproj/MainMenu.xib b/ShadowsocksX-NG/Base.lproj/MainMenu.xib index be83a01..4e1d334 100755 --- a/ShadowsocksX-NG/Base.lproj/MainMenu.xib +++ b/ShadowsocksX-NG/Base.lproj/MainMenu.xib @@ -1,5 +1,5 @@ - + @@ -16,14 +16,17 @@ + - + + + @@ -110,6 +113,24 @@ + + + + + + + + + + + + + + + + + + @@ -135,7 +156,7 @@ - + diff --git a/ShadowsocksX-NG/ServerProfileManager.swift b/ShadowsocksX-NG/ServerProfileManager.swift index c3ee9ac..32e4733 100644 --- a/ShadowsocksX-NG/ServerProfileManager.swift +++ b/ShadowsocksX-NG/ServerProfileManager.swift @@ -2,7 +2,7 @@ // ServerProfileManager.swift // ShadowsocksX-NG // -// Created by 邱宇舟 on 16/6/6. +// Created by 邱宇舟 on 16/6/6. Modified by 秦宇航 16/9/12 // Copyright © 2016年 qiuyuzhou. All rights reserved. // @@ -70,4 +70,114 @@ class ServerProfileManager: NSObject { return nil } } + + func importConfigFile() { + let openPanel = NSOpenPanel() + openPanel.title = "Choose Config Json File".localized + openPanel.allowsMultipleSelection = false + openPanel.canChooseDirectories = false + openPanel.canCreateDirectories = false + openPanel.canChooseFiles = true + openPanel.becomeKey() + openPanel.begin { (result) -> Void in + if (result == NSFileHandlingPanelOKButton && (openPanel.url) != nil) { + let fileManager = FileManager.default + let filePath:String = (openPanel.url?.path)! + if (fileManager.fileExists(atPath: filePath) && filePath.hasSuffix("json")) { + let data = fileManager.contents(atPath: filePath) + let readString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)! + let readStringData = readString.data(using: String.Encoding.utf8.rawValue) + + let jsonArr1 = try! JSONSerialization.jsonObject(with: readStringData!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary + + for item in jsonArr1.object(forKey: "configs") as! [[String: AnyObject]]{ + let profile = ServerProfile() + profile.serverHost = item["server"] as! String + profile.serverPort = UInt16((item["server_port"]?.integerValue)!) + profile.method = item["method"] as! String + profile.password = item["password"] as! String + profile.remark = item["remarks"] as! String + self.profiles.append(profile) + self.save() + NotificationCenter.default.post(name: NSNotification.Name(rawValue: NOTIFY_SERVER_PROFILES_CHANGED), object: nil) + } + let configsCount = (jsonArr1.object(forKey: "configs") as! [[String: AnyObject]]).count + let notification = NSUserNotification() + notification.title = "Import Server Profile succeed!".localized + notification.informativeText = "Successful import \(configsCount) items".localized + NSUserNotificationCenter.default + .deliver(notification) + }else{ + let notification = NSUserNotification() + notification.title = "Import Server Profile failed!".localized + notification.informativeText = "Invalid config file!".localized + NSUserNotificationCenter.default + .deliver(notification) + return + } + } + } + } + + func exportConfigFile() { + //读取example文件,删掉configs里面的配置,再用NSDictionary填充到configs里面 + let fileManager = FileManager.default + + let filePath:String = Bundle.main.bundlePath + "/Contents/Resources/example-gui-config.json" + let data = fileManager.contents(atPath: filePath) + let readString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)! + let readStringData = readString.data(using: String.Encoding.utf8.rawValue) + let jsonArr1 = try! JSONSerialization.jsonObject(with: readStringData!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary + + let configsArray:NSMutableArray = [] //not using var? + + for profile in profiles{ + let configProfile:NSMutableDictionary = [:] //not using var? + //standard ss profile + configProfile.setValue(true, forKey: "enable") + configProfile.setValue(profile.serverHost, forKey: "server") + configProfile.setValue(NSNumber(value:profile.serverPort), forKey: "server_port")//not work + configProfile.setValue(profile.password, forKey: "password") + configProfile.setValue(profile.method, forKey: "method") + configProfile.setValue(profile.remark, forKey: "remarks") + configProfile.setValue(profile.remark.data(using: String.Encoding.utf8)?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)), forKey: "remarks_base64") + configsArray.add(configProfile) + } + jsonArr1.setValue(configsArray, forKey: "configs") + let jsonData = try! JSONSerialization.data(withJSONObject: jsonArr1, options: JSONSerialization.WritingOptions.prettyPrinted) + let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String + let savePanel = NSSavePanel() + savePanel.title = "Export Config Json File".localized + savePanel.canCreateDirectories = true + savePanel.allowedFileTypes = ["json"] + savePanel.nameFieldStringValue = "export.json" + savePanel.becomeKey() + savePanel.begin { (result) -> Void in + if (result == NSFileHandlingPanelOKButton && (savePanel.url) != nil) { + //write jsonArr1 back to file + try! jsonString.write(toFile: (savePanel.url?.path)!, atomically: true, encoding: String.Encoding.utf8) + NSWorkspace.shared().selectFile((savePanel.url?.path)!, inFileViewerRootedAtPath: (savePanel.directoryURL?.path)!) + let notification = NSUserNotification() + notification.title = "Export Server Profile succeed!".localized + notification.informativeText = "Successful Export \(self.profiles.count) items".localized + NSUserNotificationCenter.default + .deliver(notification) + } + } + } + + class func showExampleConfigFile() { + //copy file to ~/Downloads folder + let filePath:String = Bundle.main.bundlePath + "/Contents/Resources/example-gui-config.json" + let fileMgr = FileManager.default + let dataPath = NSHomeDirectory() + "/Downloads" + let destPath = dataPath + "/example-gui-config.json" + //检测文件是否已经存在,如果存在直接用sharedWorkspace显示 + if fileMgr.fileExists(atPath: destPath) { + NSWorkspace.shared().selectFile(destPath, inFileViewerRootedAtPath: dataPath) + }else{ + try! fileMgr.copyItem(atPath: filePath, toPath: destPath) + NSWorkspace.shared().selectFile(destPath, inFileViewerRootedAtPath: dataPath) + } + } } diff --git a/ShadowsocksX-NG/example-gui-config.json b/ShadowsocksX-NG/example-gui-config.json new file mode 100644 index 0000000..937e1e3 --- /dev/null +++ b/ShadowsocksX-NG/example-gui-config.json @@ -0,0 +1,44 @@ +{ + "index": 0, + "random": false, + "global": false, + "enabled": true, + "shareOverLan": false, + "isDefault": false, + "localPort": 1080, + "pacUrl": null, + "useOnlinePac": false, + "reconnectTimes": 3, + "randomAlgorithm": 0, + "TTL": 0, + "proxyEnable": false, + "proxyType": 0, + "proxyHost": null, + "proxyPort": 0, + "proxyAuthUser": null, + "proxyAuthPass": null, + "authUser": null, + "authPass": null, + "autoban": false, + "configs": [{ + "remarks": "example", + "server": "abc.xyz", + "server_port": 1234, + "method": "rc4-md5", + "remarks_base64": "ZXhhbXBsZQ==", + "password": "passwd", + "tcp_over_udp": false, + "udp_over_tcp": false, + "enable": true + },{ + "remarks": "example2", + "server": "xyz.xyz", + "server_port": 1234, + "method": "rc4-md5", + "remarks_base64": "ZXhhbXBsZTI=", + "password": "passwd", + "tcp_over_udp": false, + "udp_over_tcp": false, + "enable": true + }] +} \ No newline at end of file diff --git a/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings b/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings index 99145f2..655994d 100644 --- a/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings +++ b/ShadowsocksX-NG/zh-Hans.lproj/MainMenu.strings @@ -80,3 +80,11 @@ /* Class = "NSMenuItem"; title = "HTTP Proxy Preference ..."; ObjectID = "uEp-Gz-cu0"; */ "uEp-Gz-cu0.title" = "HTTP代理设置..."; +/* Class = "NSMenuItem"; title = "Show Bunch Json Example File..."; ObjectID = "pdy-JE-50Q"; */ +"pdy-JE-50Q.title" = "显示示例服务器配置文件..."; + +/* Class = "NSMenuItem"; title = "Import Bunch Json File..."; ObjectID = "T9g-gy-gvv"; */ +"T9g-gy-gvv.title" = "导入服务器配置文件..."; + +/* Class = "NSMenuItem"; title = "Export All Server To Json..."; ObjectID = "6k0-gn-DQv"; */ +"6k0-gn-DQv.title" = "导出全部服务器配置...";