Revision 4f6d51d4

b/trunk/Pithos.Client.WPF/FileProperties/FilePropertiesView.xaml
96 96
                </TabItem>
97 97
                <TabItem Header="Permissions">
98 98
                    <StackPanel>
99
                        <TextBlock Margin="5" Visibility="{Binding Path=IsPublic,FallbackValue=false, Converter={StaticResource BoolToVisible}}">
99
                        <TextBlock Margin="5" Visibility="{Binding Path=IsPublic,FallbackValue=Collapsed, Converter={StaticResource BoolToVisible}}">
100 100
                <Run Text="Public URL:" />
101 101
                <Run Text="{Binding PublicUrl,FallbackValue='http://someurl'}" />
102 102
                        </TextBlock>
b/trunk/Pithos.Client.WPF/FileProperties/FilePropertiesViewModel.cs
12 12
using System.Diagnostics;
13 13
using System.Diagnostics.Contracts;
14 14
using System.Drawing;
15
using System.IO;
15 16
using System.Net;
16 17
using System.Threading.Tasks;
17 18
using System.Windows;
......
324 325
                if (IsPublic)
325 326
                    PublicUrl = String.Format("{0}/v1{1}", Shell.Accounts.First(account=>account.UserName==PithosFile.Account).SiteUri,value.PublicUrl);
326 327

  
327
                using (var icon = Icon.ExtractAssociatedIcon(LocalFileName))
328
                if (Directory.Exists(LocalFileName))
328 329
                {
329
                    FileIcon = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty,
330
                                                                   BitmapSizeOptions.FromEmptyOptions());
330
                    FileIcon= new BitmapImage(new Uri("../Images/Folder.ico",UriKind.Relative));
331
                }
332
                else if (File.Exists(LocalFileName))
333
                {
334
                    using (var icon = Icon.ExtractAssociatedIcon(LocalFileName))
335
                    {
336
                        FileIcon = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty,
337
                                                                       BitmapSizeOptions.FromEmptyOptions());
338
                    }
331 339
                }
332 340
                NotifyOfPropertyChange(()=>PithosFile);
333 341
                IsBusy = false;
b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj
70 70
    <AssemblyOriginatorKeyFile>pithos.snk</AssemblyOriginatorKeyFile>
71 71
  </PropertyGroup>
72 72
  <PropertyGroup>
73
    <ApplicationIcon>Images\Tray.ico</ApplicationIcon>
73
    <ApplicationIcon>Images\Pithos.ico</ApplicationIcon>
74 74
  </PropertyGroup>
75 75
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Premium Debug|x86'">
76 76
    <DebugSymbols>true</DebugSymbols>
......
228 228
    <Reference Include="WindowsBase" />
229 229
    <Reference Include="PresentationCore" />
230 230
    <Reference Include="PresentationFramework" />
231
    <Reference Include="WPFToolkit.Extended">
231
    <Reference Include="WPFToolkit.Extended, Version=1.5.0.0, Culture=neutral, PublicKeyToken=3e4669d2f30244f4, processorArchitecture=MSIL">
232
      <SpecificVersion>False</SpecificVersion>
232 233
      <HintPath>..\packages\Extended.Wpf.Toolkit.1.5.0\lib\net40\WPFToolkit.Extended.dll</HintPath>
233 234
    </Reference>
234 235
  </ItemGroup>
b/trunk/Pithos.Client.WPF/Properties/AssemblyInfo.cs
15 15
[assembly: AssemblyCopyright("Copyright © GRNet 2011")]
16 16
[assembly: AssemblyTrademark("")]
17 17
[assembly: AssemblyCulture("")]
18
[assembly: AssemblyInformationalVersion("2011-12-15")]
18
[assembly: AssemblyInformationalVersion("2012-01-08")]
19 19

  
20 20
// Setting ComVisible to false makes the types in this assembly not visible 
21 21
// to COM components.  If you need to access a type in this assembly from 
......
52 52
// You can specify all the values or you can default the Build and Revision Numbers 
53 53
// by using the '*' as shown below:
54 54
// [assembly: AssemblyVersion("1.0.*")]
55
[assembly: AssemblyVersion("1.0.0.26001")]
55
[assembly: AssemblyVersion("1.0.0.27000")]
b/trunk/Pithos.Client.WPF/Services/StatusService.cs
6 6

  
7 7

  
8 8
using System;
9
using System.ServiceModel.Description;
10 9
using Caliburn.Micro;
11 10
using System.ServiceModel;
12 11
using System.ComponentModel.Composition;
b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs
1 1
using System.Collections.Concurrent;
2
using System.ComponentModel;
3
using System.ComponentModel.Composition;
4 2
using System.Diagnostics;
5 3
using System.Diagnostics.Contracts;
6 4
using System.IO;
......
8 6
using System.Reflection;
9 7
using System.Runtime.InteropServices;
10 8
using System.ServiceModel;
11
using System.ServiceModel.Description;
12 9
using System.Threading.Tasks;
13 10
using System.Windows;
14 11
using Caliburn.Micro;
15 12
using Hardcodet.Wpf.TaskbarNotification;
16 13
using Pithos.Client.WPF.Configuration;
17 14
using Pithos.Client.WPF.FileProperties;
18
using Pithos.Client.WPF.Properties;
19 15
using Pithos.Client.WPF.SelectiveSynch;
20 16
using Pithos.Client.WPF.Services;
21 17
using Pithos.Client.WPF.Shell;
......
24 20
using System;
25 21
using System.Collections.Generic;
26 22
using System.Linq;
27
using System.Text;
28 23
using Pithos.Network;
29 24
using StatusService = Pithos.Client.WPF.Services.StatusService;
30 25

  
......
50 45
	{
51 46
		//The Status Checker provides the current synch state
52 47
		//TODO: Could we remove the status checker and use events in its place?
53
		private IStatusChecker _statusChecker;
54
		private IEventAggregator _events;
48
		private readonly IStatusChecker _statusChecker;
49
		private readonly IEventAggregator _events;
55 50

  
56 51
		public PithosSettings Settings { get; private set; }
57 52

  
58 53

  
59
        private ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
54
        private readonly ConcurrentDictionary<string, PithosMonitor> _monitors = new ConcurrentDictionary<string, PithosMonitor>();
60 55
		///<summary>
61 56
		/// Dictionary of account monitors, keyed by account
62 57
		///</summary>
......
72 67
		}
73 68

  
74 69

  
75
		///<summary>
76
		/// The status service is used by Shell extensions to retrieve file status information
77
		///</summary>
78
		//TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
79
		private ServiceHost _statusService { get; set; }
70
	    ///<summary>
71
	    /// The status service is used by Shell extensions to retrieve file status information
72
	    ///</summary>
73
	    //TODO: CODE SMELL! This is the shell! While hosting in the shell makes executing start/stop commands easier, it is still a smell
74
	    private ServiceHost _statusService;
80 75

  
81 76
		//Logging in the Pithos client is provided by log4net
82 77
		private static readonly log4net.ILog Log = log4net.LogManager.GetLogger("Pithos");
......
128 123
		}
129 124

  
130 125

  
131
		private async Task StartMonitoring()
126
		private async void StartMonitoring()
