Revision 174bbb6e

b/trunk/Pithos.Client.WPF/Configuration/PithosSettings.cs
231 231
        {
232 232
            _settings.Reload();
233 233
        }
234

  
235
        public void Reset()
236
        {
237
            _settings.Reset();
238
            _settings.Save();
239
        }
234 240
    }
235 241
}
b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj
30 30
    <IsWebBootstrapper>false</IsWebBootstrapper>
31 31
    <UseApplicationTrust>false</UseApplicationTrust>
32 32
    <BootstrapperEnabled>true</BootstrapperEnabled>
33
    <CodeContractsAssemblyMode>0</CodeContractsAssemblyMode>
33 34
  </PropertyGroup>
34 35
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
35 36
    <PlatformTarget>x86</PlatformTarget>
......
102 103
    <CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>
103 104
    <CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
104 105
    <CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
106
    <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
107
    <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface>
108
    <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure>
109
    <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires>
110
    <CodeContractsRuntimeSkipQuantifiers>False</CodeContractsRuntimeSkipQuantifiers>
111
    <CodeContractsRunCodeAnalysis>True</CodeContractsRunCodeAnalysis>
112
    <CodeContractsNonNullObligations>True</CodeContractsNonNullObligations>
113
    <CodeContractsBoundsObligations>True</CodeContractsBoundsObligations>
114
    <CodeContractsArithmeticObligations>True</CodeContractsArithmeticObligations>
115
    <CodeContractsEnumObligations>True</CodeContractsEnumObligations>
116
    <CodeContractsRedundantAssumptions>True</CodeContractsRedundantAssumptions>
117
    <CodeContractsRunInBackground>False</CodeContractsRunInBackground>
118
    <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies>
119
    <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine>
120
    <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs>
121
    <CodeContractsCustomRewriterAssembly />
122
    <CodeContractsCustomRewriterClass />
123
    <CodeContractsLibPaths />
124
    <CodeContractsExtraRewriteOptions />
125
    <CodeContractsExtraAnalysisOptions />
126
    <CodeContractsBaseLineFile />
127
    <CodeContractsCacheAnalysisResults>True</CodeContractsCacheAnalysisResults>
128
    <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel>
129
    <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly>
130
    <CodeContractsAnalysisWarningLevel>0</CodeContractsAnalysisWarningLevel>
105 131
  </PropertyGroup>
106 132
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Premium Debug|AnyCPU'">
107 133
    <DebugSymbols>true</DebugSymbols>
b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs
535 535
				monitor.Pause = !monitor.Pause;
536 536
				isPaused = monitor.Pause;
537 537
			}
538
                        
538 539

  
539 540
			PauseSyncCaption = isPaused ? "Resume syncing" : "Pause syncing";
540 541
			var iconKey = isPaused? "TraySyncPaused" : "TrayInSynch";
......
557 558
		private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
558 559
			{
559 560
				new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
560
				new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
561
				new StatusInfo(PithosStatus.PollSyncing, "Polling Files", "TraySynching"),
562
                new StatusInfo(PithosStatus.LocalSyncing, "Syncing Files", "TraySynching"),
561 563
				new StatusInfo(PithosStatus.SyncPaused, "Sync Paused", "TraySyncPaused")
562 564
			}.ToDictionary(s => s.Status);
563 565

  
564 566
		readonly IWindowManager _windowManager;
565 567
		
568
        //private int _syncCount=0;
569

  
570

  
571
        private PithosStatus _pithosStatus = PithosStatus.Disconnected;
572

  
573
        public void SetPithosStatus(PithosStatus status)
574
        {
575
            if (_pithosStatus == PithosStatus.LocalSyncing && status == PithosStatus.PollComplete)
576
                return;
577
            if (_pithosStatus == PithosStatus.PollSyncing && status == PithosStatus.LocalComplete)
578
                return;
579
            if (status == PithosStatus.LocalComplete || status == PithosStatus.PollComplete)
580
                _pithosStatus = PithosStatus.InSynch;
581
            else
582
                _pithosStatus = status;
583
            UpdateStatus();
584
        }
585

  
586
        public void SetPithosStatus(PithosStatus status,string message)
587
        {
588
            StatusMessage = message;
589
            SetPithosStatus(status);
590
        }
591

  
592

  
566 593

  
567 594
		///<summary>
568 595
		/// Updates the visual status indicators of the application depending on status changes, e.g. icon, stat		
569 596
		///</summary>
570 597
		public void UpdateStatus()
571 598
		{
572
			var pithosStatus = _statusChecker.GetPithosStatus();
573 599

  
574
			if (_iconNames.ContainsKey(pithosStatus))
600
			if (_iconNames.ContainsKey(_pithosStatus))
575 601
			{
576
				var info = _iconNames[pithosStatus];
602
				var info = _iconNames[_pithosStatus];
577 603
				StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
578

  
579

  
580

  
581
				StatusMessage = String.Format("Pithos {0}\r\n{1}", _fileVersion.Value.FileVersion,info.StatusText);
582 604
			}
583
			
584
			//_events.Publish(new Notification { Title = "Start", Message = "Start Monitoring", Level = TraceLevel.Info});
605

  
606
            if (_pithosStatus == PithosStatus.InSynch)
607
                StatusMessage = "All files up to date";
585 608
		}
586 609

  
587 610

  
......
838 861
		    var progress = notification as ProgressNotification;
839 862
		    if (progress != null)
