Revision 6bcdd8e2

b/trunk/Pithos.Client.WPF/Shell/ShellViewModel.cs
87 87
	public class ShellViewModel : Screen, IStatusNotification, IShell,
88 88
		IHandle<Notification>, IHandle<SelectiveSynchChanges>, IHandle<ShowFilePropertiesEvent>
89 89
	{
90

  
90 91
		//The Status Checker provides the current synch state
91 92
		//TODO: Could we remove the status checker and use events in its place?
92 93
		private readonly IStatusChecker _statusChecker;
......
170 171
				Log.Error("Error while starting the ShellViewModel",exc);
171 172
				throw;
172 173
			}
174

  
173 175
		}
174 176

  
175 177

  
......
832 834
				notification.Message = "Start Synchronisation";
833 835
			}
834 836

  
837
		    var progress = notification as ProgressNotification;
838
		    if (progress != null)
839
		    {
840
		        StatusMessage = String.Format("Pithos {0}\r\n{1} {2} of {3} - {4}",
841
		                                      _fileVersion.Value.FileVersion, 
842
                                              progress.Action,
843
		                                      progress.Block/(double)progress.TotalBlocks,
844
		                                      progress.FileSize.ToByteSize(),
845
		                                      progress.FileName);
846
		        return;
847
		    }
848

  
835 849
			if (String.IsNullOrWhiteSpace(notification.Message) && String.IsNullOrWhiteSpace(notification.Title))
836 850
				return;
837 851

  
b/trunk/Pithos.Client.WPF/app.config
1 1
<?xml version="1.0" encoding="utf-8"?>
2 2
<configuration>
3
  <configSections>
4
    <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
5
      <section name="Pithos.Client.WPF.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
6
    </sectionGroup>
7
    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
8
      <section name="Pithos.Client.WPF.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
9
    </sectionGroup>
10
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
11
  </configSections>
12
  <runtime>
13
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
14
      <dependentAssembly>
15
        <assemblyIdentity name="Caliburn.Micro" publicKeyToken="8e5891231f2ed21f" culture="neutral" />
16
        <bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
17
      </dependentAssembly>
18
      <dependentAssembly>
19
        <assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
20
        <bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />
21
      </dependentAssembly>
22
      <dependentAssembly>
23
        <assemblyIdentity name="NHibernate.ByteCode.Castle" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
24
        <bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />
25
      </dependentAssembly>
26
    </assemblyBinding>
27
  </runtime>
28
  <userSettings>
29
    <Pithos.Client.WPF.Properties.Settings>
30
      <setting name="PithosPath" serializeAs="String">
31
        <value>e:\Pithos</value>
32
      </setting>
33
      <setting name="IconPath" serializeAs="String">
34
        <value>C:\Program Files\Common Files\TortoiseOverlays\icons\XPStyle</value>
35
      </setting>
36
      <setting name="ProxyServer" serializeAs="String">
37
        <value />
38
      </setting>
39
      <setting name="ProxyPort" serializeAs="String">
40
        <value>8080</value>
41
      </setting>
42
      <setting name="ProxyUsername" serializeAs="String">
43
        <value />
44
      </setting>
45
      <setting name="ProxyPassword" serializeAs="String">
46
        <value />
47
      </setting>
48
      <setting name="ProxyAuthentication" serializeAs="String">
49
        <value>True</value>
50
      </setting>
51
      <setting name="ExtensionsActivated" serializeAs="String">
52
        <value>True</value>
53
      </setting>
54
      <setting name="UserName" serializeAs="String">
55
        <value>pkanavos</value>
56
      </setting>
57
      <setting name="ApiKey" serializeAs="String">
58
        <value>9d3cb7b231e96f72ebe96af1c6cd5112</value>
59
      </setting>
60
      <setting name="ShowDesktopNotifications" serializeAs="String">
61
        <value>True</value>
62
      </setting>
63
      <setting name="StartOnSystemStartup" serializeAs="String">
64
        <value>True</value>
65
      </setting>
66
      <setting name="UseNoProxy" serializeAs="String">
67
        <value>False</value>
68
      </setting>
69
      <setting name="UseDefaultProxy" serializeAs="String">
70
        <value>True</value>
71
      </setting>
72
      <setting name="UseManualProxy" serializeAs="String">
73
        <value>False</value>
74
      </setting>
75
      <setting name="MustUpgrade" serializeAs="String">
76
        <value>True</value>
77
      </setting>
78
      <setting name="PollingInterval" serializeAs="String">
79
        <value>10</value>
80
      </setting>
81
      <setting name="ProxyDomain" serializeAs="String">
82
        <value />
83
      </setting>
84
      <setting name="HashingParallelism" serializeAs="String">
85
        <value>1</value>
86
      </setting>
87
      <setting name="StartupDelay" serializeAs="String">
88
        <value>00:01:00</value>
89
      </setting>
90
    </Pithos.Client.WPF.Properties.Settings>
91
  </userSettings>
92
  <connectionStrings>
93
    <add name="Sqlite_InMemory" providerName="System.Data.SQLite" connectionString="Data Source=:memory:;Version=3;New=True" />
94
  </connectionStrings>
95
  <system.data>
96
    <DbProviderFactories>
97
      <remove invariant="System.Data.SQLite" />
98
      <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
99
    </DbProviderFactories>
100
  </system.data>
3
	<configSections>
4
		<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
5
			<section name="Pithos.Client.WPF.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
6
		</sectionGroup>
7
		<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
8
			<section name="Pithos.Client.WPF.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
9
		</sectionGroup>
10
		<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
11
	</configSections>
12
	<runtime>
13
		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
14
			<dependentAssembly>
15
				<assemblyIdentity name="Caliburn.Micro" publicKeyToken="8e5891231f2ed21f" culture="neutral" />
16
				<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
17
			</dependentAssembly>
18
			<dependentAssembly>
19
				<assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
20
				<bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />
21
			</dependentAssembly>
22
			<dependentAssembly>
23
				<assemblyIdentity name="NHibernate.ByteCode.Castle" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
24
				<bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />
25
			</dependentAssembly>
26
		</assemblyBinding>
27
	</runtime>
28
	<userSettings>
29
		<Pithos.Client.WPF.Properties.Settings>
30
			<setting name="PithosPath" serializeAs="String">
31
				<value>e:\Pithos</value>
32
			</setting>
33
			<setting name="IconPath" serializeAs="String">
34
				<value>C:\Program Files\Common Files\TortoiseOverlays\icons\XPStyle</value>
35
			</setting>
36
			<setting name="ProxyServer" serializeAs="String">
37
				<value />
38
			</setting>
39
			<setting name="ProxyPort" serializeAs="String">
40
				<value>8080</value>
41
			</setting>
42
			<setting name="ProxyUsername" serializeAs="String">
43
				<value />
44
			</setting>
45
			<setting name="ProxyPassword" serializeAs="String">
46
				<value />
47
			</setting>
48
			<setting name="ProxyAuthentication" serializeAs="String">
49
				<value>True</value>
