/SparkleShare/SparkleControllerBase.cs

http://github.com/hbons/SparkleShare · C# · 796 lines · 534 code · 218 blank · 44 comment · 75 complexity · 50287f45928e08fdeb7a7c89781f0c79 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.Linq;
  20. using System.Threading;
  21. using SparkleLib;
  22. namespace SparkleShare {
  23. public abstract class SparkleControllerBase {
  24. public SparkleRepoBase [] Repositories {
  25. get {
  26. lock (this.repo_lock)
  27. return this.repositories.GetRange (0, this.repositories.Count).ToArray ();
  28. }
  29. }
  30. private void AddRepository (SparkleRepoBase repo)
  31. {
  32. lock (this.repo_lock) {
  33. this.repositories.Add (repo);
  34. this.repositories.Sort ((x, y) => string.Compare (x.Name, y.Name));
  35. }
  36. }
  37. private void RemoveRepository (SparkleRepoBase repo)
  38. {
  39. lock (this.repo_lock)
  40. this.repositories.Remove (repo);
  41. }
  42. public SparkleRepoBase GetRepoByName (string name)
  43. {
  44. lock (this.repo_lock) {
  45. foreach (SparkleRepoBase repo in this.repositories)
  46. if (repo.Name.Equals (name))
  47. return repo;
  48. }
  49. return null;
  50. }
  51. public SparkleConfig Config { get; private set; }
  52. public bool RepositoriesLoaded { get; private set; }
  53. public string FoldersPath { get; private set; }
  54. public double ProgressPercentage = 0.0;
  55. public double ProgressSpeedUp = 0.0;
  56. public double ProgressSpeedDown = 0.0;
  57. public event ShowSetupWindowEventHandler ShowSetupWindowEvent = delegate { };
  58. public delegate void ShowSetupWindowEventHandler (PageType page_type);
  59. public event ShowNoteWindowEventHandler ShowNoteWindowEvent = delegate { };
  60. public delegate void ShowNoteWindowEventHandler (string project);
  61. public event Action ShowAboutWindowEvent = delegate { };
  62. public event Action ShowEventLogWindowEvent = delegate { };
  63. public event FolderFetchedEventHandler FolderFetched = delegate { };
  64. public delegate void FolderFetchedEventHandler (string remote_url, string [] warnings);
  65. public event FolderFetchErrorHandler FolderFetchError = delegate { };
  66. public delegate void FolderFetchErrorHandler (string remote_url, string [] errors);
  67. public event FolderFetchingHandler FolderFetching = delegate { };
  68. public delegate void FolderFetchingHandler (double percentage, double speed);
  69. public event Action FolderListChanged = delegate { };
  70. public event Action OnIdle = delegate { };
  71. public event Action OnSyncing = delegate { };
  72. public event Action OnError = delegate { };
  73. public event InviteReceivedHandler InviteReceived = delegate { };
  74. public delegate void InviteReceivedHandler (SparkleInvite invite);
  75. public event NotificationRaisedEventHandler NotificationRaised = delegate { };
  76. public delegate void NotificationRaisedEventHandler (SparkleChangeSet change_set);
  77. public event AlertNotificationRaisedEventHandler AlertNotificationRaised = delegate { };
  78. public delegate void AlertNotificationRaisedEventHandler (string title, string message);
  79. public bool FirstRun {
  80. get { return Config.User.Email.Equals ("Unknown"); }
  81. }
  82. public List<string> Folders {
  83. get {
  84. List<string> folders = Config.Folders;
  85. return folders;
  86. }
  87. }
  88. public SparkleUser CurrentUser {
  89. get { return Config.User; }
  90. set { Config.User = value; }
  91. }
  92. public bool NotificationsEnabled {
  93. get {
  94. string notifications_enabled = Config.GetConfigOption ("notifications");
  95. if (string.IsNullOrEmpty (notifications_enabled)) {
  96. Config.SetConfigOption ("notifications", bool.TrueString);
  97. return true;
  98. } else {
  99. return notifications_enabled.Equals (bool.TrueString);
  100. }
  101. }
  102. }
  103. public bool AvatarsEnabled {
  104. get {
  105. string fetch_avatars_option = Config.GetConfigOption ("fetch_avatars");
  106. if (fetch_avatars_option != null && fetch_avatars_option.Equals (bool.FalseString))
  107. return false;
  108. return true;
  109. }
  110. }
  111. // Path where the plugins are kept
  112. public abstract string PluginsPath { get; }
  113. // Enables SparkleShare to start automatically at login
  114. public abstract void CreateStartupItem ();
  115. // Installs the sparkleshare:// protocol handler
  116. public abstract void InstallProtocolHandler ();
  117. // Adds the SparkleShare folder to the user's
  118. // list of bookmarked places
  119. public abstract void AddToBookmarks ();
  120. // Creates the SparkleShare folder in the user's home folder
  121. public abstract bool CreateSparkleShareFolder ();
  122. // Opens the SparkleShare folder or an (optional) subfolder
  123. public abstract void OpenFolder (string path);
  124. // Opens a file with the appropriate application
  125. public abstract void OpenFile (string path);
  126. // Opens a file with the appropriate application
  127. public virtual void OpenWebsite (string url) { }
  128. // Copies text to the clipboard
  129. public abstract void CopyToClipboard (string text);
  130. public abstract string EventLogHTML { get; }
  131. public abstract string DayEntryHTML { get; }
  132. public abstract string EventEntryHTML { get; }
  133. private SparkleFetcherBase fetcher;
  134. private FileSystemWatcher watcher;
  135. private Object repo_lock = new Object ();
  136. private Object check_repos_lock = new Object ();
  137. private List<SparkleRepoBase> repositories = new List<SparkleRepoBase> ();
  138. private bool lost_folders_path = false;
  139. public SparkleControllerBase ()
  140. {
  141. string app_data_path = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
  142. string config_path = Path.Combine (app_data_path, "sparkleshare");
  143. Config = new SparkleConfig (config_path, "config.xml");
  144. SparkleConfig.DefaultConfig = Config;
  145. FoldersPath = Config.FoldersPath;
  146. }
  147. public virtual void Initialize ()
  148. {
  149. SparkleLogger.LogInfo ("Environment", "SparkleShare version: " + SparkleLib.SparkleBackend.Version +
  150. ", Operating system: " + SparkleLib.SparkleBackend.Platform + " (" + Environment.OSVersion + ")");
  151. SparklePlugin.PluginsPath = PluginsPath;
  152. InstallProtocolHandler ();
  153. try {
  154. if (CreateSparkleShareFolder ())
  155. AddToBookmarks ();
  156. } catch (DirectoryNotFoundException) {
  157. this.lost_folders_path = true;
  158. }
  159. bool keys_imported = false;
  160. if (FirstRun) {
  161. Config.SetConfigOption ("notifications", bool.TrueString);
  162. } else {
  163. string keys_path = Path.GetDirectoryName (Config.FullPath);
  164. string key_file_path = "";
  165. foreach (string file_path in Directory.GetFiles (keys_path)) {
  166. string file_name = Path.GetFileName(file_path);
  167. if (file_name.EndsWith (".key")) {
  168. key_file_path = Path.Combine (keys_path, file_name);
  169. // Replace spaces with underscores in old keys
  170. if (file_name.Contains (" ")) {
  171. string new_file_name = file_name.Replace (" ", "_");
  172. File.Move (key_file_path, Path.Combine (keys_path, new_file_name));
  173. File.Move (key_file_path + ".pub", Path.Combine (keys_path, new_file_name + ".pub"));
  174. key_file_path = Path.Combine (keys_path, new_file_name);
  175. }
  176. SparkleKeys.ImportPrivateKey (key_file_path);
  177. keys_imported = true;
  178. break;
  179. }
  180. }
  181. if (keys_imported) {
  182. CurrentUser.PublicKey = File.ReadAllText (key_file_path + ".pub");
  183. } else {
  184. string [] key_pair = CreateKeys ();
  185. SparkleKeys.ImportPrivateKey (key_pair [0]);
  186. CurrentUser.PublicKey = File.ReadAllText (key_pair [1]);
  187. }
  188. SparkleKeys.ListPrivateKeys ();
  189. FolderListChanged (); // FIXME: Hacky way to update status icon menu to show the key
  190. }
  191. // Watch the SparkleShare folder
  192. this.watcher = new FileSystemWatcher () {
  193. Filter = "*",
  194. IncludeSubdirectories = false,
  195. Path = FoldersPath
  196. };
  197. watcher.Created += OnFolderActivity;
  198. // FIXME watcher.Deleted += OnFolderActivity;
  199. // FIXME watcher.Renamed += OnFolderActivity;
  200. watcher.EnableRaisingEvents = true;
  201. }
  202. private int reopen_attempt_counts = 0;
  203. public void HandleReopen ()
  204. {
  205. if (Repositories.Length > 0) {
  206. ShowEventLogWindow ();
  207. } else if (reopen_attempt_counts > 1) {
  208. AlertNotificationRaised ("Hello!", "SparkleShare sits right here, as a status icon.");
  209. reopen_attempt_counts = 0;
  210. } else {
  211. reopen_attempt_counts++;
  212. }
  213. }
  214. public void UIHasLoaded ()
  215. {
  216. if (this.lost_folders_path) {
  217. Program.UI.Bubbles.Controller.ShowBubble ("Where's your SparkleShare folder?",
  218. "Did you put it on a detached drive?", null);
  219. Environment.Exit (-1);
  220. }
  221. if (FirstRun) {
  222. ShowSetupWindow (PageType.Setup);
  223. new Thread (() => {
  224. string [] key_pair = CreateKeys ();
  225. SparkleKeys.ImportPrivateKey (key_pair [0]);
  226. CurrentUser.PublicKey = File.ReadAllText (key_pair [1]);
  227. FolderListChanged (); // FIXME: Hacky way to update status icon menu to show the key
  228. }).Start ();
  229. } else {
  230. new Thread (() => {
  231. StartupInviteScan ();
  232. CheckRepositories ();
  233. RepositoriesLoaded = true;
  234. UpdateState ();
  235. }).Start ();
  236. }
  237. }
  238. public void ShowSetupWindow (PageType page_type)
  239. {
  240. ShowSetupWindowEvent (page_type);
  241. }
  242. public void ShowAboutWindow ()
  243. {
  244. ShowAboutWindowEvent ();
  245. }
  246. public void ShowNoteWindow (string project)
  247. {
  248. ShowNoteWindowEvent (project);
  249. }
  250. public void ShowEventLogWindow ()
  251. {
  252. ShowEventLogWindowEvent ();
  253. }
  254. public void OpenSparkleShareFolder ()
  255. {
  256. OpenFolder (Config.FoldersPath);
  257. }
  258. public void OpenSparkleShareFolder (string name)
  259. {
  260. OpenFolder (new SparkleFolder (name).FullPath);
  261. }
  262. public void ToggleNotifications ()
  263. {
  264. bool notifications_enabled = Config.GetConfigOption ("notifications").Equals (bool.TrueString);
  265. Config.SetConfigOption ("notifications", (!notifications_enabled).ToString ());
  266. }
  267. private void CheckRepositories ()
  268. {
  269. lock (this.check_repos_lock) {
  270. string path = Config.FoldersPath;
  271. // Detect any renames
  272. foreach (string folder_path in Directory.GetDirectories (path)) {
  273. string folder_name = Path.GetFileName (folder_path);
  274. if (folder_name.Equals (".tmp"))
  275. continue;
  276. if (Config.GetIdentifierForFolder (folder_name) == null) {
  277. string identifier_file_path = Path.Combine (folder_path, ".sparkleshare");
  278. if (!File.Exists (identifier_file_path))
  279. continue;
  280. string identifier = File.ReadAllText (identifier_file_path).Trim ();
  281. if (Config.IdentifierExists (identifier)) {
  282. RemoveRepository (GetRepoByName (folder_name));
  283. Config.RenameFolder (identifier, folder_name);
  284. string new_folder_path = Path.Combine (path, folder_name);
  285. AddRepository (new_folder_path);
  286. SparkleLogger.LogInfo ("Controller",
  287. "Renamed folder with identifier " + identifier + " to '" + folder_name + "'");
  288. }
  289. }
  290. }
  291. // Remove any deleted folders
  292. foreach (string folder_name in Config.Folders) {
  293. string folder_path = new SparkleFolder (folder_name).FullPath;
  294. if (!Directory.Exists (folder_path)) {
  295. Config.RemoveFolder (folder_name);
  296. RemoveRepository (GetRepoByName (folder_name));
  297. SparkleLogger.LogInfo ("Controller", "Removed folder '" + folder_name + "' from config");
  298. } else {
  299. AddRepository (folder_path);
  300. }
  301. }
  302. // Remove any duplicate folders
  303. string previous_name = "";
  304. foreach (string folder_name in Config.Folders) {
  305. if (!string.IsNullOrEmpty (previous_name) && folder_name.Equals (previous_name))
  306. Config.RemoveFolder (folder_name);
  307. else
  308. previous_name = folder_name;
  309. }
  310. FolderListChanged ();
  311. }
  312. }
  313. private void AddRepository (string folder_path)
  314. {
  315. SparkleRepoBase repo = null;
  316. string folder_name = Path.GetFileName (folder_path);
  317. string backend = Config.GetBackendForFolder (folder_name);
  318. try {
  319. repo = (SparkleRepoBase) Activator.CreateInstance (
  320. Type.GetType ("SparkleLib." + backend + ".SparkleRepo, SparkleLib." + backend),
  321. new object [] { folder_path, Config });
  322. } catch (Exception e) {
  323. SparkleLogger.LogInfo ("Controller", "Failed to load backend '" + backend + "' for '" + folder_name + "': ", e);
  324. return;
  325. }
  326. repo.ChangesDetected += delegate {
  327. UpdateState ();
  328. };
  329. repo.SyncStatusChanged += delegate (SyncStatus status) {
  330. if (status == SyncStatus.Idle) {
  331. ProgressPercentage = 0.0;
  332. ProgressSpeedUp = 0.0;
  333. ProgressSpeedDown = 0.0;
  334. }
  335. UpdateState ();
  336. };
  337. repo.ProgressChanged += delegate {
  338. ProgressPercentage = 0.0;
  339. ProgressSpeedUp = 0.0;
  340. ProgressSpeedDown = 0.0;
  341. double percentage = 0.0;
  342. int repo_count = 0;
  343. foreach (SparkleRepoBase rep in Repositories) {
  344. if (rep.ProgressPercentage > 0) {
  345. percentage += rep.ProgressPercentage;
  346. repo_count++;
  347. }
  348. if (rep.Status == SyncStatus.SyncUp)
  349. ProgressSpeedUp += rep.ProgressSpeed;
  350. if (rep.Status == SyncStatus.SyncDown)
  351. ProgressSpeedDown += rep.ProgressSpeed;
  352. }
  353. if (repo_count > 0)
  354. ProgressPercentage = percentage / repo_count;
  355. UpdateState ();
  356. };
  357. repo.NewChangeSet += delegate (SparkleChangeSet change_set) {
  358. if (AvatarsEnabled)
  359. change_set.User.AvatarFilePath = SparkleAvatars.GetAvatar (change_set.User.Email, 48, Config.FullPath);
  360. NotificationRaised (change_set);
  361. };
  362. repo.ConflictResolved += delegate {
  363. AlertNotificationRaised ("Resolved a file collision",
  364. "Local and server versions were kept.");
  365. };
  366. AddRepository (repo);
  367. repo.Initialize ();
  368. }
  369. private void OnFolderActivity (object o, FileSystemEventArgs args)
  370. {
  371. if (args != null && args.FullPath.EndsWith (".xml") &&
  372. args.ChangeType == WatcherChangeTypes.Created) {
  373. HandleInvite (args);
  374. return;
  375. }/* else { FIXME: on the fly folder removal doesn't always work. disabling for now
  376. Thread.Sleep (1000);
  377. if (Directory.Exists (args.FullPath) && args.ChangeType == WatcherChangeTypes.Created)
  378. return;
  379. CheckRepositories ();
  380. }*/
  381. }
  382. private void StartupInviteScan ()
  383. {
  384. foreach (string invite in Directory.GetFiles (FoldersPath, "*.xml")) {
  385. HandleInvite (invite);
  386. }
  387. }
  388. private void HandleInvite (FileSystemEventArgs args)
  389. {
  390. HandleInvite (args.FullPath);
  391. }
  392. private void HandleInvite (string path)
  393. {
  394. if (this.fetcher != null &&
  395. this.fetcher.IsActive) {
  396. AlertNotificationRaised ("SparkleShare Setup seems busy", "Please wait for it to finish");
  397. } else {
  398. SparkleInvite invite = new SparkleInvite (path);
  399. // It may be that the invite we received a path to isn't
  400. // fully downloaded yet, so we try to read it several times
  401. int tries = 0;
  402. while (!invite.IsValid) {
  403. Thread.Sleep (100);
  404. invite = new SparkleInvite (path);
  405. tries++;
  406. if (tries > 10) {
  407. AlertNotificationRaised ("Oh noes!", "This invite seems screwed up...");
  408. break;
  409. }
  410. }
  411. if (invite.IsValid)
  412. InviteReceived (invite);
  413. File.Delete (path);
  414. }
  415. }
  416. // Fires events for the current syncing state
  417. private void UpdateState ()
  418. {
  419. bool has_unsynced_repos = false;
  420. bool has_syncing_repos = false;
  421. foreach (SparkleRepoBase repo in Repositories) {
  422. if (repo.Status == SyncStatus.SyncDown || repo.Status == SyncStatus.SyncUp || repo.IsBuffering) {
  423. has_syncing_repos = true;
  424. break;
  425. } else if (repo.Status == SyncStatus.Idle && repo.HasUnsyncedChanges) {
  426. has_unsynced_repos = true;
  427. }
  428. }
  429. if (has_syncing_repos)
  430. OnSyncing ();
  431. else if (has_unsynced_repos)
  432. OnError ();
  433. else
  434. OnIdle ();
  435. }
  436. public void StartFetcher (SparkleFetcherInfo info)
  437. {
  438. string tmp_path = Config.TmpPath;
  439. if (!Directory.Exists (tmp_path)) {
  440. Directory.CreateDirectory (tmp_path);
  441. File.SetAttributes (tmp_path, File.GetAttributes (tmp_path) | FileAttributes.Hidden);
  442. }
  443. string canonical_name = Path.GetFileName (info.RemotePath);
  444. string backend = info.Backend;
  445. if (string.IsNullOrEmpty (backend))
  446. backend = SparkleFetcherBase.GetBackend (info.Address);
  447. info.TargetDirectory = Path.Combine (tmp_path, canonical_name);
  448. try {
  449. this.fetcher = (SparkleFetcherBase) Activator.CreateInstance (
  450. Type.GetType ("SparkleLib." + backend + ".SparkleFetcher, SparkleLib." + backend), info);
  451. } catch (Exception e) {
  452. SparkleLogger.LogInfo ("Controller",
  453. "Failed to load '" + backend + "' backend for '" + canonical_name + "' " + e.Message);
  454. FolderFetchError (Path.Combine (info.Address, info.RemotePath).Replace (@"\", "/"),
  455. new string [] {"Failed to load \"" + backend + "\" backend for \"" + canonical_name + "\""});
  456. return;
  457. }
  458. this.fetcher.Finished += delegate (bool repo_is_encrypted, bool repo_is_empty, string [] warnings) {
  459. if (repo_is_encrypted && repo_is_empty) {
  460. ShowSetupWindowEvent (PageType.CryptoSetup);
  461. } else if (repo_is_encrypted) {
  462. ShowSetupWindowEvent (PageType.CryptoPassword);
  463. } else {
  464. FinishFetcher ();
  465. }
  466. };
  467. this.fetcher.Failed += delegate {
  468. FolderFetchError (this.fetcher.RemoteUrl.ToString (), this.fetcher.Errors);
  469. StopFetcher ();
  470. };
  471. this.fetcher.ProgressChanged += delegate (double percentage, double speed) {
  472. FolderFetching (percentage, speed);
  473. };
  474. this.fetcher.Start ();
  475. }
  476. public void StopFetcher ()
  477. {
  478. this.fetcher.Stop ();
  479. this.fetcher.Dispose ();
  480. this.fetcher = null;
  481. this.watcher.EnableRaisingEvents = true;
  482. }
  483. public bool CheckPassword (string password)
  484. {
  485. return this.fetcher.IsFetchedRepoPasswordCorrect (password);
  486. }
  487. public void FinishFetcher (string password)
  488. {
  489. this.fetcher.EnableFetchedRepoCrypto (password);
  490. FinishFetcher ();
  491. }
  492. public void FinishFetcher ()
  493. {
  494. this.watcher.EnableRaisingEvents = false;
  495. this.fetcher.Complete ();
  496. string canonical_name = Path.GetFileName (this.fetcher.RemoteUrl.AbsolutePath);
  497. if (canonical_name.EndsWith (".git"))
  498. canonical_name = canonical_name.Replace (".git", "");
  499. canonical_name = canonical_name.Replace ("-crypto", "");
  500. canonical_name = canonical_name.ReplaceUnderscoreWithSpace ();
  501. canonical_name = canonical_name.Replace ("%20", " ");
  502. bool target_folder_exists = Directory.Exists (
  503. Path.Combine (Config.FoldersPath, canonical_name));
  504. // Add a numbered suffix to the name if a folder with the same name
  505. // already exists. Example: "Folder (2)"
  506. int suffix = 1;
  507. while (target_folder_exists) {
  508. suffix++;
  509. target_folder_exists = Directory.Exists (
  510. Path.Combine (Config.FoldersPath, canonical_name + " (" + suffix + ")"));
  511. }
  512. string target_folder_name = canonical_name;
  513. if (suffix > 1)
  514. target_folder_name += " (" + suffix + ")";
  515. string target_folder_path = Path.Combine (Config.FoldersPath, target_folder_name);
  516. try {
  517. Directory.Move (this.fetcher.TargetFolder, target_folder_path);
  518. } catch (Exception e) {
  519. SparkleLogger.LogInfo ("Controller", "Error moving directory, trying again...", e);
  520. try {
  521. ClearDirectoryAttributes (this.fetcher.TargetFolder);
  522. Directory.Move (this.fetcher.TargetFolder, target_folder_path);
  523. } catch (Exception x) {
  524. SparkleLogger.LogInfo ("Controller", "Error moving directory", x);
  525. this.fetcher.Dispose ();
  526. this.fetcher = null;
  527. this.watcher.EnableRaisingEvents = true;
  528. return;
  529. }
  530. }
  531. string backend = SparkleFetcherBase.GetBackend (this.fetcher.RemoteUrl.ToString ());
  532. Config.AddFolder (target_folder_name, this.fetcher.Identifier,
  533. this.fetcher.RemoteUrl.ToString (), backend);
  534. if (this.fetcher.OriginalFetcherInfo.AnnouncementsUrl != null) {
  535. Config.SetFolderOptionalAttribute (target_folder_name, "announcements_url",
  536. this.fetcher.OriginalFetcherInfo.AnnouncementsUrl);
  537. }
  538. RepositoriesLoaded = true;
  539. FolderFetched (this.fetcher.RemoteUrl.ToString (), this.fetcher.Warnings.ToArray ());
  540. AddRepository (target_folder_path);
  541. FolderListChanged ();
  542. this.fetcher.Dispose ();
  543. this.fetcher = null;
  544. this.watcher.EnableRaisingEvents = true;
  545. }
  546. public virtual void Quit ()
  547. {
  548. foreach (SparkleRepoBase repo in Repositories)
  549. repo.Dispose ();
  550. Environment.Exit (0);
  551. }
  552. private void ClearDirectoryAttributes (string path)
  553. {
  554. if (!Directory.Exists (path))
  555. return;
  556. string [] folders = Directory.GetDirectories (path);
  557. foreach (string folder in folders)
  558. ClearDirectoryAttributes (folder);
  559. string [] files = Directory.GetFiles(path);
  560. foreach (string file in files)
  561. if (!IsSymlink (file))
  562. File.SetAttributes (file, FileAttributes.Normal);
  563. }
  564. private string [] CreateKeys ()
  565. {
  566. string keys_path = Path.GetDirectoryName (SparkleConfig.DefaultConfig.FullPath);
  567. string key_file_name = DateTime.Now.ToString ("yyyy-MM-dd_HH\\hmm");
  568. return SparkleKeys.GenerateKeyPair (keys_path, key_file_name);
  569. }
  570. private bool IsSymlink (string file)
  571. {
  572. FileAttributes attributes = File.GetAttributes (file);
  573. return ((attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint);
  574. }
  575. }
  576. }