840 863
		    {
841
		        StatusMessage = String.Format("Pithos {0}\r\n{1} {2} of {3} - {4}",
864
		        StatusMessage = String.Format("Pithos {0}\r\n{1} {2:p2} of {3} - {4}",
842 865
		                                      _fileVersion.Value.FileVersion, 
843 866
                                              progress.Action,
844 867
		                                      progress.Block/(double)progress.TotalBlocks,
......
847 870
		        return;
848 871
		    }
849 872

  
873
		    var info = notification as StatusNotification;
874
            if (info != null)
875
            {
876
                StatusMessage = String.Format("Pithos {0}\r\n{1}",
877
                                              _fileVersion.Value.FileVersion,
878
                                              info.Title);
879
                return;
880
            }
850 881
			if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
851 882
				return;
852 883

  
853
			BalloonIcon icon;
854
			switch (notification.Level)
855
			{
856
				case TraceLevel.Error:
857
					icon = BalloonIcon.Error;
858
					break;
859
				case TraceLevel.Info:
860
				case TraceLevel.Verbose:
861
					icon = BalloonIcon.Info;
862
					break;
863
				case TraceLevel.Warning:
864
					icon = BalloonIcon.Warning;
865
					break;
866
				default:
867
					icon = BalloonIcon.None;
868
					break;
869
			}
870
			
871
			if (Settings.ShowDesktopNotifications)
872
			{
873
				var tv = (ShellView) GetView();
874
			    System.Action clickAction = null;
875
                if (notification is ExpirationNotification)
876
                {
877
                    clickAction = ()=>ShowPreferences("AccountTab");
878
                }
879
				var balloon=new PithosBalloon{Title=notification.Title,Message=notification.Message,Icon=icon,ClickAction=clickAction};
880
				tv.TaskbarView.ShowCustomBalloon(balloon,PopupAnimation.Fade,4000);
881
//				tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
882
			}
884
			ShowBalloonFor(notification);
883 885
		}
884
		#endregion
886

  
887
	    private void ShowBalloonFor(Notification notification)
888
	    {
889
            Contract.Requires(notification!=null);
890
            
891
            if (!Settings.ShowDesktopNotifications) 
892
                return;
893
            
894
            BalloonIcon icon;
895
	        switch (notification.Level)
896
	        {
897
	            case TraceLevel.Info:
898
	            case TraceLevel.Verbose:
899
	                return;
900
                case TraceLevel.Error:
901
                    icon = BalloonIcon.Error;
902
                    break;
903
                case TraceLevel.Warning:
904
	                icon = BalloonIcon.Warning;
905
	                break;
906
	            default:
907
	                return;
908
	        }
909

  
910
	        var tv = (ShellView) GetView();
911
	        System.Action clickAction = null;
912
	        if (notification is ExpirationNotification)
913
	        {
914
	            clickAction = () => ShowPreferences("AccountTab");
915
	        }
916
	        var balloon = new PithosBalloon
917
	                          {
918
	                              Title = notification.Title,
919
	                              Message = notification.Message,
920
	                              Icon = icon,
921
	                              ClickAction = clickAction
922
	                          };
923
	        tv.TaskbarView.ShowCustomBalloon(balloon, PopupAnimation.Fade, 4000);
924
	    }
925

  
926
	    #endregion
885 927

  
886 928
		public void Handle(ShowFilePropertiesEvent message)
887 929
		{
b/trunk/Pithos.Client.WPF/app.config
176 176
			<appender-ref ref="TraceAppender"/>
177 177
		</logger>
178 178

  
179
		<logger name="Pithos" additivity="false">
180
			<level value="DEBUG"/>
181
			<appender-ref ref="TraceAppender"/>
182
		</logger>
183

  
179 184
		<root>
180 185
			<level value="DEBUG" />
181 186
			<appender-ref ref="LossyFileAppender" />
b/trunk/Pithos.Core.Test/MockStatusKeeper.cs
6 6
using System.Linq;
7 7
using System.Text;
8 8
using System.Threading;
9
using System.Threading.Tasks;
9 10
using Pithos.Interfaces;
10 11
using Pithos.Network;
11 12

  
......
146 147
            throw new NotImplementedException();
147 148
        }
148 149

  
150
        public void EnsureFileState(string path)
151
        {
152
            throw new NotImplementedException();
153
        }
154

  
149 155

  
150 156
        private PithosStatus _pithosStatus = PithosStatus.InSynch;
151 157
        public void SetPithosStatus(PithosStatus status)
......
158 164
            return _pithosStatus;
159 165
        }
160 166

  
161
        public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
167
        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string shortHash = null)
162 168
        {
163 169
            _overlayCache[path] = overlayStatus;
170
            return Task.Factory.StartNew(()=>{});
164 171
        }
165 172

  
166 173
        public void RemoveFileOverlayStatus(string path)
......
177 184
            _overlayCache.TryRemove(oldPath, out value);
178 185
        }
179 186

  
180
        public void UpdateFileChecksum(string path, string checksum)
187
        public void UpdateFileChecksum(string path, string shortHash, string checksum)
181 188
        {
182 189
            _checksums[path] = checksum;
183 190
        }
b/trunk/Pithos.Core/Agents/Agent.cs
53 53
    public class Agent<TMessage> : IDisposable
54 54
    {
55 55
        private readonly ConcurrentQueue<TMessage> _queue;
56
        private readonly AsyncProducerConsumerCollection<TMessage> _messages;
56
        //private readonly AsyncCollection<TMessage> _messages;
57
        private readonly AsyncCollection<TMessage> _messages;
57 58
        private readonly CancellationTokenSource _cancelSource = new CancellationTokenSource();
58 59
        public CancellationToken CancellationToken;
59 60

  
......
63 64
        public Agent(Action<Agent<TMessage>> action)
64 65
        {
65 66
            _queue=new ConcurrentQueue<TMessage>();
66
            _messages = new AsyncProducerConsumerCollection<TMessage>(_queue);            
67
            _messages = new AsyncCollection<TMessage>(_queue);            
67 68
            _process = action;
68 69
            CancellationToken = _cancelSource.Token;
69 70
        }
......
78 79
            _messages.Add(message);
79 80
        }
80 81

  
82
        ConcurrentDictionary<TMessage,TaskCompletionSource<object>> _awaiters=new ConcurrentDictionary<TMessage,TaskCompletionSource<object>>();
83

  
84
        public Task PostAndAwait(TMessage message)
85
        {            
86
            var tcs = new TaskCompletionSource<object>();
87
            _awaiters[message] = tcs;
88
            Post(message);
89
            return tcs.Task;
90
        }
91

  
81 92
        /// <summary>
82 93
        /// Receives a message asynchronously, optionally with a timeout. Receive throws a TimeoutException if the timeout expires
83 94
        /// </summary>
......
87 98
            return _messages.Take();
88 99
        }
89 100

  
101
        public void NotifyComplete(TMessage message)
102
        {
103
            TaskCompletionSource<object> tcs;
104
            if (_awaiters.TryRemove(message,out tcs))
105
                tcs.SetResult(null);
106
        }
107

  
90 108

  
91 109

  
92 110
        /// <summary>
......
148 166
            if (disposing)
149 167
            {
150 168
                Stop();
151
                _messages.Dispose();
152 169
                _cancelSource.Dispose();
153 170
            }
154 171
        }
......
209 226
                        onError(ex);
210 227
                }
211 228
                return default(T);
212
            },CancellationToken);
229
            });
213 230
        }
214 231
    }