132 127
		{
133 128
			try
134 129
			{
......
169 164
		{
170 165
			return Task.Factory.StartNew(() =>
171 166
			{                                                
172
				PithosMonitor monitor = null;
167
				PithosMonitor monitor;
173 168
				var accountName = account.AccountName;
174 169

  
175 170
				if (_monitors.TryGetValue(accountName, out monitor))
......
199 194
				//PithosMonitor uses MEF so we need to resolve it
200 195
				IoC.BuildUp(monitor);
201 196

  
202
				var appSettings = Properties.Settings.Default;
203
				monitor.AuthenticationUrl = account.ServerUrl;
197
			    monitor.AuthenticationUrl = account.ServerUrl;
204 198

  
205 199
				_monitors[accountName] = monitor;
206 200

  
......
284 278
			get { return _statusIcon; }
285 279
			set
286 280
			{
287
				//_statusIcon = value;
281
                //TODO: Ensure all status icons use the Pithos logo
282
				_statusIcon = value;
288 283
				NotifyOfPropertyChange(() => StatusIcon);
289 284
			}
290 285
		}
......
359 354
		{
360 355
			if (String.IsNullOrWhiteSpace(filePath))
361 356
				throw new ArgumentNullException("filePath");
362
			if (!File.Exists(filePath))
357
			if (!File.Exists(filePath) && !Directory.Exists(filePath))
363 358
				throw new ArgumentException(String.Format("Non existent file {0}",filePath),"filePath");
364 359
			Contract.EndContractBlock();
365 360

  
366 361
			var pair=(from monitor in  Monitors
367 362
							   where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
368 363
								   select monitor).FirstOrDefault();
369
			var account = pair.Key;
370
			var accountMonitor = pair.Value;
364
		    var accountMonitor = pair.Value;
371 365

  
372 366
			if (accountMonitor == null)
373 367
				return;
......
401 395
			var pair=(from monitor in  Monitors
402 396
							   where filePath.StartsWith(monitor.Value.RootPath, StringComparison.InvariantCultureIgnoreCase)
403 397
								   select monitor).FirstOrDefault();
404
			var account = pair.Key;
405
			var accountMonitor = pair.Value;            
398
		    var accountMonitor = pair.Value;            
406 399
			var info = accountMonitor.GetContainerInfo(filePath);
407 400

  
408 401
			
......
462 455
		#endregion
463 456

  
464 457

  
465
		private Dictionary<PithosStatus, StatusInfo> iconNames = new List<StatusInfo>
458
		private readonly Dictionary<PithosStatus, StatusInfo> _iconNames = new List<StatusInfo>
466 459
			{
467 460
				new StatusInfo(PithosStatus.InSynch, "All files up to date", "TrayInSynch"),
468 461
				new StatusInfo(PithosStatus.Syncing, "Syncing Files", "TraySynching"),
......
479 472
		{
480 473
			var pithosStatus = _statusChecker.GetPithosStatus();
481 474

  
482
			if (iconNames.ContainsKey(pithosStatus))
475
			if (_iconNames.ContainsKey(pithosStatus))
483 476
			{
484
				var info = iconNames[pithosStatus];
477
				var info = _iconNames[pithosStatus];
485 478
				StatusIcon = String.Format(@"../Images/{0}.ico", info.IconName);
486 479

  
487 480
				Assembly assembly = Assembly.GetExecutingAssembly();                               
......
558 551

  
559 552
				var credentials = await PithosAccount.RetrieveCredentials(Settings.PithosLoginUrl);
560 553

  
561
				var account = Settings.Accounts.FirstOrDefault(act => act.AccountName == credentials.UserName);
554
				var account = Settings.Accounts.First(act => act.AccountName == credentials.UserName);
562 555
				account.ApiKey = credentials.Password;
563 556
				monitor.ApiKey = credentials.Password;
564 557
				Settings.Save();
......
571 564
				string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
572 565
				Log.Error(message, exc.InnerException);
573 566
				_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
574
				return;
575 567
			}
576 568
			catch (Exception exc)
577 569
			{
578 570
				string message = String.Format("API Key retrieval for {0} failed", monitor.UserName);
579 571
				Log.Error(message, exc);
580 572
				_events.Publish(new Notification { Title = "Authorization failed", Message = message, Level = TraceLevel.Error });
581
				return;
582
				
583 573
			}
584 574

  
585 575
		}
......
608 598

  
609 599
		public void NotifyChange(string status, TraceLevel level=TraceLevel.Info)
610 600
		{
611
			this.StatusMessage = status;
601
			StatusMessage = status;
612 602
			
613 603
			_events.Publish(new Notification { Title = "Pithos", Message = status, Level = level });
614 604
		}
......
616 606
		public void NotifyChangedFile(string filePath)
617 607
		{
618 608
			var entry = new FileEntry {FullPath=filePath};
619
			IProducerConsumerCollection<FileEntry> files=this.RecentFiles;
609
			IProducerConsumerCollection<FileEntry> files=RecentFiles;
620 610
			FileEntry popped;
621 611
			while (files.Count > 5)
622 612
				files.TryTake(out popped);
......
702 692
		{
703 693
			if (!Settings.ShowDesktopNotifications)
704 694
				return;
705
			BalloonIcon icon = BalloonIcon.None;
695
			BalloonIcon icon;
706 696
			switch (notification.Level)
707 697
			{
708 698
				case TraceLevel.Error:
......
722 712

  
723 713
			if (Settings.ShowDesktopNotifications)
724 714
			{
725
				var tv = (ShellView) this.GetView();
715
				var tv = (ShellView) GetView();
726 716
				tv.TaskbarView.ShowBalloonTip(notification.Title, notification.Message, icon);
727 717
			}
728 718
		}
......
737 727
			Contract.EndContractBlock();
738 728

  
739 729
			var fileName = message.FileName;
740

  
730
            //TODO: Display file properties for non-container folders
741 731
			if (File.Exists(fileName))
742
				this.ShowFileProperties(fileName);
732
                //Retrieve the full name with exact casing. Pithos names are case sensitive				
733
                ShowFileProperties(GetProperFilePathCapitalization(fileName));
743 734
			else if (Directory.Exists(fileName))
744
				this.ShowContainerProperties(fileName);
735
                //Retrieve the full name with exact casing. Pithos names are case sensitive
736
			{
737
			    var path = GetProperDirectoryCapitalization(fileName);
738
                if (IsContainer(path))
739
			        ShowContainerProperties(path);
740
                else
741
                    ShowFileProperties(path);
742
			}
745 743
		}
744

  
745
	    private bool IsContainer(string path)
746
	    {
747
	        var matchingFolders = from account in _accounts
748
	                              from rootFolder in Directory.GetDirectories(account.AccountPath)
749
	                              where rootFolder.Equals(path, StringComparison.InvariantCultureIgnoreCase)
750
	                              select rootFolder;
751
	        return matchingFolders.Any();
752
	    }
753

  
754
	    static string GetProperDirectoryCapitalization(string fileName)
755
        {
756
            var dirInfo = new DirectoryInfo(fileName);
757
            var parentDirInfo = dirInfo.Parent;
758
            if (null == parentDirInfo)
759
                return dirInfo.Name;
760
            return Path.Combine(GetProperDirectoryCapitalization(parentDirInfo.FullName),
761
                                parentDirInfo.GetDirectories(dirInfo.Name)[0].Name);
762
        }
763

  
764
        static string GetProperFilePathCapitalization(string fileName)
765
        {
766
            if (String.IsNullOrWhiteSpace(fileName))
767
                throw new ArgumentNullException("fileName");
768
            if (!Path.IsPathRooted(fileName))
769
                throw new ArgumentException("fileName must be an absolute path","fileName");
770
            Contract.EndContractBlock();
771

  
772
            
773
            var fileInfo = new FileInfo(fileName);
774
            var dirInfo = fileInfo.Directory;
775
            
776
            //Directory will not be null for an absolute path
777
            Contract.Assume(dirInfo!=null);
778
            
779
            return Path.Combine(GetProperDirectoryCapitalization(dirInfo.FullName),
780
                                dirInfo.GetFiles(fileInfo.Name)[0].Name);
781
        }
746 782
	}
747 783
}
b/trunk/Pithos.Core/Agents/CloudTransferAction.cs
21 21
    {
22 22
        public AccountInfo AccountInfo { get; set; }
23 23
        public CloudActionType Action { get; set; }
24
        public FileInfo LocalFile { get; set; }
24
        public FileSystemInfo LocalFile { get; set; }
25 25
        public ObjectInfo CloudFile { get; set; }
26 26
        public FileState FileState { get; set; }
27 27
        public string Container { get; set; }
......
52 52
            AccountInfo = accountInfo;
53 53
        }
54 54

  
55
        public CloudAction(AccountInfo accountInfo, CloudActionType action, FileInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm)
55
        public CloudAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm)
56 56
            : this(accountInfo,action)
57 57
        {
58 58
            LocalFile = localFile;
......
60 60
            FileState = state;
61 61
            if (LocalFile != null)
62 62
            {
63

  
63 64
                LocalHash = new Lazy<string>(() => LocalFile.CalculateHash(blockSize,algorithm),
64 65
                                             LazyThreadSafetyMode.ExecutionAndPublication);
65 66
            }
......
106 107
    }
107 108
    public class CloudDeleteAction:CloudAction
108 109
    {
109
        public CloudDeleteAction(AccountInfo accountInfo,FileInfo fileInfo, FileState fileState)
110
        public CloudDeleteAction(AccountInfo accountInfo,FileSystemInfo fileInfo, FileState fileState)
110 111
            : this(accountInfo,new ObjectInfo(accountInfo.AccountPath,accountInfo.UserName,fileInfo),fileState)
111 112
        {
112 113
        }
......
137 138

  
138 139
    public class CloudUploadAction:CloudAction
139 140
    {
140
        public CloudUploadAction(AccountInfo accountInfo, FileInfo fileInfo, FileState state, int blockSize, string algorithm)
141
        public CloudUploadAction(AccountInfo accountInfo, FileSystemInfo fileInfo, FileState state, int blockSize, string algorithm)
141 142
            : base(accountInfo, CloudActionType.UploadUnconditional, fileInfo, 
142 143
            new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName,fileInfo), state, blockSize, algorithm)
143 144
        {
......
155 156
    {
156 157
        public ObjectInfo OldCloudFile { get; set; }
157 158

  
158
        public FileInfo OldLocalFile { get; set; }
159
        public FileSystemInfo OldLocalFile { get; set; }
159 160

  
160
        public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileInfo oldFile, FileInfo newFile)
161
        public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo oldFile, FileSystemInfo newFile)
161 162
            :base(accountInfo,action)
162 163
        {
163 164
            LocalFile = newFile;
b/trunk/Pithos.Core/Agents/FileAgent.cs
75 75
                throw new ArgumentNullException("state");
76 76
            Contract.EndContractBlock();
77 77

  
78
            Debug.Assert(!Ignore(state.Path));
78
            if (Ignore(state.Path))
79
                return CompletedTask<object>.Default;
79 80

  
80 81
            var networkState = NetworkGate.GetNetworkState(state.Path);
81 82
            //Skip if the file is already being downloaded or uploaded and 
......
192 193

  
193 194
        private bool Ignore(string filePath)
194 195
        {
196
            var pithosPath = Path.Combine(RootPath, "pithos");
197
            if (pithosPath.Equals(filePath, StringComparison.InvariantCultureIgnoreCase))
198
                return true;
195 199
            if (filePath.StartsWith(CachePath))
196 200
                return true;
197 201
            if (_ignoreFiles.ContainsKey(filePath.ToLower()))
......
206 210
            var filePath = e.FullPath;
207 211
            if (Ignore(filePath)) 
208 212
                return;
209
            if (Directory.Exists(filePath))
210
                return;            
213
          /*  if (Directory.Exists(filePath))
214
                return;    */        
211 215
            _agent.Post(new WorkflowState{AccountInfo=AccountInfo, Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType });
212 216
        }
213 217

  
......
344 348
            return false;
345 349
        }
346 350

  
347
        public FileInfo GetFileInfo(string relativePath)
351
        public FileSystemInfo GetFileInfo(string relativePath)
348 352
        {
349 353
            if (String.IsNullOrWhiteSpace(relativePath))
350 354
                throw new ArgumentNullException("relativePath");
......
354 358
            Contract.EndContractBlock();            
355 359

  
356 360
            var absolutePath = Path.Combine(RootPath, relativePath);
357
//            Debug.Assert(File.Exists(absolutePath),String.Format("Path {0} doesn't exist",absolutePath));
358 361

  
359
            return new FileInfo(absolutePath);
362
            if (Directory.Exists(absolutePath))
363
                return new DirectoryInfo(absolutePath);
364
            else
365
                return new FileInfo(absolutePath);
360 366
            
361 367
        }
362 368

  
363 369
        public void Delete(string relativePath)
364 370
        {
365
            var absolutePath = Path.Combine(RootPath, relativePath);
371
            var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
366 372
            if (File.Exists(absolutePath))
367
            {                   
368
                File.Delete(absolutePath);
369
                _ignoreFiles[absolutePath.ToLower()] = absolutePath.ToLower();                
373
            {    
374
                try
375
                {
376
                    File.Delete(absolutePath);
377
                }
378
                //The file may have been deleted by another thread. Just ignore the relevant exception
379
                catch (FileNotFoundException) { }
380
            }
381
            else if (Directory.Exists(absolutePath))
382
            {
383
                try
384
                {
385
                    Directory.Delete(absolutePath, true);
386
                }
387
                //The directory may have been deleted by another thread. Just ignore the relevant exception
388
                catch (DirectoryNotFoundException){}                
370 389
            }
390
        
391
            //_ignoreFiles[absolutePath] = absolutePath;                
371 392
            StatusKeeper.ClearFileStatus(absolutePath);
372 393
        }
373 394
    }
b/trunk/Pithos.Core/Agents/FileInfoExtensions.cs
24 24
            }
25 25
        }
26 26

  
27
       public static string CalculateHash(this FileInfo info,int blockSize,string algorithm)
27
       public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm)
28 28
        {
29 29
            if (info==null)
30 30
                throw new ArgumentNullException("info");
......
36 36
                throw new ArgumentNullException("algorithm");
37 37
            Contract.EndContractBlock();
38 38

  
39
            if (info.Length <= blockSize)
39
           //The hash for directories is an empty string
40
           if (info is DirectoryInfo)
41
                return String.Empty;
42
           
43
           var fileInfo = (FileInfo)info;
44
           if (fileInfo.Length <= blockSize)
40 45
                return Signature.CalculateMD5(info.FullName);
41 46
            else
42 47
                return Signature.CalculateTreeHash(info.FullName, blockSize, algorithm).TopHash.ToHashString();
43 48

  
44 49
        }
45 50

  
51
       
52

  
46 53
    }
47 54
}
b/trunk/Pithos.Core/Agents/NetworkAgent.cs
1 1
using System;
2
using System.Collections.Concurrent;
2 3
using System.Collections.Generic;
3 4
using System.ComponentModel.Composition;
4 5
using System.Diagnostics;
......
26 27

  
27 28
        private static readonly ILog Log = LogManager.GetLogger("NetworkAgent");