50
			</setting>
51
			<setting name="ExtensionsActivated" serializeAs="String">
52
				<value>True</value>
53
			</setting>
54
			<setting name="UserName" serializeAs="String">
55
				<value>pkanavos</value>
56
			</setting>
57
			<setting name="ApiKey" serializeAs="String">
58
				<value>9d3cb7b231e96f72ebe96af1c6cd5112</value>
59
			</setting>
60
			<setting name="ShowDesktopNotifications" serializeAs="String">
61
				<value>True</value>
62
			</setting>
63
			<setting name="StartOnSystemStartup" serializeAs="String">
64
				<value>True</value>
65
			</setting>
66
			<setting name="UseNoProxy" serializeAs="String">
67
				<value>False</value>
68
			</setting>
69
			<setting name="UseDefaultProxy" serializeAs="String">
70
				<value>True</value>
71
			</setting>
72
			<setting name="UseManualProxy" serializeAs="String">
73
				<value>False</value>
74
			</setting>
75
			<setting name="MustUpgrade" serializeAs="String">
76
				<value>True</value>
77
			</setting>
78
			<setting name="PollingInterval" serializeAs="String">
79
				<value>10</value>
80
			</setting>
81
			<setting name="ProxyDomain" serializeAs="String">
82
				<value />
83
			</setting>
84
			<setting name="HashingParallelism" serializeAs="String">
85
				<value>1</value>
86
			</setting>
87
			<setting name="StartupDelay" serializeAs="String">
88
				<value>00:01:00</value>
89
			</setting>
90
		</Pithos.Client.WPF.Properties.Settings>
91
	</userSettings>
92
	<connectionStrings>
93
		<add name="Sqlite_InMemory" providerName="System.Data.SQLite" connectionString="Data Source=:memory:;Version=3;New=True" />
94
	</connectionStrings>
95
	<system.data>
96
		<DbProviderFactories>
97
			<remove invariant="System.Data.SQLite" />
98
			<add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
99
		</DbProviderFactories>
100
	</system.data>
101 101

  
102
  <startup useLegacyV2RuntimeActivationPolicy="true">
103
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />
104
  </startup>
105
  <applicationSettings>
106
    <Pithos.Client.WPF.Properties.Settings>
107
      <setting name="CloudfilesAuthenticationUrl" serializeAs="String">
108
        <value>https://auth.api.rackspacecloud.com</value>
109
      </setting>
110
      <setting name="PithosLoginUrl" serializeAs="String">
111
        <value>https://pithos.dev.grnet.gr/login</value>
112
      </setting>
113
      <setting name="FeedbackUri" serializeAs="String">
114
        <value>https://pithos.dev.grnet.gr/im/feedback</value>
115
      </setting>
116
      <setting name="ProductionServer" serializeAs="String">
117
        <value>https://plus.pithos.grnet.gr</value>
118
      </setting>
119
      <setting name="DevelopmentServer" serializeAs="String">
120
        <value>https://pithos.dev.grnet.gr</value>
121
      </setting>
122
    </Pithos.Client.WPF.Properties.Settings>
123
  </applicationSettings>
124
  <log4net>
125
    <appender name="TraceAppender" type="log4net.Appender.TraceAppender" >
126
      <layout type="log4net.Layout.PatternLayout">
127
        <conversionPattern value="%logger (%property{myContext}) [%level]- %message%newline" />
128
      </layout>
129
      <filter type="log4net.Filter.LevelRangeFilter">
130
        <levelMin value="INFO" />
131
        <levelMax value="FATAL" />
132
      </filter>
133
    </appender>
134
    	
135
	<appender name="DumpFileAppender" type="log4net.Appender.RollingFileAppender">
136
		<file value="log.txt" />
137
		<appendToFile value="false" />
138
		<rollingStyle value="Size" />
139
		<maxSizeRollBackups value="10" />
140
		<maximumFileSize value="100KB" />
141
		<staticLogFileName value="true" />
142
		<layout type="log4net.Layout.XMLLayout"/>
143
	</appender>
102
	<startup useLegacyV2RuntimeActivationPolicy="true">
103
		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />
104
	</startup>
105
	<applicationSettings>
106
		<Pithos.Client.WPF.Properties.Settings>
107
			<setting name="CloudfilesAuthenticationUrl" serializeAs="String">
108
				<value>https://auth.api.rackspacecloud.com</value>
109
			</setting>
110
			<setting name="PithosLoginUrl" serializeAs="String">
111
				<value>https://pithos.dev.grnet.gr/login</value>
112
			</setting>
113
			<setting name="FeedbackUri" serializeAs="String">
114
				<value>https://pithos.dev.grnet.gr/im/feedback</value>
115
			</setting>
116
			<setting name="ProductionServer" serializeAs="String">
117
				<value>https://plus.pithos.grnet.gr</value>
118
			</setting>
119
			<setting name="DevelopmentServer" serializeAs="String">
120
				<value>https://pithos.dev.grnet.gr</value>
121
			</setting>
122
		</Pithos.Client.WPF.Properties.Settings>
123
	</applicationSettings>
124
	<log4net>
125
		<appender name="TraceAppender" type="log4net.Appender.TraceAppender" >
126
			<layout type="log4net.Layout.PatternLayout">
127
				<conversionPattern value="%logger (%property{CloudFile}) [%level]- %message%newline" />
128
			</layout>
129
			<filter type="log4net.Filter.LoggerMatchFilter">
130
				<loggerToMatch value="NHibernate" />
131
				<acceptOnMatch value="false" />
132
			</filter>
133
			<filter type="log4net.Filter.LoggerMatchFilter">
134
				<loggerToMatch value="Caliburn" />
135
				<acceptOnMatch value="false" />
136
			</filter>
137
		</appender>
144 138

  
145
	<appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender" >
146
    <layout type="log4net.Layout.PatternLayout">
147
          <conversionPattern value="%logger (%property{myContext}) [%level]- %message%newline" />
148
    </layout>
149
</appender>
139
		<appender name="DumpFileAppender" type="log4net.Appender.RollingFileAppender">
140
			<file value="log.txt" />
141
			<appendToFile value="false" />
142
			<rollingStyle value="Size" />
143
			<maxSizeRollBackups value="10" />
144
			<maximumFileSize value="100KB" />
145
			<staticLogFileName value="true" />
146
			<layout type="log4net.Layout.XMLLayout"/>
147
		</appender>
150 148

  
151
    <appender name="LossyFileAppender" type="log4net.Appender.BufferingForwardingAppender">
152
      <filter type="log4net.Filter.LevelRangeFilter">
153
        <levelMin value="DEBUG" />
154
        <levelMax value="FATAL" />
155
      </filter>
156
      <bufferSize value="30" />
157
      <lossy value="true"/>
158
      <evaluator type="log4net.Core.LevelEvaluator">
159
        <threshold value="ERROR" />
160
      </evaluator>
161
      <appender-ref ref="DumpFileAppender" />
162
	  
163
    </appender>
149
		<appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender" >
