/SparkleShare/SparkleStatusIconController.cs

http://github.com/hbons/SparkleShare · C# · 408 lines · 282 code · 106 blank · 20 comment · 52 complexity · 58fb33fe0d5a80e56a3153b16934801f MD5 · raw file

  1. // SparkleShare, a collaboration and sharing tool.
  2. // Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Threading;
  19. using Timers = System.Timers;
  20. using SparkleLib;
  21. namespace SparkleShare {
  22. public enum IconState {
  23. Idle,
  24. SyncingUp,
  25. SyncingDown,
  26. Syncing,
  27. Error
  28. }
  29. public class ProjectInfo {
  30. private SparkleRepoBase repo;
  31. public string Name { get { return this.repo.Name; }}
  32. public string Path { get { return this.repo.LocalPath; }}
  33. public bool IsPaused { get { return this.repo.Status == SyncStatus.Paused; }}
  34. public bool HasError { get { return this.repo.Status == SyncStatus.Error; }}
  35. public string StatusMessage {
  36. get {
  37. string status_message = "Waiting to sync";
  38. if (!this.repo.LastSync.Equals (DateTime.MinValue))
  39. status_message = string.Format ("Synced {0}", this.repo.LastSync.ToPrettyDate ());
  40. if (this.repo.Status == SyncStatus.SyncUp)
  41. status_message = "Sending changes… " + this.repo.ProgressPercentage + "%";
  42. if (this.repo.Status == SyncStatus.SyncDown)
  43. status_message = "Receiving changes… " + this.repo.ProgressPercentage + "%";
  44. if (this.repo.Status == SyncStatus.SyncUp || this.repo.Status == SyncStatus.SyncDown) {
  45. if (this.repo.ProgressSpeed > 0)
  46. status_message += " " + this.repo.ProgressSpeed.ToSize () + "/s";
  47. }
  48. if (IsPaused) {
  49. return "Paused";
  50. } else if (HasError) {
  51. switch (this.repo.Error) {
  52. case ErrorStatus.HostUnreachable: return "Can’t reach the host";
  53. case ErrorStatus.HostIdentityChanged: return "The host’s identity has changed";
  54. case ErrorStatus.AuthenticationFailed: return "Authentication failed";
  55. case ErrorStatus.DiskSpaceExceeded: return "Host is out of disk space";
  56. case ErrorStatus.UnreadableFiles: return "Some local files are unreadable or in use";
  57. case ErrorStatus.NotFound: return "Project doesn’t exist on host";
  58. case ErrorStatus.IncompatibleClientServer: return "Incompatible client/server versions";
  59. }
  60. }
  61. return status_message;
  62. }
  63. }
  64. public string MoreUnsyncedChanges = "";
  65. public Dictionary<string, string> UnsyncedChangesInfo {
  66. get {
  67. Dictionary<string, string> changes_info = new Dictionary<string, string> ();
  68. int changes_count = 0;
  69. foreach (SparkleChange change in repo.UnsyncedChanges) {
  70. changes_count++;
  71. if (changes_count > 10)
  72. continue;
  73. switch (change.Type) {
  74. case SparkleChangeType.Added: changes_info [change.Path] = "document-added-12.png"; break;
  75. case SparkleChangeType.Edited: changes_info [change.Path] = "document-edited-12.png"; break;
  76. case SparkleChangeType.Deleted: changes_info [change.Path] = "document-deleted-12.png"; break;
  77. case SparkleChangeType.Moved: changes_info [change.MovedToPath] = "document-moved-12.png"; break;
  78. }
  79. }
  80. if (changes_count > 10)
  81. MoreUnsyncedChanges = string.Format ("and {0} more", changes_count - 10);
  82. return changes_info;
  83. }
  84. }
  85. public ProjectInfo (SparkleRepoBase repo)
  86. {
  87. this.repo = repo;
  88. }
  89. }
  90. public class SparkleStatusIconController {
  91. public event UpdateIconEventHandler UpdateIconEvent = delegate { };
  92. public delegate void UpdateIconEventHandler (IconState state);
  93. public event UpdateMenuEventHandler UpdateMenuEvent = delegate { };
  94. public delegate void UpdateMenuEventHandler (IconState state);
  95. public event UpdateStatusItemEventHandler UpdateStatusItemEvent = delegate { };
  96. public delegate void UpdateStatusItemEventHandler (string state_text);
  97. public event UpdateQuitItemEventHandler UpdateQuitItemEvent = delegate { };
  98. public delegate void UpdateQuitItemEventHandler (bool quit_item_enabled);
  99. public IconState CurrentState = IconState.Idle;
  100. public string StateText = "Welcome to SparkleShare!";
  101. public ProjectInfo [] Projects = new ProjectInfo [0];
  102. public int ProgressPercentage {
  103. get {
  104. return (int) Program.Controller.ProgressPercentage;
  105. }
  106. }
  107. public string ProgressSpeed {
  108. get {
  109. string progress_speed = "";
  110. if (Program.Controller.ProgressSpeedDown == 0 && Program.Controller.ProgressSpeedUp > 0) {
  111. progress_speed = Program.Controller.ProgressSpeedUp.ToSize () + "/s ";
  112. } else if (Program.Controller.ProgressSpeedUp == 0 && Program.Controller.ProgressSpeedDown > 0) {
  113. progress_speed = Program.Controller.ProgressSpeedDown.ToSize () + "/s ";
  114. } else if (Program.Controller.ProgressSpeedUp > 0 &&
  115. Program.Controller.ProgressSpeedDown > 0) {
  116. progress_speed = "Up: " + Program.Controller.ProgressSpeedUp.ToSize () + "/s " +
  117. "Down: " + Program.Controller.ProgressSpeedDown.ToSize () + "/s";
  118. }
  119. return progress_speed;
  120. }
  121. }
  122. public bool RecentEventsItemEnabled {
  123. get {
  124. return (Program.Controller.Repositories.Length > 0);
  125. }
  126. }
  127. public bool LinkCodeItemEnabled {
  128. get {
  129. return !string.IsNullOrEmpty (Program.Controller.CurrentUser.PublicKey);
  130. }
  131. }
  132. public bool QuitItemEnabled {
  133. get {
  134. return (CurrentState == IconState.Idle || CurrentState == IconState.Error);
  135. }
  136. }
  137. public SparkleStatusIconController ()
  138. {
  139. UpdateFolders ();
  140. Program.Controller.FolderListChanged += delegate {
  141. if (CurrentState != IconState.Error) {
  142. CurrentState = IconState.Idle;
  143. UpdateStateText ();
  144. }
  145. UpdateFolders ();
  146. UpdateStatusItemEvent (StateText);
  147. UpdateMenuEvent (CurrentState);
  148. };
  149. Program.Controller.OnIdle += delegate {
  150. if (CurrentState != IconState.Error) {
  151. CurrentState = IconState.Idle;
  152. UpdateStateText ();
  153. }
  154. UpdateFolders ();
  155. UpdateIconEvent (CurrentState);
  156. UpdateStatusItemEvent (StateText);
  157. UpdateQuitItemEvent (QuitItemEnabled);
  158. UpdateMenuEvent (CurrentState);
  159. };
  160. Program.Controller.OnSyncing += delegate {
  161. int repos_syncing_up = 0;
  162. int repos_syncing_down = 0;
  163. foreach (SparkleRepoBase repo in Program.Controller.Repositories) {
  164. if (repo.Status == SyncStatus.SyncUp)
  165. repos_syncing_up++;
  166. if (repo.Status == SyncStatus.SyncDown)
  167. repos_syncing_down++;
  168. }
  169. if (repos_syncing_up > 0 &&
  170. repos_syncing_down > 0) {
  171. CurrentState = IconState.Syncing;
  172. StateText = "Syncing changes…";
  173. } else if (repos_syncing_down == 0) {
  174. CurrentState = IconState.SyncingUp;
  175. StateText = "Sending changes…";
  176. } else {
  177. CurrentState = IconState.SyncingDown;
  178. StateText = "Receiving changes…";
  179. }
  180. if (ProgressPercentage > 0)
  181. StateText += " " + ProgressPercentage + "% " + ProgressSpeed;
  182. UpdateIconEvent (CurrentState);
  183. UpdateStatusItemEvent (StateText);
  184. UpdateQuitItemEvent (QuitItemEnabled);
  185. };
  186. Program.Controller.OnError += delegate {
  187. CurrentState = IconState.Error;
  188. StateText = "Some changes weren’t synced";
  189. UpdateFolders ();
  190. UpdateIconEvent (CurrentState);
  191. UpdateStatusItemEvent (StateText);
  192. UpdateQuitItemEvent (QuitItemEnabled);
  193. UpdateMenuEvent (CurrentState);
  194. };
  195. // FIXME: Work around a race condition causing
  196. // the icon to not always show the right state
  197. Timers.Timer timer = new Timers.Timer () { Interval = 30 * 1000 };
  198. timer.Elapsed += delegate {
  199. UpdateIconEvent (CurrentState);
  200. UpdateStatusItemEvent (StateText);
  201. };
  202. timer.Start ();
  203. }
  204. private string UpdateStateText ()
  205. {
  206. if (Projects.Length == 0)
  207. return StateText = "Welcome to SparkleShare!";
  208. else
  209. return StateText = "Projects up to date " + GetPausedCount ();
  210. }
  211. private string GetPausedCount ()
  212. {
  213. int paused_projects = 0;
  214. foreach (ProjectInfo project in Projects)
  215. if (project.IsPaused)
  216. paused_projects++;
  217. if (paused_projects > 0)
  218. return string.Format ("— {0} paused", paused_projects);
  219. else
  220. return "";
  221. }
  222. // Main menu items
  223. public void RecentEventsClicked ()
  224. {
  225. new Thread (() => {
  226. while (!Program.Controller.RepositoriesLoaded)
  227. Thread.Sleep (100);
  228. Program.Controller.ShowEventLogWindow ();
  229. }).Start ();
  230. }
  231. public void AddHostedProjectClicked ()
  232. {
  233. new Thread (() => Program.Controller.ShowSetupWindow (PageType.Add)).Start ();
  234. }
  235. public void CopyToClipboardClicked ()
  236. {
  237. Program.Controller.CopyToClipboard (Program.Controller.CurrentUser.PublicKey);
  238. }
  239. public void AboutClicked ()
  240. {
  241. Program.Controller.ShowAboutWindow ();
  242. }
  243. public void QuitClicked ()
  244. {
  245. Program.Controller.Quit ();
  246. }
  247. // Project items
  248. public void ProjectClicked (string project)
  249. {
  250. Program.Controller.OpenSparkleShareFolder (project);
  251. }
  252. public void PauseClicked (string project)
  253. {
  254. Program.Controller.GetRepoByName (project).Pause ();
  255. UpdateStateText ();
  256. UpdateMenuEvent (CurrentState);
  257. }
  258. public void ResumeClicked (string project)
  259. {
  260. if (Program.Controller.GetRepoByName (project).UnsyncedChanges.Count > 0) {
  261. Program.Controller.ShowNoteWindow (project);
  262. } else {
  263. new Thread (() => {
  264. Program.Controller.GetRepoByName (project).Resume ("");
  265. UpdateStateText ();
  266. UpdateMenuEvent (CurrentState);
  267. }).Start ();
  268. }
  269. }
  270. public void TryAgainClicked (string project)
  271. {
  272. new Thread (() => Program.Controller.GetRepoByName (project).ForceRetry ()).Start ();
  273. }
  274. // Helper delegates
  275. public EventHandler OpenFolderDelegate (string project)
  276. {
  277. return delegate { ProjectClicked (project); };
  278. }
  279. public EventHandler TryAgainDelegate (string project)
  280. {
  281. return delegate { TryAgainClicked (project); };
  282. }
  283. public EventHandler PauseDelegate (string project)
  284. {
  285. return delegate { PauseClicked (project); };
  286. }
  287. public EventHandler ResumeDelegate (string project)
  288. {
  289. return delegate { ResumeClicked (project); };
  290. }
  291. private Object projects_lock = new Object ();
  292. private void UpdateFolders ()
  293. {
  294. lock (this.projects_lock) {
  295. List<ProjectInfo> projects = new List<ProjectInfo> ();
  296. foreach (SparkleRepoBase repo in Program.Controller.Repositories)
  297. projects.Add (new ProjectInfo (repo));
  298. Projects = projects.ToArray ();
  299. }
  300. }
  301. }
  302. }