diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 2d9ee5ea..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "SmartIrc4net"] - path = SmartIrc4net - url = git://git.qnetp.net/smartirc4net.git diff --git a/Makefile.am b/Makefile.am index 1ca9d1da..b4293c03 100755 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -basedirs = build help SmartIrc4net SparkleLib data po +basedirs = build help SparkleLib data po SUBDIRS = $(basedirs) $(GUISUBDIRS) DIST_SUBDIRS = $(basedirs) SparkleShare diff --git a/SmartIrc4net b/SmartIrc4net deleted file mode 160000 index 036e2233..00000000 --- a/SmartIrc4net +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 036e2233aea9bd3b4a30f8c5136daff0187eb5ec diff --git a/SparkleLib/Git/SparkleFetcherGit.cs b/SparkleLib/Git/SparkleFetcherGit.cs index 36b6a45c..f9f78030 100755 --- a/SparkleLib/Git/SparkleFetcherGit.cs +++ b/SparkleLib/Git/SparkleFetcherGit.cs @@ -137,6 +137,30 @@ namespace SparkleLib { } + public override string [] Warnings { + get { + SparkleGit git = new SparkleGit (SparkleConfig.DefaultConfig.TmpPath, + "config --global core.excludesfile"); + + git.Start (); + + // Reading the standard output HAS to go before + // WaitForExit, or it will hang forever on output > 4096 bytes + string output = git.StandardOutput.ReadToEnd ().Trim (); + git.WaitForExit (); + + if (string.IsNullOrEmpty (output)) { + return null; + + } else { + return new string [] { + string.Format ("You seem to have configured a system ‘gitignore’ file. " + + "This may interfere with SparkleShare.\n({0})", output) + }; + } + } + } + public override void Stop () { if (this.git != null) { diff --git a/SparkleLib/Hg/SparkleFetcherHg.cs b/SparkleLib/Hg/SparkleFetcherHg.cs deleted file mode 100755 index d89a3243..00000000 --- a/SparkleLib/Hg/SparkleFetcherHg.cs +++ /dev/null @@ -1,172 +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 SparkleFetcherHg : SparkleFetcherBase { - - public SparkleFetcherHg (string server, string remote_folder, string target_folder) : - base (server, remote_folder, target_folder) { } - - - public override bool Fetch () - { - SparkleHg hg = new SparkleHg (SparklePaths.SparkleTmpPath, - "clone \"" + base.remote_url + "\" " + "\"" + base.target_folder + "\""); - - hg.Start (); - hg.WaitForExit (); - - SparkleHelpers.DebugInfo ("Hg", "Exit code " + hg.ExitCode.ToString ()); - - if (hg.ExitCode != 0) { - return false; - } else { - InstallConfiguration (); - InstallExcludeRules (); - return true; - } - } - - - // Install the user's name and email and some config into - // the newly cloned repository - private void InstallConfiguration () - { - string global_config_file_path = Path.Combine (SparklePaths.SparkleConfigPath, "config.xml"); - - if (!File.Exists (global_config_file_path)) - return; - - string repo_config_file_path = SparkleHelpers.CombineMore (base.target_folder, ".hg", "hgrc"); - string config = String.Join (Environment.NewLine, File.ReadAllLines (repo_config_file_path)); - - // Add user info - string n = Environment.NewLine; - XmlDocument xml = new XmlDocument(); - xml.Load (global_config_file_path); - - XmlNode node_name = xml.SelectSingleNode ("//user/name/text()"); - XmlNode node_email = xml.SelectSingleNode ("//user/email/text()"); - - // TODO this ignore duplicate names (FolderName (2)) - string ignore_file_path = base.target_folder.Replace (SparklePaths.SparkleTmpPath, - SparklePaths.SparklePath); - - ignore_file_path = SparkleHelpers.CombineMore (ignore_file_path, ".hg", "hgignore"); - - config += n + - "[ui]" + n + - "username = " + node_name.Value + " <" + node_email.Value + ">" + n + - "ignore = " + ignore_file_path + n; - - // Write the config to the file - TextWriter writer = new StreamWriter (repo_config_file_path); - writer.WriteLine (config); - writer.Close (); - - string style_file_path = SparkleHelpers.CombineMore (base.target_folder, ".hg", "log.style"); - - string style = "changeset = \"{file_mods}{file_adds}{file_dels}\"" + n + - "file_add = \"A {file_add}\\n\"" + n + - "file_mod = \"M {file_mod}\\n\"" + n + - "file_del = \"D {file_del}\\n\"" + n; - - writer = new StreamWriter (style_file_path); - writer.WriteLine (style); - writer.Close (); - - SparkleHelpers.DebugInfo ("Config", "Added configuration to '" + repo_config_file_path + "'"); - } - - - // Add a .gitignore file to the repo - private void InstallExcludeRules () - { - string exlude_rules_file_path = SparkleHelpers.CombineMore ( - this.target_folder, ".hg", "hgignore"); - - TextWriter writer = new StreamWriter (exlude_rules_file_path); - - writer.WriteLine ("syntax: glob"); - - // gedit and emacs - writer.WriteLine ("*~"); - - // vi(m) - writer.WriteLine (".*.sw[a-z]"); - writer.WriteLine ("*.un~"); - writer.WriteLine ("*.swp"); - writer.WriteLine ("*.swo"); - - // KDE - writer.WriteLine (".directory"); - - // Mac OSX - writer.WriteLine (".DS_Store"); - writer.WriteLine ("Icon?"); - writer.WriteLine ("._*"); - writer.WriteLine (".Spotlight-V100"); - writer.WriteLine (".Trashes"); - - // Mac OSX - writer.WriteLine ("*(Autosaved).graffle"); - - // Windows - writer.WriteLine ("Thumbs.db"); - writer.WriteLine ("Desktop.ini"); - - // CVS - writer.WriteLine ("*/CVS/*"); - writer.WriteLine (".cvsignore"); - writer.WriteLine ("*/.cvsignore"); - - // Subversion - writer.WriteLine ("/.svn/*"); - writer.WriteLine ("*/.svn/*"); - - writer.Close (); - } - } - - - public class SparkleHg : Process { - - public SparkleHg (string path, string args) : base () - { - EnableRaisingEvents = true; - StartInfo.FileName = "/opt/local/bin/hg"; - StartInfo.Arguments = args; - StartInfo.RedirectStandardOutput = true; - StartInfo.UseShellExecute = false; - StartInfo.WorkingDirectory = path; - } - - - new public void Start () - { - SparkleHelpers.DebugInfo ("Cmd", StartInfo.FileName + " " + StartInfo.Arguments); - base.Start (); - } - } -} diff --git a/SparkleLib/Hg/SparkleRepoHg.cs b/SparkleLib/Hg/SparkleRepoHg.cs deleted file mode 100755 index 1d0cdb8b..00000000 --- a/SparkleLib/Hg/SparkleRepoHg.cs +++ /dev/null @@ -1,319 +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 SparkleRepoHg : SparkleRepoBase { - - public SparkleRepoHg (string path, SparkleBackend backend) : - base (path, backend) { } - - - public override string Identifier { - get { - SparkleHg hg = new SparkleHg (LocalPath, "log -r : --limit 1 --template \"{node}\""); - hg.Start (); - hg.WaitForExit (); - - return hg.StandardOutput.ReadToEnd (); - } - } - - - public override string CurrentRevision { - get { - SparkleHg hg = new SparkleHg (LocalPath, "log --limit 1 --template \"{node}\""); - hg.Start (); - hg.WaitForExit (); - - string hash = hg.StandardOutput.ReadToEnd ().Trim (); - if (hash.Length > 0) - return hash; - else - return null; - } - } - - - public override bool CheckForRemoteChanges () - { - return true; // Mercurial doesn't have a way to check for the remote hash - } - - - public override bool SyncUp () - { - Add (); - - string message = FormatCommitMessage (); - Commit (message); - - SparkleHg hg = new SparkleHg (LocalPath, "push"); - - hg.Start (); - hg.WaitForExit (); - - if (hg.ExitCode == 0) { - return true; - } else { - return false; - } - } - - - public override bool SyncDown () - { - SparkleHg hg = new SparkleHg (LocalPath, "pull"); - - hg.Start (); - hg.WaitForExit (); - - if (hg.ExitCode == 0) { - Merge (); - return true; - } else { - return false; - } - } - - - public override bool AnyDifferences { - get { - SparkleHg hg = new SparkleHg (LocalPath, "status"); - hg.Start (); - hg.WaitForExit (); - - string output = hg.StandardOutput.ReadToEnd ().TrimEnd (); - string [] lines = output.Split ("\n".ToCharArray ()); - - foreach (string line in lines) { - if (line.Length > 1 && !line [1].Equals (" ")) - return true; - } - - return false; - } - } - - - public override bool HasUnsyncedChanges { - get { - string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath, - ".hg", "has_unsynced_changes"); - - return File.Exists (unsynced_file_path); - } - - set { - string unsynced_file_path = SparkleHelpers.CombineMore (LocalPath, - ".hg", "has_unsynced_changes"); - - if (value) { - if (!File.Exists (unsynced_file_path)) - File.Create (unsynced_file_path).Close (); - } else { - File.Delete (unsynced_file_path); - } - } - } - - - // Stages the made changes - private void Add () - { - SparkleHg hg = new SparkleHg (LocalPath, "addremove --quiet"); - hg.Start (); - hg.WaitForExit (); - - SparkleHelpers.DebugInfo ("Hg", "[" + Name + "] Changes staged"); - } - - - // Commits the made changes - private void Commit (string message) - { - if (!AnyDifferences) - return; - - SparkleHg hg = new SparkleHg (LocalPath, "commit -m '" + message + "'"); - hg.Start (); - hg.WaitForExit (); - - SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message); - } - - - // Merges the fetched changes - private void Merge () - { - DisableWatching (); - - if (AnyDifferences) { - Add (); - - string commit_message = FormatCommitMessage (); - Commit (commit_message); - } - - SparkleHg hg = new SparkleHg (LocalPath, "update"); - - hg.Start (); - hg.WaitForExit (); - - EnableWatching (); - } - - - // Returns a list of the latest change sets - public override List GetChangeSets (int count) - { - if (count < 1) - count = 30; - - List change_sets = new List (); - - SparkleHg hg_log = new SparkleHg (LocalPath, "log --limit " + count + " --style changelog --verbose --stat"); - Console.OutputEncoding = System.Text.Encoding.Unicode; - hg_log.Start (); - - // Reading the standard output HAS to go before - // WaitForExit, or it will hang forever on output > 4096 bytes - string output = hg_log.StandardOutput.ReadToEnd (); - hg_log.WaitForExit (); - - string [] lines = output.Split ("\n".ToCharArray ()); - List entries = new List (); - - int j = 0; - string entry = "", last_entry = ""; - foreach (string line in lines) { - if (line.StartsWith ("2") && line.EndsWith (")") && j > 0) { - entries.Add (entry); - entry = ""; - } - - entry += line + "\n"; - j++; - - last_entry = entry; - } - - entries.Add (last_entry); - - Regex regex = new Regex (@"([0-9]{4})-([0-9]{2})-([0-9]{2}).*([0-9]{2}):([0-9]{2}).*.([0-9]{4})" + - "(.+)<(.+)>.*.([a-z0-9]{12})", RegexOptions.Compiled); - - foreach (string log_entry in entries) { - - bool is_merge_commit = false; - - Match match = regex.Match (log_entry); - - if (!match.Success) - continue; - - SparkleChangeSet change_set = new SparkleChangeSet () { - Revision = match.Groups [9].Value, - 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); - - string [] entry_lines = log_entry.Split ("\n".ToCharArray ()); - - foreach (string entry_line in entry_lines) { - if (!entry_line.StartsWith ("\t* ")) - continue; - - if (entry_line.EndsWith ("new file.")) { - string files = entry_line.Substring (3, entry_line.Length - 13); - string [] added_files = files.Split (",".ToCharArray ()); - - foreach (string added_file in added_files) { - string file = added_file.TrimEnd (": ".ToCharArray ()); - change_set.Added.Add (file); - } - - } else if (entry_line.EndsWith ("deleted file.")) { - string files = entry_line.Substring (3, entry_line.Length - 17); - string [] deleted_files = files.Split (",".ToCharArray ()); - - foreach (string deleted_file in deleted_files) { - string file = deleted_file.TrimEnd (": ".ToCharArray ()); - change_set.Deleted.Add (file); - } - - } else if (!"".Equals (entry_line.Trim ())){ - string files = entry_line.Substring (3); - files = files.TrimEnd (":".ToCharArray()); - string [] edited_files = files.Split (",".ToCharArray ()); - - foreach (string edited_file in edited_files) { - if (!change_set.Added.Contains (edited_file) && - !change_set.Deleted.Contains (edited_file)) { - - change_set.Edited.Add (edited_file); - } - } - } - } - - change_sets.Add (change_set); - } - - return change_sets; - } - - - // Creates a pretty commit message based on what has changed - private string FormatCommitMessage () // TODO - { - return "SparkleShare Hg"; - } - - - public override void CreateInitialChangeSet () - { - base.CreateInitialChangeSet (); - Add (); - - string message = FormatCommitMessage (); - Commit (message); - } - - - public override bool UsesNotificationCenter - { - get { - string file_path = SparkleHelpers.CombineMore (LocalPath, ".hg", "disable_notification_center"); - return !File.Exists (file_path); - } - } - } -} diff --git a/SparkleLib/Makefile.am b/SparkleLib/Makefile.am index 6356372a..b696aa1b 100755 --- a/SparkleLib/Makefile.am +++ b/SparkleLib/Makefile.am @@ -1,12 +1,6 @@ ASSEMBLY = SparkleLib TARGET = library -if HAVE_SMARTIRC4NET -LINK = $(REF_SPARKLELIB) $(LINK_SMARTIRC4NET_SYSTEM) -else -LINK = $(REF_SPARKLELIB) $(LINK_SMARTIRC4NET_LOCAL) -endif - SOURCES = \ Defines.cs \ Git/SparkleFetcherGit.cs \ @@ -17,17 +11,11 @@ SOURCES = \ SparkleFetcherBase.cs \ SparkleHelpers.cs \ SparkleListenerBase.cs \ - SparkleListenerIrc.cs \ SparkleListenerTcp.cs \ - SparkleOptions.cs \ SparkleRepoBase.cs \ SparkleWatcher.cs -SMARTIRC4NET_FILES_EXPANDED = $(foreach file, $(SMARTIRC4NET_FILES), $(top_builddir)/$(file)) - -EXTRA_BUNDLE = $(SMARTIRC4NET_FILES_EXPANDED) - install-data-hook: for ASM in $(EXTRA_BUNDLE); do \ $(INSTALL) -m 0755 $$ASM $(DESTDIR)$(moduledir); \ diff --git a/SparkleLib/SparkleChangeSet.cs b/SparkleLib/SparkleChangeSet.cs index 5496e44e..a24faaaa 100755 --- a/SparkleLib/SparkleChangeSet.cs +++ b/SparkleLib/SparkleChangeSet.cs @@ -102,7 +102,7 @@ namespace SparkleLib { public class SparkleFolder { public string Name; - // TODO: Uri + public Uri RemoteAddress; public string FullPath { get { diff --git a/SparkleLib/SparkleConfig.cs b/SparkleLib/SparkleConfig.cs index 86f869c5..ed194027 100755 --- a/SparkleLib/SparkleConfig.cs +++ b/SparkleLib/SparkleConfig.cs @@ -19,8 +19,9 @@ using System; using System.IO; using System.Collections.Generic; using System.Xml; +using System.Security.Principal; -using Mono.Unix; +//using Mono.Unix; namespace SparkleLib { @@ -98,11 +99,11 @@ namespace SparkleLib { if (SparkleBackend.Platform == PlatformID.Unix || SparkleBackend.Platform == PlatformID.MacOSX) { - user_name = new UnixUserInfo (UnixEnvironment.UserName).RealName; + user_name = Environment.UserName; if (string.IsNullOrEmpty (user_name)) - user_name = UnixEnvironment.UserName; + user_name = ""; else - user_name = user_name.TrimEnd (",".ToCharArray()); + user_name = user_name.TrimEnd (",".ToCharArray ()); } else { user_name = Environment.UserName; diff --git a/SparkleLib/SparkleFetcherBase.cs b/SparkleLib/SparkleFetcherBase.cs index bc2adf2d..6e01c748 100755 --- a/SparkleLib/SparkleFetcherBase.cs +++ b/SparkleLib/SparkleFetcherBase.cs @@ -18,10 +18,11 @@ using System; using System.IO; using System.Diagnostics; +using System.Security.AccessControl; using System.Text.RegularExpressions; using System.Threading; -using Mono.Unix; +//using Mono.Unix; namespace SparkleLib { @@ -29,7 +30,7 @@ namespace SparkleLib { public abstract class SparkleFetcherBase { public delegate void StartedEventHandler (); - public delegate void FinishedEventHandler (); + public delegate void FinishedEventHandler (string [] warnings); public delegate void FailedEventHandler (); public delegate void ProgressChangedEventHandler (double percentage); @@ -52,7 +53,7 @@ namespace SparkleLib { public abstract bool Fetch (); - + public abstract string [] Warnings { get; } // Clones the remote repository public void Start () @@ -83,7 +84,7 @@ namespace SparkleLib { EnableHostKeyCheckingForHost (host); if (Finished != null) - Finished (); + Finished (Warnings); } else { SparkleHelpers.DebugInfo ("Fetcher", "Failed"); @@ -157,9 +158,9 @@ namespace SparkleLib { File.WriteAllText (ssh_config_file_path, ssh_config); } - UnixFileSystemInfo file_info = new UnixFileInfo (ssh_config_file_path); - file_info.FileAccessPermissions = (FileAccessPermissions.UserRead | - FileAccessPermissions.UserWrite); + //UnixFileSystemInfo file_info = new UnixFileInfo (ssh_config_file_path); + //file_info.FileAccessPermissions = (FileAccessPermissions.UserRead | + // FileAccessPermissions.UserWrite); TODO SparkleHelpers.DebugInfo ("Fetcher", "Disabled host key checking " + host); } @@ -169,8 +170,8 @@ namespace SparkleLib { { string path = SparkleConfig.DefaultConfig.HomePath; - if (!(SparkleBackend.Platform == PlatformID.Unix || - SparkleBackend.Platform == PlatformID.MacOSX)) { + if (SparkleBackend.Platform != PlatformID.Unix && + SparkleBackend.Platform != PlatformID.MacOSX) { path = Environment.ExpandEnvironmentVariables ("%HOMEDRIVE%%HOMEPATH%"); } @@ -208,9 +209,9 @@ namespace SparkleLib { } else { File.WriteAllText (ssh_config_file_path, new_ssh_config.Trim ()); - UnixFileSystemInfo file_info = new UnixFileInfo (ssh_config_file_path); - file_info.FileAccessPermissions = (FileAccessPermissions.UserRead | - FileAccessPermissions.UserWrite); + //UnixFileSystemInfo file_info = new UnixFileInfo (ssh_config_file_path); + //file_info.FileAccessPermissions = (FileAccessPermissions.UserRead | + // FileAccessPermissions.UserWrite); TODO } } diff --git a/SparkleLib/SparkleLib.csproj b/SparkleLib/SparkleLib.csproj index 8e18cd24..a0ac9494 100755 --- a/SparkleLib/SparkleLib.csproj +++ b/SparkleLib/SparkleLib.csproj @@ -31,10 +31,6 @@ - - False - ..\bin\Meebey.SmartIrc4net.dll - diff --git a/SparkleLib/SparkleListenerBase.cs b/SparkleLib/SparkleListenerBase.cs index bba5b171..1770c04a 100755 --- a/SparkleLib/SparkleListenerBase.cs +++ b/SparkleLib/SparkleListenerBase.cs @@ -75,9 +75,6 @@ namespace SparkleLib { 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; diff --git a/SparkleLib/SparkleListenerIrc.cs b/SparkleLib/SparkleListenerIrc.cs deleted file mode 100755 index 9f9a79cb..00000000 --- a/SparkleLib/SparkleListenerIrc.cs +++ /dev/null @@ -1,219 +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.Text; -using System.Threading; -using System.Security.Cryptography; - -using Meebey.SmartIrc4net; - -namespace SparkleLib { - - public class SparkleListenerIrc : SparkleListenerBase { - - private Thread thread; - private IrcClient client; - private string nick; - private string announcements_password; - private bool allow_passwordless_join; - - - public SparkleListenerIrc (Uri server, string folder_identifier) : - base (server, folder_identifier) - { - // Try to get a uniqueish nickname - this.nick = SHA1 (DateTime.Now.ToString ("ffffff") + "sparkles"); - - // Most irc servers don't allow nicknames starting - // with a number, so prefix an alphabetic character - this.nick = "s" + this.nick.Substring (0, 7); - - // Optional password to make the channel access more safe - this.announcements_password = - SparkleConfig.DefaultConfig.GetConfigOption ("announcements_password"); - - // Option to allow access to channel when no password is defined - try { - string option = SparkleConfig.DefaultConfig.GetConfigOption ("allow_passwordless_join"); - this.allow_passwordless_join = (option == null || Convert.ToBoolean (option)); - } catch (Exception) { - this.allow_passwordless_join = true; - } - - base.channels.Add ("#" + folder_identifier); - - this.client = new IrcClient () { - PingTimeout = 180, - 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 (proxy_uri.Scheme == "http") { - this.client.ProxyType = ProxyType.Http; - this.client.ProxyHost = proxy_uri.Host; - this.client.ProxyPort = proxy_uri.Port; - } - } - - this.client.OnConnected += delegate { - base.is_connecting = false; - OnConnected (); - }; - - this.client.OnDisconnected += delegate { - base.is_connecting = false; - OnDisconnected (); - }; - - this.client.OnError += delegate { - base.is_connecting = false; - OnDisconnected (); - }; - - this.client.OnChannelMessage += delegate (object o, IrcEventArgs args) { - string message = args.Data.Message.Trim (); - string folder_id = args.Data.Channel.Substring (1); // remove the starting hash - OnAnnouncement (new SparkleAnnouncement (folder_id, message)); - }; - } - - - public override bool IsConnected { - get { - return this.client.IsConnected; - } - } - - - // Starts a new thread and listens to the channel - public override void Connect () - { - SparkleHelpers.DebugInfo ("ListenerIrc", "Connecting to " + Server); - - base.is_connecting = true; - - this.thread = new Thread ( - new ThreadStart (delegate { - try { - // Connect, login, and join the channel - int port = base.server.Port; - - if (port < 0) - port = 6667; - - this.client.Connect (base.server.Host, port); - this.client.Login (this.nick, this.nick, 8, this.nick); - - foreach (string channel in base.channels) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Joining channel " + channel); - - if (!string.IsNullOrEmpty (this.announcements_password)) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Password set to access the channel"); - this.client.RfcJoin (channel, this.announcements_password); - this.client.RfcMode (channel, "+k " + this.announcements_password); - - } else { - if (this.allow_passwordless_join) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Accessing unprotected channel, change the setting to not access"); - this.client.RfcJoin (channel); - - } else { - SparkleHelpers.DebugInfo ("ListenerIrc", "Unprotected channel, change the setting to access"); - base.is_connecting = false; - OnDisconnected (); - throw new ConnectionException ("Unprotected channel, change the setting to access"); - } - } - - this.client.RfcMode (channel, "+s"); - } - - // List to the channel, this blocks the thread - this.client.Listen (); - - // Disconnect when we time out - this.client.Disconnect (); - - } catch (ConnectionException e) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Could not connect to " + Server + ": " + e.Message); - } - }) - ); - - this.thread.Start (); - } - - - public override void AlsoListenTo (string folder_identifier) - { - string channel = "#" + folder_identifier; - if (!base.channels.Contains (channel)) { - base.channels.Add (channel); - - if (IsConnected) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Joining channel " + channel); - if (this.announcements_password != null) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Password set to access the channel"); - this.client.RfcJoin (channel, this.announcements_password); - this.client.RfcMode (channel, "+k " + this.announcements_password); - } else { - if (allow_passwordless_join) { - SparkleHelpers.DebugInfo ("ListenerIrc", "Accessing a dangerous channel change the setting to not access"); - this.client.RfcJoin (channel); - } else { - SparkleHelpers.DebugInfo ("ListenerIrc", "Dangerous channel, change the setting to access"); - } - } - this.client.RfcMode (channel, "+s"); - } - } - } - - - public override void Announce (SparkleAnnouncement announcement) - { - string channel = "#" + announcement.FolderIdentifier; - this.client.SendMessage (SendType.Message, channel, announcement.Message); - - // Also announce to ourselves for debugging purposes - // base.OnAnnouncement (announcement); - } - - - public override void Dispose () - { - this.thread.Abort (); - this.thread.Join (); - base.Dispose (); - } - - - // Creates a SHA-1 hash of input - private string SHA1 (string s) - { - SHA1 sha1 = new SHA1CryptoServiceProvider (); - Byte[] bytes = ASCIIEncoding.Default.GetBytes (s); - Byte[] encoded_bytes = sha1.ComputeHash (bytes); - return BitConverter.ToString (encoded_bytes).ToLower ().Replace ("-", ""); - } - } -} diff --git a/SparkleLib/SparkleRepoBase.cs b/SparkleLib/SparkleRepoBase.cs index 1682b0d5..f9f8087e 100755 --- a/SparkleLib/SparkleRepoBase.cs +++ b/SparkleLib/SparkleRepoBase.cs @@ -72,7 +72,7 @@ namespace SparkleLib { public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set); public event NewChangeSetEventHandler NewChangeSet; - public delegate void NewNoteEventHandler (string user_name, string user_email); + public delegate void NewNoteEventHandler (SparkleUser user); public event NewNoteEventHandler NewNote; public delegate void ConflictResolvedEventHandler (); @@ -482,7 +482,7 @@ namespace SparkleLib { foreach (string added in change_set.Added) { if (added.Contains (".notes")) { if (NewNote != null) - NewNote (change_set.User.Name, change_set.User.Email); + NewNote (change_set.User); note_added = true; break; diff --git a/SparkleShare/Mac/SparkleEventLog.cs b/SparkleShare/Mac/SparkleEventLog.cs index 7cb344d7..17d7056c 100755 --- a/SparkleShare/Mac/SparkleEventLog.cs +++ b/SparkleShare/Mac/SparkleEventLog.cs @@ -193,7 +193,7 @@ namespace SparkleShare { public override void DecidePolicyForNavigation (WebView web_view, NSDictionary action_info, NSUrlRequest request, WebFrame frame, NSObject decision_token) { - Controller.LinkClicked (request.Url.ToString ()); + SparkleEventLogController.LinkClicked (request.Url.ToString ()); } } } diff --git a/SparkleShare/Mac/SparkleSetup.cs b/SparkleShare/Mac/SparkleSetup.cs index f03cc37b..c48c26b0 100755 --- a/SparkleShare/Mac/SparkleSetup.cs +++ b/SparkleShare/Mac/SparkleSetup.cs @@ -16,11 +16,9 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.IO; -using System.Timers; - -using System.Collections.Generic; using Mono.Unix; using MonoMac.Foundation; @@ -54,7 +52,9 @@ namespace SparkleShare { private NSTextField PathLabel; private NSTextField PathHelpLabel; private NSTextField AddProjectTextField; - private Timer timer; + private NSTextField WarningTextField; + private NSImage WarningImage; + private NSImageView WarningImageView; private NSTableView TableView; private NSScrollView ScrollView; private NSTableColumn IconColumn; @@ -64,7 +64,7 @@ namespace SparkleShare { public SparkleSetup () : base () { - Controller.ChangePageEvent += delegate (PageType type) { + Controller.ChangePageEvent += delegate (PageType type, string [] warnings) { InvokeOnMainThread (delegate { Reset (); @@ -87,8 +87,9 @@ namespace SparkleShare { }; FullNameTextField = new NSTextField () { - Frame = new RectangleF (330, Frame.Height - 238, 196, 22), - StringValue = Controller.GuessedUserName + Frame = new RectangleF (330, Frame.Height - 238, 196, 22), + StringValue = Controller.GuessedUserName, + Delegate = new SparkleTextFieldDelegate () }; EmailLabel = new NSTextField () { @@ -102,42 +103,45 @@ namespace SparkleShare { }; EmailTextField = new NSTextField () { - Frame = new RectangleF (330, Frame.Height - 268, 196, 22), - StringValue = Controller.GuessedUserEmail + Frame = new RectangleF (330, Frame.Height - 268, 196, 22), + StringValue = Controller.GuessedUserEmail, + Delegate = new SparkleTextFieldDelegate () }; - // TODO: Ugly hack, do properly with events - timer = new Timer () { - Interval = 50 + + (FullNameTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate { + Controller.CheckSetupPage ( + FullNameTextField.StringValue, + EmailTextField.StringValue + ); }; + (EmailTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate { + Controller.CheckSetupPage ( + FullNameTextField.StringValue, + EmailTextField.StringValue + ); + }; + + ContinueButton = new NSButton () { Title = "Continue", Enabled = false }; ContinueButton.Activated += delegate { - timer.Stop (); - timer = null; - string full_name = FullNameTextField.StringValue.Trim (); string email = EmailTextField.StringValue.Trim (); Controller.SetupPageCompleted (full_name, email); }; - timer.Elapsed += delegate { + Controller.UpdateSetupContinueButtonEvent += delegate (bool button_enabled) { InvokeOnMainThread (delegate { - bool name_is_valid = !FullNameTextField.StringValue.Trim ().Equals (""); - - bool email_is_valid = Program.Controller.IsValidEmail ( - EmailTextField.StringValue.Trim ()); - - ContinueButton.Enabled = (name_is_valid && email_is_valid); + ContinueButton.Enabled = button_enabled; }); }; - timer.Start (); ContentView.AddSubview (FullNameLabel); ContentView.AddSubview (FullNameTextField); @@ -168,7 +172,8 @@ namespace SparkleShare { Frame = new RectangleF (190, Frame.Height - 336, 196, 22), Font = SparkleUI.Font, StringValue = Controller.PreviousAddress, - Enabled = (Controller.SelectedPlugin.Address == null) + Enabled = (Controller.SelectedPlugin.Address == null), + Delegate = new SparkleTextFieldDelegate () }; @@ -186,12 +191,12 @@ namespace SparkleShare { Frame = new RectangleF (190 + 196 + 16, Frame.Height - 336, 196, 22), StringValue = Controller.PreviousPath, Enabled = (Controller.SelectedPlugin.Path == null), - Bezeled = true + Delegate = new SparkleTextFieldDelegate () }; - AddressTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail; - PathTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail; + AddressTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail; + PathTextField.Cell.LineBreakMode = NSLineBreakMode.TruncatingTail; PathHelpLabel = new NSTextField () { @@ -210,7 +215,8 @@ namespace SparkleShare { Frame = new RectangleF (0, 0, 0, 0), RowHeight = 30, IntercellSpacing = new SizeF (0, 12), - HeaderView = null + HeaderView = null, + Delegate = new SparkleTableViewDelegate () }; ScrollView = new NSScrollView () { @@ -270,33 +276,43 @@ namespace SparkleShare { }); }; + TableView.SelectRow (Controller.SelectedPluginIndex, false); - timer = new Timer () { - Interval = 50 + (AddressTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate { + Controller.CheckAddPage ( + AddressTextField.StringValue, + PathTextField.StringValue, + TableView.SelectedRow + ); }; - // TODO: Use an event - timer.Elapsed += delegate { - if (TableView.SelectedRow != Controller.SelectedPluginIndex) - Controller.SelectedPluginChanged (TableView.SelectedRow); + (PathTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate { + Controller.CheckAddPage ( + AddressTextField.StringValue, + PathTextField.StringValue, + TableView.SelectedRow + ); + }; + (TableView.Delegate as SparkleTableViewDelegate).SelectionChanged += delegate { + Controller.SelectedPluginChanged (TableView.SelectedRow); + + Controller.CheckAddPage ( + AddressTextField.StringValue, + PathTextField.StringValue, + TableView.SelectedRow + ); + }; + + + Controller.UpdateAddProjectButtonEvent += delegate (bool button_enabled) { InvokeOnMainThread (delegate { - // TODO: Move checking logic to controller - if (!string.IsNullOrWhiteSpace (AddressTextField.StringValue) && - !string.IsNullOrWhiteSpace (PathTextField.StringValue)) { - - SyncButton.Enabled = true; - - } else { - SyncButton.Enabled = false; - } + SyncButton.Enabled = button_enabled; }); }; - timer.Start (); - ContentView.AddSubview (ScrollView); ContentView.AddSubview (AddressLabel); @@ -311,9 +327,6 @@ namespace SparkleShare { }; SyncButton.Activated += delegate { - timer.Stop (); - timer = null; - Controller.AddPageCompleted ( AddressTextField.StringValue, PathTextField.StringValue @@ -442,6 +455,28 @@ namespace SparkleShare { "‘" + Controller.SyncingFolder + "’ in " + "your SparkleShare folder."; + if (warnings != null) { + WarningImage = NSImage.ImageNamed ("NSCaution"); + WarningImage.Size = new SizeF (24, 24); + + WarningImageView = new NSImageView () { + Image = WarningImage, + Frame = new RectangleF (190, Frame.Height - 175, 24, 24) + }; + + WarningTextField = new NSTextField () { + Frame = new RectangleF (230, Frame.Height - 245, 325, 100), + StringValue = warnings [0], + BackgroundColor = NSColor.WindowBackground, + Bordered = false, + Editable = false, + Font = SparkleUI.Font + }; + + ContentView.AddSubview (WarningImageView); + ContentView.AddSubview (WarningTextField); + } + FinishButton = new NSButton () { Title = "Finish" }; @@ -673,4 +708,39 @@ namespace SparkleShare { } } } + + + public class SparkleTextFieldDelegate : NSTextFieldDelegate { + + public event StringValueChangedHandler StringValueChanged; + public delegate void StringValueChangedHandler (); + + + public override void Changed (NSNotification notification) + { + if (StringValueChanged!= null) + StringValueChanged (); + } + + + public override string [] GetCompletions (NSControl control, NSTextView text_view, + string [] a, MonoMac.Foundation.NSRange range, int b) + { + return new string [0]; + } + } + + + public class SparkleTableViewDelegate : NSTableViewDelegate { + + public event SelectionChangedHandler SelectionChanged; + public delegate void SelectionChangedHandler (); + + + public override void SelectionDidChange (NSNotification notification) + { + if (SelectionChanged != null) + SelectionChanged (); + } + } } diff --git a/SparkleShare/Mac/SparkleSetupWindow.cs b/SparkleShare/Mac/SparkleSetupWindow.cs index d0a6ed79..e2bfa0c0 100755 --- a/SparkleShare/Mac/SparkleSetupWindow.cs +++ b/SparkleShare/Mac/SparkleSetupWindow.cs @@ -143,7 +143,7 @@ namespace SparkleShare { public override void PerformClose (NSObject sender) { - OrderOut (this); + base.OrderOut (this); NSApplication.SharedApplication.RemoveWindowsItem (this); return; } diff --git a/SparkleShare/Makefile.am b/SparkleShare/Makefile.am index 17e7a0b3..ec90ebe3 100755 --- a/SparkleShare/Makefile.am +++ b/SparkleShare/Makefile.am @@ -38,6 +38,7 @@ SOURCES = \ SparkleEntry.cs \ SparkleEventLog.cs \ SparkleEventLogController.cs \ + SparkleOptions.cs \ SparkleSetup.cs \ SparkleSetupController.cs \ SparkleSetupWindow.cs \ diff --git a/SparkleShare/Program.cs b/SparkleShare/Program.cs index 8d5f7697..b0917092 100755 --- a/SparkleShare/Program.cs +++ b/SparkleShare/Program.cs @@ -25,7 +25,6 @@ using System.Text; using Mono.Unix; //using Mono.Unix.Native; using SparkleLib; -using SparkleLib.Options; namespace SparkleShare { diff --git a/SparkleShare/SparkleBubblesController.cs b/SparkleShare/SparkleBubblesController.cs index dd660b8a..3fe424e4 100755 --- a/SparkleShare/SparkleBubblesController.cs +++ b/SparkleShare/SparkleBubblesController.cs @@ -16,6 +16,7 @@ using System; +using SparkleLib; namespace SparkleShare { @@ -29,14 +30,18 @@ namespace SparkleShare { { Program.Controller.ConflictNotificationRaised += delegate { ShowBubble ("Ouch! Mid-air collision!", - "Don't worry, SparkleShare made a copy of each conflicting file.", - null); + "Don't worry, SparkleShare made a copy of each conflicting file.", + null); }; - Program.Controller.NotificationRaised += delegate (string user_name, string user_email, - string message, string folder_path) { - ShowBubble (user_name, message, - Program.Controller.GetAvatar (user_email, 36)); + Program.Controller.NotificationRaised += delegate (SparkleChangeSet change_set) { + ShowBubble (change_set.User.Name, FormatMessage (change_set), + Program.Controller.GetAvatar (change_set.User.Email, 36)); + }; + + Program.Controller.NoteNotificationRaised += delegate (SparkleUser user, string folder_name) { + ShowBubble (user.Name, "added a note to '" + folder_name + "'", + Program.Controller.GetAvatar (user.Email, 36)); }; } @@ -46,5 +51,47 @@ namespace SparkleShare { if (ShowBubbleEvent != null && Program.Controller.NotificationsEnabled) ShowBubbleEvent (title, subtext, image_path); } + + + 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 = string.Format ("and {0} more", changes_count); + message += " " + String.Format (msg, changes_count); + + } else if (changes_count < 0) { + message += "did something magical"; + } + + return message; + } } } diff --git a/SparkleShare/SparkleControllerBase.cs b/SparkleShare/SparkleControllerBase.cs index e6ebe9db..f6032dad 100755 --- a/SparkleShare/SparkleControllerBase.cs +++ b/SparkleShare/SparkleControllerBase.cs @@ -42,7 +42,7 @@ namespace SparkleShare { public delegate void OnQuitWhileSyncingEventHandler (); public event FolderFetchedEventHandler FolderFetched; - public delegate void FolderFetchedEventHandler (); + public delegate void FolderFetchedEventHandler (string [] warnings); public event FolderFetchErrorEventHandler FolderFetchError; public delegate void FolderFetchErrorEventHandler (string remote_url); @@ -75,8 +75,10 @@ namespace SparkleShare { public delegate void ConflictNotificationRaisedEventHandler (); public event NotificationRaisedEventHandler NotificationRaised; - public delegate void NotificationRaisedEventHandler (string user_name, string user_email, - string message, string repository_path); + public delegate void NotificationRaisedEventHandler (SparkleChangeSet change_set); + + public event NoteNotificationRaisedEventHandler NoteNotificationRaised; + public delegate void NoteNotificationRaisedEventHandler (SparkleUser user, string folder_name); public abstract string PluginsPath { get; } @@ -581,16 +583,15 @@ namespace SparkleShare { 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); + NotificationRaised (change_set); }; - 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.NewNote += delegate (SparkleUser user) { + if (NoteNotificationRaised != null) + NoteNotificationRaised (user, repo.Name); + }; repo.ConflictResolved += delegate { @@ -702,48 +703,6 @@ namespace SparkleShare { } - 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 = Catalog.GetPluralString ("and {0} more", "and {0} more", changes_count); - 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) { @@ -1054,7 +1013,7 @@ namespace SparkleShare { if (i > 1) target_folder_name += " (" + i + ")"; - this.fetcher.Finished += delegate { + this.fetcher.Finished += delegate (string [] warnings) { // Needed to do the moving SparkleHelpers.ClearAttributes (tmp_folder); @@ -1071,7 +1030,7 @@ namespace SparkleShare { AddRepository (target_folder_path); if (FolderFetched != null) - FolderFetched (); + FolderFetched (warnings); FolderSize = GetFolderSize (); @@ -1155,16 +1114,6 @@ namespace SparkleShare { } - // 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", " "); @@ -1177,8 +1126,6 @@ namespace SparkleShare { } - - private string [] tango_palette = new string [] {"#eaab00", "#e37222", "#3892ab", "#33c2cb", "#19b271", "#9eab05", "#8599a8", "#9ca696", "#b88454", "#cc0033", "#8f6678", "#8c6cd0", "#796cbf", "#4060af", diff --git a/SparkleShare/SparkleOptions.cs b/SparkleShare/SparkleOptions.cs new file mode 100644 index 00000000..ea321f0e --- /dev/null +++ b/SparkleShare/SparkleOptions.cs @@ -0,0 +1,1101 @@ +// +// Options.cs +// +// Authors: +// Jonathan Pryor +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Compile With: +// gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll +// gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// NDesk.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +#if NDESK_OPTIONS +namespace NDesk.Options +#else +namespace SparkleShare +#endif +{ + public class OptionValueCollection : IList, IList { + + List values = new List (); + OptionContext c; + + internal OptionValueCollection (OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} + bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} + object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} + #endregion + + #region ICollection + public void Add (string item) {values.Add (item);} + public void Clear () {values.Clear ();} + public bool Contains (string item) {return values.Contains (item);} + public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} + public bool Remove (string item) {return values.Remove (item);} + public int Count {get {return values.Count;}} + public bool IsReadOnly {get {return false;}} + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IList + int IList.Add (object value) {return (values as IList).Add (value);} + bool IList.Contains (object value) {return (values as IList).Contains (value);} + int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} + void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} + void IList.Remove (object value) {(values as IList).Remove (value);} + void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} + bool IList.IsFixedSize {get {return false;}} + object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} + #endregion + + #region IList + public int IndexOf (string item) {return values.IndexOf (item);} + public void Insert (int index, string item) {values.Insert (index, item);} + public void RemoveAt (int index) {values.RemoveAt (index);} + + private void AssertValid (int index) + { + if (c.Option == null) + throw new InvalidOperationException ("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException ("index"); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException (string.Format ( + c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this [int index] { + get { + AssertValid (index); + return index >= values.Count ? null : values [index]; + } + set { + values [index] = value; + } + } + #endregion + + public List ToList () + { + return new List (values); + } + + public string[] ToArray () + { + return values.ToArray (); + } + + public override string ToString () + { + return string.Join (", ", values.ToArray ()); + } + } + + public class OptionContext { + private Option option; + private string name; + private int index; + private OptionSet set; + private OptionValueCollection c; + + public OptionContext (OptionSet set) + { + this.set = set; + this.c = new OptionValueCollection (this); + } + + public Option Option { + get {return option;} + set {option = value;} + } + + public string OptionName { + get {return name;} + set {name = value;} + } + + public int OptionIndex { + get {return index;} + set {index = value;} + } + + public OptionSet OptionSet { + get {return set;} + } + + public OptionValueCollection OptionValues { + get {return c;} + } + } + + public enum OptionValueType { + None, + Optional, + Required, + } + + public abstract class Option { + string prototype, description; + string[] names; + OptionValueType type; + int count; + string[] separators; + + protected Option (string prototype, string description) + : this (prototype, description, 1) + { + } + + protected Option (string prototype, string description, int maxValueCount) + { + if (prototype == null) + throw new ArgumentNullException ("prototype"); + if (prototype.Length == 0) + throw new ArgumentException ("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException ("maxValueCount"); + + this.prototype = prototype; + this.names = prototype.Split ('|'); + this.description = description; + this.count = maxValueCount; + this.type = ParsePrototype (); + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException ( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + "maxValueCount"); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException ( + string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + if (Array.IndexOf (names, "<>") >= 0 && + ((names.Length == 1 && this.type != OptionValueType.None) || + (names.Length > 1 && this.MaxValueCount > 1))) + throw new ArgumentException ( + "The default option handler '<>' cannot require values.", + "prototype"); + } + + public string Prototype {get {return prototype;}} + public string Description {get {return description;}} + public OptionValueType OptionValueType {get {return type;}} + public int MaxValueCount {get {return count;}} + + public string[] GetNames () + { + return (string[]) names.Clone (); + } + + public string[] GetValueSeparators () + { + if (separators == null) + return new string [0]; + return (string[]) separators.Clone (); + } + + protected static T Parse (string value, OptionContext c) + { + Type tt = typeof (T); + bool nullable = tt.IsValueType && tt.IsGenericType && + !tt.IsGenericTypeDefinition && + tt.GetGenericTypeDefinition () == typeof (Nullable<>); + Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T); + TypeConverter conv = TypeDescriptor.GetConverter (targetType); + T t = default (T); + try { + if (value != null) + t = (T) conv.ConvertFromString (value); + } + catch (Exception e) { + throw new OptionException ( + string.Format ( + c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), + value, targetType.Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names {get {return names;}} + internal string[] ValueSeparators {get {return separators;}} + + static readonly char[] NameTerminator = new char[]{'=', ':'}; + + private OptionValueType ParsePrototype () + { + char type = '\0'; + List seps = new List (); + for (int i = 0; i < names.Length; ++i) { + string name = names [i]; + if (name.Length == 0) + throw new ArgumentException ("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny (NameTerminator); + if (end == -1) + continue; + names [i] = name.Substring (0, end); + if (type == '\0' || type == name [end]) + type = name [end]; + else + throw new ArgumentException ( + string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), + "prototype"); + AddSeparators (name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException ( + string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) { + if (seps.Count == 0) + this.separators = new string[]{":", "="}; + else if (seps.Count == 1 && seps [0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray (); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private static void AddSeparators (string name, int end, ICollection seps) + { + int start = -1; + for (int i = end+1; i < name.Length; ++i) { + switch (name [i]) { + case '{': + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i+1; + break; + case '}': + if (start == -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add (name.Substring (start, i-start)); + start = -1; + break; + default: + if (start == -1) + seps.Add (name [i].ToString ()); + break; + } + } + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke (OptionContext c) + { + OnParseComplete (c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear (); + } + + protected abstract void OnParseComplete (OptionContext c); + + public override string ToString () + { + return Prototype; + } + } + + [Serializable] + public class OptionException : Exception { + private string option; + + public OptionException () + { + } + + public OptionException (string message, string optionName) + : base (message) + { + this.option = optionName; + } + + public OptionException (string message, string optionName, Exception innerException) + : base (message, innerException) + { + this.option = optionName; + } + + protected OptionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + this.option = info.GetString ("OptionName"); + } + + public string OptionName { + get {return this.option;} + } + + [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("OptionName", option); + } + } + + public delegate void OptionAction (TKey key, TValue value); + + public class OptionSet : KeyedCollection + { + public OptionSet () + : this (delegate (string f) {return f;}) + { + } + + public OptionSet (Converter localizer) + { + this.localizer = localizer; + } + + Converter localizer; + + public Converter MessageLocalizer { + get {return localizer;} + } + + protected override string GetKeyForItem (Option item) + { + if (item == null) + throw new ArgumentNullException ("option"); + if (item.Names != null && item.Names.Length > 0) + return item.Names [0]; + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException ("Option has no names!"); + } + + [Obsolete ("Use KeyedCollection.this[string]")] + protected Option GetOptionForName (string option) + { + if (option == null) + throw new ArgumentNullException ("option"); + try { + return base [option]; + } + catch (KeyNotFoundException) { + return null; + } + } + + protected override void InsertItem (int index, Option item) + { + base.InsertItem (index, item); + AddImpl (item); + } + + protected override void RemoveItem (int index) + { + base.RemoveItem (index); + Option p = Items [index]; + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) { + Dictionary.Remove (p.Names [i]); + } + } + + protected override void SetItem (int index, Option item) + { + base.SetItem (index, item); + RemoveItem (index); + AddImpl (item); + } + + private void AddImpl (Option option) + { + if (option == null) + throw new ArgumentNullException ("option"); + List added = new List (option.Names.Length); + try { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) { + Dictionary.Add (option.Names [i], option); + added.Add (option.Names [i]); + } + } + catch (Exception) { + foreach (string name in added) + Dictionary.Remove (name); + throw; + } + } + + public new OptionSet Add (Option option) + { + base.Add (option); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, int count, Action action) + : base (prototype, description, count) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (c.OptionValues); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 1, + delegate (OptionValueCollection v) { action (v [0]); }); + base.Add (p); + return this; + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 2, + delegate (OptionValueCollection v) {action (v [0], v [1]);}); + base.Add (p); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, Action action) + : base (prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (Parse (c.OptionValues [0], c)); + } + } + + sealed class ActionOption : Option { + OptionAction action; + + public ActionOption (string prototype, string description, OptionAction action) + : base (prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action ( + Parse (c.OptionValues [0], c), + Parse (c.OptionValues [1], c)); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + return Add (new ActionOption (prototype, description, action)); + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + return Add (new ActionOption (prototype, description, action)); + } + + protected virtual OptionContext CreateOptionContext () + { + return new OptionContext (this); + } + +#if LINQ + public List Parse (IEnumerable arguments) + { + bool process = true; + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + var def = GetOptionForName ("<>"); + var unprocessed = + from argument in arguments + where ++c.OptionIndex >= 0 && (process || def != null) + ? process + ? argument == "--" + ? (process = false) + : !Parse (argument, c) + ? def != null + ? Unprocessed (null, def, c, argument) + : true + : false + : def != null + ? Unprocessed (null, def, c, argument) + : true + : true + select argument; + List r = unprocessed.ToList (); + if (c.Option != null) + c.Option.Invoke (c); + return r; + } +#else + public List Parse (IEnumerable arguments) + { + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List (); + Option def = Contains ("<>") ? this ["<>"] : null; + foreach (string argument in arguments) { + ++c.OptionIndex; + if (argument == "--") { + process = false; + continue; + } + if (!process) { + Unprocessed (unprocessed, def, c, argument); + continue; + } + if (!Parse (argument, c)) + Unprocessed (unprocessed, def, c, argument); + } + if (c.Option != null) + c.Option.Invoke (c); + return unprocessed; + } +#endif + + private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) { + extra.Add (argument); + return false; + } + c.OptionValues.Add (argument); + c.Option = def; + c.Option.Invoke (c); + return false; + } + + private readonly Regex ValueOption = new Regex ( + @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) + { + if (argument == null) + throw new ArgumentNullException ("argument"); + + flag = name = sep = value = null; + Match m = ValueOption.Match (argument); + if (!m.Success) { + return false; + } + flag = m.Groups ["flag"].Value; + name = m.Groups ["name"].Value; + if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { + sep = m.Groups ["sep"].Value; + value = m.Groups ["value"].Value; + } + return true; + } + + protected virtual bool Parse (string argument, OptionContext c) + { + if (c.Option != null) { + ParseValue (argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts (argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains (n)) { + p = this [n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) { + case OptionValueType.None: + c.OptionValues.Add (n); + c.Option.Invoke (c); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue (v, c); + break; + } + return true; + } + // no match; is it a bool option? + if (ParseBool (argument, n, c)) + return true; + // is it a bundled option? + if (ParseBundledValue (f, string.Concat (n + s + v), c)) + return true; + + return false; + } + + private void ParseValue (string option, OptionContext c) + { + if (option != null) + foreach (string o in c.Option.ValueSeparators != null + ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None) + : new string[]{option}) { + c.OptionValues.Add (o); + } + if (c.OptionValues.Count == c.Option.MaxValueCount || + c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke (c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) { + throw new OptionException (localizer (string.Format ( + "Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), + c.OptionName); + } + } + + private bool ParseBool (string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && + Contains ((rn = n.Substring (0, n.Length-1)))) { + p = this [rn]; + string v = n [n.Length-1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add (v); + p.Invoke (c); + return true; + } + return false; + } + + private bool ParseBundledValue (string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) { + Option p; + string opt = f + n [i].ToString (); + string rn = n [i].ToString (); + if (!Contains (rn)) { + if (i == 0) + return false; + throw new OptionException (string.Format (localizer ( + "Cannot bundle unregistered option '{0}'."), opt), opt); + } + p = this [rn]; + switch (p.OptionValueType) { + case OptionValueType.None: + Invoke (c, opt, n, p); + break; + case OptionValueType.Optional: + case OptionValueType.Required: { + string v = n.Substring (i+1); + c.Option = p; + c.OptionName = opt; + ParseValue (v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); + } + } + return true; + } + + private static void Invoke (OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add (value); + option.Invoke (c); + } + + private const int OptionWidth = 29; + + public void WriteOptionDescriptions (TextWriter o) + { + foreach (Option p in this) { + int written = 0; + if (!WriteOptionPrototype (o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write (new string (' ', OptionWidth - written)); + else { + o.WriteLine (); + o.Write (new string (' ', OptionWidth)); + } + + bool indent = false; + string prefix = new string (' ', OptionWidth+2); + foreach (string line in GetLines (localizer (GetDescription (p.Description)))) { + if (indent) + o.Write (prefix); + o.WriteLine (line); + indent = true; + } + } + } + + bool WriteOptionPrototype (TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex (names, 0); + if (i == names.Length) + return false; + + if (names [i].Length == 1) { + Write (o, ref written, " -"); + Write (o, ref written, names [0]); + } + else { + Write (o, ref written, " --"); + Write (o, ref written, names [0]); + } + + for ( i = GetNextOptionIndex (names, i+1); + i < names.Length; i = GetNextOptionIndex (names, i+1)) { + Write (o, ref written, ", "); + Write (o, ref written, names [i].Length == 1 ? "-" : "--"); + Write (o, ref written, names [i]); + } + + if (p.OptionValueType == OptionValueType.Optional || + p.OptionValueType == OptionValueType.Required) { + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("[")); + } + Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 + ? p.ValueSeparators [0] + : " "; + for (int c = 1; c < p.MaxValueCount; ++c) { + Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); + } + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("]")); + } + } + return true; + } + + static int GetNextOptionIndex (string[] names, int i) + { + while (i < names.Length && names [i] == "<>") { + ++i; + } + return i; + } + + static void Write (TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write (s); + } + + private static string GetArgumentName (int index, int maxIndex, string description) + { + if (description == null) + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + string[] nameStart; + if (maxIndex == 1) + nameStart = new string[]{"{0:", "{"}; + else + nameStart = new string[]{"{" + index + ":"}; + for (int i = 0; i < nameStart.Length; ++i) { + int start, j = 0; + do { + start = description.IndexOf (nameStart [i], j); + } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); + if (start == -1) + continue; + int end = description.IndexOf ("}", start); + if (end == -1) + continue; + return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); + } + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + + private static string GetDescription (string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder (description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) { + switch (description [i]) { + case '{': + if (i == start) { + sb.Append ('{'); + start = -1; + } + else if (start < 0) + start = i + 1; + break; + case '}': + if (start < 0) { + if ((i+1) == description.Length || description [i+1] != '}') + throw new InvalidOperationException ("Invalid option description: " + description); + ++i; + sb.Append ("}"); + } + else { + sb.Append (description.Substring (start, i - start)); + start = -1; + } + break; + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + default: + if (start < 0) + sb.Append (description [i]); + break; + } + } + return sb.ToString (); + } + + private static IEnumerable GetLines (string description) + { + if (string.IsNullOrEmpty (description)) { + yield return string.Empty; + yield break; + } + int length = 80 - OptionWidth - 1; + int start = 0, end; + do { + end = GetLineEnd (start, length, description); + char c = description [end-1]; + if (char.IsWhiteSpace (c)) + --end; + bool writeContinuation = end != description.Length && !IsEolChar (c); + string line = description.Substring (start, end - start) + + (writeContinuation ? "-" : ""); + yield return line; + start = end; + if (char.IsWhiteSpace (c)) + ++start; + length = 80 - OptionWidth - 2 - 1; + } while (end < description.Length); + } + + private static bool IsEolChar (char c) + { + return !char.IsLetterOrDigit (c); + } + + private static int GetLineEnd (int start, int length, string description) + { + int end = System.Math.Min (start + length, description.Length); + int sep = -1; + for (int i = start; i < end; ++i) { + if (description [i] == '\n') + return i+1; + if (IsEolChar (description [i])) + sep = i+1; + } + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } +} + diff --git a/SparkleShare/SparkleSetup.cs b/SparkleShare/SparkleSetup.cs index 84a7ffd8..d01be29c 100755 --- a/SparkleShare/SparkleSetup.cs +++ b/SparkleShare/SparkleSetup.cs @@ -461,7 +461,31 @@ namespace SparkleShare { Close (); }; - Add (null); + + if (warnings != null) { + WarningImage = NSImage.ImageNamed ("NSCaution"); + WarningImage.Size = new SizeF (24, 24); + + WarningImageView = new NSImageView () { + Image = WarningImage, + Frame = new RectangleF (190, Frame.Height - 175, 24, 24) + }; + + Image warning_image = new Image (SparkleUIHelpers.GetIcon ("dialog-warning", 24)); + Label warning_label = new Label (warnings [0]) { + Xalign = 0 + }; + + HBox warning_layout = new HBox (false, 0); + warning_layout.PackStart (warning_image, false, false, 0); + warning_layout.PackStart (warning_label, true, true, 0); + + Add (warning_layout); + + } else { + Add (null); + } + AddButton (open_folder_button); AddButton (finish_button); @@ -575,42 +599,6 @@ namespace SparkleShare { } - // Enables or disables the 'Next' button depending on the - // entries filled in by the user - private void CheckSetupPage () - { - if (NameEntry.Text.Length > 0 && - Program.Controller.IsValidEmail (EmailEntry.Text)) { - - NextButton.Sensitive = true; - } else { - NextButton.Sensitive = false; - } - } - - - // Enables or disables the 'Next' button depending on the - // entries filled in by the user - public void CheckAddPage () - { - SyncButton.Sensitive = false; - - if (PathEntry.ExampleTextActive || - (AddressEntry.Sensitive && AddressEntry.ExampleTextActive)) - return; - - bool IsFolder = !PathEntry.Text.Trim ().Equals (""); - bool IsServer = !AddressEntry.Text.Trim ().Equals (""); - - if (AddressEntry.Sensitive == true) { - if (IsServer && IsFolder) - SyncButton.Sensitive = true; - } else if (IsFolder) { - SyncButton.Sensitive = true; - } - } - - private void RenderServiceColumn (TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter) { diff --git a/SparkleShare/SparkleSetupController.cs b/SparkleShare/SparkleSetupController.cs index 317a7c27..58ba3f44 100755 --- a/SparkleShare/SparkleSetupController.cs +++ b/SparkleShare/SparkleSetupController.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; using SparkleLib; @@ -32,15 +33,26 @@ namespace SparkleShare { Tutorial } + public enum FieldState { + Enabled, + Disabled + } + public class SparkleSetupController { public event ChangePageEventHandler ChangePageEvent; - public delegate void ChangePageEventHandler (PageType page); + public delegate void ChangePageEventHandler (PageType page, string [] warnings); public event UpdateProgressBarEventHandler UpdateProgressBarEvent; public delegate void UpdateProgressBarEventHandler (double percentage); + public event UpdateSetupContinueButtonEventHandler UpdateSetupContinueButtonEvent; + public delegate void UpdateSetupContinueButtonEventHandler (bool button_enabled); + + public event UpdateAddProjectButtonEventHandler UpdateAddProjectButtonEvent; + public delegate void UpdateAddProjectButtonEventHandler (bool button_enabled); + public event ChangeAddressFieldEventHandler ChangeAddressFieldEvent; public delegate void ChangeAddressFieldEventHandler (string text, string example_text, FieldState state); @@ -140,7 +152,7 @@ namespace SparkleShare { SelectedPlugin = Plugins [0]; - ChangePageEvent += delegate (PageType page) { + ChangePageEvent += delegate (PageType page, string [] warning) { this.previous_page = page; }; } @@ -149,7 +161,20 @@ namespace SparkleShare { public void ShowSetupPage () { if (ChangePageEvent != null) - ChangePageEvent (PageType.Setup); + ChangePageEvent (PageType.Setup, null); + } + + + public void CheckSetupPage (string full_name, string email) + { + full_name = full_name.Trim (); + email = email.Trim (); + + bool fields_valid = (!string.IsNullOrWhiteSpace (full_name) && + IsValidEmail (email)); + + if (UpdateSetupContinueButtonEvent != null) + UpdateSetupContinueButtonEvent (fields_valid); } @@ -162,7 +187,7 @@ namespace SparkleShare { Program.Controller.UpdateState (); if (ChangePageEvent != null) - ChangePageEvent (PageType.Tutorial); + ChangePageEvent (PageType.Tutorial, null); } @@ -171,7 +196,7 @@ namespace SparkleShare { this.tutorial_page_number++; if (ChangePageEvent != null) - ChangePageEvent (PageType.Tutorial); + ChangePageEvent (PageType.Tutorial, null); } @@ -180,19 +205,35 @@ namespace SparkleShare { this.tutorial_page_number = 4; if (ChangePageEvent != null) - ChangePageEvent (PageType.Tutorial); + ChangePageEvent (PageType.Tutorial, null); } public void ShowAddPage () { if (ChangePageEvent != null) - ChangePageEvent (PageType.Add); + ChangePageEvent (PageType.Add, null); SelectedPluginChanged (SelectedPluginIndex); } + public void CheckAddPage (string address, string remote_path, int selected_plugin) + { + if (SelectedPluginIndex != selected_plugin) + SelectedPluginChanged (selected_plugin); + + address = address.Trim (); + remote_path = remote_path.Trim (); + + bool fields_valid = (!string.IsNullOrWhiteSpace (address) && + !string.IsNullOrWhiteSpace (remote_path)); + + if (UpdateAddProjectButtonEvent != null) + UpdateAddProjectButtonEvent (fields_valid); + } + + public void AddPageCompleted (string address, string path) { this.syncing_folder = Path.GetFileNameWithoutExtension (path); @@ -200,11 +241,13 @@ namespace SparkleShare { this.previous_path = path; if (ChangePageEvent != null) - ChangePageEvent (PageType.Syncing); + ChangePageEvent (PageType.Syncing, null); - Program.Controller.FolderFetched += delegate { + // TODO: Remove events afterwards + + Program.Controller.FolderFetched += delegate (string [] warnings) { if (ChangePageEvent != null) - ChangePageEvent (PageType.Finished); + ChangePageEvent (PageType.Finished, warnings); this.previous_address = ""; this.syncing_folder = ""; @@ -216,7 +259,7 @@ namespace SparkleShare { this.previous_url = remote_url; if (ChangePageEvent != null) - ChangePageEvent (PageType.Error); + ChangePageEvent (PageType.Error, null); this.syncing_folder = ""; }; @@ -233,7 +276,7 @@ namespace SparkleShare { public void ErrorPageCompleted () { if (ChangePageEvent != null) - ChangePageEvent (PageType.Add); + ChangePageEvent (PageType.Add, null); } @@ -242,7 +285,7 @@ namespace SparkleShare { Program.Controller.StopFetcher (); if (ChangePageEvent != null) - ChangePageEvent (PageType.Add); + ChangePageEvent (PageType.Add, null); } @@ -283,11 +326,14 @@ namespace SparkleShare { ChangePathFieldEvent ("", "", FieldState.Enabled); } } - } - public enum FieldState { - Enabled, - Disabled + private bool IsValidEmail (string email) + { + Regex regex = new Regex (@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$", + RegexOptions.IgnoreCase); + + return regex.IsMatch (email); + } } } diff --git a/SparkleShare/SparkleStatusIcon.cs b/SparkleShare/SparkleStatusIcon.cs index 7e4ed555..f2996418 100755 --- a/SparkleShare/SparkleStatusIcon.cs +++ b/SparkleShare/SparkleStatusIcon.cs @@ -104,7 +104,7 @@ namespace SparkleShare { case IconState.Syncing: StateText = _("Syncing…"); - UpdateStateText (); // TODO + UpdateStateText (); if (!Animation.Enabled) Animation.Start (); diff --git a/SparkleShare/SparkleStatusIconController.cs b/SparkleShare/SparkleStatusIconController.cs index 6fc54782..30ab0408 100755 --- a/SparkleShare/SparkleStatusIconController.cs +++ b/SparkleShare/SparkleStatusIconController.cs @@ -22,6 +22,8 @@ namespace SparkleShare { public enum IconState { Idle, + SyncingUp, + SyncingDown, Syncing, Error } @@ -71,8 +73,6 @@ namespace SparkleShare { Program.Controller.OnSyncing += delegate { CurrentState = IconState.Syncing; - // TODO up down both - if (UpdateMenuEvent != null) UpdateMenuEvent (IconState.Syncing); }; diff --git a/autogen.sh b/autogen.sh index 41a48155..3af42db6 100755 --- a/autogen.sh +++ b/autogen.sh @@ -73,10 +73,6 @@ else touch gnome-doc-utils.make fi -if git --help &>/dev/null; then - git submodule update --init -fi - run intltoolize --force --copy run $LIBTOOLIZE --force --copy --automake run aclocal -I build/m4/sparkleshare -I build/m4/shamrock -I build/m4/shave $ACLOCAL_FLAGS @@ -85,12 +81,6 @@ run autoconf run automake --gnu --add-missing --force --copy \ -Wno-portability -Wno-portability -if test -d $srcdir/SmartIrc4net; then - echo Running SmartIrc4net/autogen.sh ... - (cd $srcdir/SmartIrc4net; NOCONFIGURE=1 ./autogen.sh "$@") - echo Done running SmartIrc4net/autogen.sh ... -fi - if [ ! -z "$NOCONFIGURE" ]; then echo "Done. ./configure skipped." exit $? diff --git a/build/build.environment.mk b/build/build.environment.mk index a76b32ff..e59b6fd1 100755 --- a/build/build.environment.mk +++ b/build/build.environment.mk @@ -15,8 +15,6 @@ LINK_GTK = $(GTKSHARP_LIBS) LINK_GNOME = $(GNOME_SHARP_LIBS) LINK_DBUS = $(NDESK_DBUS_LIBS) $(NDESK_DBUS_GLIB_LIBS) LINK_DBUS_NO_GLIB = $(NDESK_DBUS_LIBS) -LINK_SMARTIRC4NET_SYSTEM = $(SMARTIRC4NET_LIBS) -LINK_SMARTIRC4NET_LOCAL = -r:$(abs_top_builddir)/$(SMARTIRC4NET_ASSEMBLY) LINK_APP_INDICATOR = $(APP_INDICATOR_LIBS) REF_NOTIFY_SHARP = $(LINK_SYSTEM) $(LINK_DBUS) $(GTKSHARP_LIBS) $(GLIBSHARP_LIBS) diff --git a/build/build.rules.mk b/build/build.rules.mk index a84f08ad..7740d5a4 100755 --- a/build/build.rules.mk +++ b/build/build.rules.mk @@ -26,7 +26,7 @@ ASSEMBLY_FILE = $(top_builddir)/bin/$(ASSEMBLY).$(ASSEMBLY_EXTENSION) INSTALL_DIR_RESOLVED = $(firstword $(subst , $(DEFAULT_INSTALL_DIR), $(INSTALL_DIR))) if ENABLE_TESTS - LINK += " $(NUNIT_LIBS)" + LINK = " $(NUNIT_LIBS)" ENABLE_TESTS_FLAG = "-define:ENABLE_TESTS" endif diff --git a/build/m4/sparkleshare/smartirc4net.m4 b/build/m4/sparkleshare/smartirc4net.m4 deleted file mode 100755 index 9cb1099b..00000000 --- a/build/m4/sparkleshare/smartirc4net.m4 +++ /dev/null @@ -1,16 +0,0 @@ -AC_DEFUN([SPARKLESHARE_SMARTIRC4NET], -[ - if test ! -d "$srcdir/SmartIrc4net"; then - AC_MSG_ERROR([SmartIrc4net folder not found]) - fi - ac_configure_args="$ac_configure_args --disable-pkg-config --disable-pkg-lib --disable-pkg-gac" - AC_CONFIG_SUBDIRS([SmartIrc4net]) - asm="SmartIrc4net/bin/Meebey.SmartIrc4net.dll" - SMARTIRC4NET_ASSEMBLY="$asm" - SMARTIRC4NET_FILES="$asm" - [[ -r "$asm.mdb" ]] && SMARTIRC4NET_FILES="$SMARTIRC4NET_FILES $asm.mdb" - - AC_SUBST([SMARTIRC4NET_ASSEMBLY]) - AC_SUBST([SMARTIRC4NET_FILES]) -]) - diff --git a/configure.ac b/configure.ac index 2116eb11..2e682cec 100755 --- a/configure.ac +++ b/configure.ac @@ -87,18 +87,6 @@ dnl package checks, common for all configs SPARKLESHARE_CHECK_GTK_SHARP -SPARKLESHARE_SMARTIRC4NET -PKG_CHECK_MODULES([SMARTIRC4NET], [smartirc4net >= 0.5], - SMARTIRC4NET_ASSEMBLY="" - AC_SUBST(SMARTIRC4NET_ASSEMBLY) - , - SMARTIRC4NET_LIBS="" - AC_SUBST(SMARTIRC4NET_LIBS) - SPARKLE_SMARTIRC4NETDIR=SmartIrc4Net - AC_SUBST(SPARKLE_SMARTIRC4NETDIR) -) -AM_CONDITIONAL([HAVE_SMARTIRC4NET], test x$SPARKLE_SMARTIRC4NETDIR = x) - AC_ARG_ENABLE(gtkui, AS_HELP_STRING([--disable-gtkui], [Do not build the Gtk+ user interface]), [ enable_gtkui=no ], [ enable_gtkui=yes ])