150
			<layout type="log4net.Layout.PatternLayout">
151
				<conversionPattern value="%logger (%property{Operation}) [%level]- %message%newline" />
152
			</layout>
153
		</appender>
164 154

  
165
    <root>
166
      <level value="DEBUG" />
167
	    <appender-ref ref="LossyFileAppender" />
168
		<appender-ref ref="TraceAppender" />
169
		<appender-ref ref="OutputDebugStringAppender" />
170
	</root>
171
  </log4net>
155
		<appender name="LossyFileAppender" type="log4net.Appender.BufferingForwardingAppender">
156
			<filter type="log4net.Filter.LevelRangeFilter">
157
				<levelMin value="DEBUG" />
158
				<levelMax value="FATAL" />
159
			</filter>
160
			<bufferSize value="30" />
161
			<lossy value="true"/>
162
			<evaluator type="log4net.Core.LevelEvaluator">
163
				<threshold value="ERROR" />
164
			</evaluator>
165
			<appender-ref ref="DumpFileAppender" />
166

  
167
		</appender>
168

  
169
		<logger name="NHibernate" additivity="false">
170
			<level value="WARN"/>
171
			<appender-ref ref="TraceAppender"/>
172
		</logger>
173

  
174
		<logger name="Caliburn" additivity="false">
175
			<level value="WARN"/>
176
			<appender-ref ref="TraceAppender"/>
177
		</logger>
178

  
179
		<root>
180
			<level value="DEBUG" />
181
			<appender-ref ref="LossyFileAppender" />
182
			<appender-ref ref="TraceAppender" />
183
			<appender-ref ref="OutputDebugStringAppender" />
184
		</root>
185
	</log4net>
172 186
</configuration>
b/trunk/Pithos.Core/Agents/DeleteAgent.cs
111 111

  
112 112
            var accountInfo = action.AccountInfo;
113 113

  
114
            using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
114
            using (log4net.ThreadContext.Stacks["Operation"].Push("ProcessDelete"))
115 115
            {
116 116
                Log.InfoFormat("[ACTION] Start Processing {0}", action);
117 117

  
......
212 212

  
213 213
            var fileAgent = GetFileAgent(accountInfo);
214 214

  
215
            using (ThreadContext.Stacks["DeleteCloudFile"].Push("Delete"))
215
            using (ThreadContext.Stacks["Operation"].Push("DeleteCloudFile"))
216 216
            {
217 217
                var fileName = cloudFile.RelativeUrlToFilePath(accountInfo.UserName);
218 218
                var info = fileAgent.GetFileSystemInfo(fileName);
b/trunk/Pithos.Core/Agents/NetworkAgent.cs
115 115

  
116 116

  
117 117

  
118
            using (log4net.ThreadContext.Stacks["NETWORK"].Push("PROCESS"))
118
            using (log4net.ThreadContext.Stacks["Operation"].Push(action.ToString()))
119 119
            {                
120
                Log.InfoFormat("[ACTION] Start Processing {0}", action);
121 120

  
122 121
                var cloudFile = action.CloudFile;
123 122
                var downloadPath = action.GetDownloadPath();
......
125 124
                try
126 125
                {
127 126
                    _proceedEvent.Reset();
128
                    UpdateStatus(PithosStatus.Syncing);
127
                    //UpdateStatus(PithosStatus.Syncing);
129 128
                    var accountInfo = action.AccountInfo;
130 129

  
131 130
                    if (action.Action == CloudActionType.DeleteCloud)
......
169 168
                                break;
170 169
                        }
171 170
                    }
172
                    Log.InfoFormat("[ACTION] End Processing {0}:{1}->{2}", action.Action, action.LocalFile,
171
                    Log.InfoFormat("End Processing {0}:{1}->{2}", action.Action, action.LocalFile,
173 172
                                           action.CloudFile.Name);
174 173
                }
175 174
                catch (WebException exc)
......
230 229
            if (action.CloudFile == null)
231 230
                throw new ArgumentException("The action's cloud file is not specified", "action");
232 231
            Contract.EndContractBlock();
232
            using (ThreadContext.Stacks["Operation"].Push("RenameLocalFile"))
233
            {
233 234

  
234
            //We assume that the local file already exists, otherwise the poll agent
235
            //would have issued a download request
236

  
237
            var currentInfo = action.CloudFile;
238
            var previousInfo = action.CloudFile.Previous;
239
            var fileAgent = FileAgent.GetFileAgent(accountInfo);
235
                //We assume that the local file already exists, otherwise the poll agent
236
                //would have issued a download request
240 237

  
241
            var previousRelativepath = previousInfo.RelativeUrlToFilePath(accountInfo.UserName);
242
            var previousFile = fileAgent.GetFileSystemInfo(previousRelativepath);
238
                var currentInfo = action.CloudFile;
239
                var previousInfo = action.CloudFile.Previous;
240
                var fileAgent = FileAgent.GetFileAgent(accountInfo);
243 241

  
244
            //In every case we need to move the local file first
245
            MoveLocalFile(accountInfo, previousFile, fileAgent, currentInfo);
242
                var previousRelativepath = previousInfo.RelativeUrlToFilePath(accountInfo.UserName);
243
                var previousFile = fileAgent.GetFileSystemInfo(previousRelativepath);
246 244

  
245
                //In every case we need to move the local file first
246
                MoveLocalFile(accountInfo, previousFile, fileAgent, currentInfo);
247
            }
247 248
        }
248 249

  
249 250
        private void MoveLocalFile(AccountInfo accountInfo, FileSystemInfo previousFile, FileAgent fileAgent,
......
287 288
            if (action.CloudFile== null)
288 289
                throw new ArgumentException("The action's cloud file is not specified", "action");
289 290
            Contract.EndContractBlock();
291
            using (ThreadContext.Stacks["Operation"].Push("SyncFiles"))
292
            {
290 293

  
291
            var localFile = action.LocalFile;
292
            var cloudFile = action.CloudFile;
293
            var downloadPath=action.LocalFile.GetProperCapitalization();
294
                var localFile = action.LocalFile;
295
                var cloudFile = action.CloudFile;
296
                var downloadPath = action.LocalFile.GetProperCapitalization();
294 297

  
295
            var cloudHash = cloudFile.Hash.ToLower();
296
            var previousCloudHash = cloudFile.PreviousHash.ToLower();
297
            var localHash = action.LocalHash.Value.ToLower();
298
            var topHash = action.TopHash.Value.ToLower();
298
                var cloudHash = cloudFile.Hash.ToLower();
299
                var previousCloudHash = cloudFile.PreviousHash.ToLower();
300
                var localHash = action.LocalHash.Value.ToLower();
301
                var topHash = action.TopHash.Value.ToLower();
299 302

  
300
            //At this point we know that an object has changed on the server and that a local
301
            //file already exists. We need to decide whether the file has only changed on 
302
            //the server or there is a conflicting change on the client.
303
            //
304
            
305
            //Not enough to compare only the local hashes (MD5), also have to compare the tophashes            
306
            //If any of the hashes match, we are done
307
            if ((cloudHash == localHash || cloudHash == topHash))
308
            {
309
                Log.InfoFormat("Skipping {0}, hashes match",downloadPath);
310
                return;
311
            }
303
                //At this point we know that an object has changed on the server and that a local
304
                //file already exists. We need to decide whether the file has only changed on 
305
                //the server or there is a conflicting change on the client.
306
                //
312 307

  
313
            //The hashes DON'T match. We need to sync
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))
311
                {
312
                    Log.InfoFormat("Skipping {0}, hashes match", downloadPath);
313
                    return;
314
                }
314 315

  
315
            // If the previous tophash matches the local tophash, the file was only changed on the server. 
316
            if (localHash == previousCloudHash)
317
            {
318
                await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
319
            }
320
            else
321
            {
322
                //If the previous and local hash don't match, there was a local conflict
323
                //that was not uploaded to the server. We have a conflict
324
                ReportConflict(downloadPath);
316
                //The hashes DON'T match. We need to sync
317

  
318
                // If the previous tophash matches the local tophash, the file was only changed on the server. 
319
                if (localHash == previousCloudHash)
320
                {
321
                    await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
322
                }
323
                else
324
                {
325
                    //If the previous and local hash don't match, there was a local conflict
326
                    //that was not uploaded to the server. We have a conflict
327
                    ReportConflict(downloadPath);
328
                }
325 329
            }
326 330
        }