28 29

  
29
        private readonly List<AccountInfo> _accounts=new List<AccountInfo>();
30
        private readonly ConcurrentBag<AccountInfo> _accounts = new ConcurrentBag<AccountInfo>();
30 31

  
31 32
        public void Start()
32 33
        {
......
81 82
                            RenameCloudFile(accountInfo, moveAction);
82 83
                            break;
83 84
                        case CloudActionType.MustSynch:
84

  
85
                            if (!File.Exists(downloadPath))
85
                            if (!File.Exists(downloadPath) && !Directory.Exists(downloadPath))
86 86
                            {
87 87
                                await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
88 88
                            }
......
221 221
            Contract.EndContractBlock();
222 222
            
223 223
            //If the action targets a local file, add a treehash calculation
224
            if (cloudAction.LocalFile != null)
224
            if (cloudAction.LocalFile as FileInfo != null)
225 225
            {
226 226
                var accountInfo = cloudAction.AccountInfo;
227
                if (!Directory.Exists(cloudAction.LocalFile.FullName))
227
                var localFile = (FileInfo) cloudAction.LocalFile;
228
                if (localFile.Length > accountInfo.BlockSize)
229
                    cloudAction.TopHash =
230
                        new Lazy<string>(() => Signature.CalculateTreeHashAsync(localFile,
231
                                                                                accountInfo.BlockSize,
232
                                                                                accountInfo.BlockHash).Result
233
                                                    .TopHash.ToHashString());
234
                else
228 235
                {
229
                    if (cloudAction.LocalFile.Length > accountInfo.BlockSize)
230
                        cloudAction.TopHash =
231
                            new Lazy<string>(() => Signature.CalculateTreeHashAsync(cloudAction.LocalFile,
232
                                                                                    accountInfo.BlockSize,
233
                                                                                    accountInfo.BlockHash).Result
234
                                                       .TopHash.ToHashString());
235
                    else
236
                    {
237
                        cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value);
238
                    }
236
                    cloudAction.TopHash = new Lazy<string>(() => cloudAction.LocalHash.Value);
239 237
                }
240

  
238
            }
