/SparkleShare/SparkleSetupController.cs

http://github.com/hbons/SparkleShare · C# · 594 lines · 414 code · 153 blank · 27 comment · 66 complexity · 9a4053c581af235114495c622de92ced 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.IO;
  19. using System.Text.RegularExpressions;
  20. using System.Threading;
  21. using SparkleLib;
  22. namespace SparkleShare {
  23. public enum PageType {
  24. None,
  25. Setup,
  26. Add,
  27. Invite,
  28. Syncing,
  29. Error,
  30. Finished,
  31. Tutorial,
  32. CryptoSetup,
  33. CryptoPassword
  34. }
  35. public enum FieldState {
  36. Enabled,
  37. Disabled
  38. }
  39. public class SparkleSetupController {
  40. public event Action ShowWindowEvent = delegate { };
  41. public event Action HideWindowEvent = delegate { };
  42. public event ChangePageEventHandler ChangePageEvent = delegate { };
  43. public delegate void ChangePageEventHandler (PageType page, string [] warnings);
  44. public event UpdateProgressBarEventHandler UpdateProgressBarEvent = delegate { };
  45. public delegate void UpdateProgressBarEventHandler (double percentage, string speed);
  46. public event UpdateSetupContinueButtonEventHandler UpdateSetupContinueButtonEvent = delegate { };
  47. public delegate void UpdateSetupContinueButtonEventHandler (bool button_enabled);
  48. public event UpdateCryptoSetupContinueButtonEventHandler UpdateCryptoSetupContinueButtonEvent = delegate { };
  49. public delegate void UpdateCryptoSetupContinueButtonEventHandler (bool button_enabled);
  50. public event UpdateCryptoPasswordContinueButtonEventHandler UpdateCryptoPasswordContinueButtonEvent = delegate { };
  51. public delegate void UpdateCryptoPasswordContinueButtonEventHandler (bool button_enabled);
  52. public event UpdateAddProjectButtonEventHandler UpdateAddProjectButtonEvent = delegate { };
  53. public delegate void UpdateAddProjectButtonEventHandler (bool button_enabled);
  54. public event ChangeAddressFieldEventHandler ChangeAddressFieldEvent = delegate { };
  55. public delegate void ChangeAddressFieldEventHandler (string text, string example_text, FieldState state);
  56. public event ChangePathFieldEventHandler ChangePathFieldEvent = delegate { };
  57. public delegate void ChangePathFieldEventHandler (string text, string example_text, FieldState state);
  58. public readonly List<SparklePlugin> Plugins = new List<SparklePlugin> ();
  59. public SparklePlugin SelectedPlugin;
  60. public bool WindowIsOpen { get; private set; }
  61. public SparkleInvite PendingInvite { get; private set; }
  62. public int TutorialPageNumber { get; private set; }
  63. public string PreviousUrl { get; private set; }
  64. public string PreviousAddress { get; private set; }
  65. public string PreviousPath { get; private set; }
  66. public string SyncingFolder { get; private set; }
  67. public double ProgressBarPercentage { get; private set; }
  68. public int SelectedPluginIndex {
  69. get {
  70. return Plugins.IndexOf (SelectedPlugin);
  71. }
  72. }
  73. public bool FetchPriorHistory {
  74. get {
  75. return this.fetch_prior_history;
  76. }
  77. }
  78. private PageType current_page;
  79. private string saved_address = "";
  80. private string saved_remote_path = "";
  81. private bool create_startup_item = true;
  82. private bool fetch_prior_history = false;
  83. public SparkleSetupController ()
  84. {
  85. ChangePageEvent += delegate (PageType page_type, string [] warnings) {
  86. this.current_page = page_type;
  87. };
  88. TutorialPageNumber = 0;
  89. PreviousAddress = "";
  90. PreviousPath = "";
  91. PreviousUrl = "";
  92. SyncingFolder = "";
  93. string local_plugins_path = SparklePlugin.LocalPluginsPath;
  94. int local_plugins_count = 0;
  95. // Import all of the plugins
  96. if (Directory.Exists (local_plugins_path))
  97. // Local plugins go first...
  98. foreach (string xml_file_path in Directory.GetFiles (local_plugins_path, "*.xml")) {
  99. Plugins.Add (new SparklePlugin (xml_file_path));
  100. local_plugins_count++;
  101. }
  102. // ...system plugins after that...
  103. if (Directory.Exists (Program.Controller.PluginsPath)) {
  104. foreach (string xml_file_path in Directory.GetFiles (Program.Controller.PluginsPath, "*.xml")) {
  105. // ...and "Own server" at the very top
  106. if (xml_file_path.EndsWith ("own-server.xml")) {
  107. Plugins.Insert (0, new SparklePlugin (xml_file_path));
  108. } else if (xml_file_path.EndsWith ("ssnet.xml")) {
  109. // Plugins.Insert ((local_plugins_count + 1), new SparklePlugin (xml_file_path));
  110. // TODO: Skip this plugin for now
  111. } else {
  112. Plugins.Add (new SparklePlugin (xml_file_path));
  113. }
  114. }
  115. }
  116. SelectedPlugin = Plugins [0];
  117. Program.Controller.InviteReceived += delegate (SparkleInvite invite) {
  118. PendingInvite = invite;
  119. ChangePageEvent (PageType.Invite, null);
  120. ShowWindowEvent ();
  121. };
  122. Program.Controller.ShowSetupWindowEvent += delegate (PageType page_type) {
  123. if (page_type == PageType.CryptoSetup || page_type == PageType.CryptoPassword) {
  124. ChangePageEvent (page_type, null);
  125. return;
  126. }
  127. if (PendingInvite != null) {
  128. WindowIsOpen = true;
  129. ShowWindowEvent ();
  130. return;
  131. }
  132. if (this.current_page == PageType.Syncing ||
  133. this.current_page == PageType.Finished ||
  134. this.current_page == PageType.CryptoSetup ||
  135. this.current_page == PageType.CryptoPassword) {
  136. ShowWindowEvent ();
  137. return;
  138. }
  139. if (page_type == PageType.Add) {
  140. if (WindowIsOpen) {
  141. if (this.current_page == PageType.Error ||
  142. this.current_page == PageType.Finished ||
  143. this.current_page == PageType.None) {
  144. ChangePageEvent (PageType.Add, null);
  145. }
  146. } else if (!Program.Controller.FirstRun && TutorialPageNumber == 0) {
  147. WindowIsOpen = true;
  148. ChangePageEvent (PageType.Add, null);
  149. }
  150. ShowWindowEvent ();
  151. return;
  152. }
  153. WindowIsOpen = true;
  154. ChangePageEvent (page_type, null);
  155. ShowWindowEvent ();
  156. };
  157. }
  158. public void PageCancelled ()
  159. {
  160. PendingInvite = null;
  161. SelectedPlugin = Plugins [0];
  162. PreviousAddress = "";
  163. PreviousPath = "";
  164. PreviousUrl = "";
  165. this.saved_address = "";
  166. this.saved_remote_path = "";
  167. this.fetch_prior_history = false;
  168. WindowIsOpen = false;
  169. HideWindowEvent ();
  170. }
  171. public void CheckSetupPage (string full_name, string email)
  172. {
  173. full_name = full_name.Trim ();
  174. email = email.Trim ();
  175. bool fields_valid = (!string.IsNullOrEmpty (full_name) && IsValidEmail (email));
  176. UpdateSetupContinueButtonEvent (fields_valid);
  177. }
  178. public void SetupPageCancelled ()
  179. {
  180. Program.Controller.Quit ();
  181. }
  182. public void SetupPageCompleted (string full_name, string email)
  183. {
  184. Program.Controller.CurrentUser = new SparkleUser (full_name, email);
  185. TutorialPageNumber = 1;
  186. ChangePageEvent (PageType.Tutorial, null);
  187. }
  188. public void TutorialSkipped ()
  189. {
  190. TutorialPageNumber = 4;
  191. ChangePageEvent (PageType.Tutorial, null);
  192. }
  193. public void HistoryItemChanged (bool fetch_prior_history)
  194. {
  195. this.fetch_prior_history = fetch_prior_history;
  196. }
  197. public void TutorialPageCompleted ()
  198. {
  199. TutorialPageNumber++;
  200. if (TutorialPageNumber == 5) {
  201. TutorialPageNumber = 0;
  202. WindowIsOpen = false;
  203. HideWindowEvent ();
  204. if (this.create_startup_item)
  205. new Thread (() => Program.Controller.CreateStartupItem ()).Start ();
  206. } else {
  207. ChangePageEvent (PageType.Tutorial, null);
  208. }
  209. }
  210. public void SelectedPluginChanged (int plugin_index)
  211. {
  212. SelectedPlugin = Plugins [plugin_index];
  213. if (SelectedPlugin.Address != null) {
  214. ChangeAddressFieldEvent (SelectedPlugin.Address, "", FieldState.Disabled);
  215. } else if (SelectedPlugin.AddressExample != null) {
  216. ChangeAddressFieldEvent (this.saved_address, SelectedPlugin.AddressExample, FieldState.Enabled);
  217. } else {
  218. ChangeAddressFieldEvent (this.saved_address, "", FieldState.Enabled);
  219. }
  220. if (SelectedPlugin.Path != null) {
  221. ChangePathFieldEvent (SelectedPlugin.Path, "", FieldState.Disabled);
  222. } else if (SelectedPlugin.PathExample != null) {
  223. ChangePathFieldEvent (this.saved_remote_path, SelectedPlugin.PathExample, FieldState.Enabled);
  224. } else {
  225. ChangePathFieldEvent (this.saved_remote_path, "", FieldState.Enabled);
  226. }
  227. }
  228. public void StartupItemChanged (bool create_startup_item)
  229. {
  230. this.create_startup_item = create_startup_item;
  231. }
  232. public void CheckAddPage (string address, string remote_path, int selected_plugin)
  233. {
  234. address = address.Trim ();
  235. remote_path = remote_path.Trim ();
  236. if (selected_plugin == 0)
  237. this.saved_address = address;
  238. this.saved_remote_path = remote_path;
  239. bool fields_valid = (!string.IsNullOrEmpty (address) &&
  240. !string.IsNullOrEmpty (remote_path) && !remote_path.Contains ("\""));
  241. UpdateAddProjectButtonEvent (fields_valid);
  242. }
  243. public void AddPageCompleted (string address, string remote_path)
  244. {
  245. SyncingFolder = Path.GetFileName (remote_path);
  246. if (remote_path.EndsWith (".git"))
  247. SyncingFolder = remote_path.Substring (0, remote_path.Length - 4);
  248. SyncingFolder = SyncingFolder.Replace ("-crypto", "");
  249. SyncingFolder = SyncingFolder.ReplaceUnderscoreWithSpace ();
  250. ProgressBarPercentage = 1.0;
  251. ChangePageEvent (PageType.Syncing, null);
  252. address = Uri.EscapeUriString (address.Trim ());
  253. remote_path = remote_path.Trim ();
  254. remote_path = remote_path.TrimEnd ("/".ToCharArray ());
  255. if (SelectedPlugin.PathUsesLowerCase)
  256. remote_path = remote_path.ToLower ();
  257. PreviousAddress = address;
  258. PreviousPath = remote_path;
  259. Program.Controller.FolderFetched += AddPageFetchedDelegate;
  260. Program.Controller.FolderFetchError += AddPageFetchErrorDelegate;
  261. Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
  262. SparkleFetcherInfo info = new SparkleFetcherInfo {
  263. Address = address,
  264. Fingerprint = SelectedPlugin.Fingerprint,
  265. RemotePath = remote_path,
  266. FetchPriorHistory = this.fetch_prior_history,
  267. AnnouncementsUrl = SelectedPlugin.AnnouncementsUrl,
  268. Backend = SelectedPlugin.Backend
  269. };
  270. new Thread (() => { Program.Controller.StartFetcher (info); }).Start ();
  271. }
  272. // The following private methods are
  273. // delegates used by the previous method
  274. private void AddPageFetchedDelegate (string remote_url, string [] warnings)
  275. {
  276. SyncingFolder = "";
  277. // Create a local plugin for succesfully added projects, so
  278. // so the user can easily use the same host again
  279. if (SelectedPluginIndex == 0) {
  280. SparklePlugin new_plugin;
  281. Uri uri = new Uri (remote_url);
  282. try {
  283. string address = remote_url.Replace (uri.AbsolutePath, "");
  284. new_plugin = SparklePlugin.Create (uri.Host, address, address, "", "", "/path/to/project");
  285. if (new_plugin != null) {
  286. Plugins.Insert (1, new_plugin);
  287. SparkleLogger.LogInfo ("Controller", "Added plugin for " + uri.Host);
  288. }
  289. } catch {
  290. SparkleLogger.LogInfo ("Controller", "Failed adding plugin for " + uri.Host);
  291. }
  292. }
  293. ChangePageEvent (PageType.Finished, warnings);
  294. Program.Controller.FolderFetched -= AddPageFetchedDelegate;
  295. Program.Controller.FolderFetchError -= AddPageFetchErrorDelegate;
  296. Program.Controller.FolderFetching -= SyncingPageFetchingDelegate;
  297. }
  298. private void AddPageFetchErrorDelegate (string remote_url, string [] errors)
  299. {
  300. SyncingFolder = "";
  301. PreviousUrl = remote_url;
  302. ChangePageEvent (PageType.Error, errors);
  303. Program.Controller.FolderFetched -= AddPageFetchedDelegate;
  304. Program.Controller.FolderFetchError -= AddPageFetchErrorDelegate;
  305. Program.Controller.FolderFetching -= SyncingPageFetchingDelegate;
  306. }
  307. private void SyncingPageFetchingDelegate (double percentage, double speed)
  308. {
  309. ProgressBarPercentage = percentage;
  310. if (speed == 0.0)
  311. UpdateProgressBarEvent (ProgressBarPercentage, "");
  312. else
  313. UpdateProgressBarEvent (ProgressBarPercentage, "Fetching files… " + speed.ToSize () + "/s");
  314. }
  315. public void InvitePageCompleted ()
  316. {
  317. SyncingFolder = Path.GetFileName (PendingInvite.RemotePath);
  318. if (PendingInvite.RemotePath.EndsWith (".git"))
  319. SyncingFolder = PendingInvite.RemotePath.Substring (0, PendingInvite.RemotePath.Length - 4);
  320. SyncingFolder = SyncingFolder.Replace ("-crypto", "");
  321. SyncingFolder = SyncingFolder.ReplaceUnderscoreWithSpace ();
  322. PreviousAddress = PendingInvite.Address;
  323. PreviousPath = PendingInvite.RemotePath;
  324. ChangePageEvent (PageType.Syncing, null);
  325. new Thread (() => {
  326. if (!PendingInvite.Accept (Program.Controller.CurrentUser.PublicKey)) {
  327. PreviousUrl = PendingInvite.Address + PendingInvite.RemotePath.TrimStart ("/".ToCharArray ());
  328. ChangePageEvent (PageType.Error, new string [] { "error: Failed to upload the public key" });
  329. return;
  330. }
  331. Program.Controller.FolderFetched += InvitePageFetchedDelegate;
  332. Program.Controller.FolderFetchError += InvitePageFetchErrorDelegate;
  333. Program.Controller.FolderFetching += SyncingPageFetchingDelegate;
  334. SparkleFetcherInfo info = new SparkleFetcherInfo {
  335. Address = PendingInvite.Address,
  336. Fingerprint = PendingInvite.Fingerprint,
  337. RemotePath = PendingInvite.RemotePath,
  338. FetchPriorHistory = false, // TODO: checkbox on invite page
  339. AnnouncementsUrl = PendingInvite.AnnouncementsUrl
  340. };
  341. Program.Controller.StartFetcher (info);
  342. }).Start ();
  343. }
  344. // The following private methods are
  345. // delegates used by the previous method
  346. private void InvitePageFetchedDelegate (string remote_url, string [] warnings)
  347. {
  348. SyncingFolder = "";
  349. PendingInvite = null;
  350. ChangePageEvent (PageType.Finished, warnings);
  351. Program.Controller.FolderFetched -= AddPageFetchedDelegate;
  352. Program.Controller.FolderFetchError -= AddPageFetchErrorDelegate;
  353. Program.Controller.FolderFetching -= SyncingPageFetchingDelegate;
  354. }
  355. private void InvitePageFetchErrorDelegate (string remote_url, string [] errors)
  356. {
  357. SyncingFolder = "";
  358. PreviousUrl = remote_url;
  359. ChangePageEvent (PageType.Error, errors);
  360. Program.Controller.FolderFetched -= AddPageFetchedDelegate;
  361. Program.Controller.FolderFetchError -= AddPageFetchErrorDelegate;
  362. Program.Controller.FolderFetching -= SyncingPageFetchingDelegate;
  363. }
  364. public void SyncingCancelled ()
  365. {
  366. Program.Controller.StopFetcher ();
  367. if (PendingInvite != null)
  368. ChangePageEvent (PageType.Invite, null);
  369. else
  370. ChangePageEvent (PageType.Add, null);
  371. }
  372. public void ErrorPageCompleted ()
  373. {
  374. if (PendingInvite != null)
  375. ChangePageEvent (PageType.Invite, null);
  376. else
  377. ChangePageEvent (PageType.Add, null);
  378. }
  379. public void CheckCryptoSetupPage (string password)
  380. {
  381. new Thread (() => {
  382. bool is_valid_password = (password.Length > 0 && !password.StartsWith (" ") && !password.EndsWith (" "));
  383. UpdateCryptoSetupContinueButtonEvent (is_valid_password);
  384. }).Start ();
  385. }
  386. public void CheckCryptoPasswordPage (string password)
  387. {
  388. bool is_password_correct = Program.Controller.CheckPassword (password);
  389. UpdateCryptoPasswordContinueButtonEvent (is_password_correct);
  390. }
  391. public void CryptoPageCancelled ()
  392. {
  393. SyncingCancelled ();
  394. }
  395. public void CryptoSetupPageCompleted (string password)
  396. {
  397. CryptoPasswordPageCompleted (password);
  398. }
  399. public void CryptoPasswordPageCompleted (string password)
  400. {
  401. ProgressBarPercentage = 100.0;
  402. ChangePageEvent (PageType.Syncing, null);
  403. new Thread (() => {
  404. Thread.Sleep (1000);
  405. Program.Controller.FinishFetcher (password);
  406. }).Start ();
  407. }
  408. public void CopyToClipboardClicked ()
  409. {
  410. Program.Controller.CopyToClipboard (Program.Controller.CurrentUser.PublicKey);
  411. }
  412. public void ShowFilesClicked ()
  413. {
  414. string folder_name = Path.GetFileName (PreviousPath);
  415. folder_name = folder_name.ReplaceUnderscoreWithSpace ();
  416. if (PreviousPath.EndsWith ("-crypto"))
  417. folder_name = folder_name.Replace ("-crypto", "");
  418. if (PreviousPath.EndsWith ("-crypto.git"))
  419. folder_name = folder_name.Replace ("-crypto.git", "");
  420. Program.Controller.OpenSparkleShareFolder (folder_name);
  421. FinishPageCompleted ();
  422. }
  423. public void FinishPageCompleted ()
  424. {
  425. SelectedPlugin = Plugins [0];
  426. PreviousUrl = "";
  427. PreviousAddress = "";
  428. PreviousPath = "";
  429. this.fetch_prior_history = false;
  430. this.saved_address = "";
  431. this.saved_remote_path = "";
  432. this.current_page = PageType.None;
  433. WindowIsOpen = false;
  434. HideWindowEvent ();
  435. }
  436. private bool IsValidEmail (string email)
  437. {
  438. return new Regex (@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$", RegexOptions.IgnoreCase).IsMatch (email);
  439. }
  440. }
  441. }