From bf78e39f749d2a9aec00caac7b8df604a63428d8 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Sun, 20 Jun 2010 22:05:11 +0100 Subject: [PATCH] Warn the user for speed issues when uploading certain types of images --- SparkleShare/SparkleHelpers.cs | 35 ++- SparkleShare/SparkleRepo.cs | 462 +++++++++++++++++++++++++++++++++ 2 files changed, 489 insertions(+), 8 deletions(-) create mode 100644 SparkleShare/SparkleRepo.cs 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 ""; + + } + + } + +}