239
            else
240
            {
241
                //The hash for a directory is the empty string
242
                cloudAction.TopHash = new Lazy<string>(() => String.Empty);
241 243
            }
242 244
            _agent.Post(cloudAction);
243 245
        }
......
274 276
                    var tasks = from accountInfo in _accounts
275 277
                                select ProcessAccountFiles(accountInfo, since);
276 278

  
277
                    await TaskEx.WhenAll(tasks);
279
                    await TaskEx.WhenAll(tasks.ToList());
278 280

  
279 281
                    ProcessRemoteFiles(nextSince);
280 282
                }
......
377 379
            }
378 380
        }
379 381

  
380
        private static void CreateContainerFolders(AccountInfo accountInfo, IList<ContainerInfo> containers)
382
        private static void CreateContainerFolders(AccountInfo accountInfo, IEnumerable<ContainerInfo> containers)
381 383
        {
382 384
            var containerPaths = from container in containers
383 385
                                 let containerPath = Path.Combine(accountInfo.AccountPath, container.Name)
......
407 409
                
408 410
                if (fileAgent.Exists(relativePath))
409 411
                {
412
                    //If a directory object already exists, we don't need to perform any other action                    
410 413
                    var localFile = fileAgent.GetFileInfo(relativePath);
414
                    if (objectInfo.Content_Type == @"application/directory" && localFile is DirectoryInfo)
415
                        continue;
411 416
                    var state = FileState.FindByFilePath(localFile.FullName);
412 417
                    //Common files should be checked on a per-case basis to detect differences, which is newer
413 418

  
......
756 761
                    var cloudFile = action.CloudFile;
757 762
                    var account = cloudFile.Account ?? accountInfo.UserName;
758 763

  
759
                    var client = new CloudFilesClient(accountInfo);
764
                    var client = new CloudFilesClient(accountInfo);                    
760 765
                    //Even if GetObjectInfo times out, we can proceed with the upload            
761 766
                    var info = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name);
762
                    var cloudHash = info.Hash.ToLower();
763 767

  
764
                    var hash = action.LocalHash.Value;
765
                    var topHash = action.TopHash.Value;
768
                    //If this is a read-only file, do not upload changes
769
                    if (info.AllowedTo == "read")
770
                        return;
766 771

  
767
                    //If the file hashes match, abort the upload
768
                    if (hash == cloudHash || topHash == cloudHash)
772
                    //WRONG: If this is a directory, there is no hash to check. ????
773
                    //TODO: Check how a directory hash is calculated
774
                    if (fileInfo is DirectoryInfo)
769 775
                    {
770
                        //but store any metadata changes 
771
                        StatusKeeper.StoreInfo(fullFileName, info);
772
                        Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
773
                        return;
776
                        //If the directory doesn't exist the Hash property will be empty
777
                        if (String.IsNullOrWhiteSpace(info.Hash))
778
                            //Go on and create the directory
779
                            client.PutObject(account, cloudFile.Container, cloudFile.Name, fileInfo.FullName, String.Empty, "application/directory");
774 780
                    }
781
                    else
782
                    {
775 783

  
776
                    if (info.AllowedTo == "read")
777
                        return;
784
                        var cloudHash = info.Hash.ToLower();
778 785

  
779
                    //Mark the file as modified while we upload it
780
                    StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
781
                    //And then upload it
786
                        var hash = action.LocalHash.Value;
787
                        var topHash = action.TopHash.Value;
788

  
789
                        //If the file hashes match, abort the upload
790
                        if (hash == cloudHash || topHash == cloudHash)
791
                        {
792
                            //but store any metadata changes 
793
                            StatusKeeper.StoreInfo(fullFileName, info);
794
                            Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
795
                            return;
796
                        }
782 797

  
783
                    //Upload even small files using the Hashmap. The server may already containt
784
                    //the relevant folder
785 798

  
786
                    //First, calculate the tree hash
787
                    var treeHash = await Signature.CalculateTreeHashAsync(fileInfo.FullName, accountInfo.BlockSize,
788
                        accountInfo.BlockHash);
799
                        //Mark the file as modified while we upload it
800
                        StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
801
                        //And then upload it
789 802

  
790
                    await UploadWithHashMap(accountInfo, cloudFile, fileInfo, cloudFile.Name, treeHash);
803
                        //Upload even small files using the Hashmap. The server may already contain
804
                        //the relevant block
791 805

  
806
                        //First, calculate the tree hash
807
                        var treeHash = await Signature.CalculateTreeHashAsync(fileInfo.FullName, accountInfo.BlockSize,
808
                                                                              accountInfo.BlockHash);
809

  
810
                        await UploadWithHashMap(accountInfo, cloudFile, fileInfo as FileInfo, cloudFile.Name, treeHash);
811
                    }
792 812
                    //If everything succeeds, change the file and overlay status to normal
793 813
                    StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
794 814
                }