215 232
}
b/trunk/Pithos.Core/Agents/AsyncCollection.cs
1
//--------------------------------------------------------------------------
2
// 
3
//  Copyright (c) Microsoft Corporation.  All rights reserved. 
4
// 
5
//  File: AsyncProducerConsumerCollection.cs
6
//
7
//--------------------------------------------------------------------------
8

  
9
using System;
10
using System.Collections.Concurrent;
11
using System.Diagnostics;
12
using System.Threading;
13
using System.Threading.Tasks;
14

  
15
namespace Pithos.Core
16
{
17
    /// <summary>Provides an asynchronous producer/consumer collection.</summary>
18
    [DebuggerDisplay("Count={CurrentCount}")]
19
    public sealed class AsyncCollection<T> 
20
    {
21
        /// <summary>Asynchronous semaphore used to keep track of asynchronous work.</summary>
22
        private AsyncSemaphore _semaphore = new AsyncSemaphore(0);
23
        /// <summary>The data stored in the collection.</summary>
24
        private IProducerConsumerCollection<T> _collection;
25

  
26
        /// <summary>Initializes the asynchronous producer/consumer collection to store data in a first-in-first-out (FIFO) order.</summary>
27
        public AsyncCollection() : this(new ConcurrentQueue<T>()) { }
28

  
29
        /// <summary>Initializes the asynchronous producer/consumer collection.</summary>
30
        /// <param name="collection">The underlying collection to use to store data.</param>
31
        public AsyncCollection(IProducerConsumerCollection<T> collection)
32
        {
33
            if (collection == null) throw new ArgumentNullException("collection");
34
            _collection = collection;
35
        }
36

  
37
        /// <summary>Adds an element to the collection.</summary>
38
        /// <param name="item">The item to be added.</param>
39
        public void Add(T item)
40
        {
41
            if (_collection.TryAdd(item))
42
                Task.Factory.StartNew(s => ((AsyncSemaphore)s).Release(),
43
                    _semaphore, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
44
            else throw new InvalidOperationException("Invalid collection");
45
        }
46

  
47
        /// <summary>Takes an element from the collection asynchronously.</summary>
48
        /// <returns>A Task that represents the element removed from the collection.</returns>
49
        public Task<T> Take()
50
        {
51
            return _semaphore.WaitAsync().ContinueWith(_ =>
52
            {
53
                T result;
54
                if (!_collection.TryTake(out result)) throw new InvalidOperationException("Invalid collection");
55
                return result;
56
            }, TaskContinuationOptions.OnlyOnRanToCompletion);
57
        }
58

  
59
        /// <summary>Gets the number of elements in the collection.</summary>
60
        public int Count { get { return _collection.Count; } }
61

  
62
    }
63
}
b/trunk/Pithos.Core/Agents/AsyncManualResetEvent.cs
87 87
            while (true)
88 88
            {
89 89
                var tcs = _tcs;
90
                if (!tcs.Task.IsCompleted ||
91
                    Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
90
                if (!tcs.Task.IsCompleted)
91
                    return;
92
#pragma warning disable 420
93
                if (Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
94
#pragma warning restore 420
92 95
                    return;
93 96
            }
94 97
        }
b/trunk/Pithos.Core/Agents/AsyncSemaphore.cs
1
// -----------------------------------------------------------------------
2
// <copyright file="AsyncSemaphore.cs" company="Microsoft">
3
// TODO: Update copyright text.
4
// </copyright>
5
// -----------------------------------------------------------------------
6

  
7
using System.Threading.Tasks;
8

  
9
namespace Pithos.Core
10
{
11
    using System;
12
    using System.Collections.Generic;
13
    using System.Linq;
14
    using System.Text;
15

  
16
    /// <summary>
17
    /// TODO: Update summary.
18
    /// </summary>
19
    public class AsyncSemaphore
20
    {
21
        private readonly static Task s_completed = TaskEx.FromResult(true);
22
        private readonly Queue<TaskCompletionSource<bool>> m_waiters = new Queue<TaskCompletionSource<bool>>();
23
        private int m_currentCount; 
24

  
25
        public AsyncSemaphore(int initialCount)
26
        {
27
            if (initialCount < 0) throw new ArgumentOutOfRangeException("initialCount");
28
            m_currentCount = initialCount; 
29
        }
30
        public Task WaitAsync()
31
        {
32

  
33
            lock (m_waiters)
34
            {
35
                if (m_currentCount > 0)
36
                {
37
                    --m_currentCount;
38
                    return s_completed;
39
                }
40
                else
41
                {
42
                    var waiter = new TaskCompletionSource<bool>();
43
                    m_waiters.Enqueue(waiter);
44
                    return waiter.Task;
45
                }
46
            }
47

  
48
        }
49

  
50
        public void Release()
51
        {
52
            TaskCompletionSource<bool> toRelease = null;
53
            lock (m_waiters)
54
            {
55
                if (m_waiters.Count > 0)
56
                    toRelease = m_waiters.Dequeue();
57
                else
58
                    ++m_currentCount;
59
            }
60
            if (toRelease != null)
61
                toRelease.SetResult(true);
62
        }
63
    }
64
}
b/trunk/Pithos.Core/Agents/BlockExtensions.cs
43 43
using System.Collections.Generic;
44 44
using System.Diagnostics.Contracts;
45 45
using System.Linq;
46
using System.Security.Cryptography;
46 47
using System.Text;
47 48
using System.IO;
48 49
using System.Text.RegularExpressions;
......
87 88
           return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm).TopHash.ToHashString();
88 89

  
89 90
        }
91

  
92
       /// <summary>
93
       ///Calculates a simple hash for an entire file
94
       /// </summary>
95
       /// <param name="info">The file to hash</param>
96
       /// <param name="hasher">The hash algorithm to use</param>
97
       /// <returns>A hash value for the entire file. An empty string if the file does not exist.</returns>
98
       public static string ComputeShortHash(this FileInfo info, HashAlgorithm hasher)
99
       {
100
           Contract.Requires(info != null);
101
           Contract.Requires(hasher!= null);           
102

  
103
           if (!info.Exists)
104
               return String.Empty;
105

  
106
           using (var stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
107
           {
108
               var hash = hasher.ComputeHash(stream);
109
               var hashString = hash.ToHashString();
110
               return hashString;
111
           }
112
       }
113

  
114
        public static string ComputeShortHash(this FileInfo info)
115
       {
116
           Contract.Requires(info != null);           
117

  
118
           using (var hasher=HashAlgorithm.Create("sha1"))
119
           {               
120
               return ComputeShortHash(info,hasher);
121
           }
122
       }
123

  
90 124
    }
91 125
}
b/trunk/Pithos.Core/Agents/BlockUpdater.cs
207 207
            if (File.Exists(FilePath))
208 208
                File.Replace(TempPath, FilePath, null, true);
209 209
            else
210
            {
211
                var targetDirectory = Path.GetDirectoryName(FilePath);
212
                if (!Directory.Exists(targetDirectory))
213
                    Directory.CreateDirectory(targetDirectory);
210 214
                File.Move(TempPath, FilePath);
215
            }
211 216
        }
212 217

  
213 218
        private void ClearBlocks()
b/trunk/Pithos.Core/Agents/CloudTransferAction.cs
71 71
        public readonly DateTime Created = DateTime.Now;
72 72

  
73 73

  
74
        public Lazy<string> LocalHash { get; protected set; }
75
        public Lazy<string> TopHash { get; set; }
74
        public Lazy<TreeHash> TreeHash { get; protected set; }
