diff --git a/SparkleLib/Git/SparkleFetcherGit.cs b/SparkleLib/Git/SparkleFetcherGit.cs index 889937c8..5f3d0a10 100755 --- a/SparkleLib/Git/SparkleFetcherGit.cs +++ b/SparkleLib/Git/SparkleFetcherGit.cs @@ -30,12 +30,13 @@ namespace SparkleLib.Git { public class SparkleFetcher : SparkleFetcherBase { private SparkleGit git; + private bool use_git_bin; private string crypto_salt = "e0d592768d7cf99a"; // TODO: Make unique per repo public SparkleFetcher (string server, string required_fingerprint, string remote_path, - string target_folder, bool fetch_prior_history) : base (server, required_fingerprint, remote_path, - target_folder, fetch_prior_history) + string target_folder, bool fetch_prior_history) : base (server, required_fingerprint, + remote_path, target_folder, fetch_prior_history) { Uri uri = RemoteUrl; @@ -60,6 +61,9 @@ namespace SparkleLib.Git { } else if (uri.Host.Equals ("github.com")) { uri = new Uri ("ssh://git@github.com" + uri.AbsolutePath); + } else if (uri.Host.Equals ("bitbucket.org")) { + // Nothing really + } else if (uri.Host.Equals ("gnome.org")) { uri = new Uri ("ssh://git@gnome.org/git" + uri.AbsolutePath); @@ -73,6 +77,8 @@ namespace SparkleLib.Git { else uri = new Uri (uri.Scheme + "://git@" + uri.Host + ":" + uri.Port + uri.AbsolutePath); } + + this.use_git_bin = true; } TargetFolder = target_folder; @@ -128,14 +134,21 @@ namespace SparkleLib.Git { } else { SparkleHelpers.DebugInfo ("Fetcher", line); - if (line.StartsWith ("fatal:", true, null) || - line.StartsWith ("error:", true, null)) { + line = line.Trim (new char [] {' ', '@'}); + + if (line.StartsWith ("fatal:", StringComparison.InvariantCultureIgnoreCase) || + line.StartsWith ("error:", StringComparison.InvariantCultureIgnoreCase)) { base.errors.Add (line); + + } else if (line.StartsWith ("WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!")) { + base.errors.Add ("warning: Remote host identification has changed!"); + + } else if (line.StartsWith ("WARNING: POSSIBLE DNS SPOOFING DETECTED!")) { + base.errors.Add ("warning: Possible DNS spoofing detected!"); } } - if (number >= percentage) { percentage = number; @@ -148,8 +161,6 @@ namespace SparkleLib.Git { this.git.WaitForExit (); - SparkleHelpers.DebugInfo ("Git", "Exit code: " + this.git.ExitCode); - if (this.git.ExitCode == 0) { while (percentage < 100) { percentage += 25; @@ -165,6 +176,7 @@ namespace SparkleLib.Git { InstallConfiguration (); InstallExcludeRules (); + InstallAttributeRules (); AddWarnings (); @@ -179,11 +191,13 @@ namespace SparkleLib.Git { public override bool IsFetchedRepoEmpty { get { SparkleGit git = new SparkleGit (TargetFolder, "rev-parse HEAD"); + git.StartInfo.RedirectStandardError = true; git.Start (); // Reading the standard output HAS to go before // WaitForExit, or it will hang forever on output > 4096 bytes git.StandardOutput.ReadToEnd (); + git.StandardError.ReadToEnd (); git.WaitForExit (); return (git.ExitCode != 0); @@ -210,7 +224,7 @@ namespace SparkleLib.Git { string git_attributes_file_path = SparkleHelpers.CombineMore ( TargetFolder, ".git", "info", "attributes"); - File.WriteAllText (git_attributes_file_path, "* filter=crypto"); + File.AppendAllText (git_attributes_file_path, "\n* filter=crypto"); // Store the password @@ -285,158 +299,99 @@ namespace SparkleLib.Git { public override void Complete () { - if (IsFetchedRepoEmpty) - return; + if (!IsFetchedRepoEmpty) { + SparkleGit git = new SparkleGit (TargetFolder, "checkout --quiet HEAD"); + git.Start (); + git.WaitForExit (); + } - SparkleGit git = new SparkleGit (TargetFolder, "checkout --quiet HEAD"); - git.Start (); - git.WaitForExit (); + base.Complete (); } - // Install the user's name and email and some config into - // the newly cloned repository private void InstallConfiguration () { - string repo_config_file_path = SparkleHelpers.CombineMore (TargetFolder, ".git", "config"); - string config = File.ReadAllText (repo_config_file_path); + string [] settings = new string [] { + "core.quotepath false", // Don't quote "unusual" characters in path names + "core.ignorecase false", // Be case sensitive explicitly to work on Mac + "core.filemode false", // Ignore permission changes + "core.autocrlf false", // Don't change file line endings + "core.safecrlf false", + "core.packedGitLimit 128m", // Some memory limiting options + "core.packedGitWindowSize 128m", + "pack.deltaCacheSize 128m", + "pack.packSizeLimit 128m", + "pack.windowMemory 128m" + }; - string n = Environment.NewLine; + foreach (string setting in settings) { + SparkleGit git_config = new SparkleGit (TargetFolder, "config " + setting); + git_config.Start (); + git_config.WaitForExit (); + } - config = config.Replace ("[core]" + n, - "[core]" + n + - "\tquotepath = false" + n + // Show special characters in the logs - "\tpackedGitLimit = 128m" + n + - "\tautocrlf = false" + n + - "\tsafecrlf = false" + n + - "\tpackedGitWindowSize = 128m" + n); + if (this.use_git_bin) + InstallGitBinConfiguration (); + } - config = config.Replace ("[remote \"origin\"]" + n, - "[pack]" + n + - "\tdeltaCacheSize = 128m" + n + - "\tpackSizeLimit = 128m" + n + - "\twindowMemory = 128m" + n + - "[remote \"origin\"]" + n); - // Be case sensitive explicitly to work on Mac - config = config.Replace ("ignorecase = true", "ignorecase = false"); + public void InstallGitBinConfiguration () + { + string [] settings = new string [] { + "core.bigFileThreshold 8g", + "filter.bin.clean \"git bin clean %f\"", + "filter.bin.smudge \"git bin smudge\"", + "git-bin.chunkSize 1m", + "git-bin.s3bucket \"your bucket name\"", + "git-bin.s3key \"your key\"", + "git-bin.s3secretKey \"your secret key\"" + }; - // Ignore permission changes - config = config.Replace ("filemode = true", "filemode = false"); - - // Write the config to the file - File.WriteAllText (repo_config_file_path, config); - SparkleHelpers.DebugInfo ("Fetcher", "Added configuration to '" + repo_config_file_path + "'"); + foreach (string setting in settings) { + SparkleGit git_config = new SparkleGit (TargetFolder, "config " + setting); + git_config.Start (); + git_config.WaitForExit (); + } } // Add a .gitignore file to the repo private void InstallExcludeRules () { - DirectoryInfo info = Directory.CreateDirectory ( - SparkleHelpers.CombineMore (TargetFolder, ".git", "info")); - - // File that lists the files we want git to ignore - string exclude_rules_file_path = Path.Combine (info.FullName, "exclude"); + // Compile a list of files we want Git to ignore + string exclude_rules_file_path = SparkleHelpers.CombineMore (TargetFolder, ".git", "info", "exclude"); TextWriter writer = new StreamWriter (exclude_rules_file_path); foreach (string exclude_rule in ExcludeRules) writer.WriteLine (exclude_rule); writer.Close (); + } - // File that lists the files we want don't want git to compress. - // Not compressing the already compressed files saves us memory - // usage and increases speed - string no_compression_rules_file_path = Path.Combine (info.FullName, "attributes"); - writer = new StreamWriter (no_compression_rules_file_path); + private void InstallAttributeRules () + { + string attribute_rules_file_path = SparkleHelpers.CombineMore (TargetFolder, ".git", "info", "attributes"); + TextWriter writer = new StreamWriter (attribute_rules_file_path); - // Images - writer.WriteLine ("*.jpg -delta"); - writer.WriteLine ("*.jpeg -delta"); - writer.WriteLine ("*.JPG -delta"); - writer.WriteLine ("*.JPEG -delta"); + if (this.use_git_bin) { + writer.WriteLine ("* filter=bin binary"); - writer.WriteLine ("*.png -delta"); - writer.WriteLine ("*.PNG -delta"); + } else { + // Compile a list of files we don't want Git to compress. + // Not compressing already compressed files decreases memory usage and increases speed + string [] extensions = new string [] { + "jpg", "jpeg", "png", "tiff", "gif", // Images + "flac", "mp3", "ogg", "oga", // Audio + "avi", "mov", "mpg", "mpeg", "mkv", "ogv", "ogx", "webm", // Video + "zip", "gz", "bz", "bz2", "rpm", "deb", "tgz", "rar", "ace", "7z", "pak", "tar" // Archives + }; - writer.WriteLine ("*.tiff -delta"); - writer.WriteLine ("*.TIFF -delta"); - - // Audio - writer.WriteLine ("*.flac -delta"); - writer.WriteLine ("*.FLAC -delta"); - - writer.WriteLine ("*.mp3 -delta"); - writer.WriteLine ("*.MP3 -delta"); - - writer.WriteLine ("*.ogg -delta"); - writer.WriteLine ("*.OGG -delta"); - - writer.WriteLine ("*.oga -delta"); - writer.WriteLine ("*.OGA -delta"); - - // Video - writer.WriteLine ("*.avi -delta"); - writer.WriteLine ("*.AVI -delta"); - - writer.WriteLine ("*.mov -delta"); - writer.WriteLine ("*.MOV -delta"); - - writer.WriteLine ("*.mpg -delta"); - writer.WriteLine ("*.MPG -delta"); - writer.WriteLine ("*.mpeg -delta"); - writer.WriteLine ("*.MPEG -delta"); - - writer.WriteLine ("*.mkv -delta"); - writer.WriteLine ("*.MKV -delta"); - - writer.WriteLine ("*.ogv -delta"); - writer.WriteLine ("*.OGV -delta"); - - writer.WriteLine ("*.ogx -delta"); - writer.WriteLine ("*.OGX -delta"); - - writer.WriteLine ("*.webm -delta"); - writer.WriteLine ("*.WEBM -delta"); - - // Archives - writer.WriteLine ("*.zip -delta"); - writer.WriteLine ("*.ZIP -delta"); - - writer.WriteLine ("*.gz -delta"); - writer.WriteLine ("*.GZ -delta"); - - writer.WriteLine ("*.bz -delta"); - writer.WriteLine ("*.BZ -delta"); - - writer.WriteLine ("*.bz2 -delta"); - writer.WriteLine ("*.BZ2 -delta"); - - writer.WriteLine ("*.rpm -delta"); - writer.WriteLine ("*.RPM -delta"); - - writer.WriteLine ("*.deb -delta"); - writer.WriteLine ("*.DEB -delta"); - - writer.WriteLine ("*.tgz -delta"); - writer.WriteLine ("*.TGZ -delta"); - - writer.WriteLine ("*.rar -delta"); - writer.WriteLine ("*.RAR -delta"); - - writer.WriteLine ("*.ace -delta"); - writer.WriteLine ("*.ACE -delta"); - - writer.WriteLine ("*.7z -delta"); - writer.WriteLine ("*.7Z -delta"); - - writer.WriteLine ("*.pak -delta"); - writer.WriteLine ("*.PAK -delta"); - - writer.WriteLine ("*.tar -delta"); - writer.WriteLine ("*.TAR -delta"); + foreach (string extension in extensions) { + writer.WriteLine ("*." + extension + " -delta"); + writer.WriteLine ("*." + extension.ToUpper () + " -delta"); + } + } writer.Close (); } diff --git a/SparkleLib/Git/SparkleGit.cs b/SparkleLib/Git/SparkleGit.cs index 7e13a46e..a95972a9 100644 --- a/SparkleLib/Git/SparkleGit.cs +++ b/SparkleLib/Git/SparkleGit.cs @@ -22,18 +22,61 @@ using SparkleLib; namespace SparkleLib.Git { - public class SparkleGit : Process { + public abstract class SparkleProcess : Process { - public static string ExecPath = null; - public static string Path = null; - - - public SparkleGit (string path, string args) : base () + public SparkleProcess (string path, string args) : base () { - Path = LocateGit (); + StartInfo.FileName = path; + StartInfo.Arguments = args; + } + + + new public void Start () + { + SparkleHelpers.DebugInfo ("Cmd | " + System.IO.Path.GetFileName (StartInfo.WorkingDirectory), + System.IO.Path.GetFileName (StartInfo.FileName) + " " + StartInfo.Arguments); + + try { + base.Start (); + + } catch (Exception e) { + SparkleHelpers.DebugInfo ("Cmd", "Couldn't execute command: " + e.Message); + Environment.Exit (-1); + } + } + + + protected string LocateCommand (string name) + { + string [] possible_command_paths = new string [] { + "/usr/bin/" + name, + "/usr/local/bin/" + name, + "/opt/local/bin/" + name + }; + + foreach (string path in possible_command_paths) { + if (File.Exists (path)) + return path; + } + + return name; + } + } + + + public class SparkleGit : SparkleProcess { + + public static string ExecPath; + public static string GitPath; + + + public SparkleGit (string path, string args) : base (path, args) + { + if (string.IsNullOrEmpty (GitPath)) + GitPath = LocateCommand ("git"); EnableRaisingEvents = true; - StartInfo.FileName = Path; + StartInfo.FileName = GitPath; StartInfo.RedirectStandardOutput = true; StartInfo.UseShellExecute = false; StartInfo.WorkingDirectory = path; @@ -44,40 +87,26 @@ namespace SparkleLib.Git { else StartInfo.Arguments = "--exec-path=\"" + ExecPath + "\" " + args; } + } - new public void Start () + public class SparkleGitBin : SparkleProcess { + + public static string GitBinPath; + + + public SparkleGitBin (string path, string args) : base (path, args) { - SparkleHelpers.DebugInfo ("Cmd | " + System.IO.Path.GetFileName (StartInfo.WorkingDirectory), - "git " + StartInfo.Arguments); + if (string.IsNullOrEmpty (GitBinPath)) + GitBinPath = LocateCommand ("git-bin"); - try { - base.Start (); - - } catch (Exception e) { - SparkleHelpers.DebugInfo ("Cmd", "There's a problem running Git: " + e.Message); - Environment.Exit (-1); - } - } - - - private string LocateGit () - { - if (!string.IsNullOrEmpty (Path)) - return Path; - - string [] possible_git_paths = new string [] { - "/usr/bin/git", - "/usr/local/bin/git", - "/opt/local/bin/git", - "/usr/local/git/bin/git" - }; - - foreach (string path in possible_git_paths) - if (File.Exists (path)) - return path; - - return "git"; + EnableRaisingEvents = true; + StartInfo.FileName = GitBinPath; + StartInfo.RedirectStandardOutput = true; + StartInfo.UseShellExecute = false; + StartInfo.WorkingDirectory = path; + StartInfo.CreateNoWindow = true; + StartInfo.Arguments = args; } } } diff --git a/SparkleLib/Git/SparkleRepoGit.cs b/SparkleLib/Git/SparkleRepoGit.cs index a4fe379c..0adff153 100644 --- a/SparkleLib/Git/SparkleRepoGit.cs +++ b/SparkleLib/Git/SparkleRepoGit.cs @@ -27,11 +27,18 @@ namespace SparkleLib.Git { public class SparkleRepo : SparkleRepoBase { - private bool author_set = false; + private bool user_is_set; + private bool remote_url_is_set; + private bool use_git_bin; public SparkleRepo (string path) : base (path) { + SparkleGit git = new SparkleGit (LocalPath, "config --get filter.bin.clean"); + git.Start (); + git.WaitForExit (); + + this.use_git_bin = (git.ExitCode == 0); } @@ -84,14 +91,7 @@ namespace SparkleLib.Git { File.WriteAllText (size_file_path, size.ToString ()); File.WriteAllText (history_size_file_path, history_size.ToString ()); } - - - public override void CreateInitialChangeSet () - { - base.CreateInitialChangeSet (); - SyncUp (); // FIXME: Weird freeze happens when base class handles this - } - + public override string [] UnsyncedFilePaths { get { @@ -152,7 +152,7 @@ namespace SparkleLib.Git { SparkleHelpers.DebugInfo ("Git", Name + " | Checking for remote changes..."); string current_revision = CurrentRevision; - SparkleGit git = new SparkleGit (LocalPath, "ls-remote --exit-code \"" + RemoteUrl + "\" master"); + SparkleGit git = new SparkleGit (LocalPath, "ls-remote --heads --exit-code \"" + RemoteUrl + "\" master"); git.Start (); git.WaitForExit (); @@ -189,7 +189,25 @@ namespace SparkleLib.Git { Commit (message); } - SparkleGit git = new SparkleGit (LocalPath, + SparkleGit git; + + if (this.use_git_bin) { + if (this.remote_url_is_set) { + git = new SparkleGit (LocalPath, "config remote.origin.url \"" + RemoteUrl + "\""); + git.Start (); + git.WaitForExit (); + + this.remote_url_is_set = true; + } + + SparkleGitBin git_bin = new SparkleGitBin (LocalPath, "push"); + git_bin.Start (); + git_bin.WaitForExit (); + + // TODO: Progress + } + + git = new SparkleGit (LocalPath, "push --progress " + // Redirects progress stats to standarderror "\"" + RemoteUrl + "\" master"); @@ -249,10 +267,13 @@ namespace SparkleLib.Git { UpdateSizes (); ChangeSets = GetChangeSets (); - if (git.ExitCode == 0) + if (git.ExitCode == 0) { + ClearCache (); return true; - else + + } else { return false; + } } @@ -319,6 +340,7 @@ namespace SparkleLib.Git { ); ChangeSets = GetChangeSets (); + ClearCache (); return true; @@ -383,7 +405,7 @@ namespace SparkleLib.Git { { SparkleGit git; - if (!this.author_set) { + if (!this.user_is_set) { git = new SparkleGit (LocalPath, "config user.name \"" + SparkleConfig.DefaultConfig.User.Name + "\""); @@ -396,7 +418,7 @@ namespace SparkleLib.Git { git.Start (); git.WaitForExit (); - this.author_set = true; + this.user_is_set = true; } git = new SparkleGit (LocalPath, @@ -760,11 +782,6 @@ namespace SparkleLib.Git { } - /// - /// Resolves special characters like \303\244 (รค) to their real character - /// - /// - /// private string ResolveSpecialChars (string s) { StringBuilder builder = new StringBuilder (s.Length); @@ -793,6 +810,17 @@ namespace SparkleLib.Git { } + private void ClearCache () + { + if (!this.use_git_bin) + return; + + SparkleGitBin git_bin = new SparkleGitBin (LocalPath, "clear -f"); + git_bin.Start (); + git_bin.WaitForExit (); + } + + // Git doesn't track empty directories, so this method // fills them all with a hidden empty file. // diff --git a/SparkleLib/SparkleConfig.cs b/SparkleLib/SparkleConfig.cs index 71bc275c..6cbcc3f8 100755 --- a/SparkleLib/SparkleConfig.cs +++ b/SparkleLib/SparkleConfig.cs @@ -210,11 +210,14 @@ namespace SparkleLib { } - public void AddFolder (string name, string url, string backend) + public void AddFolder (string name, string identifier, string url, string backend) { XmlNode node_name = CreateElement ("name"); node_name.InnerText = name; + XmlNode node_identifier = CreateElement ("identifier"); + node_identifier.InnerText = identifier; + XmlNode node_url = CreateElement ("url"); node_url.InnerText = url; @@ -223,6 +226,7 @@ namespace SparkleLib { XmlNode node_folder = CreateNode (XmlNodeType.Element, "folder", null); node_folder.AppendChild (node_name); + node_folder.AppendChild (node_identifier); node_folder.AppendChild (node_url); node_folder.AppendChild (node_backend); @@ -244,18 +248,44 @@ namespace SparkleLib { } + public void RenameFolder (string identifier, string name) + { + XmlNode node_folder = SelectSingleNode (string.Format ("/sparkleshare/folder[identifier=\"{0}\"]", identifier)); + node_folder ["name"].InnerText = name; + + Save (); + } + + public string GetBackendForFolder (string name) { return GetFolderValue (name, "backend"); } + public string GetIdentifierForFolder (string name) + { + return GetFolderValue (name, "identifier"); + } + + public string GetUrlForFolder (string name) { return GetFolderValue (name, "url"); } + public bool IdentifierExists (string identifier) + { + foreach (XmlNode node_folder in SelectNodes ("/sparkleshare/folder")) { + if (node_folder ["identifier"].InnerText.Equals (identifier)) + return true; + } + + return false; + } + + public bool SetFolderOptionalAttribute (string folder_name, string key, string value) { XmlNode folder = GetFolder (folder_name); @@ -304,11 +334,10 @@ namespace SparkleLib { { XmlNode folder = GetFolder(name); - if ((folder != null) && (folder [key] != null)) { + if ((folder != null) && (folder [key] != null)) return folder [key].InnerText; - } - - return null; + else + return null; } diff --git a/SparkleLib/SparkleFetcherBase.cs b/SparkleLib/SparkleFetcherBase.cs index 440c0e44..1d436f00 100755 --- a/SparkleLib/SparkleFetcherBase.cs +++ b/SparkleLib/SparkleFetcherBase.cs @@ -41,7 +41,6 @@ namespace SparkleLib { public abstract bool Fetch (); public abstract void Stop (); - public abstract void Complete (); public abstract bool IsFetchedRepoEmpty { get; } public abstract bool IsFetchedRepoPasswordCorrect (string password); public abstract void EnableFetchedRepoCrypto (string password); @@ -51,6 +50,7 @@ namespace SparkleLib { public readonly bool FetchPriorHistory = false; public string TargetFolder { get; protected set; } public bool IsActive { get; private set; } + public string Identifier; public string [] Warnings { get { @@ -66,13 +66,34 @@ namespace SparkleLib { protected List warnings = new List (); - protected List errors = new List (); + protected List errors = new List (); + + protected string [] ExcludeRules = new string [] { + "*.autosave", // Various autosaving apps + "*~", // gedit and emacs + ".~lock.*", // LibreOffice + "*.part", "*.crdownload", // Firefox and Chromium temporary download files + ".*.sw[a-z]", "*.un~", "*.swp", "*.swo", // vi(m) + ".directory", // KDE + ".DS_Store", "Icon\r\r", "._*", ".Spotlight-V100", ".Trashes", // Mac OS X + "*(Autosaved).graffle", // Omnigraffle + "Thumbs.db", "Desktop.ini", // Windows + "~*.tmp", "~*.TMP", "*~*.tmp", "*~*.TMP", // MS Office + "~*.ppt", "~*.PPT", "~*.pptx", "~*.PPTX", + "~*.xls", "~*.XLS", "~*.xlsx", "~*.XLSX", + "~*.doc", "~*.DOC", "~*.docx", "~*.DOCX", + "*/CVS/*", ".cvsignore", "*/.cvsignore", // CVS + "/.svn/*", "*/.svn/*", // Subversion + "/.hg/*", "*/.hg/*", "*/.hgignore", // Mercurial + "/.bzr/*", "*/.bzr/*", "*/.bzrignore" // Bazaar + }; + private Thread thread; - public SparkleFetcherBase (string server, string required_fingerprint, string remote_path, - string target_folder, bool fetch_prior_history) + public SparkleFetcherBase (string server, string required_fingerprint, + string remote_path, string target_folder, bool fetch_prior_history) { RequiredFingerprint = required_fingerprint; FetchPriorHistory = fetch_prior_history; @@ -100,12 +121,11 @@ namespace SparkleLib { if (Started != null) Started (); - SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl); + SparkleHelpers.DebugInfo ("Fetcher", TargetFolder + " | Fetching folder: " + RemoteUrl); if (Directory.Exists (TargetFolder)) Directory.Delete (TargetFolder, true); - string host = RemoteUrl.Host; string host_key = GetHostKey (); @@ -116,7 +136,6 @@ namespace SparkleLib { return; } - bool warn = true; if (RequiredFingerprint != null) { string host_fingerprint = GetFingerprint (host_key); @@ -141,35 +160,99 @@ namespace SparkleLib { AcceptHostKey (host_key, warn); + this.thread = new Thread ( + new ThreadStart (delegate { + if (Fetch ()) { + Thread.Sleep (500); + SparkleHelpers.DebugInfo ("Fetcher", "Finished"); - this.thread = new Thread (new ThreadStart (delegate { - if (Fetch ()) { - Thread.Sleep (500); - SparkleHelpers.DebugInfo ("Fetcher", "Finished"); + IsActive = false; - IsActive = false; + // TODO: Find better way to determine if folder should have crypto setup + bool repo_is_encrypted = RemoteUrl.ToString ().Contains ("crypto"); - // TODO: Find better way to determine if folder should have crypto setup - bool repo_is_encrypted = RemoteUrl.ToString ().Contains ("crypto"); + if (Finished != null) + Finished (repo_is_encrypted, IsFetchedRepoEmpty, Warnings); - if (Finished != null) - Finished (repo_is_encrypted, IsFetchedRepoEmpty, Warnings); + } else { + Thread.Sleep (500); + SparkleHelpers.DebugInfo ("Fetcher", "Failed"); - } else { - Thread.Sleep (500); - SparkleHelpers.DebugInfo ("Fetcher", "Failed"); + IsActive = false; - IsActive = false; - - if (Failed != null) - Failed (); - } - })); + if (Failed != null) + Failed (); + } + }) + ); this.thread.Start (); } + public virtual void Complete () + { + string identifier_path = Path.Combine (TargetFolder, ".sparkleshare"); + + if (File.Exists (identifier_path)) { + Identifier = File.ReadAllText (identifier_path).Trim (); + + } else { + Identifier = CreateIdentifier (); + File.WriteAllText (identifier_path, Identifier); + } + + if (IsFetchedRepoEmpty) + CreateInitialChangeSet (); + } + + + // Create an initial change set when the + // user has fetched an empty remote folder + public void CreateInitialChangeSet () + { + string file_path = Path.Combine (TargetFolder, "SparkleShare.txt"); + string n = Environment.NewLine; + + string text = "Congratulations, you've successfully created a SparkleShare repository!" + n + + n + + "Any files you add or change in this folder will be automatically synced to " + n + + RemoteUrl + " and everyone connected to it." + n + + n + + "SparkleShare is an Open Source software program that helps people " + n + + "collaborate and share files. If you like what we do, please consider a small " + n + + "donation to support the project: http://sparkleshare.org/support-us/" + n + + n + + "Have fun! :)" + n; + + File.WriteAllText (file_path, text); + } + + + public static string CreateIdentifier () + { + string random = Path.GetRandomFileName (); + return SparkleHelpers.SHA1 (random); + } + + + public static string GetBackend (string path) + { + string extension = Path.GetExtension (path); + + if (!string.IsNullOrEmpty (extension)) { + extension = extension.Substring (1); + char [] letters = extension.ToCharArray (); + letters [0] = char.ToUpper (letters [0]); + + return new string (letters); + + } else { + return "Git"; + } + } + + public void Dispose () { if (this.thread != null) { @@ -178,7 +261,7 @@ namespace SparkleLib { } } - + protected void OnProgressChanged (double percentage) { if (ProgressChanged != null) ProgressChanged (percentage); @@ -199,7 +282,7 @@ namespace SparkleLib { process.StartInfo.RedirectStandardOutput = true; process.StartInfo.CreateNoWindow = true; - process.StartInfo.FileName = "ssh-keyscan"; + process.StartInfo.FileName = "ssh-keyscan"; process.StartInfo.Arguments = "-t rsa " + host; process.Start (); @@ -240,7 +323,7 @@ namespace SparkleLib { // WaitForExit, or it will hang forever on output > 4096 bytes string fingerprint = process.StandardOutput.ReadToEnd ().Trim (); process.WaitForExit (); - + File.Delete (tmp_file_path); try { @@ -286,98 +369,5 @@ namespace SparkleLib { if (warn) this.warnings.Add ("The following host key has been accepted:\n" + GetFingerprint (host_key)); } - - - public static string GetBackend (string path) - { - string extension = Path.GetExtension (path); - - if (!string.IsNullOrEmpty (extension)) { - extension = extension.Substring (1); - char [] letters = extension.ToCharArray (); - letters [0] = char.ToUpper (letters [0]); - - return new string (letters); - - } else { - return "Git"; - } - } - - - protected string [] ExcludeRules = new string [] { - // Various autosaving apps - "*.autosave", - - // gedit and emacs - "*~", - - // LibreOffice - ".~lock.*", - - // Firefox and Chromium temporary download files - "*.part", - "*.crdownload", - - // vi(m) - ".*.sw[a-z]", - "*.un~", - "*.swp", - "*.swo", - - // KDE - ".directory", - - // Mac OS X - ".DS_Store", - "Icon\r\r", - "._*", - ".Spotlight-V100", - ".Trashes", - - // Omnigraffle - "*(Autosaved).graffle", - - // Windows - "Thumbs.db", - "Desktop.ini", - - // MS Office - "~*.tmp", - "~*.TMP", - "*~*.tmp", - "*~*.TMP", - "~*.ppt", - "~*.PPT", - "~*.pptx", - "~*.PPTX", - "~*.xls", - "~*.XLS", - "~*.xlsx", - "~*.XLSX", - "~*.doc", - "~*.DOC", - "~*.docx", - "~*.DOCX", - - // CVS - "*/CVS/*", - ".cvsignore", - "*/.cvsignore", - - // Subversion - "/.svn/*", - "*/.svn/*", - - // Mercurial - "/.hg/*", - "*/.hg/*", - "*/.hgignore", - - // Bazaar - "/.bzr/*", - "*/.bzr/*", - "*/.bzrignore" - }; } } diff --git a/SparkleLib/SparkleListenerBase.cs b/SparkleLib/SparkleListenerBase.cs index c7e16b4b..ac7408b4 100755 --- a/SparkleLib/SparkleListenerBase.cs +++ b/SparkleLib/SparkleListenerBase.cs @@ -31,8 +31,8 @@ namespace SparkleLib { public event DisconnectedEventHandler Disconnected; public delegate void DisconnectedEventHandler (); - public event ReceivedEventHandler Received; - public delegate void ReceivedEventHandler (SparkleAnnouncement announcement); + public event AnnouncementReceivedEventHandler AnnouncementReceived; + public delegate void AnnouncementReceivedEventHandler (SparkleAnnouncement announcement); public readonly Uri Server; @@ -175,8 +175,8 @@ namespace SparkleLib { AddRecentAnnouncement (announcement); this.queue_down [announcement.FolderIdentifier] = announcement; - if (Received != null) - Received (announcement); + if (AnnouncementReceived != null) + AnnouncementReceived (announcement); } diff --git a/SparkleLib/SparkleRepoBase.cs b/SparkleLib/SparkleRepoBase.cs index fe5d2aa0..42c5ac50 100755 --- a/SparkleLib/SparkleRepoBase.cs +++ b/SparkleLib/SparkleRepoBase.cs @@ -83,13 +83,16 @@ namespace SparkleLib { if (File.Exists (id_path)) this.identifier = File.ReadAllText (id_path).Trim (); - if (this.identifier != null && this.identifier.Length > 0) { + if (!string.IsNullOrEmpty (this.identifier)) { return this.identifier; } else { - Random random = new Random (); - string number = "" + random.Next () + "" + random.Next () + "" + random.Next (); - this.identifier = SparkleHelpers.SHA1 (number); + string config_identifier = SparkleConfig.DefaultConfig.GetIdentifierForFolder (Name); + + if (!string.IsNullOrEmpty (config_identifier)) + this.identifier = config_identifier; + else + this.identifier = SparkleFetcherBase.CreateIdentifier (); File.WriteAllText (id_path, this.identifier); File.SetAttributes (id_path, FileAttributes.Hidden); @@ -148,9 +151,6 @@ namespace SparkleLib { this.identifier = Identifier; - if (CurrentRevision == null) - CreateInitialChangeSet (); - ChangeSets = GetChangeSets (); this.watcher = CreateWatcher (); @@ -194,28 +194,6 @@ namespace SparkleLib { } - // 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"); - string n = Environment.NewLine; - - File.WriteAllText (file_path, - "Congratulations, you've successfully created a SparkleShare repository!" + n + - "" + n + - "Any files you add or change in this folder will be automatically synced to " + n + - RemoteUrl + " and everyone connected to it." + n + - "" + n + - "SparkleShare is an Open Source software program that helps people " + n + - "collaborate and share files. If you like what we do, please consider a small " + n + - "donation to support the project: http://sparkleshare.org/support-us/" + n + - "" + n + - "Have fun! :)" + n - ); - } - - public List GetChangeSets () { return GetChangeSets (30); } @@ -427,52 +405,15 @@ namespace SparkleLib { new Thread ( new ThreadStart (delegate { - if (!is_syncing && HasRemoteChanges) + if (!is_syncing && !HasLocalChanges && HasRemoteChanges) SyncDownBase (); }) ).Start (); } - // Stop polling when the connection to the irc channel is succesful - this.listener.Connected += delegate { - this.poll_interval = PollInterval.Long; - this.last_poll = DateTime.Now; - - if (!is_syncing) { - // Check for changes manually one more time - if (HasRemoteChanges) - SyncDownBase (); - - // Push changes that were made since the last disconnect - if (HasUnsyncedChanges) - SyncUpBase (); - } - }; - - // Start polling when the connection to the channel is lost - this.listener.Disconnected += delegate { - this.poll_interval = PollInterval.Short; - SparkleHelpers.DebugInfo (Name, "Falling back to polling"); - }; - - // Fetch changes when there is a message in the irc channel - this.listener.Received += delegate (SparkleAnnouncement announcement) { - string identifier = Identifier; - - if (announcement.FolderIdentifier.Equals (identifier) && - !announcement.Message.Equals (CurrentRevision)) { - - while (this.is_syncing) - System.Threading.Thread.Sleep (100); - - SparkleHelpers.DebugInfo ("Listener", "Syncing due to announcement"); - SyncDownBase (); - - } else { - if (announcement.FolderIdentifier.Equals (identifier)) - SparkleHelpers.DebugInfo ("Listener", "Not syncing, message is for current revision"); - } - }; + this.listener.Connected += ListenerConnectedDelegate; + this.listener.Disconnected += ListenerDisconnectedDelegate; + this.listener.AnnouncementReceived += ListenerAnnouncementReceivedDelegate; // Start listening if (!this.listener.IsConnected && !this.listener.IsConnecting) @@ -480,6 +421,53 @@ namespace SparkleLib { } + // Stop polling when the connection to the irc channel is succesful + private void ListenerConnectedDelegate () + { + this.poll_interval = PollInterval.Long; + this.last_poll = DateTime.Now; + + if (!is_syncing) { + // Check for changes manually one more time + if (HasRemoteChanges) + SyncDownBase (); + + // Push changes that were made since the last disconnect + if (HasUnsyncedChanges) + SyncUpBase (); + } + } + + + // Start polling when the connection to the channel is lost + private void ListenerDisconnectedDelegate () + { + this.poll_interval = PollInterval.Short; + SparkleHelpers.DebugInfo (Name, "Falling back to polling"); + } + + + // Fetch changes when there is an announcement + private void ListenerAnnouncementReceivedDelegate (SparkleAnnouncement announcement) + { + string identifier = Identifier; + + if (announcement.FolderIdentifier.Equals (identifier) && + !announcement.Message.Equals (CurrentRevision)) { + + while (this.is_syncing) + System.Threading.Thread.Sleep (100); + + SparkleHelpers.DebugInfo ("Listener", "Syncing due to announcement"); + SyncDownBase (); + + } else { + if (announcement.FolderIdentifier.Equals (identifier)) + SparkleHelpers.DebugInfo ("Listener", "Not syncing, message is for current revision"); + } + } + + private DateTime progress_last_change = DateTime.Now; private TimeSpan progress_change_interval = new TimeSpan (0, 0, 0, 1); @@ -535,8 +523,15 @@ namespace SparkleLib { public void Dispose () { + this.remote_timer.Stop (); this.remote_timer.Dispose (); + + this.listener.Connected -= ListenerConnectedDelegate; + this.listener.Disconnected -= ListenerDisconnectedDelegate; + this.listener.AnnouncementReceived -= ListenerAnnouncementReceivedDelegate; + this.listener.Dispose (); + this.watcher.Dispose (); } } } diff --git a/SparkleLib/SparkleWatcherFactory.cs b/SparkleLib/SparkleWatcherFactory.cs new file mode 100644 index 00000000..94606363 --- /dev/null +++ b/SparkleLib/SparkleWatcherFactory.cs @@ -0,0 +1,50 @@ +// SparkleShare, a collaboration and sharing tool. +// 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.IO; + +namespace SparkleLib { + + public static class SparkleListenerFactory { + + private static List watchers = new List (); + + + public static SparkleWatcher CreateWatcher (string path_to_watch) + { + path_to_watch = Path.GetDirectoryName (path_to_watch); + + foreach (SparkleWatcher watcher in watchers) { + if (watcher.Path.Equals (path_to_watch)) { + SparkleHelpers.DebugInfo ("WatcherFactory", + "Refered to existing watcher for " + path_to_watch); + + return watcher; + } + } + + watchers.Add (new SparkleWatcher (path_to_watch)); + + SparkleHelpers.DebugInfo ("WatcherFactory", + "Issued new watcher for " + path_to_watch); + + return watchers [watchers.Count - 1]; + } + } +} diff --git a/SparkleShare/Mac/SparkleController.cs b/SparkleShare/Mac/SparkleController.cs index de0d5fec..2a3ffc79 100755 --- a/SparkleShare/Mac/SparkleController.cs +++ b/SparkleShare/Mac/SparkleController.cs @@ -56,7 +56,7 @@ namespace SparkleShare { // Let's use the bundled git first - SparkleLib.Git.SparkleGit.Path = + SparkleLib.Git.SparkleGit.GitPath = Path.Combine (NSBundle.MainBundle.ResourcePath, "git", "libexec", "git-core", "git"); diff --git a/SparkleShare/SparkleControllerBase.cs b/SparkleShare/SparkleControllerBase.cs index e02bf021..19985933 100644 --- a/SparkleShare/SparkleControllerBase.cs +++ b/SparkleShare/SparkleControllerBase.cs @@ -34,12 +34,8 @@ namespace SparkleShare { public SparkleRepoBase [] Repositories { get { - lock (this.repo_lock) { - SparkleRepoBase [] repositories = - this.repositories.GetRange (0, this.repositories.Count).ToArray (); - - return repositories; - } + lock (this.repo_lock) + return this.repositories.GetRange (0, this.repositories.Count).ToArray (); } } @@ -170,8 +166,8 @@ namespace SparkleShare { private SparkleFetcherBase fetcher; - private Object repo_lock = new Object (); - private Object delete_watcher_lock = new Object (); + private Object repo_lock = new Object (); + private Object check_repos_lock = new Object (); // Short alias for the translations @@ -186,6 +182,113 @@ namespace SparkleShare { } + public void HandleInvite (FileSystemEventArgs args) + { + if (this.fetcher != null && + this.fetcher.IsActive) { + + if (AlertNotificationRaised != null) + AlertNotificationRaised ("SparkleShare Setup seems busy", + "Please wait for it to finish"); + + } else { + if (InviteReceived != null) { + SparkleInvite invite = new SparkleInvite (args.FullPath); + + // It may be that the invite we received a path to isn't + // fully downloaded yet, so we try to read it several times + int tries = 0; + while (!invite.IsValid) { + Thread.Sleep (1 * 250); + invite = new SparkleInvite (args.FullPath); + tries++; + if (tries > 20) + break; + } + + if (invite.IsValid) { + InviteReceived (invite); + + } else { + if (AlertNotificationRaised != null) + AlertNotificationRaised ("Oh noes!", + "This invite seems screwed up..."); + } + + File.Delete (args.FullPath); + } + } + } + + + public void CheckRepositories () + { + lock (this.check_repos_lock) { + string path = SparkleConfig.DefaultConfig.FoldersPath; + + foreach (string folder_path in Directory.GetDirectories (path)) { + string folder_name = Path.GetFileName (folder_path); + + if (folder_name.Equals (".tmp")) + continue; + + if (SparkleConfig.DefaultConfig.GetIdentifierForFolder (folder_name) == null) { + string identifier_file_path = Path.Combine (folder_path, ".sparkleshare"); + + if (!File.Exists (identifier_file_path)) + continue; + + string identifier = File.ReadAllText (identifier_file_path).Trim (); + + if (SparkleConfig.DefaultConfig.IdentifierExists (identifier)) { + RemoveRepository (folder_path); + SparkleConfig.DefaultConfig.RenameFolder (identifier, folder_name); + + string new_folder_path = Path.Combine (path, folder_name); + AddRepository (new_folder_path); + + SparkleHelpers.DebugInfo ("Controller", + "Renamed folder with identifier " + identifier + " to '" + folder_name + "'"); + } + } + } + + foreach (string folder_name in SparkleConfig.DefaultConfig.Folders) { + string folder_path = new SparkleFolder (folder_name).FullPath; + + if (!Directory.Exists (folder_path)) { + SparkleConfig.DefaultConfig.RemoveFolder (folder_name); + RemoveRepository (folder_path); + + SparkleHelpers.DebugInfo ("Controller", + "Removed folder '" + folder_name + "' from config"); + + } else { + AddRepository (folder_path); + } + } + + if (FolderListChanged != null) + FolderListChanged (); + } + } + + + public void OnFolderActivity (object o, FileSystemEventArgs args) + { + if (args != null && + args.ChangeType == WatcherChangeTypes.Created && + args.FullPath.EndsWith (".xml")) { + + HandleInvite (args); + return; + + } else { + CheckRepositories (); + } + } + + public virtual void Initialize () { SparklePlugin.PluginsPath = PluginsPath; @@ -201,69 +304,17 @@ namespace SparkleShare { ImportPrivateKey (); // Watch the SparkleShare folder - FileSystemWatcher watcher = new FileSystemWatcher (SparkleConfig.DefaultConfig.FoldersPath) { + FileSystemWatcher watcher = new FileSystemWatcher () { + Filter = "*", IncludeSubdirectories = false, - EnableRaisingEvents = true, - Filter = "*" + Path = SparkleConfig.DefaultConfig.FoldersPath }; - watcher.Deleted += delegate (object o, FileSystemEventArgs args) { - lock (this.delete_watcher_lock) { - foreach (string folder_name in SparkleConfig.DefaultConfig.Folders) { - string folder_path = new SparkleFolder (folder_name).FullPath; + watcher.Deleted += OnFolderActivity; + watcher.Created += OnFolderActivity; + watcher.Renamed += OnFolderActivity; - if (!Directory.Exists (folder_path)) { - SparkleConfig.DefaultConfig.RemoveFolder (folder_name); - RemoveRepository (folder_path); - } - } - - if (FolderListChanged != null) - FolderListChanged (); - } - }; - - watcher.Created += delegate (object o, FileSystemEventArgs args) { - if (!args.FullPath.EndsWith (".xml")) - return; - - if (this.fetcher != null && - this.fetcher.IsActive) { - - if (AlertNotificationRaised != null) - AlertNotificationRaised ("SparkleShare Setup seems busy", - "Please wait for it to finish"); - - } else { - if (InviteReceived != null) { - SparkleInvite invite = new SparkleInvite (args.FullPath); - - // It may be that the invite we received a path to isn't - // fully downloaded yet, so we try to read it several times - int tries = 0; - while (!invite.IsValid) { - Thread.Sleep (1 * 250); - invite = new SparkleInvite (args.FullPath); - tries++; - if (tries > 20) - break; - } - - if (invite.IsValid) { - InviteReceived (invite); - - } else { - invite = null; - - if (AlertNotificationRaised != null) - AlertNotificationRaised ("Oh noes!", - "This invite seems screwed up..."); - } - - File.Delete (args.FullPath); - } - } - }; + watcher.EnableRaisingEvents = true; } @@ -327,11 +378,9 @@ namespace SparkleShare { string path = new SparkleFolder (name).FullPath; - lock (this.repo_lock) { - foreach (SparkleRepoBase repo in Repositories) { - if (repo.LocalPath.Equals (path)) - return repo.ChangeSets; - } + foreach (SparkleRepoBase repo in Repositories) { + if (repo.LocalPath.Equals (path)) + return repo.ChangeSets; } return null; @@ -511,7 +560,6 @@ namespace SparkleShare { } - // Adds a repository to the list of repositories private void AddRepository (string folder_path) { SparkleRepoBase repo = null; @@ -531,16 +579,8 @@ namespace SparkleShare { return; } - - repo.NewChangeSet += delegate (SparkleChangeSet change_set) { - if (NotificationRaised != null) - NotificationRaised (change_set); - }; - - repo.ConflictResolved += delegate { - if (AlertNotificationRaised != null) - AlertNotificationRaised ("Conflict detected", - "Don't worry, SparkleShare made a copy of each conflicting file."); + repo.ChangesDetected += delegate { + UpdateState (); }; repo.SyncStatusChanged += delegate (SyncStatus status) { @@ -565,8 +605,15 @@ namespace SparkleShare { UpdateState (); }; - repo.ChangesDetected += delegate { - UpdateState (); + repo.NewChangeSet += delegate (SparkleChangeSet change_set) { + if (NotificationRaised != null) + NotificationRaised (change_set); + }; + + repo.ConflictResolved += delegate { + if (AlertNotificationRaised != null) + AlertNotificationRaised ("Conflict detected", + "Don't worry, SparkleShare made a copy of each conflicting file."); }; this.repositories.Add (repo); @@ -574,45 +621,25 @@ namespace SparkleShare { } - // Removes a repository from the list of repositories and - // updates the statusicon menu private void RemoveRepository (string folder_path) { - string folder_name = Path.GetFileName (folder_path); + for (int i = 0; i < this.repositories.Count; i++) { + SparkleRepoBase repo = this.repositories [i]; - for (int i = 0; i < Repositories.Length; i++) { - SparkleRepoBase repo = Repositories [i]; - - if (repo.Name.Equals (folder_name)) { + if (repo.LocalPath.Equals (folder_path)) { repo.Dispose (); - - lock (this.repo_lock) { - this.repositories.Remove (repo); - } + this.repositories.Remove (repo); repo = null; - break; + return; } } - } - // Updates the list of repositories with all the - // folders in the SparkleShare folder private void PopulateRepositories () { - lock (this.repo_lock) { - foreach (string folder_name in SparkleConfig.DefaultConfig.Folders) { - string folder_path = new SparkleFolder (folder_name).FullPath; - - if (Directory.Exists (folder_path)) - AddRepository (folder_path); - else - SparkleConfig.DefaultConfig.RemoveFolder (folder_name); - } - } - + CheckRepositories (); RepositoriesLoaded = true; if (FolderListChanged != null) @@ -910,7 +937,8 @@ namespace SparkleShare { Directory.Move (this.fetcher.TargetFolder, target_folder_path); string backend = SparkleFetcherBase.GetBackend (this.fetcher.RemoteUrl.AbsolutePath); - SparkleConfig.DefaultConfig.AddFolder (target_folder_name, this.fetcher.RemoteUrl.ToString (), backend); + SparkleConfig.DefaultConfig.AddFolder (target_folder_name, this.fetcher.Identifier, + this.fetcher.RemoteUrl.ToString (), backend); if (FolderFetched != null) FolderFetched (this.fetcher.RemoteUrl.ToString (), this.fetcher.Warnings.ToArray ()); @@ -921,9 +949,7 @@ namespace SparkleShare { target_folder_name, "announcements_url", announcements_url); */ - lock (this.repo_lock) { - AddRepository (target_folder_path); - } + AddRepository (target_folder_path); if (FolderListChanged != null) FolderListChanged ();