Files
duplicati/Duplicati/GUI/Duplicati.GUI.TrayIcon/RumpsRunner.cs
T
2017-12-17 01:34:21 +07:00

368 lines
14 KiB
C#

// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using CoCoL;
namespace Duplicati.GUI.TrayIcon
{
public class RumpsRunner : TrayIconBase
{
private class MenuItemWrapper : Duplicati.GUI.TrayIcon.IMenuItem
{
private RumpsRunner m_parent;
private string m_text;
private MenuIcons m_icon;
private bool m_enabled;
private bool m_default;
public MenuItemWrapper(RumpsRunner parent, string text, Duplicati.GUI.TrayIcon.MenuIcons icon, Action callback, IList<Duplicati.GUI.TrayIcon.IMenuItem> subitems)
{
m_parent = parent;
Key = Guid.NewGuid().ToString("N");
m_text = text ?? "";
Callback = callback;
m_enabled = true;
m_default = false;
if (subitems != null)
Subitems = subitems.Cast<MenuItemWrapper>().ToList();
}
[JsonIgnore]
public Action Callback { get; private set; }
public string Key { get; private set; }
public IList<MenuItemWrapper> Subitems { get; private set; }
#region IMenuItem implementation
public string Text
{
get { return m_text; }
set
{
if (m_text != value)
{
m_text = value;
m_parent.UpdateMenu(this);
}
}
}
public MenuIcons Icon
{
get { return m_icon; }
set
{
if (m_icon != value)
{
m_icon = value;
m_parent.UpdateMenu(this);
}
}
}
public bool Enabled
{
get { return m_enabled; }
set
{
if (m_enabled != value)
{
m_enabled = value;
m_parent.UpdateMenu(this);
}
}
}
public bool Default
{
get { return m_default; }
set
{
if (m_default != value)
{
m_default = value;
m_parent.UpdateMenu(this);
}
}
}
#endregion
}
private static readonly System.Reflection.Assembly ASSEMBLY = System.Reflection.Assembly.GetExecutingAssembly();
private static readonly string ICON_PATH = ASSEMBLY.GetName().Name + ".OSX_Icons.";
private static readonly string ICON_NORMAL = ICON_PATH + "normal.png";
private static readonly string ICON_PAUSED = ICON_PATH + "normal-pause.png";
private static readonly string ICON_RUNNING = ICON_PATH + "normal-running.png";
private static readonly string ICON_ERROR = ICON_PATH + "normal-error.png";
private Dictionary<Duplicati.GUI.TrayIcon.TrayIcons, string> m_images = new Dictionary<Duplicati.GUI.TrayIcon.TrayIcons, string>();
private System.Diagnostics.Process m_rumpsProcess;
private static readonly string RUMPS_PYTHON = string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("RUMPS_PYTHON")) ? "/usr/bin/python2.7" : Environment.GetEnvironmentVariable("RUMPS_PYTHON");
private static readonly string SCRIPT_PATH = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "OSXTrayHost", "osx-trayicon-rumps.py");
private IsolatedChannelScope m_scope;
private IWriteChannelEnd<string> m_toRumps;
private List<MenuItemWrapper> m_menus;
private bool m_isQuitting = false;
private TrayIcons m_lastIcon;
public static bool CanRun()
{
if (!Library.Utility.Utility.IsClientOSX)
return false;
if (!File.Exists(SCRIPT_PATH) || !File.Exists(RUMPS_PYTHON))
return false;
try
{
var si = System.Diagnostics.Process.Start(RUMPS_PYTHON, string.Format("\"{0}\" TEST", SCRIPT_PATH));
si.WaitForExit((int)TimeSpan.FromSeconds(5).TotalMilliseconds);
if (!si.HasExited)
{
si.Kill();
return false;
}
return si.ExitCode == 0;
}
catch
{
}
return false;
}
private void ResetMenus()
{
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "setmenus", Menus = m_menus}));
}
private void Restart()
{
if (m_toRumps != null)
m_toRumps.Dispose();
var startinfo = new System.Diagnostics.ProcessStartInfo(RUMPS_PYTHON, string.Format("\"{0}\"", SCRIPT_PATH));
startinfo.CreateNoWindow = true;
startinfo.UseShellExecute = false;
startinfo.RedirectStandardInput = true;
startinfo.RedirectStandardOutput = true;
startinfo.RedirectStandardError = true;
m_rumpsProcess = System.Diagnostics.Process.Start(startinfo);
var ch = ChannelManager.CreateChannel<string>();
m_toRumps = ch.AsWriteOnly();
WriteChannel(m_rumpsProcess.StandardInput, ch.AsReadOnly());
var standardOutputTask = ReadChannel(m_rumpsProcess.StandardOutput);
var standardErrorTask = ReadChannel(m_rumpsProcess.StandardError);
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "background"}));
//m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "setappicon", Image = GetIcon(m_lastIcon)}));
if (m_menus != null)
{
ResetMenus();
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "seticon", Image = GetIcon(m_lastIcon)}));
}
}
public override void Init(string[] args)
{
m_scope = new IsolatedChannelScope();
Restart();
base.Init(args);
}
private static Task WriteChannel(StreamWriter stream, IReadChannelEnd<string> ch)
{
stream.AutoFlush = true;
return AutomationExtensions.RunTask(
new {
Input = ch
},
async self =>
{
using(stream)
{
while(true)
{
var line = await self.Input.ReadAsync();
await stream.WriteLineAsync(line);
//Console.WriteLine("Wrote {0}", line);
}
}
}
);
}
private async Task ReadChannel(StreamReader stream)
{
string line;
using(stream)
while ((line = await stream.ReadLineAsync()) != null)
{
//Console.WriteLine("Got message: {0}", line);
line = line.Trim();
if (line.StartsWith("click:", StringComparison.OrdinalIgnoreCase))
{
var key = line.Substring("click:".Length);
var menu = m_menus.Where(x => string.Equals(x.Key, key)).FirstOrDefault();
if (menu == null)
{
Console.WriteLine("Menu not found: {0}", key);
}
else
{
menu.Callback();
}
}
else if (!line.StartsWith("info", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(line))
{
Console.WriteLine("Unexpected message: {0}", line);
}
}
}
private void UpdateMenu(MenuItemWrapper menu)
{
if (m_menus != null)
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "setmenu", Menu = menu}));
}
private string LoadStream(System.IO.Stream s)
{
using(var ms = new MemoryStream())
{
s.CopyTo(ms);
return Convert.ToBase64String(ms.ToArray());
}
}
protected override void Run(string[] args)
{
m_rumpsProcess.WaitForExit();
while(!m_isQuitting)
{
Restart();
m_rumpsProcess.WaitForExit();
}
}
protected override void UpdateUIState(Action action)
{
action();
}
private string GetIcon(Duplicati.GUI.TrayIcon.TrayIcons icon)
{
if (!m_images.ContainsKey(icon))
{
switch(icon)
{
case Duplicati.GUI.TrayIcon.TrayIcons.IdleError:
m_images[icon] = LoadStream(ASSEMBLY.GetManifestResourceStream(ICON_ERROR));
break;
case Duplicati.GUI.TrayIcon.TrayIcons.Paused:
m_images[icon] = LoadStream(ASSEMBLY.GetManifestResourceStream(ICON_PAUSED));
break;
case Duplicati.GUI.TrayIcon.TrayIcons.PausedError:
m_images[icon] = LoadStream(ASSEMBLY.GetManifestResourceStream(ICON_PAUSED));
break;
case Duplicati.GUI.TrayIcon.TrayIcons.Running:
m_images[icon] = LoadStream(ASSEMBLY.GetManifestResourceStream(ICON_RUNNING));
break;
case Duplicati.GUI.TrayIcon.TrayIcons.RunningError:
m_images[icon] = LoadStream(ASSEMBLY.GetManifestResourceStream(ICON_RUNNING));
break;
case Duplicati.GUI.TrayIcon.TrayIcons.Idle:
default:
m_images[icon] = LoadStream(ASSEMBLY.GetManifestResourceStream(ICON_NORMAL));
break;
}
}
return m_images[icon];
}
protected override Duplicati.GUI.TrayIcon.TrayIcons Icon
{
set
{
m_lastIcon = value;
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "seticon", Image = GetIcon(value)}));
}
}
protected override Duplicati.GUI.TrayIcon.IMenuItem CreateMenuItem (string text, Duplicati.GUI.TrayIcon.MenuIcons icon, Action callback, System.Collections.Generic.IList<Duplicati.GUI.TrayIcon.IMenuItem> subitems)
{
return new MenuItemWrapper(this, text, icon, callback, subitems);
}
protected override void Exit()
{
m_isQuitting = true;
if (m_rumpsProcess != null && !m_rumpsProcess.HasExited)
{
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "shutdown"}));
if (m_toRumps != null)
{
m_toRumps.Dispose();
m_toRumps = null;
}
m_rumpsProcess.WaitForExit((int)TimeSpan.FromSeconds(5).TotalMilliseconds);
m_rumpsProcess.Kill();
m_rumpsProcess = null;
}
}
protected override void SetMenu(System.Collections.Generic.IEnumerable<Duplicati.GUI.TrayIcon.IMenuItem> items)
{
m_menus = items.Cast<MenuItemWrapper>().ToList();
ResetMenus();
}
protected override void NotifyUser(string title, string message, NotificationType type)
{
m_toRumps.WriteNoWait(JsonConvert.SerializeObject(new {Action = "notification", Title = title, Message = message}));
}
public override void Dispose()
{
if (m_scope != null)
m_scope.Dispose();
}
}
}