75
        //public Lazy<string> TopHash { get; set; }
76 76

  
77 77

  
78 78
        [ContractInvariantMethod]
......
102 102
            LocalFile = localFile.WithProperCapitalization();
103 103
            CloudFile = cloudFile;
104 104
            FileState = state;
105
            if (LocalFile != null)
106
            {
105
            
106
            if (LocalFile == null) 
107
                return;
107 108

  
108
                LocalHash = new Lazy<string>(() => LocalFile.CalculateHash(blockSize,algorithm),
109
                                             LazyThreadSafetyMode.ExecutionAndPublication);
110
            }
109
            TreeHash = new Lazy<TreeHash>(() => Signature.CalculateTreeHash(LocalFile, blockSize,algorithm),
110
                                            LazyThreadSafetyMode.ExecutionAndPublication);
111 111
        }
112 112

  
113 113
        //Calculate the download path for the cloud file
......
239 239
            OldCloudFile = CreateObjectInfoFor(accountInfo, oldFile);
240 240

  
241 241
            //This is a rename operation, a hash will not be used
242
            LocalHash = new Lazy<string>(() => String.Empty, LazyThreadSafetyMode.ExecutionAndPublication);
242
            TreeHash = new Lazy<TreeHash>(() => Network.TreeHash.Empty, LazyThreadSafetyMode.ExecutionAndPublication);
243 243
        }
244 244

  
245 245
        public override string ToString()
b/trunk/Pithos.Core/Agents/FileAgent.cs
41 41
#endregion
42 42
using System;
43 43
using System.Collections.Generic;
44
using System.ComponentModel.Composition;
45
using System.Diagnostics;
46 44
using System.Diagnostics.Contracts;
47 45
using System.IO;
48 46
using System.Linq;
49 47
using System.Reflection;
50
using System.Text;
51 48
using System.Threading.Tasks;
52 49
using Pithos.Interfaces;
53 50
using Pithos.Network;
54 51
using log4net;
55
using log4net.Core;
56 52

  
57 53
namespace Pithos.Core.Agents
58 54
{
......
67 63

  
68 64
        //[Import]
69 65
        public IStatusKeeper StatusKeeper { get; set; }
66

  
67
        public IStatusNotification StatusNotification { get; set; }
70 68
        //[Import]
71 69
        public IPithosWorkflow Workflow { get; set; }
72 70
        //[Import]
......
89 87

  
90 88
            AccountInfo = accountInfo;
91 89
            RootPath = rootPath;
92
            _watcher = new FileSystemWatcher(rootPath) {IncludeSubdirectories = true};
90
            _watcher = new FileSystemWatcher(rootPath) {IncludeSubdirectories = true,InternalBufferSize=8*4096};
93 91
            _adapter = new FileSystemWatcherAdapter(_watcher);
94 92

  
95 93
            _adapter.Changed += OnFileEvent;
96 94
            _adapter.Created += OnFileEvent;
97 95
            _adapter.Deleted += OnFileEvent;
98
            _adapter.Renamed += OnRenameEvent;
96
            //_adapter.Renamed += OnRenameEvent;
99 97
            _adapter.Moved += OnMoveEvent;
100 98
            _watcher.EnableRaisingEvents = true;
101 99

  
......
135 133

  
136 134
            try
137 135
            {
136
                //StatusKeeper.EnsureFileState(state.Path);
137
                
138 138
                UpdateFileStatus(state);
139 139
                UpdateOverlayStatus(state);
140 140
                UpdateFileChecksum(state);
......
193 193
        {
194 194
            if (_watcher != null)
195 195
            {
196
                _watcher.Changed -= OnFileEvent;
197
                _watcher.Created -= OnFileEvent;
198
                _watcher.Deleted -= OnFileEvent;
199
                _watcher.Renamed -= OnRenameEvent;
200 196
                _watcher.Dispose();
201 197
            }
202 198
            _watcher = null;
......
329 325
        }
330 326

  
331 327

  
328
/*
332 329
        //Post a Change message for renames containing the old and new names
333 330
        void OnRenameEvent(object sender, RenamedEventArgs e)
334 331
        {
......
347 344
                TriggeringChange = e.ChangeType
348 345
            });
349 346
        }
347
*/
350 348

  
351
        //Post a Change message for renames containing the old and new names
349
        //Post a Change message for moves containing the old and new names
352 350
        void OnMoveEvent(object sender, MovedEventArgs e)
353 351
        {
354 352
            var oldFullPath = e.OldFullPath;
......
415 413
            switch (state.Status)
416 414
            {
417 415
                case FileStatus.Created:
416
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash);
417
                    break;
418 418
                case FileStatus.Modified:
419
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
419
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash);
420 420
                    break;
421 421
                case FileStatus.Deleted:
422 422
                    //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
423 423
                    break;
424 424
                case FileStatus.Renamed:
425 425
                    this.StatusKeeper.ClearFileStatus(state.OldPath);
426
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified);
426
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ShortHash);
427 427
                    break;
428 428
                case FileStatus.Unchanged:
429
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal);
429
                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal,state.ShortHash);
430 430
                    break;
431 431
            }
432 432

  
......
451 451
            if (Directory.Exists(path))
452 452
                return state;
453 453

  
454

  
454 455
            var info = new FileInfo(path);
455
            string hash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
456
            StatusKeeper.UpdateFileChecksum(path, hash);
456
            StatusNotification.Notify(new StatusNotification(String.Format("Hashing [{0}]",info.Name)));
457

  
458
            var shortHash = info.ComputeShortHash(); 
459
            
460
            string merkleHash = info.CalculateHash(StatusKeeper.BlockSize,StatusKeeper.BlockHash);
461
            StatusKeeper.UpdateFileChecksum(path,shortHash, merkleHash);
457 462

  
458
            state.Hash = hash;
463
            state.Hash = merkleHash;
459 464
            return state;
460 465
        }
461 466

  
b/trunk/Pithos.Core/Agents/FileSystemWatcherAdapter.cs
41 41
#endregion
42 42
using System.Diagnostics.Contracts;
43 43
using System.IO;
44
using System.Reflection;
44 45
using System.Threading.Tasks;
45 46
using Pithos.Interfaces;
47
using log4net;
46 48

  
47 49
namespace Pithos.Core.Agents
48 50
{
......
53 55
    /// </summary>
54 56
    public class FileSystemWatcherAdapter
55 57
    {
56
         public event FileSystemEventHandler Changed;
58
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
59

  
60

  
61
        public event FileSystemEventHandler Changed;
57 62
        public event FileSystemEventHandler Created;
58 63
        public event FileSystemEventHandler Deleted;
59
        public event RenamedEventHandler Renamed;
64
        //public event RenamedEventHandler Renamed;
60 65
        public event MovedEventHandler Moved;
61 66

  
62 67
        public FileSystemWatcherAdapter(FileSystemWatcher watcher)
......
68 73
            watcher.Changed += OnChangeOrCreate;
69 74
            watcher.Created += OnChangeOrCreate;
70 75
            watcher.Deleted += OnDeleted;
71
            watcher.Renamed += OnRename;            
72
            
76
            watcher.Renamed += OnRename;
77
            watcher.Error += OnError;
78
        }
79

  
80
        private void OnError(object sender, ErrorEventArgs e)
81
        {
82
            var error = e.GetException();
83
            Log.Error("FSW error",error);
73 84
        }
74 85

  
75 86
        private string _cachedDeletedFullPath;
......
86 97
            Contract.Ensures(!String.IsNullOrWhiteSpace(_cachedDeletedFullPath));
87 98
            Contract.EndContractBlock();
88 99

  
89
            //Handle any previously deleted event
100
            TaskEx.Run(() => InnerOnDeleted(sender, e));
101
        }
