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