b/trunk/Pithos.Core/Agents/WorkflowAgent.cs
62 62
                    }
63 63
                    string path = state.Path.ToLower();
64 64

  
65

  
66

  
67
                    FileSystemInfo info = Directory.Exists(path) ? (FileSystemInfo) new DirectoryInfo(path) : new FileInfo(path);
68

  
65 69
                    //Bypass deleted files, unless the status is Deleted
66
                    if (!File.Exists(path) && state.Status != FileStatus.Deleted)
70
                    if (!info.Exists && state.Status != FileStatus.Deleted)
67 71
                    {
68 72
                        state.Skip = true;
69 73
                        this.StatusKeeper.ClearFileStatus(path);
......
74 78
                    }
75 79

  
76 80
                    var fileState = FileState.FindByFilePath(path);
77
                    var info = new FileInfo(path);
78 81

  
79 82

  
80 83
                    switch (state.Status)
......
88 91
                            NetworkAgent.Post(new CloudDeleteAction(accountInfo, info, fileState));
89 92
                            break;
90 93
                        case FileStatus.Renamed:
94
                            FileSystemInfo oldInfo = Directory.Exists(state.OldPath) ? (FileSystemInfo)new DirectoryInfo(state.OldPath) : new FileInfo(state.OldPath);
95
                            FileSystemInfo newInfo = Directory.Exists(state.Path) ? (FileSystemInfo)new DirectoryInfo(state.Path) : new FileInfo(state.Path);
91 96
                            NetworkAgent.Post(new CloudMoveAction(accountInfo, CloudActionType.RenameCloud,
92
                                                                  new FileInfo(state.OldPath),
93
                                                                  new FileInfo(state.Path)));
97
                                                                  oldInfo,
98
                                                                  newInfo));
94 99
                            break;
95 100
                    }
96 101

  
......
131 136
                var pendingStates = new List<WorkflowState>();
132 137
                foreach (var state in pendingEntries)
133 138
                {
134
                    if (!Directory.Exists(state.FilePath))
135 139
                        pendingStates.Add(new WorkflowState(account, state));
136 140
                }
137 141
                if (Log.IsDebugEnabled)
......
140 144

  
141 145
                foreach (var entry in pendingStates)
142 146
                {
143
                    //Remove invalid state            
144
                    if (Directory.Exists(entry.Path))
145
                    {
146
                        Debug.Assert(false, "State has path instead of file", entry.Path);
147
                        StatusKeeper.ClearFileStatus(entry.Path);
148
                        return;
149
                    }
150
                    else
151
                        Post(entry);
147
                       Post(entry);
152 148
                }
153 149
            }
154 150
        }
......
161 157
                Log.DebugFormat("Posted {0} {1} {2}", workflowState.Path, workflowState.Status, workflowState.TriggeringChange);
162 158

  
163 159
            //Remove invalid state            
164
            Debug.Assert(!Directory.Exists(workflowState.Path), "State has path instead of file", workflowState.Path);
160
            //For now, ignore paths
161
           /* if (Directory.Exists(workflowState.Path))
162
                return;*/
163
            //TODO: Need to handle folder renames            
165 164

  
166 165
            Debug.Assert(workflowState.Path.StartsWith(workflowState.AccountInfo.AccountPath, StringComparison.InvariantCultureIgnoreCase), "File from wrong account posted");