102

  
103
        private void InnerOnDeleted(object sender, FileSystemEventArgs e)
104
        {
105
//Handle any previously deleted event
106
            if (Log.IsDebugEnabled)
107
                Log.DebugFormat("[{0}] for [{1}]", Enum.GetName(typeof(WatcherChangeTypes), e.ChangeType), e.FullPath);
90 108
            PropagateCachedDeleted(sender);
91 109

  
92 110
            //A delete event may be an actual delete event or the first event in a move action.
......
99 117
            //      as this is actually a MOVE operation
100 118
            //Deleting by Shift+Delete results in a delete event for each file followed by the delete of the folder itself
101 119
            _cachedDeletedFullPath = e.FullPath;
102
            
120

  
103 121
            //TODO: This requires synchronization of the _cachedDeletedFullPath field
104 122
            //TODO: This creates a new task for each file even though we can cancel any existing tasks if a new event arrives
105 123
            //Maybe, use a timer instead of a task
106
            
124

  
107 125
            TaskEx.Delay(PropagateDelay).ContinueWith(t =>
108
                                                           {
109
                                                               var myPath = e.FullPath;
110
                                                               if (_cachedDeletedFullPath==myPath)
111
                                                                    PropagateCachedDeleted(sender);
112
                                                           });
126
                                                          {
127
                                                              var myPath = e.FullPath;
128
                                                              if (_cachedDeletedFullPath == myPath)
129
                                                                  PropagateCachedDeleted(sender);
130
                                                          });
113 131
        }
114 132

  
115 133
        private void OnRename(object sender, RenamedEventArgs e)
......
119 137
            Contract.Ensures(_cachedDeletedFullPath == null);
120 138
            Contract.EndContractBlock();
121 139

  
140
            TaskEx.Run(() => InnerRename(sender, e));
141
        }
142

  
143
        private void InnerRename(object sender, RenamedEventArgs e)
144
        {
122 145
            try
123 146
            {
147
                if (Log.IsDebugEnabled)
148
                    Log.DebugFormat("[{0}] for [{1}]", Enum.GetName(typeof(WatcherChangeTypes), e.ChangeType), e.FullPath);
124 149
                //Propagate any previous cached delete event
125 150
                PropagateCachedDeleted(sender);
126
                
127
                if (Renamed!=null)
128
                    Renamed(sender, e);
129
                
151

  
152
                if (Moved!= null)
153
                {
154
                    try
155
                    {
156

  
157
                        Moved(sender, new MovedEventArgs(e.FullPath,e.Name,e.OldFullPath,e.OldName));
158
                    }
159
                    catch (Exception exc)
160
                    {
161
                        Log.Error("Rename event error", exc);
162
                        throw;
163
                    }
164

  
165
                    var directory = new DirectoryInfo(e.FullPath);
166
                    if (directory.Exists)
167
                    {
168
                        var newDirectory = e.FullPath;
169
                        var oldDirectory = e.OldFullPath;
170

  
171
                        foreach (
172
                            var child in
173
                                directory.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
174
                        {
175
                            var newChildDirectory = Path.GetDirectoryName(child.FullName);
176

  
177
                            var relativePath = child.AsRelativeTo(newDirectory);
178
                            var relativeFolder = Path.GetDirectoryName(relativePath);
179
                            var oldChildDirectory = Path.Combine(oldDirectory, relativeFolder);
180
                            Moved(sender,
181
                                  new MovedEventArgs(newChildDirectory, child.Name, oldChildDirectory,
182
                                                     child.Name));
183
                        }
184
                    }
185
                }
130 186
            }
131 187
            finally
132 188
            {
133
                _cachedDeletedFullPath = null;    
189
                _cachedDeletedFullPath = null;
134 190
            }
135 191
        }
136 192

  
......
142 198
                throw new ArgumentException("e");
143 199
            Contract.Ensures(_cachedDeletedFullPath == null);
144 200
            Contract.EndContractBlock();
201
            TaskEx.Run(() => InnerChangeOrCreated(sender, e));
145 202

  
203
        }
204

  
205
        private void InnerChangeOrCreated(object sender, FileSystemEventArgs e)
206
        {
146 207
            try
147 208
            {
209
                if (Log.IsDebugEnabled)
210
                    Log.DebugFormat("[{0}] for [{1}]",Enum.GetName(typeof(WatcherChangeTypes),e.ChangeType),e.FullPath);
148 211
                //A Move action results in a sequence of a Delete and a Create or Change event
149 212
                //If the actual action is a Move, raise a Move event instead of the actual event
150 213
                if (HandleMoved(sender, e))
......
159 222
                {
160 223
                    actualEvent(sender, e);
161 224
                    //For Folders, raise Created events for all children
162
                    RaiseCreatedForChildren(sender,e);
225
                    RaiseCreatedForChildren(sender, e);
163 226
                }
164 227
            }
165 228
            finally
......
167 230
                //Finally, make sure the cached path is cleared
168 231
                _cachedDeletedFullPath = null;
169 232
            }
170

  
171 233
        }
172 234

  
173 235
        private void RaiseCreatedForChildren(object sender, FileSystemEventArgs e)
......
181 243
            //Skip if this is not a folder
182 244
            if (!dir.Exists)
183 245
                return;
184
            foreach (var info in dir.EnumerateFileSystemInfos("*",SearchOption.AllDirectories))
246
            try
185 247
            {
186
                var path = Path.GetDirectoryName(info.FullName);
187
                Created(sender,new FileSystemEventArgs(WatcherChangeTypes.Created,path,info.Name));
188
            }            
248
                foreach (var info in dir.EnumerateFileSystemInfos("*",SearchOption.AllDirectories))
249
                {
250
                    var path = Path.GetDirectoryName(info.FullName);
251
                    Created(sender,new FileSystemEventArgs(WatcherChangeTypes.Created,path,info.Name));
252
                }
253
            }
254
            catch (IOException exc)
