diff --git a/SparkleLib/Makefile.am b/SparkleLib/Makefile.am index e0b2d050..51075e07 100644 --- a/SparkleLib/Makefile.am +++ b/SparkleLib/Makefile.am @@ -9,14 +9,15 @@ SOURCES = \ SparkleChangeSet.cs \ SparkleFetcherBase.cs \ SparkleFetcherGit.cs \ - SparkleGit.cs \ + SparkleFetcherMercurial.cs \ SparkleHelpers.cs \ SparkleListenerBase.cs \ SparkleListenerIrc.cs \ SparkleOptions.cs \ SparklePaths.cs \ SparkleRepoBase.cs \ - SparkleRepoGit.cs + SparkleRepoGit.cs \ + SparkleRepoMercurial.cs SMARTIRC4NET_FILES_EXPANDED = $(foreach file, $(SMARTIRC4NET_FILES), $(top_builddir)/$(file)) diff --git a/SparkleLib/SparkleFetcherGit.cs b/SparkleLib/SparkleFetcherGit.cs index 6842ec47..2d7d4ed8 100644 --- a/SparkleLib/SparkleFetcherGit.cs +++ b/SparkleLib/SparkleFetcherGit.cs @@ -136,4 +136,24 @@ namespace SparkleLib { writer.Close (); } } + + public class SparkleGit : Process { + + public SparkleGit (string path, string args) : base () + { + EnableRaisingEvents = true; + StartInfo.FileName = SparkleBackend.DefaultBackend.Path; + StartInfo.Arguments = args; + StartInfo.RedirectStandardOutput = true; + StartInfo.UseShellExecute = false; + StartInfo.WorkingDirectory = path; + } + + + new public void Start () + { + SparkleHelpers.DebugInfo ("Cmd", StartInfo.FileName + " " + StartInfo.Arguments); + base.Start (); + } + } } diff --git a/SparkleLib/SparkleFetcherMercurial.cs b/SparkleLib/SparkleFetcherMercurial.cs new file mode 100644 index 00000000..08057ecd --- /dev/null +++ b/SparkleLib/SparkleFetcherMercurial.cs @@ -0,0 +1,161 @@ +// SparkleShare, an instant update workflow to Git. +// Copyright (C) 2010 Hylke Bons +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +using System; +using System.IO; +using System.Diagnostics; +using System.Xml; + +namespace SparkleLib { + + // Sets up a fetcher that can get remote folders + public class SparkleFetcherHg : SparkleFetcherBase { + + public SparkleFetcherHg (string remote_url, string target_folder) : + base (remote_url, target_folder) { } + + + public override bool Fetch () + { + SparkleHg hg = new SparkleHg (SparklePaths.SparkleTmpPath, + "clone \"" + base.remote_url + "\" " + "\"" + base.target_folder + "\""); + + hg.Start (); + hg.WaitForExit (); + + SparkleHelpers.DebugInfo ("Hg", "Exit code " + hg.ExitCode.ToString ()); + + if (hg.ExitCode != 0) { + return false; + } else { + InstallConfiguration (); + InstallExcludeRules (); + return true; + } + } + + + // Install the user's name and email and some config into + // the newly cloned repository + private void InstallConfiguration () + { + string global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config.xml"); + + if (!File.Exists (global_config_file_path)) + return; + + string repo_config_file_path = SparkleHelpers.CombineMore (base.target_folder, ".hg", "hgrc"); + string config = String.Join (Environment.NewLine, File.ReadAllLines (repo_config_file_path)); + + // Add user info + string n = Environment.NewLine; + XmlDocument xml = new XmlDocument(); + xml.Load (global_config_file_path); + + XmlNode node_name = xml.SelectSingleNode ("//user/name/text()"); + XmlNode node_email = xml.SelectSingleNode ("//user/email/text()"); + + // TODO this ignore duplicate names (FolderName (2)) + string ignore_file_path = base.target_folder.Replace (SparklePaths.SparkleTmpPath, + SparklePaths.SparklePath); + + ignore_file_path = SparkleHelpers.CombineMore (ignore_file_path, ".hg", "hgignore"); + + config += n + + "[ui]" + n + + "username = " + node_name.Value + " <" + node_email.Value + ">" + n + + "ignore = " + ignore_file_path + n; + + // Write the config to the file + TextWriter writer = new StreamWriter (repo_config_file_path); + writer.WriteLine (config); + writer.Close (); + + SparkleHelpers.DebugInfo ("Config", "Added configuration to '" + repo_config_file_path + "'"); + } + + + // Add a .gitignore file to the repo + private void InstallExcludeRules () + { + string exlude_rules_file_path = SparkleHelpers.CombineMore ( + this.target_folder, ".hg", "hgignore"); + + TextWriter writer = new StreamWriter (exlude_rules_file_path); + + writer.WriteLine ("syntax: glob"); + + // gedit and emacs + writer.WriteLine ("*~"); + + // vi(m) + writer.WriteLine (".*.sw[a-z]"); + writer.WriteLine ("*.un~"); + writer.WriteLine ("*.swp"); + writer.WriteLine ("*.swo"); + + // KDE + writer.WriteLine (".directory"); + + // Mac OSX + writer.WriteLine (".DS_Store"); + writer.WriteLine ("Icon?"); + writer.WriteLine ("._*"); + writer.WriteLine (".Spotlight-V100"); + writer.WriteLine (".Trashes"); + + // Mac OSX + writer.WriteLine ("*(Autosaved).graffle"); + + // Windows + writer.WriteLine ("Thumbs.db"); + writer.WriteLine ("Desktop.ini"); + + // CVS + writer.WriteLine ("*/CVS/*"); + writer.WriteLine (".cvsignore"); + writer.WriteLine ("*/.cvsignore"); + + // Subversion + writer.WriteLine ("/.svn/*"); + writer.WriteLine ("*/.svn/*"); + + writer.Close (); + } + } + + + public class SparkleHg : Process { + + public SparkleHg (string path, string args) : base () + { + EnableRaisingEvents = true; + StartInfo.FileName = "/opt/local/bin/hg"; + StartInfo.Arguments = args; + StartInfo.RedirectStandardOutput = true; + StartInfo.UseShellExecute = false; + StartInfo.WorkingDirectory = path; + } + + + new public void Start () + { + SparkleHelpers.DebugInfo ("Cmd", StartInfo.FileName + " " + StartInfo.Arguments); + base.Start (); + } + } +} diff --git a/SparkleLib/SparkleGit.cs b/SparkleLib/SparkleGit.cs deleted file mode 100644 index 2bc9add1..00000000 --- a/SparkleLib/SparkleGit.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SparkleShare, an instant update workflow to Git. -// Copyright (C) 2010 Hylke Bons -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - - -using System; -using System.Diagnostics; - -namespace SparkleLib { - - public class SparkleGit : Process { - - public SparkleGit (string path, string args) : base () - { - EnableRaisingEvents = true; - StartInfo.FileName = SparkleBackend.DefaultBackend.Path; - StartInfo.Arguments = args; - StartInfo.RedirectStandardOutput = true; - StartInfo.UseShellExecute = false; - StartInfo.WorkingDirectory = path; - } - - - new public void Start () - { - SparkleHelpers.DebugInfo ("Cmd", StartInfo.FileName + " " + StartInfo.Arguments); - base.Start (); - } - } -} diff --git a/SparkleLib/SparkleLib.csproj b/SparkleLib/SparkleLib.csproj index e297a0b7..17ece9e9 100644 --- a/SparkleLib/SparkleLib.csproj +++ b/SparkleLib/SparkleLib.csproj @@ -39,8 +39,10 @@ + + diff --git a/SparkleLib/SparkleRepoBase.cs b/SparkleLib/SparkleRepoBase.cs index bbe73d4a..7180ba54 100644 --- a/SparkleLib/SparkleRepoBase.cs +++ b/SparkleLib/SparkleRepoBase.cs @@ -21,6 +21,7 @@ using System.Diagnostics; // remove using System.IO; using System.Text.RegularExpressions; using System.Timers; +using System.Xml; namespace SparkleLib { @@ -53,11 +54,11 @@ namespace SparkleLib { public string Domain { get { - Regex regex = new Regex (@"*://(.+)(/|:)*"); + Regex regex = new Regex (@"(@|://)([a-z0-9\.]+)/"); Match match = regex.Match (Url); if (match.Success) - return match.Groups [1].Value; + return match.Groups [2].Value; else return null; } @@ -69,43 +70,9 @@ namespace SparkleLib { public abstract string CurrentRevision { get; } public abstract bool SyncUp (); public abstract bool SyncDown (); - - public virtual bool CheckForRemoteChanges () // HasRemoteChanges { get; } ? - { - return true; - } - - public virtual List GetChangeSets (int count) { - return null; - } - - public virtual bool UsesNotificationCenter { - get { - return true; - } - } - - public string RemoteName { - get { - return Path.GetFileNameWithoutExtension (Url); - } - } - - - public bool IsBuffering { - get { - return this.is_buffering; - } - } - - public bool IsPolling { - get { - return this.is_polling; - } - } - public abstract bool HasUnsyncedChanges { get; set; } + public bool ServerOnline { get { return this.server_online; @@ -152,21 +119,9 @@ namespace SparkleLib { SyncUpBase (); } - // Watch the repository's folder - this.watcher = new FileSystemWatcher (LocalPath) { - IncludeSubdirectories = true, - EnableRaisingEvents = true, - Filter = "*" - }; - - this.watcher.Changed += new FileSystemEventHandler (OnFileActivity); - this.watcher.Created += new FileSystemEventHandler (OnFileActivity); - this.watcher.Deleted += new FileSystemEventHandler (OnFileActivity); - this.watcher.Renamed += new RenamedEventHandler (OnFileActivity); - + CreateWatcher (); CreateListener (); - this.local_timer.Elapsed += delegate (object o, ElapsedEventArgs args) { CheckForChanges (); }; @@ -188,14 +143,95 @@ namespace SparkleLib { this.local_timer.Start (); // Sync up everything that changed - // since we've been off - DisableWatching (); + // since we've been offline if (AnyDifferences) { + DisableWatching (); SyncUpBase (); + while (HasUnsyncedChanges) SyncUpBase (); + EnableWatching (); } - EnableWatching (); + } + + + // Create an initial change set when the + // user has fetched an empty remote folder + public virtual void CreateInitialChangeSet () + { + string file_path = Path.Combine (LocalPath, "SparkleShare.txt"); + TextWriter writer = new StreamWriter (file_path); + writer.WriteLine (":)"); + writer.Close (); + } + + + public virtual bool CheckForRemoteChanges () // HasRemoteChanges { get; } ? + { + return true; + } + + + public virtual List GetChangeSets (int count) { + return null; + } + + + public virtual bool UsesNotificationCenter { + get { + return true; + } + } + + + public string RemoteName { + get { + return Path.GetFileNameWithoutExtension (Url); + } + } + + + public bool IsBuffering { + get { + return this.is_buffering; + } + } + + + public bool IsPolling { + get { + return this.is_polling; + } + } + + + public static bool IsRepo (string path) + { + return true; //TODO + } + + + // Disposes all resourses of this object + public void Dispose () + { + this.remote_timer.Dispose (); + this.local_timer.Dispose (); + this.listener.Dispose (); + } + + + private void CreateWatcher () + { + this.watcher = new FileSystemWatcher (LocalPath) { + IncludeSubdirectories = true, + EnableRaisingEvents = true, + Filter = "*" + }; + + this.watcher.Changed += new FileSystemEventHandler (OnFileActivity); + this.watcher.Created += new FileSystemEventHandler (OnFileActivity); + this.watcher.Deleted += new FileSystemEventHandler (OnFileActivity); + this.watcher.Renamed += new RenamedEventHandler (OnFileActivity); } @@ -229,8 +265,7 @@ namespace SparkleLib { // Fetch changes when there is a message in the irc channel this.listener.RemoteChange += delegate (string change_id) { if (!change_id.Equals (CurrentRevision) && change_id.Length == 40) { - if (Status != SyncStatus.SyncUp && - Status != SyncStatus.SyncDown && + if ((Status != SyncStatus.SyncUp) && (Status != SyncStatus.SyncDown) && !this.is_buffering) { while (this.listener.ChangesQueue > 0) { @@ -304,7 +339,7 @@ namespace SparkleLib { } - public void SyncUpBase () + private void SyncUpBase () { try { this.local_timer.Stop (); @@ -313,8 +348,8 @@ namespace SparkleLib { SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Initiated"); //if (AnyDifferences) { - if (SyncStatusChanged != null) - SyncStatusChanged (SyncStatus.SyncUp); + if (SyncStatusChanged != null) + SyncStatusChanged (SyncStatus.SyncUp); if (SyncUp ()) { SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Done"); @@ -387,29 +422,37 @@ namespace SparkleLib { } - private string GetUserName () // TODO + protected string UserName { - SparkleGit git = new SparkleGit (LocalPath, "config --get user.name"); - git.Start (); - git.WaitForExit (); + get { + string global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config.xml"); + + if (!File.Exists (global_config_file_path)) + return ""; + + XmlDocument xml = new XmlDocument(); + xml.Load (global_config_file_path); - string output = git.StandardOutput.ReadToEnd (); - string user_name = output.Trim (); - - return user_name; + XmlNode node = xml.SelectSingleNode("//user/name/text()"); + return node.Value; + } } - private string GetUserEmail () + protected string UserEmail { - SparkleGit git = new SparkleGit (LocalPath, "config --get user.email"); - git.Start (); - git.WaitForExit (); + get { + string global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config.xml"); - string output = git.StandardOutput.ReadToEnd (); - string user_email = output.Trim (); + if (!File.Exists (global_config_file_path)) + return ""; - return user_email; + XmlDocument xml = new XmlDocument(); + xml.Load (global_config_file_path); + + XmlNode node = xml.SelectSingleNode("//user/email/text()"); + return node.Value; + } } @@ -438,42 +481,5 @@ namespace SparkleLib { return size; } - - - // Create an initial change set when the - // user has fetched an empty remote folder - public virtual void CreateInitialChangeSet () - { - string file_path = Path.Combine (LocalPath, "SparkleShare.txt"); - TextWriter writer = new StreamWriter (file_path); - writer.WriteLine (":)"); - writer.Close (); - } - - - public string GetConfigItem (string name) - { - if (String.Compare (name, "sparkleshare.user.name", true) == 0) - return GetUserName (); - else if (String.Compare (name, "sparkleshare.user.email", true) == 0) - return GetUserEmail (); - else - return null; - } - - - public static bool IsRepo (string path) - { - return System.IO.Directory.Exists (Path.Combine (path, ".git")); - } - - - // Disposes all resourses of this object - public void Dispose () - { - this.remote_timer.Dispose (); - this.local_timer.Dispose (); - this.listener.Dispose (); - } } } diff --git a/SparkleLib/SparkleRepoGit.cs b/SparkleLib/SparkleRepoGit.cs index 61cf9f03..14e27eae 100644 --- a/SparkleLib/SparkleRepoGit.cs +++ b/SparkleLib/SparkleRepoGit.cs @@ -318,7 +318,7 @@ namespace SparkleLib { // Append a timestamp to local version string timestamp = DateTime.Now.ToString ("HH:mm MMM d"); - string their_path = conflicting_path + " (" + GetConfigItem ("sparkleshare.user.name") + ", " + timestamp + ")"; + string their_path = conflicting_path + " (" + UserName + ", " + timestamp + ")"; string abs_conflicting_path = Path.Combine (LocalPath, conflicting_path); string abs_their_path = Path.Combine (LocalPath, their_path); @@ -568,7 +568,5 @@ namespace SparkleLib { return !File.Exists (file_path); } } - - } } diff --git a/SparkleLib/SparkleRepoMercurial.cs b/SparkleLib/SparkleRepoMercurial.cs new file mode 100644 index 00000000..d682eb59 --- /dev/null +++ b/SparkleLib/SparkleRepoMercurial.cs @@ -0,0 +1,249 @@ +// SparkleShare, an instant update workflow to Git. +// Copyright (C) 2010 Hylke Bons +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; + +namespace SparkleLib { + + public class SparkleRepoMercurial : SparkleRepoBase { + + public SparkleRepoMercurial (string path, SparkleBackend backend) : + base (path, backend) { } + + + public override string Url { + get { + string repo_config_file_path = SparkleHelpers.CombineMore (LocalPath, ".hg", "hgrc"); + Regex regex = new Regex (@"default = (.+)"); + + foreach (string line in File.ReadAllLines (repo_config_file_path)) { + Match match = regex.Match (line); + + if (match.Success) + return match.Groups [1].Value.TrimEnd (); + } + + return null; + } + } + + + public override string Identifier { + get { + SparkleHg hg = new SparkleHg (LocalPath, "log -r : --limit 1 --template \"{node}\""); + hg.Start (); + hg.WaitForExit (); + + return hg.StandardOutput.ReadToEnd (); + } + } + + + public override string CurrentRevision { + get { + SparkleHg hg = new SparkleHg (LocalPath, "log --limit 1 --template \"{node}\""); + hg.Start (); + hg.WaitForExit (); + + return hg.StandardOutput.ReadToEnd (); + } + } + + + public override bool CheckForRemoteChanges () + { + return true; // Mercurial doesn't have a way to check for the remote hash + } + + + public override bool SyncUp () + { + Add (); + + string message = FormatCommitMessage (); + Commit (message); + + SparkleHg hg = new SparkleHg (LocalPath, "push"); + + hg.Start (); + hg.WaitForExit (); + + if (hg.ExitCode == 0) { + return true; + //FetchRebaseAndPush ();TODO + } else { + return false; + } + } + + + public override bool SyncDown () + { + SparkleHg hg = new SparkleHg (LocalPath, "pull"); + + hg.Start (); + hg.WaitForExit (); + + if (hg.ExitCode == 0) { + Merge (); + return true; + } else { + return false; + } + } + + + public override bool AnyDifferences { + get { + SparkleHg hg = new SparkleHg (LocalPath, "status"); + hg.Start (); + hg.WaitForExit (); + + string output = hg.StandardOutput.ReadToEnd ().TrimEnd (); + string [] lines = output.Split ("\n".ToCharArray ()); + + foreach (string line in lines) { + if (line.Length > 1 && !line [1].Equals (" ")) + return true; + } + + return false; + } + } + + + public override bool HasUnsyncedChanges { + get { + string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath, + ".hg", "has_unsynced_changes"); + + return File.Exists (unsynced_file_path); + } + + set { + string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath, + ".hg", "has_unsynced_changes"); + + if (value) { + if (!File.Exists (unsynced_file_path)) + File.Create (unsynced_file_path); + } else { + File.Delete (unsynced_file_path); + } + } + } + + + // Stages the made changes + private void Add () + { + SparkleHg hg = new SparkleHg (LocalPath, "addremove --quiet"); + hg.Start (); + hg.WaitForExit (); + + SparkleHelpers.DebugInfo ("Hg", "[" + Name + "] Changes staged"); + } + + + // Commits the made changes + private void Commit (string message) + { + if (!AnyDifferences) + return; + + SparkleHg hg = new SparkleHg (LocalPath, "commit -m '" + message + "'"); + hg.Start (); + hg.WaitForExit (); + + SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message); + } + + + // Merges the fetched changes + private void Merge () + { + DisableWatching (); + + if (AnyDifferences) { + Add (); + + string commit_message = FormatCommitMessage (); + Commit (commit_message); + } + + SparkleHg hg = new SparkleHg (LocalPath, "update"); + + hg.Start (); + hg.WaitForExit (); + + EnableWatching (); + } + + + // Returns a list of the latest change sets + // TODO: Method needs to be made a lot faster + public override List GetChangeSets (int count) + { + SparkleChangeSet change_set = new SparkleChangeSet (); + + change_set.Revision = "test"; + change_set.UserName = "test"; + change_set.UserEmail = "test"; + change_set.IsMerge = false; + + change_set.Timestamp = DateTime.Now; + List change_sets = new List (); + change_sets.Add (change_set); + return change_sets; + } + + + // Creates a pretty commit message based on what has changed + private string FormatCommitMessage () + { + return "SparkleShare Hg"; + } + + + public override void CreateInitialChangeSet () + { + base.CreateInitialChangeSet (); + Add (); + + string message = FormatCommitMessage (); + Commit (message); + } + + new public static bool IsRepo (string path) + { + return System.IO.Directory.Exists (Path.Combine (path, ".hg")); + } + + + public override bool UsesNotificationCenter + { + get { + string file_path = SparkleHelpers.CombineMore (LocalPath, ".hg", "disable_notification_center"); + return !File.Exists (file_path); + } + } + } +} diff --git a/SparkleShare/SparkleController.cs b/SparkleShare/SparkleController.cs index 435bb169..60b7a98a 100644 --- a/SparkleShare/SparkleController.cs +++ b/SparkleShare/SparkleController.cs @@ -511,12 +511,21 @@ namespace SparkleShare { // need to keep track of a table with folder+backendtype // and use GitBackend.IsValidFolder (string path); - // Check if the folder is a Git repository TODO: remove later - if (!SparkleRepoBase.IsRepo (folder_path)) + if (folder_path.Equals (SparklePaths.SparkleTmpPath)) return; - //TODO - SparkleRepoBase repo = new SparkleRepoGit (folder_path, SparkleBackend.DefaultBackend); + Console.WriteLine (folder_path); + + SparkleRepoBase repo = null; + if (Directory.Exists (Path.Combine (folder_path, ".git"))) { + Console.WriteLine (folder_path + " == Git"); + repo = new SparkleRepoGit (folder_path, SparkleBackend.DefaultBackend); + } else if (Directory.Exists (Path.Combine (folder_path, ".hg"))) { + + Console.WriteLine (folder_path + " == Hg"); + SparkleBackend hg_backend = new SparkleBackend ("Hg", new string [] {"/opt/local/bin/hg"}); + repo = new SparkleRepoMercurial (folder_path, hg_backend); + } repo.NewChangeSet += delegate (SparkleChangeSet change_set, string repository_path) { string message = FormatMessage (change_set); @@ -964,8 +973,7 @@ namespace SparkleShare { { SparkleHelpers.DebugInfo ("Controller", "Formed URL: " + url); - // TODO: This is all too git specific, - // The controller should be ignorant of git + // TODO: GetDomain method string host = url.Substring (url.IndexOf ("@") + 1); if (host.Contains (":")) host = host.Substring (0, host.IndexOf (":")); @@ -979,7 +987,7 @@ namespace SparkleShare { string tmp_folder = Path.Combine (SparklePaths.SparkleTmpPath, canonical_name); // TODO: backend detection - SparkleFetcherBase fetcher = new SparkleFetcherGit (url, tmp_folder); + SparkleFetcherBase fetcher = new SparkleFetcherHg (url, tmp_folder); bool folder_exists = Directory.Exists (Path.Combine (SparklePaths.SparklePath, canonical_name)); // Add a numbered suffix to the nameif a folder with the same name