diff --git a/SparkleShare/SparkleHelpers.cs b/SparkleShare/SparkleHelpers.cs
index f37f0d01..4c418064 100644
--- a/SparkleShare/SparkleHelpers.cs
+++ b/SparkleShare/SparkleHelpers.cs
@@ -15,6 +15,7 @@
// along with this program. If not, see .
using Gtk;
+using Mono.Unix;
using System;
using System.IO;
using System.Net;
@@ -27,6 +28,11 @@ namespace SparkleShare {
public static class SparkleHelpers
{
+ public static string _ (string s)
+ {
+ return Catalog.GetString (s);
+ }
+
// Get's the avatar for a specific email address and size
public static Gdk.Pixbuf GetAvatar (string Email, int Size)
{
@@ -151,52 +157,65 @@ namespace SparkleShare {
if (time_span <= TimeSpan.FromSeconds (60)) {
if (time_span.Seconds > 1)
- return string.Format ("{0} seconds ago", time_span.Seconds);
+ return string.Format (_("{0} seconds ago"), time_span.Seconds);
else
return "a second ago";
}
if (time_span <= TimeSpan.FromSeconds (60)) {
if (time_span.Minutes > 1)
- return string.Format ("about {0} minutes ago", time_span.Minutes);
+ return string.Format (_("about {0} minutes ago"), time_span.Minutes);
else
return "a minute ago";
}
if (time_span <= TimeSpan.FromHours(24)) {
if (time_span.Hours > 1)
- return string.Format ("about {0} minutes ago", time_span.Hours);
+ return string.Format (_("about {0} minutes ago"), time_span.Hours);
else
return "about an hour ago";
}
if (time_span <= TimeSpan.FromDays(30)) {
if (time_span.Days > 1)
- return string.Format ("{0} days ago", time_span.Days);
+ return string.Format (_("{0} days ago"), time_span.Days);
else
return "yesterday";
}
if (time_span <= TimeSpan.FromDays(365)) {
if (time_span.Days > 1)
- return string.Format ("{0} months ago", (int) time_span.Days / 30);
+ return string.Format (_("{0} months ago"), (int) time_span.Days / 30);
else
return "a month ago";
}
if (time_span <= TimeSpan.FromDays(365)) {
if (time_span.Days > 1)
- return string.Format ("{0} months ago", (int) time_span.Days / 365);
+ return string.Format (_("{0} months ago"), (int) time_span.Days / 365);
else
return "a month ago";
}
if (time_span.Days > 365)
- return string.Format ("{0} months ago", (int) time_span.Days / 365);
+ return string.Format (_("{0} months ago"), (int) time_span.Days / 365);
else
return "a year ago";
- }
+ }
+
+ // Checks for unicorns
+ public static void CheckForUnicorns (string s) {
+ s = s.ToLower ();
+ if (s.Contains ("unicorn") && (s.Contains (".png") || s.Contains (".jpg"))) {
+ string title = _("Hold your ponies!");
+ string subtext = _("SparkleShare is known to be insanely fast with \n" +
+ "pictures of unicorns. Please make sure your internets\n" +
+ "are upgraded to the latest version to avoid problems.");
+ SparkleBubble unicorn_bubble = new SparkleBubble (title, subtext);
+ unicorn_bubble.Show ();
+ }
+ }
}
diff --git a/SparkleShare/SparkleRepo.cs b/SparkleShare/SparkleRepo.cs
new file mode 100644
index 00000000..d9dac422
--- /dev/null
+++ b/SparkleShare/SparkleRepo.cs
@@ -0,0 +1,462 @@
+// SparkleShare, an instant update workflow to Git.
+// Copyright (C) 2010 Hylke Bons
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+using Gtk;
+using Mono.Unix;
+using SparkleShare;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Timers;
+
+namespace SparkleShare {
+
+ // SparkleRepo class holds repository information and timers
+ public class SparkleRepo
+ {
+
+ private Process Process;
+ private Timer FetchTimer;
+ private Timer BufferTimer;
+ private FileSystemWatcher Watcher;
+ private bool HasChanged = false;
+ private DateTime LastChange;
+
+ public string Name;
+ public string Domain;
+ public string LocalPath;
+ public string RemoteOriginUrl;
+ public string CurrentHash;
+
+ public string UserEmail;
+ public string UserName;
+
+ public static string _ (string s)
+ {
+ return Catalog.GetString (s);
+ }
+
+ public SparkleRepo (string RepoPath)
+ {
+
+ Process = new Process ();
+ Process.EnableRaisingEvents = true;
+ Process.StartInfo.RedirectStandardOutput = true;
+ Process.StartInfo.UseShellExecute = false;
+
+ // Get the repository's path, example: "/home/user/SparkleShare/repo"
+ LocalPath = RepoPath;
+ Process.StartInfo.WorkingDirectory = LocalPath;
+
+ // Get user.name, example: "User Name"
+ UnixUserInfo UnixUserInfo = new UnixUserInfo (UnixEnvironment.UserName);
+ if (UnixUserInfo.RealName.Equals (""))
+ UserName = "Anonymous";
+ else
+ UserName = UnixUserInfo.RealName;
+
+ Process.StartInfo.FileName = "git";
+ Process.StartInfo.Arguments = "config user.name " + UserName;
+ Process.Start ();
+
+ // Get user.email, example: "user@github.com"
+ UserEmail = "not.set@git-scm.com";
+ Process.StartInfo.FileName = "git";
+ Process.StartInfo.Arguments = "config --get user.email";
+ Process.Start ();
+ UserEmail = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ // Get remote.origin.url, example: "ssh://git@github.com/user/repo"
+ Process.StartInfo.FileName = "git";
+ Process.StartInfo.Arguments = "config --get remote.origin.url";
+ Process.Start ();
+ RemoteOriginUrl = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ // Get the repository name, example: "Project"
+ Name = Path.GetFileName (LocalPath);
+
+ // Get the domain, example: "github.com"
+ Domain = RemoteOriginUrl;
+ Domain = Domain.Substring (Domain.IndexOf ("@") + 1);
+ if (Domain.IndexOf (":") > -1)
+ Domain = Domain.Substring (0, Domain.IndexOf (":"));
+ else
+ Domain = Domain.Substring (0, Domain.IndexOf ("/"));
+
+ // Get hash of the current commit
+ Process.StartInfo.FileName = "git";
+ Process.StartInfo.Arguments = "rev-list --max-count=1 HEAD";
+ Process.Start ();
+ CurrentHash = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ // Watch the repository's folder
+ Watcher = new FileSystemWatcher (LocalPath);
+ Watcher.IncludeSubdirectories = true;
+ Watcher.EnableRaisingEvents = true;
+ Watcher.Filter = "*";
+ Watcher.Changed += new FileSystemEventHandler (OnFileActivity);
+ Watcher.Created += new FileSystemEventHandler (OnFileActivity);
+ Watcher.Deleted += new FileSystemEventHandler (OnFileActivity);
+
+ // Fetch remote changes every 20 seconds
+ FetchTimer = new Timer ();
+ FetchTimer.Interval = 20000;
+ FetchTimer.Elapsed += delegate {
+ Fetch ();
+ };
+
+ FetchTimer.Start ();
+
+ BufferTimer = new Timer ();
+ BufferTimer.Interval = 4000;
+ BufferTimer.Elapsed += delegate (object o, ElapsedEventArgs args) {
+ SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Checking for changes.");
+
+ if (HasChanged) {
+ SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes found, checking if settled.");
+ DateTime now = DateTime.UtcNow;
+ TimeSpan changed = new TimeSpan (now.Ticks - LastChange.Ticks);
+ if (changed.TotalMilliseconds > 5000) {
+ HasChanged = false;
+ SparkleHelpers.DebugInfo ("Buffer", "[" + Name + "] Changes have settled, adding.");
+ AddCommitAndPush ();
+ }
+ }
+ };
+
+ BufferTimer.Start ();
+
+ // Add everything that changed
+ // since SparkleShare was stopped
+
+ AddCommitAndPush ();
+
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Nothing going on...");
+
+ }
+
+ // Starts a time buffer when something changes
+ private void OnFileActivity (object o, FileSystemEventArgs args)
+ {
+ WatcherChangeTypes wct = args.ChangeType;
+ if (!ShouldIgnore (args.Name)) {
+ SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + args.Name + "'");
+ FetchTimer.Stop ();
+ LastChange = DateTime.UtcNow;
+ HasChanged = true;
+ }
+ }
+
+ // When there are changes we generally want to Add, Commit and Push
+ // so this method does them all with appropriate timers, etc switched off
+ public void AddCommitAndPush ()
+ {
+ BufferTimer.Stop ();
+ FetchTimer.Stop ();
+
+ Add ();
+ string Message = FormatCommitMessage ();
+ if (!Message.Equals ("")) {
+ Commit (Message);
+ Fetch ();
+ Push ();
+ }
+
+ FetchTimer.Start ();
+ BufferTimer.Start ();
+
+ SparkleHelpers.CheckForUnicorns (Message);
+
+ }
+
+ // Stages the made changes
+ private void Add ()
+ {
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Staging changes...");
+ Process.StartInfo.Arguments = "add --all";
+ Process.Start ();
+ Process.WaitForExit ();
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes staged.");
+// SparkleUI.NotificationIcon.SetSyncingState ();
+// SparkleUI.NotificationIcon.SetIdleState ();
+ }
+
+ // Commits the made changes
+ public void Commit (string Message)
+ {
+ SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + Message);
+ Process.StartInfo.Arguments = "commit -m \"" + Message + "\"";
+ Process.Start ();
+ Process.WaitForExit ();
+ }
+
+ // Fetches changes from the remote repo
+ public void Fetch ()
+ {
+ FetchTimer.Stop ();
+// SparkleUI.NotificationIcon.SetSyncingState ();
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Fetching changes...");
+ Process.StartInfo.Arguments = "fetch -v";
+ Process.Start ();
+ string Output = Process.StandardOutput.ReadToEnd ().Trim (); // TODO: This doesn't work :(
+ Process.WaitForExit ();
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes fetched.");
+ if (!Output.Contains ("up to date"))
+ Rebase ();
+// SparkleUI.NotificationIcon.SetIdleState ();
+ FetchTimer.Start ();
+ }
+
+ // Merges the fetched changes
+ public void Rebase ()
+ {
+
+ Watcher.EnableRaisingEvents = false;
+
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Rebasing changes...");
+ Process.StartInfo.Arguments = "rebase origin";
+ Process.WaitForExit ();
+ Process.Start ();
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes rebased.");
+ string Output = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ // Show notification if there are updates
+ if (!Output.Contains ("up to date")) {
+
+ if (Output.Contains ("Failed to merge")) {
+
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Resolving conflict...");
+
+ Process.StartInfo.Arguments = "status";
+ Process.WaitForExit ();
+ Process.Start ();
+ Output = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ foreach (string Line in Regex.Split (Output, "\n")) {
+
+ if (Line.Contains ("needs merge")) {
+
+ string ProblemFileName = Line.Substring (Line.IndexOf (": needs merge"));
+
+ Process.StartInfo.Arguments = "checkout --ours " + ProblemFileName;
+ Process.WaitForExit ();
+ Process.Start ();
+
+ DateTime DateTime = new DateTime ();
+ string TimeStamp = DateTime.Now.ToString ("H:mm, d MMM yyyy");
+
+ File.Move (ProblemFileName,
+ ProblemFileName + " (" + UserName + " - " + TimeStamp + ")");
+
+ Process.StartInfo.Arguments
+ = "checkout --theirs " + ProblemFileName;
+ Process.WaitForExit ();
+ Process.Start ();
+
+ string ConflictTitle = "A mid-air collision happened!\n";
+ string ConflictSubtext = "Don't worry, SparkleShare made\na copy of the conflicting files.";
+
+ SparkleBubble ConflictBubble =
+ new SparkleBubble(_(ConflictTitle), _(ConflictSubtext));
+
+ ConflictBubble.Show ();
+
+ }
+
+ }
+
+ Add ();
+
+ Process.StartInfo.Arguments = "rebase --continue";
+ Process.WaitForExit ();
+ Process.Start ();
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
+ Push ();
+ Fetch ();
+
+ }
+
+ // Get the last committer e-mail
+ Process.StartInfo.Arguments = "log --format=\"%ae\" -1";
+ Process.Start ();
+ string LastCommitEmail = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ // Get the last commit message
+ Process.StartInfo.Arguments = "log --format=\"%s\" -1";
+ Process.Start ();
+ string LastCommitMessage = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ // Get the last commiter
+ Process.StartInfo.Arguments = "log --format=\"%an\" -1";
+ Process.Start ();
+ string LastCommitUserName = Process.StandardOutput.ReadToEnd ().Trim ();
+
+ string NotifySettingFile = SparkleHelpers.CombineMore (SparklePaths.SparkleConfigPath,
+ "sparkleshare.notify");
+
+ if (File.Exists (NotifySettingFile)) {
+
+ SparkleHelpers.DebugInfo ("Notification", "[" + Name + "] Showing message...");
+
+ SparkleBubble StuffChangedBubble = new SparkleBubble (LastCommitUserName, LastCommitMessage);
+ StuffChangedBubble.Icon = SparkleHelpers.GetAvatar (LastCommitEmail, 32);
+
+ // Add a button to open the folder where the changed file is
+ StuffChangedBubble.AddAction ("", _("Open Folder"),
+ delegate {
+ switch (SparklePlatform.Name) {
+ case "GNOME":
+ Process.StartInfo.FileName = "xdg-open";
+ break;
+ case "OSX":
+ Process.StartInfo.FileName = "open";
+ break;
+ }
+ Process.StartInfo.Arguments = LocalPath;
+ Process.Start ();
+ Process.StartInfo.FileName = "git";
+ } );
+
+ StuffChangedBubble.Show ();
+
+ }
+
+ }
+
+ Watcher.EnableRaisingEvents = true;
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Nothing going on...");
+
+ }
+
+ // Pushes the changes to the remote repo
+ public void Push ()
+ {
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing changes...");
+ Process.StartInfo.Arguments = "push";
+ Process.Start ();
+ Process.WaitForExit ();
+ SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed.");
+// SparkleUI.NotificationIcon.SetIdleState ();
+ }
+
+ // Ignores Repos, dotfiles, swap files and the like.
+ private bool ShouldIgnore (string FileName) {
+ if (FileName.Substring (0, 1).Equals (".") ||
+ FileName.Contains (".lock") ||
+ FileName.Contains (".git") ||
+ FileName.Contains ("/.") ||
+ Directory.Exists (LocalPath + FileName))
+ return true; // Yes, ignore it.
+ else if (FileName.Length > 3 && FileName.Substring (FileName.Length - 4).Equals (".swp"))
+ return true;
+ else return false;
+ }
+
+ // Creates a pretty commit message based on what has changed
+ private string FormatCommitMessage ()
+ {
+
+ bool DoneAddCommit = false;
+ bool DoneEditCommit = false;
+ bool DoneRenameCommit = false;
+ bool DoneDeleteCommit = false;
+ int FilesAdded = 0;
+ int FilesEdited = 0;
+ int FilesRenamed = 0;
+ int FilesDeleted = 0;
+
+ Process.StartInfo.Arguments = "status";
+ Process.Start ();
+ string Output = Process.StandardOutput.ReadToEnd ();
+
+ foreach (string Line in Regex.Split (Output, "\n")) {
+ if (Line.IndexOf ("new file:") > -1)
+ FilesAdded++;
+ if (Line.IndexOf ("modified:") > -1)
+ FilesEdited++;
+ if (Line.IndexOf ("renamed:") > -1)
+ FilesRenamed++;
+ if (Line.IndexOf ("deleted:") > -1)
+ FilesDeleted++;
+ }
+
+ foreach (string Line in Regex.Split (Output, "\n")) {
+
+ // Format message for when files are added,
+ // example: "added 'file' and 3 more."
+ if (Line.IndexOf ("new file:") > -1 && !DoneAddCommit) {
+ DoneAddCommit = true;
+ if (FilesAdded > 1)
+ return "added ‘" +
+ Line.Replace ("#\tnew file:", "").Trim () +
+ "’\nand " + (FilesAdded - 1) + " more.";
+ else
+ return "added ‘" +
+ Line.Replace ("#\tnew file:", "").Trim () + "’.";
+ }
+
+ // Format message for when files are edited,
+ // example: "edited 'file'."
+ if (Line.IndexOf ("modified:") > -1 && !DoneEditCommit) {
+ DoneEditCommit = true;
+ if (FilesEdited > 1)
+ return "edited ‘" +
+ Line.Replace ("#\tmodified:", "").Trim () +
+ "’\nand " + (FilesEdited - 1) + " more.";
+ else
+ return "edited ‘" +
+ Line.Replace ("#\tmodified:", "").Trim () + "’.";
+ }
+
+ // Format message for when files are edited,
+ // example: "deleted 'file'."
+ if (Line.IndexOf ("deleted:") > -1 && !DoneDeleteCommit) {
+ DoneDeleteCommit = true;
+ if (FilesDeleted > 1)
+ return "deleted ‘" +
+ Line.Replace ("#\tdeleted:", "").Trim () +
+ "’\nand " + (FilesDeleted - 1) + " more.";
+ else
+ return "deleted ‘" +
+ Line.Replace ("#\tdeleted:", "").Trim () + "’.";
+ }
+
+ // Format message for when files are renamed,
+ // example: "renamed 'file' to 'new name'."
+ if (Line.IndexOf ("renamed:") > -1 && !DoneRenameCommit) {
+ DoneDeleteCommit = true;
+ if (FilesRenamed > 1)
+ return "renamed ‘" +
+ Line.Replace ("#\trenamed:", "").Trim ().Replace
+ (" -> ", "’ to ‘") + "’ and " + (FilesDeleted - 1) +
+ " more.";
+ else
+ return "renamed ‘" +
+ Line.Replace ("#\trenamed:", "").Trim ().Replace
+ (" -> ", "’ to ‘") + "’.";
+ }
+
+ }
+
+ // Nothing happened:
+ return "";
+
+ }
+
+ }
+
+}