255
            {
256
                TaskEx.Delay(1000)
257
                    .ContinueWith(_=>RaiseCreatedForChildren(sender,e));
258
                
259
            }
189 260
        }
190 261

  
191 262
        private bool HandleMoved(object sender, FileSystemEventArgs e)
......
212 283

  
213 284
            try
214 285
            {
286
                if (Log.IsDebugEnabled)
287
                    Log.DebugFormat("Moved for [{0}]",  e.FullPath);
288

  
215 289
                //If the actual action is a Move, raise a Move event instead of the actual event
216 290
                var newDirectory = Path.GetDirectoryName(e.FullPath);
217 291
                var oldDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);
......
261 335
            var deletedFileName = Path.GetFileName(_cachedDeletedFullPath);
262 336
            var deletedFileDirectory = Path.GetDirectoryName(_cachedDeletedFullPath);
263 337

  
338
            if (Log.IsDebugEnabled)
339
                Log.DebugFormat("Propagating delete for [{0}]", _cachedDeletedFullPath);
340

  
264 341
            //Only a single file Delete event is raised when moving a file to the Recycle Bin, as this is actually a MOVE operation
265 342
            //In this case we need to raise the proper events for all child objects of the deleted directory.
266 343
            //UNFORTUNATELY, this can't be detected here, eg. by retrieving the child objects, because they are already deleted
b/trunk/Pithos.Core/Agents/NetworkAgent.cs
89 89

  
90 90
        public void Start()
91 91
        {
92
            if (_agent != null)
93
                return;
94

  
95
            if (Log.IsDebugEnabled)
96
                Log.Debug("Starting Network Agent");
97

  
92 98
            _agent = Agent<CloudAction>.Start(inbox =>
93 99
            {
94 100
                Action loop = null;
......
123 129

  
124 130
                try
125 131
                {
132
                    StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,"Processing");
126 133
                    _proceedEvent.Reset();
127 134
                    //UpdateStatus(PithosStatus.Syncing);
128 135
                    var accountInfo = action.AccountInfo;
......
204 211
                {
205 212
                    if (_agent.IsEmpty)
206 213
                        _proceedEvent.Set();
207
                    UpdateStatus(PithosStatus.InSynch);                                        
214
                    UpdateStatus(PithosStatus.LocalComplete);                                        
208 215
                }
209 216
            }
210 217
        }
......
212 219

  
213 220
        private void UpdateStatus(PithosStatus status)
214 221
        {
215
            StatusKeeper.SetPithosStatus(status);
216
            StatusNotification.Notify(new Notification());
222
            StatusNotification.SetPithosStatus(status);
223
            //StatusNotification.Notify(new Notification());
217 224
        }
218 225

  
219 226
        private void RenameLocalFile(AccountInfo accountInfo, CloudAction action)
......
297 304

  
298 305
                var cloudHash = cloudFile.Hash.ToLower();
299 306
                var previousCloudHash = cloudFile.PreviousHash.ToLower();
300
                var localHash = action.LocalHash.Value.ToLower();
301
                var topHash = action.TopHash.Value.ToLower();
307
                var localHash = action.TreeHash.Value.TopHash.ToHashString();// LocalHash.Value.ToLower();
308
                //var topHash = action.TopHash.Value.ToLower();
302 309

  
303 310
                //At this point we know that an object has changed on the server and that a local
304 311
                //file already exists. We need to decide whether the file has only changed on 
305 312
                //the server or there is a conflicting change on the client.
306 313
                //
307 314

  
308
                //Not enough to compare only the local hashes (MD5), also have to compare the tophashes            
309
                //If any of the hashes match, we are done
310
                if ((cloudHash == localHash || cloudHash == topHash))
315
                //If the hashes match, we are done
316
                if (cloudHash == localHash)
311 317
                {
312 318
                    Log.InfoFormat("Skipping {0}, hashes match", downloadPath);
313 319
                    return;
......
351 357
            Contract.EndContractBlock();
352 358

  
353 359
            _deleteAgent.ProceedEvent.Wait();
360
/*
354 361

  
355 362
            //If the action targets a local file, add a treehash calculation
356 363
            if (!(cloudAction is CloudDeleteAction) && cloudAction.LocalFile as FileInfo != null)
357 364
            {
358 365
                var accountInfo = cloudAction.AccountInfo;
359 366
                var localFile = (FileInfo) cloudAction.LocalFile;
367

  
360 368
                if (localFile.Length > accountInfo.BlockSize)
361 369
                    cloudAction.TopHash =
362 370
                        new Lazy<string>(() => Signature.CalculateTreeHashAsync(localFile,
......
367 375
                {
368 376
                    cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value);
369 377
                }
378

  
370 379
            }
371 380
            else
372 381
            {
373 382
                //The hash for a directory is the empty string
374 383
                cloudAction.TopHash = new Lazy<string>(() => String.Empty);
375 384
            }
385
*/
376 386
            
377 387
            if (cloudAction is CloudDeleteAction)
378 388
                _deleteAgent.Post((CloudDeleteAction)cloudAction);
......
497 507
                    }
498 508
                    else
499 509
                    {
500
                        //Retrieve the hashmap from the server
501
                        var serverHash = await client.GetHashMap(account, container, url);
502
                        //If it's a small file
503
                        if (serverHash.Hashes.Count == 1)
504
                            //Download it in one go
505
                            await
506
                                DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath,
507
                                                        serverHash);
508
                            //Otherwise download it block by block
509
                        else
510
                            await DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
511

  
512
                        if (cloudFile.AllowedTo == "read")
510
                        var isChanged = IsObjectChanged(cloudFile, localPath);
511
                        if (isChanged)
513 512
                        {
514
                            var attributes = File.GetAttributes(localPath);
515
                            File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
513
                            //Retrieve the hashmap from the server
514
                            var serverHash = await client.GetHashMap(account, container, url);
515
                            //If it's a small file
516
                            if (serverHash.Hashes.Count == 1)
517
                                //Download it in one go
518
                                await
519
                                    DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath,
520
                                                            serverHash);
521
                                //Otherwise download it block by block
522
                            else
523
                                await
524
                                    DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath,
525
                                                       serverHash);
526

  
527
                            if (cloudFile.AllowedTo == "read")
528
                            {
529
                                var attributes = File.GetAttributes(localPath);
530
                                File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
531
                            }
516 532
                        }
517 533
                    }
518 534

  
......
523 539
            }
524 540
        }
525 541

  
542
        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)
543
        {
544
            //If the target is a directory, there are no changes to download
545
            if (Directory.Exists(localPath))
546
                return false;
547
            //If the file doesn't exist, we have a chagne
548
            if (!File.Exists(localPath)) 
549
                return true;
550
            //If there is no stored state, we have a change
551
            var localState = StatusKeeper.GetStateByFilePath(localPath);
552
            if (localState == null)
553
                return true;
554

  
555
            var info = new FileInfo(localPath);
556
            var shortHash = info.ComputeShortHash();
557
            //If the file is different from the stored state, we have a change
558
            if (localState.ShortHash != shortHash)
559
                return true;
560
            //If the top hashes differ, we have a change
561
            return (localState.Checksum != cloudFile.Hash);
562
        }
563

  
526 564
        //Download a small file with a single GET operation
527 565
        private async Task DownloadEntireFileAsync(AccountInfo accountInfo, CloudFilesClient client, ObjectInfo cloudFile, Uri relativeUrl, string filePath,TreeHash serverHash)
528 566
        {
......
539 577
            Contract.EndContractBlock();
540 578

  
541 579
            var localPath = Pithos.Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
542
            //If the file already exists
543
            if (File.Exists(localPath))
544
            {
545
                //First check with MD5 as this is a small file
546
                var localMD5 = Signature.CalculateMD5(localPath);
547
                var cloudHash=serverHash.TopHash.ToHashString();
548
                if (localMD5==cloudHash)
549
                    return;
550
                //Then check with a treehash
551
                var localTreeHash = Signature.CalculateTreeHash(localPath, serverHash.BlockSize, serverHash.BlockHash);
552
                var localHash = localTreeHash.TopHash.ToHashString();
553
                if (localHash==cloudHash)
554
                    return;
555
            }
580
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Downloading {0}",Path.GetFileName(localPath)));
556 581
            StatusNotification.Notify(new CloudNotification { Data = cloudFile });
557 582

  
558 583
            var fileAgent = GetFileAgent(accountInfo);
......
608 633
            var blockUpdater = new BlockUpdater(fileAgent.CachePath, localPath, relativePath, serverHash);
609 634

  
610 635
            
611
                        
636
            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Calculating hashmap for {0} before download",Path.GetFileName(localPath)));
612 637
            //Calculate the file's treehash
613 638
            var treeHash = await Signature.CalculateTreeHashAsync(localPath, serverHash.BlockSize, serverHash.BlockHash, 2);
614 639
                
......
676 701

  
677 702
                    if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))
678 703
                        return;
704
                    
705
                    //
706
                    if (action.FileState == null)
707
                        action.FileState = StatusKeeper.GetStateByFilePath(fileInfo.FullName);
679 708
                    //Do not upload files in conflict
680 709
                    if (action.FileState.FileStatus == FileStatus.Conflict )
681 710
                    {
......
724 753

  
725 754
                        var client = new CloudFilesClient(accountInfo);
726 755
                        //Even if GetObjectInfo times out, we can proceed with the upload            
727
                        var info = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name);
756
                        var cloudInfo = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name);
728 757

  
729 758
                        //If this is a read-only file, do not upload changes
730
                        if (info.AllowedTo == "read")
759
                        if (cloudInfo.AllowedTo == "read")
731 760
                            return;
732 761

  
733 762
                        //TODO: Check how a directory hash is calculated -> All dirs seem to have the same hash
734 763
                            if (fileInfo is DirectoryInfo)
735 764
                            {
736 765
                                //If the directory doesn't exist the Hash property will be empty
737
                                if (String.IsNullOrWhiteSpace(info.Hash))
766
                                if (String.IsNullOrWhiteSpace(cloudInfo.Hash))
738 767
                                    //Go on and create the directory
739 768
                                    await
740 769
                                        client.PutObject(account, cloudFile.Container, cloudFile.Name, fullFileName,
......
743 772
                            else
744 773
                            {
745 774

  
746
                                var cloudHash = info.Hash.ToLower();
775
                                var cloudHash = cloudInfo.Hash.ToLower();
776

  
777
                                StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} for Upload",fileInfo.Name)));
778

  
779
                                //TODO: This is the same as the calculation for the Local Hash!
780
                                //First, calculate the tree hash
781
/*
782
                                var treeHash = await Signature.CalculateTreeHashAsync(fullFileName, accountInfo.BlockSize,
783
                                                                                      accountInfo.BlockHash, 2);
784
*/
747 785

  
748
                                var hash = action.LocalHash.Value;
749
                                var topHash = action.TopHash.Value;
786

  
787
                                var treeHash = action.TreeHash.Value;
788
                                var topHash = treeHash.TopHash.ToHashString();
789
                                
790
                                //var topHash = action.TopHash.Value;
750 791

  
751 792
                                //If the file hashes match, abort the upload
752
                                if (hash == cloudHash || topHash == cloudHash)
793
                                if (topHash == cloudHash /*|| topHash == cloudHash*/)
753 794
                                {
754 795
                                    //but store any metadata changes 
755
                                    StatusKeeper.StoreInfo(fullFileName, info);
796
                                    StatusKeeper.StoreInfo(fullFileName, cloudInfo);
756 797
                                    Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
757 798
                                    return;
758 799
                                }
......
765 806
                                //Upload even small files using the Hashmap. The server may already contain
766 807
                                //the relevant block
767 808

  
768
                                //First, calculate the tree hash
769
                                var treeHash = await Signature.CalculateTreeHashAsync(fullFileName, accountInfo.BlockSize,
770
                                                                                      accountInfo.BlockHash, 2);
771

  
772 809
                                //TODO: If the upload fails with a 403, abort it and mark conflict
773 810

  
774 811
                                await
......
847 884
                throw new ArgumentException("Invalid container","cloudFile");
848 885
            Contract.EndContractBlock();
849 886

  
887
            StatusNotification.Notify(new StatusNotification(String.Format("Uploading {0} for Upload", fileInfo.Name)));
888

  
850 889
            var fullFileName = fileInfo.GetProperCapitalization();
851 890

  
852 891
            var account = cloudFile.Account ?? accountInfo.UserName;
......
886 925
                //Repeat until there are no more missing hashes                
887 926
                missingHashes = await client.PutHashMap(account, container, url, treeHash);
888 927
            }
928

  
889 929
            ReportUploadProgress(fileInfo.Name, missingHashes.Count, missingHashes.Count, fileInfo.Length);
890 930
        }
891 931

  
892 932
        private void ReportUploadProgress(string fileName,int block, int totalBlocks, long fileSize)
893 933
        {
894
            StatusNotification.Notify(new ProgressNotification(fileName,"Uploading",block,totalBlocks,fileSize) );            
934
            StatusNotification.Notify(totalBlocks == 0
935
                                          ? new ProgressNotification(fileName, "Uploading", 1, 1, fileSize)
936
                                          : new ProgressNotification(fileName, "Uploading", block, totalBlocks, fileSize));
895 937
        }
938

  
896 939
        private void ReportDownloadProgress(string fileName,int block, int totalBlocks, long fileSize)
