save work

This commit is contained in:
Hylke Bons 2011-05-19 17:05:58 +01:00
parent 19aca9d238
commit 2168fa2ab9
5 changed files with 643 additions and 599 deletions

View file

@ -15,7 +15,9 @@ SOURCES = \
SparkleListenerIrc.cs \
SparkleOptions.cs \
SparklePaths.cs \
SparkleRepo.cs
SparkleRepoBase.cs \
SparkleRepoGit.cs
SMARTIRC4NET_FILES_EXPANDED = $(foreach file, $(SMARTIRC4NET_FILES), $(top_builddir)/$(file))

View file

@ -37,7 +37,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="SparkleRepo.cs" />
<Compile Include="SparkleRepoBase.cs" />
<Compile Include="SparkleRepoGit.cs" />
<Compile Include="SparkleFetcherBase.cs" />
<Compile Include="SparkleFetcherGit.cs" />
<Compile Include="Defines.cs" />

View file

@ -0,0 +1,479 @@
// SparkleShare, an instant update workflow to Git.
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Diagnostics; // remove
using System.IO;
using System.Text.RegularExpressions;
using System.Timers;
namespace SparkleLib {
public enum SyncStatus {
Idle,
SyncUp,
SyncDown,
Error
}
public abstract class SparkleRepoBase {
public readonly SparkleBackend Backend;
public readonly string LocalPath;
public readonly string Name;
protected SyncStatus status;
protected bool is_buffering = false;
protected bool is_polling = true;
protected bool server_online = true;
private Timer local_timer = new Timer () { Interval = 250 };
private Timer remote_timer = new Timer () { Interval = 60000 };
private FileSystemWatcher watcher;
private SparkleListenerBase listener;
private List <double> sizebuffer = new List<double> ();
private bool has_changed = false;
private Object change_lock = new Object ();
public string Domain {
get {
Regex regex = new Regex (@"*://(.+)(/|:)*");
Match match = regex.Match (Url);
if (match.Success)
return match.Groups [1].Value;
else
return null;
}
}
public abstract string Url { get; }
public abstract bool AnyDifferences { get; }
public abstract string Identifier { get; }
public abstract string CurrentRevision { get; }
public abstract bool SyncUp ();
public abstract bool SyncDown ();
public virtual bool CheckForRemoteChanges () // HasRemoteChanges { get; } ?
{
return true;
}
public virtual List<SparkleChangeSet> GetChangeSets (int count) {
return null;
}
public virtual bool UsesNotificationCenter {
get {
return true;
}
}
public string RemoteName {
get {
return Path.GetFileNameWithoutExtension (Url);
}
}
public bool IsBuffering {
get {
return this.is_buffering;
}
}
public bool IsPolling {
get {
return this.is_polling;
}
}
public abstract bool HasUnsyncedChanges { get; set; }
public bool ServerOnline {
get {
return this.server_online;
}
}
public SyncStatus Status {
get {
return this.status;
}
}
public delegate void SyncStatusChangedEventHandler (SyncStatus new_status);
public event SyncStatusChangedEventHandler SyncStatusChanged;
public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set, string source_path);
public delegate void ConflictResolvedEventHandler ();
public delegate void ChangesDetectedEventHandler ();
public event NewChangeSetEventHandler NewChangeSet;
public event ConflictResolvedEventHandler ConflictResolved;
public event ChangesDetectedEventHandler ChangesDetected;
protected void OnConflictResolved ()
{
if (ConflictResolved != null)
ConflictResolved ();
}
// TODO: constructor (path, url, backend)
public SparkleRepoBase (string path, SparkleBackend backend)
{
LocalPath = path;
Name = Path.GetFileName (LocalPath);
Backend = backend;
SyncStatusChanged += delegate (SyncStatus status) {
this.status = status;
};
if (CurrentRevision == null) {
CreateInitialChangeSet ();
SyncUpBase ();
}
// Watch the repository's folder
this.watcher = new FileSystemWatcher (LocalPath) {
IncludeSubdirectories = true,
EnableRaisingEvents = true,
Filter = "*"
};
this.watcher.Changed += new FileSystemEventHandler (OnFileActivity);
this.watcher.Created += new FileSystemEventHandler (OnFileActivity);
this.watcher.Deleted += new FileSystemEventHandler (OnFileActivity);
this.watcher.Renamed += new RenamedEventHandler (OnFileActivity);
CreateListener ();
this.local_timer.Elapsed += delegate (object o, ElapsedEventArgs args) {
CheckForChanges ();
};
this.remote_timer.Elapsed += delegate {
if (this.is_polling) {
if (CheckForRemoteChanges ())
SyncDownBase ();
}
if (this.is_polling && !this.listener.IsConnected)
this.listener.Connect ();
if (HasUnsyncedChanges)
SyncUpBase ();
};
this.remote_timer.Start ();
this.local_timer.Start ();
// Sync up everything that changed
// since we've been off
DisableWatching ();
if (AnyDifferences) {
SyncUpBase ();
while (HasUnsyncedChanges)
SyncUpBase ();
}
EnableWatching ();
}
private void CreateListener ()
{
NotificationServerType server_type;
if (UsesNotificationCenter)
server_type = NotificationServerType.Central;
else
server_type = NotificationServerType.Own;
this.listener = new SparkleListenerIrc (Domain, Identifier, server_type);////////////
// Stop polling when the connection to the irc channel is succesful
this.listener.Connected += delegate {
this.is_polling = false;
// Check for changes manually one more time
CheckForRemoteChanges ();
// Push changes that were made since the last disconnect
if (HasUnsyncedChanges)
SyncUpBase ();
};
// Start polling when the connection to the irc channel is lost
this.listener.Disconnected += delegate {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Falling back to polling");
this.is_polling = true;
};
// Fetch changes when there is a message in the irc channel
this.listener.RemoteChange += delegate (string change_id) {
if (!change_id.Equals (CurrentRevision) && change_id.Length == 40) {
if (Status != SyncStatus.SyncUp &&
Status != SyncStatus.SyncDown &&
!this.is_buffering) {
while (this.listener.ChangesQueue > 0) {
SyncDownBase ();
this.listener.DecrementChangesQueue ();
}
}
}
};
// Start listening
this.listener.Connect ();
}
private void CheckForChanges ()
{
lock (this.change_lock) {
if (this.has_changed) {
if ( this.sizebuffer.Count >= 4)
this.sizebuffer.RemoveAt (0);
DirectoryInfo dir_info = new DirectoryInfo (LocalPath);
this.sizebuffer.Add (CalculateFolderSize (dir_info));
if ( this.sizebuffer [0].Equals (this.sizebuffer [1]) &&
this.sizebuffer [1].Equals (this.sizebuffer [2]) &&
this.sizebuffer [2].Equals (this.sizebuffer [3])) {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes have settled.");
this.is_buffering = false;
this.has_changed = false;
DisableWatching ();
while (AnyDifferences)
SyncUpBase ();//TODO look at algorithm
EnableWatching ();
}
}
}
}
// Starts a timer when something changes
private void OnFileActivity (object o, FileSystemEventArgs fse_args)
{
if (fse_args.Name.StartsWith (".git/"))
return;
WatcherChangeTypes wct = fse_args.ChangeType;
if (AnyDifferences) {
this.is_buffering = true;
// Only fire the event if the timer has been stopped.
// This prevents multiple events from being raised whilst "buffering".
if (!this.has_changed) {
if (ChangesDetected != null)
ChangesDetected ();
}
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + fse_args.Name + "'");
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes found, checking if settled.");
this.remote_timer.Stop ();
lock (this.change_lock) {
this.has_changed = true;
}
}
}
public void SyncUpBase ()
{
try {
this.local_timer.Stop ();
this.remote_timer.Stop ();
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Initiated");
//if (AnyDifferences) {
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.SyncUp);
if (SyncUp ()) {
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Done");
HasUnsyncedChanges = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.listener.Announce (CurrentRevision);
} else {
SparkleHelpers.DebugInfo ("SyncUp", "[" + Name + "] Error");
HasUnsyncedChanges = true;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
SyncDownBase ();
}
//}
} finally {
this.remote_timer.Start ();
this.local_timer.Start ();
}
}
private void SyncDownBase ()
{
SparkleHelpers.DebugInfo ("SyncDown", "[" + Name + "] Initiated");
this.remote_timer.Stop ();
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.SyncDown);
if (SyncDown ()) {
SparkleHelpers.DebugInfo ("SyncDown", "[" + Name + "] Done");
this.server_online = true;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
} else {
SparkleHelpers.DebugInfo ("SyncDown", "[" + Name + "] Error");
this.server_online = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
}
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.remote_timer.Start ();
if (NewChangeSet != null)
NewChangeSet (GetChangeSets (1) [0], LocalPath);
}
public void DisableWatching ()
{
this.watcher.EnableRaisingEvents = false;
}
public void EnableWatching ()
{
this.watcher.EnableRaisingEvents = true;
}
private string GetUserName () // TODO
{
SparkleGit git = new SparkleGit (LocalPath, "config --get user.name");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
string user_name = output.Trim ();
return user_name;
}
private string GetUserEmail ()
{
SparkleGit git = new SparkleGit (LocalPath, "config --get user.email");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
string user_email = output.Trim ();
return user_email;
}
// Recursively gets a folder's size in bytes
private double CalculateFolderSize (DirectoryInfo parent)
{
if (!System.IO.Directory.Exists (parent.ToString ()))
return 0;
double size = 0;
// Ignore the temporary 'rebase-apply' directory. This prevents potential
// crashes when files are being queried whilst the files have already been deleted.
if (parent.Name.Equals ("rebase-apply"))
return 0;
foreach (FileInfo file in parent.GetFiles()) {
if (!file.Exists)
return 0;
size += file.Length;
}
foreach (DirectoryInfo directory in parent.GetDirectories())
size += CalculateFolderSize (directory);
return size;
}
// Create an initial change set when the
// user has fetched an empty remote folder
public virtual void CreateInitialChangeSet ()
{
string file_path = Path.Combine (LocalPath, "SparkleShare.txt");
TextWriter writer = new StreamWriter (file_path);
writer.WriteLine (":)");
writer.Close ();
}
public string GetConfigItem (string name)
{
if (String.Compare (name, "sparkleshare.user.name", true) == 0)
return GetUserName ();
else if (String.Compare (name, "sparkleshare.user.email", true) == 0)
return GetUserEmail ();
else
return null;
}
public static bool IsRepo (string path)
{
return System.IO.Directory.Exists (Path.Combine (path, ".git"));
}
// Disposes all resourses of this object
public void Dispose ()
{
this.remote_timer.Dispose ();
this.local_timer.Dispose ();
this.listener.Dispose ();
}
}
}

View file

@ -20,91 +20,152 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Timers;
using Meebey.SmartIrc4net;
using Mono.Unix;
namespace SparkleLib {
public enum SyncStatus {
Idle,
SyncUp,
SyncDown,
Error
}
public class SparkleRepoGit : SparkleRepoBase {
public SparkleRepoGit (string path, SparkleBackend backend) :
base (path, backend) { }
public class SparkleRepo {
public readonly SparkleBackend Backend;
public readonly string LocalPath;
public readonly string Name;
protected SyncStatus status;
protected string revision;
protected bool is_buffering = false;
protected bool is_polling = true;
protected bool server_online = true;
private Timer remote_timer;
private Timer local_timer;
private FileSystemWatcher watcher;
private SparkleListenerBase listener;
private List <double> sizebuffer;
private bool has_changed = false;
private Object change_lock = new Object ();
// TODO: make this a regexp
public string Url {
public override string Url {
get {
SparkleGit git = new SparkleGit (LocalPath, "config --get remote.origin.url");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
return output.TrimEnd ();
}
}
// TODO: make this a regexp
public string Domain {
get {
string domain = Url.Substring (Url.IndexOf ("@") + 1);
if (domain.Contains (":"))
return domain = domain.Substring (0, domain.IndexOf (":"));
else
return domain = domain.Substring (0, domain.IndexOf ("/"));
public override string Identifier {
get {
// Because git computes a hash based on content,
// author, and timestamp; it is unique enough to
// use the hash of the first commit as an identifier
// for our folder
SparkleGit git = new SparkleGit (LocalPath, "rev-list --reverse HEAD");
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 ();
git.WaitForExit ();
return output.Substring (0, 40);
}
}
public string RemoteName {
public override string CurrentRevision {
get {
return Path.GetFileNameWithoutExtension (Url);
// Remove stale rebase-apply files because it
// makes the method return the wrong hashes.
string rebase_apply_file = SparkleHelpers.CombineMore (LocalPath, ".git", "rebase-apply");
if (File.Exists (rebase_apply_file))
File.Delete (rebase_apply_file);
SparkleGit git = new SparkleGit (LocalPath, "log -1 --format=%H");
git.Start ();
git.WaitForExit ();
if (git.ExitCode == 0) {
string output = git.StandardOutput.ReadToEnd ();
return output.TrimEnd ();
} else {
return null;
}
}
}
public string Revision {
get {
return this.revision;
public override bool CheckForRemoteChanges ()
{
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Checking for remote changes...");
SparkleGit git = new SparkleGit (LocalPath, "ls-remote origin master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode != 0)
return false;
string remote_revision = git.StandardOutput.ReadToEnd ().TrimEnd ();
if (!remote_revision.StartsWith (CurrentRevision)) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found. (" + remote_revision + ")");
return true;
} else {
return false;
}
}
public bool IsBuffering {
get {
return this.is_buffering;
public override bool SyncUp ()
{
Add ();
string message = FormatCommitMessage ();
Commit (message);
SparkleGit git = new SparkleGit (LocalPath, "push origin master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode == 0) {
return true;
//FetchRebaseAndPush ();TODO
} else {
return false;
}
}
public bool IsPolling {
get {
return this.is_polling;
public override bool SyncDown ()
{
SparkleGit git = new SparkleGit (LocalPath, "fetch -v origin master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode == 0) {
Rebase ();
return true;
} else {
return false;
}
}
public bool HasUnsyncedChanges {
public override bool AnyDifferences {
get {
SparkleGit git = new SparkleGit (LocalPath, "status --porcelain");
git.Start ();
git.WaitForExit ();
string output = git.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,
".git", "has_unsynced_changes");
@ -125,324 +186,10 @@ namespace SparkleLib {
}
}
public bool ServerOnline {
get {
return this.server_online;
}
}
public SyncStatus Status {
get {
return this.status;
}
}
public delegate void SyncStatusChangedEventHandler (SyncStatus new_status);
public event SyncStatusChangedEventHandler SyncStatusChanged;
public delegate void NewChangeSetEventHandler (SparkleChangeSet change_set, string source_path);
public delegate void ConflictResolvedEventHandler ();
public delegate void ChangesDetectedEventHandler ();
public event NewChangeSetEventHandler NewChangeSet;
public event ConflictResolvedEventHandler ConflictResolved;
public event ChangesDetectedEventHandler ChangesDetected;
public SparkleRepo (string path, SparkleBackend backend)
{
LocalPath = path;
Name = Path.GetFileName (LocalPath);
Backend = backend;
SyncStatusChanged += delegate (SyncStatus status) {
this.status = status;
};
if (IsEmpty)
this.revision = null;
else
this.revision = GetRevision ();
if (this.revision == null)
CreateInitialChangeSet ();
// Watch the repository's folder
this.watcher = new FileSystemWatcher (LocalPath) {
IncludeSubdirectories = true,
EnableRaisingEvents = true,
Filter = "*"
};
this.watcher.Changed += new FileSystemEventHandler (OnFileActivity);
this.watcher.Created += new FileSystemEventHandler (OnFileActivity);
this.watcher.Deleted += new FileSystemEventHandler (OnFileActivity);
this.watcher.Renamed += new RenamedEventHandler (OnFileActivity);
NotificationServerType server_type;
if (UsesNotificationCenter)
server_type = NotificationServerType.Central;
else
server_type = NotificationServerType.Own;
this.listener = new SparkleListenerIrc (Domain, Identifier, server_type);
// ...fetch remote changes every 60 seconds if that fails
this.remote_timer = new Timer () {
Interval = 60000
};
this.remote_timer.Elapsed += delegate {
if (this.is_polling) {
CheckForRemoteChanges ();
if (!this.listener.IsConnected)
this.listener.Connect ();
}
if (HasUnsyncedChanges)
FetchRebaseAndPush ();
};
// Stop polling when the connection to the irc channel is succesful
this.listener.Connected += delegate {
this.is_polling = false;
// Check for changes manually one more time
CheckForRemoteChanges ();
// Push changes that were made since the last disconnect
if (HasUnsyncedChanges)
Push ();
};
// Start polling when the connection to the irc channel is lost
this.listener.Disconnected += delegate {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Falling back to polling");
this.is_polling = true;
};
// Fetch changes when there is a message in the irc channel
this.listener.RemoteChange += delegate (string change_id) {
if (!change_id.Equals (this.revision) && change_id.Length == 40) {
if (Status != SyncStatus.SyncUp &&
Status != SyncStatus.SyncDown &&
!this.is_buffering) {
while (this.listener.ChangesQueue > 0) {
SyncDown ();
this.listener.DecrementChangesQueue ();
}
}
}
};
// Start listening
this.listener.Connect ();
this.sizebuffer = new List <double> ();
// Keep a timer that checks if there are changes and
// whether they have settled
this.local_timer = new Timer () {
Interval = 250
};
this.local_timer.Elapsed += delegate (object o, ElapsedEventArgs args) {
CheckForChanges ();
};
this.remote_timer.Start ();
this.local_timer.Start ();
// Add everything that changed
// since SparkleShare was stopped
AddCommitAndPush ();
if (this.revision == null)
this.revision = GetRevision ();
}
public string Identifier {
get {
// Because git computes a hash based on content,
// author, and timestamp; it is unique enough to
// use the hash of the first commit as an identifier
// for our folder
SparkleGit git = new SparkleGit (LocalPath, "rev-list --reverse HEAD");
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 ();
git.WaitForExit ();
return output.Substring (0, 40);
}
}
private void CheckForRemoteChanges ()
{
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Checking for remote changes...");
SparkleGit git = new SparkleGit (LocalPath, "ls-remote origin master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode != 0)
return;
string remote_revision = git.StandardOutput.ReadToEnd ().TrimEnd ();
if (!remote_revision.StartsWith (this.revision)) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Remote changes found. (" + remote_revision + ")");
SyncDown ();
}
}
private void CheckForChanges ()
{
lock (this.change_lock) {
if (this.has_changed) {
if ( this.sizebuffer.Count >= 4)
this.sizebuffer.RemoveAt (0);
DirectoryInfo dir_info = new DirectoryInfo (LocalPath);
this.sizebuffer.Add (CalculateFolderSize (dir_info));
if ( this.sizebuffer [0].Equals (this.sizebuffer [1]) &&
this.sizebuffer [1].Equals (this.sizebuffer [2]) &&
this.sizebuffer [2].Equals (this.sizebuffer [3])) {
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes have settled.");
this.is_buffering = false;
this.has_changed = false;
while (AnyDifferences) {
this.watcher.EnableRaisingEvents = false;
AddCommitAndPush ();
this.watcher.EnableRaisingEvents = true;
}
}
}
}
}
// Starts a timer when something changes
private void OnFileActivity (object o, FileSystemEventArgs fse_args)
{
if (fse_args.Name.StartsWith (".git/"))
return;
WatcherChangeTypes wct = fse_args.ChangeType;
if (AnyDifferences) {
this.is_buffering = true;
// Only fire the event if the timer has been stopped.
// This prevents multiple events from being raised whilst "buffering".
if (!this.has_changed) {
if (ChangesDetected != null)
ChangesDetected ();
}
SparkleHelpers.DebugInfo ("Event", "[" + Name + "] " + wct.ToString () + " '" + fse_args.Name + "'");
SparkleHelpers.DebugInfo ("Local", "[" + Name + "] Changes found, checking if settled.");
this.remote_timer.Stop ();
lock (this.change_lock) {
this.has_changed = 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 ()
{
try {
this.local_timer.Stop ();
this.remote_timer.Stop ();
if (AnyDifferences) {
Add ();
string message = FormatCommitMessage ();
Commit (message);
Push ();
} else {
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle); // TODO: in checklocalforchanges
}
} finally {
this.remote_timer.Start ();
this.local_timer.Start ();
}
}
public void FetchRebaseAndPush ()
{
CheckForRemoteChanges ();
Push ();
}
private bool AnyDifferences {
get {
SparkleGit git = new SparkleGit (LocalPath, "status --porcelain");
git.Start ();
git.WaitForExit ();
string output = git.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;
}
}
private bool IsEmpty {
get {
SparkleGit git = new SparkleGit (LocalPath, "log -1");
git.Start ();
git.WaitForExit ();
return (git.ExitCode != 0);
}
}
private string GetRevision ()
{
// Remove stale rebase-apply files because it
// makes the method return the wrong hashes.
string rebase_apply_file = SparkleHelpers.CombineMore (LocalPath, ".git", "rebase-apply");
if (File.Exists (rebase_apply_file))
File.Delete (rebase_apply_file);
SparkleGit git = new SparkleGit (LocalPath, "log -1 --format=%H");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
string revision = output.Trim ();
return revision;
}
// Stages the made changes
@ -477,59 +224,15 @@ namespace SparkleLib {
git.Start ();
git.WaitForExit ();
this.revision = GetRevision ();
SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message + " (" + this.revision + ")");
SparkleHelpers.DebugInfo ("Commit", "[" + Name + "] " + message);
// Collect garbage pseudo-randomly
if (DateTime.Now.Second % 10 == 0)
CollectGarbage ();
}
public virtual void SyncDownBase ()
{
SparkleHelpers.DebugInfo ("Sync", "[" + Name + "] Initiated");
this.remote_timer.Stop ();
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.SyncDown);
if (SyncDown ()) {
SparkleHelpers.DebugInfo ("Sync", "[" + Name + "] Done");
this.server_online = true;
this.revision = GetRevision ();
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
} else {
SparkleHelpers.DebugInfo ("Sync", "[" + Name + "] Error");
this.server_online = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
}
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.remote_timer.Start ();
}
// Fetches changes from the remote repository
public bool SyncDown ()
{
SparkleGit git = new SparkleGit (LocalPath, "fetch -v origin master");
git.Start ();
git.WaitForExit ();
if (git.ExitCode == 0) {
Rebase ();
return true;
} else {
return false;
}
}
// Merges the fetched changes
@ -539,7 +242,7 @@ namespace SparkleLib {
if (AnyDifferences) {
Add ();
string commit_message = FormatCommitMessage ();
Commit (commit_message);
}
@ -559,21 +262,17 @@ namespace SparkleLib {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Conflict resolved.");
EnableWatching ();
if (ConflictResolved != null)
ConflictResolved ();
OnConflictResolved ();
Push ();
//TODO Push ();
}
this.revision = GetRevision ();
if (NewChangeSet != null)
NewChangeSet (GetChangeSets (1) [0], LocalPath);
EnableWatching ();
}
private void ResolveConflict ()
{
// This is al list of conflict status codes that Git uses, their
@ -669,159 +368,19 @@ namespace SparkleLib {
}
// Pushes the changes to the remote repo
public void Push ()
{
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Pushing changes");
SparkleGit git = new SparkleGit (LocalPath, "push origin master");
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.SyncUp);
git.Start ();
git.WaitForExit ();
if (git.ExitCode != 0) {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes not pushed");
HasUnsyncedChanges = true;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Error);
FetchRebaseAndPush ();
} else {
SparkleHelpers.DebugInfo ("Git", "[" + Name + "] Changes pushed");
HasUnsyncedChanges = false;
if (SyncStatusChanged != null)
SyncStatusChanged (SyncStatus.Idle);
this.listener.Announce (this.revision);
}
}
public void DisableWatching ()
{
this.watcher.EnableRaisingEvents = false;
}
public void EnableWatching ()
{
this.watcher.EnableRaisingEvents = true;
}
// Gets the repository's description
private string GetDescription ()
{
string description_file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "description");
if (!File.Exists (description_file_path))
return null;
StreamReader reader = new StreamReader (description_file_path);
string description = reader.ReadToEnd ();
reader.Close ();
if (description.StartsWith ("Unnamed"))
description = null;
return description;
}
private string GetUserName ()
{
SparkleGit git = new SparkleGit (LocalPath, "config --get user.name");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
string user_name = output.Trim ();
return user_name;
}
private string GetUserEmail ()
{
SparkleGit git = new SparkleGit (LocalPath, "config --get user.email");
git.Start ();
git.WaitForExit ();
string output = git.StandardOutput.ReadToEnd ();
string user_email = output.Trim ();
return user_email;
}
// Recursively gets a folder's size in bytes
private double CalculateFolderSize (DirectoryInfo parent)
{
if (!System.IO.Directory.Exists (parent.ToString ()))
return 0;
double size = 0;
// Ignore the temporary 'rebase-apply' directory. This prevents potential
// crashes when files are being queried whilst the files have already been deleted.
if (parent.Name.Equals ("rebase-apply"))
return 0;
foreach (FileInfo file in parent.GetFiles()) {
if (!file.Exists)
return 0;
size += file.Length;
}
foreach (DirectoryInfo directory in parent.GetDirectories())
size += CalculateFolderSize (directory);
return size;
}
// Create an initial change set when the
// user has fetched an empty remote folder
protected virtual void CreateInitialChangeSet ()
{
string file_path = Path.Combine (LocalPath, "SparkleShare.txt");
TextWriter writer = new StreamWriter (file_path);
writer.WriteLine (":)");
writer.Close ();
}
public string GetConfigItem (string name)
{
if (String.Compare (name, "sparkleshare.user.name", true) == 0)
return GetUserName ();
else if (String.Compare (name, "sparkleshare.user.email", true) == 0)
return GetUserEmail ();
else
return null;
}
// Returns a list of the latest change sets
// TODO: Method needs to be made a lot faster
public virtual List <SparkleChangeSet> GetChangeSets (int count)
public override List <SparkleChangeSet> GetChangeSets (int count)
{
if (count < 1)
count = 30;
List <SparkleChangeSet> change_sets = new List <SparkleChangeSet> ();
SparkleGit git_log = new SparkleGit (LocalPath, "log -" + count + " --raw -M --date=iso");
Console.OutputEncoding = System.Text.Encoding.Unicode;
git_log.Start ();
// Reading the standard output HAS to go before
// WaitForExit, or it will hang forever on output > 4096 bytes
string output = git_log.StandardOutput.ReadToEnd ();
@ -836,14 +395,14 @@ namespace SparkleLib {
if (line.StartsWith ("commit") && j > 0) {
entries.Add (entry);
entry = "";
}
}
entry += line + "\n";
j++;
last_entry = entry;
}
entries.Add (last_entry);
Regex merge_regex = new Regex (@"commit ([a-z0-9]{40})\n" +
@ -863,19 +422,19 @@ namespace SparkleLib {
foreach (string log_entry in entries) {
Regex regex;
bool is_merge_commit = false;
if (log_entry.Contains ("\nMerge: ")) {
regex = merge_regex;
is_merge_commit = true;
} else {
regex = non_merge_regex;
}
Match match = regex.Match (log_entry);
if (match.Success) {
SparkleChangeSet change_set = new SparkleChangeSet ();
change_set.Revision = match.Groups [1].Value;
change_set.UserName = match.Groups [2].Value;
change_set.UserEmail = match.Groups [3].Value;
@ -885,16 +444,16 @@ namespace SparkleLib {
int.Parse (match.Groups [5].Value), int.Parse (match.Groups [6].Value),
int.Parse (match.Groups [7].Value), int.Parse (match.Groups [8].Value),
int.Parse (match.Groups [9].Value));
string [] entry_lines = log_entry.Split ("\n".ToCharArray ());
foreach (string entry_line in entry_lines) {
if (entry_line.StartsWith (":")) {
string change_type = entry_line [37].ToString ();
string file_path = entry_line.Substring (39);
string to_file_path;
if (change_type.Equals ("A")) {
change_set.Added.Add (file_path);
} else if (change_type.Equals ("M")) {
@ -911,7 +470,7 @@ namespace SparkleLib {
}
}
}
change_sets.Add (change_set);
}
}
@ -987,13 +546,22 @@ namespace SparkleLib {
}
public static bool IsRepo (string path)
public override void CreateInitialChangeSet ()
{
base.CreateInitialChangeSet ();
Add ();
string message = FormatCommitMessage ();
Commit (message);
}
new public static bool IsRepo (string path)
{
return System.IO.Directory.Exists (Path.Combine (path, ".git"));
}
public bool UsesNotificationCenter
public override bool UsesNotificationCenter
{
get {
string file_path = SparkleHelpers.CombineMore (LocalPath, ".git", "disable_notification_center");
@ -1002,12 +570,5 @@ namespace SparkleLib {
}
// Disposes all resourses of this object
public void Dispose ()
{
this.remote_timer.Dispose ();
this.local_timer.Dispose ();
this.listener.Dispose ();
}
}
}

View file

@ -33,7 +33,7 @@ namespace SparkleShare {
public abstract class SparkleController {
public List <SparkleRepo> Repositories;
public List <SparkleRepoBase> Repositories;
public string FolderSize;
public bool FirstRun;
public readonly string SparklePath;
@ -162,7 +162,7 @@ namespace SparkleShare {
// FIXME: this is broken :\
if (OnInvitation != null)
OnInvitation (server, folder, token);
} else if (SparkleRepo.IsRepo (args.FullPath)) {
} else if (SparkleRepoBase.IsRepo (args.FullPath)) {
AddRepository (args.FullPath);
if (FolderListChanged != null)
@ -240,7 +240,7 @@ namespace SparkleShare {
get {
List <string> folders = new List <string> ();
foreach (SparkleRepo repo in Repositories)
foreach (SparkleRepoBase repo in Repositories)
folders.Add (repo.LocalPath);
return folders;
@ -253,7 +253,7 @@ namespace SparkleShare {
string path = Path.Combine (SparklePaths.SparklePath, name);
int log_size = 30;
foreach (SparkleRepo repo in Repositories) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.LocalPath.Equals (path))
return repo.GetChangeSets (log_size);
}
@ -477,7 +477,7 @@ namespace SparkleShare {
// Fires events for the current syncing state
private void UpdateState ()
{
foreach (SparkleRepo repo in Repositories) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Status == SyncStatus.SyncDown ||
repo.Status == SyncStatus.SyncUp ||
repo.IsBuffering) {
@ -512,10 +512,11 @@ namespace SparkleShare {
// and use GitBackend.IsValidFolder (string path);
// Check if the folder is a Git repository TODO: remove later
if (!SparkleRepo.IsRepo (folder_path))
if (!SparkleRepoBase.IsRepo (folder_path))
return;
SparkleRepo repo = new SparkleRepo (folder_path, SparkleBackend.DefaultBackend);
//TODO
SparkleRepoBase repo = new SparkleRepoGit (folder_path, SparkleBackend.DefaultBackend);
repo.NewChangeSet += delegate (SparkleChangeSet change_set, string repository_path) {
string message = FormatMessage (change_set);
@ -554,7 +555,7 @@ namespace SparkleShare {
string folder_name = Path.GetFileName (folder_path);
for (int i = 0; i < Repositories.Count; i++) {
SparkleRepo repo = Repositories [i];
SparkleRepoBase repo = Repositories [i];
if (repo.Name.Equals (folder_name)) {
Repositories.Remove (repo);
@ -570,7 +571,7 @@ namespace SparkleShare {
// folders in the SparkleShare folder
private void PopulateRepositories ()
{
Repositories = new List <SparkleRepo> ();
Repositories = new List <SparkleRepoBase> ();
foreach (string folder_path in Directory.GetDirectories (SparklePaths.SparklePath))
AddRepository (folder_path);
@ -1056,7 +1057,7 @@ namespace SparkleShare {
// quits if safe
public void TryQuit ()
{
foreach (SparkleRepo repo in Repositories) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Status == SyncStatus.SyncUp ||
repo.Status == SyncStatus.SyncDown ||
repo.IsBuffering) {
@ -1074,7 +1075,7 @@ namespace SparkleShare {
public void Quit ()
{
foreach (SparkleRepo repo in Repositories)
foreach (SparkleRepoBase repo in Repositories)
repo.Dispose ();
Environment.Exit (0);