167 166

  
b/trunk/Pithos.Interfaces/FileInfoExtensions.cs
12 12
{
13 13
    public static class FileInfoExtensions
14 14
    {
15
        public static  string AsRelativeTo(this FileInfo fileInfo,string path )
15
        public static  string AsRelativeTo(this FileSystemInfo fileInfo,string path )
16 16
        {
17 17
            if (String.IsNullOrWhiteSpace(path))
18 18
                throw new ArgumentNullException("path");            
......
33 33
            return relativePath;
34 34
        }
35 35

  
36
        public static string AsRelativeUrlTo(this FileInfo fileInfo,string path )
36
        public static string AsRelativeUrlTo(this FileSystemInfo fileInfo,string path )
37 37
        {
38 38
            if (String.IsNullOrWhiteSpace(path))
39 39
                throw new ArgumentNullException("path");
b/trunk/Pithos.Interfaces/ObjectInfo.cs
114 114
        public ObjectInfo()
115 115
        {}
116 116

  
117
        public ObjectInfo(string accountPath,string accountName,FileInfo fileInfo)
117
        public ObjectInfo(string accountPath,string accountName,FileSystemInfo fileInfo)
118 118
        {
119 119
            if (String.IsNullOrWhiteSpace(accountPath))
120 120
                throw new ArgumentNullException("accountPath");
b/trunk/Pithos.Network/CloudFilesClient.cs
219 219

  
220 220
        private string GetAccountUrl(string account)
221 221
        {
222
            return new Uri(this.RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri;
222
            return new Uri(RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri;
223 223
        }
224 224

  
225 225
        public IList<ShareAccountInfo> ListSharingAccounts(DateTime? since=null)
226 226
        {
227
            using (log4net.ThreadContext.Stacks["Share"].Push("List Accounts"))
227
            using (ThreadContext.Stacks["Share"].Push("List Accounts"))
228 228
            {
229 229
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
230 230

  
......
258 258
        public IList<ObjectInfo> ListSharedObjects(DateTime? since = null)
259 259
        {
260 260

  
261
            using (log4net.ThreadContext.Stacks["Share"].Push("List Objects"))
261
            using (ThreadContext.Stacks["Share"].Push("List Objects"))
262 262
            {
263 263
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
264 264

  
......
269 269
                    var containers = ListContainers(account.name);
270 270
                    foreach (var container in containers)
271 271
                    {
272
                        var containerObjects = ListObjects(account.name, container.Name, null);
272
                        var containerObjects = ListObjects(account.name, container.Name);
273 273
                        objects.AddRange(containerObjects);
274 274
                    }
275 275
                }
......
288 288
                throw new ArgumentNullException("target");
289 289
            Contract.EndContractBlock();
290 290

  
291
            using (log4net.ThreadContext.Stacks["Share"].Push("Share Object"))
291
            using (ThreadContext.Stacks["Share"].Push("Share Object"))
292 292
            {
293 293
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
294 294

  
......
306 306
                        client.Headers.Add(headerTag, tag.Value);
307 307
                    }
308 308
                    
309
                    var content = client.DownloadStringWithRetry(target.Container, 3);
309
                    client.DownloadStringWithRetry(target.Container, 3);
310 310

  
311 311
                    
312 312
                    client.AssertStatusOK("SetTags failed");
......
340 340
                throw new ArgumentNullException("shareTo");
341 341
            Contract.EndContractBlock();
342 342

  
343
            using (log4net.ThreadContext.Stacks["Share"].Push("Share Object"))
343
            using (ThreadContext.Stacks["Share"].Push("Share Object"))
344 344
            {
345 345
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
346 346
                
......
382 382
                throw new ArgumentNullException("accountInfo");
383 383
            Contract.EndContractBlock();
384 384

  
385
            using (log4net.ThreadContext.Stacks["Account"].Push("GetPolicies"))
385
            using (ThreadContext.Stacks["Account"].Push("GetPolicies"))
386 386
            {
387 387
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
388 388

  
......
417 417
                throw new ArgumentNullException("objectInfo");
418 418
            Contract.EndContractBlock();
419 419

  
420
            using (log4net.ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))
420
            using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata"))
421 421
            {
422 422
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
423 423

  
......
452 452
                    var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name);
453 453
                    var uri = uriBuilder.Uri;
454 454

  
455
                    var content = client.UploadValues(uri,new NameValueCollection());
455
                    client.UploadValues(uri,new NameValueCollection());
456 456

  
457 457

  
458 458
                    client.AssertStatusOK("UpdateMetadata failed");
......
475 475
                throw new ArgumentNullException("containerInfo");
476 476
            Contract.EndContractBlock();
477 477

  
478
            using (log4net.ThreadContext.Stacks["Containers"].Push("UpdateMetadata"))
478
            using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata"))
479 479
            {
480 480
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
481 481

  
......
507 507
                    var uriBuilder = client.GetAddressBuilder(containerInfo.Name,"");
508 508
                    var uri = uriBuilder.Uri;
509 509

  
510
                    var content = client.UploadValues(uri,new NameValueCollection());
510
                    client.UploadValues(uri,new NameValueCollection());
511 511

  
512 512

  
513 513
                    client.AssertStatusOK("UpdateMetadata failed");
......
531 531
                throw new ArgumentNullException("container");
532 532
            Contract.EndContractBlock();
533 533

  
534
            using (log4net.ThreadContext.Stacks["Objects"].Push("List"))
534
            using (ThreadContext.Stacks["Objects"].Push("List"))
535 535
            {
536 536
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
537 537

  
......
574 574
                throw new ArgumentNullException("folder");
575 575
            Contract.EndContractBlock();
576 576

  
577
            using (log4net.ThreadContext.Stacks["Objects"].Push("List"))
577
            using (ThreadContext.Stacks["Objects"].Push("List"))
578 578
            {
579 579
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
580 580

  
......
605 605
                throw new ArgumentNullException("container", "The container property can't be empty");
606 606
            Contract.EndContractBlock();
607 607

  
608
            using (log4net.ThreadContext.Stacks["Containters"].Push("Exists"))
608
            using (ThreadContext.Stacks["Containters"].Push("Exists"))
609 609
            {
610 610
                if (Log.IsDebugEnabled) Log.DebugFormat("START");
611 611

  
......
676 676
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
677 677
            Contract.EndContractBlock();
678 678

  
679
            using (log4net.ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
679
            using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo"))
680 680
            {                
681 681

  
682 682
                using (var client = new RestClient(_baseClient))
......
735 735
                    }
736 736
                    catch (RetryException)
737 737
                    {
738
                        Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.");
738
                        Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName);
739 739
                        return ObjectInfo.Empty;
740 740
                    }
741 741
                    catch (WebException e)
......
1001 1001

  
1002 1002
                            return hashes;
1003 1003
                        }                        
1004
                    }
1005
                    else
1006
                        //Any other status code is unexpected and the exception should be rethrown
1007
                        throw ex;
1004
                    }                    
1005
                    //Any other status code is unexpected and the exception should be rethrown
1006
                    throw ex;
1008 1007
                    
1009 1008
                }
1010 1009
                //Any other status code is unexpected but there was no exception. We can probably continue processing
1011
                else
1012
                {
1013
                    Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription);                    
1014
                }
1010
                Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription);                    
1011
                
1015 1012
                return empty;
1016 1013
            });
1017 1014

  
......
1051 1048
        }
1052 1049

  
1053 1050

  
1054
        public Task PostBlock(string account, string container, byte[] block, int offset, int count)
1051
        public async Task PostBlock(string account, string container, byte[] block, int offset, int count)
1055 1052
        {
1056 1053
            if (String.IsNullOrWhiteSpace(container))
1057 1054
                throw new ArgumentNullException("container");
......
1067 1064
                throw new InvalidOperationException("Invalid Storage Url");                        
1068 1065
            Contract.EndContractBlock();
1069 1066

  
1070
                        
1071
            //Don't use a timeout because putting the hashmap may be a long process
1072
            var client = new RestClient(_baseClient) { Timeout = 0 };
1073
            if (!String.IsNullOrWhiteSpace(account))
1074
                client.BaseAddress = GetAccountUrl(account);
1075 1067

  
1076
            var builder = client.GetAddressBuilder(container, "");
1077
            //We are doing an update
1078
            builder.Query = "update";
1079
            var uri = builder.Uri;
1068
            try
1069
            {
1080 1070

  
1081
            client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1071
            //Don't use a timeout because putting the hashmap may be a long process
1072
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1073
                {
1074
                    if (!String.IsNullOrWhiteSpace(account))
1075
                        client.BaseAddress = GetAccountUrl(account);
1076

  
1077
                    var builder = client.GetAddressBuilder(container, "");
1078
                    //We are doing an update
1079
                    builder.Query = "update";
1080
                    var uri = builder.Uri;
1082 1081

  
1083
            Log.InfoFormat("[BLOCK POST] START");
1082
                    client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";
1084 1083

  
1085
            client.UploadProgressChanged += (sender, args) => 
1086
                Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",
1087
                                    args.ProgressPercentage, args.BytesSent,
1088
                                    args.TotalBytesToSend);
1089
            client.UploadFileCompleted += (sender, args) => 
1090
                Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");
1084
                    Log.InfoFormat("[BLOCK POST] START");
1091 1085

  
1092
            var buffer = new byte[count];
1093
            Buffer.BlockCopy(block,offset,buffer,0,count);
1094
            //Send the block
1095
            var uploadTask = client.UploadDataTask(uri, "POST", buffer)
1096
            .ContinueWith(upload =>
1097
            {
1098
                client.Dispose();
1086
                    client.UploadProgressChanged += (sender, args) =>
1087
                                                    Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}",
1088
                                                                   args.ProgressPercentage, args.BytesSent,
1089
                                                                   args.TotalBytesToSend);
1090
                    client.UploadFileCompleted += (sender, args) =>
1091
                                                  Log.InfoFormat("[BLOCK POST PROGRESS] Completed ");
1099 1092

  
1100
                if (upload.IsFaulted)
1101
                {
1102
                    var exception = upload.Exception.InnerException;
1103
                    Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exception);                        
1104
                    throw exception;
1093
                    var buffer = new byte[count];
1094
                    Buffer.BlockCopy(block, offset, buffer, 0, count);
1095
                    //Send the block
1096
                    await client.UploadDataTask(uri, "POST", buffer);
1097
                    Log.InfoFormat("[BLOCK POST] END");
1105 1098
                }
1106
                    
1107
                Log.InfoFormat("[BLOCK POST] END");
1108
            });
1109
            return uploadTask;            
1099
            }
1100
            catch (Exception exc)
1101
            {
1102
                Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc);                                        
1103
                throw;
1104
            }
1110 1105
        }
1111 1106

  
1112 1107

  
......
1167 1162
        /// <param name="fileName"></param>
1168 1163
        /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>
1169 1164
        /// <remarks>>This method should have no timeout or a very long one</remarks>
1170
        public Task PutObject(string account, string container, string objectName, string fileName, string hash = null)
1165
        public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")
1171 1166
        {
1172 1167
            if (String.IsNullOrWhiteSpace(container))
1173 1168
                throw new ArgumentNullException("container", "The container property can't be empty");
......
1175 1170
                throw new ArgumentNullException("objectName", "The objectName property can't be empty");
1176 1171
            if (String.IsNullOrWhiteSpace(fileName))
1177 1172
                throw new ArgumentNullException("fileName", "The fileName property can't be empty");
1178
            if (!File.Exists(fileName))
1179
                throw new FileNotFoundException("The file does not exist",fileName);
1173
/*
1174
            if (!File.Exists(fileName) && !Directory.Exists(fileName))
1175
                throw new FileNotFoundException("The file or directory does not exist",fileName);
1176
*/
1180 1177
            Contract.EndContractBlock();
1181 1178
            
1182 1179
            try
1183 1180
            {
1184 1181

  
1185
                var client = new RestClient(_baseClient){Timeout=0};
1186
                if (!String.IsNullOrWhiteSpace(account))
1187
                    client.BaseAddress = GetAccountUrl(account);
1188

  
1189
                var builder = client.GetAddressBuilder(container, objectName);
1190
                var uri = builder.Uri;
1191

  
1192
                string etag = hash ?? CalculateHash(fileName);
1193

  
1194
                client.Headers.Add("Content-Type", "application/octet-stream");
1195
                client.Headers.Add("ETag", etag);
1196

  
1197

  
1198
                Log.InfoFormat("[PUT] START {0}", objectName);
1199
                client.UploadProgressChanged += (sender, args) =>
1182
                using (var client = new RestClient(_baseClient) { Timeout = 0 })
1200 1183
                {
1201
                    using (log4net.ThreadContext.Stacks["PUT"].Push("Progress"))
1202
                    {
1203
                        Log.InfoFormat("{0} {1}% {2} of {3}", fileName, args.ProgressPercentage,
1204
                                       args.BytesSent, args.TotalBytesToSend);
1205
                    }
1206
                };
1184
                    if (!String.IsNullOrWhiteSpace(account))
1185
                        client.BaseAddress = GetAccountUrl(account);
1207 1186

  
1208
                client.UploadFileCompleted += (sender, args) =>
1209
                {
1210
                    using (log4net.ThreadContext.Stacks["PUT"].Push("Progress"))
1211
                    {
1212
                        Log.InfoFormat("Completed {0}", fileName);
1213
                    }
1214
                };
1215
                return client.UploadFileTask(uri, "PUT", fileName)
1216
                    .ContinueWith(upload=>
1217
                                      {
1218
                                          client.Dispose();
1187
                    var builder = client.GetAddressBuilder(container, objectName);
1188
                    var uri = builder.Uri;
1219 1189

  
1220
                                          if (upload.IsFaulted)
1221
                                          {
1222
                                              var exc = upload.Exception.InnerException;
1223
                                              Log.ErrorFormat("[PUT] FAIL for {0} with \r{1}",objectName,exc);
1224
                                              throw exc;
1225
                                          }
1226
                                          else
1227
                                            Log.InfoFormat("[PUT] END {0}", objectName);
1228
                                      });
1190
                    string etag = hash ?? CalculateHash(fileName);
1191

  
1192
                    client.Headers.Add("Content-Type", contentType);
1193
                    client.Headers.Add("ETag", etag);
1194

  
1195

  
1196
                    Log.InfoFormat("[PUT] START {0}", objectName);
1197
                    client.UploadProgressChanged += (sender, args) =>
1198
                                                        {
1199
                                                            using (ThreadContext.Stacks["PUT"].Push("Progress"))
1200
                                                            {
1201
                                                                Log.InfoFormat("{0} {1}% {2} of {3}", fileName,
1202
                                                                               args.ProgressPercentage,
1203
                                                                               args.BytesSent, args.TotalBytesToSend);
1204
                                                            }
1205
                                                        };
1206

  
1207
                    client.UploadFileCompleted += (sender, args) =>
1208
                                                      {
1209
                                                          using (ThreadContext.Stacks["PUT"].Push("Progress"))
1210
                                                          {
1211
                                                              Log.InfoFormat("Completed {0}", fileName);
1212
                                                          }
1213
                                                      };
1214
                    if (contentType=="application/directory")
1215
                        await client.UploadDataTaskAsync(uri, "PUT", new byte[0]);
1216
                    else
1217
                        await client.UploadFileTaskAsync(uri, "PUT", fileName);
1218
                }
1219

  
1220
                Log.InfoFormat("[PUT] END {0}", objectName);
1229 1221
            }
1230 1222
            catch (Exception exc)
1231 1223
            {
b/trunk/Pithos.Network/ICloudClient.cs
36 36

  
37 37
        #region Object operations        
38 38
        Task GetObject(string account, string container, string objectName, string fileName);
39
        Task PutObject(string account, string container, string objectName, string fileName, string hash = null);
39
        Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream");
40 40
        void DeleteObject(string account, string container, string objectName);
41 41
        //void DeleteObject(string container, string objectName, string account = null);
42 42
        void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName);
......
211 211
            return default(Task);
212 212
        }
213 213

  
214
        public Task PutObject(string account, string container, string objectName, string fileName, string hash = null)
214
        public Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")
215 215
        {
216 216
            
217 217
            Contract.Requires(!String.IsNullOrWhiteSpace(Token));
......
219 219
            Contract.Requires(!String.IsNullOrWhiteSpace(container));
220 220
            Contract.Requires(!String.IsNullOrWhiteSpace(fileName));                        
221 221
            Contract.Requires(!String.IsNullOrWhiteSpace(objectName));
222
            Contract.Requires(!String.IsNullOrWhiteSpace(contentType));
222 223

  
223 224
            return default(Task);
224 225
        }
b/trunk/Pithos.Setup.x64/Pithos.Setup.x64.vdproj
22 22
        "Entry"
23 23
        {
24 24
        "MsmKey" = "8:_00C78A0AE638245667A5681AAAA0F51F"
25
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
26
        "MsmSig" = "8:_UNDEFINED"
27
        }
28
        "Entry"
29
        {
30
        "MsmKey" = "8:_00C78A0AE638245667A5681AAAA0F51F"
31 25
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
32 26
        "MsmSig" = "8:_UNDEFINED"
33 27
        }
......
64 58
        "Entry"
65 59
        {
66 60
        "MsmKey" = "8:_07169606B53C447F3C4DEF318E8BB147"
67
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
61
        "OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34"
68 62
        "MsmSig" = "8:_UNDEFINED"
69 63
        }
70 64
        "Entry"
......
81 75
        }
82 76
        "Entry"
83 77
        {
84
        "MsmKey" = "8:_12F390B80DE8E9BA050029362F21B586"
85
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
86
        "MsmSig" = "8:_UNDEFINED"
87
        }
88
        "Entry"
89
        {
90 78
        "MsmKey" = "8:_13807DF64A18092E2104382D6350B99E"
91 79
        "OwnerKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC"
92 80
        "MsmSig" = "8:_UNDEFINED"
......
94 82
        "Entry"
95 83
        {
96 84
        "MsmKey" = "8:_13807DF64A18092E2104382D6350B99E"
97
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
98
        "MsmSig" = "8:_UNDEFINED"
99
        }
100
        "Entry"
101
        {
102
        "MsmKey" = "8:_13807DF64A18092E2104382D6350B99E"
103 85
        "OwnerKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2"
104 86
        "MsmSig" = "8:_UNDEFINED"
105 87
        }
......
129 111
        }