327 331

  
......
413 417
            if (action.OldCloudFile==null)
414 418
                throw new ArgumentException("OldCloudFile","action");
415 419
            Contract.EndContractBlock();
416
            
417
            
418
            var newFilePath = action.LocalFile.FullName;
419
            
420
            //How do we handle concurrent renames and deletes/uploads/downloads?
421
            //* A conflicting upload means that a file was renamed before it had a chance to finish uploading
422
            //  This should never happen as the network agent executes only one action at a time
423
            //* A conflicting download means that the file was modified on the cloud. While we can go on and complete
424
            //  the rename, there may be a problem if the file is downloaded in blocks, as subsequent block requests for the 
425
            //  same name will fail.
426
            //  This should never happen as the network agent executes only one action at a time.
427
            //* A conflicting delete can happen if the rename was followed by a delete action that didn't have the chance
428
            //  to remove the rename from the queue.
429
            //  We can probably ignore this case. It will result in an error which should be ignored            
430 420

  
431
            
432
            //The local file is already renamed
433
            StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Modified);
421
            using (ThreadContext.Stacks["Operation"].Push("RenameCloudFile"))
422
            {
434 423

  
424
                var newFilePath = action.LocalFile.FullName;
425

  
426
                //How do we handle concurrent renames and deletes/uploads/downloads?
427
                //* A conflicting upload means that a file was renamed before it had a chance to finish uploading
428
                //  This should never happen as the network agent executes only one action at a time
429
                //* A conflicting download means that the file was modified on the cloud. While we can go on and complete
430
                //  the rename, there may be a problem if the file is downloaded in blocks, as subsequent block requests for the 
431
                //  same name will fail.
432
                //  This should never happen as the network agent executes only one action at a time.
433
                //* A conflicting delete can happen if the rename was followed by a delete action that didn't have the chance
434
                //  to remove the rename from the queue.
435
                //  We can probably ignore this case. It will result in an error which should be ignored            
435 436

  
436
            var account = action.CloudFile.Account ?? accountInfo.UserName;
437
            var container = action.CloudFile.Container;
438
            
439
            var client = new CloudFilesClient(accountInfo);
440
            //TODO: What code is returned when the source file doesn't exist?
441
            client.MoveObject(account, container, action.OldCloudFile.Name, container, action.CloudFile.Name);
442 437

  
443
            StatusKeeper.SetFileStatus(newFilePath, FileStatus.Unchanged);
444
            StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Normal);
445
            NativeMethods.RaiseChangeNotification(newFilePath);
438
                //The local file is already renamed
439
                StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Modified);
440

  
441

  
442
                var account = action.CloudFile.Account ?? accountInfo.UserName;
443
                var container = action.CloudFile.Container;
444

  
445
                var client = new CloudFilesClient(accountInfo);
446
                //TODO: What code is returned when the source file doesn't exist?
447
                client.MoveObject(account, container, action.OldCloudFile.Name, container, action.CloudFile.Name);
448

  
449
                StatusKeeper.SetFileStatus(newFilePath, FileStatus.Unchanged);
450
                StatusKeeper.SetFileOverlayStatus(newFilePath, FileOverlayStatus.Normal);
451
                NativeMethods.RaiseChangeNotification(newFilePath);
452
            }
446 453
        }
447 454

  
448 455
        //Download a file.
......
461 468
            if (!Path.IsPathRooted(filePath))
462 469
                throw new ArgumentException("The filePath must be rooted", "filePath");
463 470
            Contract.EndContractBlock();
464
            
465 471

  
466
            var localPath = Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
467
            var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
468

  
469
            var url = relativeUrl.ToString();
470
            if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
471
                return;
472
            using (ThreadContext.Stacks["Operation"].Push("DownloadCloudFile"))
473
            {
472 474

  
475
                var localPath = Interfaces.FileInfoExtensions.GetProperFilePathCapitalization(filePath);
476
                var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);
473 477

  
474
            //Are we already downloading or uploading the file? 
475
            using (var gate=NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
476
            {
477
                if (gate.Failed)
478
                var url = relativeUrl.ToString();
479
                if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
478 480
                    return;
479
                
480
                var client = new CloudFilesClient(accountInfo);
481
                var account = cloudFile.Account;
482
                var container = cloudFile.Container;
483 481

  
484
                if (cloudFile.Content_Type == @"application/directory")
482

  
483
                //Are we already downloading or uploading the file? 
484
                using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
485 485
                {
486
                    if (!Directory.Exists(localPath))
487
                        Directory.CreateDirectory(localPath);
488
                }
489
                else
490
                {                    
491
                    //Retrieve the hashmap from the server
492
                    var serverHash = await client.GetHashMap(account, container, url);
493
                    //If it's a small file
494
                    if (serverHash.Hashes.Count == 1)
495
                        //Download it in one go
496
                        await
497
                            DownloadEntireFileAsync(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
498
                        //Otherwise download it block by block
499
                    else
500
                        await DownloadWithBlocks(accountInfo, client, cloudFile, relativeUrl, localPath, serverHash);
486
                    if (gate.Failed)
487
                        return;
488

  
489
                    var client = new CloudFilesClient(accountInfo);
490
                    var account = cloudFile.Account;
491
                    var container = cloudFile.Container;
501 492

  
502
                    if (cloudFile.AllowedTo == "read")
493
                    if (cloudFile.Content_Type == @"application/directory")
503 494
                    {
504
                        var attributes = File.GetAttributes(localPath);
505
                        File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);                        
495
                        if (!Directory.Exists(localPath))
496
                            Directory.CreateDirectory(localPath);
497
                    }
498
                    else
499
                    {
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")
513
                        {
514
                            var attributes = File.GetAttributes(localPath);
515
                            File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);
516
                        }
506 517
                    }
507
                }
508 518

  
509
                //Now we can store the object's metadata without worrying about ghost status entries
510
                StatusKeeper.StoreInfo(localPath, cloudFile);
511
                
519
                    //Now we can store the object's metadata without worrying about ghost status entries
520
                    StatusKeeper.StoreInfo(localPath, cloudFile);
521

  
522
                }
512 523
            }
513 524
        }
514 525

  
......
604 615
            //And compare it with the server's hash
605 616
            var upHashes = serverHash.GetHashesAsStrings();
606 617
            var localHashes = treeHash.HashDictionary;
618
            ReportDownloadProgress(Path.GetFileName(localPath),0,upHashes.Length,cloudFile.Bytes);
607 619
            for (int i = 0; i < upHashes.Length; i++)
608 620
            {
609 621
                //For every non-matching hash
......
633 645

  
634 646
                    Log.InfoFormat("[BLOCK GET] FINISH {0} of {1} for {2}", i, upHashes.Length, localPath);
635 647
                }
648
                ReportDownloadProgress(Path.GetFileName(localPath), i, upHashes.Length, cloudFile.Bytes);
636 649
            }
637 650

  
638 651
            //Want to avoid notifications if no changes were made
......
652 665
            if (action == null)
653 666
                throw new ArgumentNullException("action");           
654 667
            Contract.EndContractBlock();
655

  
656
            try
657
            {                
658
                var accountInfo = action.AccountInfo;
659

  
660
                var fileInfo = action.LocalFile;
661

  
662
                if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))
663
                    return;
664
                
665
                var relativePath = fileInfo.AsRelativeTo(accountInfo.AccountPath);
666
                if (relativePath.StartsWith(FolderConstants.OthersFolder))
668
            using(ThreadContext.Stacks["Operation"].Push("UploadCloudFile"))
669
            {
670
                try
667 671
                {
668
                    var parts = relativePath.Split('\\');
669
                    var accountName = parts[1];
670
                    var oldName = accountInfo.UserName;
671
                    var absoluteUri = accountInfo.StorageUri.AbsoluteUri;
672
                    var nameIndex = absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
673
                    var root = absoluteUri.Substring(0, nameIndex);
674

  
675
                    accountInfo = new AccountInfo
676
                    {
677
                        UserName = accountName,
678
                        AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
679
                        StorageUri = new Uri(root + accountName),
680
                        BlockHash = accountInfo.BlockHash,
681
                        BlockSize = accountInfo.BlockSize,
682
                        Token = accountInfo.Token
683
                    };
684
                }
672
                    var accountInfo = action.AccountInfo;
685 673

  
674
                    var fileInfo = action.LocalFile;
686 675

  
687
                var fullFileName = fileInfo.GetProperCapitalization();
688
                using (var gate = NetworkGate.Acquire(fullFileName, NetworkOperation.Uploading))
689
                {
690
                    //Abort if the file is already being uploaded or downloaded
691
                    if (gate.Failed)
676
                    if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))
692 677
                        return;
693 678

  
694
                    var cloudFile = action.CloudFile;
695
                    var account = cloudFile.Account ?? accountInfo.UserName;
696

  
697
                    var client = new CloudFilesClient(accountInfo);                    
698
                    //Even if GetObjectInfo times out, we can proceed with the upload            
699
                    var info = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name);
700

  
701
                    //If this is a read-only file, do not upload changes
702
                    if (info.AllowedTo == "read")
703
                        return;
704
                    
705
                    //TODO: Check how a directory hash is calculated -> All dirs seem to have the same hash
706
                    if (fileInfo is DirectoryInfo)
679
                    var relativePath = fileInfo.AsRelativeTo(accountInfo.AccountPath);
680
                    if (relativePath.StartsWith(FolderConstants.OthersFolder))
707 681
                    {
708
                        //If the directory doesn't exist the Hash property will be empty
709
                        if (String.IsNullOrWhiteSpace(info.Hash))
710
                            //Go on and create the directory
711
                            await client.PutObject(account, cloudFile.Container, cloudFile.Name, fullFileName, String.Empty, "application/directory");
682
                        var parts = relativePath.Split('\\');
683
                        var accountName = parts[1];
684
                        var oldName = accountInfo.UserName;
685
                        var absoluteUri = accountInfo.StorageUri.AbsoluteUri;
686
                        var nameIndex = absoluteUri.IndexOf(oldName, StringComparison.Ordinal);
687
                        var root = absoluteUri.Substring(0, nameIndex);
688

  
689
                        accountInfo = new AccountInfo
690
                                          {
691
                                              UserName = accountName,
692
                                              AccountPath = Path.Combine(accountInfo.AccountPath, parts[0], parts[1]),
693
                                              StorageUri = new Uri(root + accountName),
694
                                              BlockHash = accountInfo.BlockHash,
695
                                              BlockSize = accountInfo.BlockSize,
696
                                              Token = accountInfo.Token
697
                                          };
712 698
                    }
713
                    else
699

  
700

  
701
                    var fullFileName = fileInfo.GetProperCapitalization();
702
                    using (var gate = NetworkGate.Acquire(fullFileName, NetworkOperation.Uploading))
714 703
                    {
704
                        //Abort if the file is already being uploaded or downloaded
705
                        if (gate.Failed)
706
                            return;
715 707

  
716
                        var cloudHash = info.Hash.ToLower();
708
                        var cloudFile = action.CloudFile;
709
                        var account = cloudFile.Account ?? accountInfo.UserName;
717 710

  
718
                        var hash = action.LocalHash.Value;
719
                        var topHash = action.TopHash.Value;
711
                        var client = new CloudFilesClient(accountInfo);
712
                        //Even if GetObjectInfo times out, we can proceed with the upload            
713
                        var info = client.GetObjectInfo(account, cloudFile.Container, cloudFile.Name);
720 714

  
721
                        //If the file hashes match, abort the upload
722
                        if (hash == cloudHash || topHash == cloudHash)
723
                        {
724
                            //but store any metadata changes 
725
                            StatusKeeper.StoreInfo(fullFileName, info);
726
                            Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
715
                        //If this is a read-only file, do not upload changes
716
                        if (info.AllowedTo == "read")
727 717
                            return;
718

  
719
                        //TODO: Check how a directory hash is calculated -> All dirs seem to have the same hash
720
                        if (fileInfo is DirectoryInfo)
721
                        {
722
                            //If the directory doesn't exist the Hash property will be empty
723
                            if (String.IsNullOrWhiteSpace(info.Hash))
724
                                //Go on and create the directory
725
                                await
726
                                    client.PutObject(account, cloudFile.Container, cloudFile.Name, fullFileName,
727
                                                     String.Empty, "application/directory");
728 728
                        }
729
                        else
730
                        {
731

  
732
                            var cloudHash = info.Hash.ToLower();
733

  
734
                            var hash = action.LocalHash.Value;
735
                            var topHash = action.TopHash.Value;
736

  
737
                            //If the file hashes match, abort the upload
738
                            if (hash == cloudHash || topHash == cloudHash)
739
                            {
740
                                //but store any metadata changes 
741
                                StatusKeeper.StoreInfo(fullFileName, info);
742
                                Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);
743
                                return;
744
                            }
729 745

  
730 746

  
731
                        //Mark the file as modified while we upload it
732
                        StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
733
                        //And then upload it
747
                            //Mark the file as modified while we upload it
748
                            StatusKeeper.SetFileOverlayStatus(fullFileName, FileOverlayStatus.Modified);
749
                            //And then upload it
734 750

  
735
                        //Upload even small files using the Hashmap. The server may already contain
736
                        //the relevant block
751
                            //Upload even small files using the Hashmap. The server may already contain
752
                            //the relevant block
737 753

  
738
                        //First, calculate the tree hash
739
                        var treeHash = await Signature.CalculateTreeHashAsync(fullFileName, accountInfo.BlockSize,
740
                                                                              accountInfo.BlockHash, 2);
754
                            //First, calculate the tree hash
755
                            var treeHash = await Signature.CalculateTreeHashAsync(fullFileName, accountInfo.BlockSize,
756
                                                                                  accountInfo.BlockHash, 2);
741 757

  
742
                        await UploadWithHashMap(accountInfo, cloudFile, fileInfo as FileInfo, cloudFile.Name, treeHash);
758
                            await
759
                                UploadWithHashMap(accountInfo, cloudFile, fileInfo as FileInfo, cloudFile.Name, treeHash);
760
                        }
761
                        //If everything succeeds, change the file and overlay status to normal
762
                        StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
743 763
                    }
744
                    //If everything succeeds, change the file and overlay status to normal
745
                    StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged, FileOverlayStatus.Normal);
764
                    //Notify the Shell to update the overlays
765
                    NativeMethods.RaiseChangeNotification(fullFileName);
766
                    StatusNotification.NotifyChangedFile(fullFileName);
767
                }
768
                catch (AggregateException ex)
769
                {
770
                    var exc = ex.InnerException as WebException;
771
                    if (exc == null)
772
                        throw ex.InnerException;
773
                    if (HandleUploadWebException(action, exc))
774
                        return;
775
                    throw;
776
                }
777
                catch (WebException ex)
778
                {
779
                    if (HandleUploadWebException(action, ex))
780
                        return;
781
                    throw;
782
                }
783
                catch (Exception ex)
784
                {
785
                    Log.Error("Unexpected error while uploading file", ex);
786
                    throw;
746 787
                }
747
                //Notify the Shell to update the overlays
748
                NativeMethods.RaiseChangeNotification(fullFileName);
749
                StatusNotification.NotifyChangedFile(fullFileName);
750
            }
751
            catch (AggregateException ex)
752
            {
753
                var exc = ex.InnerException as WebException;
754
                if (exc == null)
755
                    throw ex.InnerException;
756
                if (HandleUploadWebException(action, exc)) 
757
                    return;
758
                throw;
759
            }
760
            catch (WebException ex)
761
            {
762
                if (HandleUploadWebException(action, ex))
763
                    return;
764
                throw;
765
            }
766
            catch (Exception ex)
767
            {
768
                Log.Error("Unexpected error while uploading file", ex);
769
                throw;
770 788
            }
771

  
772 789
        }
773 790

  
774 791

  
......
810 827
            var account = cloudFile.Account ?? accountInfo.UserName;
811 828
            var container = cloudFile.Container ;
812 829

  
813
            var client = new CloudFilesClient(accountInfo);
830
            var client = new CloudFilesClient(accountInfo);            
814 831
            //Send the hashmap to the server            
815 832
            var missingHashes =  await client.PutHashMap(account, container, url, treeHash);
833
            int block = 0;
834
            ReportUploadProgress(fileInfo.Name,block++, missingHashes.Count, fileInfo.Length);
816 835
            //If the server returns no missing hashes, we are done
817 836
            while (missingHashes.Count > 0)
818 837
            {
......
834 853
                    }
835 854
                    catch (Exception exc)
836 855
                    {
837
                        Log.ErrorFormat("[ERROR] uploading block {0} of {1}\n{2}", blockIndex, fullFileName, exc);
856
                        Log.Error(String.Format("Uploading block {0} of {1}", blockIndex, fullFileName), exc);
838 857
                    }
839

  
858
                    ReportUploadProgress(fileInfo.Name,block++, missingHashes.Count, fileInfo.Length);
840 859
                }
841 860

  
842 861
                //Repeat until there are no more missing hashes                
843 862
                missingHashes = await client.PutHashMap(account, container, url, treeHash);
844 863
            }
864
            ReportUploadProgress(fileInfo.Name, missingHashes.Count, missingHashes.Count, fileInfo.Length);
845 865
        }
846 866

  
847

  
867
        private void ReportUploadProgress(string fileName,int block, int totalBlocks, long fileSize)
868
        {
869
            StatusNotification.Notify(new ProgressNotification(fileName,"Uploading",block,totalBlocks,fileSize) );            
870
        }
871
        private void ReportDownloadProgress(string fileName,int block, int totalBlocks, long fileSize)
872
        {
873
            StatusNotification.Notify(new ProgressNotification(fileName,"Downloading",block,totalBlocks,fileSize) );            
874
        }
848 875
    }
849 876

  
850 877
   
b/trunk/Pithos.Core/Agents/PollAgent.cs
352 352
                var deletedFiles = new List<FileSystemInfo>();
353 353
                foreach (var objectInfo in cloudFiles)
354 354
                {
355
                    Log.DebugFormat("Handle deleted [{0}]",objectInfo.Uri);
355 356
                    var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
356 357
                    var item = FileAgent.GetFileAgent(accountInfo).GetFileSystemInfo(relativePath);
358
                    Log.DebugFormat("Will delete [{0}] for [{1}]", item.FullName,objectInfo.Uri);
357 359
                    if (item.Exists)
358 360
                    {
359 361
                        if ((item.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
......
361 363
                            item.Attributes = item.Attributes & ~FileAttributes.ReadOnly;
362 364

  
363 365
                        }
364
                        item.Delete();
366
                        var directory = item as DirectoryInfo;
367
                        if (directory!=null)
368
                            directory.Delete(true);
369
                        else
370
                            item.Delete();
371
                        Log.DebugFormat("Deleted [{0}] for [{1}]", item.FullName, objectInfo.Uri);
365 372
                        DateTime lastDate;
366 373
                        _lastSeen.TryRemove(item.FullName, out lastDate);
367 374
                        deletedFiles.Add(item);
368 375
                    }
369 376
                    StatusKeeper.SetFileState(item.FullName, FileStatus.Deleted, FileOverlayStatus.Deleted);
370 377
                }
378
                Log.InfoFormat("[{0}] files were deleted",deletedFiles.Count);
371 379
                StatusNotification.NotifyForFiles(deletedFiles, String.Format("{0} files were deleted", deletedFiles.Count), TraceLevel.Info);
372 380
            }
