diff --git a/.gitignore b/.gitignore index 4de048e7..16c48d78 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ SparkleShare/Nautilus/sparkleshare-nautilus-extension.py gnome-doc-utils.make /sparkleshare-* desktop.ini +_ReSharper.* diff --git a/SparkleLib/Git/SparkleFetcherGit.cs b/SparkleLib/Git/SparkleFetcherGit.cs index b8e6a75e..11a39046 100644 --- a/SparkleLib/Git/SparkleFetcherGit.cs +++ b/SparkleLib/Git/SparkleFetcherGit.cs @@ -18,6 +18,7 @@ using System; using System.IO; using System.Diagnostics; +using System.Text.RegularExpressions; using System.Xml; namespace SparkleLib { @@ -25,12 +26,15 @@ namespace SparkleLib { // Sets up a fetcher that can get remote folders public class SparkleFetcherGit : SparkleFetcherBase { + private SparkleGit git; + + public SparkleFetcherGit (string server, string remote_folder, string target_folder) : base (server, remote_folder, target_folder) { remote_folder = remote_folder.Trim ("/".ToCharArray ()); - if (server.StartsWith("http")) { + if (server.StartsWith ("http")) { base.target_folder = target_folder; base.remote_url = server; return; @@ -57,13 +61,20 @@ namespace SparkleLib { } else { server = server.TrimEnd ("/".ToCharArray ()); + string protocol = "ssh://"; + if (server.StartsWith ("ssh://")) + server = server.Substring (6); + + if (server.StartsWith ("git://")) { server = server.Substring (6); + protocol = "git://"; + } if (!server.Contains ("@")) server = "git@" + server; - server = "ssh://" + server; + server = protocol + server; } base.target_folder = target_folder; @@ -73,15 +84,51 @@ namespace SparkleLib { public override bool Fetch () { - SparkleGit git = new SparkleGit (SparklePaths.SparkleTmpPath, - "clone \"" + base.remote_url + "\" " + "\"" + base.target_folder + "\""); + this.git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath, + "clone " + + "--progress " + // Redirects progress stats to standarderror + "\"" + base.remote_url + "\" " + "\"" + base.target_folder + "\""); + + this.git.StartInfo.RedirectStandardError = true; + this.git.Start (); + + double percentage = 1.0; + Regex progress_regex = new Regex (@"([0-9]+)%", RegexOptions.Compiled); + + while (!this.git.StandardError.EndOfStream) { + string line = this.git.StandardError.ReadLine (); + Match match = progress_regex.Match (line); + + double number = 0.0; + if (match.Success) { + number = double.Parse (match.Groups [1].Value); + + // The cloning progress consists of two stages: the "Compressing + // objects" stage which we count as 20% of the total progress, and + // the "Receiving objects" stage which we count as the last 80% + if (line.Contains ("|")) + // "Receiving objects" stage + number = (number / 100 * 75 + 20); + else + // "Compressing objects" stage + number = (number / 100 * 20); + } + + if (number >= percentage) { + percentage = number; + + // FIXME: for some reason it doesn't go above 95% + base.OnProgressChanged (percentage); + } + + System.Threading.Thread.Sleep (100); + } + + this.git.WaitForExit (); - git.Start (); - git.WaitForExit (); + SparkleHelpers.DebugInfo ("Git", "Exit code " + this.git.ExitCode.ToString ()); - SparkleHelpers.DebugInfo ("Git", "Exit code " + git.ExitCode.ToString ()); - - if (git.ExitCode != 0) { + if (this.git.ExitCode != 0) { return false; } else { InstallConfiguration (); @@ -91,11 +138,22 @@ namespace SparkleLib { } + public override void Stop () + { + if (this.git != null) { + this.git.Kill (); + this.git.Dispose (); + } + + base.Stop (); + } + + // 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"); + string global_config_file_path = Path.Combine (SparkleConfig.DefaultConfig.TmpPath, "config.xml"); if (!File.Exists (global_config_file_path)) return; @@ -103,17 +161,20 @@ namespace SparkleLib { string repo_config_file_path = SparkleHelpers.CombineMore (base.target_folder, ".git", "config"); string config = String.Join (Environment.NewLine, File.ReadAllLines (repo_config_file_path)); + string n = Environment.NewLine; + + // Show special characters in the logs + config = config.Replace ("[core]" + n, + "[core]" + n + "quotepath = false" + n); + // Be case sensitive explicitly to work on Mac config = config.Replace ("ignorecase = true", "ignorecase = false"); // Ignore permission changes config = config.Replace ("filemode = true", "filemode = false"); - config = config.Replace ("fetch = +refs/heads/*:refs/remotes/origin/*", - "fetch = +refs/heads/*:refs/remotes/origin/*" + Environment.NewLine + - "\tfetch = +refs/notes/*:refs/notes/*"); + // Add user info - string n = Environment.NewLine; XmlDocument xml = new XmlDocument(); xml.Load (global_config_file_path); @@ -146,6 +207,10 @@ namespace SparkleLib { // gedit and emacs writer.WriteLine ("*~"); + // Firefox and Chromium temporary download files + writer.WriteLine ("*.part"); + writer.WriteLine ("*.crdownload"); + // vi(m) writer.WriteLine (".*.sw[a-z]"); writer.WriteLine ("*.un~"); @@ -168,7 +233,6 @@ namespace SparkleLib { // Windows writer.WriteLine ("Thumbs.db"); writer.WriteLine ("Desktop.ini"); - writer.WriteLine ("~*"); // CVS writer.WriteLine ("*/CVS/*"); diff --git a/SparkleLib/Git/SparkleRepoGit.cs b/SparkleLib/Git/SparkleRepoGit.cs index 0b0f631f..43b11ed4 100644 --- a/SparkleLib/Git/SparkleRepoGit.cs +++ b/SparkleLib/Git/SparkleRepoGit.cs @@ -131,7 +131,9 @@ namespace SparkleLib { public override bool SyncUp () { Add (); - Commit ("Changes made by SparkleShare"); + + string message = FormatCommitMessage (); + Commit (message); SparkleGit git = new SparkleGit (LocalPath, "push origin master"); git.Start (); @@ -163,6 +165,8 @@ namespace SparkleLib { public override bool AnyDifferences { get { + FillEmptyDirectories (LocalPath); + SparkleGit git = new SparkleGit (LocalPath, "status --porcelain"); git.Start (); @@ -197,7 +201,7 @@ namespace SparkleLib { if (value) { if (!File.Exists (unsynced_file_path)) - File.Create (unsynced_file_path); + File.Create (unsynced_file_path).Close (); } else { File.Delete (unsynced_file_path); } @@ -330,7 +334,7 @@ namespace SparkleLib { // Windows doesn't allow colons in the file name, so // we use "h" between the hours and minutes instead. string timestamp = DateTime.Now.ToString ("HH\\hmm MMM d"); - string their_path = conflicting_path + " (" + SparkleConfig.DefaultConfig.UserName + ", " + timestamp + ")"; + string their_path = conflicting_path + " (" + SparkleConfig.DefaultConfig.User.Name + ", " + timestamp + ")"; string abs_conflicting_path = Path.Combine (LocalPath, conflicting_path); string abs_their_path = Path.Combine (LocalPath, their_path); @@ -452,9 +456,9 @@ namespace SparkleLib { change_set.Folder = Name; change_set.Revision = match.Groups [1].Value; - change_set.UserName = match.Groups [2].Value; - change_set.UserEmail = match.Groups [3].Value; - change_set.IsMerge = is_merge_commit; + change_set.User.Name = match.Groups [2].Value; + change_set.User.Email = match.Groups [3].Value; + change_set.IsMagical = is_merge_commit; change_set.Timestamp = new DateTime (int.Parse (match.Groups [4].Value), int.Parse (match.Groups [5].Value), int.Parse (match.Groups [6].Value), @@ -477,6 +481,9 @@ namespace SparkleLib { string file_path = entry_line.Substring (39); string to_file_path; + if (file_path.EndsWith (".empty")) + file_path = file_path.Substring (0, file_path.Length - ".empty".Length); + if (change_type.Equals ("A") && !file_path.Contains (".notes")) { change_set.Added.Add (file_path); @@ -512,6 +519,22 @@ namespace SparkleLib { } + // Git doesn't track empty directories, so this method + // fills them all with a hidden empty file + private void FillEmptyDirectories (string path) + { + foreach (string child_path in Directory.GetDirectories (path)) { + if (child_path.EndsWith (".git") || child_path.EndsWith (".notes")) + continue; + + FillEmptyDirectories (child_path); + } + + if (Directory.GetFiles (path).Length == 0) + File.Create (Path.Combine (path, ".empty")).Close (); + } + + // Creates a pretty commit message based on what has changed private string FormatCommitMessage () { diff --git a/SparkleLib/Hg/SparkleRepoHg.cs b/SparkleLib/Hg/SparkleRepoHg.cs index d59c3915..1d0cdb8b 100644 --- a/SparkleLib/Hg/SparkleRepoHg.cs +++ b/SparkleLib/Hg/SparkleRepoHg.cs @@ -130,7 +130,7 @@ namespace SparkleLib { if (value) { if (!File.Exists (unsynced_file_path)) - File.Create (unsynced_file_path); + File.Create (unsynced_file_path).Close (); } else { File.Delete (unsynced_file_path); } @@ -234,11 +234,13 @@ namespace SparkleLib { SparkleChangeSet change_set = new SparkleChangeSet () { Revision = match.Groups [9].Value, - UserName = match.Groups [7].Value.Trim (), - UserEmail = match.Groups [8].Value, - IsMerge = is_merge_commit + IsMagical = is_merge_commit }; + change_set.User.Name = match.Groups [7].Value.Trim (); + change_set.User.Email = match.Groups [8].Value; + + change_set.Timestamp = new DateTime (int.Parse (match.Groups [1].Value), int.Parse (match.Groups [2].Value), int.Parse (match.Groups [3].Value), int.Parse (match.Groups [4].Value), int.Parse (match.Groups [5].Value), 0); diff --git a/SparkleLib/Makefile.am b/SparkleLib/Makefile.am index e5fdcd51..6356372a 100644 --- a/SparkleLib/Makefile.am +++ b/SparkleLib/Makefile.am @@ -11,10 +11,6 @@ SOURCES = \ Defines.cs \ Git/SparkleFetcherGit.cs \ Git/SparkleRepoGit.cs \ - Hg/SparkleFetcherHg.cs \ - Hg/SparkleRepoHg.cs \ - Scp/SparkleFetcherScp.cs \ - Scp/SparkleRepoScp.cs \ SparkleBackend.cs \ SparkleChangeSet.cs \ SparkleConfig.cs \ @@ -24,7 +20,6 @@ SOURCES = \ SparkleListenerIrc.cs \ SparkleListenerTcp.cs \ SparkleOptions.cs \ - SparklePaths.cs \ SparkleRepoBase.cs \ SparkleWatcher.cs diff --git a/SparkleLib/Scp/SparkleFetcherScp.cs b/SparkleLib/Scp/SparkleFetcherScp.cs deleted file mode 100644 index d20e22a7..00000000 --- a/SparkleLib/Scp/SparkleFetcherScp.cs +++ /dev/null @@ -1,139 +0,0 @@ -// 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.IO; -using System.Diagnostics; -using System.Xml; - -namespace SparkleLib { - - // Sets up a fetcher that can get remote folders - public class SparkleFetcherScp : SparkleFetcherBase { - - public SparkleFetcherScp (string server, string remote_folder, string target_folder) : - base (server, remote_folder, target_folder) { } - - - public override bool Fetch () - { - SparkleScp scp = new SparkleScp (SparklePaths.SparkleTmpPath, - "-r \"" + base.remote_url + "\" " + "\"" + base.target_folder + "\""); - - scp.Start (); - scp.WaitForExit (); - - SparkleHelpers.DebugInfo ("Scp", "Exit code " + scp.ExitCode.ToString ()); - - if (scp.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 log_file_path = SparkleHelpers.CombineMore (base.target_folder, ".sparkleshare", "log"); - File.Create (log_file_path); - - string config_file_path = SparkleHelpers.CombineMore (base.target_folder, ".sparkleshare", "config"); - File.Create (config_file_path); - - string config = ""; - - // Write the config to the file - TextWriter writer = new StreamWriter (config_file_path); - writer.WriteLine (config); - writer.Close (); - - SparkleHelpers.DebugInfo ("Config", "Added configuration to '" + config_file_path + "'"); - } - - - // Add a .gitignore file to the repo - private void InstallExcludeRules () - { - string exlude_rules_file_path = SparkleHelpers.CombineMore (base.target_folder, ".sparkleshare", "exclude"); - File.Create (exlude_rules_file_path); - - TextWriter writer = new StreamWriter (exlude_rules_file_path); - - // 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 SparkleScp : Process { - - public SparkleScp (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/Scp/SparkleRepoScp.cs b/SparkleLib/Scp/SparkleRepoScp.cs deleted file mode 100644 index a3030557..00000000 --- a/SparkleLib/Scp/SparkleRepoScp.cs +++ /dev/null @@ -1,115 +0,0 @@ -// 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.Diagnostics; -using System.IO; -using System.Text.RegularExpressions; - -namespace SparkleLib { - - public class SparkleRepoScp : SparkleRepoBase { - - public SparkleRepoScp (string path, SparkleBackend backend) : - base (path, backend) { } - - - public override string Identifier { - get { - return "sparkles"; - } - } - - - public override string CurrentRevision { - get { - return ""; - } - } - - - public override bool CheckForRemoteChanges () - { - return true; - } - - - public override bool SyncUp () - { - return true; - } - - - public override bool SyncDown () - { - return true; - } - - - public override bool AnyDifferences { - get { - return false; - } - } - - - public override bool HasUnsyncedChanges { - get { - string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath, - ".sparkleshare", "has_unsynced_changes"); - - return File.Exists (unsynced_file_path); - } - - set { - string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath, - ".sparkleshare", "has_unsynced_changes"); - - if (value) { - if (!File.Exists (unsynced_file_path)) - File.Create (unsynced_file_path); - } else { - File.Delete (unsynced_file_path); - } - } - } - - - public override List GetChangeSets (int count) - { - var l = new List (); - l.Add (new SparkleChangeSet () { UserName = "test", UserEmail = "test", Revision = "test", Timestamp = DateTime.Now }); - return l; - } - - - public override void CreateInitialChangeSet () - { - base.CreateInitialChangeSet (); - } - - - public override bool UsesNotificationCenter - { - get { - string file_path = SparkleHelpers.CombineMore (LocalPath, ".sparkleshare", "disable_notification_center"); - return !File.Exists (file_path); - } - } - } -} diff --git a/SparkleLib/SparkleBackend.cs b/SparkleLib/SparkleBackend.cs index ee9d1208..c916fa83 100644 --- a/SparkleLib/SparkleBackend.cs +++ b/SparkleLib/SparkleBackend.cs @@ -32,7 +32,7 @@ namespace SparkleLib { public SparkleBackend (string name, string [] paths) { Name = name; - Path = "git"; // default + Path = "git"; foreach (string path in paths) { if (File.Exists (path)) { diff --git a/SparkleLib/SparkleChangeSet.cs b/SparkleLib/SparkleChangeSet.cs index 3961c39c..5496e44e 100644 --- a/SparkleLib/SparkleChangeSet.cs +++ b/SparkleLib/SparkleChangeSet.cs @@ -16,20 +16,20 @@ using System; +using System.IO; using System.Collections.Generic; namespace SparkleLib { public class SparkleChangeSet { - public string UserName; - public string UserEmail; + public SparkleUser User = new SparkleUser ("Unknown", "Unknown"); public string Folder; public string Revision; public DateTime Timestamp; public DateTime FirstTimestamp; - public bool IsMerge = false; + public bool IsMagical = false; public List Added = new List (); public List Deleted = new List (); @@ -76,10 +76,44 @@ namespace SparkleLib { public class SparkleNote { - public string UserName; - public string UserEmail; + public SparkleUser User; public DateTime Timestamp; public string Body; } + + + public class SparkleUser { + + public string Name; + public string Email; + + public string PublicKey; + + + public SparkleUser (string name, string email) + { + Name = name; + Email = email; + } + } + + + public class SparkleFolder { + + public string Name; + // TODO: Uri + + public string FullPath { + get { + return Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, Name); + } + } + + + public SparkleFolder (string name) + { + Name = name; + } + } } diff --git a/SparkleLib/SparkleConfig.cs b/SparkleLib/SparkleConfig.cs index d6eea46b..17282bb0 100644 --- a/SparkleLib/SparkleConfig.cs +++ b/SparkleLib/SparkleConfig.cs @@ -23,19 +23,37 @@ using System.Xml; #if __MonoCS__ using Mono.Unix; #endif + namespace SparkleLib { public class SparkleConfig : XmlDocument { - public static SparkleConfig DefaultConfig = new SparkleConfig ( - SparklePaths.SparkleConfigPath, "config.xml"); + public static string ConfigPath = Path.Combine ( + Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), + "sparkleshare"); - public string Path; + public static SparkleConfig DefaultConfig = new SparkleConfig (ConfigPath, "config.xml"); + + + public string FullPath; + + public string HomePath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + public string TmpPath; + + public string FoldersPath { + get { + if (GetConfigOption ("folders_path") != null) + return GetConfigOption ("folders_path"); + else + return Path.Combine (HomePath, "SparkleShare"); + } + } public SparkleConfig (string config_path, string config_file_name) { - Path = System.IO.Path.Combine (config_path, config_file_name); + FullPath = System.IO.Path.Combine (config_path, config_file_name); + TmpPath = Path.Combine (FoldersPath, ".tmp"); if (!Directory.Exists (config_path)) { Directory.CreateDirectory (config_path); @@ -48,10 +66,30 @@ namespace SparkleLib { SparkleHelpers.DebugInfo ("Config", "Created \"" + icons_path + "\""); } - if (!File.Exists (Path)) + try { + Load (FullPath); + + } catch (TypeInitializationException) { CreateInitialConfig (); - Load (Path); + } catch (IOException) { + CreateInitialConfig (); + + } catch (XmlException) { + + FileInfo file = new FileInfo (FullPath); + + if (file.Length == 0) { + File.Delete (FullPath); + CreateInitialConfig (); + + } else { + throw new XmlException (FullPath + " does not contain a valid config XML structure."); + } + + } finally { + Load (FullPath); + } } @@ -59,20 +97,23 @@ namespace SparkleLib { { string user_name = "Unknown"; + if (SparkleBackend.Platform == PlatformID.Unix || + SparkleBackend.Platform == PlatformID.MacOSX) { #if __MonoCS__ user_name = new UnixUserInfo (UnixEnvironment.UserName).RealName; if (string.IsNullOrEmpty (user_name)) user_name = UnixEnvironment.UserName; else user_name = user_name.TrimEnd (",".ToCharArray()); -#else - user_name = Environment.UserName; #endif + } else { + user_name = Environment.UserName; + } if (string.IsNullOrEmpty (user_name)) user_name = "Unknown"; - TextWriter writer = new StreamWriter (Path); + TextWriter writer = new StreamWriter (FullPath); string n = Environment.NewLine; writer.Write ("" + n + @@ -84,36 +125,31 @@ namespace SparkleLib { ""); writer.Close (); - SparkleHelpers.DebugInfo ("Config", "Created \"" + Path + "\""); + SparkleHelpers.DebugInfo ("Config", "Created \"" + FullPath + "\""); } - public string UserName { + public SparkleUser User { get { - XmlNode node = SelectSingleNode ("/sparkleshare/user/name/text()"); - return node.Value; + XmlNode name_node = SelectSingleNode ("/sparkleshare/user/name/text()"); + string name = name_node.Value; + + XmlNode email_node = SelectSingleNode ("/sparkleshare/user/email/text()"); + string email = email_node.Value; + + return new SparkleUser (name, email); } set { - XmlNode node = SelectSingleNode ("/sparkleshare/user/name/text()"); - node.InnerText = value; + SparkleUser user = (SparkleUser) value; - Save (); - } - } + XmlNode name_node = SelectSingleNode ("/sparkleshare/user/name/text()"); + name_node.InnerText = user.Name; + XmlNode email_node = SelectSingleNode ("/sparkleshare/user/email/text()"); + email_node.InnerText = user.Email; - public string UserEmail { - get { - XmlNode node = SelectSingleNode ("/sparkleshare/user/email/text()"); - return node.Value; - } - - set { - XmlNode node = SelectSingleNode ("/sparkleshare/user/email/text()"); - node.InnerText = value; - - Save (); + this.Save (); } } @@ -149,22 +185,7 @@ namespace SparkleLib { XmlNode node_root = SelectSingleNode ("/sparkleshare"); node_root.AppendChild (node_folder); - Save (); - } - - public bool SetFolderOptionalAttribute (string name, string key, string value) - { - XmlNode folder = this.GetFolder(name); - if (folder == null) return false; - - if (folder[key] != null) { - folder[key].InnerText = value; - } else { - XmlNode new_node = CreateElement(key); - new_node.InnerText = value; - folder.AppendChild(new_node); - } - return true; + this.Save (); } @@ -175,28 +196,64 @@ namespace SparkleLib { SelectSingleNode ("/sparkleshare").RemoveChild (node_folder); } - Save (); + this.Save (); } public bool FolderExists (string name) { - XmlNode folder = this.GetFolder(name); - return folder != null; + XmlNode folder = this.GetFolder (name); + return (folder != null); } public string GetBackendForFolder (string name) { - return this.GetFolderValue(name, "backend"); + return this.GetFolderValue (name, "backend"); } public string GetUrlForFolder (string name) { - return this.GetFolderValue(name, "url"); + return this.GetFolderValue (name, "url"); } - + + + public bool SetFolderOptionalAttribute (string folder_name, string key, string value) + { + XmlNode folder = this.GetFolder (folder_name); + + if (folder == null) + return false; + + if (folder [key] != null) { + folder [key].InnerText = value; + + } else { + XmlNode new_node = CreateElement (key); + new_node.InnerText = value; + folder.AppendChild (new_node); + } + + return true; + } + + + public string GetFolderOptionalAttribute (string folder_name, string key) + { + XmlNode folder = this.GetFolder (folder_name); + + if (folder != null) { + if (folder [key] != null) + return folder [key].InnerText; + else + return null; + + } else { + return null; + } + } + public List Hosts { get { @@ -204,6 +261,7 @@ namespace SparkleLib { foreach (XmlNode node_folder in SelectNodes ("/sparkleshare/folder")) { Uri uri = new Uri (node_folder ["url"].InnerText); + if (!hosts.Contains (uri.Host)) hosts.Add (uri.Host); } @@ -213,21 +271,40 @@ namespace SparkleLib { } - public string GetAnnouncementsForFolder (string name) - { - return this.GetFolderValue(name, "announcements"); + public List HostsWithUsername { + get { + List hosts = new List (); + + foreach (XmlNode node_folder in SelectNodes ("/sparkleshare/folder")) { + Uri uri = new Uri (node_folder ["url"].InnerText); + + if ("git" != uri.UserInfo && !hosts.Contains (uri.UserInfo + "@" + uri.Host)) + hosts.Add (uri.UserInfo + "@" + uri.Host); + } + + return hosts; + } } - public string GetAnnouncementUrlForFolder (string name) + private XmlNode GetFolder (string name) { - // examples? - // tcp://localhost:9999/ - // xmpp:someuser@somexmppserver?canhavefunnybits - // irc://hbons/#somechatroom - return this.GetFolderValue(name, "announcements_url"); + return SelectSingleNode (String.Format("/sparkleshare/folder[name='{0}']", name)); } + + private string GetFolderValue (string name, string key) + { + XmlNode folder = this.GetFolder(name); + + if ((folder != null) && (folder [key] != null)) { + return folder [key].InnerText; + } + + return null; + } + + public string GetConfigOption (string name) { XmlNode node = SelectSingleNode ("/sparkleshare/" + name); @@ -255,34 +332,17 @@ namespace SparkleLib { } SparkleHelpers.DebugInfo ("Config", "Updated " + name + ":" + content); - Save (); + this.Save (); } - public void Save () + private void Save () { - if (!File.Exists (Path)) - throw new ConfigFileNotFoundException (Path + " does not exist"); + if (!File.Exists (FullPath)) + throw new ConfigFileNotFoundException (FullPath + " does not exist"); - Save (Path); - SparkleHelpers.DebugInfo ("Config", "Updated \"" + Path + "\""); - } - - - private XmlNode GetFolder (string name) - { - return SelectSingleNode(String.Format("/sparkleshare/folder[name='{0}']", name)); - } - - - private string GetFolderValue (string name, string key) - { - XmlNode folder = this.GetFolder(name); - - if ((folder != null) && (folder[key] != null)) { - return folder[key].InnerText; - } - return null; + this.Save (FullPath); + SparkleHelpers.DebugInfo ("Config", "Updated \"" + FullPath + "\""); } } @@ -293,3 +353,4 @@ namespace SparkleLib { base (message) { } } } + diff --git a/SparkleLib/SparkleFetcherBase.cs b/SparkleLib/SparkleFetcherBase.cs index 6a767add..1650b1aa 100644 --- a/SparkleLib/SparkleFetcherBase.cs +++ b/SparkleLib/SparkleFetcherBase.cs @@ -33,18 +33,19 @@ namespace SparkleLib { public delegate void StartedEventHandler (); public delegate void FinishedEventHandler (); public delegate void FailedEventHandler (); + public delegate void ProgressChangedEventHandler (double percentage); public event StartedEventHandler Started; public event FinishedEventHandler Finished; public event FailedEventHandler Failed; + public event ProgressChangedEventHandler ProgressChanged; protected string target_folder; protected string remote_url; + private Thread thread; - public abstract bool Fetch (); - - + public SparkleFetcherBase (string server, string remote_folder, string target_folder) { this.target_folder = target_folder; @@ -52,6 +53,9 @@ namespace SparkleLib { } + public abstract bool Fetch (); + + // Clones the remote repository public void Start () { @@ -97,6 +101,13 @@ namespace SparkleLib { } + public virtual void Stop () + { + this.thread.Abort (); + this.thread.Join (); + } + + public string RemoteUrl { get { return this.remote_url; @@ -112,10 +123,16 @@ namespace SparkleLib { } } - + + protected void OnProgressChanged (double percentage) { + if (ProgressChanged != null) + ProgressChanged (percentage); + } + + private void DisableHostKeyCheckingForHost (string host) { - string path = SparklePaths.HomePath; + string path = SparkleConfig.DefaultConfig.HomePath; if (!(SparkleBackend.Platform == PlatformID.Unix || SparkleBackend.Platform == PlatformID.MacOSX)) { @@ -154,7 +171,7 @@ namespace SparkleLib { private void EnableHostKeyCheckingForHost (string host) { - string path = SparklePaths.HomePath; + string path = SparkleConfig.DefaultConfig.HomePath; if (!(SparkleBackend.Platform == PlatformID.Unix || SparkleBackend.Platform == PlatformID.MacOSX)) { diff --git a/SparkleLib/SparkleListenerBase.cs b/SparkleLib/SparkleListenerBase.cs index e35e036f..e7ae5c4f 100644 --- a/SparkleLib/SparkleListenerBase.cs +++ b/SparkleLib/SparkleListenerBase.cs @@ -41,35 +41,44 @@ namespace SparkleLib { public static SparkleListenerBase CreateListener (string folder_name, string folder_identifier) { - string announce_uri = SparkleConfig.DefaultConfig.GetAnnouncementUrlForFolder (folder_name); + string uri = SparkleConfig.DefaultConfig.GetFolderOptionalAttribute ( + folder_name, "announcements_url"); - if (announce_uri == null) { + if (uri == null) { // This is SparkleShare's centralized notification service. // Don't worry, we only use this server as a backup if you // don't have your own. All data needed to connect is hashed and // we don't store any personal information ever - - announce_uri = "irc://204.62.14.135/"; + + uri = "tcp://204.62.14.135:1986"; // TODO: announcements.sparkleshare.org } + Uri announce_uri = new Uri (uri); + + // We use only one listener per server to keep + // the number of connections as low as possible foreach (SparkleListenerBase listener in listeners) { if (listener.Server.Equals (announce_uri)) { - SparkleHelpers.DebugInfo ("ListenerFactory", "Refered to existing listener for " + announce_uri); + SparkleHelpers.DebugInfo ("ListenerFactory", + "Refered to existing listener for " + announce_uri); + listener.AlsoListenTo (folder_identifier); return (SparkleListenerBase) listener; } } - Uri listen_on = new Uri (announce_uri); - - switch (listen_on.Scheme) { - case "tcp": - listeners.Add (new SparkleListenerTcp (listen_on, folder_identifier)); - break; - case "irc": - default: - listeners.Add (new SparkleListenerIrc (listen_on, folder_identifier)); - break; + // Create a new listener with the appropriate + // type if one doesn't exist yet for that server + switch (announce_uri.Scheme) { + case "tcp": + listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier)); + break; + case "irc": + listeners.Add (new SparkleListenerIrc (announce_uri, folder_identifier)); + break; + default: + listeners.Add (new SparkleListenerTcp (announce_uri, folder_identifier)); + break; } SparkleHelpers.DebugInfo ("ListenerFactory", "Issued new listener for " + announce_uri); @@ -109,20 +118,25 @@ namespace SparkleLib { protected Uri server; protected Timer reconnect_timer = new Timer { Interval = 60 * 1000, Enabled = true }; - public SparkleListenerBase (Uri server, string folder_identifier) { + public SparkleListenerBase (Uri server, string folder_identifier) + { + this.server = server; + this.reconnect_timer.Elapsed += delegate { if (!IsConnected && !this.is_connecting) Reconnect (); }; - this.server = server; this.reconnect_timer.Start (); } - public void AnnounceBase (SparkleAnnouncement announcement) { + public void AnnounceBase (SparkleAnnouncement announcement) + { if (IsConnected) { - SparkleHelpers.DebugInfo ("Listener", "Announcing to " + announcement.FolderIdentifier + " on " + this.server); + SparkleHelpers.DebugInfo ("Listener", + "Announcing to " + announcement.FolderIdentifier + " on " + this.server); + Announce (announcement); } else { @@ -161,6 +175,7 @@ namespace SparkleLib { if (this.queue_up.Count > 0) { SparkleHelpers.DebugInfo ("Listener", "Delivering queued messages..."); + foreach (SparkleAnnouncement announcement in this.queue_up) { AnnounceBase (announcement); this.queue_up.Remove (announcement); @@ -171,7 +186,7 @@ namespace SparkleLib { public void OnDisconnected () { - SparkleHelpers.DebugInfo ("Listener", "Disonnected"); + SparkleHelpers.DebugInfo ("Listener", "Disonnected from " + Server); if (Disconnected != null) Disconnected (); diff --git a/SparkleLib/SparkleListenerIrc.cs b/SparkleLib/SparkleListenerIrc.cs index 1932ef3e..2bedae0f 100644 --- a/SparkleLib/SparkleListenerIrc.cs +++ b/SparkleLib/SparkleListenerIrc.cs @@ -48,6 +48,20 @@ namespace SparkleLib { PingInterval = 60 }; + string proxy = Environment.GetEnvironmentVariable ("http_proxy"); + Uri proxy_uri = null; + if (!String.IsNullOrEmpty (proxy) && + Uri.TryCreate (proxy, UriKind.Absolute, out proxy_uri)) { + +#if __MonoCS__ + if (proxy_uri.Scheme == "http") { + this.client.ProxyType = ProxyType.Http; + this.client.ProxyHost = proxy_uri.Host; + this.client.ProxyPort = proxy_uri.Port; + } +#endif + } + this.client.OnConnected += delegate { base.is_connecting = false; OnConnected (); @@ -92,11 +106,12 @@ namespace SparkleLib { int port = base.server.Port; if (port < 0) port = 6667; this.client.Connect (base.server.Host, port); - this.client.Login (this.nick, this.nick); + this.client.Login (this.nick, this.nick, 8, this.nick); foreach (string channel in base.channels) { SparkleHelpers.DebugInfo ("ListenerIrc", "Joining channel " + channel); this.client.RfcJoin (channel); + this.client.RfcMode (channel, "+s"); } // List to the channel, this blocks the thread @@ -124,6 +139,7 @@ namespace SparkleLib { if (IsConnected) { SparkleHelpers.DebugInfo ("ListenerIrc", "Joining channel " + channel); this.client.RfcJoin (channel); + this.client.RfcMode (channel, "+s"); } } } diff --git a/SparkleLib/SparkleListenerTcp.cs b/SparkleLib/SparkleListenerTcp.cs index ad7e71ae..ee82127a 100644 --- a/SparkleLib/SparkleListenerTcp.cs +++ b/SparkleLib/SparkleListenerTcp.cs @@ -46,11 +46,12 @@ namespace SparkleLib { public override bool IsConnected { get { - //return this.client.IsConnected; bool result = false; + lock (this.mutex) { result = this.connected; } + return result; } } @@ -70,40 +71,46 @@ namespace SparkleLib { int port = Server.Port; if (port < 0) port = 9999; this.socket.Connect (Server.Host, port); + lock (this.mutex) { base.is_connecting = false; this.connected = true; + OnConnected (); + foreach (string channel in base.channels) { SparkleHelpers.DebugInfo ("ListenerTcp", "Subscribing to channel " + channel); this.socket.Send (Encoding.UTF8.GetBytes ("subscribe " + channel + "\n")); } } - byte [] bytes = new byte [4096]; // List to the channels, this blocks the thread while (this.socket.Connected) { int bytes_read = this.socket.Receive (bytes); + if (bytes_read > 0) { string received = Encoding.UTF8.GetString (bytes); string folder_identifier = received.Substring (0, received.IndexOf ("!")); string message = received.Substring (received.IndexOf ("!") + 1); OnAnnouncement (new SparkleAnnouncement (folder_identifier, message)); + } else { SparkleHelpers.DebugInfo ("ListenerTcp", "Error on socket"); + lock (this.mutex) { - this.socket.Close(); + this.socket.Close (); this.connected = false; + + OnDisconnected (); } } } SparkleHelpers.DebugInfo ("ListenerTcp", "Disconnected from " + Server.Host); - // TODO: attempt to reconnect..? } catch (SocketException e) { SparkleHelpers.DebugInfo ("ListenerTcp", "Could not connect to " + Server + ": " + e.Message); } diff --git a/SparkleLib/SparkleRepoBase.cs b/SparkleLib/SparkleRepoBase.cs index f781d57c..9be8e97a 100644 --- a/SparkleLib/SparkleRepoBase.cs +++ b/SparkleLib/SparkleRepoBase.cs @@ -67,9 +67,12 @@ namespace SparkleLib { public delegate void SyncStatusChangedEventHandler (SyncStatus new_status); public event SyncStatusChangedEventHandler SyncStatusChanged; - public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set, string source_path); + public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set); public event NewChangeSetEventHandler NewChangeSet; + public delegate void NewNoteEventHandler (string user_name, string user_email); + public event NewNoteEventHandler NewNote; + public delegate void ConflictResolvedEventHandler (); public event ConflictResolvedEventHandler ConflictResolved; @@ -253,6 +256,8 @@ namespace SparkleLib { this.listener.Announcement += delegate (SparkleAnnouncement announcement) { string identifier = Identifier; + Console.WriteLine (announcement.Message + " ! " + CurrentRevision); + if (announcement.FolderIdentifier == identifier && !announcement.Message.Equals (CurrentRevision)) { if ((Status != SyncStatus.SyncUp) && @@ -276,7 +281,6 @@ namespace SparkleLib { { lock (this.change_lock) { if (this.has_changed) { - Console.WriteLine ("checking..."); if (this.sizebuffer.Count >= 4) this.sizebuffer.RemoveAt (0); @@ -363,8 +367,8 @@ namespace SparkleLib { if (match_notes.Success) { SparkleNote note = new SparkleNote () { - UserName = match_notes.Groups [1].Value, - UserEmail = match_notes.Groups [2].Value, + User = new SparkleUser (match_notes.Groups [1].Value, + match_notes.Groups [2].Value), Timestamp = new DateTime (1970, 1, 1).AddSeconds (int.Parse (match_notes.Groups [3].Value)), Body = match_notes.Groups [4].Value }; @@ -446,13 +450,26 @@ namespace SparkleLib { if (change_sets != null && change_sets.Count > 0) { SparkleChangeSet change_set = change_sets [0]; - if (NewChangeSet != null) - NewChangeSet (change_set, LocalPath); + bool note_added = false; + foreach (string added in change_set.Added) { + if (added.Contains (".notes")) { + if (NewNote != null) + NewNote (change_set.User.Name, change_set.User.Email); + + note_added = true; + break; + } + } + + if (!note_added) { + if (NewChangeSet != null) + NewChangeSet (change_set); + } } - // There could be changes from a - // resolved conflict. Tries only once, - //then let the timer try again periodicallly + // There could be changes from a resolved + // conflict. Tries only once, then lets + // the timer try again periodically if (HasUnsyncedChanges) SyncUp (); @@ -510,8 +527,8 @@ namespace SparkleLib { string n = Environment.NewLine; note = "" + n + " " + n + - " " + SparkleConfig.DefaultConfig.UserName + "" + n + - " " + SparkleConfig.DefaultConfig.UserEmail + "" + n + + " " + SparkleConfig.DefaultConfig.User.Name + "" + n + + " " + SparkleConfig.DefaultConfig.User.Email + "" + n + " " + n + " " + timestamp + "" + n + " " + note + "" + n + diff --git a/SparkleLib/windows/SparkleLib.csproj b/SparkleLib/windows/SparkleLib.csproj index 76e3de80..3bb20808 100644 --- a/SparkleLib/windows/SparkleLib.csproj +++ b/SparkleLib/windows/SparkleLib.csproj @@ -79,16 +79,12 @@ SparkleRepoHg.cs - - SparkleFetcherScp.cs - Component - - - SparkleRepoScp.cs - SparkleConfig.cs + + SparklePaths.cs + SparkleRepoBase.cs @@ -105,7 +101,6 @@ - diff --git a/SparkleShare/Makefile.am b/SparkleShare/Makefile.am index 15dee260..802cbdbd 100644 --- a/SparkleShare/Makefile.am +++ b/SparkleShare/Makefile.am @@ -12,19 +12,20 @@ BUILD_DEFINES="-define:HAVE_APP_INDICATOR" endif SOURCES = \ + Program.cs \ SparkleAbout.cs \ SparkleAboutController.cs \ SparkleBubbles.cs \ SparkleBubblesController.cs \ SparkleController.cs \ + SparkleControllerBase.cs \ SparkleEntry.cs \ SparkleEventLog.cs \ SparkleEventLogController.cs \ - SparkleLinController.cs \ + SparkleExtensions.cs \ SparkleSetup.cs \ SparkleSetupController.cs \ SparkleSetupWindow.cs \ - SparkleShare.cs \ SparkleSpinner.cs \ SparkleStatusIcon.cs \ SparkleStatusIconController.cs \ @@ -40,7 +41,7 @@ SOURCES = \ SparkleSetup.cs \ SparkleSetupController.cs \ SparkleSetupWindow.cs \ - SparkleShare.cs \ + Program.cs \ SparkleSpinner.cs \ SparkleStatusIcon.cs \ SparkleStatusIconController.cs \ diff --git a/SparkleShare/Program.cs b/SparkleShare/Program.cs new file mode 100644 index 00000000..8d5f7697 --- /dev/null +++ b/SparkleShare/Program.cs @@ -0,0 +1,131 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +using Mono.Unix; +//using Mono.Unix.Native; +using SparkleLib; +using SparkleLib.Options; + +namespace SparkleShare { + + // This is SparkleShare! + public class Program { + + public static SparkleController Controller; + public static SparkleUI UI; + + + // Short alias for the translations + public static string _ (string s) + { + return Catalog.GetString (s); + } + + + public static void Main (string [] args) + { + // Parse the command line options + bool show_help = false; + OptionSet option_set = new OptionSet () { + { "v|version", _("Print version information"), v => { PrintVersion (); } }, + { "h|help", _("Show this help text"), v => show_help = v != null } + }; + + try { + option_set.Parse (args); + + } catch (OptionException e) { + Console.Write ("SparkleShare: "); + Console.WriteLine (e.Message); + Console.WriteLine ("Try `sparkleshare --help' for more information."); + } + + if (show_help) + ShowHelp (option_set); + + + // Initialize the controller this way so that + // there aren't any exceptions in the OS specific UI's + Controller = new SparkleController (); + Controller.Initialize (); + + if (Controller != null) { + UI = new SparkleUI (); + UI.Run (); + } + } + + + // Prints the help output + public static void ShowHelp (OptionSet option_set) + { + Console.WriteLine (" "); + Console.WriteLine (_("SparkleShare, a collaboration and sharing tool.")); + Console.WriteLine (_("Copyright (C) 2010 Hylke Bons")); + Console.WriteLine (" "); + Console.WriteLine (_("This program comes with ABSOLUTELY NO WARRANTY.")); + Console.WriteLine (" "); + Console.WriteLine (_("This is free software, and you are welcome to redistribute it ")); + Console.WriteLine (_("under certain conditions. Please read the GNU GPLv3 for details.")); + Console.WriteLine (" "); + Console.WriteLine (_("SparkleShare automatically syncs Git repositories in ")); + Console.WriteLine (_("the ~/SparkleShare folder with their remote origins.")); + Console.WriteLine (" "); + Console.WriteLine (_("Usage: sparkleshare [start|stop|restart] [OPTION]...")); + Console.WriteLine (_("Sync SparkleShare folder with remote repositories.")); + Console.WriteLine (" "); + Console.WriteLine (_("Arguments:")); + + option_set.WriteOptionDescriptions (Console.Out); + Environment.Exit (0); + } + + + // Prints the version information + public static void PrintVersion () + { + Console.WriteLine (_("SparkleShare " + Defines.VERSION)); + Environment.Exit (0); + } + + + // Strange magic needed by SetProcessName () + [DllImport ("libc")] + private static extern int prctl (int option, byte [] arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5); + + + // Sets the Unix process name to 'sparkleshare' instead of 'mono' + private static void SetProcessName (string name) + { + try { + if (prctl (15, Encoding.ASCII.GetBytes (name + "\0"), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) != 0) + throw new ApplicationException ("Error setting process name: " + + Mono.Unix.Native.Stdlib.GetLastError ()); + + } catch (EntryPointNotFoundException) { + Console.WriteLine ("SetProcessName: Entry point not found"); + } + } + } +} diff --git a/SparkleShare/SparkleAbout.cs b/SparkleShare/SparkleAbout.cs index 5f132110..7bb6e2ad 100644 --- a/SparkleShare/SparkleAbout.cs +++ b/SparkleShare/SparkleAbout.cs @@ -21,20 +21,20 @@ using System.IO; using System.Net; using Gtk; +using Mono.Unix; namespace SparkleShare { public class SparkleAbout : Window { - public SparkleAboutController Controller = new SparkleAboutController (); - + public SparkleAboutController Controller; private Label updates; // Short alias for the translations public static string _(string s) { - return s; + return Catalog.GetString (s); } @@ -53,21 +53,22 @@ namespace SparkleShare { Title = _("About SparkleShare"); AppPaintable = true; - MemoryStream MemStream = new MemoryStream(); - Icons.about.Save(MemStream, System.Drawing.Imaging.ImageFormat.Png); - MemStream.Seek(0, SeekOrigin.Begin); + string image_path = new string [] {SparkleUI.AssetsPath, + "pixmaps", "about.png"}.Combine (); + Realize (); - Gdk.Pixbuf buf = new Gdk.Pixbuf(MemStream); + Gdk.Pixbuf buf = new Gdk.Pixbuf (image_path); Gdk.Pixmap map, map2; buf.RenderPixmapAndMask (out map, out map2, 255); GdkWindow.SetBackPixmap (map, false); - CreateAbout (); + Controller = new SparkleAboutController (); Controller.NewVersionEvent += delegate (string new_version) { Application.Invoke (delegate { this.updates.Markup = String.Format ("{0}", String.Format (_("A newer version ({0}) is available!"), new_version)); + this.updates.ShowAll (); }); }; @@ -76,6 +77,7 @@ namespace SparkleShare { Application.Invoke (delegate { this.updates.Markup = String.Format ("{0}", _("You are running the latest version.")); + this.updates.ShowAll (); }); }; @@ -84,9 +86,12 @@ namespace SparkleShare { Application.Invoke (delegate { this.updates.Markup = String.Format ("{0}", _("Checking for updates...")); + this.updates.ShowAll (); }); }; + + this.CreateAbout (); } diff --git a/SparkleShare/SparkleAboutController.cs b/SparkleShare/SparkleAboutController.cs index d35f78eb..5495dfc9 100644 --- a/SparkleShare/SparkleAboutController.cs +++ b/SparkleShare/SparkleAboutController.cs @@ -68,24 +68,24 @@ namespace SparkleShare { Uri uri = new Uri ("http://www.sparkleshare.org/version"); web_client.DownloadStringCompleted += delegate (object o, DownloadStringCompletedEventArgs args) { - if (args.Error != null) { - Console.WriteLine ("Error during version check: {0}", args.Error.Message); + if (args.Error != null) + return; + + string new_version = args.Result.Trim (); + + // Add a little delay, making it seems we're + // actually doing hard work + Thread.Sleep (2 * 1000); + + if (RunningVersion.Equals (new_version)) { + if (VersionUpToDateEvent != null) + VersionUpToDateEvent (); + } else { - string new_version = args.Result.Trim (); - - // Add a little delay, making it seems we're - // actually doing hard work - Thread.Sleep (2 * 1000); - - if (RunningVersion.Equals (new_version)) { - if (VersionUpToDateEvent != null) - VersionUpToDateEvent (); - - } else { - if (NewVersionEvent != null) - NewVersionEvent (new_version); - } + if (NewVersionEvent != null) + NewVersionEvent (new_version); } + this.version_checker.Start (); }; diff --git a/SparkleShare/SparkleBubbles.cs b/SparkleShare/SparkleBubbles.cs index 7f0f2cf3..8b29622b 100644 --- a/SparkleShare/SparkleBubbles.cs +++ b/SparkleShare/SparkleBubbles.cs @@ -30,27 +30,24 @@ namespace SparkleShare { public SparkleBubbles () { Controller.ShowBubbleEvent += delegate (string title, string subtext, string image_path) { - Notification notification = new Notification () { - Timeout = 5 * 1000, - Urgency = Urgency.Low - }; + try { + Notification notification = new Notification () { + Timeout = 5 * 1000, + Urgency = Urgency.Low + }; + + if (image_path != null) + notification.Icon = new Gdk.Pixbuf (image_path); + else + notification.IconName = "folder-sparkleshare"; - if (image_path != null) - notification.Icon = new Gdk.Pixbuf (image_path); - else - notification.IconName = "folder-sparkleshare"; + notification.Show (); - notification.Show (); + } catch (Exception) { + // Ignore exceptions thrown by libnotify, + // they're not important enough to crash + } }; } - - - // Checks whether the system allows adding buttons to a notification, - // prevents error messages in Ubuntu. -// new public void AddAction (string action, string label, ActionHandler handler) -// { -// if (Array.IndexOf (Notifications.Global.Capabilities, "actions") > -1) -// base.AddAction (action, label, handler); -// } } } diff --git a/SparkleShare/SparkleBubblesController.cs b/SparkleShare/SparkleBubblesController.cs index 56ffd3a9..e6983625 100644 --- a/SparkleShare/SparkleBubblesController.cs +++ b/SparkleShare/SparkleBubblesController.cs @@ -27,19 +27,24 @@ namespace SparkleShare { public SparkleBubblesController () { - SparkleShare.Controller.ConflictNotificationRaised += delegate { - if (ShowBubbleEvent != null && SparkleShare.Controller.NotificationsEnabled) - ShowBubbleEvent ("Ouch! Mid-air collision!", - "Don't worry, SparkleShare made a copy of each conflicting file.", null); + Program.Controller.ConflictNotificationRaised += delegate { + ShowBubble ("Ouch! Mid-air collision!", + "Don't worry, SparkleShare made a copy of each conflicting file.", + null); }; - SparkleShare.Controller.NotificationRaised += delegate (string user_name, string user_email, + Program.Controller.NotificationRaised += delegate (string user_name, string user_email, string message, string folder_path) { - - if (ShowBubbleEvent != null && SparkleShare.Controller.NotificationsEnabled) - ShowBubbleEvent (user_name, message, - SparkleShare.Controller.GetAvatar (user_email, 36)); + ShowBubble (user_name, message, + Program.Controller.GetAvatar (user_email, 36)); }; } + + + public void ShowBubble (string title, string subtext, string image_path) + { + if (ShowBubbleEvent != null && Program.Controller.NotificationsEnabled) + ShowBubbleEvent (title, subtext, image_path); + } } } diff --git a/SparkleShare/SparkleController.cs b/SparkleShare/SparkleController.cs index 112081b6..d4ceda9e 100644 --- a/SparkleShare/SparkleController.cs +++ b/SparkleShare/SparkleController.cs @@ -19,1139 +19,195 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; +using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; using System.Threading; -using System.Xml; +using Mono.Unix; using SparkleLib; namespace SparkleShare { - public abstract class SparkleController { + public class SparkleController : SparkleControllerBase { - public List Repositories; - public string FolderSize; - public readonly string SparklePath = SparklePaths.SparklePath; - - public event OnQuitWhileSyncingEventHandler OnQuitWhileSyncing; - public delegate void OnQuitWhileSyncingEventHandler (); - - public event FolderFetchedEventHandler FolderFetched; - public delegate void FolderFetchedEventHandler (string target_folder_name); - - public event FolderFetchErrorEventHandler FolderFetchError; - public delegate void FolderFetchErrorEventHandler (); - - public event FolderListChangedEventHandler FolderListChanged; - public delegate void FolderListChangedEventHandler (); - - public event FolderSizeChangedEventHandler FolderSizeChanged; - public delegate void FolderSizeChangedEventHandler (string folder_size); - - public event AvatarFetchedEventHandler AvatarFetched; - public delegate void AvatarFetchedEventHandler (); - - public event OnIdleEventHandler OnIdle; - public delegate void OnIdleEventHandler (); - - public event OnSyncingEventHandler OnSyncing; - public delegate void OnSyncingEventHandler (); - - public event OnErrorEventHandler OnError; - public delegate void OnErrorEventHandler (); - - public event OnInvitationEventHandler OnInvitation; - public delegate void OnInvitationEventHandler (string server, string folder, string token); - - public event ConflictNotificationRaisedEventHandler ConflictNotificationRaised; - public delegate void ConflictNotificationRaisedEventHandler (); - - public event NotificationRaisedEventHandler NotificationRaised; - public delegate void NotificationRaisedEventHandler (string user_name, string user_email, - string message, string repository_path); - - - // Short alias for the translations - public static string _ (string s) + public SparkleController () : base () { - return s; - } - - - public SparkleController () { } - - public virtual void Initialize () - { - InstallLauncher (); - EnableSystemAutostart (); - - // Create the SparkleShare folder and add it to the bookmarks - if (CreateSparkleShareFolder ()) - AddToBookmarks (); - - FolderSize = GetFolderSize (); - - // TODO: Legacy. Remove at some later point - string old_global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config"); - if (File.Exists (old_global_config_file_path)) - MigrateConfig (); - - if (FirstRun) - SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.TrueString); - else - AddKey (); - - // Watch the SparkleShare folder - FileSystemWatcher watcher = new FileSystemWatcher (SparklePaths.SparklePath) { - IncludeSubdirectories = false, - EnableRaisingEvents = true, - Filter = "*" - }; - - // Remove the repository when a delete event occurs - watcher.Deleted += delegate (object o, FileSystemEventArgs args) { - RemoveRepository (args.FullPath); - SparkleConfig.DefaultConfig.RemoveFolder (Path.GetFileName (args.Name)); - - if (FolderListChanged != null) - FolderListChanged (); - - FolderSize = GetFolderSize (); - - if (FolderSizeChanged != null) - FolderSizeChanged (FolderSize); - }; - - - watcher.Created += delegate (object o, FileSystemEventArgs args) { - - // Handle invitations when the user saves an - // invitation into the SparkleShare folder - if (args.Name.EndsWith (".sparkle") && !FirstRun) { - XmlDocument xml_doc = new XmlDocument (); - xml_doc.Load (args.Name); - - string server = xml_doc.GetElementsByTagName ("server") [0].InnerText; - string folder = xml_doc.GetElementsByTagName ("folder") [0].InnerText; - string token = xml_doc.GetElementsByTagName ("token") [0].InnerText; - - // FIXME: this is broken :\ - if (OnInvitation != null) - OnInvitation (server, folder, token); - } - }; - - PopulateRepositories(); - } - - - public bool FirstRun { - get { - return SparkleConfig.DefaultConfig.UserEmail.Equals ("Unknown"); - } - } - - - private void MigrateConfig () - { - string old_global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config"); - - StreamReader reader = new StreamReader (old_global_config_file_path); - string global_config_file = reader.ReadToEnd (); - reader.Close (); - - Regex regex = new Regex (@"name.+= (.+)"); - Match match = regex.Match (global_config_file); - - string user_name = match.Groups [1].Value; - - regex = new Regex (@"email.+= (.+)"); - match = regex.Match (global_config_file); - - string user_email = match.Groups [1].Value; - - SparkleConfig.DefaultConfig.UserName = user_name; - SparkleConfig.DefaultConfig.UserEmail = user_email; - - File.Delete (old_global_config_file_path); - } - - - // Uploads the user's public key to the server - public bool AcceptInvitation (string server, string folder, string token) - { - // The location of the user's public key for SparkleShare - string public_key_file_path = SparkleHelpers.CombineMore (SparklePaths.HomePath, ".ssh", - "sparkleshare." + UserEmail + ".key.pub"); - - if (!File.Exists (public_key_file_path)) - return false; - - StreamReader reader = new StreamReader (public_key_file_path); - string public_key = reader.ReadToEnd (); - reader.Close (); - - string url = "https://" + server + "/?folder=" + folder + - "&token=" + token + "&pubkey=" + public_key; - - SparkleHelpers.DebugInfo ("WebRequest", url); - - HttpWebRequest request = (HttpWebRequest) WebRequest.Create (url); - HttpWebResponse response = (HttpWebResponse) request.GetResponse(); - - if (response.StatusCode == HttpStatusCode.OK) { - response.Close (); - return true; - - } else { - response.Close (); - return false; - } - } - - - public List Folders { - get { - List folders = SparkleConfig.DefaultConfig.Folders; - folders.Sort (); - return folders; - } - } - - - public List UnsyncedFolders { - get { - List unsynced_folders = new List (); - - foreach (SparkleRepoBase repo in Repositories) { - if (repo.HasUnsyncedChanges) - unsynced_folders.Add (repo.Name); - } - - return unsynced_folders; - } - } - - - public List GetLog () - { - List list = new List (); - - foreach (SparkleRepoBase repo in Repositories) - list.AddRange (repo.GetChangeSets (50)); - - list.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp))); - list.Reverse (); - - if (list.Count > 100) - return list.GetRange (0, 100); - else - return list.GetRange (0, list.Count); - } - - - public List GetLog (string name) - { - if (name == null) - return GetLog (); - - string path = Path.Combine (SparklePaths.SparklePath, name); - int log_size = 50; - - foreach (SparkleRepoBase repo in Repositories) { - if (repo.LocalPath.Equals (path)) - return repo.GetChangeSets (log_size); - } - - return null; - } - - - public abstract string EventLogHTML { get; } - public abstract string DayEntryHTML { get; } - public abstract string EventEntryHTML { get; } - - - public string GetHTMLLog (List change_sets) - { - List activity_days = new List (); - List emails = new List (); - - change_sets.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp))); - change_sets.Reverse (); - - if (change_sets.Count == 0) - return null; - - foreach (SparkleChangeSet change_set in change_sets) { - if (!emails.Contains (change_set.UserEmail)) - emails.Add (change_set.UserEmail); - - bool change_set_inserted = false; - foreach (ActivityDay stored_activity_day in activity_days) { - if (stored_activity_day.DateTime.Year == change_set.Timestamp.Year && - stored_activity_day.DateTime.Month == change_set.Timestamp.Month && - stored_activity_day.DateTime.Day == change_set.Timestamp.Day) { - - bool squash = false; - foreach (SparkleChangeSet existing_set in stored_activity_day) { - if (change_set.UserName.Equals (existing_set.UserName) && - change_set.UserEmail.Equals (existing_set.UserEmail) && - change_set.Folder.Equals (existing_set.Folder)) { - - existing_set.Added.AddRange (change_set.Added); - existing_set.Edited.AddRange (change_set.Edited); - existing_set.Deleted.AddRange (change_set.Deleted); - existing_set.MovedFrom.AddRange (change_set.MovedFrom); - existing_set.MovedTo.AddRange (change_set.MovedTo); - existing_set.Notes.AddRange (change_set.Notes); - - existing_set.Added = existing_set.Added.Distinct ().ToList (); - existing_set.Edited = existing_set.Edited.Distinct ().ToList (); - existing_set.Deleted = existing_set.Deleted.Distinct ().ToList (); - - if (DateTime.Compare (existing_set.Timestamp, change_set.Timestamp) < 1) { - existing_set.FirstTimestamp = existing_set.Timestamp; - existing_set.Timestamp = change_set.Timestamp; - existing_set.Revision = change_set.Revision; - - } else { - existing_set.FirstTimestamp = change_set.Timestamp; - } - - squash = true; - } - } - - if (!squash) - stored_activity_day.Add (change_set); - - change_set_inserted = true; - break; - } - } - - if (!change_set_inserted) { - ActivityDay activity_day = new ActivityDay (change_set.Timestamp); - activity_day.Add (change_set); - activity_days.Add (activity_day); - } - } - - new Thread (new ThreadStart (delegate { - FetchAvatars (emails, 48); - })).Start (); - - string event_log_html = EventLogHTML; - string day_entry_html = DayEntryHTML; - string event_entry_html = EventEntryHTML; - string event_log = ""; - - foreach (ActivityDay activity_day in activity_days) { - string event_entries = ""; - - foreach (SparkleChangeSet change_set in activity_day) { - string event_entry = "
"; - - if (change_set.IsMerge) { - event_entry += "
Did something magical
"; - - } else { - if (change_set.Edited.Count > 0) { - foreach (string file_path in change_set.Edited) { - string absolute_file_path = SparkleHelpers.CombineMore (SparklePaths.SparklePath, - change_set.Folder, file_path); - - if (File.Exists (absolute_file_path)) - event_entry += "
" + file_path + "
"; - else - event_entry += "
" + file_path + "
"; - } - } - - if (change_set.Added.Count > 0) { - foreach (string file_path in change_set.Added) { - string absolute_file_path = SparkleHelpers.CombineMore (SparklePaths.SparklePath, - change_set.Folder, file_path); - - if (File.Exists (absolute_file_path)) - event_entry += "
" + file_path + "
"; - else - event_entry += "
" + file_path + "
"; - } - } - - if (change_set.Deleted.Count > 0) { - foreach (string file_path in change_set.Deleted) { - string absolute_file_path = SparkleHelpers.CombineMore (SparklePaths.SparklePath, - change_set.Folder, file_path); - - if (File.Exists (absolute_file_path)) - event_entry += "
" + file_path + "
"; - else - event_entry += "
" + file_path + "
"; - } - } - - if (change_set.MovedFrom.Count > 0) { - int i = 0; - foreach (string file_path in change_set.MovedFrom) { - string to_file_path = change_set.MovedTo [i]; - string absolute_file_path = SparkleHelpers.CombineMore (SparklePaths.SparklePath, - change_set.Folder, file_path); - string absolute_to_file_path = SparkleHelpers.CombineMore (SparklePaths.SparklePath, - change_set.Folder, to_file_path); - - if (File.Exists (absolute_file_path)) - event_entry += "
" + file_path + "
"; - else - event_entry += "
" + file_path + "
"; - - if (File.Exists (absolute_to_file_path)) - event_entry += "" + to_file_path + "
"; - else - event_entry += to_file_path + ""; - - i++; - } - } - } - - string comments = ""; - comments = "
"; - - if (change_set.Notes != null) { - change_set.Notes.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp))); - - foreach (SparkleNote note in change_set.Notes) { - comments += "
" + - "

" + - note.UserName + "

" + - note.Body + - "
"; - } - } - - comments += "
"; - - string avatar_email = ""; - if (File.Exists (GetAvatar (change_set.UserEmail, 48))) - avatar_email = change_set.UserEmail; - - event_entry += "
"; - - string timestamp = change_set.Timestamp.ToString ("H:mm"); - - if (!change_set.FirstTimestamp.Equals (new DateTime ())) - timestamp = change_set.FirstTimestamp.ToString ("H:mm") + - " – " + timestamp; - - event_entries += event_entry_html.Replace ("", event_entry) - .Replace ("", change_set.UserName) - .Replace ("", "file://" + GetAvatar (avatar_email, 48)) - .Replace ("", timestamp) - .Replace ("", change_set.Folder) - .Replace ("", change_set.Revision) - .Replace ("", AssignColor (change_set.Folder)) - .Replace ("", comments); - } - - string day_entry = ""; - DateTime today = DateTime.Now; - DateTime yesterday = DateTime.Now.AddDays (-1); - - if (today.Day == activity_day.DateTime.Day && - today.Month == activity_day.DateTime.Month && - today.Year == activity_day.DateTime.Year) { - - day_entry = day_entry_html.Replace ("", "Today"); - - } else if (yesterday.Day == activity_day.DateTime.Day && - yesterday.Month == activity_day.DateTime.Month && - yesterday.Year == activity_day.DateTime.Year) { - - day_entry = day_entry_html.Replace ("", "Yesterday"); - - } else { - if (activity_day.DateTime.Year != DateTime.Now.Year) { - // TRANSLATORS: This is the date in the event logs - day_entry = day_entry_html.Replace ("", - activity_day.DateTime.ToString (_("dddd, MMMM d, yyyy"))); - - } else { - // TRANSLATORS: This is the date in the event logs, without the year - day_entry = day_entry_html.Replace ("", - activity_day.DateTime.ToString (_("dddd, MMMM d"))); - } - } - - event_log += day_entry.Replace ("", event_entries); - } - - string html = event_log_html.Replace ("", event_log) - .Replace ("", UserName) - .Replace ("", "file://" + GetAvatar (UserEmail, 48)); - - return html; } // Creates a .desktop entry in autostart folder to // start SparkleShare automatically at login - public abstract void EnableSystemAutostart (); + public override void EnableSystemAutostart () + { + string autostart_path = Path.Combine (Environment.GetFolderPath ( + Environment.SpecialFolder.ApplicationData), "autostart"); + + string desktopfile_path = Path.Combine (autostart_path, "sparkleshare.desktop"); + + if (!Directory.Exists (autostart_path)) + Directory.CreateDirectory (autostart_path); + + if (!File.Exists (desktopfile_path)) { + TextWriter writer = new StreamWriter (desktopfile_path); + writer.WriteLine ("[Desktop Entry]\n" + + "Type=Application\n" + + "Name=SparkleShare\n" + + "Exec=sparkleshare start\n" + + "Icon=folder-sparkleshare\n" + + "Terminal=false\n" + + "X-GNOME-Autostart-enabled=true\n" + + "Categories=Network"); + writer.Close (); + + // Give the launcher the right permissions so it can be launched by the user + UnixFileInfo file_info = new UnixFileInfo (desktopfile_path); + file_info.Create (FileAccessPermissions.UserReadWriteExecute); + + SparkleHelpers.DebugInfo ("Controller", "Enabled autostart on login"); + } + } + // Installs a launcher so the user can launch SparkleShare // from the Internet category if needed - public abstract void InstallLauncher (); + public override void InstallLauncher () + { + string apps_path = + new string [] {SparkleConfig.DefaultConfig.HomePath, + ".local", "share", "applications"}.Combine (); + + string desktopfile_path = Path.Combine (apps_path, "sparkleshare.desktop"); + + if (!File.Exists (desktopfile_path)) { + if (!Directory.Exists (apps_path)) + Directory.CreateDirectory (apps_path); + + TextWriter writer = new StreamWriter (desktopfile_path); + writer.WriteLine ("[Desktop Entry]\n" + + "Type=Application\n" + + "Name=SparkleShare\n" + + "Comment=Share documents\n" + + "Exec=sparkleshare start\n" + + "Icon=folder-sparkleshare\n" + + "Terminal=false\n" + + "Categories=Network;"); + writer.Close (); + + // Give the launcher the right permissions so it can be launched by the user + UnixFileInfo file_info = new UnixFileInfo (desktopfile_path); + file_info.FileAccessPermissions = FileAccessPermissions.UserReadWriteExecute; + + SparkleHelpers.DebugInfo ("Controller", "Created '" + desktopfile_path + "'"); + } + } + // Adds the SparkleShare folder to the user's // list of bookmarked places - public abstract void AddToBookmarks (); + public override void AddToBookmarks () + { + string bookmarks_file_path = Path.Combine (SparkleConfig.DefaultConfig.HomePath, ".gtk-bookmarks"); + string sparkleshare_bookmark = "file://" + SparkleConfig.DefaultConfig.FoldersPath + " SparkleShare"; + + if (File.Exists (bookmarks_file_path)) { + StreamReader reader = new StreamReader (bookmarks_file_path); + string bookmarks = reader.ReadToEnd (); + reader.Close (); + + if (!bookmarks.Contains (sparkleshare_bookmark)) { + TextWriter writer = File.AppendText (bookmarks_file_path); + writer.WriteLine ("file://" + SparkleConfig.DefaultConfig.FoldersPath + " SparkleShare"); + writer.Close (); + } + } else { + StreamWriter writer = new StreamWriter (bookmarks_file_path); + writer.WriteLine ("file://" + SparkleConfig.DefaultConfig.FoldersPath + " SparkleShare"); + writer.Close (); + } + } + // Creates the SparkleShare folder in the user's home folder - public abstract bool CreateSparkleShareFolder (); - - // Opens the SparkleShare folder or an (optional) subfolder - public abstract void OpenSparkleShareFolder (string subfolder); - - - // Fires events for the current syncing state - public void UpdateState () + public override bool CreateSparkleShareFolder () { - foreach (SparkleRepoBase repo in Repositories) { - if (repo.Status == SyncStatus.SyncDown || - repo.Status == SyncStatus.SyncUp || - repo.IsBuffering) { + if (!Directory.Exists (SparkleConfig.DefaultConfig.FoldersPath)) { + + Directory.CreateDirectory (SparkleConfig.DefaultConfig.FoldersPath); + SparkleHelpers.DebugInfo ("Controller", "Created '" + SparkleConfig.DefaultConfig.FoldersPath + "'"); - if (OnSyncing != null) - OnSyncing (); + string gvfs_command_path = + new string [] {Path.VolumeSeparatorChar.ToString (), + "usr", "bin", "gvfs-set-attribute"}.Combine (); - return; + // Add a special icon to the SparkleShare folder + if (File.Exists (gvfs_command_path)) { + Process process = new Process (); - } else if (repo.HasUnsyncedChanges) { - if (OnError != null) - OnError (); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "gvfs-set-attribute"; - return; + // Clear the custom (legacy) icon path + process.StartInfo.Arguments = "-t unset " + SparkleConfig.DefaultConfig.FoldersPath + " metadata::custom-icon"; + process.Start (); + process.WaitForExit (); + + // Give the SparkleShare folder an icon name, so that it scales + process.StartInfo.Arguments = SparkleConfig.DefaultConfig.FoldersPath + " metadata::custom-icon-name 'folder-sparkleshare'"; + process.Start (); + process.WaitForExit (); } + + return true; } - if (OnIdle != null) - OnIdle (); - - FolderSize = GetFolderSize (); - - if (FolderSizeChanged != null) - FolderSizeChanged (FolderSize); + return false; } + - - // Adds a repository to the list of repositories - private void AddRepository (string folder_path) - { - if (folder_path.Equals (SparklePaths.SparkleTmpPath)) - return; - - string folder_name = Path.GetFileName (folder_path); - string backend = SparkleConfig.DefaultConfig.GetBackendForFolder (folder_name); - - if (backend == null) - return; - - SparkleRepoBase repo = null; - - if (backend.Equals ("Hg")) - repo = new SparkleRepoHg (folder_path, new SparkleBackendHg ()); - - else if (backend.Equals ("Scp")) - repo = new SparkleRepoScp (folder_path, new SparkleBackendScp ()); - - else - repo = new SparkleRepoGit (folder_path, SparkleBackend.DefaultBackend); - - repo.NewChangeSet += delegate (SparkleChangeSet change_set, string repository_path) { - string message = FormatMessage (change_set); - - if (NotificationRaised != null) - NotificationRaised (change_set.UserName, change_set.UserEmail, message, repository_path); - }; - - repo.ConflictResolved += delegate { - if (ConflictNotificationRaised != null) - ConflictNotificationRaised (); - }; - - repo.SyncStatusChanged += delegate (SyncStatus status) { -/* if (status == SyncStatus.SyncUp) { - foreach (string path in repo.UnsyncedFilePaths) - Console.WriteLine (path); - } -*/ - if (status == SyncStatus.Idle || - status == SyncStatus.SyncUp || - status == SyncStatus.SyncDown || - status == SyncStatus.Error) { - - UpdateState (); - } - }; - - repo.ChangesDetected += delegate { - UpdateState (); - }; - - Repositories.Add (repo); - } - - - // 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 < Repositories.Count; i++) { - SparkleRepoBase repo = Repositories [i]; - - if (repo.Name.Equals (folder_name)) { - repo.Dispose (); - Repositories.Remove (repo); - repo = null; - break; - } - } - } - - - // Updates the list of repositories with all the - // folders in the SparkleShare folder - private void PopulateRepositories () - { - Repositories = new List (); - - foreach (string folder_name in SparkleConfig.DefaultConfig.Folders) { - string folder_path = Path.Combine (SparklePaths.SparklePath, folder_name); - - if (Directory.Exists (folder_path)) - AddRepository (folder_path); - else - SparkleConfig.DefaultConfig.RemoveFolder (folder_name); - } - - if (FolderListChanged != null) - FolderListChanged (); - - FolderSize = GetFolderSize (); - - if (FolderSizeChanged != null) - FolderSizeChanged (FolderSize); - } - - - public bool NotificationsEnabled { + public override string EventLogHTML { get { - string notifications_enabled = - SparkleConfig.DefaultConfig.GetConfigOption ("notifications"); + string path = new string [] {Defines.PREFIX, + "share", "sparkleshare", "html", "event-log.html"}.Combine (); - if (String.IsNullOrEmpty (notifications_enabled)) { - SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.TrueString); - return true; + string html = String.Join (Environment.NewLine, File.ReadAllLines (path)); - } else { - return notifications_enabled.Equals (bool.TrueString); - } + html = html.Replace ("", "file://" + + new string [] {Defines.PREFIX, "share", "sparkleshare", "html", "jquery.js"}.Combine ()); + + return html; } - } - - - public void ToggleNotifications () { - bool notifications_enabled = - SparkleConfig.DefaultConfig.GetConfigOption ("notifications") - .Equals (bool.TrueString); - - if (notifications_enabled) - SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.FalseString); - else - SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.TrueString); - } - - - private string GetFolderSize () - { - double folder_size = CalculateFolderSize (new DirectoryInfo (SparklePaths.SparklePath)); - return FormatFolderSize (folder_size); - } - - - private string FormatMessage (SparkleChangeSet change_set) - { - string file_name = ""; - string message = ""; - - if (change_set.Added.Count > 0) { - file_name = change_set.Added [0]; - message = String.Format (_("added ‘{0}’"), file_name); - } - - if (change_set.MovedFrom.Count > 0) { - file_name = change_set.MovedFrom [0]; - message = String.Format (_("moved ‘{0}’"), file_name); - } - - if (change_set.Edited.Count > 0) { - file_name = change_set.Edited [0]; - message = String.Format (_("edited ‘{0}’"), file_name); - } - - if (change_set.Deleted.Count > 0) { - file_name = change_set.Deleted [0]; - message = String.Format (_("deleted ‘{0}’"), file_name); - } - - int changes_count = (change_set.Added.Count + - change_set.Edited.Count + - change_set.Deleted.Count + - change_set.MovedFrom.Count) - 1; - - if (changes_count > 0) { - string msg = "and {0} more"; - message += " " + String.Format (msg, changes_count); - - } else if (changes_count < 0) { - message += _("did something magical"); - } - - return message; - } // TODO: move to bubbles controller - - - // Recursively gets a folder's size in bytes - private double CalculateFolderSize (DirectoryInfo parent) - { - if (!Directory.Exists (parent.ToString ())) - return 0; - - double size = 0; - - // Ignore the temporary 'rebase-apply' and '.tmp' directories. This prevents potential - // crashes when files are being queried whilst the files have already been deleted. - if (parent.Name.Equals ("rebase-apply") || - parent.Name.Equals (".tmp")) - return 0; - - try { - foreach (FileInfo file in parent.GetFiles()) { - if (!file.Exists) - return 0; - - size += file.Length; - } - - foreach (DirectoryInfo directory in parent.GetDirectories()) - size += CalculateFolderSize (directory); - - } catch (Exception) { - return 0; - } - - return size; - } - - - // Format a file size nicely with small caps. - // Example: 1048576 becomes "1 ᴍʙ" - private string FormatFolderSize (double byte_count) - { - if (byte_count >= 1099511627776) - return String.Format ("{0:##.##} ᴛʙ", Math.Round (byte_count / 1099511627776, 1)); - else if (byte_count >= 1073741824) - return String.Format ("{0:##.##} ɢʙ", Math.Round (byte_count / 1073741824, 1)); - else if (byte_count >= 1048576) - return String.Format ("{0:##.##} ᴍʙ", Math.Round (byte_count / 1048576, 1)); - else if (byte_count >= 1024) - return String.Format ("{0:##.##} ᴋʙ", Math.Round (byte_count / 1024, 1)); - else - return byte_count.ToString () + " bytes"; - } - - - public void OpenSparkleShareFolder () - { - OpenSparkleShareFolder (""); } - // Adds the user's SparkleShare key to the ssh-agent, - // so all activity is done with this key - public void AddKey () + public override string DayEntryHTML { + get { + string path = new string [] {Defines.PREFIX, + "share", "sparkleshare", "html", "day-entry.html"}.Combine (); + + return String.Join (Environment.NewLine, File.ReadAllLines (path)); + } + } + + + public override string EventEntryHTML { + get { + string path = new string [] {Defines.PREFIX, + "share", "sparkleshare", "html", "event-entry.html"}.Combine (); + + return String.Join (Environment.NewLine, File.ReadAllLines (path)); + } + } + + + public override void OpenSparkleShareFolder (string subfolder) { - string keys_path = SparklePaths.SparkleConfigPath; - string key_file_name = "sparkleshare." + UserEmail + ".key"; + string folder = Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, subfolder); Process process = new Process (); - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.UseShellExecute = false; - process.StartInfo.FileName = "ssh-add"; - process.StartInfo.Arguments = "\"" + Path.Combine (keys_path, key_file_name) + "\""; + process.StartInfo.FileName = "xdg-open"; + process.StartInfo.Arguments = "\"" + folder + "\""; process.Start (); - process.WaitForExit (); - } - - - public bool BackendIsPresent { - get { - return SparkleBackend.DefaultBackend.IsPresent; - } - } - - - // Looks up the user's name from the global configuration - public string UserName - { - get { - return SparkleConfig.DefaultConfig.UserName; - } - - set { - SparkleConfig.DefaultConfig.UserName = value; - } - } - - - // Looks up the user's email from the global configuration - public string UserEmail - { - get { - return SparkleConfig.DefaultConfig.UserEmail; - } - - set { - SparkleConfig.DefaultConfig.UserEmail = value; - } - } - - - // Generates and installs an RSA keypair to identify this system - public void GenerateKeyPair () - { - string keys_path = SparklePaths.SparkleConfigPath; - string key_file_name = "sparkleshare." + UserEmail + ".key"; - string key_file_path = Path.Combine (keys_path, key_file_name); - - if (File.Exists (key_file_path)) { - SparkleHelpers.DebugInfo ("Config", "Key already exists ('" + key_file_name + "'), " + - "leaving it untouched"); - return; - } - - if (!Directory.Exists (keys_path)) - Directory.CreateDirectory (keys_path); - - if (!File.Exists (key_file_name)) { - Process process = new Process () { - EnableRaisingEvents = true - }; - - process.StartInfo.WorkingDirectory = keys_path; - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.FileName = "ssh-keygen"; - - // -t is the crypto type - // -P is the password (none) - // -f is the file name to store the private key in - process.StartInfo.Arguments = "-t rsa -P \"\" -f " + key_file_name; - - process.Exited += delegate { - SparkleHelpers.DebugInfo ("Config", "Created private key '" + key_file_name + "'"); - SparkleHelpers.DebugInfo ("Config", "Created public key '" + key_file_name + ".pub'"); - - // Create an easily accessible copy of the public - // key in the user's SparkleShare folder - string PublicKeyCopy = Path.Combine (SparklePath, UserName + "'s key.txt"); - if (File.Exists (PublicKeyCopy)) - File.Delete (PublicKeyCopy); - File.Copy (key_file_path + ".pub", PublicKeyCopy); - }; - - process.Start (); - process.WaitForExit (); - } - } - - private bool FetchingAvatars = false; - // Gets the avatar for a specific email address and size - public void FetchAvatars (List emails, int size) - { - if (FetchingAvatars) - return; - FetchingAvatars = true; - - List old_avatars = new List (); - bool avatar_fetched = false; - string avatar_path = SparkleHelpers.CombineMore ( - SparklePaths.SparkleLocalIconPath, size + "x" + size, "status"); - - if (!Directory.Exists (avatar_path)) { - Directory.CreateDirectory (avatar_path); - SparkleHelpers.DebugInfo ("Config", "Created '" + avatar_path + "'"); - } - - foreach (string email in emails) { - string avatar_file_path = Path.Combine (avatar_path, "avatar-" + email); - - if (File.Exists (avatar_file_path)) { - FileInfo avatar_info = new FileInfo (avatar_file_path); - - // Delete avatars older than a month - if (avatar_info.CreationTime < DateTime.Now.AddMonths (-1)) { - avatar_info.Delete (); - old_avatars.Add (email); - } - - } else { - WebClient client = new WebClient (); - string url = "http://gravatar.com/avatar/" + GetMD5 (email) + - ".jpg?s=" + size + "&d=404"; - - try { - // Fetch the avatar - byte [] buffer = client.DownloadData (url); - - // Write the avatar data to a - // if not empty - if (buffer.Length > 255) { - avatar_fetched = true; - File.WriteAllBytes (avatar_file_path, buffer); - SparkleHelpers.DebugInfo ("Controller", "Fetched gravatar for " + email); - } - - } catch (WebException ex) { - SparkleHelpers.DebugInfo ("Controller", "Failed fetching gravatar for " + email); - - if (ex.Status == WebExceptionStatus.Timeout) { - // stop downloading further avatars if we have no internet access - break; - } - } - } - } - FetchingAvatars = false; - - // Fetch new versions of the avatars that we - // deleted because they were too old - if (old_avatars.Count > 0) - FetchAvatars (old_avatars, size); - - if (AvatarFetched != null && avatar_fetched) - AvatarFetched (); - } - - - public virtual string GetAvatar (string email, int size) - { - string avatar_file_path = SparkleHelpers.CombineMore ( - SparklePaths.SparkleLocalIconPath, size + "x" + size, "status", "avatar-" + email); - - return avatar_file_path; - } - - - public void FetchFolder (string server, string remote_folder) - { - server = server.Trim (); - remote_folder = remote_folder.Trim (); - - if (!Directory.Exists (SparklePaths.SparkleTmpPath)) - Directory.CreateDirectory (SparklePaths.SparkleTmpPath); - - // Strip the '.git' from the name - string canonical_name = Regex.Replace (remote_folder, @"\..*$", ""); - canonical_name = Regex.Replace (remote_folder, @"^.*/", ""); - - string tmp_folder = Path.Combine (SparklePaths.SparkleTmpPath, canonical_name); - - SparkleFetcherBase fetcher = null; - string backend = null; - - if (remote_folder.EndsWith (".hg")) { - remote_folder = remote_folder.Substring (0, (remote_folder.Length - 3)); - fetcher = new SparkleFetcherHg (server, remote_folder, tmp_folder); - backend = "Hg"; - - } else if (remote_folder.EndsWith (".scp")) { - remote_folder = remote_folder.Substring (0, (remote_folder.Length - 4)); - fetcher = new SparkleFetcherScp (server, remote_folder, tmp_folder); - backend = "Scp"; - - } else { - fetcher = new SparkleFetcherGit (server, remote_folder, tmp_folder); - backend = "Git"; - } - - bool target_folder_exists = Directory.Exists (Path.Combine (SparklePaths.SparklePath, canonical_name)); - - // Add a numbered suffix to the nameif a folder with the same name - // already exists. Example: "Folder (2)" - int i = 1; - while (target_folder_exists) { - i++; - target_folder_exists = Directory.Exists ( - Path.Combine (SparklePaths.SparklePath, canonical_name + " (" + i + ")")); - } - - string target_folder_name = canonical_name; - if (i > 1) - target_folder_name += " (" + i + ")"; - - fetcher.Finished += delegate { - - // Needed to do the moving - SparkleHelpers.ClearAttributes (tmp_folder); - string target_folder_path = Path.Combine (SparklePaths.SparklePath, target_folder_name); - - try { - Directory.Move (tmp_folder, target_folder_path); - } catch (Exception e) { - SparkleHelpers.DebugInfo ("Controller", "Error moving folder: " + e.Message); - } - - SparkleConfig.DefaultConfig.AddFolder (target_folder_name, fetcher.RemoteUrl, backend); - AddRepository (target_folder_path); - - if (FolderFetched != null) - FolderFetched (target_folder_name); - - FolderSize = GetFolderSize (); - - if (FolderSizeChanged != null) - FolderSizeChanged (FolderSize); - - if (FolderListChanged != null) - FolderListChanged (); - - fetcher.Dispose (); - - if (Directory.Exists (SparklePaths.SparkleTmpPath)) - Directory.Delete (SparklePaths.SparkleTmpPath, true); - }; - - - fetcher.Failed += delegate { - if (FolderFetchError != null) - FolderFetchError (); - - fetcher.Dispose (); - - if (Directory.Exists (SparklePaths.SparkleTmpPath)) - Directory.Delete (SparklePaths.SparkleTmpPath, true); - }; - - - fetcher.Start (); - } - - - // Creates an MD5 hash of input - private string GetMD5 (string s) - { - MD5 md5 = new MD5CryptoServiceProvider (); - Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); - Byte[] encoded_bytes = md5.ComputeHash (bytes); - return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); - } - - - // Checks whether there are any folders syncing and - // quits if safe - public void TryQuit () - { - foreach (SparkleRepoBase repo in Repositories) { - if (repo.Status == SyncStatus.SyncUp || - repo.Status == SyncStatus.SyncDown || - repo.IsBuffering) { - - if (OnQuitWhileSyncing != null) - OnQuitWhileSyncing (); - - return; - } - } - - Quit (); - } - - - public void Quit () - { - foreach (SparkleRepoBase repo in Repositories) - repo.Dispose (); - Repositories = null; - -#if __MonoCS__ - Gtk.Application.Quit (); -#else - System.Windows.Forms.Application.Exit (); -#endif - } - - - // Checks to see if an email address is valid - public bool IsValidEmail (string email) - { - Regex regex = new Regex (@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", RegexOptions.IgnoreCase); - return regex.IsMatch (email); - } - - - - - public void AddNoteToFolder (string folder_name, string revision, string note) - { - folder_name = folder_name.Replace ("%20", " "); - note = note.Replace ("%20", " "); - - foreach (SparkleRepoBase repo in Repositories) { - if (repo.Name.Equals (folder_name)) - repo.AddNote (revision, note); - } - } - - - - - private string [] tango_palette = new string [] {"#eaab00", "#e37222", - "#3892ab", "#33c2cb", "#19b271", "#9eab05", "#8599a8", "#9ca696", - "#b88454", "#cc0033", "#8f6678", "#8c6cd0", "#796cbf", "#4060af", - "#aa9c8f", "#818a8f"}; - - private string AssignColor (string s) - { - string hash = GetMD5 (s).Substring (0, 8); - string numbers = Regex.Replace (hash, "[a-z]", ""); - int number = 3 + int.Parse (numbers); - return this.tango_palette [number % this.tango_palette.Length]; - } - } - - - public class ChangeSet : SparkleChangeSet { } - - - // All change sets that happened on a day - public class ActivityDay : List - { - public DateTime DateTime; - - public ActivityDay (DateTime date_time) - { - DateTime = date_time; - DateTime = new DateTime (DateTime.Year, DateTime.Month, DateTime.Day); } } } diff --git a/SparkleShare/SparkleControllerBase.cs b/SparkleShare/SparkleControllerBase.cs new file mode 100644 index 00000000..f2276553 --- /dev/null +++ b/SparkleShare/SparkleControllerBase.cs @@ -0,0 +1,1183 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; + +#if __MonoCS__ +using Mono.Unix; +#endif +using SparkleLib; + +namespace SparkleShare { + + public abstract class SparkleControllerBase { + + public List Repositories; + public string FolderSize; + public readonly string SparklePath = SparkleConfig.DefaultConfig.FoldersPath; + + public event OnQuitWhileSyncingEventHandler OnQuitWhileSyncing; + public delegate void OnQuitWhileSyncingEventHandler (); + + public event FolderFetchedEventHandler FolderFetched; + public delegate void FolderFetchedEventHandler (); + + public event FolderFetchErrorEventHandler FolderFetchError; + public delegate void FolderFetchErrorEventHandler (string remote_url); + + public event FolderFetchingEventHandler FolderFetching; + public delegate void FolderFetchingEventHandler (double percentage); + + public event FolderListChangedEventHandler FolderListChanged; + public delegate void FolderListChangedEventHandler (); + + public event FolderSizeChangedEventHandler FolderSizeChanged; + public delegate void FolderSizeChangedEventHandler (string folder_size); + + public event AvatarFetchedEventHandler AvatarFetched; + public delegate void AvatarFetchedEventHandler (); + + public event OnIdleEventHandler OnIdle; + public delegate void OnIdleEventHandler (); + + public event OnSyncingEventHandler OnSyncing; + public delegate void OnSyncingEventHandler (); + + public event OnErrorEventHandler OnError; + public delegate void OnErrorEventHandler (); + + public event OnInvitationEventHandler OnInvitation; + public delegate void OnInvitationEventHandler (string server, string folder, string token); + + public event ConflictNotificationRaisedEventHandler ConflictNotificationRaised; + public delegate void ConflictNotificationRaisedEventHandler (); + + public event NotificationRaisedEventHandler NotificationRaised; + public delegate void NotificationRaisedEventHandler (string user_name, string user_email, + string message, string repository_path); + + private SparkleFetcherBase fetcher; + + + // Short alias for the translations + public static string _ (string s) + { +#if __MonoCS__ + return Catalog.GetString (s); +#else + return s; +#endif + } + + + public SparkleControllerBase () + { + } + + + public virtual void Initialize () + { + InstallLauncher (); + EnableSystemAutostart (); + + // Create the SparkleShare folder and add it to the bookmarks + if (CreateSparkleShareFolder ()) + AddToBookmarks (); + + FolderSize = GetFolderSize (); + + if (FirstRun) + SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.TrueString); + else + AddKey (); + + // Watch the SparkleShare folder + FileSystemWatcher watcher = new FileSystemWatcher (SparkleConfig.DefaultConfig.FoldersPath) { + IncludeSubdirectories = false, + EnableRaisingEvents = true, + Filter = "*" + }; + + // Remove the repository when a delete event occurs + watcher.Deleted += delegate (object o, FileSystemEventArgs args) { + RemoveRepository (args.FullPath); + SparkleConfig.DefaultConfig.RemoveFolder (Path.GetFileName (args.Name)); + + if (FolderListChanged != null) + FolderListChanged (); + + FolderSize = GetFolderSize (); + + if (FolderSizeChanged != null) + FolderSizeChanged (FolderSize); + }; + + + watcher.Created += delegate (object o, FileSystemEventArgs args) { + + // Handle invitations when the user saves an + // invitation into the SparkleShare folder + if (args.Name.EndsWith (".sparkle") && !FirstRun) { + XmlDocument xml_doc = new XmlDocument (); + xml_doc.Load (args.Name); + + string server = xml_doc.GetElementsByTagName ("server") [0].InnerText; + string folder = xml_doc.GetElementsByTagName ("folder") [0].InnerText; + string token = xml_doc.GetElementsByTagName ("token") [0].InnerText; + + // FIXME: this is broken :\ + if (OnInvitation != null) + OnInvitation (server, folder, token); + } + }; + + new Thread (new ThreadStart (PopulateRepositories)).Start (); + } + + + public bool FirstRun { + get { + return SparkleConfig.DefaultConfig.User.Email.Equals ("Unknown"); + } + } + + + // Uploads the user's public key to the server + public bool AcceptInvitation (string server, string folder, string token) + { + // The location of the user's public key for SparkleShare + string public_key_file_path = SparkleHelpers.CombineMore (SparkleConfig.DefaultConfig.HomePath, ".ssh", + "sparkleshare." + UserEmail + ".key.pub"); + + if (!File.Exists (public_key_file_path)) + return false; + + StreamReader reader = new StreamReader (public_key_file_path); + string public_key = reader.ReadToEnd (); + reader.Close (); + + string url = "https://" + server + "/?folder=" + folder + + "&token=" + token + "&pubkey=" + public_key; + + SparkleHelpers.DebugInfo ("WebRequest", url); + + HttpWebRequest request = (HttpWebRequest) WebRequest.Create (url); + HttpWebResponse response = (HttpWebResponse) request.GetResponse(); + + if (response.StatusCode == HttpStatusCode.OK) { + response.Close (); + return true; + + } else { + response.Close (); + return false; + } + } + + + public List Folders { + get { + List folders = SparkleConfig.DefaultConfig.Folders; + folders.Sort (); + return folders; + } + } + + + public List PreviousHosts { + get { + List hosts = SparkleConfig.DefaultConfig.HostsWithUsername; + hosts.AddRange(SparkleConfig.DefaultConfig.Hosts); + hosts.Sort (); + return hosts; + } + } + + + public List UnsyncedFolders { + get { + List unsynced_folders = new List (); + + foreach (SparkleRepoBase repo in Repositories) { + if (repo.HasUnsyncedChanges) + unsynced_folders.Add (repo.Name); + } + + return unsynced_folders; + } + } + + + public List GetLog () + { + List list = new List (); + + foreach (SparkleRepoBase repo in Repositories) { + List change_sets = repo.GetChangeSets (50); + + if (change_sets != null) + list.AddRange (change_sets); + else + SparkleHelpers.DebugInfo ("Log", "Could not create log for " + repo.Name); + } + + list.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp))); + list.Reverse (); + + if (list.Count > 100) + return list.GetRange (0, 100); + else + return list.GetRange (0, list.Count); + } + + + public List GetLog (string name) + { + if (name == null) + return GetLog (); + + string path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, name}.Combine (); + int log_size = 50; + + foreach (SparkleRepoBase repo in Repositories) { + if (repo.LocalPath.Equals (path)) + return repo.GetChangeSets (log_size); + } + + return null; + } + + + public abstract string EventLogHTML { get; } + public abstract string DayEntryHTML { get; } + public abstract string EventEntryHTML { get; } + + + public string GetHTMLLog (List change_sets) + { + List activity_days = new List (); + List emails = new List (); + + change_sets.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp))); + change_sets.Reverse (); + + if (change_sets.Count == 0) + return null; + + foreach (SparkleChangeSet change_set in change_sets) { + if (!emails.Contains (change_set.User.Email)) + emails.Add (change_set.User.Email); + + bool change_set_inserted = false; + foreach (ActivityDay stored_activity_day in activity_days) { + if (stored_activity_day.DateTime.Year == change_set.Timestamp.Year && + stored_activity_day.DateTime.Month == change_set.Timestamp.Month && + stored_activity_day.DateTime.Day == change_set.Timestamp.Day) { + + bool squash = false; + foreach (SparkleChangeSet existing_set in stored_activity_day) { + if (change_set.User.Name.Equals (existing_set.User.Name) && + change_set.User.Email.Equals (existing_set.User.Email) && + change_set.Folder.Equals (existing_set.Folder)) { + + existing_set.Added.AddRange (change_set.Added); + existing_set.Edited.AddRange (change_set.Edited); + existing_set.Deleted.AddRange (change_set.Deleted); + existing_set.MovedFrom.AddRange (change_set.MovedFrom); + existing_set.MovedTo.AddRange (change_set.MovedTo); + existing_set.Notes.AddRange (change_set.Notes); + + existing_set.Added = existing_set.Added.Distinct ().ToList (); + existing_set.Edited = existing_set.Edited.Distinct ().ToList (); + existing_set.Deleted = existing_set.Deleted.Distinct ().ToList (); + + if (DateTime.Compare (existing_set.Timestamp, change_set.Timestamp) < 1) { + existing_set.FirstTimestamp = existing_set.Timestamp; + existing_set.Timestamp = change_set.Timestamp; + existing_set.Revision = change_set.Revision; + + } else { + existing_set.FirstTimestamp = change_set.Timestamp; + } + + squash = true; + } + } + + if (!squash) + stored_activity_day.Add (change_set); + + change_set_inserted = true; + break; + } + } + + if (!change_set_inserted) { + ActivityDay activity_day = new ActivityDay (change_set.Timestamp); + activity_day.Add (change_set); + activity_days.Add (activity_day); + } + } + + new Thread (new ThreadStart (delegate { + FetchAvatars (emails, 48); + })).Start (); + + string event_log_html = EventLogHTML; + string day_entry_html = DayEntryHTML; + string event_entry_html = EventEntryHTML; + string event_log = ""; + + foreach (ActivityDay activity_day in activity_days) { + string event_entries = ""; + + foreach (SparkleChangeSet change_set in activity_day) { + string event_entry = "
"; + + if (change_set.IsMagical) { + event_entry += "
Did something magical
"; + + } else { + if (change_set.Edited.Count > 0) { + foreach (string file_path in change_set.Edited) { + string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, + change_set.Folder, file_path}.Combine (); + + if (File.Exists (absolute_file_path)) + event_entry += "
" + file_path + "
"; + else + event_entry += "
" + file_path + "
"; + } + } + + if (change_set.Added.Count > 0) { + foreach (string file_path in change_set.Added) { + string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, + change_set.Folder, file_path}.Combine (); + + if (File.Exists (absolute_file_path)) + event_entry += "
" + file_path + "
"; + else + event_entry += "
" + file_path + "
"; + } + } + + if (change_set.Deleted.Count > 0) { + foreach (string file_path in change_set.Deleted) { + string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, + change_set.Folder, file_path}.Combine (); + + if (File.Exists (absolute_file_path)) + event_entry += "
" + file_path + "
"; + else + event_entry += "
" + file_path + "
"; + } + } + + if (change_set.MovedFrom.Count > 0) { + int i = 0; + foreach (string file_path in change_set.MovedFrom) { + string to_file_path = change_set.MovedTo [i]; + + string absolute_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, + change_set.Folder, file_path}.Combine (); + + string absolute_to_file_path = new string [] {SparkleConfig.DefaultConfig.FoldersPath, + change_set.Folder, to_file_path}.Combine (); + + if (File.Exists (absolute_file_path)) + event_entry += "
" + file_path + "
"; + else + event_entry += "
" + file_path + "
"; + + if (File.Exists (absolute_to_file_path)) + event_entry += "" + to_file_path + "
"; + else + event_entry += to_file_path + ""; + + i++; + } + } + } + + string comments = ""; + comments = "
"; + + if (change_set.Notes != null) { + change_set.Notes.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp))); + + foreach (SparkleNote note in change_set.Notes) { + + string note_avatar = GetAvatar (note.User.Email, 48); + if (File.Exists (note_avatar)) + note_avatar = "file://" + note_avatar; + else + note_avatar = ""; + + comments += "
" + + "

" + + note.User.Name + "

" + + note.Body + + "
"; + } + } + + comments += "
"; + + string change_set_avatar = GetAvatar (change_set.User.Email, 48); + if (File.Exists (change_set_avatar)) + change_set_avatar = "file://" + change_set_avatar; + else + change_set_avatar = ""; + + event_entry += "
"; + + string timestamp = change_set.Timestamp.ToString ("H:mm"); + + if (!change_set.FirstTimestamp.Equals (new DateTime ())) + timestamp = change_set.FirstTimestamp.ToString ("H:mm") + + " – " + timestamp; + + event_entries += event_entry_html.Replace ("", event_entry) + .Replace ("", change_set.User.Name) + .Replace ("", change_set_avatar) + .Replace ("", timestamp) + .Replace ("", change_set.Folder) + .Replace ("", change_set.Revision) + .Replace ("", AssignColor (change_set.Folder)) + .Replace ("", comments); + } + + string day_entry = ""; + DateTime today = DateTime.Now; + DateTime yesterday = DateTime.Now.AddDays (-1); + + if (today.Day == activity_day.DateTime.Day && + today.Month == activity_day.DateTime.Month && + today.Year == activity_day.DateTime.Year) { + + day_entry = day_entry_html.Replace ("", "Today"); + + } else if (yesterday.Day == activity_day.DateTime.Day && + yesterday.Month == activity_day.DateTime.Month && + yesterday.Year == activity_day.DateTime.Year) { + + day_entry = day_entry_html.Replace ("", "Yesterday"); + + } else { + if (activity_day.DateTime.Year != DateTime.Now.Year) { + + // TRANSLATORS: This is the date in the event logs + day_entry = day_entry_html.Replace ("", + activity_day.DateTime.ToString (_("dddd, MMMM d, yyyy"))); + + } else { + + // TRANSLATORS: This is the date in the event logs, without the year + day_entry = day_entry_html.Replace ("", + activity_day.DateTime.ToString (_("dddd, MMMM d"))); + } + } + + event_log += day_entry.Replace ("", event_entries); + } + + string html = event_log_html.Replace ("", event_log) + .Replace ("", UserName) + .Replace ("", "file://" + GetAvatar (UserEmail, 48)); + + return html; + } + + + // Creates a .desktop entry in autostart folder to + // start SparkleShare automatically at login + public abstract void EnableSystemAutostart (); + + // Installs a launcher so the user can launch SparkleShare + // from the Internet category if needed + public abstract void InstallLauncher (); + + // Adds the SparkleShare folder to the user's + // list of bookmarked places + public abstract void AddToBookmarks (); + + // Creates the SparkleShare folder in the user's home folder + public abstract bool CreateSparkleShareFolder (); + + // Opens the SparkleShare folder or an (optional) subfolder + public abstract void OpenSparkleShareFolder (string subfolder); + + + // Fires events for the current syncing state + public void UpdateState () + { + foreach (SparkleRepoBase repo in Repositories) { + if (repo.Status == SyncStatus.SyncDown || + repo.Status == SyncStatus.SyncUp || + repo.IsBuffering) { + + if (OnSyncing != null) + OnSyncing (); + + return; + + } else if (repo.HasUnsyncedChanges) { + if (OnError != null) + OnError (); + + return; + } + } + + if (OnIdle != null) + OnIdle (); + + FolderSize = GetFolderSize (); + + if (FolderSizeChanged != null) + FolderSizeChanged (FolderSize); + } + + + // Adds a repository to the list of repositories + private void AddRepository (string folder_path) + { + if (folder_path.Equals (SparkleConfig.DefaultConfig.TmpPath)) + return; + + string folder_name = Path.GetFileName (folder_path); + string backend = SparkleConfig.DefaultConfig.GetBackendForFolder (folder_name); + + if (backend == null) + return; + + SparkleRepoBase repo = new SparkleRepoGit (folder_path, SparkleBackend.DefaultBackend); + + repo.NewChangeSet += delegate (SparkleChangeSet change_set) { + string message = FormatMessage (change_set); + + if (NotificationRaised != null) + NotificationRaised (change_set.User.Name, change_set.User.Email, message, repo.LocalPath); + }; + + repo.NewNote += delegate (string user_name, string user_email) { + if (NotificationRaised != null) + NotificationRaised (user_name, user_email, + "added a note to " + Path.GetFileName (repo.LocalPath), repo.LocalPath); + }; + + repo.ConflictResolved += delegate { + if (ConflictNotificationRaised != null) + ConflictNotificationRaised (); + }; + + repo.SyncStatusChanged += delegate (SyncStatus status) { +/* if (status == SyncStatus.SyncUp) { + foreach (string path in repo.UnsyncedFilePaths) + Console.WriteLine (path); + } +*/ + if (status == SyncStatus.Idle || + status == SyncStatus.SyncUp || + status == SyncStatus.SyncDown || + status == SyncStatus.Error) { + + UpdateState (); + } + }; + + repo.ChangesDetected += delegate { + UpdateState (); + }; + + Repositories.Add (repo); + } + + + // 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 < Repositories.Count; i++) { + SparkleRepoBase repo = Repositories [i]; + + if (repo.Name.Equals (folder_name)) { + repo.Dispose (); + Repositories.Remove (repo); + repo = null; + break; + } + } + } + + + // Updates the list of repositories with all the + // folders in the SparkleShare folder + private void PopulateRepositories () + { + Repositories = new List (); + + 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); + } + + if (FolderListChanged != null) + FolderListChanged (); + + FolderSize = GetFolderSize (); + + if (FolderSizeChanged != null) + FolderSizeChanged (FolderSize); + } + + + public bool NotificationsEnabled { + get { + string notifications_enabled = + SparkleConfig.DefaultConfig.GetConfigOption ("notifications"); + + if (String.IsNullOrEmpty (notifications_enabled)) { + SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.TrueString); + return true; + + } else { + return notifications_enabled.Equals (bool.TrueString); + } + } + } + + + public void ToggleNotifications () { + bool notifications_enabled = + SparkleConfig.DefaultConfig.GetConfigOption ("notifications") + .Equals (bool.TrueString); + + if (notifications_enabled) + SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.FalseString); + else + SparkleConfig.DefaultConfig.SetConfigOption ("notifications", bool.TrueString); + } + + + private string GetFolderSize () + { + double folder_size = CalculateFolderSize ( + new DirectoryInfo (SparkleConfig.DefaultConfig.FoldersPath)); + + return FormatFolderSize (folder_size); + } + + + private string FormatMessage (SparkleChangeSet change_set) + { + string file_name = ""; + string message = ""; + + if (change_set.Added.Count > 0) { + file_name = change_set.Added [0]; + message = String.Format (_("added ‘{0}’"), file_name); + } + + if (change_set.MovedFrom.Count > 0) { + file_name = change_set.MovedFrom [0]; + message = String.Format (_("moved ‘{0}’"), file_name); + } + + if (change_set.Edited.Count > 0) { + file_name = change_set.Edited [0]; + message = String.Format (_("edited ‘{0}’"), file_name); + } + + if (change_set.Deleted.Count > 0) { + file_name = change_set.Deleted [0]; + message = String.Format (_("deleted ‘{0}’"), file_name); + } + + int changes_count = (change_set.Added.Count + + change_set.Edited.Count + + change_set.Deleted.Count + + change_set.MovedFrom.Count) - 1; + + if (changes_count > 0) { +#if __MonoCS__ + string msg = Catalog.GetPluralString ("and {0} more", "and {0} more", changes_count); + message += " " + String.Format (msg, changes_count); +#else + message += " " + String.Format ("and {0} more", changes_count); +#endif + + } else if (changes_count < 0) { + message += _("did something magical"); + } + + return message; + } // TODO: move to bubbles controller + + + // Recursively gets a folder's size in bytes + private double CalculateFolderSize (DirectoryInfo parent) + { + if (!Directory.Exists (parent.ToString ())) + return 0; + + double size = 0; + + // Ignore the temporary 'rebase-apply' and '.tmp' directories. This prevents potential + // crashes when files are being queried whilst the files have already been deleted. + if (parent.Name.Equals ("rebase-apply") || + parent.Name.Equals (".tmp")) + return 0; + + try { + foreach (FileInfo file in parent.GetFiles()) { + if (!file.Exists) + return 0; + + size += file.Length; + } + + foreach (DirectoryInfo directory in parent.GetDirectories()) + size += CalculateFolderSize (directory); + + } catch (Exception) { + return 0; + } + + return size; + } + + + // Format a file size nicely with small caps. + // Example: 1048576 becomes "1 ᴍʙ" + private string FormatFolderSize (double byte_count) + { + if (byte_count >= 1099511627776) + return String.Format ("{0:##.##} ᴛʙ", Math.Round (byte_count / 1099511627776, 1)); + else if (byte_count >= 1073741824) + return String.Format ("{0:##.##} ɢʙ", Math.Round (byte_count / 1073741824, 1)); + else if (byte_count >= 1048576) + return String.Format ("{0:##.##} ᴍʙ", Math.Round (byte_count / 1048576, 1)); + else if (byte_count >= 1024) + return String.Format ("{0:##.##} ᴋʙ", Math.Round (byte_count / 1024, 1)); + else + return byte_count.ToString () + " bytes"; + } + + + public void OpenSparkleShareFolder () + { + OpenSparkleShareFolder (""); + } + + + // Adds the user's SparkleShare key to the ssh-agent, + // so all activity is done with this key + public void AddKey () + { + string keys_path = Path.GetDirectoryName (SparkleConfig.DefaultConfig.FullPath); + string key_file_name = "sparkleshare." + UserEmail + ".key"; + + Process process = new Process (); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.FileName = "ssh-add"; + process.StartInfo.Arguments = "\"" + Path.Combine (keys_path, key_file_name) + "\""; + process.Start (); + process.WaitForExit (); + } + + + public bool BackendIsPresent { + get { + return SparkleBackend.DefaultBackend.IsPresent; + } + } + + + // Looks up the user's name from the global configuration + public string UserName + { + get { + return SparkleConfig.DefaultConfig.User.Name; + } + + set { + SparkleConfig.DefaultConfig.User = new SparkleUser (value, UserEmail); + } + } + + + // Looks up the user's email from the global configuration + public string UserEmail + { + get { + return SparkleConfig.DefaultConfig.User.Email; + } + + set { + SparkleConfig.DefaultConfig.User = new SparkleUser (UserName, value); + } + } + + + // Generates and installs an RSA keypair to identify this system + public void GenerateKeyPair () + { + string keys_path = Path.GetDirectoryName (SparkleConfig.DefaultConfig.FullPath); + string key_file_name = "sparkleshare." + UserEmail + ".key"; + string key_file_path = Path.Combine (keys_path, key_file_name); + + if (File.Exists (key_file_path)) { + SparkleHelpers.DebugInfo ("Config", "Key already exists ('" + key_file_name + "'), " + + "leaving it untouched"); + return; + } + + if (!Directory.Exists (keys_path)) + Directory.CreateDirectory (keys_path); + + if (!File.Exists (key_file_name)) { + Process process = new Process () { + EnableRaisingEvents = true + }; + + process.StartInfo.WorkingDirectory = keys_path; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.FileName = "ssh-keygen"; + + // -t is the crypto type + // -P is the password (none) + // -f is the file name to store the private key in + process.StartInfo.Arguments = "-t rsa -P \"\" -f " + key_file_name; + + process.Exited += delegate { + SparkleHelpers.DebugInfo ("Config", "Created private key '" + key_file_name + "'"); + SparkleHelpers.DebugInfo ("Config", "Created public key '" + key_file_name + ".pub'"); + + // Create an easily accessible copy of the public + // key in the user's SparkleShare folder + File.Copy (key_file_path + ".pub", + Path.Combine (SparklePath, UserName + "'s key.txt")); + }; + + process.Start (); + process.WaitForExit (); + } + } + + + // Gets the avatar for a specific email address and size + public void FetchAvatars (List emails, int size) + { + List old_avatars = new List (); + bool avatar_fetched = false; + string avatar_path = new string [] { + Path.GetDirectoryName (SparkleConfig.DefaultConfig.FullPath), + "icons", size + "x" + size, "status"}.Combine (); + + if (!Directory.Exists (avatar_path)) { + Directory.CreateDirectory (avatar_path); + SparkleHelpers.DebugInfo ("Config", "Created '" + avatar_path + "'"); + } + + foreach (string raw_email in emails) { + + // Gravatar wants lowercase emails + string email = raw_email.ToLower (); + string avatar_file_path = Path.Combine (avatar_path, "avatar-" + email); + + if (File.Exists (avatar_file_path)) { + FileInfo avatar_info = new FileInfo (avatar_file_path); + + // Delete avatars older than a month + if (avatar_info.CreationTime < DateTime.Now.AddMonths (-1)) { + avatar_info.Delete (); + old_avatars.Add (email); + } + + } else { + WebClient client = new WebClient (); + string url = "http://gravatar.com/avatar/" + GetMD5 (email) + + ".jpg?s=" + size + "&d=404"; + + try { + // Fetch the avatar + byte [] buffer = client.DownloadData (url); + + // Write the avatar data to a + // if not empty + if (buffer.Length > 255) { + avatar_fetched = true; + File.WriteAllBytes (avatar_file_path, buffer); + SparkleHelpers.DebugInfo ("Controller", "Fetched gravatar for " + email); + } + + } catch (WebException e) { + SparkleHelpers.DebugInfo ("Controller", "Failed fetching gravatar for " + email); + + // Stop downloading further avatars if we have no internet access + if (e.Status == WebExceptionStatus.Timeout) + break; + } + } + } + + // Fetch new versions of the avatars that we + // deleted because they were too old + if (old_avatars.Count > 0) + FetchAvatars (old_avatars, size); + + if (AvatarFetched != null && avatar_fetched) + AvatarFetched (); + } + + + public virtual string GetAvatar (string email, int size) + { + string avatar_file_path = SparkleHelpers.CombineMore ( + Path.GetDirectoryName (SparkleConfig.DefaultConfig.FullPath), "icons", + size + "x" + size, "status", "avatar-" + email); + + return avatar_file_path; + } + + + public void FetchFolder (string server, string remote_folder) + { + server = server.Trim (); + remote_folder = remote_folder.Trim (); + + string tmp_path = SparkleConfig.DefaultConfig.TmpPath; + if (!Directory.Exists (tmp_path)) + Directory.CreateDirectory (tmp_path); + + // Strip the '.git' from the name + string canonical_name = Path.GetFileNameWithoutExtension (remote_folder); + string tmp_folder = Path.Combine (tmp_path, canonical_name); + + string backend = null; + +/* if (remote_folder.EndsWith (".hg")) { + remote_folder = remote_folder.Substring (0, (remote_folder.Length - 3)); + fetcher = new SparkleFetcherHg (server, remote_folder, tmp_folder); + backend = "Hg"; + + } else if (remote_folder.EndsWith (".scp")) { + remote_folder = remote_folder.Substring (0, (remote_folder.Length - 4)); + fetcher = new SparkleFetcherScp (server, remote_folder, tmp_folder); + backend = "Scp"; + + } else {*/ + this.fetcher = new SparkleFetcherGit (server, remote_folder, tmp_folder); + backend = "Git"; + //} + + bool target_folder_exists = Directory.Exists ( + Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, canonical_name)); + + // Add a numbered suffix to the nameif a folder with the same name + // already exists. Example: "Folder (2)" + int i = 1; + while (target_folder_exists) { + i++; + target_folder_exists = Directory.Exists ( + Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, canonical_name + " (" + i + ")")); + } + + string target_folder_name = canonical_name; + if (i > 1) + target_folder_name += " (" + i + ")"; + + this.fetcher.Finished += delegate { + + // Needed to do the moving + SparkleHelpers.ClearAttributes (tmp_folder); + string target_folder_path = Path.Combine ( + SparkleConfig.DefaultConfig.FoldersPath, target_folder_name); + + try { + Directory.Move (tmp_folder, target_folder_path); + } catch (Exception e) { + SparkleHelpers.DebugInfo ("Controller", "Error moving folder: " + e.Message); + } + + SparkleConfig.DefaultConfig.AddFolder (target_folder_name, this.fetcher.RemoteUrl, backend); + AddRepository (target_folder_path); + + if (FolderFetched != null) + FolderFetched (); + + FolderSize = GetFolderSize (); + + if (FolderSizeChanged != null) + FolderSizeChanged (FolderSize); + + if (FolderListChanged != null) + FolderListChanged (); + + this.fetcher.Dispose (); + + if (Directory.Exists (tmp_path)) + Directory.Delete (tmp_path, true); + }; + + + this.fetcher.Failed += delegate { + if (FolderFetchError != null) + FolderFetchError (this.fetcher.RemoteUrl); + + this.fetcher.Dispose (); + + if (Directory.Exists (tmp_path)) + Directory.Delete (tmp_path, true); + }; + + + this.fetcher.ProgressChanged += delegate (double percentage) { + if (FolderFetching != null) + FolderFetching (percentage); + }; + + + this.fetcher.Start (); + } + + + public void StopFetcher () + { + if (fetcher != null) + fetcher.Stop (); + } + + + // Creates an MD5 hash of input + private string GetMD5 (string s) + { + MD5 md5 = new MD5CryptoServiceProvider (); + Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); + Byte[] encoded_bytes = md5.ComputeHash (bytes); + return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); + } + + + // Checks whether there are any folders syncing and + // quits if safe + public void TryQuit () + { + foreach (SparkleRepoBase repo in Repositories) { + if (repo.Status == SyncStatus.SyncUp || + repo.Status == SyncStatus.SyncDown || + repo.IsBuffering) { + + if (OnQuitWhileSyncing != null) + OnQuitWhileSyncing (); + + return; + } + } + + Quit (); + } + + + public void Quit () + { + foreach (SparkleRepoBase repo in Repositories) + repo.Dispose (); + +#if __MonoCS__ + Environment.Exit (0); +#else + System.Windows.Forms.Application.Exit (); +#endif + } + + + // Checks to see if an email address is valid + public bool IsValidEmail (string email) + { + Regex regex = new Regex (@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", RegexOptions.IgnoreCase); + return regex.IsMatch (email); + } + + + + + public void AddNoteToFolder (string folder_name, string revision, string note) + { + folder_name = folder_name.Replace ("%20", " "); + note = note.Replace ("%20", " "); + + foreach (SparkleRepoBase repo in Repositories) { + if (repo.Name.Equals (folder_name)) + repo.AddNote (revision, note); + } + } + + + + + private string [] tango_palette = new string [] {"#eaab00", "#e37222", + "#3892ab", "#33c2cb", "#19b271", "#9eab05", "#8599a8", "#9ca696", + "#b88454", "#cc0033", "#8f6678", "#8c6cd0", "#796cbf", "#4060af", + "#aa9c8f", "#818a8f"}; + + private string AssignColor (string s) + { + string hash = GetMD5 (s).Substring (0, 8); + string numbers = Regex.Replace (hash, "[a-z]", ""); + int number = 3 + int.Parse (numbers); + return this.tango_palette [number % this.tango_palette.Length]; + } + } + + + public class ChangeSet : SparkleChangeSet { } + + + // All change sets that happened on a day + public class ActivityDay : List + { + public DateTime DateTime; + + public ActivityDay (DateTime date_time) + { + DateTime = date_time; + DateTime = new DateTime (DateTime.Year, DateTime.Month, DateTime.Day); + } + } +} diff --git a/SparkleShare/SparkleEventLog.cs b/SparkleShare/SparkleEventLog.cs index 4379e9a8..5ab9e152 100644 --- a/SparkleShare/SparkleEventLog.cs +++ b/SparkleShare/SparkleEventLog.cs @@ -23,24 +23,21 @@ using System.Threading; using Gtk; using Mono.Unix; -using SparkleLib; using WebKit; namespace SparkleShare { public class SparkleEventLog : Window { - private ScrolledWindow ScrolledWindow; - private MenuBar MenuBar; - private WebView WebView; - private string LinkStatus; - private SparkleSpinner Spinner; - private string HTML; - private EventBox LogContent; - private List change_sets; - private string selected_log = null; - private ComboBox combo_box; + public SparkleEventLogController Controller = new SparkleEventLogController (); + private HBox layout_horizontal; + private ComboBox combo_box; + private EventBox content_wrapper; + private ScrolledWindow scrolled_window; + private WebView web_view; + private SparkleSpinner spinner; + private string link_status; // Short alias for the translations @@ -63,49 +60,43 @@ namespace SparkleShare { DeleteEvent += Close; - CreateEvents (); - UpdateEvents (false); - UpdateChooser (); - } - - - private void CreateEvents () - { VBox layout_vertical = new VBox (false, 0); - LogContent = new EventBox (); + this.spinner = new SparkleSpinner (22); + this.content_wrapper = new EventBox (); + this.scrolled_window = new ScrolledWindow (); - ScrolledWindow = new ScrolledWindow (); - - WebView = new WebView () { + this.web_view = new WebView () { Editable = false }; - WebView.HoveringOverLink += delegate (object o, WebKit.HoveringOverLinkArgs args) { - LinkStatus = args.Link; + this.web_view.HoveringOverLink += delegate (object o, WebKit.HoveringOverLinkArgs args) { + this.link_status = args.Link; }; - WebView.NavigationRequested += delegate (object o, WebKit.NavigationRequestedArgs args) { - if (args.Request.Uri == LinkStatus) { + this.web_view.NavigationRequested += delegate (object o, WebKit.NavigationRequestedArgs args) { + if (args.Request.Uri == this.link_status) { + // TODO: controller Process process = new Process (); process.StartInfo.FileName = "xdg-open"; process.StartInfo.Arguments = args.Request.Uri.Replace (" ", "\\ "); // Escape space-characters process.Start (); } else { - Regex regex = new Regex (@"(.+)~(.+)~(.+)"); - Match match = regex.Match (args.Request.Uri); + //TODO: controller + Regex regex = new Regex (@"(.+)~(.+)~(.+)"); + Match match = regex.Match (args.Request.Uri); - if (match.Success) { - string folder_name = match.Groups [1].Value; - string revision = match.Groups [2].Value; - string note = match.Groups [3].Value.Replace ("%20", " "); + if (match.Success) { + string folder_name = match.Groups [1].Value; + string revision = match.Groups [2].Value; + string note = match.Groups [3].Value; - Thread thread = new Thread (new ThreadStart (delegate { - SparkleShare.Controller.AddNoteToFolder (folder_name, revision, note); - })); + Thread thread = new Thread (new ThreadStart (delegate { + Program.Controller.AddNoteToFolder (folder_name, revision, note); + })); - thread.Start (); - } + thread.Start (); + } } // Don't follow HREFs (as this would cause a page refresh) @@ -113,44 +104,75 @@ namespace SparkleShare { args.RetVal = 1; }; - ScrolledWindow.Add (WebView); - LogContent.Add (ScrolledWindow); + this.scrolled_window.Add (this.web_view); + this.content_wrapper.Add (this.spinner); + + this.spinner.Start (); this.layout_horizontal = new HBox (true, 0); this.layout_horizontal.PackStart (new Label (""), true, true, 0); this.layout_horizontal.PackStart (new Label (""), true, true, 0); - layout_vertical.PackStart (layout_horizontal, false, false, 0); - layout_vertical.PackStart (LogContent, true, true, 0); - - // We have to hide the menubar somewhere... + layout_vertical.PackStart (this.layout_horizontal, false, false, 0); layout_vertical.PackStart (CreateShortcutsBar (), false, false, 0); + layout_vertical.PackStart (this.content_wrapper, true, true, 0); Add (layout_vertical); ShowAll (); + + UpdateChooser (null); + UpdateContent (null); + + + // Hook up the controller events + Controller.UpdateChooserEvent += delegate (string [] folders) { + Application.Invoke (delegate { + UpdateChooser (folders); + }); + }; + + Controller.UpdateContentEvent += delegate (string html) { + Application.Invoke (delegate { + UpdateContent (html); + }); + }; + + Controller.ContentLoadingEvent += delegate { + Application.Invoke (delegate { + if (this.content_wrapper.Child != null) + this.content_wrapper.Remove (this.content_wrapper.Child); + + this.content_wrapper.Add (this.spinner); + this.spinner.Start (); + this.content_wrapper.ShowAll (); + }); + }; } - public void UpdateChooser () + public void UpdateChooser (string [] folders) { + if (folders == null) + folders = Controller.Folders; + if (this.combo_box != null && this.combo_box.Parent != null) this.layout_horizontal.Remove (this.combo_box); this.combo_box = new ComboBox (); - this.layout_horizontal.BorderWidth = 9; CellRendererText cell = new CellRendererText(); this.combo_box.PackStart (cell, false); this.combo_box.AddAttribute (cell, "text", 0); + ListStore store = new ListStore (typeof (string)); - this.combo_box.Model = store; - + store.AppendValues (_("All Folders")); store.AppendValues ("---"); - foreach (string folder_name in SparkleShare.Controller.Folders) - store.AppendValues (folder_name); + foreach (string folder in folders) + store.AppendValues (folder); + this.combo_box.Model = store; this.combo_box.Active = 0; this.combo_box.RowSeparatorFunc = delegate (TreeModel model, TreeIter iter) { @@ -158,116 +180,75 @@ namespace SparkleShare { return (item == "---"); }; - if (this.selected_log != null && - !SparkleShare.Controller.Folders.Contains (this.selected_log)) { - - this.selected_log = null; - } - this.combo_box.Changed += delegate { TreeIter iter; this.combo_box.GetActiveIter (out iter); - string selection = (string) this.combo_box.Model.GetValue (iter, 0); if (selection.Equals (_("All Folders"))) - this.selected_log = null; + Controller.SelectedFolder = null; else - this.selected_log = selection; - - UpdateEvents (false); + Controller.SelectedFolder = selection; }; + this.layout_horizontal.BorderWidth = 9; this.layout_horizontal.PackStart (this.combo_box, true, true, 0); this.layout_horizontal.ShowAll (); } - public void UpdateEvents () + public void UpdateContent (string html) { - UpdateEvents (true); - } - - - public void UpdateEvents (bool silent) - { - if (!silent) { - LogContent.Remove (LogContent.Child); - Spinner = new SparkleSpinner (22); - LogContent.Add (Spinner); - LogContent.ShowAll (); - } - Thread thread = new Thread (new ThreadStart (delegate { - Stopwatch watch = new Stopwatch (); - watch.Start (); - this.change_sets = SparkleShare.Controller.GetLog (this.selected_log); - GenerateHTML (); - watch.Stop (); + if (html == null) + html = Controller.HTML; - // A short delay is less annoying than - // a flashing window - if (watch.ElapsedMilliseconds < 500 && !silent) - Thread.Sleep (500 - (int) watch.ElapsedMilliseconds); + if (html == null) + return; - AddHTML (); + html = html.Replace ("", (double) (Style.FontDescription.Size / 1024 + 3) + "px"); + html = html.Replace ("", (Style.FontDescription.Size / 1024 + 3) + "px"); + html = html.Replace ("", "#0085cf"); + html = html.Replace ("", "#009ff8"); + html = html.Replace ("", "\"" + Style.FontDescription.Family + "\""); + html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Normal))); + html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (new TreeView ().Style.Base (StateType.Normal))); + html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Background (StateType.Normal))); + html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive))); + html = html.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive))); + html = html.Replace ("", "file://" + + new string [] {SparkleUI.AssetsPath, "icons", + "hicolor", "32x32", "status", "avatar-default.png"}.Combine ()); + html = html.Replace ("", "file://" + + new string [] {SparkleUI.AssetsPath, "icons", + "hicolor", "12x12", "status", "document-added.png"}.Combine ()); + html = html.Replace ("", "file://" + + new string [] {SparkleUI.AssetsPath, "icons", + "hicolor", "12x12", "status", "document-edited.png"}.Combine ()); + html = html.Replace ("", "file://" + + new string [] {SparkleUI.AssetsPath, "icons", + "hicolor", "12x12", "status", "document-deleted.png"}.Combine ()); + html = html.Replace ("", "file://" + + new string [] {SparkleUI.AssetsPath, "icons", + "hicolor", "12x12", "status", "document-moved.png"}.Combine ()); + + Application.Invoke (delegate { + this.spinner.Stop (); + this.web_view.LoadString (html, null, null, "file://"); + this.content_wrapper.Remove (this.content_wrapper.Child); + this.content_wrapper.Add (this.scrolled_window); + this.content_wrapper.ShowAll (); + }); })); thread.Start (); } - private void GenerateHTML () - { - HTML = SparkleShare.Controller.GetHTMLLog (this.change_sets); - - HTML = HTML.Replace ("", (double) (Style.FontDescription.Size / 1024 + 3) + "px"); - HTML = HTML.Replace ("", (Style.FontDescription.Size / 1024 + 3) + "px"); - HTML = HTML.Replace ("", "#0085cf"); - HTML = HTML.Replace ("", "#009ff8"); - HTML = HTML.Replace ("", "\"" + Style.FontDescription.Family + "\""); - HTML = HTML.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Normal))); - HTML = HTML.Replace ("", SparkleUIHelpers.GdkColorToHex (new TreeView ().Style.Base (StateType.Normal))); - HTML = HTML.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Background (StateType.Normal))); - HTML = HTML.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive))); - HTML = HTML.Replace ("", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive))); - HTML = HTML.Replace ("", "file://" + - SparkleHelpers.CombineMore (Defines.PREFIX, "share", "sparkleshare", "icons", - "hicolor", "32x32", "status", "avatar-default.png")); - HTML = HTML.Replace ("", "file://" + - SparkleHelpers.CombineMore (Defines.PREFIX, "share", "sparkleshare", "icons", - "hicolor", "12x12", "status", "document-added.png")); - HTML = HTML.Replace ("", "file://" + - SparkleHelpers.CombineMore (Defines.PREFIX, "share", "sparkleshare", "icons", - "hicolor", "12x12", "status", "document-edited.png")); - HTML = HTML.Replace ("", "file://" + - SparkleHelpers.CombineMore (Defines.PREFIX, "share", "sparkleshare", "icons", - "hicolor", "12x12", "status", "document-deleted.png")); - HTML = HTML.Replace ("", "file://" + - SparkleHelpers.CombineMore (Defines.PREFIX, "share", "sparkleshare", "icons", - "hicolor", "12x12", "status", "document-moved.png")); - } - - - private void AddHTML () - { - Application.Invoke (delegate { - Spinner.Stop (); - LogContent.Remove (LogContent.Child); - - WebView.LoadString (HTML, null, null, "file://"); - - LogContent.Add (ScrolledWindow); - LogContent.ShowAll (); - }); - } - - public void Close (object o, DeleteEventArgs args) { HideAll (); args.RetVal = true; - // TODO: window positions aren't saved } @@ -275,7 +256,7 @@ namespace SparkleShare { { // Adds a hidden menubar that contains to enable keyboard // shortcuts to close the log - MenuBar = new MenuBar (); + MenuBar menu_bar = new MenuBar (); MenuItem file_item = new MenuItem ("File"); @@ -304,15 +285,14 @@ namespace SparkleShare { file_item.Submenu = file_menu; - MenuBar.Append (file_item); + menu_bar.Append (file_item); // Hacky way to hide the menubar, but the accellerators // will simply be disabled when using Hide () - MenuBar.HeightRequest = 1; - MenuBar.ModifyBg (StateType.Normal, Style.Background (StateType.Normal)); + menu_bar.HeightRequest = 1; + menu_bar.ModifyBg (StateType.Normal, Style.Background (StateType.Normal)); - return MenuBar; + return menu_bar; } } } - diff --git a/SparkleShare/SparkleEventLogController.cs b/SparkleShare/SparkleEventLogController.cs index fdc99f3d..98d95ded 100644 --- a/SparkleShare/SparkleEventLogController.cs +++ b/SparkleShare/SparkleEventLogController.cs @@ -69,14 +69,14 @@ namespace SparkleShare { public string HTML { get { - List change_sets = SparkleShare.Controller.GetLog (this.selected_folder); - return SparkleShare.Controller.GetHTMLLog (change_sets); + List change_sets = Program.Controller.GetLog (this.selected_folder); + return Program.Controller.GetHTMLLog (change_sets); } } public string [] Folders { get { - return SparkleShare.Controller.Folders.ToArray (); + return Program.Controller.Folders.ToArray (); } } @@ -86,19 +86,19 @@ namespace SparkleShare { public SparkleEventLogController () { - SparkleShare.Controller.AvatarFetched += delegate { + Program.Controller.AvatarFetched += delegate { if (UpdateContentEvent != null) UpdateContentEvent (HTML); }; - SparkleShare.Controller.OnIdle += delegate { + Program.Controller.OnIdle += delegate { if (UpdateContentEvent != null) UpdateContentEvent (HTML); }; - SparkleShare.Controller.FolderListChanged += delegate { + Program.Controller.FolderListChanged += delegate { if (this.selected_folder != null && - !SparkleShare.Controller.Folders.Contains (this.selected_folder)) { + !Program.Controller.Folders.Contains (this.selected_folder)) { this.selected_folder = null; } @@ -110,7 +110,7 @@ namespace SparkleShare { UpdateContentEvent (HTML); }; - SparkleShare.Controller.NotificationRaised += delegate { + Program.Controller.NotificationRaised += delegate { if (UpdateContentEvent != null) UpdateContentEvent (HTML); }; diff --git a/SparkleShare/SparkleExtensions.cs b/SparkleShare/SparkleExtensions.cs new file mode 100644 index 00000000..348835b9 --- /dev/null +++ b/SparkleShare/SparkleExtensions.cs @@ -0,0 +1,35 @@ +// SparkleShare, a collaboration and sharing tool. +// Copyright (C) 2010 Hylke Bons (hylkebons@gmail.com) +// +// 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 (http://www.gnu.org/licenses/). + + +using System; +using System.IO; + +namespace SparkleShare { + + public static class Extensions { + + public static string Combine (this String [] parts) + { + string new_path = ""; + + foreach (string part in parts) + new_path = Path.Combine (new_path, part); + + return new_path; + } + } +} diff --git a/SparkleShare/SparkleLinController.cs b/SparkleShare/SparkleLinController.cs index 34e27385..0cc2168e 100644 --- a/SparkleShare/SparkleLinController.cs +++ b/SparkleShare/SparkleLinController.cs @@ -73,8 +73,11 @@ namespace SparkleShare { // from the Internet category if needed public override void InstallLauncher () { - string apps_path = SparkleHelpers.CombineMore (SparklePaths.HomePath, ".local", "share", "applications"); - string desktopfile_path = SparkleHelpers.CombineMore (apps_path, "sparkleshare.desktop"); + string apps_path = + new string [] {SparkleConfig.DefaultConfig.HomePath, + ".local", "share", "applications"}.Combine (); + + string desktopfile_path = Path.Combine (apps_path, "sparkleshare.desktop"); if (!File.Exists (desktopfile_path)) { if (!Directory.Exists (apps_path)) @@ -104,8 +107,8 @@ namespace SparkleShare { // list of bookmarked places public override void AddToBookmarks () { - string bookmarks_file_path = Path.Combine (SparklePaths.HomePath, ".gtk-bookmarks"); - string sparkleshare_bookmark = "file://" + SparklePaths.SparklePath + " SparkleShare"; + string bookmarks_file_path = Path.Combine (SparkleConfig.DefaultConfig.HomePath, ".gtk-bookmarks"); + string sparkleshare_bookmark = "file://" + SparkleConfig.DefaultConfig.FoldersPath + " SparkleShare"; if (File.Exists (bookmarks_file_path)) { StreamReader reader = new StreamReader (bookmarks_file_path); @@ -114,12 +117,12 @@ namespace SparkleShare { if (!bookmarks.Contains (sparkleshare_bookmark)) { TextWriter writer = File.AppendText (bookmarks_file_path); - writer.WriteLine ("file://" + SparklePaths.SparklePath + " SparkleShare"); + writer.WriteLine ("file://" + SparkleConfig.DefaultConfig.FoldersPath + " SparkleShare"); writer.Close (); } } else { StreamWriter writer = new StreamWriter (bookmarks_file_path); - writer.WriteLine ("file://" + SparklePaths.SparklePath + " SparkleShare"); + writer.WriteLine ("file://" + SparkleConfig.DefaultConfig.FoldersPath + " SparkleShare"); writer.Close (); } } @@ -128,13 +131,14 @@ namespace SparkleShare { // Creates the SparkleShare folder in the user's home folder public override bool CreateSparkleShareFolder () { - if (!Directory.Exists (SparklePaths.SparklePath)) { + if (!Directory.Exists (SparkleConfig.DefaultConfig.FoldersPath)) { - Directory.CreateDirectory (SparklePaths.SparklePath); - SparkleHelpers.DebugInfo ("Controller", "Created '" + SparklePaths.SparklePath + "'"); + Directory.CreateDirectory (SparkleConfig.DefaultConfig.FoldersPath); + SparkleHelpers.DebugInfo ("Controller", "Created '" + SparkleConfig.DefaultConfig.FoldersPath + "'"); - string gvfs_command_path = SparkleHelpers.CombineMore (Path.VolumeSeparatorChar.ToString (), - "usr", "bin", "gvfs-set-attribute"); + string gvfs_command_path = + new string [] {Path.VolumeSeparatorChar.ToString (), + "usr", "bin", "gvfs-set-attribute"}.Combine (); // Add a special icon to the SparkleShare folder if (File.Exists (gvfs_command_path)) { @@ -145,12 +149,12 @@ namespace SparkleShare { process.StartInfo.FileName = "gvfs-set-attribute"; // Clear the custom (legacy) icon path - process.StartInfo.Arguments = "-t unset " + SparklePaths.SparklePath + " metadata::custom-icon"; + process.StartInfo.Arguments = "-t unset " + SparkleConfig.DefaultConfig.FoldersPath + " metadata::custom-icon"; process.Start (); process.WaitForExit (); // Give the SparkleShare folder an icon name, so that it scales - process.StartInfo.Arguments = SparklePaths.SparklePath + " metadata::custom-icon-name 'folder-sparkleshare'"; + process.StartInfo.Arguments = SparkleConfig.DefaultConfig.FoldersPath + " metadata::custom-icon-name 'folder-sparkleshare'"; process.Start (); process.WaitForExit (); } @@ -164,13 +168,13 @@ namespace SparkleShare { public override string EventLogHTML { get { - string path = SparkleHelpers.CombineMore (Defines.PREFIX, - "share", "sparkleshare", "html", "event-log.html"); + string path = new string [] {Defines.PREFIX, + "share", "sparkleshare", "html", "event-log.html"}.Combine (); string html = String.Join (Environment.NewLine, File.ReadAllLines (path)); html = html.Replace ("", "file://" + - SparkleHelpers.CombineMore (Defines.PREFIX, "share", "sparkleshare", "html", "jquery.js")); + new string [] {Defines.PREFIX, "share", "sparkleshare", "html", "jquery.js"}.Combine ()); return html; } @@ -179,8 +183,8 @@ namespace SparkleShare { public override string DayEntryHTML { get { - string path = SparkleHelpers.CombineMore (Defines.PREFIX, - "share", "sparkleshare", "html", "day-entry.html"); + string path = new string [] {Defines.PREFIX, + "share", "sparkleshare", "html", "day-entry.html"}.Combine (); return String.Join (Environment.NewLine, File.ReadAllLines (path)); } @@ -189,8 +193,8 @@ namespace SparkleShare { public override string EventEntryHTML { get { - string path = SparkleHelpers.CombineMore (Defines.PREFIX, - "share", "sparkleshare", "html", "event-entry.html"); + string path = new string [] {Defines.PREFIX, + "share", "sparkleshare", "html", "event-entry.html"}.Combine (); return String.Join (Environment.NewLine, File.ReadAllLines (path)); } @@ -199,7 +203,7 @@ namespace SparkleShare { public override void OpenSparkleShareFolder (string subfolder) { - string folder = Path.Combine (SparklePaths.SparklePath, subfolder); + string folder = Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, subfolder); Process process = new Process (); process.StartInfo.FileName = "xdg-open"; diff --git a/SparkleShare/SparkleSetup.cs b/SparkleShare/SparkleSetup.cs index 0e7ecb72..2ea766ad 100644 --- a/SparkleShare/SparkleSetup.cs +++ b/SparkleShare/SparkleSetup.cs @@ -43,10 +43,8 @@ namespace SparkleShare { private Button SyncButton; private Table Table; - - private ProgressBar progress_bar = new ProgressBar () { PulseStep = 0.01 }; - private Timer progress_bar_pulse_timer = new Timer () { Interval = 25, Enabled = true }; - + private ProgressBar progress_bar = new ProgressBar (); + // Short alias for the translations public static string _ (string s) @@ -64,7 +62,7 @@ namespace SparkleShare { Reset (); switch (type) { - case PageType.Setup: + case PageType.Setup: { Header = _("Welcome to SparkleShare!"); Description = _("Before we can create a SparkleShare folder on this " + @@ -79,12 +77,12 @@ namespace SparkleShare { Xalign = 0 }; - NameEntry = new Entry (SparkleShare.Controller.UserName); + NameEntry = new Entry (Controller.GuessedUserName); NameEntry.Changed += delegate { CheckSetupPage (); }; - EmailEntry = new Entry (); + EmailEntry = new Entry (Controller.GuessedUserEmail); EmailEntry.Changed += delegate { CheckSetupPage (); }; @@ -116,13 +114,14 @@ namespace SparkleShare { CheckSetupPage (); break; + } - case PageType.Add: + case PageType.Add: { - Header = _("Where is your remote folder?"); + Header = _("Where is your project?"); Table = new Table (6, 2, false) { - RowSpacing = 12 + RowSpacing = 0 }; HBox layout_server = new HBox (true, 0); @@ -150,8 +149,8 @@ namespace SparkleShare { ListStore server_store = new ListStore (typeof (string)); - //TODO foreach (string host in SparkleShare.Controller.PreviousHosts) - // server_store.AppendValues (host); + foreach (string host in Program.Controller.PreviousHosts) + server_store.AppendValues (host); ServerEntry.Completion.Model = server_store; ServerEntry.Completion.TextColumn = 0; @@ -173,11 +172,7 @@ namespace SparkleShare { Table.Attach (layout_server, 0, 2, 1, 2); // Github radiobutton - string github_text = "" + "Github" + "\n" + - "" + - _("Free hosting for Free and Open Source Software projects.") + "\n" + - _("Also has paid accounts for extra private space and bandwidth.") + - ""; + string github_text = "" + "Github" + ""; RadioButton radio_button_github = new RadioButton (radio_button, github_text); (radio_button_github.Child as Label).UseMarkup = true; @@ -190,11 +185,7 @@ namespace SparkleShare { // Gitorious radiobutton - string gitorious_text = "" + _("Gitorious") + "\n" + - "" + - _("Completely Free as in Freedom infrastructure.") + "\n" + - _("Free accounts for Free and Open Source projects.") + - ""; + string gitorious_text = "" + _("Gitorious") + ""; RadioButton radio_button_gitorious = new RadioButton (radio_button, gitorious_text); (radio_button_gitorious.Child as Label).UseMarkup = true; @@ -207,11 +198,7 @@ namespace SparkleShare { // GNOME radiobutton - string gnome_text = "" + _("The GNOME Project") + "\n"+ - "" + - _("GNOME is an easy to understand interface to your computer.") + "\n" + - _("Select this option if you’re a developer or designer working on GNOME.") + - ""; + string gnome_text = "" + _("The GNOME Project") + ""; RadioButton radio_button_gnome = new RadioButton (radio_button, gnome_text); (radio_button_gnome.Child as Label).UseMarkup = true; @@ -236,6 +223,15 @@ namespace SparkleShare { FolderEntry = new SparkleEntry (); FolderEntry.ExampleText = _("Folder"); + FolderEntry.Completion = new EntryCompletion(); + + ListStore folder_store = new ListStore (typeof (string)); + + //foreach (string host in Program.Controller.FolderPaths) + // folder_store.AppendValues (host); + + FolderEntry.Completion.Model = folder_store; + FolderEntry.Completion.TextColumn = 0; FolderEntry.Changed += delegate { CheckAddPage (); @@ -245,7 +241,10 @@ namespace SparkleShare { layout_folder.PackStart (FolderEntry, true, true, 0); Table.Attach (layout_folder, 0, 2, 5, 6); - Add (Table); + + VBox box = new VBox (false, 0); + box.PackStart (Table, false, false, 0); + Add (box); // Cancel button Button cancel_button = new Button (_("Cancel")); @@ -256,7 +255,7 @@ namespace SparkleShare { // Sync button - SyncButton = new Button (_("Sync")); + SyncButton = new Button (_("Add")); SyncButton.Clicked += delegate { string server = ServerEntry.Text; @@ -280,27 +279,33 @@ namespace SparkleShare { CheckAddPage (); break; + } - case PageType.Syncing: + case PageType.Syncing: { - Header = String.Format (_("Syncing folder ‘{0}’…"), Controller.SyncingFolder); + Header = String.Format (_("Adding project ‘{0}’…"), Controller.SyncingFolder); Description = _("This may take a while." + Environment.NewLine) + _("Are you sure it’s not coffee o'clock?"); - Button button = new Button () { + Button finish_button = new Button () { Sensitive = false, Label = _("Finish") }; - button.Clicked += delegate { - Close (); + Button cancel_button = new Button () { + Label = _("Cancel") }; - AddButton (button); + cancel_button.Clicked += delegate { + Controller.SyncingCancelled (); + }; - this.progress_bar_pulse_timer.Elapsed += delegate { + AddButton (cancel_button); + AddButton (finish_button); + + Controller.UpdateProgressBarEvent += delegate (double percentage) { Application.Invoke (delegate { - progress_bar.Pulse (); + this.progress_bar.Fraction = percentage / 100; }); }; @@ -313,32 +318,59 @@ namespace SparkleShare { Add (bar_wrapper); break; + } - case PageType.Error: - - string n = Environment.NewLine; + case PageType.Error: { Header = _("Something went wrong") + "…"; - Description = "We don't know exactly what the problem is, " + - "but we can try to help you pinpoint it."; + + VBox points = new VBox (false, 0); + Image list_point_one = new Image (SparkleUIHelpers.GetIcon ("list-point", 16)) { }; + Image list_point_two = new Image (SparkleUIHelpers.GetIcon ("list-point", 16)) { }; + Image list_point_three = new Image (SparkleUIHelpers.GetIcon ("list-point", 16)) { }; + + Label label_one = new Label () { + Text = "First, have you tried turning it off and on again?", + Wrap = true, + Xalign = 0 + }; + + Label label_two = new Label () { + Markup = "" + Controller.PreviousUrl + " is the address we've compiled. " + + "Does this look alright?", + Wrap = true, + Xalign = 0 + }; + + Label label_three = new Label () { + Text = "The host needs to know who you are. Did you upload the key that's in " + + "your SparkleShare folder?", + Wrap = true, + Xalign = 0 + }; + + + points.PackStart (new Label ("Please check the following:") { Xalign = 0 }, false, false, 6); + + HBox point_one = new HBox (false, 0); + point_one.PackStart (list_point_one, false, false, 0); + point_one.PackStart (label_one, true, true, 12); + points.PackStart (point_one, false, false, 12); + + HBox point_two = new HBox (false, 0); + point_two.PackStart (list_point_two, false, false, 0); + point_two.PackStart (label_two, true, true, 12); + points.PackStart (point_two, false, false, 12); + + HBox point_three = new HBox (false, 0); + point_three.PackStart (list_point_three, false, false, 0); + point_three.PackStart (label_three, true, true, 12); + points.PackStart (point_three, false, false, 12); + + points.PackStart (new Label (""), true, true, 0); - Label l = new Label ( - "First, have you tried turning it off and on again?" + n + - n + - Controller.SyncingFolder +" is the address we've compiled from the information " + - "you entered. Does this look correct?" + n + - n + - "The host needs to know who you are. Have you uploaded the key that sits in your SparkleShare folder?"); - - - - l.Xpad = 12; - l.Wrap = true; - - - - Button try_again_button = new Button (_("Try Again")) { + Button try_again_button = new Button (_("Try Again…")) { Sensitive = true }; @@ -347,34 +379,36 @@ namespace SparkleShare { }; AddButton (try_again_button); - Add (l); + Add (points); break; + } - case PageType.Finished: + case PageType.Finished: { UrgencyHint = true; if (!HasToplevelFocus) { string title = String.Format (_("‘{0}’ has been successfully added"), Controller.SyncingFolder); - string subtext = _(""); + string subtext = ""; - //TODO new SparkleBubble (title, subtext).Show (); + SparkleUI.Bubbles.Controller.ShowBubble (title, subtext, null); } - Header = _("Folder synced successfully!"); - Description = _("Access the synced files from your SparkleShare folder."); + Header = _("Project successfully added!"); + Description = _("Access the files from your SparkleShare folder."); // A button that opens the synced folder Button open_folder_button = new Button (_("Open Folder")); open_folder_button.Clicked += delegate { - SparkleShare.Controller.OpenSparkleShareFolder (Controller.SyncingFolder); + Program.Controller.OpenSparkleShareFolder (Controller.SyncingFolder); }; Button finish_button = new Button (_("Finish")); finish_button.Clicked += delegate { + Controller.FinishedPageCompleted (); Close (); }; @@ -386,10 +420,116 @@ namespace SparkleShare { break; } + + case PageType.Tutorial: { + + switch (Controller.TutorialPageNumber) { + case 1: { + Header = _("What's happening next?"); + Description = _("SparkleShare creates a special folder in your personal folder " + + "that will keep track of your projects."); + + Button skip_tutorial_button = new Button (_("Skip Tutorial")); + skip_tutorial_button.Clicked += delegate { + Controller.TutorialSkipped (); + }; + + Button continue_button = new Button (_("Continue")); + continue_button.Clicked += delegate { + Controller.TutorialPageCompleted (); + }; + + Image slide = SparkleUIHelpers.GetImage ("tutorial-slide-1.png"); + + Add (slide); + + AddButton (skip_tutorial_button); + AddButton (continue_button); + + break; + } + + case 2: { + Header = _("Sharing files with others"); + Description = _("All files added to your project folders are synced with the host " + + "automatically, as well as with your collaborators."); + + Button continue_button = new Button (_("Continue")); + continue_button.Clicked += delegate { + Controller.TutorialPageCompleted (); + }; + + Image slide = SparkleUIHelpers.GetImage ("tutorial-slide-2.png"); + + Add (slide); + AddButton (continue_button); + + break; + } + + case 3: { + Header = _("The status icon is here to help"); + Description = _("It shows the syncing process status, " + + "and contains links to your projects and the event log."); + + Button continue_button = new Button (_("Continue")); + continue_button.Clicked += delegate { + Controller.TutorialPageCompleted (); + }; + + Image slide = SparkleUIHelpers.GetImage ("tutorial-slide-3.png"); + + Add (slide); + AddButton (continue_button); + + break; + } + + case 4: { + Header = _("Adding projects to SparkleShare"); + Description = _("Just click this button when you see it on the web, and " + + "the project will be automatically added:"); + + Label label = new Label (_("…or select ‘Add Project…’ from the status icon menu " + + "to add one by hand.")) { + Wrap = true, + Xalign = 0, + UseMarkup = true + }; + + Image slide = SparkleUIHelpers.GetImage ("tutorial-slide-4.png"); + + Button add_project_button = new Button (_("Add Project…")); + add_project_button.Clicked += delegate { + Controller.TutorialPageCompleted (); + }; + + Button finish_button = new Button (_("Finish")); + finish_button.Clicked += delegate { + Close (); + }; + + + VBox box = new VBox (false, 0); + box.Add (slide); + box.Add (label); + + Add (box); + + AddButton (add_project_button); + AddButton (finish_button); + + break; + } + } + + break; + } + } + ShowAll (); }); }; - } @@ -398,7 +538,7 @@ namespace SparkleShare { private void CheckSetupPage () { if (NameEntry.Text.Length > 0 && - SparkleShare.Controller.IsValidEmail (EmailEntry.Text)) { + Program.Controller.IsValidEmail (EmailEntry.Text)) { NextButton.Sensitive = true; } else { diff --git a/SparkleShare/SparkleSetupController.cs b/SparkleShare/SparkleSetupController.cs index 1ca4f9cc..af0b141f 100644 --- a/SparkleShare/SparkleSetupController.cs +++ b/SparkleShare/SparkleSetupController.cs @@ -25,7 +25,8 @@ namespace SparkleShare { Add, Syncing, Error, - Finished + Finished, + Tutorial } @@ -33,6 +34,22 @@ namespace SparkleShare { public event ChangePageEventHandler ChangePageEvent; public delegate void ChangePageEventHandler (PageType page); + + public event UpdateProgressBarEventHandler UpdateProgressBarEvent; + public delegate void UpdateProgressBarEventHandler (double percentage); + + + public int TutorialPageNumber { + get { + return this.tutorial_page_number; + } + } + + public string PreviousUrl { + get { + return this.previous_url; + } + } public string PreviousServer { get { @@ -58,11 +75,30 @@ namespace SparkleShare { } } - private string previous_server = ""; - private string previous_folder = ""; - private string syncing_folder = ""; + public string GuessedUserName { + get { + return Program.Controller.UserName; + } + } + + public string GuessedUserEmail { + get { + if (Program.Controller.UserEmail.Equals ("Unknown")) + return ""; + else + return Program.Controller.UserEmail; + } + } + + + private string previous_server = ""; + private string previous_folder = ""; + private string previous_url = ""; + private string syncing_folder = ""; + private int tutorial_page_number = 1; private PageType previous_page; + public SparkleSetupController () { ChangePageEvent += delegate (PageType page) { @@ -94,34 +130,59 @@ namespace SparkleShare { Program.Controller.UpdateState (); if (ChangePageEvent != null) - ChangePageEvent (PageType.Add); + ChangePageEvent (PageType.Tutorial); + } + + + public void TutorialPageCompleted () + { + this.tutorial_page_number++; + + if (ChangePageEvent != null) + ChangePageEvent (PageType.Tutorial); + } + + + public void TutorialSkipped () + { + this.tutorial_page_number = 4; + + if (ChangePageEvent != null) + ChangePageEvent (PageType.Tutorial); } public void AddPageCompleted (string server, string folder_name) { - this.syncing_folder = folder_name; + this.syncing_folder = Path.GetFileNameWithoutExtension (folder_name); this.previous_server = server; this.previous_folder = folder_name; if (ChangePageEvent != null) ChangePageEvent (PageType.Syncing); - Program.Controller.FolderFetched += (target_folder_name) => { - this.syncing_folder = target_folder_name; - + Program.Controller.FolderFetched += delegate { if (ChangePageEvent != null) ChangePageEvent (PageType.Finished); + + this.syncing_folder = ""; }; - Program.Controller.FolderFetchError += delegate { + Program.Controller.FolderFetchError += delegate (string remote_url) { + this.previous_url = remote_url; + if (ChangePageEvent != null) ChangePageEvent (PageType.Error); this.syncing_folder = ""; }; + + Program.Controller.FolderFetching += delegate (double percentage) { + if (UpdateProgressBarEvent != null) + UpdateProgressBarEvent (percentage); + }; - Program.Controller.FetchFolder (server, this.syncing_folder); + Program.Controller.FetchFolder (server, folder_name); } @@ -132,6 +193,15 @@ namespace SparkleShare { } + public void SyncingCancelled () + { + Program.Controller.StopFetcher (); + + if (ChangePageEvent != null) + ChangePageEvent (PageType.Add); + } + + public void FinishedPageCompleted () { this.previous_server = ""; diff --git a/SparkleShare/SparkleSetupWindow.cs b/SparkleShare/SparkleSetupWindow.cs index 68284636..15ec845f 100644 --- a/SparkleShare/SparkleSetupWindow.cs +++ b/SparkleShare/SparkleSetupWindow.cs @@ -23,6 +23,7 @@ using System.Text.RegularExpressions; using System.Timers; using Gtk; +using Mono.Unix; using SparkleLib; namespace SparkleShare { @@ -41,7 +42,7 @@ namespace SparkleShare { public SparkleSetupWindow () : base ("") { - Title = "SparkleShare Setup"; + Title = Catalog.GetString ("SparkleShare Setup"); BorderWidth = 0; IconName = "folder-sparkleshare"; Resizable = false; @@ -70,17 +71,13 @@ namespace SparkleShare { EventBox box = new EventBox (); Gdk.Color bg_color = new Gdk.Color (); - Gdk.Color.Parse ("#2e3336", ref bg_color); + Gdk.Color.Parse ("#000", ref bg_color); box.ModifyBg (StateType.Normal, bg_color); - string image_path = SparkleHelpers.CombineMore (Defines.DATAROOTDIR, "sparkleshare", - "pixmaps", "side-splash.png"); + Image side_splash = SparkleUIHelpers.GetImage ("side-splash.png"); + side_splash.Yalign = 1; - Image side_splash = new Image (image_path) { - Yalign = 1 - }; - - box.Add (side_splash); + box.Add (side_splash); HBox.PackStart (box, false, false, 0); HBox.PackStart (VBox, true, true, 0); @@ -125,7 +122,7 @@ namespace SparkleShare { layout_vertical.PackStart (description, false, false, 21); if (widget != null) - layout_vertical.PackStart (widget, true, true, 21); + layout_vertical.PackStart (widget, true, true, 0); Wrapper.PackStart (layout_vertical, true, true, 0); ShowAll (); @@ -148,9 +145,7 @@ namespace SparkleShare { new public void ShowAll () { - - Present (); - + Present (); base.ShowAll (); } diff --git a/SparkleShare/SparkleShare.csproj b/SparkleShare/SparkleShare.csproj index 079ec8de..199f702d 100644 --- a/SparkleShare/SparkleShare.csproj +++ b/SparkleShare/SparkleShare.csproj @@ -1,5 +1,5 @@ - + Debug AnyCPU @@ -9,6 +9,7 @@ SparkleShare 2.0 SparkleShare + v3.5 true @@ -37,15 +38,9 @@ - - - {2C914413-B31C-4362-93C7-1AE34F09112A} - SparkleLib - - - + @@ -63,11 +58,10 @@ - - + @@ -77,5 +71,7 @@ + + diff --git a/SparkleShare/SparkleShare.sln b/SparkleShare/SparkleShare.sln index 7f23f4cf..9716fc21 100644 --- a/SparkleShare/SparkleShare.sln +++ b/SparkleShare/SparkleShare.sln @@ -1,20 +1,14 @@  -Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual Studio 2005 +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkleShare", "SparkleShare.csproj", "{728483AA-E34B-4441-BF2C-C8BC2901E4E0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkleLib", "..\SparkleLib\SparkleLib.csproj", "{2C914413-B31C-4362-93C7-1AE34F09112A}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2C914413-B31C-4362-93C7-1AE34F09112A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C914413-B31C-4362-93C7-1AE34F09112A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C914413-B31C-4362-93C7-1AE34F09112A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C914413-B31C-4362-93C7-1AE34F09112A}.Release|Any CPU.Build.0 = Release|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -22,7 +16,5 @@ Global EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = SparkleShare.csproj - outputpath = ..\bin - name = SparkleShare EndGlobalSection EndGlobal diff --git a/SparkleShare/SparkleStatusIcon.cs b/SparkleShare/SparkleStatusIcon.cs index c468bc9d..d779684d 100644 --- a/SparkleShare/SparkleStatusIcon.cs +++ b/SparkleShare/SparkleStatusIcon.cs @@ -23,7 +23,7 @@ using System.Timers; using AppIndicator; #endif using Gtk; -using SparkleLib; +using Mono.Unix; namespace SparkleShare { @@ -31,6 +31,8 @@ namespace SparkleShare { // user's notification area public class SparkleStatusIcon { + public SparkleStatusIconController Controller = new SparkleStatusIconController (); + private Timer Animation; private Gdk.Pixbuf [] AnimationFrames; private int FrameNumber; @@ -46,14 +48,14 @@ namespace SparkleShare { // Short alias for the translations public static string _ (string s) { - return s; + return Catalog.GetString (s); } public SparkleStatusIcon () { AnimationFrames = CreateAnimationFrames (); - Animation = CreateAnimation (); + Animation = CreateAnimation (); #if HAVE_APP_INDICATOR this.indicator = new ApplicationIndicator ("sparkleshare", @@ -66,45 +68,63 @@ namespace SparkleShare { this.status_icon.Activate += ShowMenu; // Primary mouse button click this.status_icon.PopupMenu += ShowMenu; // Secondary mouse button click + this.status_icon.Pixbuf = AnimationFrames [0]; #endif - SetNormalState (); + if (Controller.Folders.Length == 0) + StateText = _("Welcome to SparkleShare!"); + else + StateText = _("Up to date") + " " + Controller.FolderSize; + CreateMenu (); - SparkleShare.Controller.FolderSizeChanged += delegate { + Controller.UpdateMenuEvent += delegate (IconState state) { Application.Invoke (delegate { - if (!Animation.Enabled) - SetNormalState (); + switch (state) { + case IconState.Idle: - UpdateMenu (); - }); - }; - - SparkleShare.Controller.FolderListChanged += delegate { - Application.Invoke (delegate { - SetNormalState (); - CreateMenu (); - }); - }; + Animation.Stop (); - SparkleShare.Controller.OnIdle += delegate { - Application.Invoke (delegate { - SetNormalState (); - UpdateMenu (); - }); - }; + if (Controller.Folders.Length == 0) + StateText = _("Welcome to SparkleShare!"); + else + StateText = _("Up to date") + " " + Controller.FolderSize; - SparkleShare.Controller.OnSyncing += delegate { - Application.Invoke (delegate { - SetAnimationState (); - UpdateMenu (); - }); - }; + #if HAVE_APP_INDICATOR + this.indicator.IconName = "process-syncing-sparkleshare-i"; + #else + this.status_icon.Pixbuf = AnimationFrames [0]; + #endif - SparkleShare.Controller.OnError += delegate { - Application.Invoke (delegate { - SetNormalState (true); - UpdateMenu (); + UpdateStateText (); + CreateMenu (); + + break; + + case IconState.Syncing: + + StateText = _("Syncing"); + UpdateStateText (); // TODO + + if (!Animation.Enabled) + Animation.Start (); + + break; + + case IconState.Error: + + StateText = _("Not everything is synced"); + UpdateStateText (); + CreateMenu (); + + #if HAVE_APP_INDICATOR + this.indicator.IconName = "sparkleshare-syncing-error"; + #else + this.status_icon.Pixbuf = SparkleUIHelpers.GetIcon ("sparkleshare-syncing-error", 24); + #endif + + break; + } }); }; } @@ -176,18 +196,18 @@ namespace SparkleShare { }; folder_item.Activated += delegate { - SparkleShare.Controller.OpenSparkleShareFolder (); + Program.Controller.OpenSparkleShareFolder (); }; Menu.Add (folder_item); - if (SparkleShare.Controller.Folders.Count > 0) { + if (Program.Controller.Folders.Count > 0) { // Creates a menu item for each repository with a link to their logs - foreach (string folder_name in SparkleShare.Controller.Folders) { + foreach (string folder_name in Program.Controller.Folders) { Gdk.Pixbuf folder_icon; - if (SparkleShare.Controller.UnsyncedFolders.Contains (folder_name)) { + if (Program.Controller.UnsyncedFolders.Contains (folder_name)) { folder_icon = IconTheme.Default.LoadIcon ("dialog-error", 16, IconLookupFlags.GenericFallback); @@ -205,7 +225,7 @@ namespace SparkleShare { } } else { - MenuItem no_folders_item = new MenuItem (_("No Remote Folders Yet")) { + MenuItem no_folders_item = new MenuItem (_("No projects yet")) { Sensitive = false }; @@ -215,9 +235,9 @@ namespace SparkleShare { Menu.Add (new SeparatorMenuItem ()); // Opens the wizard to add a new remote folder - MenuItem sync_item = new MenuItem (_("Add Remote Folder…")); + MenuItem sync_item = new MenuItem (_("Add Project")); - if (SparkleShare.Controller.FirstRun) + if (Program.Controller.FirstRun) sync_item.Sensitive = false; sync_item.Activated += delegate { @@ -241,7 +261,7 @@ namespace SparkleShare { MenuItem recent_events_item = new MenuItem (_("Show Recent Events")); - if (SparkleShare.Controller.Folders.Count < 1) + if (Program.Controller.Folders.Count < 1) recent_events_item.Sensitive = false; recent_events_item.Activated += delegate { @@ -258,13 +278,13 @@ namespace SparkleShare { MenuItem notify_item; - if (SparkleShare.Controller.NotificationsEnabled) + if (Program.Controller.NotificationsEnabled) notify_item = new MenuItem (_("Turn Notifications Off")); else notify_item = new MenuItem (_("Turn Notifications On")); notify_item.Activated += delegate { - SparkleShare.Controller.ToggleNotifications (); + Program.Controller.ToggleNotifications (); CreateMenu (); }; @@ -291,7 +311,7 @@ namespace SparkleShare { MenuItem quit_item = new MenuItem (_("Quit")); quit_item.Activated += delegate { - SparkleShare.Controller.Quit (); + Program.Controller.Quit (); }; Menu.Add (quit_item); @@ -308,12 +328,12 @@ namespace SparkleShare { private EventHandler OpenFolderDelegate (string name) { return delegate { - SparkleShare.Controller.OpenSparkleShareFolder (name); + Program.Controller.OpenSparkleShareFolder (name); }; } - public void UpdateMenu () + public void UpdateStateText () { ((Menu.Children [0] as MenuItem).Child as Label).Text = StateText; Menu.ShowAll (); @@ -323,11 +343,7 @@ namespace SparkleShare { // Makes the menu visible private void ShowMenu (object o, EventArgs args) { - if ((SparkleBackend.Platform == PlatformID.Unix || - SparkleBackend.Platform == PlatformID.MacOSX)) - Menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime); - else - Menu.Popup (null, null, null, 0, Global.CurrentEventTime); + Menu.Popup (null, null, SetPosition, 0, Global.CurrentEventTime); } @@ -337,63 +353,6 @@ namespace SparkleShare { StatusIcon.PositionMenu (menu, out x, out y, out push_in, this.status_icon.Handle); } #endif - - // The state when there's nothing going on - private void SetNormalState () - { - SetNormalState (false); - } - - - // The state when there's nothing going on - private void SetNormalState (bool error) - { - Animation.Stop (); - - if (SparkleShare.Controller.Folders.Count == 0) { - StateText = _("Welcome to SparkleShare!"); - - Application.Invoke (delegate { - #if HAVE_APP_INDICATOR - this.indicator.IconName = "process-syncing-sparkleshare-i"; - #else - this.status_icon.Pixbuf = AnimationFrames [0]; - #endif - }); - - } else { - if (error) { - StateText = _("Not everything is synced"); - - Application.Invoke (delegate { - #if HAVE_APP_INDICATOR - this.indicator.IconName = "sparkleshare-syncing-error"; - #else - this.status_icon.Pixbuf = SparkleUIHelpers.GetIcon ("sparkleshare-syncing-error", 24); - #endif - }); - } else { - StateText = _("Up to date") + " (" + SparkleShare.Controller.FolderSize + ")"; - Application.Invoke (delegate { - #if HAVE_APP_INDICATOR - this.indicator.IconName = "process-syncing-sparkleshare-i"; - #else - this.status_icon.Pixbuf = AnimationFrames [0]; - #endif - }); - } - } - } - - - // The state when animating - private void SetAnimationState () - { - StateText = _("Syncing…"); - - if (!Animation.Enabled) - Animation.Start (); - } } diff --git a/SparkleShare/SparkleStatusIconController.cs b/SparkleShare/SparkleStatusIconController.cs index 0353c214..6fc54782 100644 --- a/SparkleShare/SparkleStatusIconController.cs +++ b/SparkleShare/SparkleStatusIconController.cs @@ -38,29 +38,29 @@ namespace SparkleShare { public string [] Folders { get { - return SparkleShare.Controller.Folders.ToArray (); + return Program.Controller.Folders.ToArray (); } } public string FolderSize { get { - return SparkleShare.Controller.FolderSize; + return Program.Controller.FolderSize; } } public SparkleStatusIconController () { - SparkleShare.Controller.FolderSizeChanged += delegate { + Program.Controller.FolderSizeChanged += delegate { if (UpdateMenuEvent != null) UpdateMenuEvent (CurrentState); }; - SparkleShare.Controller.FolderListChanged += delegate { + Program.Controller.FolderListChanged += delegate { if (UpdateMenuEvent != null) UpdateMenuEvent (CurrentState); }; - SparkleShare.Controller.OnIdle += delegate { + Program.Controller.OnIdle += delegate { if (CurrentState != IconState.Error) CurrentState = IconState.Idle; @@ -68,14 +68,16 @@ namespace SparkleShare { UpdateMenuEvent (CurrentState); }; - SparkleShare.Controller.OnSyncing += delegate { + Program.Controller.OnSyncing += delegate { CurrentState = IconState.Syncing; + // TODO up down both + if (UpdateMenuEvent != null) UpdateMenuEvent (IconState.Syncing); }; - SparkleShare.Controller.OnError += delegate { + Program.Controller.OnError += delegate { CurrentState = IconState.Error; if (UpdateMenuEvent != null) diff --git a/SparkleShare/SparkleUIHelpers.cs b/SparkleShare/SparkleUIHelpers.cs index 4786be01..2ad40ff0 100644 --- a/SparkleShare/SparkleUIHelpers.cs +++ b/SparkleShare/SparkleUIHelpers.cs @@ -40,14 +40,20 @@ namespace SparkleShare { public static Gdk.Pixbuf GetIcon (string name, int size) { IconTheme icon_theme = new IconTheme (); - icon_theme.AppendSearchPath (SparklePaths.SparkleIconPath); - icon_theme.AppendSearchPath (SparklePaths.SparkleLocalIconPath); + + icon_theme.AppendSearchPath ( + Path.Combine (SparkleUI.AssetsPath, "icons")); + + icon_theme.AppendSearchPath ( + Path.Combine (SparkleConfig.ConfigPath, "icons")); try { return icon_theme.LoadIcon (name, size, IconLookupFlags.GenericFallback); + } catch { try { return icon_theme.LoadIcon ("gtk-missing-image", size, IconLookupFlags.GenericFallback); + } catch { return null; } @@ -55,6 +61,15 @@ namespace SparkleShare { } + public static Image GetImage (string name) + { + string image_path = SparkleHelpers.CombineMore (Defines.DATAROOTDIR, "sparkleshare", + "pixmaps", name); + + return new Image (image_path); + } + + // Converts a Gdk RGB color to a hex value. // Example: from "rgb:0,0,0" to "#000000" public static string GdkColorToHex (Gdk.Color color) diff --git a/SparkleShare/Windows/Program.cs b/SparkleShare/Windows/Program.cs index 54a104d0..50e42c1c 100644 --- a/SparkleShare/Windows/Program.cs +++ b/SparkleShare/Windows/Program.cs @@ -22,13 +22,16 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; +#if __MonoCS__ +using Mono.Unix; +//using Mono.Unix.Native; +#endif using SparkleLib; using SparkleLib.Options; namespace SparkleShare { // This is SparkleShare! - // http://blogs.msdn.com/b/ericlippert/archive/2010/03/09/do-not-name-a-class-the-same-as-its-namespace-part-one.aspx public class Program { public static SparkleController Controller; @@ -38,22 +41,18 @@ namespace SparkleShare { // Short alias for the translations public static string _ (string s) { +#if __MonoCS__ + return Catalog.GetString (s); +#else return s; +#endif } +#if !__MonoCS__ [STAThread] +#endif public static void Main (string [] args) { - // Don't allow running as root on Linux or Mac -#if __MonoCS__ - if (new Mono.Unix.UnixUserInfo (Mono.Unix.UnixEnvironment.UserName).UserId == 0) { - - Console.WriteLine (_("Sorry, you can't run SparkleShare with these permissions.")); - Console.WriteLine (_("Things would go utterly wrong.")); - Environment.Exit (-1); - } -#endif - // Parse the command line options bool show_help = false; OptionSet option_set = new OptionSet () { @@ -73,25 +72,10 @@ namespace SparkleShare { if (show_help) ShowHelp (option_set); - // Load the right controller for the OS - string controller_name = "Lin"; - switch (SparkleBackend.Platform) { - case PlatformID.Unix: - SetProcessName ("sparkleshare"); - break; - case PlatformID.MacOSX: - controller_name = "Mac"; - break; - case PlatformID.Win32NT: - controller_name = "Win"; - break; - } // Initialize the controller this way so that // there aren't any exceptions in the OS specific UI's - Controller = (SparkleController) Activator.CreateInstance ( - Type.GetType ("SparkleShare.Sparkle" + controller_name + "Controller")); - + Controller = new SparkleController (); Controller.Initialize (); if (Controller != null) { diff --git a/SparkleShare/Windows/SparkleWinController.cs b/SparkleShare/Windows/SparkleController.cs similarity index 98% rename from SparkleShare/Windows/SparkleWinController.cs rename to SparkleShare/Windows/SparkleController.cs index 202cc4e9..73f063f2 100644 --- a/SparkleShare/Windows/SparkleWinController.cs +++ b/SparkleShare/Windows/SparkleController.cs @@ -28,9 +28,9 @@ using CefSharp; namespace SparkleShare { - public class SparkleWinController : SparkleController { + public class SparkleController : SparkleControllerBase { - public SparkleWinController () : base () + public SparkleController () : base () { } diff --git a/SparkleShare/Windows/SparkleShare.csproj b/SparkleShare/Windows/SparkleShare.csproj index e23c68c6..1a53f228 100644 --- a/SparkleShare/Windows/SparkleShare.csproj +++ b/SparkleShare/Windows/SparkleShare.csproj @@ -71,6 +71,12 @@ True GlobalAssemblyInfo.tt
+ + SparkleControllerBase.cs + + + SparkleExtensions.cs + @@ -104,8 +110,8 @@ - + Form @@ -121,7 +127,6 @@ - Component diff --git a/SparkleShare/sparkleshare.in b/SparkleShare/sparkleshare.in index 88302a5c..3e71f404 100644 --- a/SparkleShare/sparkleshare.in +++ b/SparkleShare/sparkleshare.in @@ -1,5 +1,10 @@ #!/bin/bash +if [[ $UID -eq 0 ]]; then + echo "Cannot run as root. Things would go utterly wrong." + exit 1 +fi + if [ "$XDG_RUNTIME_DIR" ]; then pidfile=${XDG_RUNTIME_DIR}/sparkleshare.pid else