897 940
        {
898
            StatusNotification.Notify(new ProgressNotification(fileName,"Downloading",block,totalBlocks,fileSize) );            
941
            StatusNotification.Notify(totalBlocks == 0
942
                                          ? new ProgressNotification(fileName, "Downloading", 1, 1, fileSize)
943
                                          : new ProgressNotification(fileName, "Downloading", block, totalBlocks, fileSize));
899 944
        }
900 945
    }
901 946

  
b/trunk/Pithos.Core/Agents/PollAgent.cs
105 105
        /// <returns></returns>
106 106
        public async Task PollRemoteFiles(DateTime? since = null)
107 107
        {
108
            Debug.Assert(Thread.CurrentThread.IsBackground, "Polling Ended up in the main thread!");
108
            if (Log.IsDebugEnabled)
109
                Log.DebugFormat("Polling changes after [{0}]",since);
109 110

  
110
            UpdateStatus(PithosStatus.Syncing);
111
            StatusNotification.Notify(new PollNotification());
111
            Debug.Assert(Thread.CurrentThread.IsBackground, "Polling Ended up in the main thread!");
112
            
112 113

  
113 114
            using (ThreadContext.Stacks["Retrieve Remote"].Push("All accounts"))
114 115
            {
......
116 117
                var nextSince = since;
117 118
                try
118 119
                {
120
                    UpdateStatus(PithosStatus.PollSyncing);
121

  
119 122
                    //Next time we will check for all changes since the current check minus 1 second
120 123
                    //This is done to ensure there are no discrepancies due to clock differences
121 124
                    var current = DateTime.Now.AddSeconds(-1);
......
135 138
                    //In case of failure retry with the same "since" value
136 139
                }
137 140

  
138
                UpdateStatus(PithosStatus.InSynch);
141
                UpdateStatus(PithosStatus.PollComplete);
139 142
                //The multiple try blocks are required because we can't have an await call
140 143
                //inside a finally block
141 144
                //TODO: Find a more elegant solution for reschedulling in the event of an exception
......
512 515
        {
513 516
            try
514 517
            {
515
                StatusKeeper.SetPithosStatus(status);
516
                StatusNotification.Notify(new Notification());
518
                StatusNotification.SetPithosStatus(status);
519
                //StatusNotification.Notify(new Notification());
517 520
            }
518 521
            catch (Exception exc)
519 522
            {
b/trunk/Pithos.Core/Agents/StatusAgent.cs
48 48
using System.IO;
49 49
using System.Linq;
50 50
using System.Reflection;
51
using System.Security.Cryptography;
51 52
using System.Text;
52 53
using System.Threading;
53 54
using System.Threading.Tasks;
54 55
using Castle.ActiveRecord;
56
using Castle.ActiveRecord.Framework;
55 57
using Castle.ActiveRecord.Framework.Config;
56 58
using Pithos.Interfaces;
57 59
using Pithos.Network;
......
185 187
                        {
186 188
                            Log.ErrorFormat("[ERROR] STATE \n{0}", ex);
187 189
                        }
190
                        queue.NotifyComplete(action);
188 191
// ReSharper disable AccessToModifiedClosure
189 192
                        queue.DoAsync(loop);
190 193
// ReSharper restore AccessToModifiedClosure
......
201 204
        {
202 205
            _persistenceAgent.Stop();            
203 206
        }
204
       
207
               
205 208

  
206 209
        public void ProcessExistingFiles(IEnumerable<FileInfo> existingFiles)
207 210
        {
......
230 233
                        where missingStates.Contains(state.Id)
231 234
                        select new { File = default(FileInfo), State = state };
232 235

  
233
            var pairs = currentFiles.Union(deletedFiles);
236
            var pairs = currentFiles.Union(deletedFiles).ToList();
234 237

  
235
            foreach(var pair in pairs)
238
            using (var shortHasher = HashAlgorithm.Create("sha1"))
236 239
            {
237
                var fileState = pair.State;
238
                var file = pair.File;
239
                if (fileState == null)
240
                {
241
                    //This is a new file
242
                    var fullPath = pair.File.FullName;
243
                    var createState = FileState.CreateForAsync(fullPath, BlockSize, BlockHash);
244
                    createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create));
245
                }                
246
                else if (file == null)
240
                foreach (var pair in pairs)
247 241
                {
248
                    //This file was deleted while we were down. We should mark it as deleted
249
                    //We have to go through UpdateStatus here because the state object we are using
250
                    //was created by a different ORM session.
251
                    _persistenceAgent.Post(()=> UpdateStatusDirect(fileState.Id, FileStatus.Deleted));                    
252
                }
253
                else
254
                {
255
                    //This file has a matching state. Need to check for possible changes
256
                    var hashString = file.CalculateHash(BlockSize,BlockHash);
257
                    //TODO: Need a way to attach the hashes to the filestate so we don't
258
                    //recalculate them each time a call to calculate has is made
259
                    //We can either store them to the filestate or add them to a 
260
                    //dictionary
261

  
262
                    //If the hashes don't match the file was changed
263
                    if (fileState.Checksum != hashString)
242
                    var fileState = pair.State;
243
                    var file = pair.File;
244
                    if (fileState == null)
264 245
                    {
265
                        _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
266
                    }                    
246
                        //This is a new file                        
247
                        var createState = FileState.CreateFor(file);
248
                        _persistenceAgent.Post(createState.Create);                        
249
                    }
250
                    else if (file == null)
251
                    {
252
                        //This file was deleted while we were down. We should mark it as deleted
253
                        //We have to go through UpdateStatus here because the state object we are using
254
                        //was created by a different ORM session.
255
                        _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Deleted));
256
                    }
257
                    else
258
                    {
259
                        //This file has a matching state. Need to check for possible changes
260
                        //To check for changes, we use the cheap (in CPU terms) SHA1 algorithm
261
                        //on the entire file.
262
                        
263
                        var hashString = file.ComputeShortHash(shortHasher);                        
264
                        //TODO: Need a way to attach the hashes to the filestate so we don't
265
                        //recalculate them each time a call to calculate has is made
266
                        //We can either store them to the filestate or add them to a 
267
                        //dictionary
268

  
269
                        //If the hashes don't match the file was changed
270
                        if (fileState.ShortHash != hashString)
271
                        {
272
                            _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified));
273
                        }
274
                    }
267 275
                }
268
            };            
276
            }
277
                        
269 278
         
270 279
        }
280
        
281

  
271 282

  
272 283
        private int UpdateStatusDirect(Guid id, FileStatus status)
273 284
        {
......
387 398

  
388 399
        }
389 400

  
390
        private PithosStatus _pithosStatus=PithosStatus.InSynch;       
391

  
392
        public void SetPithosStatus(PithosStatus status)
393
        {
394
            _pithosStatus = status;
395
        }
396

  
397
        public PithosStatus GetPithosStatus()
398
        {
399
            return _pithosStatus;
400
        }
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff