using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Castle.ActiveRecord; using Castle.ActiveRecord.Framework.Config; using Pithos.Interfaces; namespace Pithos.Core { [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))] public class StatusKeeper:IStatusChecker,IStatusKeeper { [System.ComponentModel.Composition.Import] public IPithosSettings Settings { get; set; } private JobQueue _statusUpdateQueue=new JobQueue(); public StatusKeeper() { var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var pithosDbPath = Path.Combine(appDataPath , "Pithos"); var source = GetConfiguration(pithosDbPath); ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag)); if (!File.Exists(Path.Combine(pithosDbPath ,"pithos.db"))) ActiveRecordStarter.CreateSchema(); } private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath) { var properties = new Dictionary { {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"}, {"dialect", "NHibernate.Dialect.SQLiteDialect"}, {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"}, { "proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" }, }; var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3", pithosDbPath); properties.Add("connection.connection_string", connectionString); var source = new InPlaceConfigurationSource(); source.Add(typeof (ActiveRecordBase), properties); return source; } public void StartProcessing(CancellationToken token) { _statusUpdateQueue.Start(token); } public void Stop() { _statusUpdateQueue.Stop(); } public IEnumerable StoreUnversionedFiles(ParallelQuery paths) { var existingFiles = from state in FileState.Queryable select state.FilePath; var newFiles = (from file in paths.Except(existingFiles.AsParallel()) select new FileState { FilePath = file, OverlayStatus = FileOverlayStatus.Unversioned, FileStatus=FileStatus.Created, Checksum=Signature.CalculateHash(file) } ); //var files=new ConcurrentBag(); newFiles.ForAll(state=> _statusUpdateQueue.Add(state.Save)); return newFiles.Select(state => state.FilePath);// files.GetConsumingEnumerable(); } /* private static Task CalculateHashAsync(string path) { string hash; using (var hasher = MD5.Create()) { return FileAsync.ReadAllBytes(path) .ContinueWith(t => hasher.ComputeHash(t.Result)) .ContinueWith(t => { //var hashBuilder = new StringBuilder(); return (from byte b in t.Result.AsParallel() select b.ToString("x2").ToLower()).Aggregate((s1, s2) => s1 + s2); }); } /*using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true)) { stream.ReadAllBytes() .ContinueWith(result => hasher.ComputeHash(result.Result)) .; var hashBytes = hasher.ComputeHash(stream); var hashBuilder = new StringBuilder(); foreach (byte b in hasher.ComputeHash(stream)) hashBuilder.Append(b.ToString("x2").ToLower()); hash = hashBuilder.ToString(); } return hash;#1# } */ private PithosStatus _pithosStatus=PithosStatus.InSynch; public void SetPithosStatus(PithosStatus status) { _pithosStatus = status; } public PithosStatus GetPithosStatus() { return _pithosStatus; } ConcurrentDictionary _networkState=new ConcurrentDictionary(); public void SetNetworkState(string path,NetworkState state) { _networkState[path.ToLower()] = state; //Removing may fail so we store the "None" value anyway if (state == NetworkState.None) { NetworkState oldState; _networkState.TryRemove(path, out oldState); } } public NetworkState GetNetworkState(string path) { NetworkState state ; if (_networkState.TryGetValue(path, out state)) return state; return NetworkState.None; } public T GetStatus(string path,Func getter,T defaultValue ) { try { var state = FileState.TryFind(path.ToLower()); return state == null ? defaultValue : getter(state); } catch (Exception exc) { Trace.TraceError(exc.ToString()); return defaultValue; } } /// /// Sets the status of a file, creating a new FileState entry if one doesn't already exist. /// /// /// public void SetStatus(string path,Action setter) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path", "path can't be empty"); if (setter==null) throw new ArgumentNullException("setter", "setter can't be empty"); _statusUpdateQueue.Add(() => { var filePath = path.ToLower(); var state = FileState.TryFind(filePath); if (state != null) { setter(state); state.Update(); } else { state = new FileState {FilePath = filePath}; setter(state); state.Save(); } }); } /// /// Sets the status of a file only if the file already exists /// /// /// private void UpdateStatus(string path, Action setter) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path", "path can't be empty"); if (setter == null) throw new ArgumentNullException("setter", "setter can't be empty"); _statusUpdateQueue.Add(() => { var filePath = path.ToLower(); var state = FileState.TryFind(filePath); if (state == null) { Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", filePath); return; } setter(state); state.Save(); }); } public FileOverlayStatus GetFileOverlayStatus(string path) { try { var state = FileState.TryFind(path.ToLower()); return state == null ? FileOverlayStatus.Unversioned : state.OverlayStatus; } catch (Exception exc) { Trace.TraceError(exc.ToString()); return FileOverlayStatus.Unversioned; } } public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus) { SetStatus(path,s=>s.OverlayStatus=overlayStatus); } public void RemoveFileOverlayStatus(string path) { _statusUpdateQueue.Add(() => InnerRemoveFileOverlayStatus(path)); } private static void InnerRemoveFileOverlayStatus(string path) { FileState.DeleteAll(new[] {path}); } public void RenameFileOverlayStatus(string oldPath, string newPath) { _statusUpdateQueue.Add(() => InnerRenameFileOverlayStatus(oldPath, newPath)); } private static void InnerRenameFileOverlayStatus(string oldPath, string newPath) { var state = FileState.TryFind(oldPath); if (state == null) { Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", oldPath); return; } //NOTE: This will cause problems if path is used as a key in relationships state.FilePath = newPath; state.Update(); } public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path", "path can't be empty"); UpdateStatus(path,state=> { state.FileStatus = fileStatus; state.OverlayStatus = overlayStatus; }); } public void StoreInfo(string path,ObjectInfo objectInfo) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path", "path can't be empty"); if (objectInfo==null) throw new ArgumentNullException("objectInfo", "objectInfo can't be empty"); var state = FileState.TryFind(path)??new FileState(); state.FilePath = path.ToLower(); state.Checksum = objectInfo.Hash; state.FileStatus = FileStatus.Unchanged; state.OverlayStatus = FileOverlayStatus.Normal; foreach (var tag in objectInfo.Tags) { state.Tags.Add(new FileTag { FilePath=state.FilePath, FileState=state, Value=tag.Value }); } state.Save(); } public void SetFileStatus(string path, FileStatus status) { UpdateStatus(path, state=>state.FileStatus = status); } /* private static void InnerSetFileStatus(string path, FileStatus status) { var filePath = path.ToLower(); var state = FileState.TryFind(filePath); if (state == null) { Trace.TraceWarning("[NOFILE] Unable to set status for {0}.", filePath); return; } state.FileStatus = status; state.Update(); }*/ public FileStatus GetFileStatus(string path) { var state = FileState.TryFind(path.ToLower()); return (state==null)?FileStatus.Missing:state.FileStatus ; } public void ClearFileStatus(string path) { //TODO:SHOULDN'T need both clear file status and remove overlay status _statusUpdateQueue.Add(()=> FileState.DeleteAll(new[] { path.ToLower() })); } public void UpdateFileChecksum(string path, string checksum) { var state = FileState.TryFind(path); if (state == null) { Trace.TraceWarning("[NOFILE] Unable to set checkesum for {0}.",path); return; } state.Checksum = checksum; state.Update(); } } public enum NetworkState { None, Uploading, Downloading } }