You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ShadowsocksController.cs 24 kB

7 years ago
11 years ago
7 years ago
11 years ago
11 years ago
11 years ago
10 years ago
7 years ago
11 years ago
11 years ago
11 years ago
7 years ago
7 years ago
7 years ago
11 years ago
7 years ago
11 years ago
7 years ago
7 years ago
11 years ago
11 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
10 years ago
10 years ago
7 years ago
10 years ago
7 years ago
11 years ago
7 years ago
7 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Web;
  12. using System.Windows.Forms;
  13. using NLog;
  14. using Shadowsocks.Controller.Service;
  15. using Shadowsocks.Controller.Strategy;
  16. using Shadowsocks.Model;
  17. using Shadowsocks.Util;
  18. namespace Shadowsocks.Controller
  19. {
  20. public class ShadowsocksController
  21. {
  22. private static Logger logger = LogManager.GetCurrentClassLogger();
  23. // controller:
  24. // handle user actions
  25. // manipulates UI
  26. // interacts with low level logic
  27. #region Members definition
  28. private Thread _ramThread;
  29. private Thread _trafficThread;
  30. private Listener _listener;
  31. private PACDaemon _pacDaemon;
  32. private PACServer _pacServer;
  33. private Configuration _config;
  34. private StrategyManager _strategyManager;
  35. private PrivoxyRunner privoxyRunner;
  36. private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer;
  37. public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance;
  38. public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
  39. private long _inboundCounter = 0;
  40. private long _outboundCounter = 0;
  41. public long InboundCounter => Interlocked.Read(ref _inboundCounter);
  42. public long OutboundCounter => Interlocked.Read(ref _outboundCounter);
  43. public Queue<TrafficPerSecond> trafficPerSecondQueue;
  44. private bool stopped = false;
  45. public class PathEventArgs : EventArgs
  46. {
  47. public string Path;
  48. }
  49. public class UpdatedEventArgs : EventArgs
  50. {
  51. public string OldVersion;
  52. public string NewVersion;
  53. }
  54. public class TrafficPerSecond
  55. {
  56. public long inboundCounter;
  57. public long outboundCounter;
  58. public long inboundIncreasement;
  59. public long outboundIncreasement;
  60. }
  61. public event EventHandler ConfigChanged;
  62. public event EventHandler EnableStatusChanged;
  63. public event EventHandler EnableGlobalChanged;
  64. public event EventHandler ShareOverLANStatusChanged;
  65. public event EventHandler VerboseLoggingStatusChanged;
  66. public event EventHandler ShowPluginOutputChanged;
  67. public event EventHandler TrafficChanged;
  68. // when user clicked Edit PAC, and PAC file has already created
  69. public event EventHandler<PathEventArgs> PACFileReadyToOpen;
  70. public event EventHandler<PathEventArgs> UserRuleFileReadyToOpen;
  71. public event EventHandler<GeositeResultEventArgs> UpdatePACFromGeositeCompleted;
  72. public event ErrorEventHandler UpdatePACFromGeositeError;
  73. public event ErrorEventHandler Errored;
  74. // Invoked when controller.Start();
  75. public event EventHandler<UpdatedEventArgs> ProgramUpdated;
  76. #endregion
  77. public ShadowsocksController()
  78. {
  79. _config = Configuration.Load();
  80. StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
  81. _strategyManager = new StrategyManager(this);
  82. _pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
  83. StartReleasingMemory();
  84. StartTrafficStatistics(61);
  85. ProgramUpdated += (o, e) =>
  86. {
  87. logger.Info($"Updated from {e.OldVersion} to {e.NewVersion}");
  88. };
  89. }
  90. #region Basic
  91. public void Start(bool regHotkeys = true)
  92. {
  93. if (_config.updated && regHotkeys)
  94. {
  95. _config.updated = false;
  96. ProgramUpdated.Invoke(this, new UpdatedEventArgs()
  97. {
  98. OldVersion = _config.version,
  99. NewVersion = UpdateChecker.Version,
  100. });
  101. Configuration.Save(_config);
  102. }
  103. Reload();
  104. if (regHotkeys)
  105. {
  106. HotkeyReg.RegAllHotkeys();
  107. }
  108. }
  109. public void Stop()
  110. {
  111. if (stopped)
  112. {
  113. return;
  114. }
  115. stopped = true;
  116. if (_listener != null)
  117. {
  118. _listener.Stop();
  119. }
  120. StopPlugins();
  121. if (privoxyRunner != null)
  122. {
  123. privoxyRunner.Stop();
  124. }
  125. if (_config.enabled)
  126. {
  127. SystemProxy.Update(_config, true, null);
  128. }
  129. Encryption.RNG.Close();
  130. }
  131. protected void Reload()
  132. {
  133. Encryption.RNG.Reload();
  134. // some logic in configuration updated the config when saving, we need to read it again
  135. _config = Configuration.Load();
  136. NLogConfig.LoadConfiguration();
  137. StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
  138. privoxyRunner = privoxyRunner ?? new PrivoxyRunner();
  139. _pacDaemon = _pacDaemon ?? new PACDaemon(_config);
  140. _pacDaemon.PACFileChanged += PacDaemon_PACFileChanged;
  141. _pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged;
  142. _pacServer = _pacServer ?? new PACServer(_pacDaemon);
  143. _pacServer.UpdatePACURL(_config); // So PACServer works when system proxy disabled.
  144. GeositeUpdater.ResetEvent();
  145. GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted;
  146. GeositeUpdater.Error += PacServer_PACUpdateError;
  147. availabilityStatistics.UpdateConfiguration(this);
  148. _listener?.Stop();
  149. StopPlugins();
  150. // don't put PrivoxyRunner.Start() before pacServer.Stop()
  151. // or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
  152. // though UseShellExecute is set to true now
  153. // http://stackoverflow.com/questions/10235093/socket-doesnt-close-after-application-exits-if-a-launched-process-is-open
  154. privoxyRunner.Stop();
  155. try
  156. {
  157. var strategy = GetCurrentStrategy();
  158. strategy?.ReloadServers();
  159. StartPlugin();
  160. privoxyRunner.Start(_config);
  161. TCPRelay tcpRelay = new TCPRelay(this, _config);
  162. tcpRelay.OnConnected += UpdateLatency;
  163. tcpRelay.OnInbound += UpdateInboundCounter;
  164. tcpRelay.OnOutbound += UpdateOutboundCounter;
  165. tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server);
  166. UDPRelay udpRelay = new UDPRelay(this);
  167. List<Listener.IService> services = new List<Listener.IService>
  168. {
  169. tcpRelay,
  170. udpRelay,
  171. _pacServer,
  172. new PortForwarder(privoxyRunner.RunningPort)
  173. };
  174. _listener = new Listener(services);
  175. _listener.Start(_config);
  176. }
  177. catch (Exception e)
  178. {
  179. // translate Microsoft language into human language
  180. // i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use
  181. if (e is SocketException se)
  182. {
  183. if (se.SocketErrorCode == SocketError.AddressAlreadyInUse)
  184. {
  185. e = new Exception(I18N.GetString("Port {0} already in use", _config.localPort), e);
  186. }
  187. else if (se.SocketErrorCode == SocketError.AccessDenied)
  188. {
  189. e = new Exception(I18N.GetString("Port {0} is reserved by system", _config.localPort), e);
  190. }
  191. }
  192. logger.LogUsefulException(e);
  193. ReportError(e);
  194. }
  195. ConfigChanged?.Invoke(this, new EventArgs());
  196. UpdateSystemProxy();
  197. Utils.ReleaseMemory(true);
  198. }
  199. protected void SaveConfig(Configuration newConfig)
  200. {
  201. Configuration.Save(newConfig);
  202. Reload();
  203. }
  204. protected void ReportError(Exception e)
  205. {
  206. Errored?.Invoke(this, new ErrorEventArgs(e));
  207. }
  208. public Server GetCurrentServer()
  209. {
  210. return _config.GetCurrentServer();
  211. }
  212. // always return copy
  213. public Configuration GetConfigurationCopy()
  214. {
  215. return Configuration.Load();
  216. }
  217. // always return current instance
  218. public Configuration GetCurrentConfiguration()
  219. {
  220. return _config;
  221. }
  222. public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint)
  223. {
  224. IStrategy strategy = GetCurrentStrategy();
  225. if (strategy != null)
  226. {
  227. return strategy.GetAServer(type, localIPEndPoint, destEndPoint);
  228. }
  229. if (_config.index < 0)
  230. {
  231. _config.index = 0;
  232. }
  233. return GetCurrentServer();
  234. }
  235. public void SaveServers(List<Server> servers, int localPort, bool portableMode)
  236. {
  237. _config.configs = servers;
  238. _config.localPort = localPort;
  239. _config.portableMode = portableMode;
  240. Configuration.Save(_config);
  241. }
  242. public void SelectServerIndex(int index)
  243. {
  244. _config.index = index;
  245. _config.strategy = null;
  246. SaveConfig(_config);
  247. }
  248. public void ToggleShareOverLAN(bool enabled)
  249. {
  250. _config.shareOverLan = enabled;
  251. SaveConfig(_config);
  252. ShareOverLANStatusChanged?.Invoke(this, new EventArgs());
  253. }
  254. #endregion
  255. #region OS Proxy
  256. public void ToggleEnable(bool enabled)
  257. {
  258. _config.enabled = enabled;
  259. SaveConfig(_config);
  260. EnableStatusChanged?.Invoke(this, new EventArgs());
  261. }
  262. public void ToggleGlobal(bool global)
  263. {
  264. _config.global = global;
  265. SaveConfig(_config);
  266. EnableGlobalChanged?.Invoke(this, new EventArgs());
  267. }
  268. public void SaveProxy(ProxyConfig proxyConfig)
  269. {
  270. _config.proxy = proxyConfig;
  271. SaveConfig(_config);
  272. }
  273. private void UpdateSystemProxy()
  274. {
  275. SystemProxy.Update(_config, false, _pacServer);
  276. }
  277. #endregion
  278. #region PAC
  279. private void PacDaemon_PACFileChanged(object sender, EventArgs e)
  280. {
  281. UpdateSystemProxy();
  282. }
  283. private void PacServer_PACUpdateCompleted(object sender, GeositeResultEventArgs e)
  284. {
  285. UpdatePACFromGeositeCompleted?.Invoke(this, e);
  286. }
  287. private void PacServer_PACUpdateError(object sender, ErrorEventArgs e)
  288. {
  289. UpdatePACFromGeositeError?.Invoke(this, e);
  290. }
  291. private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };
  292. private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e)
  293. {
  294. GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode);
  295. UpdateSystemProxy();
  296. }
  297. public void CopyPacUrl()
  298. {
  299. Clipboard.SetDataObject(_pacServer.PacUrl);
  300. }
  301. public void SavePACUrl(string pacUrl)
  302. {
  303. _config.pacUrl = pacUrl;
  304. SaveConfig(_config);
  305. ConfigChanged?.Invoke(this, new EventArgs());
  306. }
  307. public void UseOnlinePAC(bool useOnlinePac)
  308. {
  309. _config.useOnlinePac = useOnlinePac;
  310. SaveConfig(_config);
  311. ConfigChanged?.Invoke(this, new EventArgs());
  312. }
  313. public void TouchPACFile()
  314. {
  315. string pacFilename = _pacDaemon.TouchPACFile();
  316. PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename });
  317. }
  318. public void TouchUserRuleFile()
  319. {
  320. string userRuleFilename = _pacDaemon.TouchUserRuleFile();
  321. UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename });
  322. }
  323. public void ToggleSecureLocalPac(bool enabled)
  324. {
  325. _config.secureLocalPac = enabled;
  326. SaveConfig(_config);
  327. ConfigChanged?.Invoke(this, new EventArgs());
  328. }
  329. #endregion
  330. #region SIP002
  331. public bool AskAddServerBySSURL(string ssURL)
  332. {
  333. var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo);
  334. if (dr == DialogResult.Yes)
  335. {
  336. if (AddServerBySSURL(ssURL))
  337. {
  338. MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL));
  339. return true;
  340. }
  341. else
  342. {
  343. MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid."));
  344. }
  345. }
  346. return false;
  347. }
  348. public bool AddServerBySSURL(string ssURL)
  349. {
  350. try
  351. {
  352. if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace())
  353. return false;
  354. var servers = Server.GetServers(ssURL);
  355. if (servers == null || servers.Count == 0)
  356. return false;
  357. foreach (var server in servers)
  358. {
  359. _config.configs.Add(server);
  360. }
  361. _config.index = _config.configs.Count - 1;
  362. SaveConfig(_config);
  363. return true;
  364. }
  365. catch (Exception e)
  366. {
  367. logger.LogUsefulException(e);
  368. return false;
  369. }
  370. }
  371. public string GetServerURLForCurrentServer()
  372. {
  373. return GetCurrentServer().GetURL(_config.generateLegacyUrl);
  374. }
  375. #endregion
  376. #region Misc
  377. public void ToggleVerboseLogging(bool enabled)
  378. {
  379. _config.isVerboseLogging = enabled;
  380. SaveConfig(_config);
  381. NLogConfig.LoadConfiguration(); // reload nlog
  382. VerboseLoggingStatusChanged?.Invoke(this, new EventArgs());
  383. }
  384. public void ToggleCheckingUpdate(bool enabled)
  385. {
  386. _config.autoCheckUpdate = enabled;
  387. Configuration.Save(_config);
  388. ConfigChanged?.Invoke(this, new EventArgs());
  389. }
  390. public void ToggleCheckingPreRelease(bool enabled)
  391. {
  392. _config.checkPreRelease = enabled;
  393. Configuration.Save(_config);
  394. ConfigChanged?.Invoke(this, new EventArgs());
  395. }
  396. public void SaveLogViewerConfig(LogViewerConfig newConfig)
  397. {
  398. _config.logViewer = newConfig;
  399. newConfig.SaveSize();
  400. Configuration.Save(_config);
  401. ConfigChanged?.Invoke(this, new EventArgs());
  402. }
  403. public void SaveHotkeyConfig(HotkeyConfig newConfig)
  404. {
  405. _config.hotkey = newConfig;
  406. SaveConfig(_config);
  407. ConfigChanged?.Invoke(this, new EventArgs());
  408. }
  409. #endregion
  410. #region Statistic
  411. public void SelectStrategy(string strategyID)
  412. {
  413. _config.index = -1;
  414. _config.strategy = strategyID;
  415. SaveConfig(_config);
  416. }
  417. public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
  418. {
  419. StatisticsConfiguration = configuration;
  420. StatisticsStrategyConfiguration.Save(configuration);
  421. }
  422. public IList<IStrategy> GetStrategies()
  423. {
  424. return _strategyManager.GetStrategies();
  425. }
  426. public IStrategy GetCurrentStrategy()
  427. {
  428. foreach (var strategy in _strategyManager.GetStrategies())
  429. {
  430. if (strategy.ID == _config.strategy)
  431. {
  432. return strategy;
  433. }
  434. }
  435. return null;
  436. }
  437. public void UpdateStatisticsConfiguration(bool enabled)
  438. {
  439. if (availabilityStatistics != null)
  440. {
  441. availabilityStatistics.UpdateConfiguration(this);
  442. _config.availabilityStatistics = enabled;
  443. SaveConfig(_config);
  444. }
  445. }
  446. public void UpdateLatency(object sender, SSTCPConnectedEventArgs args)
  447. {
  448. GetCurrentStrategy()?.UpdateLatency(args.server, args.latency);
  449. if (_config.availabilityStatistics)
  450. {
  451. availabilityStatistics.UpdateLatency(args.server, (int)args.latency.TotalMilliseconds);
  452. }
  453. }
  454. public void UpdateInboundCounter(object sender, SSTransmitEventArgs args)
  455. {
  456. GetCurrentStrategy()?.UpdateLastRead(args.server);
  457. Interlocked.Add(ref _inboundCounter, args.length);
  458. if (_config.availabilityStatistics)
  459. {
  460. availabilityStatistics.UpdateInboundCounter(args.server, args.length);
  461. }
  462. }
  463. public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args)
  464. {
  465. GetCurrentStrategy()?.UpdateLastWrite(args.server);
  466. Interlocked.Add(ref _outboundCounter, args.length);
  467. if (_config.availabilityStatistics)
  468. {
  469. availabilityStatistics.UpdateOutboundCounter(args.server, args.length);
  470. }
  471. }
  472. #endregion
  473. #region SIP003
  474. private void StartPlugin()
  475. {
  476. var server = _config.GetCurrentServer();
  477. GetPluginLocalEndPointIfConfigured(server);
  478. }
  479. private void StopPlugins()
  480. {
  481. foreach (var serverAndPlugin in _pluginsByServer)
  482. {
  483. serverAndPlugin.Value?.Dispose();
  484. }
  485. _pluginsByServer.Clear();
  486. }
  487. public EndPoint GetPluginLocalEndPointIfConfigured(Server server)
  488. {
  489. var plugin = _pluginsByServer.GetOrAdd(
  490. server,
  491. x => Sip003Plugin.CreateIfConfigured(x, _config.showPluginOutput));
  492. if (plugin == null)
  493. {
  494. return null;
  495. }
  496. try
  497. {
  498. if (plugin.StartIfNeeded())
  499. {
  500. logger.Info(
  501. $"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}");
  502. }
  503. }
  504. catch (Exception ex)
  505. {
  506. logger.Error("Failed to start SIP003 plugin: " + ex.Message);
  507. throw;
  508. }
  509. return plugin.LocalEndPoint;
  510. }
  511. public void ToggleShowPluginOutput(bool enabled)
  512. {
  513. _config.showPluginOutput = enabled;
  514. SaveConfig(_config);
  515. ShowPluginOutputChanged?.Invoke(this, new EventArgs());
  516. }
  517. #endregion
  518. #region Memory Management
  519. private void StartReleasingMemory()
  520. {
  521. _ramThread = new Thread(new ThreadStart(ReleaseMemory))
  522. {
  523. IsBackground = true
  524. };
  525. _ramThread.Start();
  526. }
  527. private void ReleaseMemory()
  528. {
  529. while (true)
  530. {
  531. Utils.ReleaseMemory(false);
  532. Thread.Sleep(30 * 1000);
  533. }
  534. }
  535. #endregion
  536. #region Traffic Statistics
  537. private void StartTrafficStatistics(int queueMaxSize)
  538. {
  539. trafficPerSecondQueue = new Queue<TrafficPerSecond>();
  540. for (int i = 0; i < queueMaxSize; i++)
  541. {
  542. trafficPerSecondQueue.Enqueue(new TrafficPerSecond());
  543. }
  544. _trafficThread = new Thread(new ThreadStart(() => TrafficStatistics(queueMaxSize)))
  545. {
  546. IsBackground = true
  547. };
  548. _trafficThread.Start();
  549. }
  550. private void TrafficStatistics(int queueMaxSize)
  551. {
  552. TrafficPerSecond previous, current;
  553. while (true)
  554. {
  555. previous = trafficPerSecondQueue.Last();
  556. current = new TrafficPerSecond
  557. {
  558. inboundCounter = InboundCounter,
  559. outboundCounter = OutboundCounter
  560. };
  561. current.inboundIncreasement = current.inboundCounter - previous.inboundCounter;
  562. current.outboundIncreasement = current.outboundCounter - previous.outboundCounter;
  563. trafficPerSecondQueue.Enqueue(current);
  564. if (trafficPerSecondQueue.Count > queueMaxSize)
  565. trafficPerSecondQueue.Dequeue();
  566. TrafficChanged?.Invoke(this, new EventArgs());
  567. Thread.Sleep(1000);
  568. }
  569. }
  570. #endregion
  571. #region SIP008
  572. public async Task<int> UpdateOnlineConfigInternal(string url)
  573. {
  574. var onlineServer = await OnlineConfigResolver.GetOnline(url, _config.WebProxy);
  575. _config.configs = Configuration.SortByOnlineConfig(
  576. _config.configs
  577. .Where(c => c.group != url)
  578. .Concat(onlineServer)
  579. );
  580. logger.Info($"updated {onlineServer.Count} server from {url}");
  581. return onlineServer.Count;
  582. }
  583. public async Task<bool> UpdateOnlineConfig(string url)
  584. {
  585. var selected = GetCurrentServer();
  586. try
  587. {
  588. int count = await UpdateOnlineConfigInternal(url);
  589. }
  590. catch (Exception e)
  591. {
  592. logger.LogUsefulException(e);
  593. return false;
  594. }
  595. _config.index = _config.configs.IndexOf(selected);
  596. SaveConfig(_config);
  597. return true;
  598. }
  599. public async Task<int> UpdateAllOnlineConfig()
  600. {
  601. var selected = GetCurrentServer();
  602. int failCount = 0;
  603. foreach (var url in _config.onlineConfigSource)
  604. {
  605. try
  606. {
  607. await UpdateOnlineConfigInternal(url);
  608. }
  609. catch (Exception e)
  610. {
  611. logger.LogUsefulException(e);
  612. failCount++;
  613. }
  614. }
  615. _config.index = _config.configs.IndexOf(selected);
  616. SaveConfig(_config);
  617. return failCount;
  618. }
  619. public void SaveOnlineConfigSource(IEnumerable<string> vs)
  620. {
  621. _config.onlineConfigSource = vs.ToList();
  622. SaveConfig(_config);
  623. }
  624. public void RemoveOnlineConfig(string url)
  625. {
  626. _config.onlineConfigSource.RemoveAll(v => v == url);
  627. _config.configs = Configuration.SortByOnlineConfig(
  628. _config.configs.Where(c => c.group != url)
  629. );
  630. SaveConfig(_config);
  631. }
  632. #endregion
  633. }
  634. }