// 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 Lesser 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.IO; using System.Threading; namespace Sparkles { public class SparkleFetcherInfo { public string Address; // TODO: Uri object public string RemotePath; public string Fingerprint; public string Backend; public string TargetDirectory; public bool FetchPriorHistory; public string AnnouncementsUrl; // TODO: Uri object } public abstract class BaseFetcher { public event Action Started = delegate { }; public event Action Failed = delegate { }; public event FinishedEventHandler Finished = delegate { }; public delegate void FinishedEventHandler (StorageType storage_type, string [] warnings); public event ProgressChangedEventHandler ProgressChanged = delegate { }; public delegate void ProgressChangedEventHandler (double percentage, double speed, string information); public abstract bool Fetch (); public abstract void Stop (); public bool IsActive { get; protected set; } public double ProgressPercentage { get; private set; } public double ProgressSpeed { get; private set; } protected abstract bool IsFetchedRepoEmpty { get; } public StorageType FetchedRepoStorageType { get; protected set; } public abstract bool IsFetchedRepoPasswordCorrect (string password); public abstract void EnableFetchedRepoCrypto (string password); public readonly List AvailableStorageTypes = new List (); public Uri RemoteUrl { get; protected set; } public string RequiredFingerprint { get; protected set; } public readonly bool FetchPriorHistory; public string TargetFolder { get; protected set; } public SparkleFetcherInfo OriginalFetcherInfo; protected List warnings = new List (); protected List errors = new List (); public string [] Warnings { get { return warnings.ToArray (); } } public string [] Errors { get { return errors.ToArray (); } } protected BaseFetcher (SparkleFetcherInfo info) { FetchedRepoStorageType = StorageType.Unknown; AvailableStorageTypes.Add ( new StorageTypeInfo (StorageType.Plain, "Plain Storage", "Nothing fancy;\nmaximum compatibility")); OriginalFetcherInfo = info; RequiredFingerprint = info.Fingerprint; FetchPriorHistory = info.FetchPriorHistory; string remote_path = info.RemotePath.Trim ("/".ToCharArray ()); string address = info.Address; if (address.EndsWith ("/", StringComparison.InvariantCulture)) address = address.Substring (0, address.Length - 1); if (!remote_path.StartsWith ("/", StringComparison.InvariantCulture)) remote_path = "/" + remote_path; if (!address.Contains ("://")) address = "ssh://" + address; TargetFolder = info.TargetDirectory; RemoteUrl = new Uri (address + remote_path); IsActive = false; } Thread thread; public void Start () { IsActive = true; Started (); Logger.LogInfo ("Fetcher", TargetFolder + " | Fetching folder: " + RemoteUrl); try { if (Directory.Exists (TargetFolder)) Directory.Delete (TargetFolder, recursive: true); } catch (IOException) { errors.Add ("\"" + TargetFolder + "\" is read-only."); Failed (); return; } thread = new Thread (() => { if (Fetch ()) { Thread.Sleep (500); Logger.LogInfo ("Fetcher", "Finished"); IsActive = false; Finished (FetchedRepoStorageType, Warnings); } else { Thread.Sleep (500); if (IsActive) { Logger.LogInfo ("Fetcher", "Failed"); Failed (); } else { Logger.LogInfo ("Fetcher", "Failed: cancelled by user"); } IsActive = false; } }); thread.Start (); } public void Complete () { if (FetchedRepoStorageType == StorageType.Unknown) { Complete (StorageType.Plain); return; } this.Complete (FetchedRepoStorageType); } public virtual string Complete (StorageType storage_type) { FetchedRepoStorageType = storage_type; if (IsFetchedRepoEmpty) CreateInitialChangeSet (); return Path.GetRandomFileName ().SHA256 (); } // Create an initial change set when the // user has fetched an empty remote folder void CreateInitialChangeSet () { string n = Environment.NewLine; string file_path = Path.Combine (TargetFolder, "SparkleShare.txt"); var uri_builder = new UriBuilder (RemoteUrl); // Don't expose possible username or password if (RemoteUrl.Scheme.StartsWith ("http", StringComparison.InvariantCultureIgnoreCase)) { uri_builder.UserName = ""; uri_builder.Password = ""; } string text = "Congratulations, you've successfully created a SparkleShare repository!" + n + n + "Any files you add or change in this folder will be automatically synced to " + n + uri_builder.Uri + " and everyone connected to it." + n + n + "SparkleShare is an Open Source software program that helps people collaborate and " + n + "share files. If you like what we do, consider buying us a beer: http://www.sparkleshare.org/" + n + n + "Have fun! :)" + n; if (FetchedRepoStorageType == StorageType.Encrypted) text = text.Replace ("a SparkleShare repository", "an encrypted SparkleShare repository"); File.WriteAllText (file_path, text); } DateTime progress_last_change = DateTime.Now; protected void OnProgressChanged (double percentage, double speed, string information) { // Only trigger the ProgressChanged event once per second if (DateTime.Compare (this.progress_last_change, DateTime.Now.Subtract (new TimeSpan (0, 0, 0, 1))) >= 0) return; ProgressChanged (percentage, speed, information); } public static string GetBackend (string address) { if (address.StartsWith ("ssh+", StringComparison.InvariantCultureIgnoreCase)) { string backend = address.Substring (0, address.IndexOf ("://", StringComparison.InvariantCulture)); backend = backend.Substring (4); return char.ToUpper (backend [0]) + backend.Substring (1); } return "Git"; } public virtual string FormatName () { return Path.GetFileName (RemoteUrl.AbsolutePath); } public void Dispose () { if (thread != null) thread.Abort (); } protected string [] ExcludeRules = { "*.autosave", // Various autosaving apps "*~", // gedit and emacs ".~lock.*", // LibreOffice "*.part", "*.crdownload", // Firefox and Chromium temporary download files ".*.sw[a-z]", "*.un~", "*.swp", "*.swo", // vi(m) ".directory", // KDE "*.kate-swp", // Kate ".DS_Store", "Icon\r", "._*", ".Spotlight-V100", ".Trashes", // Mac OS X "*(Autosaved).graffle", // Omnigraffle "Thumbs.db", "Desktop.ini", // Windows "~*.tmp", "~*.TMP", "*~*.tmp", "*~*.TMP", // MS Office "~*.ppt", "~*.PPT", "~*.pptx", "~*.PPTX", "~*.xls", "~*.XLS", "~*.xlsx", "~*.XLSX", "~*.doc", "~*.DOC", "~*.docx", "~*.DOCX", "~$*", "*.a$v", // QuarkXPress "*/CVS/*", ".cvsignore", "*/.cvsignore", // CVS "/.svn/*", "*/.svn/*", // Subversion "/.hg/*", "*/.hg/*", "*/.hgignore", // Mercurial "/.bzr/*", "*/.bzr/*", "*/.bzrignore", // Bazaar "*<*", "*>*", "*:*", "*\"*", "*|*", "*\\?*", "*\\**", "*\\\\*" // Not allowed on Windows systems, // see (http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx) }; } }