373 381

  
b/trunk/Pithos.Core/Agents/StatusAgent.cs
254 254
                {
255 255
                    //This file has a matching state. Need to check for possible changes
256 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

  
257 262
                    //If the hashes don't match the file was changed
258 263
                    if (fileState.Checksum != hashString)
259 264
                    {
b/trunk/Pithos.Core/Agents/WorkflowAgent.cs
70 70
        [System.ComponentModel.Composition.Import]
71 71
        public NetworkAgent NetworkAgent { get; set; }
72 72

  
73
        [System.ComponentModel.Composition.Import]
74
        public IPithosSettings Settings { get; set; }
73 75

  
74 76

  
75 77
        public WorkflowAgent()
......
210 212
        {
211 213
            
212 214

  
213
            using (log4net.ThreadContext.Stacks["Workflow"].Push("Restart"))
215
            using (log4net.ThreadContext.Stacks["Operation"].Push("RestartInterrupted"))
214 216
            {
215 217
                if (Log.IsDebugEnabled)
216 218
                    Log.Debug("Starting interrupted files");
......
219 221
                    .ToLower();
220 222

  
221 223

  
222

  
224
                
225
                
223 226
                var account = accountInfo;
224 227
                var pendingEntries = (from state in FileState.Queryable
225 228
                                     where state.FileStatus != FileStatus.Unchanged &&
226 229
                                           !state.FilePath.StartsWith(cachePath) &&
227 230
                                           !state.FilePath.EndsWith(".ignore") &&
228
                                           state.FilePath.StartsWith(account.AccountPath)
231
                                           state.FilePath.StartsWith(account.AccountPath)                                            
229 232
                                     select state).ToList();
230 233
                if (pendingEntries.Count>0)
231 234
                    StatusNotification.NotifyChange("Restart processing interrupted files", TraceLevel.Verbose);
b/trunk/Pithos.Core/IStatusNotification.cs
71 71
        }
72 72
    }
73 73

  
74
    public class ProgressNotification:Notification
75
    {
76
        public string FileName { get; set; }
77
        public int Block { get; set; }
78
        public int TotalBlocks { get; set; }
79
        public long FileSize { get; set; }
80
        public string Action { get; set; }
81
        public ProgressNotification(string fileName, string action,int block, int totalBlocks, long fileSize)
82
        {
83
            FileName = fileName;
84
            Block = block;
85
            TotalBlocks = totalBlocks;
86
            FileSize = fileSize;
87
            Action = action;
88
        }
89
    }
90

  
74 91
    public class Notification<T>:Notification
75 92
    {
76 93
        public T Data { get; set; }
b/trunk/Pithos.Core/PithosMonitor.cs
269 269
        private void IndexLocalFiles()
270 270
        {
271 271
            StatusNotification.NotifyChange("Indexing Local Files");
272
            using (ThreadContext.Stacks["Monitor"].Push("Indexing local files"))
272
            using (ThreadContext.Stacks["Operation"].Push("Indexing local files"))
273 273
            {
274 274
                Log.Info("START");
275 275
                try
b/trunk/Pithos.Interfaces/ObjectInfo.cs
139 139
            get
140 140
            {
141 141
                var relativeUrl=String.Format("{0}/{1}/{2}",Account, Container,Name);
142
                return new Uri(StorageUri,relativeUrl);
142
                return StorageUri==null 
143
                    ? new Uri(relativeUrl,UriKind.Relative) 
144
                    : new Uri(StorageUri, relativeUrl);
143 145
            }
144 146
        }
145 147

  
b/trunk/Pithos.Network/BlockHashAlgorithms.cs
42 42
using System.Collections.Concurrent;
43 43
using System.Diagnostics.Contracts;
44 44
using System.IO;
45
using System.Reflection;
45 46
using System.Security.Cryptography;
46 47
using System.Threading.Tasks;
47 48
using System.Threading.Tasks.Dataflow;
......
58 59
    /// </summary>
59 60
    public static class BlockHashAlgorithms
60 61
    {
62
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
63

  
61 64
        public static Func<FileStream, int, string, ConcurrentDictionary<int, byte[]>, int, Task<ConcurrentDictionary<int, byte[]>>> CalculateBlockHash;
62 65

  
63 66
        public static Task<ConcurrentDictionary<int, byte[]>> CalculateBlockHashesRecursiveAsync(FileStream stream, int blockSize, string algorithm, ConcurrentDictionary<int, byte[]> hashes = null, int index = 0)
......
143 146

  
144 147
            var hashes = new ConcurrentDictionary<int, byte[]>();
145 148

  
149
            var path = stream.Name;
150
            var size = stream.Length;
151
            Log.DebugFormat("Hashing [{0}] size [{1}]",path,size);
152
            
153
/*
146 154
            var options = new ExecutionDataflowBlockOptions {BoundedCapacity = parallelism,MaxDegreeOfParallelism=parallelism};
147 155
            var hashBlock=new ActionBlock<Tuple<int,byte[]>>(input=>
148 156
                              {
......
158 166
                                      hashes[idx] = hash;
159 167
                                  }                                  
160 168
                              },options);
169
*/
161 170

  
162 171
            var buffer = new byte[blockSize];
163 172
            int read;
164 173
            int index = 0;
165
            while ((read = await stream.ReadAsync(buffer, 0, blockSize)) > 0)
174
            using (var hasher = HashAlgorithm.Create(algorithm))
166 175
            {
167
                var block = new byte[read];
168
                Buffer.BlockCopy(buffer,0,block,0,read);
169
                await hashBlock.SendAsync(Tuple.Create(index, block));
170
                index += read;
171
            };
176
                while ((read = await stream.ReadAsync(buffer, 0, blockSize)) > 0)
177
                {
178
                    //                var block = new byte[read];
179

  
180
                    //This code was added for compatibility with the way Pithos calculates the last hash
181
                    //We calculate the hash only up to the last non-null byte
182
                    var lastByteIndex = Array.FindLastIndex(buffer, read - 1, aByte => aByte != 0);
183

  
184
                    var hash = hasher.ComputeHash(buffer, 0, lastByteIndex + 1);
185
                    Log.DebugFormat("Hashed [{0}] [{1}/{2}] [{3:p}]", path, index,size,(double)index/size);
186
                    hashes[index] = hash;
187
                    index += read;
188
                }
189

  
190
                /*
191
                                Buffer.BlockCopy(buffer,0,block,0,read);
192
                                await hashBlock.SendAsync(Tuple.Create(index, block));
193
                */
194
                
195
            }
196
            
172 197

  
198
/*
173 199
            hashBlock.Complete();
174 200
            await hashBlock.Completion;
201
*/
175 202

  
176 203
            return hashes;
177 204
        }
b/trunk/Pithos.Network/CloudFilesClient.cs
46 46

  
47 47

  
48 48
using System;
49
using System.Collections;
50 49
using System.Collections.Generic;
51 50
using System.Collections.Specialized;
52 51
using System.ComponentModel.Composition;
b/trunk/Pithos.Network/RestClient.cs
147 147

  
148 148
        //Asynchronous version
149 149
        protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
150
        {
151
            Log.InfoFormat("ASYNC [{0}] {1}",request.Method, request.RequestUri);
150
        {            
151
            Log.InfoFormat("[{0}] {1}", request.Method, request.RequestUri); 
152 152
            HttpWebResponse response = null;
153 153

  
154 154
            try
......
157 157
            }
158 158
            catch (WebException exc)
159 159
            {
160
                Log.WarnFormat("[{0}] {1} {2}", request.Method, exc.Status, request.RequestUri);      
160 161
                if (!TryGetResponse(exc, out response))
161 162
                    throw;
162 163
            }
......
174 175
        {
175 176
            HttpWebResponse response = null;
176 177
            try
177
            {                                
178
            {           
179
                Log.InfoFormat("[{0}] {1}",request.Method,request.RequestUri);     
178 180
                response = (HttpWebResponse)base.GetWebResponse(request);
179 181
            }
180 182
            catch (WebException exc)
181 183
            {
184
                Log.WarnFormat("[{0}] {1} {2}", request.Method, exc.Status,  request.RequestUri);     
182 185
                if (!TryGetResponse(exc, out response))
183 186
                    throw;
184 187
            }
b/trunk/Pithos.Network/Signature.cs
44 44
using System.Collections.Generic;
45 45
using System.Diagnostics.Contracts;
46 46
using System.IO;
47
using System.Reflection;
47 48
using System.Runtime.Remoting.Metadata.W3cXsd2001;
48 49
using System.Security.Cryptography;
49 50
using System.Threading.Tasks;
......
53 54
{
54 55
    public static class Signature
55 56
    {
57
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
58

  
56 59
        public static string CalculateMD5(FileInfo info)
57 60
        {
58 61
            if (info==null)
b/trunk/Pithos.Setup.x64/Pithos.Setup.x64.vdproj
171 171
        }
172 172
        "Entry"
173 173
        {
174
        "MsmKey" = "8:_928344C82AF3698D55890E76C45404A5"
175
        "OwnerKey" = "8:_8A9B95E5C0E945ECA7F21580A0D088D9"
176
        "MsmSig" = "8:_UNDEFINED"
177
        }
178
        "Entry"
179
        {
174 180
        "MsmKey" = "8:_A490E069DF5DE5852575B6E157EB50BE"
175 181
        "OwnerKey" = "8:_1BD6A9CD577C40098C968C8B464A03BC"
176 182
        "MsmSig" = "8:_UNDEFINED"
......
364 370
        "Entry"
365 371
        {
366 372
        "MsmKey" = "8:_UNDEFINED"
373
        "OwnerKey" = "8:_928344C82AF3698D55890E76C45404A5"
374
        "MsmSig" = "8:_UNDEFINED"
375
        }
376
        "Entry"
377
        {
378
        "MsmKey" = "8:_UNDEFINED"
367 379
        "OwnerKey" = "8:_8A9B95E5C0E945ECA7F21580A0D088D9"
368 380
        "MsmSig" = "8:_UNDEFINED"
369 381
        }
......
871 883
            "IsDependency" = "11:TRUE"
872 884
            "IsolateTo" = "8:"
873 885
            }
886
            "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_928344C82AF3698D55890E76C45404A5"
887
            {
888
            "AssemblyRegister" = "3:1"
889
            "AssemblyIsInGAC" = "11:FALSE"
890
            "AssemblyAsmDisplayName" = "8:AppLimit.NetSparkle.Net40, Version=1.0.83.0, Culture=neutral, processorArchitecture=MSIL"
891
                "ScatterAssemblies"
892
                {
893
                    "_928344C82AF3698D55890E76C45404A5"
894
                    {
895
                    "Name" = "8:AppLimit.NetSparkle.Net40.dll"
896
                    "Attributes" = "3:512"
897
                    }
898
                }
899
            "SourcePath" = "8:AppLimit.NetSparkle.Net40.dll"
900
            "TargetName" = "8:"
901
            "Tag" = "8:"
902
            "Folder" = "8:_FA3E4362540D4C76A5914763C178A3BD"
903
            "Condition" = "8:"
904
            "Transitive" = "11:FALSE"
905
            "Vital" = "11:TRUE"
906
            "ReadOnly" = "11:FALSE"
907
            "Hidden" = "11:FALSE"
908
            "System" = "11:FALSE"
909
            "Permanent" = "11:FALSE"
910
            "SharedLegacy" = "11:FALSE"
911
            "PackageAs" = "3:1"
912
            "Register" = "3:1"
913
            "Exclude" = "11:FALSE"
914
            "IsDependency" = "11:TRUE"
915
            "IsolateTo" = "8:"
916
            }
874 917
            "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_A490E069DF5DE5852575B6E157EB50BE"
875 918
            {
876 919
            "AssemblyRegister" = "3:1"
......
1333 1376
        "Name" = "8:Microsoft Visual Studio"
1334 1377
        "ProductName" = "8:Pithos"
1335 1378
        "ProductCode" = "8:{57BE17ED-F02F-43C0-B44B-845697CD3D6A}"
1336
        "PackageCode" = "8:{F55471B6-9395-4893-810B-8199D93F61CD}"
1379
        "PackageCode" = "8:{A4185E8B-DEC9-4E52-BBE4-B959A8B30DD2}"
1337 1380
        "UpgradeCode" = "8:{205365D1-28AA-4322-A46C-FCB37502C6EF}"
1338 1381
        "AspNetVersion" = "8:4.0.30319.0"
1339 1382
        "RestartWWWService" = "11:FALSE"
......
1344 1387
        "Manufacturer" = "8:GRNET"
1345 1388
        "ARPHELPTELEPHONE" = "8:"
1346 1389
        "ARPHELPLINK" = "8:http://code.grnet.gr/projects/pithos-ms-client"
1347
        "Title" = "8:Pithos"
1390
        "Title" = "8:Pithos [Version]"
1348 1391
        "Subject" = "8:"
1349 1392
        "ARPCONTACT" = "8:GRNet"
1350 1393
        "Keywords" = "8:"
b/trunk/Pithos.sln
37 37
EndProject
38 38
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Pithos.Setup.x86", "Pithos.Setup.x86\Pithos.Setup.x86.vdproj", "{0D7E50F2-D7B4-4458-AA01-2CAC0F386737}"
39 39
EndProject
40
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pithos.AppCast", "Pithos.AppCast", "{4ACC38DA-F81B-4DA5-A1AB-CEAAF3B7008A}"
41
	ProjectSection(SolutionItems) = preProject
42
		Pithos.AppCast\rnotes.css = Pithos.AppCast\rnotes.css
43
		Pithos.AppCast\versioninfo.xml = Pithos.AppCast\versioninfo.xml
44
	EndProjectSection
45
EndProject
40 46
Global
41 47
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
42 48
		Debug All|Any CPU = Debug All|Any CPU

Also available in: Unified diff