130 112
        "Entry"
131 113
        {
132
        "MsmKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34"
133
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
134
        "MsmSig" = "8:_UNDEFINED"
135
        }
136
        "Entry"
137
        {
138 114
        "MsmKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC"
139 115
        "OwnerKey" = "8:_UNDEFINED"
140 116
        "MsmSig" = "8:_UNDEFINED"
......
148 124
        "Entry"
149 125
        {
150 126
        "MsmKey" = "8:_2486B3A261E9B1189CF1D6B7923DEEE4"
151
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
152
        "MsmSig" = "8:_UNDEFINED"
153
        }
154
        "Entry"
155
        {
156
        "MsmKey" = "8:_2486B3A261E9B1189CF1D6B7923DEEE4"
157 127
        "OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34"
158 128
        "MsmSig" = "8:_UNDEFINED"
159 129
        }
......
178 148
        "Entry"
179 149
        {
180 150
        "MsmKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2"
181
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
182
        "MsmSig" = "8:_UNDEFINED"
183
        }
184
        "Entry"
185
        {
186
        "MsmKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2"
187 151
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
188 152
        "MsmSig" = "8:_UNDEFINED"
189 153
        }
......
196 160
        "Entry"
197 161
        {
198 162
        "MsmKey" = "8:_4DEB12AE1A97F1592C5B75ADEBF86C3D"
199
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
200
        "MsmSig" = "8:_UNDEFINED"
201
        }
202
        "Entry"
203
        {
204
        "MsmKey" = "8:_4DEB12AE1A97F1592C5B75ADEBF86C3D"
205 163
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
206 164
        "MsmSig" = "8:_UNDEFINED"
207 165
        }
......
213 171
        }
214 172
        "Entry"
215 173
        {
216
        "MsmKey" = "8:_5907BDFBCCD9996878A22FB928EC136D"
217
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
218
        "MsmSig" = "8:_UNDEFINED"
219
        }
220
        "Entry"
221
        {
222 174
        "MsmKey" = "8:_5A954DFDECEA1772693C525449B45377"
223 175
        "OwnerKey" = "8:_94AF0407772DC0469EA5A27F079132FE"
224 176
        "MsmSig" = "8:_UNDEFINED"
......
226 178
        "Entry"
227 179
        {
228 180
        "MsmKey" = "8:_5A954DFDECEA1772693C525449B45377"
229
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
230
        "MsmSig" = "8:_UNDEFINED"
231
        }
232
        "Entry"
233
        {
234
        "MsmKey" = "8:_5A954DFDECEA1772693C525449B45377"
235 181
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
236 182
        "MsmSig" = "8:_UNDEFINED"
237 183
        }
......
244 190
        "Entry"
245 191
        {
246 192
        "MsmKey" = "8:_5BDDF78B8F57F5F0C86B681FC536679D"
247
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
248
        "MsmSig" = "8:_UNDEFINED"
249
        }
250
        "Entry"
251
        {
252
        "MsmKey" = "8:_5BDDF78B8F57F5F0C86B681FC536679D"
253 193
        "OwnerKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2"
254 194
        "MsmSig" = "8:_UNDEFINED"
255 195
        }
......
274 214
        "Entry"
275 215
        {
276 216
        "MsmKey" = "8:_5CB7E7BC9A3FBCBBAAA5B13892A9804A"
277
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
278
        "MsmSig" = "8:_UNDEFINED"
279
        }
280
        "Entry"
281
        {
282
        "MsmKey" = "8:_5CB7E7BC9A3FBCBBAAA5B13892A9804A"
283 217
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
284 218
        "MsmSig" = "8:_UNDEFINED"
285 219
        }
......
310 244
        "Entry"
311 245
        {
312 246
        "MsmKey" = "8:_86C6A4ACD935E66BC53753149FB5DB8E"
313
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
314
        "MsmSig" = "8:_UNDEFINED"
315
        }
316
        "Entry"
317
        {
318
        "MsmKey" = "8:_86C6A4ACD935E66BC53753149FB5DB8E"
319 247
        "OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34"
320 248
        "MsmSig" = "8:_UNDEFINED"
321 249
        }
......
333 261
        }
334 262
        "Entry"
335 263
        {
336
        "MsmKey" = "8:_94AF0407772DC0469EA5A27F079132FE"
337
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
338
        "MsmSig" = "8:_UNDEFINED"
339
        }
340
        "Entry"
341
        {
342 264
        "MsmKey" = "8:_9C9414F9AC6F40DD9B23916692C951BE"
343 265
        "OwnerKey" = "8:_UNDEFINED"
344 266
        "MsmSig" = "8:_UNDEFINED"
......
352 274
        "Entry"
353 275
        {
354 276
        "MsmKey" = "8:_B0464BCF85DA5A9724AC7A145E4742A1"
355
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
356
        "MsmSig" = "8:_UNDEFINED"
357
        }
358
        "Entry"
359
        {
360
        "MsmKey" = "8:_B0464BCF85DA5A9724AC7A145E4742A1"
361 277
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
362 278
        "MsmSig" = "8:_UNDEFINED"
363 279
        }
......
376 292
        "Entry"
377 293
        {
378 294
        "MsmKey" = "8:_C19B1A60C99103E4AB0CB34FBD46A86E"
379
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
380
        "MsmSig" = "8:_UNDEFINED"
381
        }
382
        "Entry"
383
        {
384
        "MsmKey" = "8:_C19B1A60C99103E4AB0CB34FBD46A86E"
385 295
        "OwnerKey" = "8:_EBAF62F0335F4918AB23EDA708507D1F"
386 296
        "MsmSig" = "8:_UNDEFINED"
387 297
        }
......
417 327
        }
418 328
        "Entry"
419 329
        {
420
        "MsmKey" = "8:_CC30DE73FADCF70C6AD1C4570027C5C3"
421
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
330
        "MsmKey" = "8:_CCAE7818C895B517369F69B2359B7A13"
331
        "OwnerKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC"
422 332
        "MsmSig" = "8:_UNDEFINED"
423 333
        }
424 334
        "Entry"
......
430 340
        "Entry"
431 341
        {
432 342
        "MsmKey" = "8:_D90F384DDD0DCD7EF3877F70303E7A5E"
433
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
434
        "MsmSig" = "8:_UNDEFINED"
435
        }
436
        "Entry"
437
        {
438
        "MsmKey" = "8:_D90F384DDD0DCD7EF3877F70303E7A5E"
439 343
        "OwnerKey" = "8:_3455E6F41DFDCA75B1C8BAD894A467F2"
440 344
        "MsmSig" = "8:_UNDEFINED"
441 345
        }
......
460 364
        "Entry"
461 365
        {
462 366
        "MsmKey" = "8:_DB755C1EAC71B2C6FAB0C47B84E8DC98"
463
        "OwnerKey" = "8:_11BDAAF760654D3792A432B5EB874364"
464
        "MsmSig" = "8:_UNDEFINED"
465
        }
466
        "Entry"
467
        {
468
        "MsmKey" = "8:_DB755C1EAC71B2C6FAB0C47B84E8DC98"
469 367
        "OwnerKey" = "8:_15C6D19249891DD0DF6332A0CC3AAB34"
470 368
        "MsmSig" = "8:_UNDEFINED"
471 369
        }
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff