Files
2015-01-15 01:53:42 +08:00

313 lines
9.0 KiB
C#
Executable File

using System.IO;
using Shadowsocks.Model;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net.Sockets;
namespace Shadowsocks.Controller
{
public class ShadowsocksController
{
// controller:
// handle user actions
// manipulates UI
// interacts with low level logic
private Thread _ramThread;
private Local local;
private PACServer pacServer;
private Configuration _config;
private PolipoRunner polipoRunner;
private GFWListUpdater gfwListUpdater;
private bool stopped = false;
private bool _systemProxyIsDirty = false;
public class PathEventArgs : EventArgs
{
public string Path;
}
public event EventHandler ConfigChanged;
public event EventHandler EnableStatusChanged;
public event EventHandler EnableGlobalChanged;
public event EventHandler ShareOverLANStatusChanged;
// when user clicked Edit PAC, and PAC file has already created
public event EventHandler<PathEventArgs> PACFileReadyToOpen;
public event EventHandler UpdatePACFromGFWListCompleted;
public event ErrorEventHandler UpdatePACFromGFWListError;
public event ErrorEventHandler Errored;
public ShadowsocksController()
{
_config = Configuration.Load();
}
public void Start()
{
Reload();
}
protected void ReportError(Exception e)
{
if (Errored != null)
{
Errored(this, new ErrorEventArgs(e));
}
}
public Server GetCurrentServer()
{
return _config.GetCurrentServer();
}
// always return copy
public Configuration GetConfiguration()
{
return Configuration.Load();
}
public void SaveServers(List<Server> servers)
{
_config.configs = servers;
SaveConfig(_config);
}
public bool AddServerBySSURL(string ssURL)
{
try
{
var server = new Server(ssURL);
_config.configs.Add(server);
_config.index = _config.configs.Count - 1;
SaveConfig(_config);
return true;
}
catch (Exception e)
{
Logging.LogUsefulException(e);
return false;
}
}
public void ToggleEnable(bool enabled)
{
_config.enabled = enabled;
UpdateSystemProxy();
SaveConfig(_config);
if (EnableStatusChanged != null)
{
EnableStatusChanged(this, new EventArgs());
}
}
public void ToggleGlobal(bool global)
{
_config.global = global;
UpdateSystemProxy();
SaveConfig(_config);
if (EnableGlobalChanged != null)
{
EnableGlobalChanged(this, new EventArgs());
}
}
public void ToggleShareOverLAN(bool enabled)
{
_config.shareOverLan = enabled;
SaveConfig(_config);
if (ShareOverLANStatusChanged != null)
{
ShareOverLANStatusChanged(this, new EventArgs());
}
}
public void SelectServerIndex(int index)
{
_config.index = index;
SaveConfig(_config);
}
public void Stop()
{
Console.WriteLine("Stop");
if (stopped)
{
return;
}
stopped = true;
if (local != null)
{
local.Stop();
}
if (polipoRunner != null)
{
polipoRunner.Stop();
}
if (_config.enabled)
{
SystemProxy.Disable();
}
}
public void TouchPACFile()
{
string pacFilename = pacServer.TouchPACFile();
if (PACFileReadyToOpen != null)
{
PACFileReadyToOpen(this, new PathEventArgs() { Path = pacFilename });
}
}
public string GetQRCodeForCurrentServer()
{
Server server = GetCurrentServer();
string parts = server.method + ":" + server.password + "@" + server.server + ":" + server.server_port;
string base64 = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(parts));
return "ss://" + base64;
}
public void UpdatePACFromGFWList()
{
if (gfwListUpdater != null)
{
gfwListUpdater.UpdatePACFromGFWList();
}
}
protected void Reload()
{
Console.WriteLine("Reload");
// some logic in configuration updated the config when saving, we need to read it again
_config = Configuration.Load();
if (polipoRunner == null)
{
polipoRunner = new PolipoRunner();
}
if (pacServer == null)
{
pacServer = new PACServer();
pacServer.PACFileChanged += pacServer_PACFileChanged;
}
if (gfwListUpdater == null)
{
gfwListUpdater = new GFWListUpdater();
gfwListUpdater.UpdateCompleted += pacServer_PACUpdateCompleted;
gfwListUpdater.Error += pacServer_PACUpdateError;
}
pacServer.Stop();
if (local != null)
{
local.Stop();
}
// don't put polipoRunner.Start() before pacServer.Stop()
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
// though UseShellExecute is set to true now
// http://stackoverflow.com/questions/10235093/socket-doesnt-close-after-application-exits-if-a-launched-process-is-open
polipoRunner.Stop();
try
{
polipoRunner.Start(_config);
local = new Local(_config);
local.Start();
pacServer.Start(_config);
}
catch (Exception e)
{
// translate Microsoft language into human language
// i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use
if (e is SocketException)
{
SocketException se = (SocketException)e;
if (se.SocketErrorCode == SocketError.AccessDenied)
{
e = new Exception(I18N.GetString("Port already in use"), e);
}
}
Logging.LogUsefulException(e);
ReportError(e);
}
if (ConfigChanged != null)
{
ConfigChanged(this, new EventArgs());
}
UpdateSystemProxy();
Util.Utils.ReleaseMemory();
}
protected void SaveConfig(Configuration newConfig)
{
Configuration.Save(newConfig);
Reload();
}
private void UpdateSystemProxy()
{
Console.WriteLine("UpdateSystemProxy");
if (_config.enabled)
{
SystemProxy.Enable(_config.global);
_systemProxyIsDirty = true;
}
else
{
// only switch it off if we have switched it on
if (_systemProxyIsDirty)
{
SystemProxy.Disable();
_systemProxyIsDirty = false;
}
}
}
private void pacServer_PACFileChanged(object sender, EventArgs e)
{
UpdateSystemProxy();
}
private void pacServer_PACUpdateCompleted(object sender, EventArgs e)
{
if (UpdatePACFromGFWListCompleted != null)
UpdatePACFromGFWListCompleted(this, e);
}
private void pacServer_PACUpdateError(object sender, ErrorEventArgs e)
{
if (UpdatePACFromGFWListError != null)
UpdatePACFromGFWListError(this, e);
}
private void StartReleasingMemory()
{
_ramThread = new Thread(new ThreadStart(ReleaseMemory));
_ramThread.IsBackground = true;
_ramThread.Start();
}
private void ReleaseMemory()
{
while (true)
{
Util.Utils.ReleaseMemory();
Thread.Sleep(30 * 1000);
}
}
}
}