Changed ETag calculation to SHA256
authorpkanavos <pkanavos@gmail.com>
Tue, 4 Sep 2012 15:29:38 +0000 (18:29 +0300)
committerpkanavos <pkanavos@gmail.com>
Tue, 4 Sep 2012 16:00:04 +0000 (19:00 +0300)
ETag not posted on directory uploads, to avoid 422 errors
Etag posted in other cases, even if the server ignores it
Merkle block hashes are now stored in the database
Hash calculations first check the state database for existing valid hashes, to avoid recalculating the Merkle hash for unchanged files

15 files changed:
trunk/Pithos.Client.WPF/app.config
trunk/Pithos.Core.Test/MockStatusKeeper.cs
trunk/Pithos.Core/Agents/BlockExtensions.cs
trunk/Pithos.Core/Agents/CloudTransferAction.cs
trunk/Pithos.Core/Agents/Downloader.cs
trunk/Pithos.Core/Agents/FileAgent.cs
trunk/Pithos.Core/Agents/PollAgent.cs
trunk/Pithos.Core/Agents/StatusAgent.cs
trunk/Pithos.Core/Agents/Uploader.cs
trunk/Pithos.Core/FileState.cs
trunk/Pithos.Core/IStatusKeeper.cs
trunk/Pithos.Interfaces/ObjectInfo.cs
trunk/Pithos.Network/CloudFilesClient.cs
trunk/Pithos.Network/Signature.cs
trunk/Pithos.Network/TreeHash.cs

index 98a9084..20dc7fb 100644 (file)
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-       <configSections>
-               <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-                       <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" />
-               </sectionGroup>
-               <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-                       <section name="Pithos.Client.WPF.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
-               </sectionGroup>
-               <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
-       </configSections>
-       <runtime>
-               <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-                       <dependentAssembly>
-                               <assemblyIdentity name="Caliburn.Micro" publicKeyToken="8e5891231f2ed21f" culture="neutral" />
-                               <bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
-                       </dependentAssembly>
-                       <dependentAssembly>
-                               <assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
-                               <bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />
-                       </dependentAssembly>
-                       <dependentAssembly>
-                               <assemblyIdentity name="NHibernate.ByteCode.Castle" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
-                               <bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />
-                       </dependentAssembly>
-               </assemblyBinding>
-       </runtime>
-
-<system.diagnostics >
-               <sources >
-
-                       <source name="System.Net"  switchValue="Warning" tracemode="protocolonly" maxdatasize="65536" >
-                               <listeners>
-                                       <add name="ms" type="Pithos.Client.WPF.Diagnostics.Log4NetForwarder,PithosPlus" />          
-                               </listeners>        
-                       </source>
-               </sources>
-       </system.diagnostics>
-       <userSettings>
-               <Pithos.Client.WPF.Properties.Settings>
-   <setting name="PithosPath" serializeAs="String">
-    <value>e:\Pithos</value>
-   </setting>
-   <setting name="IconPath" serializeAs="String">
-    <value>C:\Program Files\Common Files\TortoiseOverlays\icons\XPStyle</value>
-   </setting>
-   <setting name="ProxyServer" serializeAs="String">
-    <value />
-   </setting>
-   <setting name="ProxyPort" serializeAs="String">
-    <value>8080</value>
-   </setting>
-   <setting name="ProxyUsername" serializeAs="String">
-    <value />
-   </setting>
-   <setting name="ProxyPassword" serializeAs="String">
-    <value />
-   </setting>
-   <setting name="ProxyAuthentication" serializeAs="String">
-    <value>True</value>
-   </setting>
-   <setting name="ExtensionsActivated" serializeAs="String">
-    <value>True</value>
-   </setting>
-   <setting name="UserName" serializeAs="String">
-    <value>grnet</value>
-   </setting>
-   <setting name="ApiKey" serializeAs="String">
-    <value>-</value>
-   </setting>
-   <setting name="ShowDesktopNotifications" serializeAs="String">
-    <value>True</value>
-   </setting>
-   <setting name="StartOnSystemStartup" serializeAs="String">
-    <value>True</value>
-   </setting>
-   <setting name="UseNoProxy" serializeAs="String">
-    <value>False</value>
-   </setting>
-   <setting name="UseDefaultProxy" serializeAs="String">
-    <value>True</value>
-   </setting>
-   <setting name="UseManualProxy" serializeAs="String">
-    <value>False</value>
-   </setting>
-   <setting name="MustUpgrade" serializeAs="String">
-    <value>True</value>
-   </setting>
-   <setting name="PollingInterval" serializeAs="String">
-    <value>10</value>
-   </setting>
-   <setting name="ProxyDomain" serializeAs="String">
-    <value />
-   </setting>
-   <setting name="HashingParallelism" serializeAs="String">
-    <value>1</value>
-   </setting>
-   <setting name="StartupDelay" serializeAs="String">
-    <value>00:01:00</value>
-   </setting>
-   <setting name="UpdateDiagnostics" serializeAs="String">
-    <value>False</value>
-   </setting>
-   <setting name="UpdateCheckInterval" serializeAs="String">
-    <value>24.00:00:00</value>
-   </setting>
-   <setting name="DebugLoggingEnabled" serializeAs="String">
-    <value>False</value>
-   </setting>
-   <setting name="IgnoreCertificateErrors" serializeAs="String">
-    <value>False</value>
-   </setting>
-  </Pithos.Client.WPF.Properties.Settings>
-       </userSettings>
-       <startup>
-               <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />
-       </startup>
-       <applicationSettings>
-               <Pithos.Client.WPF.Properties.Settings>
-   <setting name="CloudfilesAuthenticationUrl" serializeAs="String">
-    <value>https://auth.api.rackspacecloud.com</value>
-   </setting>
-   <setting name="FeedbackUri" serializeAs="String">
-    <value>https://pithos.dev.grnet.gr/im/feedback</value>
-   </setting>
-   <setting name="ProductionServer" serializeAs="String">
-    <value>https://pithos.okeanos.grnet.gr</value>
-   </setting>
-   <setting name="DevelopmentServer" serializeAs="String">
-    <value>https://pithos.dev.grnet.gr</value>
-   </setting>
-   <setting name="UpdateUrl" serializeAs="String">
-    <value>https://code.grnet.gr/projects/pithos-ms-client/repository/changes/trunk/Pithos.Installer/versioninfo.xml?rev=Polling</value>
-   </setting>
-   <setting name="FileIdleTimeout" serializeAs="String">
-    <value>00:00:10</value>
-   </setting>
-   <setting name="UpdateForceCheck" serializeAs="String">
-    <value>True</value>
-   </setting>
-  </Pithos.Client.WPF.Properties.Settings>
-       </applicationSettings>
-       <log4net>
-               <appender name="TraceAppender" type="log4net.Appender.TraceAppender">
-                       <layout type="log4net.Layout.PatternLayout">
-                               <conversionPattern value="%logger (%property{CloudFile}) [%level]- %message%newline" />
-                       </layout>
-                       <filter type="log4net.Filter.LoggerMatchFilter">
-                               <loggerToMatch value="NHibernate" />
-                               <acceptOnMatch value="false" />
-                       </filter>
-                       <filter type="log4net.Filter.LoggerMatchFilter">
-                               <loggerToMatch value="Caliburn" />
-                               <acceptOnMatch value="false" />
-                       </filter>
-               </appender>
-               <appender name="DumpFileAppender" type="log4net.Appender.RollingFileAppender">
-                       <file value="errorlog.xml" />
-                       <appendToFile value="false" />
-                       <rollingStyle value="Size" />
-                       <maxSizeRollBackups value="10" />
-                       <maximumFileSize value="100KB" />
-                       <staticLogFileName value="true" />
-                       <layout type="log4net.Layout.XMLLayout" />
-               </appender>
-               <appender name="DebugFileAppender" type="log4net.Appender.RollingFileAppender">
-                       <file value="debuglog.xml" />
-                       <appendToFile value="true" />
-                       <rollingStyle value="Size" />
-                       <maxSizeRollBackups value="10" />
-                       <maximumFileSize value="2MB" />
-                       <staticLogFileName value="true" />
-                       <layout type="log4net.Layout.XMLLayout" />
-               </appender>
-               <appender name="NetworkFileAppender" type="log4net.Appender.RollingFileAppender">
-                       <file value="network.log" />
-                       <appendToFile value="true" />
-                       <rollingStyle value="Size" />
-                       <maxSizeRollBackups value="10" />
-                       <maximumFileSize value="2MB" />
-                       <staticLogFileName value="true" />
-                       <layout type="log4net.Layout.PatternLayout">
-                               <conversionPattern value="%logger (%property{Operation}) [%level]- %message%newline" />
-                       </layout>
-               </appender>
-               <appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender">
-                       <layout type="log4net.Layout.PatternLayout">
-                               <conversionPattern value="%logger (%property{Operation}) [%level]- %message%newline" />
-                       </layout>
-               </appender>
-               <appender name="LossyFileAppender" type="log4net.Appender.BufferingForwardingAppender">
-                       <filter type="log4net.Filter.LevelRangeFilter">
-                               <levelMin value="DEBUG" />
-                               <levelMax value="FATAL" />
-                       </filter>
-                       <bufferSize value="30" />
-                       <lossy value="true" />
-                       <evaluator type="log4net.Core.LevelEvaluator">
-                               <threshold value="ERROR" />
-                       </evaluator>
-                       <appender-ref ref="DumpFileAppender" />
-               </appender>
-               <!--
-               <appender name="MemoryAppender" type="log4net.Appender.MemoryAppender" >
-                       
-                       <threshold value="All" />
-               </appender>
--->
-               <!--
-               <appender name="LossySmtpAppender" type="log4net.Appender.SmtpAppender">
-                       <to value="pkanavos@gmail.com" />
-                       <from value="pkpithos@gmail.com" />
-                       <subject value="Some subject" />
-                       <smtpHost value="smtp.gmail.com" />
-                       <authentication value="Basic" />
-                       <port value="587" />
-                       <bufferSize value="30" />
-                       <EnableSsl value="true"/>
-                       <lossy value="true" />
-                       <evaluator type="log4net.Core.LevelEvaluator">
-                               <threshold value="ERROR"/>
-                       </evaluator>
-                       <layout type="log4net.Layout.PatternLayout">
-                               <conversionPattern value="%newline%date [%thread] %-5level %logger [%property{Operation}] - %message%newline%newline%newline" />
-                       </layout>
-               </appender>
--->
-               <logger name="NHibernate" additivity="true">
-                       <level value="WARN" />
-                       <appender-ref ref="TraceAppender" />
-               </logger>
-               <logger name="Caliburn" additivity="false">
-                       <level value="ALL" />
-                       <appender-ref ref="TraceAppender" />
-               </logger>
-               <logger name="Pithos" additivity="true">
-                       <level value="ALL" />
-                       <appender-ref ref="TraceAppender" />
-               </logger>
-               <logger name="System.Net" additivity="true">
-                       <level value="ALL" />
-                       <appender-ref ref="NetworkFileAppender" />
-               </logger>
-               <root>
-                       <level value="ALL" />
-                       <appender-ref ref="DebugFileAppender" />
-                       <appender-ref ref="LossyFileAppender" />
-                       <!--
-                       <appender-ref ref="LossySmtpAppender" />
--->
-                       <appender-ref ref="TraceAppender" />
-                       <appender-ref ref="MemoryAppender" />
-                       <appender-ref ref="OutputDebugStringAppender" />
-               </root>
-       </log4net>
+<?xml version="1.0" encoding="utf-8"?>\r
+<configuration>\r
+       <configSections>\r
+               <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">\r
+                       <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" />\r
+               </sectionGroup>\r
+               <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">\r
+                       <section name="Pithos.Client.WPF.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />\r
+               </sectionGroup>\r
+               <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />\r
+       </configSections>\r
+       <runtime>\r
+               <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">\r
+                       <dependentAssembly>\r
+                               <assemblyIdentity name="Caliburn.Micro" publicKeyToken="8e5891231f2ed21f" culture="neutral" />\r
+                               <bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />\r
+                       </dependentAssembly>\r
+                       <dependentAssembly>\r
+                               <assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral" />\r
+                               <bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />\r
+                       </dependentAssembly>\r
+                       <dependentAssembly>\r
+                               <assemblyIdentity name="NHibernate.ByteCode.Castle" publicKeyToken="aa95f207798dfdb4" culture="neutral" />\r
+                               <bindingRedirect oldVersion="0.0.0.0-3.1.0.4000" newVersion="3.1.0.4000" />\r
+                       </dependentAssembly>\r
+               </assemblyBinding>\r
+       </runtime>\r
+\r
+<system.diagnostics >\r
+               <sources >\r
+\r
+                       <source name="System.Net"  switchValue="Warning" tracemode="protocolonly" maxdatasize="65536" >\r
+                               <listeners>\r
+                                       <add name="ms" type="Pithos.Client.WPF.Diagnostics.Log4NetForwarder,PithosPlus" />          \r
+                               </listeners>        \r
+                       </source>\r
+               </sources>\r
+       </system.diagnostics>\r
+       <userSettings>\r
+               <Pithos.Client.WPF.Properties.Settings>\r
+   <setting name="PithosPath" serializeAs="String">\r
+    <value>e:\Pithos</value>\r
+   </setting>\r
+   <setting name="IconPath" serializeAs="String">\r
+    <value>C:\Program Files\Common Files\TortoiseOverlays\icons\XPStyle</value>\r
+   </setting>\r
+   <setting name="ProxyServer" serializeAs="String">\r
+    <value />\r
+   </setting>\r
+   <setting name="ProxyPort" serializeAs="String">\r
+    <value>8080</value>\r
+   </setting>\r
+   <setting name="ProxyUsername" serializeAs="String">\r
+    <value />\r
+   </setting>\r
+   <setting name="ProxyPassword" serializeAs="String">\r
+    <value />\r
+   </setting>\r
+   <setting name="ProxyAuthentication" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+   <setting name="ExtensionsActivated" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+   <setting name="UserName" serializeAs="String">\r
+    <value>grnet</value>\r
+   </setting>\r
+   <setting name="ApiKey" serializeAs="String">\r
+    <value>-</value>\r
+   </setting>\r
+   <setting name="ShowDesktopNotifications" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+   <setting name="StartOnSystemStartup" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+   <setting name="UseNoProxy" serializeAs="String">\r
+    <value>False</value>\r
+   </setting>\r
+   <setting name="UseDefaultProxy" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+   <setting name="UseManualProxy" serializeAs="String">\r
+    <value>False</value>\r
+   </setting>\r
+   <setting name="MustUpgrade" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+   <setting name="PollingInterval" serializeAs="String">\r
+    <value>10</value>\r
+   </setting>\r
+   <setting name="ProxyDomain" serializeAs="String">\r
+    <value />\r
+   </setting>\r
+   <setting name="HashingParallelism" serializeAs="String">\r
+    <value>1</value>\r
+   </setting>\r
+   <setting name="StartupDelay" serializeAs="String">\r
+    <value>00:01:00</value>\r
+   </setting>\r
+   <setting name="UpdateDiagnostics" serializeAs="String">\r
+    <value>False</value>\r
+   </setting>\r
+   <setting name="UpdateCheckInterval" serializeAs="String">\r
+    <value>24.00:00:00</value>\r
+   </setting>\r
+   <setting name="DebugLoggingEnabled" serializeAs="String">\r
+    <value>False</value>\r
+   </setting>\r
+   <setting name="IgnoreCertificateErrors" serializeAs="String">\r
+    <value>False</value>\r
+   </setting>\r
+  </Pithos.Client.WPF.Properties.Settings>\r
+       </userSettings>\r
+       <startup>\r
+               <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client" />\r
+       </startup>\r
+       <applicationSettings>\r
+               <Pithos.Client.WPF.Properties.Settings>\r
+   <setting name="CloudfilesAuthenticationUrl" serializeAs="String">\r
+    <value>https://auth.api.rackspacecloud.com</value>\r
+   </setting>\r
+   <setting name="FeedbackUri" serializeAs="String">\r
+    <value>https://pithos.dev.grnet.gr/im/feedback</value>\r
+   </setting>\r
+   <setting name="ProductionServer" serializeAs="String">\r
+    <value>https://pithos.okeanos.grnet.gr</value>\r
+   </setting>\r
+   <setting name="DevelopmentServer" serializeAs="String">\r
+    <value>https://pithos.dev.grnet.gr</value>\r
+   </setting>\r
+   <setting name="UpdateUrl" serializeAs="String">\r
+    <value>https://code.grnet.gr/projects/pithos-ms-client/repository/changes/trunk/Pithos.Installer/versioninfo.xml?rev=Polling</value>\r
+   </setting>\r
+   <setting name="FileIdleTimeout" serializeAs="String">\r
+    <value>00:00:10</value>\r
+   </setting>\r
+   <setting name="UpdateForceCheck" serializeAs="String">\r
+    <value>True</value>\r
+   </setting>\r
+  </Pithos.Client.WPF.Properties.Settings>\r
+       </applicationSettings>\r
+       <log4net>\r
+               <appender name="TraceAppender" type="log4net.Appender.TraceAppender">\r
+                       <layout type="log4net.Layout.PatternLayout">\r
+                               <conversionPattern value="%logger (%property{CloudFile}) [%level]- %message%newline" />\r
+                       </layout>\r
+                       <filter type="log4net.Filter.LoggerMatchFilter">\r
+                               <loggerToMatch value="NHibernate" />\r
+                               <acceptOnMatch value="false" />\r
+                       </filter>\r
+                       <filter type="log4net.Filter.LoggerMatchFilter">\r
+                               <loggerToMatch value="Caliburn" />\r
+                               <acceptOnMatch value="false" />\r
+                       </filter>\r
+               </appender>\r
+               <appender name="DumpFileAppender" type="log4net.Appender.RollingFileAppender">\r
+                       <file value="errorlog.xml" />\r
+                       <appendToFile value="false" />\r
+                       <rollingStyle value="Size" />\r
+                       <maxSizeRollBackups value="10" />\r
+                       <maximumFileSize value="100KB" />\r
+                       <staticLogFileName value="true" />\r
+                       <layout type="log4net.Layout.XMLLayout" />\r
+               </appender>\r
+               <appender name="DebugFileAppender" type="log4net.Appender.RollingFileAppender">\r
+                       <file value="debuglog.xml" />\r
+                       <appendToFile value="true" />\r
+                       <rollingStyle value="Size" />\r
+                       <maxSizeRollBackups value="10" />\r
+                       <maximumFileSize value="2MB" />\r
+                       <staticLogFileName value="true" />\r
+                       <layout type="log4net.Layout.XMLLayout" />\r
+               </appender>\r
+               <appender name="NetworkFileAppender" type="log4net.Appender.RollingFileAppender">\r
+                       <file value="network.log" />\r
+                       <appendToFile value="true" />\r
+                       <rollingStyle value="Size" />\r
+                       <maxSizeRollBackups value="10" />\r
+                       <maximumFileSize value="2MB" />\r
+                       <staticLogFileName value="true" />\r
+                       <layout type="log4net.Layout.PatternLayout">\r
+                               <conversionPattern value="%logger (%property{Operation}) [%level]- %message%newline" />\r
+                       </layout>\r
+               </appender>\r
+               <appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender">\r
+                       <layout type="log4net.Layout.PatternLayout">\r
+                               <conversionPattern value="%logger (%property{Operation}) [%level]- %message%newline" />\r
+                       </layout>\r
+               </appender>\r
+               <appender name="LossyFileAppender" type="log4net.Appender.BufferingForwardingAppender">\r
+                       <filter type="log4net.Filter.LevelRangeFilter">\r
+                               <levelMin value="DEBUG" />\r
+                               <levelMax value="FATAL" />\r
+                       </filter>\r
+                       <bufferSize value="30" />\r
+                       <lossy value="true" />\r
+                       <evaluator type="log4net.Core.LevelEvaluator">\r
+                               <threshold value="ERROR" />\r
+                       </evaluator>\r
+                       <appender-ref ref="DumpFileAppender" />\r
+               </appender>\r
+               <!--\r
+               <appender name="MemoryAppender" type="log4net.Appender.MemoryAppender" >\r
+                       \r
+                       <threshold value="All" />\r
+               </appender>\r
+-->\r
+               <!--\r
+               <appender name="LossySmtpAppender" type="log4net.Appender.SmtpAppender">\r
+                       <to value="pkanavos@gmail.com" />\r
+                       <from value="pkpithos@gmail.com" />\r
+                       <subject value="Some subject" />\r
+                       <smtpHost value="smtp.gmail.com" />\r
+                       <authentication value="Basic" />\r
+                       <port value="587" />\r
+                       <bufferSize value="30" />\r
+                       <EnableSsl value="true"/>\r
+                       <lossy value="true" />\r
+                       <evaluator type="log4net.Core.LevelEvaluator">\r
+                               <threshold value="ERROR"/>\r
+                       </evaluator>\r
+                       <layout type="log4net.Layout.PatternLayout">\r
+                               <conversionPattern value="%newline%date [%thread] %-5level %logger [%property{Operation}] - %message%newline%newline%newline" />\r
+                       </layout>\r
+               </appender>\r
+-->\r
+               <logger name="NHibernate" additivity="true">\r
+                       <level value="WARN" />\r
+                       <appender-ref ref="TraceAppender" />\r
+               </logger>\r
+               <logger name="Caliburn" additivity="false">\r
+                       <level value="ALL" />\r
+                       <appender-ref ref="TraceAppender" />\r
+               </logger>\r
+               <logger name="Pithos" additivity="true">\r
+                       <level value="ALL" />\r
+                       <appender-ref ref="TraceAppender" />\r
+               </logger>\r
+               <logger name="System.Net" additivity="true">\r
+                       <level value="ALL" />\r
+                       <appender-ref ref="NetworkFileAppender" />\r
+               </logger>\r
+               <root>\r
+                       <level value="ALL" />\r
+                       <appender-ref ref="DebugFileAppender" />\r
+                       <appender-ref ref="LossyFileAppender" />\r
+                       <!--\r
+                       <appender-ref ref="LossySmtpAppender" />\r
+-->\r
+                       <appender-ref ref="TraceAppender" />\r
+                       <appender-ref ref="MemoryAppender" />\r
+                       <appender-ref ref="OutputDebugStringAppender" />\r
+               </root>\r
+       </log4net>\r
+\r
+  <mscorlib>\r
+    <cryptographySettings>\r
+      <cryptoNameMapping>\r
+        <nameEntry name="SHA1" class="SHA256Cng"/>\r
+        <nameEntry name="System.Security.Cryptography.SHA1"\r
+                   class="MySHA1Hash"/>\r
+        <nameEntry name="System.Security.Cryptography.HashAlgorithm"\r
+                   class="MySHA1Hash"/>\r
+      </cryptoNameMapping>\r
+    </cryptographySettings>\r
+  </mscorlib>\r
+  \r
 </configuration>
\ No newline at end of file
index 9db184d..b07e457 100644 (file)
@@ -73,6 +73,13 @@ namespace Pithos.Core.Test
             SetFileOverlayStatus(path, overlayStatus);\r
         }\r
 \r
+        public void StoreInfo(string path, ObjectInfo objectInfo,TreeHash treeHash)\r
+        {\r
+            StoreInfo(path,objectInfo);\r
+\r
+\r
+        }\r
+\r
         public void StoreInfo(string path, ObjectInfo objectInfo)\r
         {\r
             if (String.IsNullOrWhiteSpace(path))\r
@@ -167,7 +174,7 @@ namespace Pithos.Core.Test
             return _pithosStatus;\r
         }\r
 \r
-        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string etag = null)\r
+        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)\r
         {\r
             _overlayCache[path] = overlayStatus;\r
             return Task.Factory.StartNew(()=>{});\r
@@ -187,9 +194,9 @@ namespace Pithos.Core.Test
             _overlayCache.TryRemove(oldPath, out value);\r
         }\r
 \r
-        public void UpdateFileChecksum(string path, string etag, string checksum)\r
+        public void UpdateFileChecksum(string path, string etag, TreeHash treeHash)\r
         {\r
-            _checksums[path] = checksum;\r
+            _checksums[path] = treeHash.TopHash.ToHashString();\r
         }\r
 \r
         public void UpdateFileTreeHash(string path, TreeHash treeHash)\r
index 5cd73c6..7cb826c 100644 (file)
@@ -74,6 +74,7 @@ namespace Pithos.Core.Agents
             }\r
         }\r
 \r
+    \r
        public static string CalculateHash(this FileSystemInfo info,int blockSize,string algorithm,CancellationToken token,IProgress<double> progress )\r
         {\r
             if (info==null)\r
index 7b36f1c..b3d35ca 100644 (file)
-#region
-/* -----------------------------------------------------------------------
- * <copyright file="CloudTransferAction.cs" company="GRNet">
- * 
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- *   1. Redistributions of source code must retain the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer in the documentation and/or other materials
- *      provided with the distribution.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- * </copyright>
- * -----------------------------------------------------------------------
- */
-#endregion
-using System;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Threading;
-using Pithos.Interfaces;
-using Pithos.Network;
-
-namespace Pithos.Core.Agents
-{
-    public enum CloudActionType
-    {
-        MustSynch,
-        UploadUnconditional,
-        DownloadUnconditional,
-        DeleteLocal,
-        DeleteCloud,
-        RenameCloud,
-        RenameLocal
-    }
-
-    public class CloudAction
-    {
-
-        public object Originator { get; set; }
-        public AccountInfo AccountInfo { get; set; }
-        public CloudActionType Action { get; set; }
-        public FileSystemInfo LocalFile { get; set; }
-        public ObjectInfo CloudFile { get; set; }
-        public FileState FileState { get; set; }
-        public string Container { get; set; }
-
-        public readonly DateTime Created = DateTime.Now;
-
-
-        public Lazy<TreeHash> TreeHash { get; protected set; }
-        //public Lazy<string> TopHash { get; set; }
-
-
-        [ContractInvariantMethod]
-        private void Invariants()
-        {
-            Contract.Invariant(AccountInfo!=null);
-        }
-
-        public bool IsShared
-        {
-            get { return  CloudFile!=null && AccountInfo.UserName != CloudFile.Account; }
-        }
-
-        protected CloudAction(AccountInfo accountInfo,CloudActionType action,object originator)
-        {
-            if (accountInfo==null)
-                throw new ArgumentNullException("accountInfo");
-            Contract.EndContractBlock();
-
-            Action = action;
-            AccountInfo = accountInfo;
-            Originator = originator;
-        }
-
-        public CloudAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm,object originator,CancellationToken token,IProgress<double> progress )
-            : this(accountInfo,action,originator)
-        {
-            if(blockSize<=0)
-                throw new ArgumentOutOfRangeException("blockSize");
-            Contract.EndContractBlock();
-            LocalFile = localFile.WithProperCapitalization();
-            CloudFile = cloudFile;
-            FileState = state;
-            
-            if (LocalFile == null) 
-                return;
-
-            TreeHash = new Lazy<TreeHash>(() => Signature.CalculateTreeHash(LocalFile, blockSize,algorithm,token,progress),
-                                            LazyThreadSafetyMode.ExecutionAndPublication);
-        }
-
-        //Calculate the download path for the cloud file
-        public string GetDownloadPath()
-        {
-            if (CloudFile == null)
-                return String.Empty;
-            var filePath = CloudFile.RelativeUrlToFilePath(AccountInfo.UserName);
-            return Path.Combine(AccountInfo.AccountPath, filePath);
-        }
-
-        public override string ToString()
-        {
-            return String.Format("{0}:{1}->{2}", Action, LocalFile.FullName, CloudFile.Name);
-        }
-
-        public static ObjectInfo CreateObjectInfoFor(AccountInfo accountInfo, FileSystemInfo fileInfo)
-        {
-            if(accountInfo==null)
-                throw new ArgumentNullException("accountInfo");
-            if(fileInfo==null)
-                throw new ArgumentNullException("fileInfo");
-            Contract.Ensures(Contract.Result<ObjectInfo>()!=null);
-            Contract.EndContractBlock();
-
-            var capitalizedFileInfo = fileInfo.WithProperCapitalization();
-            var fullLocalName = capitalizedFileInfo.FullName;
-            var othersPath = Path.Combine(accountInfo.AccountPath, FolderConstants.OthersFolder);
-
-            ObjectInfo objectInfo;
-
-            var isShared = fullLocalName.StartsWith(othersPath, StringComparison.InvariantCultureIgnoreCase);
-            if (isShared)
-            {                
-                var pathRelativeToOther = fullLocalName.Substring(othersPath.Length + 1);
-                var otherParts = pathRelativeToOther.Split('\\');
-                var otherName = otherParts[0];
-                var otherContainer = otherParts[1];
-                objectInfo=new ObjectInfo
-                           {
-                               Account = otherName, 
-                               Container = otherContainer, 
-                               Name = String.Join("/", otherParts.Splice(2))
-                           };
-            }
-            else
-                objectInfo=new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName, fileInfo);
-            
-            objectInfo.Content_Type= (fileInfo is DirectoryInfo)
-                                    ?   "appication/directory"
-                                    :   "application/octet-stream";
-            return objectInfo;
-        }
-    }    
-
-    public class CloudDownloadAction:CloudAction
-    {
-        public CloudDownloadAction(AccountInfo accountInfo, ObjectInfo cloudFile,object originator)
-            :base(accountInfo,CloudActionType.DownloadUnconditional,originator)
-        {            
-            if (String.IsNullOrWhiteSpace(cloudFile.Container))
-                throw new ArgumentException("CloudFile.Container","cloudFile");
-            Contract.EndContractBlock();
-
-            CloudFile = cloudFile;
-        }
-
-        [ContractInvariantMethod]
-        private void Invariants()
-        {
-            Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));
-        }
-
-        public override string ToString()
-        {
-            return String.Format("{0}: _ <- {1}", Action, CloudFile.Name);
-        }
-        
-    }
-    public class CloudDeleteAction:CloudAction
-    {
-        public CloudDeleteAction(AccountInfo accountInfo,FileSystemInfo fileInfo, FileState fileState,object originator)
-            : this(accountInfo,fileInfo,CreateObjectInfoFor(accountInfo, fileInfo),fileState,originator)
-        {            
-        }
-
-        public CloudDeleteAction(AccountInfo accountInfo, FileSystemInfo fileInfo,ObjectInfo cloudFile, FileState fileState,object originator) 
-            : base(accountInfo,CloudActionType.DeleteCloud,originator)
-        {
-            CloudFile = cloudFile;
-            LocalFile = fileInfo;
-            FileState = fileState;
-        }
-
-        public CloudDeleteAction(CloudAction action)
-            : this(action.AccountInfo,action.LocalFile,action.CloudFile,action.FileState,action)
-        {}
-
-        [ContractInvariantMethod]
-        private void Invariants()
-        {
-            Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));
-        }
-
-        public override string ToString()
-        {
-            return String.Format("{0}: _ ->{1}", Action, CloudFile.Name);
-        }
-
-    }
-
-    public class CloudUploadAction:CloudAction
-    {
-
-        public bool IsCreation { get; set; }
-
-        public CloudUploadAction(AccountInfo accountInfo, FileSystemInfo fileInfo, FileState state, int blockSize, string algorithm,object originator,bool isCreation,CancellationToken token,IProgress<double> progress )
-            : base(accountInfo, CloudActionType.UploadUnconditional,fileInfo,CreateObjectInfoFor(accountInfo,fileInfo),state,blockSize,algorithm,originator,token,progress)
-        {
-            IsCreation = isCreation;
-        }
-
-        [ContractInvariantMethod]
-        private void Invariants()
-        {
-            Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));
-        }
-
-    }
-
-    public class CloudMoveAction:CloudAction
-    {
-        public ObjectInfo OldCloudFile { get; set; }
-
-        public FileSystemInfo OldLocalFile { get; set; }
-
-        public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo oldFile, FileSystemInfo newFile,object originator)
-            :base(accountInfo,action,originator)
-        {
-            LocalFile = newFile;
-            CloudFile = CreateObjectInfoFor(accountInfo, newFile);
-            
-            OldLocalFile = oldFile;
-            OldCloudFile = CreateObjectInfoFor(accountInfo, oldFile);
-
-            //This is a rename operation, a hash will not be used
-            TreeHash = new Lazy<TreeHash>(() => Network.TreeHash.Empty, LazyThreadSafetyMode.ExecutionAndPublication);
-        }
-
-        public override string ToString()
-        {
-            return String.Format("{0}:{1}->{2}", Action, OldCloudFile.Name, CloudFile.Name);
-        }
-
-    }
-
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="CloudTransferAction.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Threading;\r
+using Pithos.Interfaces;\r
+using Pithos.Network;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+    public enum CloudActionType\r
+    {\r
+        MustSynch,\r
+        UploadUnconditional,\r
+        DownloadUnconditional,\r
+        DeleteLocal,\r
+        DeleteCloud,\r
+        RenameCloud,\r
+        RenameLocal\r
+    }\r
+\r
+    public class CloudAction\r
+    {\r
+\r
+        public object Originator { get; set; }\r
+        public AccountInfo AccountInfo { get; set; }\r
+        public CloudActionType Action { get; set; }\r
+        public FileSystemInfo LocalFile { get; set; }\r
+        public ObjectInfo CloudFile { get; set; }\r
+        public FileState FileState { get; set; }\r
+        public string Container { get; set; }\r
+\r
+        public readonly DateTime Created = DateTime.Now;\r
+\r
+\r
+        public Lazy<TreeHash> TreeHash { get; protected set; }\r
+        //public Lazy<string> TopHash { get; set; }\r
+\r
+\r
+        [ContractInvariantMethod]\r
+        private void Invariants()\r
+        {\r
+            Contract.Invariant(AccountInfo!=null);\r
+        }\r
+\r
+        public bool IsShared\r
+        {\r
+            get { return  CloudFile!=null && AccountInfo.UserName != CloudFile.Account; }\r
+        }\r
+\r
+        protected CloudAction(AccountInfo accountInfo,CloudActionType action,object originator)\r
+        {\r
+            if (accountInfo==null)\r
+                throw new ArgumentNullException("accountInfo");\r
+            Contract.EndContractBlock();\r
+\r
+            Action = action;\r
+            AccountInfo = accountInfo;\r
+            Originator = originator;\r
+        }\r
+\r
+        public CloudAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo localFile, ObjectInfo cloudFile, FileState state, int blockSize, string algorithm,object originator,CancellationToken token,IProgress<double> progress )\r
+            : this(accountInfo,action,originator)\r
+        {\r
+            if(blockSize<=0)\r
+                throw new ArgumentOutOfRangeException("blockSize");\r
+            Contract.EndContractBlock();\r
+            LocalFile = localFile.WithProperCapitalization();\r
+            CloudFile = cloudFile;\r
+            FileState = state;\r
+            \r
+            if (LocalFile == null) \r
+                return;\r
+\r
+            TreeHash = new Lazy<TreeHash>(() => Signature.CalculateTreeHash(LocalFile, blockSize,algorithm,token,progress),\r
+                                            LazyThreadSafetyMode.ExecutionAndPublication);\r
+        }\r
+\r
+        //Calculate the download path for the cloud file\r
+        public string GetDownloadPath()\r
+        {\r
+            if (CloudFile == null)\r
+                return String.Empty;\r
+            var filePath = CloudFile.RelativeUrlToFilePath(AccountInfo.UserName);\r
+            return Path.Combine(AccountInfo.AccountPath, filePath);\r
+        }\r
+\r
+        public override string ToString()\r
+        {\r
+            return String.Format("{0}:{1}->{2}", Action, LocalFile.FullName, CloudFile.Name);\r
+        }\r
+\r
+        public static ObjectInfo CreateObjectInfoFor(AccountInfo accountInfo, FileSystemInfo fileInfo)\r
+        {\r
+            if(accountInfo==null)\r
+                throw new ArgumentNullException("accountInfo");\r
+            if(fileInfo==null)\r
+                throw new ArgumentNullException("fileInfo");\r
+            Contract.Ensures(Contract.Result<ObjectInfo>()!=null);\r
+            Contract.EndContractBlock();\r
+\r
+            var capitalizedFileInfo = fileInfo.WithProperCapitalization();\r
+            var fullLocalName = capitalizedFileInfo.FullName;\r
+            var othersPath = Path.Combine(accountInfo.AccountPath, FolderConstants.OthersFolder);\r
+\r
+            ObjectInfo objectInfo;\r
+\r
+            var isShared = fullLocalName.StartsWith(othersPath, StringComparison.InvariantCultureIgnoreCase);\r
+            if (isShared)\r
+            {                \r
+                var pathRelativeToOther = fullLocalName.Substring(othersPath.Length + 1);\r
+                var otherParts = pathRelativeToOther.Split('\\');\r
+                var otherName = otherParts[0];\r
+                var otherContainer = otherParts[1];\r
+                objectInfo=new ObjectInfo\r
+                           {\r
+                               Account = otherName, \r
+                               Container = otherContainer, \r
+                               Name = String.Join("/", otherParts.Splice(2))\r
+                           };\r
+            }\r
+            else\r
+                objectInfo=new ObjectInfo(accountInfo.AccountPath, accountInfo.UserName, fileInfo);\r
+            \r
+            objectInfo.Content_Type= (fileInfo is DirectoryInfo)\r
+                                    ?   "appication/directory"\r
+                                    :   "application/octet-stream";\r
+            return objectInfo;\r
+        }\r
+    }    \r
+\r
+    public class CloudDownloadAction:CloudAction\r
+    {\r
+        public CloudDownloadAction(AccountInfo accountInfo, ObjectInfo cloudFile,object originator)\r
+            :base(accountInfo,CloudActionType.DownloadUnconditional,originator)\r
+        {            \r
+            if (String.IsNullOrWhiteSpace(cloudFile.Container))\r
+                throw new ArgumentException("CloudFile.Container","cloudFile");\r
+            Contract.EndContractBlock();\r
+\r
+            CloudFile = cloudFile;\r
+        }\r
+\r
+        [ContractInvariantMethod]\r
+        private void Invariants()\r
+        {\r
+            Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));\r
+        }\r
+\r
+        public override string ToString()\r
+        {\r
+            return String.Format("{0}: _ <- {1}", Action, CloudFile.Name);\r
+        }\r
+        \r
+    }\r
+    public class CloudDeleteAction:CloudAction\r
+    {\r
+        public CloudDeleteAction(AccountInfo accountInfo,FileSystemInfo fileInfo, FileState fileState,object originator)\r
+            : this(accountInfo,fileInfo,CreateObjectInfoFor(accountInfo, fileInfo),fileState,originator)\r
+        {            \r
+        }\r
+\r
+        public CloudDeleteAction(AccountInfo accountInfo, FileSystemInfo fileInfo,ObjectInfo cloudFile, FileState fileState,object originator) \r
+            : base(accountInfo,CloudActionType.DeleteCloud,originator)\r
+        {\r
+            CloudFile = cloudFile;\r
+            LocalFile = fileInfo;\r
+            FileState = fileState;\r
+        }\r
+\r
+        public CloudDeleteAction(CloudAction action)\r
+            : this(action.AccountInfo,action.LocalFile,action.CloudFile,action.FileState,action)\r
+        {}\r
+\r
+        [ContractInvariantMethod]\r
+        private void Invariants()\r
+        {\r
+            Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));\r
+        }\r
+\r
+        public override string ToString()\r
+        {\r
+            return String.Format("{0}: _ ->{1}", Action, CloudFile.Name);\r
+        }\r
+\r
+    }\r
+\r
+    public class CloudUploadAction:CloudAction\r
+    {\r
+\r
+        public bool IsCreation { get; set; }\r
+\r
+        public CloudUploadAction(AccountInfo accountInfo, FileSystemInfo fileInfo, FileState state, int blockSize, string algorithm,object originator,bool isCreation,CancellationToken token,IProgress<double> progress,TreeHash treeHash=null )\r
+            : base(accountInfo, CloudActionType.UploadUnconditional,fileInfo,CreateObjectInfoFor(accountInfo,fileInfo),state,blockSize,algorithm,originator,token,progress)\r
+        {\r
+            IsCreation = isCreation;\r
+            if (treeHash != null)\r
+            {\r
+                TreeHash = new Lazy<TreeHash>(() => treeHash);\r
+                TreeHash.Force();\r
+            }\r
+\r
+        }\r
+\r
+        [ContractInvariantMethod]\r
+        private void Invariants()\r
+        {\r
+            Contract.Invariant(!String.IsNullOrWhiteSpace(CloudFile.Container));\r
+        }\r
+\r
+    }\r
+\r
+    public class CloudMoveAction:CloudAction\r
+    {\r
+        public ObjectInfo OldCloudFile { get; set; }\r
+\r
+        public FileSystemInfo OldLocalFile { get; set; }\r
+\r
+        public CloudMoveAction(AccountInfo accountInfo, CloudActionType action, FileSystemInfo oldFile, FileSystemInfo newFile,object originator)\r
+            :base(accountInfo,action,originator)\r
+        {\r
+            LocalFile = newFile;\r
+            CloudFile = CreateObjectInfoFor(accountInfo, newFile);\r
+            \r
+            OldLocalFile = oldFile;\r
+            OldCloudFile = CreateObjectInfoFor(accountInfo, oldFile);\r
+\r
+            //This is a rename operation, a hash will not be used\r
+            TreeHash = new Lazy<TreeHash>(() => Network.TreeHash.Empty, LazyThreadSafetyMode.ExecutionAndPublication);\r
+        }\r
+\r
+        public override string ToString()\r
+        {\r
+            return String.Format("{0}:{1}->{2}", Action, OldCloudFile.Name, CloudFile.Name);\r
+        }\r
+\r
+    }\r
+\r
 }
\ No newline at end of file
index f7a017e..ea480ff 100644 (file)
@@ -1,9 +1,7 @@
 using System;\r
-using System.Collections.Generic;\r
 using System.ComponentModel.Composition;\r
 using System.Diagnostics.Contracts;\r
 using System.IO;\r
-using System.Linq;\r
 using System.Reflection;\r
 using System.Threading;\r
 using System.Threading.Tasks;\r
@@ -37,7 +35,7 @@ namespace Pithos.Core.Agents
 \r
 \r
         //Download a file.\r
-        public async Task DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,CancellationToken cancellationToken)\r
+        public async Task<TreeHash> DownloadCloudFile(AccountInfo accountInfo, ObjectInfo cloudFile, string filePath,CancellationToken cancellationToken)\r
         {\r
             if (accountInfo == null)\r
                 throw new ArgumentNullException("accountInfo");\r
@@ -56,40 +54,43 @@ namespace Pithos.Core.Agents
                 {\r
                    // var cancellationToken=_cts.Token;//  .ThrowIfCancellationRequested();\r
 \r
+                    //The file's treehash after download completes. For directories, the treehash is always the empty hash\r
+                    var finalHash = TreeHash.Empty;\r
+\r
                     if (await WaitOrAbort(accountInfo,cloudFile, cancellationToken).ConfigureAwait(false))\r
-                        return;\r
+                        return finalHash;\r
 \r
                     var fileName = Path.GetFileName(filePath);\r
-                    var progress = new Progress<double>(d =>\r
-                        StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
 \r
+                    var info = FileInfoExtensions.FromPath(filePath).WithProperCapitalization();\r
 \r
                     TreeHash localTreeHash;\r
-                    \r
+\r
                     using (StatusNotification.GetNotifier("Hashing for Download {0}", "Hashed for Download {0}", fileName))\r
                     {\r
+                        var state = StatusKeeper.GetStateByFilePath(filePath);\r
+                        var progress = new Progress<double>(d =>\r
+                            StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
 \r
-                        localTreeHash = Signature.CalculateTreeHashAsync(filePath,\r
-                                                                            accountInfo.BlockSize,\r
-                                                                            accountInfo.BlockHash, Settings.HashingParallelism,cancellationToken,progress);\r
+                        localTreeHash = StatusAgent.CalculateTreeHash(info, accountInfo, state, Settings.HashingParallelism, cancellationToken, progress);\r
                     }\r
-\r
-                    var localPath = FileInfoExtensions.GetProperFilePathCapitalization(filePath);\r
+                    \r
+                    var localPath = info.FullName;\r
                     var relativeUrl = new Uri(cloudFile.Name, UriKind.Relative);\r
 \r
                     var url = relativeUrl.ToString();\r
                     if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))\r
-                        return;\r
+                        return finalHash;\r
 \r
                     if (!Selectives.IsSelected(accountInfo,cloudFile))\r
-                        return;\r
+                        return finalHash;\r
 \r
 \r
                     //Are we already downloading or uploading the file? \r
                     using (var gate = NetworkGate.Acquire(localPath, NetworkOperation.Downloading))\r
                     {\r
                         if (gate.Failed)\r
-                            return;\r
+                            return finalHash;\r
 \r
                         var client = new CloudFilesClient(accountInfo);\r
                         var account = cloudFile.Account;\r
@@ -119,7 +120,7 @@ namespace Pithos.Core.Agents
                         }\r
                         else\r
                         {\r
-                            var isChanged = IsObjectChanged(cloudFile, localPath);\r
+                            var isChanged = IsObjectChanged(cloudFile, localPath,localTreeHash);\r
                             if (isChanged)\r
                             {\r
                                 //Retrieve the hashmap from the server\r
@@ -140,13 +141,18 @@ namespace Pithos.Core.Agents
                                     var attributes = File.GetAttributes(localPath);\r
                                     File.SetAttributes(localPath, attributes | FileAttributes.ReadOnly);\r
                                 }\r
+\r
+                                //Once download completes, the final hash will be equal to the server hash\r
+                                finalHash = serverHash;\r
+\r
                             }\r
                         }\r
 \r
                         //Now we can store the object's metadata without worrying about ghost status entries\r
-                        StatusKeeper.StoreInfo(localPath, cloudFile);\r
+                        StatusKeeper.StoreInfo(localPath, cloudFile,finalHash);\r
 \r
                     }\r
+                    return finalHash;\r
                 }\r
            \r
         }\r
@@ -186,8 +192,7 @@ namespace Pithos.Core.Agents
             var fileName = Path.GetFileName(localPath);\r
             var progress = new Progress<double>(d =>\r
                 StatusNotification.Notify(new StatusNotification(String.Format("Hashing for Download {0} of {1}", d, fileName))));\r
-\r
-            //TODO: Should pass cancellation token here\r
+            \r
             var treeHash = localTreeHash ?? Signature.CalculateTreeHashAsync(localPath, (int)serverHash.BlockSize, serverHash.BlockHash, Settings.HashingParallelism,cancellationToken,progress);\r
 \r
             //And compare it with the server's hash\r
@@ -225,7 +230,7 @@ namespace Pithos.Core.Agents
                     if (i < upHashes.Length - 1)\r
                         end = ((i + 1) * serverHash.BlockSize);\r
 \r
-                    //TODO: Pass token here\r
+                    \r
                     //Download the missing block\r
                     byte[] block = await client.GetBlock(cloudFile.Account, cloudFile.Container, relativeUrl, start, end, cancellationToken).ConfigureAwait(false);\r
 \r
@@ -247,6 +252,7 @@ namespace Pithos.Core.Agents
                 StatusNotification.NotifyChangedFile(localPath);\r
 \r
             Log.InfoFormat("[BLOCK GET] COMPLETE {0}", localPath);\r
+            \r
         }\r
 \r
         //Download a small file with a single GET operation\r
@@ -284,8 +290,6 @@ namespace Pithos.Core.Agents
             if (!Directory.Exists(tempFolder))\r
                 Directory.CreateDirectory(tempFolder);\r
 \r
-            //TODO: Should pass the token here\r
-\r
             //Download the object to the temporary location\r
             await client.GetObject(cloudFile.Account, cloudFile.Container, relativeUrl.ToString(), tempPath, cancellationToken).ConfigureAwait(false);\r
 \r
@@ -329,7 +333,7 @@ namespace Pithos.Core.Agents
                                           : new ProgressNotification(fileName, "Downloading", block, blockPercentage, totalBlocks, fileSize));\r
         }\r
 \r
-        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath)\r
+        private bool IsObjectChanged(ObjectInfo cloudFile, string localPath,TreeHash localTreeHash)\r
         {\r
             //If the target is a directory, there are no changes to download\r
             if (Directory.Exists(localPath))\r
@@ -342,10 +346,9 @@ namespace Pithos.Core.Agents
             if (localState == null)\r
                 return true;\r
 \r
-            var info = new FileInfo(localPath);\r
-            var etag = info.ComputeShortHash(StatusNotification);\r
+            var localHash= localTreeHash.TopHash.ToHashString();\r
             //If the file is different from the stored state, we have a change\r
-            if (localState.ETag != etag)\r
+            if (localState.Checksum != localHash)\r
                 return true;\r
             //If the top hashes differ, we have a change\r
             return (localState.Checksum != cloudFile.X_Object_Hash);\r
index 7a45474..e1db414 100644 (file)
-#region
-/* -----------------------------------------------------------------------
- * <copyright file="FileAgent.cs" company="GRNet">
- * 
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- *   1. Redistributions of source code must retain the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer in the documentation and/or other materials
- *      provided with the distribution.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- * </copyright>
- * -----------------------------------------------------------------------
- */
-#endregion
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-using Pithos.Interfaces;
-using Pithos.Network;
-using log4net;
-
-namespace Pithos.Core.Agents
-{
-//    [Export]
-    public class FileAgent
-    {
-        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
-
-        /*
-                Agent<WorkflowState> _agent;
-        */
-        private FileSystemWatcher _watcher;
-        private FileSystemWatcherAdapter _adapter;
-        private FileEventIdleBatch _eventIdleBatch;
-
-        //[Import]
-        public IStatusKeeper StatusKeeper { get; set; }
-
-        public IStatusNotification StatusNotification { get; set; }
-        //[Import]
-        public IPithosWorkflow Workflow { get; set; }
-        //[Import]
-        //public WorkflowAgent WorkflowAgent { get; set; }
-
-        private AccountInfo AccountInfo { get; set; }
-
-        internal string RootPath { get;  set; }
-        
-        public TimeSpan IdleTimeout { get; set; }
-
-        public PollAgent PollAgent { get; set; }
-
-        private void ProcessBatchedEvents(FileSystemEventArgs[] fileEvents)
-        {
-            var paths = new HashSet<string>();
-
-            foreach (var evt in fileEvents)
-            {
-                paths.Add(evt.FullPath);
-                if (evt is MovedEventArgs)
-                {
-                    paths.Add((evt as MovedEventArgs).OldFullPath);
-                }
-                else if (evt is RenamedEventArgs)
-                {
-                    paths.Add((evt as RenamedEventArgs).OldFullPath);
-                }
-            }
-                  
-            PollAgent.SynchNow(paths);
-        }
-
-/*
-        private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)
-        {
-            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));
-            //Start with events that do not originate in one of the ignored folders
-            var initialEvents = from evt in fileEvents
-                              where !IgnorePaths(evt.Key)
-                              select evt;
-
-            IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;
-
-            
-            var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);
-            //When selective sync is enabled,
-            if (selectiveEnabled)
-            {
-                //Include all selected items
-                var selectedEvents = from evt in initialEvents
-                                     where Selectives.IsSelected(AccountInfo, evt.Key)
-                                     select evt;                
-                //And all folder creations in the unselected folders
-                var folderCreations = from evt in initialEvents
-                                      let folderPath=evt.Key
-                                      //The original folder may not exist due to renames. Just make sure that the path is not a file
-                                      where !File.Exists(folderPath)
-                                            //We only want unselected items
-                                            && !Selectives.IsSelected(AccountInfo, folderPath)
-                                            //Is there any creation event related to the folder?
-                                            && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)
-                                      select evt;
-                cleanEvents = selectedEvents.Union(folderCreations).ToList();
-            }
-            //If selective is disabled, only exclude the shared folders 
-            else
-            {
-                cleanEvents = (from evt in initialEvents
-                              where !evt.Key.IsSharedTo(AccountInfo)
-                              select evt).ToList();
-            }
-
-
-            foreach (var fileEvent in cleanEvents)
-            {
-                //var filePath = fileEvent.Key;
-                var changes = fileEvent.Value;
-
-                var isNotFile = !File.Exists(fileEvent.Key);
-                foreach (var change in changes)
-                {
-                    if (change.ChangeType == WatcherChangeTypes.Renamed)
-                    {
-                        var rename = (MovedEventArgs) change;
-                        _agent.Post(new WorkflowState(change)
-                                        {
-                                            AccountInfo = AccountInfo,
-                                            OldPath = rename.OldFullPath,
-                                            OldFileName = Path.GetFileName(rename.OldName),
-                                            Path = rename.FullPath,
-                                            FileName = Path.GetFileName(rename.Name),
-                                            TriggeringChange = rename.ChangeType
-                                        });
-                    }
-                    else
-                    {
-                        var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;
-                        _agent.Post(new WorkflowState(change)
-                                        {
-                                            AccountInfo = AccountInfo,
-                                            Path = change.FullPath,
-                                            FileName = Path.GetFileName(change.Name),
-                                            TriggeringChange = change.ChangeType,
-                                            IsCreation=isCreation
-                                        });
-                    }
-                }
-            }
-            StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);
-        }
-*/
-
-        public void Start(AccountInfo accountInfo,string rootPath)
-        {
-            if (accountInfo==null)
-                throw new ArgumentNullException("accountInfo");
-            if (String.IsNullOrWhiteSpace(rootPath))
-                throw new ArgumentNullException("rootPath");
-            if (!Path.IsPathRooted(rootPath))
-                throw new ArgumentException("rootPath must be an absolute path","rootPath");
-            if (IdleTimeout == null)
-                throw new InvalidOperationException("IdleTimeout must have a valid value");
-                Contract.EndContractBlock();
-
-            AccountInfo = accountInfo;
-            RootPath = rootPath;
-            
-            _eventIdleBatch = new FileEventIdleBatch(PollAgent,(int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);
-
-            _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };
-            _adapter = new FileSystemWatcherAdapter(_watcher,this);
-
-            _adapter.Changed += OnFileEvent;
-            _adapter.Created += OnFileEvent;
-            _adapter.Deleted += OnFileEvent;
-            //_adapter.Renamed += OnRenameEvent;
-            _adapter.Moved += OnMoveEvent;
-            _watcher.EnableRaisingEvents = true;
-
-/*            
-
-
-
-            _agent = Agent<WorkflowState>.Start(inbox =>
-            {
-                Action loop = null;
-                loop = () =>
-                {
-                    var message = inbox.Receive();
-                    var process=message.Then(Process,inbox.CancellationToken);                    
-                    inbox.LoopAsync(process,loop,ex=>
-                        Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));
-                };
-                loop();
-            });*/
-        }
-
-/*
-        private Task<object> Process(WorkflowState state)
-        {
-            if (state==null)
-                throw new ArgumentNullException("state");
-            Contract.EndContractBlock();
-
-            if (Ignore(state.Path))
-                return CompletedTask<object>.Default;
-
-            var networkState = NetworkGate.GetNetworkState(state.Path);
-            //Skip if the file is already being downloaded or uploaded and 
-            //the change is create or modify
-            if (networkState != NetworkOperation.None &&
-                (
-                    state.TriggeringChange == WatcherChangeTypes.Created ||
-                    state.TriggeringChange == WatcherChangeTypes.Changed
-                ))
-                return CompletedTask<object>.Default;
-
-            try
-            {
-                //StatusKeeper.EnsureFileState(state.Path);
-                
-                UpdateFileStatus(state);
-                UpdateOverlayStatus(state);
-                UpdateLastMD5(state);
-                WorkflowAgent.Post(state);
-            }
-            catch (IOException exc)
-            {
-                if (File.Exists(state.Path))
-                {
-                    Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);
-                    _agent.Post(state);
-                }
-                else
-                {
-                    Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);
-                }
-            }
-            catch (Exception exc)
-            {
-                Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",
-                               state.Path, exc);
-            }
-            return CompletedTask<object>.Default;
-        }
-
-        public bool Pause
-        {
-            get { return _watcher == null || !_watcher.EnableRaisingEvents; }
-            set
-            {
-                if (_watcher != null)
-                    _watcher.EnableRaisingEvents = !value;                
-            }
-        }
-*/
-
-        public string CachePath { get; set; }
-
-        /*private List<string> _selectivePaths = new List<string>();
-        public List<string> SelectivePaths
-        {
-            get { return _selectivePaths; }
-            set { _selectivePaths = value; }
-        }
-*/
-        public Selectives Selectives { get; set; }
-
-
-/*
-        public void Post(WorkflowState workflowState)
-        {
-            if (workflowState == null)
-                throw new ArgumentNullException("workflowState");
-            Contract.EndContractBlock();
-
-            _agent.Post(workflowState);
-        }
-
-        public void Stop()
-        {
-            if (_watcher != null)
-            {
-                _watcher.Dispose();
-            }
-            _watcher = null;
-
-            if (_agent!=null)
-                _agent.Stop();
-        }
-
-*/
-        // Enumerate all files in the Pithos directory except those in the Fragment folder
-        // and files with a .ignore extension
-        public IEnumerable<string> EnumerateFiles(string searchPattern="*")
-        {
-            var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)
-                                 where !Ignore(filePath)
-                                 orderby filePath ascending 
-                                 select filePath;
-            return monitoredFiles;
-        }
-
-        public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")
-        {
-            var rootDir = new DirectoryInfo(RootPath);
-            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
-                                 where !Ignore(file.FullName)
-                                 orderby file.FullName ascending 
-                                 select file;
-            return monitoredFiles;
-        }                
-
-        public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")
-        {
-            var rootDir = new DirectoryInfo(RootPath);
-            //Ensure folders appear first, to allow folder processing as soon as possilbe
-            var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)
-                                     where !Ignore(file.FullName)
-                                     orderby file.FullName ascending
-                                     select file).ToList();
-            var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
-                                  where !Ignore(file.FullName)
-                                  orderby file.Length ascending
-                                  select file as FileSystemInfo).ToList();
-            var monitoredFiles = folders
-                                 //Process small files first, leaving expensive large files for last
-                                 .Concat(files);
-            return monitoredFiles;
-        }                
-
-        public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")
-        {
-            var rootDir = new DirectoryInfo(RootPath);
-            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)
-                                 where !Ignore(file.FullName)
-                                 orderby file.FullName ascending 
-                                 select file.AsRelativeUrlTo(RootPath);
-            return monitoredFiles;
-        }                
-
-        public IEnumerable<string> EnumerateFilesSystemInfosAsRelativeUrls(string searchPattern="*")
-        {
-            var rootDir = new DirectoryInfo(RootPath);
-            var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)
-                                 where !Ignore(file.FullName)
-                                 orderby file.FullName ascending 
-                                 select file.AsRelativeUrlTo(RootPath);
-            return monitoredFiles;
-        }                
-
-
-        
-
-        public bool Ignore(string filePath)
-        {
-            if (IgnorePaths(filePath)) return true;
-
-
-            //If selective sync is enabled, 
-            if (IsUnselectedRootFolder(filePath))
-                    return false;
-            //Ignore if selective synchronization is defined, 
-            //And the target file is not below any of the selective paths
-            var ignore = !Selectives.IsSelected(AccountInfo, filePath);
-            return ignore;
-        }
-
-        public bool IsUnselectedRootFolder(string filePath)
-        {
-            return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events 
-                   && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers
-                   && FoundBelowRoot(filePath, RootPath, 2);
-        }
-
-        public bool IgnorePaths(string filePath)
-        {
-//Ignore all first-level directories and files (ie at the container folders level)
-            if (FoundBelowRoot(filePath, RootPath, 1))
-                return true;
-
-            //Ignore first-level items under the "others" folder (ie at the accounts folders level).
-            var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);
-            if (FoundBelowRoot(filePath, othersPath, 1))
-                return true;
-
-            //Ignore second-level (container) folders under the "others" folder (ie at the container folders level). 
-            if (FoundBelowRoot(filePath, othersPath, 2))
-                return true;
-
-
-            //Ignore anything happening in the cache path
-            if (filePath.StartsWith(CachePath))
-                return true;
-            
-            //Finally, ignore events about one of the ignored files
-            return _ignoreFiles.ContainsKey(filePath.ToLower());
-        }
-
-/*        private static bool FoundInRoot(string filePath, string rootPath)
-        {
-            //var rootDirectory = new DirectoryInfo(rootPath);
-
-            //If the paths are equal, return true
-            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
-                return true;
-
-            //If the filepath is below the root path
-            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
-            {
-                //Get the relative path
-                var relativePath = filePath.Substring(rootPath.Length + 1);
-                //If the relativePath does NOT contains a path separator, we found a match
-                return (!relativePath.Contains(@"\"));
-            }
-
-            //If the filepath is not under the root path, return false
-            return false;            
-        }*/
-
-
-        private static bool FoundBelowRoot(string filePath, string rootPath,int level)
-        {
-            //var rootDirectory = new DirectoryInfo(rootPath);
-
-            //If the paths are equal, return true
-            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))
-                return true;
-
-            //If the filepath is below the root path
-            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))
-            {
-                //Get the relative path
-                var relativePath = filePath.Substring(rootPath.Length + 1);
-                //If the relativePath does NOT contains a path separator, we found a match
-                var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1;                
-                return levels==level;
-            }
-
-            //If the filepath is not under the root path, return false
-            return false;            
-        }
-
-        /*
-        //Post a Change message for renames containing the old and new names
-        void OnRenameEvent(object sender, RenamedEventArgs e)
-        {
-            var oldFullPath = e.OldFullPath;
-            var fullPath = e.FullPath;
-            if (Ignore(oldFullPath) || Ignore(fullPath))
-                return;
-
-            _agent.Post(new WorkflowState
-            {
-                AccountInfo=AccountInfo,
-                OldPath = oldFullPath,
-                OldFileName = e.OldName,
-                Path = fullPath,
-                FileName = e.Name,
-                TriggeringChange = e.ChangeType
-            });
-        }
-        */
-
-        //Post a Change message for all events except rename
-        void OnFileEvent(object sender, FileSystemEventArgs e)
-        {
-            //Ignore events that affect the cache folder
-            var filePath = e.FullPath;
-            if (Ignore(filePath))
-                return;
-            _eventIdleBatch.Post(e);
-        }
-
-        //Post a Change message for moves containing the old and new names
-        void OnMoveEvent(object sender, MovedEventArgs e)
-        {
-            var oldFullPath = e.OldFullPath;
-            var fullPath = e.FullPath;
-            
-
-            //If the source path is one of the ignored folders, ignore
-            if (IgnorePaths(oldFullPath)) 
-                return;
-
-            //TODO: Must prevent move propagation if the source folder is blocked by selective sync
-            //Ignore takes into account Selective Sync
-            if (Ignore(fullPath))
-                return;
-            PollAgent.PostMove(e);
-            _eventIdleBatch.Post(e);
-        }
-
-
-
-        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>
-                                                                             {
-            {WatcherChangeTypes.Created,FileStatus.Created},
-            {WatcherChangeTypes.Changed,FileStatus.Modified},
-            {WatcherChangeTypes.Deleted,FileStatus.Deleted},
-            {WatcherChangeTypes.Renamed,FileStatus.Renamed}
-        };
-
-        private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();
-
-        private WorkflowState UpdateFileStatus(WorkflowState state)
-        {
-            if (state==null)
-                throw new ArgumentNullException("state");
-            if (String.IsNullOrWhiteSpace(state.Path))
-                throw new ArgumentException("The state's Path can't be empty","state");
-            Contract.EndContractBlock();
-
-            var path = state.Path;
-            var status = _statusDict[state.TriggeringChange];
-            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);
-            if (status == oldStatus)
-            {
-                state.Status = status;
-                state.Skip = true;
-                return state;
-            }
-            if (state.Status == FileStatus.Renamed)
-                Workflow.ClearFileStatus(path);
-
-            state.Status = Workflow.SetFileStatus(path, status);
-            return state;
-        }
-
-        private WorkflowState UpdateOverlayStatus(WorkflowState state)
-        {
-            if (state==null)
-                throw new ArgumentNullException("state");
-            Contract.EndContractBlock();
-
-            if (state.Skip)
-                return state;
-
-            switch (state.Status)
-            {
-                case FileStatus.Created:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ETag).Wait();
-                    break;
-                case FileStatus.Modified:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
-                    break;
-                case FileStatus.Deleted:
-                    //this.StatusAgent.RemoveFileOverlayStatus(state.Path);
-                    break;
-                case FileStatus.Renamed:
-                    this.StatusKeeper.ClearFileStatus(state.OldPath);
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();
-                    break;
-                case FileStatus.Unchanged:
-                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ETag).Wait();
-                    break;
-            }
-
-            if (state.Status == FileStatus.Deleted)
-                NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));
-            else
-                NativeMethods.RaiseChangeNotification(state.Path);
-            return state;
-        }
-
-
-        private WorkflowState UpdateFileChecksum(WorkflowState state)
-        {
-            if (state.Skip)
-                return state;
-
-            if (state.Status == FileStatus.Deleted)
-                return state;
-
-            var path = state.Path;
-            //Skip calculation for folders
-            if (Directory.Exists(path))
-                return state;
-
-            var info = new FileInfo(path);
-
-            using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))
-            {
-
-                var etag = info.ComputeShortHash(StatusNotification);
-
-                var progress = new Progress<double>(d =>
-                    StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} of {1}", d, info.Name))));
-
-                string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash,PollAgent.CancellationToken,progress);
-                StatusKeeper.UpdateFileChecksum(path, etag, merkleHash);
-
-                state.Hash = merkleHash;
-                return state;
-            }
-        }
-
-        //Does the file exist in the container's local folder?
-        public bool Exists(string relativePath)
-        {
-            if (String.IsNullOrWhiteSpace(relativePath))
-                throw new ArgumentNullException("relativePath");
-            //A RootPath must be set before calling this method
-            if (String.IsNullOrWhiteSpace(RootPath))
-                throw new InvalidOperationException("RootPath was not set");
-            Contract.EndContractBlock();
-            //Create the absolute path by combining the RootPath with the relativePath
-            var absolutePath=Path.Combine(RootPath, relativePath);
-            //Is this a valid file?
-            if (File.Exists(absolutePath))
-                return true;
-            //Or a directory?
-            if (Directory.Exists(absolutePath))
-                return true;
-            //Fail if it is neither
-            return false;
-        }
-
-        public static FileAgent GetFileAgent(AccountInfo accountInfo)
-        {
-            return GetFileAgent(accountInfo.AccountPath);
-        }
-
-        public static FileAgent GetFileAgent(string rootPath)
-        {
-            return AgentLocator<FileAgent>.Get(rootPath.ToLower());
-        }
-
-
-        public FileSystemInfo GetFileSystemInfo(string relativePath)
-        {
-            if (String.IsNullOrWhiteSpace(relativePath))
-                throw new ArgumentNullException("relativePath");
-            //A RootPath must be set before calling this method
-            if (String.IsNullOrWhiteSpace(RootPath))
-                throw new InvalidOperationException("RootPath was not set");            
-            Contract.EndContractBlock();            
-
-            var absolutePath = Path.Combine(RootPath, relativePath);
-
-            if (Directory.Exists(absolutePath))
-                return new DirectoryInfo(absolutePath).WithProperCapitalization();
-            else
-                return new FileInfo(absolutePath).WithProperCapitalization();
-            
-        }
-
-        public void Delete(string relativePath)
-        {
-            var absolutePath = Path.Combine(RootPath, relativePath).ToLower();
-            if (Log.IsDebugEnabled)
-                Log.DebugFormat("Deleting {0}", absolutePath);
-            if (File.Exists(absolutePath))
-            {    
-                try
-                {
-                    File.Delete(absolutePath);
-                }
-                //The file may have been deleted by another thread. Just ignore the relevant exception
-                catch (FileNotFoundException) { }
-            }
-            else if (Directory.Exists(absolutePath))
-            {
-                DeleteWithRetry(absolutePath, 3);
-            }
-
-            //_ignoreFiles[absolutePath] = absolutePath;                
-            StatusKeeper.ClearFileStatus(absolutePath);
-        }
-
-        private static void DeleteWithRetry(string absolutePath, int retries)
-        {
-            try
-            {
-                var dirinfo = new DirectoryInfo(absolutePath);
-                dirinfo.Attributes &= ~FileAttributes.ReadOnly;
-                foreach (var info in dirinfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
-                {
-                    info.Attributes &= ~FileAttributes.ReadOnly;
-                }
-                dirinfo.Refresh();
-                dirinfo.Delete(true);
-            }
-            //The directory may have been deleted by another thread. Just ignore the relevant exception
-            catch (DirectoryNotFoundException) { }
-            catch (IOException)
-            {
-                if (retries>0)
-                    DeleteWithRetry(absolutePath,retries-1);
-                else
-                {
-                    throw;
-                }
-            }
-        }
-    }
-}
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="FileAgent.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Reflection;\r
+using System.Threading.Tasks;\r
+using Pithos.Interfaces;\r
+using Pithos.Network;\r
+using log4net;\r
+\r
+namespace Pithos.Core.Agents\r
+{\r
+//    [Export]\r
+    public class FileAgent\r
+    {\r
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+\r
+        /*\r
+                Agent<WorkflowState> _agent;\r
+        */\r
+        private FileSystemWatcher _watcher;\r
+        private FileSystemWatcherAdapter _adapter;\r
+        private FileEventIdleBatch _eventIdleBatch;\r
+\r
+        //[Import]\r
+        public IStatusKeeper StatusKeeper { get; set; }\r
+\r
+        public IStatusNotification StatusNotification { get; set; }\r
+        //[Import]\r
+        public IPithosWorkflow Workflow { get; set; }\r
+        //[Import]\r
+        //public WorkflowAgent WorkflowAgent { get; set; }\r
+\r
+        private AccountInfo AccountInfo { get; set; }\r
+\r
+        internal string RootPath { get;  set; }\r
+        \r
+        public TimeSpan IdleTimeout { get; set; }\r
+\r
+        public PollAgent PollAgent { get; set; }\r
+\r
+        private void ProcessBatchedEvents(FileSystemEventArgs[] fileEvents)\r
+        {\r
+            var paths = new HashSet<string>();\r
+\r
+            foreach (var evt in fileEvents)\r
+            {\r
+                paths.Add(evt.FullPath);\r
+                if (evt is MovedEventArgs)\r
+                {\r
+                    paths.Add((evt as MovedEventArgs).OldFullPath);\r
+                }\r
+                else if (evt is RenamedEventArgs)\r
+                {\r
+                    paths.Add((evt as RenamedEventArgs).OldFullPath);\r
+                }\r
+            }\r
+                  \r
+            PollAgent.SynchNow(paths);\r
+        }\r
+\r
+/*\r
+        private void ProcessBatchedEvents(Dictionary<string, FileSystemEventArgs[]> fileEvents)\r
+        {\r
+            StatusNotification.SetPithosStatus(PithosStatus.LocalSyncing,String.Format("Uploading {0} files",fileEvents.Count));\r
+            //Start with events that do not originate in one of the ignored folders\r
+            var initialEvents = from evt in fileEvents\r
+                              where !IgnorePaths(evt.Key)\r
+                              select evt;\r
+\r
+            IEnumerable<KeyValuePair<string, FileSystemEventArgs[]>> cleanEvents;\r
+\r
+            \r
+            var selectiveEnabled = Selectives.IsSelectiveEnabled(AccountInfo.AccountKey);\r
+            //When selective sync is enabled,\r
+            if (selectiveEnabled)\r
+            {\r
+                //Include all selected items\r
+                var selectedEvents = from evt in initialEvents\r
+                                     where Selectives.IsSelected(AccountInfo, evt.Key)\r
+                                     select evt;                \r
+                //And all folder creations in the unselected folders\r
+                var folderCreations = from evt in initialEvents\r
+                                      let folderPath=evt.Key\r
+                                      //The original folder may not exist due to renames. Just make sure that the path is not a file\r
+                                      where !File.Exists(folderPath)\r
+                                            //We only want unselected items\r
+                                            && !Selectives.IsSelected(AccountInfo, folderPath)\r
+                                            //Is there any creation event related to the folder?\r
+                                            && evt.Value.Any(arg => arg.ChangeType == WatcherChangeTypes.Created)\r
+                                      select evt;\r
+                cleanEvents = selectedEvents.Union(folderCreations).ToList();\r
+            }\r
+            //If selective is disabled, only exclude the shared folders \r
+            else\r
+            {\r
+                cleanEvents = (from evt in initialEvents\r
+                              where !evt.Key.IsSharedTo(AccountInfo)\r
+                              select evt).ToList();\r
+            }\r
+\r
+\r
+            foreach (var fileEvent in cleanEvents)\r
+            {\r
+                //var filePath = fileEvent.Key;\r
+                var changes = fileEvent.Value;\r
+\r
+                var isNotFile = !File.Exists(fileEvent.Key);\r
+                foreach (var change in changes)\r
+                {\r
+                    if (change.ChangeType == WatcherChangeTypes.Renamed)\r
+                    {\r
+                        var rename = (MovedEventArgs) change;\r
+                        _agent.Post(new WorkflowState(change)\r
+                                        {\r
+                                            AccountInfo = AccountInfo,\r
+                                            OldPath = rename.OldFullPath,\r
+                                            OldFileName = Path.GetFileName(rename.OldName),\r
+                                            Path = rename.FullPath,\r
+                                            FileName = Path.GetFileName(rename.Name),\r
+                                            TriggeringChange = rename.ChangeType\r
+                                        });\r
+                    }\r
+                    else\r
+                    {\r
+                        var isCreation = selectiveEnabled && isNotFile && change.ChangeType == WatcherChangeTypes.Created;\r
+                        _agent.Post(new WorkflowState(change)\r
+                                        {\r
+                                            AccountInfo = AccountInfo,\r
+                                            Path = change.FullPath,\r
+                                            FileName = Path.GetFileName(change.Name),\r
+                                            TriggeringChange = change.ChangeType,\r
+                                            IsCreation=isCreation\r
+                                        });\r
+                    }\r
+                }\r
+            }\r
+            StatusNotification.SetPithosStatus(PithosStatus.LocalComplete);\r
+        }\r
+*/\r
+\r
+        public void Start(AccountInfo accountInfo,string rootPath)\r
+        {\r
+            if (accountInfo==null)\r
+                throw new ArgumentNullException("accountInfo");\r
+            if (String.IsNullOrWhiteSpace(rootPath))\r
+                throw new ArgumentNullException("rootPath");\r
+            if (!Path.IsPathRooted(rootPath))\r
+                throw new ArgumentException("rootPath must be an absolute path","rootPath");\r
+            if (IdleTimeout == null)\r
+                throw new InvalidOperationException("IdleTimeout must have a valid value");\r
+                Contract.EndContractBlock();\r
+\r
+            AccountInfo = accountInfo;\r
+            RootPath = rootPath;\r
+            \r
+            _eventIdleBatch = new FileEventIdleBatch(PollAgent,(int)IdleTimeout.TotalMilliseconds, ProcessBatchedEvents);\r
+\r
+            _watcher = new FileSystemWatcher(rootPath) { IncludeSubdirectories = true, InternalBufferSize = 8 * 4096 };\r
+            _adapter = new FileSystemWatcherAdapter(_watcher,this);\r
+\r
+            _adapter.Changed += OnFileEvent;\r
+            _adapter.Created += OnFileEvent;\r
+            _adapter.Deleted += OnFileEvent;\r
+            //_adapter.Renamed += OnRenameEvent;\r
+            _adapter.Moved += OnMoveEvent;\r
+            _watcher.EnableRaisingEvents = true;\r
+\r
+/*            \r
+\r
+\r
+\r
+            _agent = Agent<WorkflowState>.Start(inbox =>\r
+            {\r
+                Action loop = null;\r
+                loop = () =>\r
+                {\r
+                    var message = inbox.Receive();\r
+                    var process=message.Then(Process,inbox.CancellationToken);                    \r
+                    inbox.LoopAsync(process,loop,ex=>\r
+                        Log.ErrorFormat("[ERROR] File Event Processing:\r{0}", ex));\r
+                };\r
+                loop();\r
+            });*/\r
+        }\r
+\r
+/*\r
+        private Task<object> Process(WorkflowState state)\r
+        {\r
+            if (state==null)\r
+                throw new ArgumentNullException("state");\r
+            Contract.EndContractBlock();\r
+\r
+            if (Ignore(state.Path))\r
+                return CompletedTask<object>.Default;\r
+\r
+            var networkState = NetworkGate.GetNetworkState(state.Path);\r
+            //Skip if the file is already being downloaded or uploaded and \r
+            //the change is create or modify\r
+            if (networkState != NetworkOperation.None &&\r
+                (\r
+                    state.TriggeringChange == WatcherChangeTypes.Created ||\r
+                    state.TriggeringChange == WatcherChangeTypes.Changed\r
+                ))\r
+                return CompletedTask<object>.Default;\r
+\r
+            try\r
+            {\r
+                //StatusKeeper.EnsureFileState(state.Path);\r
+                \r
+                UpdateFileStatus(state);\r
+                UpdateOverlayStatus(state);\r
+                UpdateLastMD5(state);\r
+                WorkflowAgent.Post(state);\r
+            }\r
+            catch (IOException exc)\r
+            {\r
+                if (File.Exists(state.Path))\r
+                {\r
+                    Log.WarnFormat("File access error occured, retrying {0}\n{1}", state.Path, exc);\r
+                    _agent.Post(state);\r
+                }\r
+                else\r
+                {\r
+                    Log.WarnFormat("File {0} does not exist. Will be ignored\n{1}", state.Path, exc);\r
+                }\r
+            }\r
+            catch (Exception exc)\r
+            {\r
+                Log.WarnFormat("Error occured while indexing{0}. The file will be skipped\n{1}",\r
+                               state.Path, exc);\r
+            }\r
+            return CompletedTask<object>.Default;\r
+        }\r
+\r
+        public bool Pause\r
+        {\r
+            get { return _watcher == null || !_watcher.EnableRaisingEvents; }\r
+            set\r
+            {\r
+                if (_watcher != null)\r
+                    _watcher.EnableRaisingEvents = !value;                \r
+            }\r
+        }\r
+*/\r
+\r
+        public string CachePath { get; set; }\r
+\r
+        /*private List<string> _selectivePaths = new List<string>();\r
+        public List<string> SelectivePaths\r
+        {\r
+            get { return _selectivePaths; }\r
+            set { _selectivePaths = value; }\r
+        }\r
+*/\r
+        public Selectives Selectives { get; set; }\r
+\r
+\r
+/*\r
+        public void Post(WorkflowState workflowState)\r
+        {\r
+            if (workflowState == null)\r
+                throw new ArgumentNullException("workflowState");\r
+            Contract.EndContractBlock();\r
+\r
+            _agent.Post(workflowState);\r
+        }\r
+\r
+        public void Stop()\r
+        {\r
+            if (_watcher != null)\r
+            {\r
+                _watcher.Dispose();\r
+            }\r
+            _watcher = null;\r
+\r
+            if (_agent!=null)\r
+                _agent.Stop();\r
+        }\r
+\r
+*/\r
+        // Enumerate all files in the Pithos directory except those in the Fragment folder\r
+        // and files with a .ignore extension\r
+        public IEnumerable<string> EnumerateFiles(string searchPattern="*")\r
+        {\r
+            var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories)\r
+                                 where !Ignore(filePath)\r
+                                 orderby filePath ascending \r
+                                 select filePath;\r
+            return monitoredFiles;\r
+        }\r
+\r
+        public IEnumerable<FileInfo> EnumerateFileInfos(string searchPattern="*")\r
+        {\r
+            var rootDir = new DirectoryInfo(RootPath);\r
+            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)\r
+                                 where !Ignore(file.FullName)\r
+                                 orderby file.FullName ascending \r
+                                 select file;\r
+            return monitoredFiles;\r
+        }                \r
+\r
+        public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern="*")\r
+        {\r
+            var rootDir = new DirectoryInfo(RootPath);\r
+            //Ensure folders appear first, to allow folder processing as soon as possilbe\r
+            var folders = (from file in rootDir.EnumerateDirectories(searchPattern, SearchOption.AllDirectories)\r
+                                     where !Ignore(file.FullName)\r
+                                     orderby file.FullName ascending\r
+                                     select file).ToList();\r
+            var files = (from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)\r
+                                  where !Ignore(file.FullName)\r
+                                  orderby file.Length ascending\r
+                                  select file as FileSystemInfo).ToList();\r
+            var monitoredFiles = folders\r
+                                 //Process small files first, leaving expensive large files for last\r
+                                 .Concat(files);\r
+            return monitoredFiles;\r
+        }                \r
+\r
+        public IEnumerable<string> EnumerateFilesAsRelativeUrls(string searchPattern="*")\r
+        {\r
+            var rootDir = new DirectoryInfo(RootPath);\r
+            var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories)\r
+                                 where !Ignore(file.FullName)\r
+                                 orderby file.FullName ascending \r
+                                 select file.AsRelativeUrlTo(RootPath);\r
+            return monitoredFiles;\r
+        }                \r
+\r
+        public IEnumerable<string> EnumerateFilesSystemInfosAsRelativeUrls(string searchPattern="*")\r
+        {\r
+            var rootDir = new DirectoryInfo(RootPath);\r
+            var monitoredFiles = from file in rootDir.EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories)\r
+                                 where !Ignore(file.FullName)\r
+                                 orderby file.FullName ascending \r
+                                 select file.AsRelativeUrlTo(RootPath);\r
+            return monitoredFiles;\r
+        }                \r
+\r
+\r
+        \r
+\r
+        public bool Ignore(string filePath)\r
+        {\r
+            if (IgnorePaths(filePath)) return true;\r
+\r
+\r
+            //If selective sync is enabled, \r
+            if (IsUnselectedRootFolder(filePath))\r
+                    return false;\r
+            //Ignore if selective synchronization is defined, \r
+            //And the target file is not below any of the selective paths\r
+            var ignore = !Selectives.IsSelected(AccountInfo, filePath);\r
+            return ignore;\r
+        }\r
+\r
+        public bool IsUnselectedRootFolder(string filePath)\r
+        {\r
+            return Selectives.IsSelectiveEnabled(AccountInfo.AccountKey) //propagate folder events \r
+                   && Directory.Exists(filePath) //from the container root folder only. Note, in the first level below the account root path are the containers\r
+                   && FoundBelowRoot(filePath, RootPath, 2);\r
+        }\r
+\r
+        public bool IgnorePaths(string filePath)\r
+        {\r
+//Ignore all first-level directories and files (ie at the container folders level)\r
+            if (FoundBelowRoot(filePath, RootPath, 1))\r
+                return true;\r
+\r
+            //Ignore first-level items under the "others" folder (ie at the accounts folders level).\r
+            var othersPath = Path.Combine(RootPath, FolderConstants.OthersFolder);\r
+            if (FoundBelowRoot(filePath, othersPath, 1))\r
+                return true;\r
+\r
+            //Ignore second-level (container) folders under the "others" folder (ie at the container folders level). \r
+            if (FoundBelowRoot(filePath, othersPath, 2))\r
+                return true;\r
+\r
+\r
+            //Ignore anything happening in the cache path\r
+            if (filePath.StartsWith(CachePath))\r
+                return true;\r
+            \r
+            //Finally, ignore events about one of the ignored files\r
+            return _ignoreFiles.ContainsKey(filePath.ToLower());\r
+        }\r
+\r
+/*        private static bool FoundInRoot(string filePath, string rootPath)\r
+        {\r
+            //var rootDirectory = new DirectoryInfo(rootPath);\r
+\r
+            //If the paths are equal, return true\r
+            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))\r
+                return true;\r
+\r
+            //If the filepath is below the root path\r
+            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))\r
+            {\r
+                //Get the relative path\r
+                var relativePath = filePath.Substring(rootPath.Length + 1);\r
+                //If the relativePath does NOT contains a path separator, we found a match\r
+                return (!relativePath.Contains(@"\"));\r
+            }\r
+\r
+            //If the filepath is not under the root path, return false\r
+            return false;            \r
+        }*/\r
+\r
+\r
+        private static bool FoundBelowRoot(string filePath, string rootPath,int level)\r
+        {\r
+            //var rootDirectory = new DirectoryInfo(rootPath);\r
+\r
+            //If the paths are equal, return true\r
+            if (filePath.Equals(rootPath, StringComparison.InvariantCultureIgnoreCase))\r
+                return true;\r
+\r
+            //If the filepath is below the root path\r
+            if (filePath.StartsWith(rootPath,StringComparison.InvariantCulture))\r
+            {\r
+                //Get the relative path\r
+                var relativePath = filePath.Substring(rootPath.Length + 1);\r
+                //If the relativePath does NOT contains a path separator, we found a match\r
+                var levels=relativePath.ToCharArray().Count(c=>c=='\\')+1;                \r
+                return levels==level;\r
+            }\r
+\r
+            //If the filepath is not under the root path, return false\r
+            return false;            \r
+        }\r
+\r
+        /*\r
+        //Post a Change message for renames containing the old and new names\r
+        void OnRenameEvent(object sender, RenamedEventArgs e)\r
+        {\r
+            var oldFullPath = e.OldFullPath;\r
+            var fullPath = e.FullPath;\r
+            if (Ignore(oldFullPath) || Ignore(fullPath))\r
+                return;\r
+\r
+            _agent.Post(new WorkflowState\r
+            {\r
+                AccountInfo=AccountInfo,\r
+                OldPath = oldFullPath,\r
+                OldFileName = e.OldName,\r
+                Path = fullPath,\r
+                FileName = e.Name,\r
+                TriggeringChange = e.ChangeType\r
+            });\r
+        }\r
+        */\r
+\r
+        //Post a Change message for all events except rename\r
+        void OnFileEvent(object sender, FileSystemEventArgs e)\r
+        {\r
+            //Ignore events that affect the cache folder\r
+            var filePath = e.FullPath;\r
+            if (Ignore(filePath))\r
+                return;\r
+            _eventIdleBatch.Post(e);\r
+        }\r
+\r
+        //Post a Change message for moves containing the old and new names\r
+        void OnMoveEvent(object sender, MovedEventArgs e)\r
+        {\r
+            var oldFullPath = e.OldFullPath;\r
+            var fullPath = e.FullPath;\r
+            \r
+\r
+            //If the source path is one of the ignored folders, ignore\r
+            if (IgnorePaths(oldFullPath)) \r
+                return;\r
+\r
+            //TODO: Must prevent move propagation if the source folder is blocked by selective sync\r
+            //Ignore takes into account Selective Sync\r
+            if (Ignore(fullPath))\r
+                return;\r
+            PollAgent.PostMove(e);\r
+            _eventIdleBatch.Post(e);\r
+        }\r
+\r
+\r
+\r
+        private Dictionary<WatcherChangeTypes, FileStatus> _statusDict = new Dictionary<WatcherChangeTypes, FileStatus>\r
+                                                                             {\r
+            {WatcherChangeTypes.Created,FileStatus.Created},\r
+            {WatcherChangeTypes.Changed,FileStatus.Modified},\r
+            {WatcherChangeTypes.Deleted,FileStatus.Deleted},\r
+            {WatcherChangeTypes.Renamed,FileStatus.Renamed}\r
+        };\r
+\r
+        private Dictionary<string, string> _ignoreFiles=new Dictionary<string, string>();\r
+\r
+        private WorkflowState UpdateFileStatus(WorkflowState state)\r
+        {\r
+            if (state==null)\r
+                throw new ArgumentNullException("state");\r
+            if (String.IsNullOrWhiteSpace(state.Path))\r
+                throw new ArgumentException("The state's Path can't be empty","state");\r
+            Contract.EndContractBlock();\r
+\r
+            var path = state.Path;\r
+            var status = _statusDict[state.TriggeringChange];\r
+            var oldStatus = Workflow.StatusKeeper.GetFileStatus(path);\r
+            if (status == oldStatus)\r
+            {\r
+                state.Status = status;\r
+                state.Skip = true;\r
+                return state;\r
+            }\r
+            if (state.Status == FileStatus.Renamed)\r
+                Workflow.ClearFileStatus(path);\r
+\r
+            state.Status = Workflow.SetFileStatus(path, status);\r
+            return state;\r
+        }\r
+\r
+    /*    private WorkflowState UpdateOverlayStatus(WorkflowState state)\r
+        {\r
+            if (state==null)\r
+                throw new ArgumentNullException("state");\r
+            Contract.EndContractBlock();\r
+\r
+            if (state.Skip)\r
+                return state;\r
+\r
+            switch (state.Status)\r
+            {\r
+                case FileStatus.Created:\r
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified,state.ETag).Wait();\r
+                    break;\r
+                case FileStatus.Modified:\r
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();\r
+                    break;\r
+                case FileStatus.Deleted:\r
+                    //this.StatusAgent.RemoveFileOverlayStatus(state.Path);\r
+                    break;\r
+                case FileStatus.Renamed:\r
+                    this.StatusKeeper.ClearFileStatus(state.OldPath);\r
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Modified, state.ETag).Wait();\r
+                    break;\r
+                case FileStatus.Unchanged:\r
+                    this.StatusKeeper.SetFileOverlayStatus(state.Path, FileOverlayStatus.Normal, state.ETag).Wait();\r
+                    break;\r
+            }\r
+\r
+            if (state.Status == FileStatus.Deleted)\r
+                NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path));\r
+            else\r
+                NativeMethods.RaiseChangeNotification(state.Path);\r
+            return state;\r
+        }\r
+\r
+\r
+        private WorkflowState UpdateFileChecksum(WorkflowState state)\r
+        {\r
+            if (state.Skip)\r
+                return state;\r
+\r
+            if (state.Status == FileStatus.Deleted)\r
+                return state;\r
+\r
+            var path = state.Path;\r
+            //Skip calculation for folders\r
+            if (Directory.Exists(path))\r
+                return state;\r
+\r
+            var info = new FileInfo(path);\r
+\r
+            using (StatusNotification.GetNotifier("Hashing {0}", "Finished Hashing {0}", info.Name))\r
+            {\r
+\r
+                var etag = info.ComputeShortHash(StatusNotification);\r
+\r
+                var progress = new Progress<double>(d =>\r
+                    StatusNotification.Notify(new StatusNotification(String.Format("Hashing {0} of {1}", d, info.Name))));\r
+\r
+                string merkleHash = info.CalculateHash(StatusKeeper.BlockSize, StatusKeeper.BlockHash,PollAgent.CancellationToken,progress);\r
+                StatusKeeper.UpdateFileChecksum(path, etag, merkleHash);\r
+\r
+                state.Hash = merkleHash;\r
+                return state;\r
+            }\r
+        }*/\r
+\r
+        //Does the file exist in the container's local folder?\r
+        public bool Exists(string relativePath)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(relativePath))\r
+                throw new ArgumentNullException("relativePath");\r
+            //A RootPath must be set before calling this method\r
+            if (String.IsNullOrWhiteSpace(RootPath))\r
+                throw new InvalidOperationException("RootPath was not set");\r
+            Contract.EndContractBlock();\r
+            //Create the absolute path by combining the RootPath with the relativePath\r
+            var absolutePath=Path.Combine(RootPath, relativePath);\r
+            //Is this a valid file?\r
+            if (File.Exists(absolutePath))\r
+                return true;\r
+            //Or a directory?\r
+            if (Directory.Exists(absolutePath))\r
+                return true;\r
+            //Fail if it is neither\r
+            return false;\r
+        }\r
+\r
+        public static FileAgent GetFileAgent(AccountInfo accountInfo)\r
+        {\r
+            return GetFileAgent(accountInfo.AccountPath);\r
+        }\r
+\r
+        public static FileAgent GetFileAgent(string rootPath)\r
+        {\r
+            return AgentLocator<FileAgent>.Get(rootPath.ToLower());\r
+        }\r
+\r
+\r
+        public FileSystemInfo GetFileSystemInfo(string relativePath)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(relativePath))\r
+                throw new ArgumentNullException("relativePath");\r
+            //A RootPath must be set before calling this method\r
+            if (String.IsNullOrWhiteSpace(RootPath))\r
+                throw new InvalidOperationException("RootPath was not set");            \r
+            Contract.EndContractBlock();            \r
+\r
+            var absolutePath = Path.Combine(RootPath, relativePath);\r
+\r
+            if (Directory.Exists(absolutePath))\r
+                return new DirectoryInfo(absolutePath).WithProperCapitalization();\r
+            else\r
+                return new FileInfo(absolutePath).WithProperCapitalization();\r
+            \r
+        }\r
+\r
+        public void Delete(string relativePath)\r
+        {\r
+            var absolutePath = Path.Combine(RootPath, relativePath).ToLower();\r
+            if (Log.IsDebugEnabled)\r
+                Log.DebugFormat("Deleting {0}", absolutePath);\r
+            if (File.Exists(absolutePath))\r
+            {    \r
+                try\r
+                {\r
+                    File.Delete(absolutePath);\r
+                }\r
+                //The file may have been deleted by another thread. Just ignore the relevant exception\r
+                catch (FileNotFoundException) { }\r
+            }\r
+            else if (Directory.Exists(absolutePath))\r
+            {\r
+                DeleteWithRetry(absolutePath, 3);\r
+            }\r
+\r
+            //_ignoreFiles[absolutePath] = absolutePath;                \r
+            StatusKeeper.ClearFileStatus(absolutePath);\r
+        }\r
+\r
+        private static void DeleteWithRetry(string absolutePath, int retries)\r
+        {\r
+            try\r
+            {\r
+                var dirinfo = new DirectoryInfo(absolutePath);\r
+                dirinfo.Attributes &= ~FileAttributes.ReadOnly;\r
+                foreach (var info in dirinfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))\r
+                {\r
+                    info.Attributes &= ~FileAttributes.ReadOnly;\r
+                }\r
+                dirinfo.Refresh();\r
+                dirinfo.Delete(true);\r
+            }\r
+            //The directory may have been deleted by another thread. Just ignore the relevant exception\r
+            catch (DirectoryNotFoundException) { }\r
+            catch (IOException)\r
+            {\r
+                if (retries>0)\r
+                    DeleteWithRetry(absolutePath,retries-1);\r
+                else\r
+                {\r
+                    throw;\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
index 1e39baf..9b9c204 100644 (file)
@@ -614,7 +614,8 @@ namespace Pithos.Core.Agents
                             {\r
                                 //Detect server moves\r
                                 var targetPath = MoveForServerMove(accountInfo, tuple);\r
-                                StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
+                                Debug.Assert(tuple.Merkle!=null);\r
+                                StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo,tuple.Merkle);\r
                                 \r
                                 AddOwnFolderToSelectives(accountInfo, tuple, targetPath);\r
                             }\r
@@ -675,15 +676,15 @@ namespace Pithos.Core.Agents
             StatusKeeper.SetFileState(targetPath, FileStatus.Modified, FileOverlayStatus.Modified,\r
                                       "");\r
 \r
-            await\r
+            var finalHash=await\r
                 NetworkAgent.Downloader.DownloadCloudFile(accountInfo, tuple.ObjectInfo, targetPath,\r
                                                           token)\r
                     .ConfigureAwait(false);\r
             //updateRecord( L = S )\r
             StatusKeeper.UpdateFileChecksum(targetPath, tuple.ObjectInfo.ETag,\r
-                                            tuple.ObjectInfo.X_Object_Hash);\r
+                                            finalHash);\r
 \r
-            StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo);\r
+            StatusKeeper.StoreInfo(targetPath, tuple.ObjectInfo,finalHash);\r
         }\r
 \r
         private async Task UploadLocalFile(AccountInfo accountInfo, StateTuple tuple, CancellationToken token,\r
@@ -691,7 +692,8 @@ namespace Pithos.Core.Agents
         {\r
             var action = new CloudUploadAction(accountInfo, localInfo, tuple.FileState,\r
                                                accountInfo.BlockSize, accountInfo.BlockHash,\r
-                                               "Poll", isUnselectedRootFolder, token, progress);\r
+                                               "Poll", isUnselectedRootFolder, token, progress,tuple.Merkle);            \r
+            \r
             using (StatusNotification.GetNotifier("Uploading {0}", "Uploaded {0}",\r
                                                   localInfo.Name))\r
             {\r
@@ -814,7 +816,8 @@ namespace Pithos.Core.Agents
                     di.MoveTo(serverPath);\r
                 }\r
             }\r
-            StatusKeeper.StoreInfo(serverPath, tuple.ObjectInfo);\r
+\r
+            StatusKeeper.StoreInfo(serverPath, tuple.ObjectInfo,null);\r
 \r
             return serverPath;\r
         }\r
@@ -836,9 +839,14 @@ namespace Pithos.Core.Agents
 \r
             var dirInfo = tuple.FileInfo as DirectoryInfo;\r
             var folderTuples = from folder in dirInfo.EnumerateDirectories("*", SearchOption.AllDirectories)\r
-                               select new StateTuple(folder){C=Signature.MD5_EMPTY};\r
+                               select new StateTuple(folder){C=Signature.MERKLE_EMPTY};\r
+            \r
             var fileTuples = from file in dirInfo.EnumerateFiles("*", SearchOption.AllDirectories)\r
-                             select new StateTuple(file){C=file.ComputeShortHash(this.StatusNotification)};\r
+                             let state=StatusKeeper.GetStateByFilePath(file.FullName)\r
+                             select new StateTuple(file){\r
+                                            Merkle=StatusAgent.CalculateTreeHash(file,accountInfo,state,\r
+                                            Settings.HashingParallelism,token,null)\r
+                                        };\r
             \r
             //Process folders first, to ensure folders appear on the sever as soon as possible\r
             folderTuples.ApplyAction(t => SyncSingleItem(accountInfo, t, agent, moves, processedPaths,token).Wait());\r
@@ -1019,23 +1027,23 @@ namespace Pithos.Core.Agents
                 var storedDate = state.NullSafe(s => s.LastWriteDate) ?? DateTime.MinValue;\r
                 var storedLength = state.NullSafe(s => s.LastLength);\r
 \r
-                var md5Hash = Signature.MD5_EMPTY;\r
-                var topHash = storedHash ?? Signature.MERKLE_EMPTY;\r
-                \r
+                var md5Hash = Signature.MD5_EMPTY;                \r
                 var merkle=TreeHash.Empty;\r
+\r
                 if (hashTuple.FileInfo is FileInfo)\r
                 {\r
-                    var file = hashTuple.FileInfo.WithProperCapitalization() as FileInfo;\r
+                    var file = (FileInfo)hashTuple.FileInfo.WithProperCapitalization();\r
+                    \r
                     //Attributes unchanged?\r
-\r
+                    //LastWriteTime is only accurate to the second\r
                     var unchangedAttributes = file.LastWriteTime - storedDate < TimeSpan.FromSeconds(1) \r
                         && storedLength == file.Length;\r
                     \r
-                    //Attributes appear unchanged but the file length doesn't match the stored hash\r
+                    //Attributes appear unchanged but the file length doesn't match the stored hash ?\r
                     var nonEmptyMismatch = unchangedAttributes && \r
                         (file.Length == 0 ^ storedHash== Signature.MERKLE_EMPTY);\r
 \r
-                    //Missing hashes for NON-EMPTY hash\r
+                    //Missing hashes for NON-EMPTY hash ?\r
                     var missingHashes = storedHash != Signature.MERKLE_EMPTY &&\r
                         String.IsNullOrWhiteSpace(storedHashes);\r
 \r
index 90d950d..883ce70 100644 (file)
@@ -319,7 +319,7 @@ namespace Pithos.Core.Agents
             Contract.EndContractBlock();\r
 \r
             //Find new or matching files with a left join to the stored states\r
-            var fileStates = FileState.Queryable.ToList();\r
+            var fileStates = ActiveRecordLinqBase<FileState>.Queryable.ToList();\r
             var currentFiles = from file in existingFiles\r
                                join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into\r
                                    gs\r
@@ -352,7 +352,7 @@ namespace Pithos.Core.Agents
 \r
         int i = 1;\r
 \r
-        private void ProcessFile(int total, Tuple<FileInfo,FileState> pair)\r
+        private void ProcessFile(int total, Tuple<FileInfo, FileState> pair)\r
         {\r
             var idx = Interlocked.Increment(ref i);\r
             using (StatusNotification.GetNotifier("Indexing file {0} of {1}", "Indexed file {0} of {1} ", idx, total))\r
@@ -399,7 +399,7 @@ namespace Pithos.Core.Agents
 \r
         private int UpdateStatusDirect(Guid id, FileStatus status)\r
         {\r
-            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
+            using (ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
             {\r
 \r
                 try\r
@@ -430,7 +430,7 @@ namespace Pithos.Core.Agents
         \r
         private int UpdateStatusDirect(string path, FileStatus status)\r
         {\r
-            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
+            using (ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
             {\r
 \r
                 try\r
@@ -470,7 +470,7 @@ namespace Pithos.Core.Agents
 \r
         private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason)\r
         {\r
-            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
+            using (ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))\r
             {\r
 \r
                 try\r
@@ -495,11 +495,11 @@ namespace Pithos.Core.Agents
 \r
                         if (affected == 0)\r
                         {\r
+                            //Can happen when downloading a new file\r
                             var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(absolutePath), StatusNotification);\r
                             createdState.FileStatus = fileStatus;\r
-                            createdState.OverlayStatus = overlayStatus;\r
+                            createdState.OverlayStatus = overlayStatus;                            \r
                             createdState.ConflictReason = conflictReason;\r
-                            createdState.LastMD5 = String.Empty;\r
                             session.Save(createdState);\r
                             //createdState.Create();\r
                         }\r
@@ -526,7 +526,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("oldPath");\r
             if (!Path.IsPathRooted(oldPath))\r
                 throw new ArgumentException("oldPath must be an absolute path", "oldPath");\r
-            if (string.IsNullOrWhiteSpace(newPath))\r
+            if (String.IsNullOrWhiteSpace(newPath))\r
                 throw new ArgumentNullException("newPath");\r
             if (!Path.IsPathRooted(newPath))\r
                 throw new ArgumentException("newPath must be an absolute path", "newPath");\r
@@ -657,7 +657,7 @@ namespace Pithos.Core.Agents
             _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus));\r
         }*/\r
 \r
-        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string etag = null)\r
+        public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)\r
         {\r
             if (String.IsNullOrWhiteSpace(path))\r
                 throw new ArgumentNullException("path");\r
@@ -665,7 +665,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The path must be rooted","path");\r
             Contract.EndContractBlock();\r
 \r
-            return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,etag));\r
+            return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus));\r
         }\r
 \r
        /* public void RenameFileOverlayStatus(string oldPath, string newPath)\r
@@ -697,7 +697,23 @@ namespace Pithos.Core.Agents
             _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus, conflictReason));\r
         }\r
 \r
-        \r
+\r
+        public void StoreInfo(string path, ObjectInfo objectInfo, TreeHash treeHash)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(path))\r
+                throw new ArgumentNullException("path");\r
+            if (treeHash==null)\r
+                throw new ArgumentNullException("treeHash");\r
+            if (!Path.IsPathRooted(path))\r
+                throw new ArgumentException("The path must be rooted", "path");\r
+            if (objectInfo == null)\r
+                throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");\r
+            Contract.EndContractBlock();\r
+\r
+            _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo, treeHash));\r
+\r
+        }\r
+\r
         public void StoreInfo(string path, ObjectInfo objectInfo)\r
         {\r
             if (String.IsNullOrWhiteSpace(path))\r
@@ -708,18 +724,18 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");\r
             Contract.EndContractBlock();\r
 \r
-            _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));\r
+            _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo, null));\r
 \r
         }\r
 \r
-        private void StoreInfoDirect(string path, ObjectInfo objectInfo)\r
+        private void StoreInfoDirect(string path, ObjectInfo objectInfo,TreeHash treeHash)\r
         {\r
             try\r
             {\r
                     using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState)))\r
                     using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted))\r
                     {\r
-\r
+                        \r
                         //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL");\r
                         var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL");\r
                         walquery.List();\r
@@ -730,39 +746,63 @@ namespace Pithos.Core.Agents
                            .SetString("path",path);                        \r
                         deletecmd.ExecuteUpdate();\r
 \r
-\r
-                        Func<IQuery, IQuery> setCriteria = q => q\r
-                                                    .SetString("path", path)\r
-                                                    .SetString("checksum",objectInfo.X_Object_Hash)\r
-                                                    .SetString("etag", objectInfo.ETag)\r
-                                                    .SetInt64("version", objectInfo.Version.GetValueOrDefault())\r
-                                                    .SetDateTime("versionTimeStamp",objectInfo.VersionTimestamp.GetValueOrDefault())\r
-                                                    .SetEnum("fileStatus", FileStatus.Unchanged)\r
-                                                    .SetEnum("overlayStatus",FileOverlayStatus.Normal)\r
-                                                    .SetString("objectID", objectInfo.UUID);\r
-                        //IQuery updatecmd = session.CreateSQLQuery(\r
-                        IQuery updatecmd = session.CreateQuery(\r
-                            "update FileState set FilePath=:path,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where ObjectID = :objectID  ");\r
-                        updatecmd = setCriteria(updatecmd);                           \r
-                        var affected = updatecmd.ExecuteUpdate();                        \r
+                        string md5=treeHash.NullSafe(t=>t.MD5);                        \r
+                        string hashes=treeHash.NullSafe(t=>t.ToJson());\r
+\r
+                        var info = FileInfoExtensions.FromPath(path);\r
+                        var lastWriteTime = info.LastWriteTime;\r
+                        var isFolder = (info is DirectoryInfo);\r
+                        var lastLength=isFolder ? 0:((FileInfo) info).Length;\r
+\r
+                        Func<IQuery, IQuery> setCriteria = q => {\r
+                                var q1=q.SetString("path", path)\r
+                                    .SetBoolean("isFolder",isFolder)\r
+                                    .SetDateTime("lastWrite",lastWriteTime)\r
+                                    .SetInt64("lastLength",lastLength)\r
+                                    .SetString("checksum", objectInfo.X_Object_Hash)\r
+                                    .SetString("etag", objectInfo.ETag)\r
+                                    .SetInt64("version",objectInfo.Version.GetValueOrDefault())\r
+                                    .SetDateTime("versionTimeStamp",objectInfo.VersionTimestamp.GetValueOrDefault())\r
+                                    .SetEnum("fileStatus", FileStatus.Unchanged)\r
+                                    .SetEnum("overlayStatus",FileOverlayStatus.Normal)\r
+                                    .SetString("objectID", objectInfo.UUID);\r
+                                if (treeHash!=null)\r
+                                {\r
+                                    q1=q1.SetString("hashes", hashes)\r
+                                        .SetString("md5", md5);     \r
+                                }\r
+                                return q1;\r
+                        };\r
+\r
+                    var updateStatement=(treeHash!=null)\r
+                        ? "update FileState set FilePath=:path,IsFolder=:isFolder,LastWriteDate=:lastWrite,LastLength=:lastLength,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, Checksum=:checksum,Hashes=:hashes, ETag=:etag,LastMD5=:md5,Version=:version,VersionTimeStamp=:versionTimeStamp "\r
+                        : "update FileState set FilePath=:path,IsFolder=:isFolder,LastWriteDate=:lastWrite,LastLength=:lastLength,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, Checksum=:checksum, ETag=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp ";\r
+\r
+                    IQuery updatecmd = session.CreateQuery(updateStatement + " where ObjectID = :objectID  ");\r
+\r
+\r
+                    updatecmd = setCriteria(updatecmd);\r
+                    //If the ID exists, update the status                                          \r
+                    var affected = updatecmd.ExecuteUpdate();                        \r
                         \r
-                    //If the ID exists, update the status\r
+                    //If the ID doesn't exist, try to update using the path, and store the ID as well.\r
                     if (affected == 0)\r
                     {\r
-                        //If the ID doesn't exist, try to update using the path, and store the ID as well.\r
-                        //updatecmd = session.CreateSQLQuery(\r
-                        updatecmd = session.CreateQuery(\r
-                        //    "update FileState set FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path  COLLATE NOCASE ");\r
-                            "update FileState set FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path");\r
+                        updateStatement=(treeHash!=null)\r
+                            ? "update FileState set IsFolder=:isFolder,LastWriteDate=:lastWrite,LastLength=:lastLength,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum,Hashes=:hashes, ETag=:etag,LastMD5=:md5,Version=:version,VersionTimeStamp=:versionTimeStamp "\r
+                            : "update FileState set IsFolder=:isFolder,LastWriteDate=:lastWrite,LastLength=:lastLength,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp ";\r
+\r
+                        updatecmd = session.CreateQuery(updateStatement + " where FilePath = :path");\r
                         updatecmd=setCriteria(updatecmd);\r
                         affected = updatecmd.ExecuteUpdate();\r
                     }\r
+                    //If the record can't be located, create a new one\r
                     if (affected==0)\r
-                    {\r
-                        //IQuery insertCmd=session.CreateSQLQuery(\r
-                        IQuery insertCmd = session.CreateSQLQuery(\r
-                            "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ETag,LastMD5,FileStatus,OverlayStatus,ObjectID) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:etag,:etag,:fileStatus,:overlayStatus,:objectID)");\r
-                        insertCmd=setCriteria(insertCmd).SetGuid("id", Guid.NewGuid());\r
+                    {                        \r
+                        IQuery insertCmd = session.CreateSQLQuery("INSERT INTO FileState (Id,FilePath,IsFolder,LastWriteDate,LastLength,Checksum,Hashes,Version,VersionTimeStamp,ETag,LastMD5,FileStatus,OverlayStatus,ObjectID) " + \r
+                            "VALUES (:id,:path,:isFolder,:lastWrite,:lastLength,:checksum,:hashes,:version,:versionTimeStamp,:etag,:md5,:fileStatus,:overlayStatus,:objectID)");\r
+                        insertCmd=setCriteria(insertCmd)\r
+                            .SetGuid("id", Guid.NewGuid());\r
                         affected = insertCmd.ExecuteUpdate();\r
                     }\r
                     tx.Commit();\r
@@ -870,7 +910,7 @@ namespace Pithos.Core.Agents
                 throw new ArgumentNullException("fileState");\r
             Contract.EndContractBlock();\r
 \r
-            var children = from state in FileState.Queryable\r
+            var children = from state in ActiveRecordLinqBase<FileState>.Queryable\r
                            where state.FilePath.StartsWith(fileState.FilePath + "\\")\r
                            select state;\r
             return children;\r
@@ -893,7 +933,7 @@ namespace Pithos.Core.Agents
 \r
         private int DeleteDirect(string filePath)\r
         {\r
-            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))\r
+            using (ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))\r
             {\r
 \r
                 try\r
@@ -921,7 +961,7 @@ namespace Pithos.Core.Agents
 \r
         private int DeleteFolderDirect(string filePath)\r
         {\r
-            using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))\r
+            using (ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))\r
             {\r
 \r
                 try\r
@@ -962,7 +1002,7 @@ namespace Pithos.Core.Agents
             _persistenceAgent.Post(() => FileState.UpdateFileTreeHash(path, treeHash));\r
         }\r
 \r
-        public void UpdateFileChecksum(string path, string etag, string checksum)\r
+        public void UpdateFileChecksum(string path, string etag, TreeHash treeHash)\r
         {\r
             if (String.IsNullOrWhiteSpace(path))\r
                 throw new ArgumentNullException("path");\r
@@ -970,10 +1010,10 @@ namespace Pithos.Core.Agents
                 throw new ArgumentException("The path must be rooted", "path");            \r
             Contract.EndContractBlock();\r
 \r
-            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, etag,checksum));\r
+            _persistenceAgent.Post(() => FileState.UpdateChecksum(path, etag,treeHash));\r
         }\r
 \r
-        public void UpdateLastMD5(FileInfo file, string etag)\r
+      /*  public void UpdateLastMD5(FileInfo file, string etag)\r
         {\r
             if (file==null)\r
                 throw new ArgumentNullException("file");\r
@@ -982,7 +1022,7 @@ namespace Pithos.Core.Agents
             Contract.EndContractBlock();\r
 \r
             _persistenceAgent.Post(() => FileState.UpdateLastMD5(file, etag));\r
-        }\r
+        }*/\r
 \r
 \r
         public void CleanupOrphanStates()\r
@@ -993,7 +1033,7 @@ namespace Pithos.Core.Agents
             var roots=(from account in Settings.Accounts\r
                       select account.RootPath).ToList();\r
             \r
-            var allStates = from state in FileState.Queryable\r
+            var allStates = from state in ActiveRecordLinqBase<FileState>.Queryable\r
                 select state.FilePath;\r
 \r
             foreach (var statePath in allStates)\r
@@ -1029,7 +1069,7 @@ namespace Pithos.Core.Agents
                               select Path.Combine(accountInfo.AccountPath,info.RelativeUrlToFilePath(accountInfo.UserName));\r
             var serverSet = new HashSet<string>(serverFiles);\r
 \r
-            var allStates = from state in FileState.Queryable\r
+            var allStates = from state in ActiveRecordLinqBase<FileState>.Queryable\r
                             where state.FilePath.StartsWith(agent.RootPath)\r
                             select state.FilePath;\r
             var stateSet = new HashSet<string>(allStates);\r
@@ -1043,6 +1083,26 @@ namespace Pithos.Core.Agents
 \r
             \r
         }\r
+\r
+        public static TreeHash CalculateTreeHash(FileSystemInfo fileInfo, AccountInfo accountInfo, FileState fileState, byte hashingParallelism, CancellationToken cancellationToken, Progress<double> progress)\r
+        {\r
+            //FileState may be null if there is no stored state for this file\r
+            if (fileState==null)\r
+                return Signature.CalculateTreeHashAsync(fileInfo,\r
+                                                 accountInfo.BlockSize,\r
+                                                 accountInfo.BlockHash,\r
+                                                 hashingParallelism,\r
+                                                 cancellationToken, progress);\r
+            //Can we use the stored hashes?\r
+            var localTreeHash = fileState.LastMD5 == Signature.CalculateMD5(fileInfo)\r
+                                    ? TreeHash.Parse(fileState.Hashes)\r
+                                    : Signature.CalculateTreeHashAsync(fileInfo,\r
+                                                                       accountInfo.BlockSize,\r
+                                                                       accountInfo.BlockHash,\r
+                                                                       hashingParallelism,\r
+                                                                       cancellationToken, progress);\r
+            return localTreeHash;\r
+        }\r
     }\r
 \r
    \r
index b62ffb1..c27a731 100644 (file)
@@ -50,26 +50,33 @@ namespace Pithos.Core.Agents
 \r
                     var fileInfo = action.LocalFile;\r
 \r
-                    var progress=new Progress<double>(d=>\r
-                        StatusNotification.Notify(new StatusNotification(String.Format("Merkle Hashing for Upload {0:p} of {1}",d,fileInfo.Name))));\r
-\r
-                    TreeHash localTreeHash;\r
-                    using (StatusNotification.GetNotifier("Merkle Hashing for Upload {0}", "Merkle Hashed for Upload {0}", fileInfo.Name))\r
-                    {\r
-                        localTreeHash = Signature.CalculateTreeHashAsync(fileInfo,\r
-                                                                         action.AccountInfo.BlockSize,\r
-                                                                         action.AccountInfo.BlockHash, Settings.HashingParallelism,cancellationToken,progress);\r
-                    }\r
-\r
                     if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))\r
                         return;\r
 \r
                     if (!Selectives.IsSelected(action.AccountInfo, fileInfo) && !action.IsCreation)\r
                         return;\r
 \r
+\r
                     //Try to load the action's local state, if it is empty\r
                     if (action.FileState == null)\r
                         action.FileState = StatusKeeper.GetStateByFilePath(fileInfo.FullName);\r
+\r
+                    TreeHash localTreeHash;\r
+                    using (StatusNotification.GetNotifier("Merkle Hashing for Upload {0}", "Merkle Hashed for Upload {0}", fileInfo.Name))\r
+                    {\r
+                        //TODO: Load the stored treehash if appropriate\r
+                        //TODO: WHO updates LastMD5?\r
+\r
+                        var progress = new Progress<double>(d => StatusNotification.Notify(\r
+                            new StatusNotification(String.Format("Merkle Hashing for Upload {0:p} of {1}", d, fileInfo.Name))));\r
+\r
+                        //If the action's Treehash is already calculated, use it instead of reprocessing\r
+                        localTreeHash = action.TreeHash.IsValueCreated\r
+                            ? action.TreeHash.Value \r
+                            : StatusAgent.CalculateTreeHash(fileInfo, action.AccountInfo, action.FileState, Settings.HashingParallelism, cancellationToken, progress);\r
+                    }\r
+\r
+\r
                     if (action.FileState != null)\r
                     {\r
                         /*\r
@@ -156,7 +163,7 @@ namespace Pithos.Core.Agents
                                         //Go on and create the directory\r
                                         await\r
                                             client.PutObject(account, cloudFile.Container, cloudFile.Name, fullFileName,\r
-                                                             String.Empty, "application/directory");\r
+                                                             Signature.MERKLE_EMPTY, ObjectInfo.CONTENT_TYPE_DIRECTORY);\r
                                     //If the upload is in response to a Folder create with Selective Sync enabled\r
                                     if (action.IsCreation)\r
                                     {\r
@@ -187,7 +194,7 @@ namespace Pithos.Core.Agents
                                     if (cloudInfo != ObjectInfo.Empty && (topHash == cloudHash))\r
                                     {\r
                                         //but store any metadata changes \r
-                                        StatusKeeper.StoreInfo(fullFileName, cloudInfo);\r
+                                        StatusKeeper.StoreInfo(fullFileName, cloudInfo,treeHash);\r
                                         Log.InfoFormat("Skip upload of {0}, hashes match", fullFileName);\r
                                         return;\r
                                     }\r
@@ -211,15 +218,23 @@ namespace Pithos.Core.Agents
 \r
                                 var currentInfo = client.GetObjectInfo(cloudFile.Account, cloudFile.Container,\r
                                                                        cloudFile.Name);\r
+\r
+                                StatusKeeper.StoreInfo(fullFileName, currentInfo, localTreeHash);\r
+                                //Ensure the status is cleared\r
+                                StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged,\r
+                                                              FileOverlayStatus.Normal, "");\r
+/*\r
                                 //If there is no stored ObjectID in the file state, add it\r
+                                //TODO: Why not just update everything, then change the state?\r
                                 if (action.FileState == null || action.FileState.ObjectID == null)\r
                                 {\r
-                                    StatusKeeper.StoreInfo(fullFileName, currentInfo);\r
+                                    \r
                                 }\r
                                 else\r
                                     //If everything succeeds, change the file and overlay status to normal\r
                                     StatusKeeper.SetFileState(fullFileName, FileStatus.Unchanged,\r
                                                               FileOverlayStatus.Normal, "");\r
+*/\r
                             }\r
                             else\r
                             {\r
@@ -268,6 +283,7 @@ namespace Pithos.Core.Agents
             }\r
         }\r
 \r
+\r
         private static void MakeFileReadOnly(string fullFileName)\r
         {\r
             var attributes = File.GetAttributes(fullFileName);\r
index edfa1ff..8cc274d 100644 (file)
@@ -252,7 +252,7 @@ namespace Pithos.Core
         }\r
 */\r
 \r
-        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus,string etag)\r
+        public static void StoreOverlayStatus(string absolutePath, FileOverlayStatus newStatus)\r
         {\r
             if (string.IsNullOrWhiteSpace(absolutePath))\r
                 throw new ArgumentNullException("absolutePath");\r
@@ -275,7 +275,7 @@ namespace Pithos.Core
                                                            FilePath = absolutePath,\r
                                                            Id = Guid.NewGuid(),\r
                                                            OverlayStatus = newStatus,\r
-                                                           ETag = etag ?? String.Empty,\r
+                                                           ETag = Signature.MERKLE_EMPTY,\r
                                                            LastMD5=String.Empty,\r
                                                            IsFolder = Directory.Exists(absolutePath)\r
                                                        };\r
@@ -371,33 +371,40 @@ namespace Pithos.Core
                 throw new ArgumentNullException("absolutePath");\r
             Contract.EndContractBlock();\r
 \r
+            var hashes = treeHash.ToJson();\r
+            var topHash = treeHash.TopHash.ToHashString();\r
+\r
             ExecuteWithRetry((session, instance) =>\r
             {\r
                 \r
-                var hashes=treeHash.ToJson();\r
-                const string hqlUpdate = "update FileState set Checksum= :checksum,ETag=:etag, Hashes=:hashes where FilePath = :path ";\r
+                const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes,LastMD5=:md5 where FilePath = :path ";                \r
                 var updatedEntities = session.CreateQuery(hqlUpdate)\r
                     .SetString("path", absolutePath)\r
-                    .SetString("checksum", treeHash.TopHash.ToHashString())\r
-                    .SetString("etag", treeHash.MD5)\r
+                    .SetString("checksum", topHash)\r
+                    .SetString("md5",treeHash.MD5)\r
                     .SetString("hashes",hashes)\r
                     .ExecuteUpdate();\r
                 return updatedEntities;\r
             }, null);\r
         }\r
 \r
-        public static void UpdateChecksum(string absolutePath, string etag, string checksum)\r
+        //TODO: Must separate between UpdateChecksum and UpdateFileTreeHash\r
+        public static void UpdateChecksum(string absolutePath, string etag, TreeHash treeHash)\r
         {\r
             if (string.IsNullOrWhiteSpace(absolutePath))\r
                 throw new ArgumentNullException("absolutePath");\r
             Contract.EndContractBlock();\r
 \r
+            var hashes = treeHash.ToJson();\r
+            var topHash = treeHash.TopHash.ToHashString(); \r
+            \r
             ExecuteWithRetry((session, instance) =>\r
                         {\r
-                            const string hqlUpdate = "update FileState set Checksum= :checksum,ETag=:etag where FilePath = :path ";\r
+                            const string hqlUpdate = "update FileState set Checksum= :checksum,Hashes=:hashes,ETag=:etag where FilePath = :path ";\r
                             var updatedEntities = session.CreateQuery(hqlUpdate)\r
                                 .SetString("path", absolutePath)\r
-                                .SetString("checksum", checksum)\r
+                                .SetString("checksum", topHash)\r
+                                .SetString("hashes", hashes)\r
                                 .SetString("etag", etag)\r
                                 .ExecuteUpdate();\r
                             return updatedEntities;\r
@@ -405,6 +412,7 @@ namespace Pithos.Core
 \r
         }\r
 \r
+/*\r
 \r
         public static void UpdateLastMD5(FileInfo file, string md5)\r
         {\r
@@ -451,6 +459,7 @@ namespace Pithos.Core
                         }, null);\r
 \r
         }\r
+*/\r
 \r
         public static void ChangeRootPath(string oldPath, string newPath)\r
         {\r
@@ -488,26 +497,29 @@ namespace Pithos.Core
                 throw new ArgumentNullException("info");\r
             Contract.EndContractBlock();\r
             \r
-            if (info is DirectoryInfo)\r
-                return new FileState\r
-                {\r
-                    FilePath = info.FullName,\r
-                    OverlayStatus = FileOverlayStatus.Unversioned,\r
-                    FileStatus = FileStatus.Created,\r
-                    ETag=String.Empty,\r
-                    LastMD5=String.Empty,\r
-                    Id = Guid.NewGuid()\r
-                };\r
+            //The file may not exist, if the call is made for a new server object\r
 \r
-            \r
-            var etag = ((FileInfo)info).ComputeShortHash(notification);\r
+            var md5 = (info is DirectoryInfo || !info.Exists)\r
+                ?Signature.MD5_EMPTY\r
+                :((FileInfo)info).ComputeShortHash(notification);\r
+\r
+            var length = (info is DirectoryInfo || !info.Exists)\r
+                             ? 0\r
+                             : ((FileInfo) info).Length;\r
+\r
+            var lastWriteTime = (info.Exists)\r
+                                ?(DateTime?)info.LastWriteTime\r
+                                :null;\r
             var fileState = new FileState\r
                                 {\r
                                     FilePath = info.FullName,\r
                                     OverlayStatus = FileOverlayStatus.Unversioned,\r
                                     FileStatus = FileStatus.Created,               \r
-                                    ETag=etag,\r
-                                    LastMD5=String.Empty,\r
+                                    ETag=String.Empty,\r
+                                    LastMD5=md5,\r
+                                    LastWriteDate=lastWriteTime,\r
+                                    LastLength=length,\r
+                                    IsFolder=(info is DirectoryInfo),                                    \r
                                     Id = Guid.NewGuid()\r
                                 };\r
             return fileState;\r
@@ -577,8 +589,8 @@ namespace Pithos.Core
 \r
         public string ToDebugString()\r
         {\r
-            return String.Format("[STATE] FilePath:[{0}] ObjectID:[{1}], ETAG: [{2}], LastMD5:[{3}]", FilePath, ObjectID,\r
-                                 ETag, LastMD5);\r
+            return String.Format("[STATE] FilePath:[{0}] ObjectID:[{1}], ETAG: [{2}], Checksum: [{3}] LastMD5:[{4}]", FilePath, ObjectID,\r
+                                 ETag, Checksum,LastMD5);\r
         }\r
     }\r
 \r
index 8c49f0c..ce005c0 100644 (file)
@@ -54,8 +54,8 @@ namespace Pithos.Core
     [ContractClass(typeof(IStatusKeeperContract))]\r
     public interface IStatusKeeper\r
     {\r
-        Task SetFileOverlayStatus(string path, FileOverlayStatus status, string etag = null);\r
-        void UpdateFileChecksum(string path, string etag, string checksum);\r
+        Task SetFileOverlayStatus(string path, FileOverlayStatus status);\r
+        void UpdateFileChecksum(string path, string etag, TreeHash treeHash);\r
         void UpdateFileTreeHash(string path, TreeHash treeHash);\r
         void SetFileStatus(string path, FileStatus status);\r
         FileStatus GetFileStatus(string path);\r
@@ -64,7 +64,9 @@ namespace Pithos.Core
         void ProcessExistingFiles(IEnumerable<FileInfo> paths);\r
         void Stop();\r
         void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string localFileMissingFromServer);\r
+        //Only update locations, not hashes\r
         void StoreInfo(string path, ObjectInfo objectInfo);\r
+        void StoreInfo(string path, ObjectInfo objectInfo,TreeHash hash);\r
         //T GetStatus<T>(string path,Func<FileState,T> getter,T defaultValue );\r
         //void SetStatus(string path, Action<FileState> setter);        \r
 \r
@@ -82,23 +84,23 @@ namespace Pithos.Core
 \r
         void CleanupStaleStates(Network.AccountInfo accountInfo, List<ObjectInfo> objectInfos);\r
         void CleanupOrphanStates();\r
-        void UpdateLastMD5(FileInfo path, string etag);\r
+        //void UpdateLastMD5(FileInfo path, string etag);\r
     }\r
 \r
     [ContractClassFor(typeof(IStatusKeeper))]\r
     public abstract class IStatusKeeperContract : IStatusKeeper\r
     {\r
-        public Task SetFileOverlayStatus(string path, FileOverlayStatus status, string etag = null)\r
+        public Task SetFileOverlayStatus(string path, FileOverlayStatus status)\r
         {\r
             Contract.Requires(!String.IsNullOrWhiteSpace(path));\r
             Contract.Requires(Path.IsPathRooted(path));\r
             return default(Task);\r
         }\r
 \r
-        public void UpdateFileChecksum(string path, string etag, string checksum)\r
+        public void UpdateFileChecksum(string path, string etag, TreeHash treeHash)\r
         {\r
             Contract.Requires(!String.IsNullOrWhiteSpace(path));\r
-            Contract.Requires(checksum!=null);\r
+            Contract.Requires(treeHash!=null);\r
             Contract.Requires(Path.IsPathRooted(path));\r
         }\r
 \r
@@ -162,10 +164,18 @@ namespace Pithos.Core
             Contract.Requires(Path.IsPathRooted(path));\r
         }\r
 \r
-        public void StoreInfo(string path, ObjectInfo objectInfo)\r
+        public void StoreInfo(string path, ObjectInfo objectInfo,TreeHash hash)\r
         {\r
             Contract.Requires(!String.IsNullOrWhiteSpace(path));\r
             Contract.Requires(objectInfo!=null);\r
+            Contract.Requires(hash != null);\r
+            Contract.Requires(Path.IsPathRooted(path));\r
+        }\r
+\r
+        public void StoreInfo(string path, ObjectInfo objectInfo)\r
+        {\r
+            Contract.Requires(!String.IsNullOrWhiteSpace(path));\r
+            Contract.Requires(objectInfo!=null);            \r
             Contract.Requires(Path.IsPathRooted(path));\r
         }\r
 \r
index 1fa79d8..766c5c2 100644 (file)
-#region
-/* -----------------------------------------------------------------------
- * <copyright file="ObjectInfo.cs" company="GRNet">
- * 
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- *   1. Redistributions of source code must retain the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer in the documentation and/or other materials
- *      provided with the distribution.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- * </copyright>
- * -----------------------------------------------------------------------
- */
-#endregion
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.Contracts;
-using System.Dynamic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using Newtonsoft.Json;
-
-namespace Pithos.Interfaces
-{
-    [DebuggerDisplay("Name {Name}")]
-    public class ObjectInfo//:DynamicObject 
-    {
-        private readonly List<string> _knownContainers= new List<string>{"trash"};
-        public string Name { get; set; }
-
-        [JsonProperty("hash")]
-        public string ETag { get; set; }
-
-        //public string Hash { get; set; }
-
-/*
-        public string X_Object_Hash { get { return Hash; } set { Hash = value; } }
-*/
-
-        public string X_Object_Hash { get; set; }
-
-        [JsonProperty("x_object_uuid")]
-        public string UUID { get; set; }
-
-        public long Bytes { get; set; }
-        public string Content_Type { get; set; }
-        public DateTime Last_Modified { get; set; }
-
-        private Dictionary<string, string> _tags=new Dictionary<string, string>();
-        public Dictionary<string, string> Tags
-        {
-            get { return _tags; }
-            set { _tags = value; }
-        }
-
-        private Dictionary<string, string> _extensions=new Dictionary<string, string>();
-        public Dictionary<string, string> Extensions
-        {
-            get { return _extensions; }
-            set
-            {
-                _extensions = value;
-                ExtractKnownExtensions();
-            }
-        }
-        
-        
-        private Dictionary<string, string> _permissions=new Dictionary<string, string>();
-        [JsonProperty("x_object_sharing")]
-        [JsonConverter(typeof(PermissionConverter))]
-        public Dictionary<string, string> Permissions
-        {
-            get { return _permissions; }
-            set
-            {
-                _permissions = value;                
-            }
-        }
-
-        /// <summary>
-        /// Version number
-        /// </summary>
-        [JsonProperty("x_object_version")]
-        public long? Version { get; set; }
-
-
-        /// <summary>
-        /// Shared object permissions can be Read or Write
-        /// </summary>
-        [JsonProperty("x_object_allowed_to")]
-        public string AllowedTo { get; set; }
-
-
-        /// <summary>
-        /// Version timestamp
-        /// </summary>
-        [JsonProperty("X_Object_Version_Timestamp"), JsonConverter(typeof(PithosDateTimeConverter))]
-        public DateTime? VersionTimestamp { get; set; }
-
-        [JsonProperty("X_Object_Modified_By")]
-        public string ModifiedBy { get; set; }
-
-
-        public Stream Stream { get; set; }
-
-
-        public Uri StorageUri { get; set; }
-
-        public string Account { get; set; }
-
-        public string Container { get; set; }
-
-        public Uri Uri
-        {
-            get
-            {
-                var relativeUrl=String.Format("{0}/{1}/{2}",Account, Container,Name);
-                return StorageUri==null 
-                    ? new Uri(relativeUrl,UriKind.Relative) 
-                    : new Uri(StorageUri, relativeUrl);
-            }
-        }
-
-        public string ContendDisposition { get; set; }
-
-        public string ContentEncoding { get; set; }
-
-        public string Manifest { get; set; }
-        
-        public bool IsPublic
-        {
-            get { return !String.IsNullOrWhiteSpace(PublicUrl); }
-            set
-            {
-                if (!value)
-                    PublicUrl = null;
-                else if (String.IsNullOrWhiteSpace(PublicUrl))
-                    PublicUrl="true";                
-            }
-        }
-
-        [JsonProperty("X_Object_Public")]
-        public string PublicUrl { get; set; }
-
-        public string PreviousHash { get; set; }
-
-        public ObjectInfo()
-        {}
-
-        public ObjectInfo(string accountPath,string accountName,FileSystemInfo fileInfo)
-        {
-            if (String.IsNullOrWhiteSpace(accountPath))
-                throw new ArgumentNullException("accountPath");
-            if (string.IsNullOrWhiteSpace(accountName))
-                throw new ArgumentNullException("accountName");
-            if (fileInfo == null)
-                throw new ArgumentNullException("fileInfo");
-            Contract.EndContractBlock();
-
-            var relativeUrl = fileInfo.WithProperCapitalization().AsRelativeUrlTo(accountPath);
-            //The first part of the URL is the container
-            var slashIndex = relativeUrl.IndexOf('/');
-            var container = relativeUrl.Substring(0, slashIndex);
-            //The second is the file's url relative to the container
-            var fileUrl = relativeUrl.Substring(slashIndex + 1);
-
-            Account = accountName;
-            Container = container;
-            Name = fileUrl; 
-        }
-
-
-        private void ExtractKnownExtensions()
-        {
-            Version=GetLong(KnownExtensions.X_Object_Version);
-            VersionTimestamp = GetTimestamp(KnownExtensions.X_Object_Version_Timestamp);
-            ModifiedBy = GetString(KnownExtensions.X_Object_Modified_By);
-        }
-
-        private string GetString(string name)
-        {            
-            string value;
-            _extensions.TryGetValue(name, out value);
-            return value ;                        
-        }
-        
-        private long? GetLong(string name)
-        {
-            string version;
-            long value;
-            return _extensions.TryGetValue(name, out version) && long.TryParse(version, out value)
-                       ? (long?) value
-                       : null;
-        }
-
-        private DateTime? GetTimestamp(string name)
-        {
-            string version;
-            DateTime value;
-            if (_extensions.TryGetValue(name, out version) && 
-                DateTime.TryParse(version,CultureInfo.InvariantCulture,DateTimeStyles.AdjustToUniversal, out value))
-            {
-                return value;
-            }
-            return null;
-        }
-
-
-        public static ObjectInfo Empty = new ObjectInfo
-        {
-            Name = String.Empty,
-            ETag= String.Empty,
-            X_Object_Hash= String.Empty,
-            Bytes = 0,
-            Content_Type = String.Empty,
-            Last_Modified = DateTime.MinValue,
-            Exists=false
-        };
-
-        private bool _exists=true;
-
-        public bool Exists
-        {
-            get {
-                return _exists;
-            }
-            set {
-                _exists = value;
-            }
-        }
-
-
-        public string RelativeUrlToFilePath(string currentAccount)
-        {
-            if (Name==null)
-                throw new InvalidOperationException("Name can't be null");
-            if (String.IsNullOrWhiteSpace(currentAccount))
-                throw new ArgumentNullException("currentAccount");
-            Contract.EndContractBlock();
-
-            if (this == Empty)
-                return String.Empty;
-
-            var unescaped = Uri.UnescapeDataString(Name);
-            var path = unescaped.Replace("/", "\\");
-            var pathParts=new Stack<string>();
-            pathParts.Push(path);
-            if (!String.IsNullOrWhiteSpace(Container) && !_knownContainers.Contains(Container))
-                pathParts.Push(Container);
-            if (!currentAccount.Equals(Account, StringComparison.InvariantCultureIgnoreCase))
-            {
-                if (Account != null)
-                {
-                    pathParts.Push(Account);
-                    pathParts.Push(FolderConstants.OthersFolder);
-                }
-            }
-            var finalPath=Path.Combine(pathParts.ToArray());
-            return finalPath;
-        }
-
-/*
-        public override bool TrySetMember(SetMemberBinder binder, object value)
-        {
-            if (binder.Name.StartsWith("x_object_meta"))
-            {
-                Tags[binder.Name] = value.ToString();
-            }
-            return false;
-        }
-*/
-
-        public string GetPermissionString()
-        {
-            if (Permissions==null)
-                throw new InvalidOperationException();
-            Contract.EndContractBlock();
-
-            if (Permissions.Count == 0)
-                return "~";
-            var permissionBuilder = new StringBuilder();
-            var groupings = Permissions.GroupBy(pair => pair.Value.Trim(), pair => pair.Key.Trim());
-            foreach (var grouping in groupings)
-            {
-                permissionBuilder.AppendFormat("{0}={1};", grouping.Key, String.Join(",", grouping));
-            }
-            var permissions = Uri.EscapeDataString(permissionBuilder.ToString().Trim(';'));
-            return permissions;
-        }
-
-        public void SetPermissions(string permissions)
-        {
-            if (String.IsNullOrWhiteSpace(permissions))
-                return;
-            
-            Permissions = PermissionConverter.ParsePermissions(permissions);
-        }
-
-        //The previous values that correspond to a NoModification object
-        //have the same account, container and possibly the same folder
-        public bool CorrespondsTo(ObjectInfo other)
-        {
-            return other.Account == this.Account
-                   && other.Container == this.Container
-                   && (this.Name == null || other.Name.StartsWith(this.Name));
-        }
-
-        public bool IsWritable(string account)
-        {
-            //If the Allowed To header has no value, try to determine the Share permissions
-            if (AllowedTo == null)
-            {
-                //If this file has no permissions defined, we can probably write it
-                //This case should occur only when the info comes from a listing of the user's own files
-                if (Permissions == null || Permissions.Count == 0)
-                    return true;
-                string perms;
-
-                //Do we have an explicit write share to this account?
-                return Permissions.TryGetValue(account, out perms) 
-                    && perms.Equals("write",StringComparison.InvariantCultureIgnoreCase);
-                
-            }
-            //Otherwise return the permissions specified by AllowedTo
-            return AllowedTo.Equals("write",StringComparison.InvariantCultureIgnoreCase) ;
-        }
-
-        public ObjectInfo Previous { get; private set; }
-
-        public bool IsDirectory
-        {
-            get
-            {
-                if (Content_Type == null)
-                    return false;
-                if (Content_Type.StartsWith(@"application/directory",StringComparison.InvariantCultureIgnoreCase))
-                    return true;
-                if (Content_Type.StartsWith(@"application/folder",StringComparison.InvariantCultureIgnoreCase))
-                    return true;
-                return false;
-            }
-        }
-
-        public Uri AccountKey
-        {
-            get { return new Uri(StorageUri,"../" + Account); }
-        }
-
-        public ObjectInfo SetPrevious(ObjectInfo previous)
-        {            
-            Previous = previous;
-            PreviousHash = previous.X_Object_Hash;
-            return this;
-        }
-
-        public bool? IsShared
-        {
-            get
-            {
-                if (Uri == null || StorageUri == null)
-                    return null;
-                var isShared = !Uri.ToString().StartsWith(StorageUri.ToString());
-                return isShared;
-            }
-        }
-    }
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="ObjectInfo.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Diagnostics;\r
+using System.Diagnostics.Contracts;\r
+using System.Dynamic;\r
+using System.Globalization;\r
+using System.IO;\r
+using System.Linq;\r
+using System.Text;\r
+using Newtonsoft.Json;\r
+\r
+namespace Pithos.Interfaces\r
+{\r
+    [DebuggerDisplay("Name {Name}")]\r
+    public class ObjectInfo//:DynamicObject \r
+    {\r
+        public const string CONTENT_TYPE_DIRECTORY = @"application/directory";\r
+        public const string CONTENT_TYPE_FOLDER = @"application/folder";\r
+        private readonly List<string> _knownContainers= new List<string>{"trash"};\r
+        public string Name { get; set; }\r
+\r
+        [JsonProperty("hash")]\r
+        public string ETag { get; set; }\r
+\r
+        //public string Hash { get; set; }\r
+\r
+/*\r
+        public string X_Object_Hash { get { return Hash; } set { Hash = value; } }\r
+*/\r
+\r
+        public string X_Object_Hash { get; set; }\r
+\r
+        [JsonProperty("x_object_uuid")]\r
+        public string UUID { get; set; }\r
+\r
+        public long Bytes { get; set; }\r
+        public string Content_Type { get; set; }\r
+        public DateTime Last_Modified { get; set; }\r
+\r
+        private Dictionary<string, string> _tags=new Dictionary<string, string>();\r
+        public Dictionary<string, string> Tags\r
+        {\r
+            get { return _tags; }\r
+            set { _tags = value; }\r
+        }\r
+\r
+        private Dictionary<string, string> _extensions=new Dictionary<string, string>();\r
+        public Dictionary<string, string> Extensions\r
+        {\r
+            get { return _extensions; }\r
+            set\r
+            {\r
+                _extensions = value;\r
+                ExtractKnownExtensions();\r
+            }\r
+        }\r
+        \r
+        \r
+        private Dictionary<string, string> _permissions=new Dictionary<string, string>();\r
+        [JsonProperty("x_object_sharing")]\r
+        [JsonConverter(typeof(PermissionConverter))]\r
+        public Dictionary<string, string> Permissions\r
+        {\r
+            get { return _permissions; }\r
+            set\r
+            {\r
+                _permissions = value;                \r
+            }\r
+        }\r
+\r
+        /// <summary>\r
+        /// Version number\r
+        /// </summary>\r
+        [JsonProperty("x_object_version")]\r
+        public long? Version { get; set; }\r
+\r
+\r
+        /// <summary>\r
+        /// Shared object permissions can be Read or Write\r
+        /// </summary>\r
+        [JsonProperty("x_object_allowed_to")]\r
+        public string AllowedTo { get; set; }\r
+\r
+\r
+        /// <summary>\r
+        /// Version timestamp\r
+        /// </summary>\r
+        [JsonProperty("X_Object_Version_Timestamp"), JsonConverter(typeof(PithosDateTimeConverter))]\r
+        public DateTime? VersionTimestamp { get; set; }\r
+\r
+        [JsonProperty("X_Object_Modified_By")]\r
+        public string ModifiedBy { get; set; }\r
+\r
+\r
+        public Stream Stream { get; set; }\r
+\r
+\r
+        public Uri StorageUri { get; set; }\r
+\r
+        public string Account { get; set; }\r
+\r
+        public string Container { get; set; }\r
+\r
+        public Uri Uri\r
+        {\r
+            get\r
+            {\r
+                var relativeUrl=String.Format("{0}/{1}/{2}",Account, Container,Name);\r
+                return StorageUri==null \r
+                    ? new Uri(relativeUrl,UriKind.Relative) \r
+                    : new Uri(StorageUri, relativeUrl);\r
+            }\r
+        }\r
+\r
+        public string ContendDisposition { get; set; }\r
+\r
+        public string ContentEncoding { get; set; }\r
+\r
+        public string Manifest { get; set; }\r
+        \r
+        public bool IsPublic\r
+        {\r
+            get { return !String.IsNullOrWhiteSpace(PublicUrl); }\r
+            set\r
+            {\r
+                if (!value)\r
+                    PublicUrl = null;\r
+                else if (String.IsNullOrWhiteSpace(PublicUrl))\r
+                    PublicUrl="true";                \r
+            }\r
+        }\r
+\r
+        [JsonProperty("X_Object_Public")]\r
+        public string PublicUrl { get; set; }\r
+\r
+        public string PreviousHash { get; set; }\r
+\r
+        public ObjectInfo()\r
+        {}\r
+\r
+        public ObjectInfo(string accountPath,string accountName,FileSystemInfo fileInfo)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(accountPath))\r
+                throw new ArgumentNullException("accountPath");\r
+            if (string.IsNullOrWhiteSpace(accountName))\r
+                throw new ArgumentNullException("accountName");\r
+            if (fileInfo == null)\r
+                throw new ArgumentNullException("fileInfo");\r
+            Contract.EndContractBlock();\r
+\r
+            var relativeUrl = fileInfo.WithProperCapitalization().AsRelativeUrlTo(accountPath);\r
+            //The first part of the URL is the container\r
+            var slashIndex = relativeUrl.IndexOf('/');\r
+            var container = relativeUrl.Substring(0, slashIndex);\r
+            //The second is the file's url relative to the container\r
+            var fileUrl = relativeUrl.Substring(slashIndex + 1);\r
+\r
+            Account = accountName;\r
+            Container = container;\r
+            Name = fileUrl; \r
+        }\r
+\r
+\r
+        private void ExtractKnownExtensions()\r
+        {\r
+            Version=GetLong(KnownExtensions.X_Object_Version);\r
+            VersionTimestamp = GetTimestamp(KnownExtensions.X_Object_Version_Timestamp);\r
+            ModifiedBy = GetString(KnownExtensions.X_Object_Modified_By);\r
+        }\r
+\r
+        private string GetString(string name)\r
+        {            \r
+            string value;\r
+            _extensions.TryGetValue(name, out value);\r
+            return value ;                        \r
+        }\r
+        \r
+        private long? GetLong(string name)\r
+        {\r
+            string version;\r
+            long value;\r
+            return _extensions.TryGetValue(name, out version) && long.TryParse(version, out value)\r
+                       ? (long?) value\r
+                       : null;\r
+        }\r
+\r
+        private DateTime? GetTimestamp(string name)\r
+        {\r
+            string version;\r
+            DateTime value;\r
+            if (_extensions.TryGetValue(name, out version) && \r
+                DateTime.TryParse(version,CultureInfo.InvariantCulture,DateTimeStyles.AdjustToUniversal, out value))\r
+            {\r
+                return value;\r
+            }\r
+            return null;\r
+        }\r
+\r
+\r
+        public static ObjectInfo Empty = new ObjectInfo\r
+        {\r
+            Name = String.Empty,\r
+            ETag= String.Empty,\r
+            X_Object_Hash= String.Empty,\r
+            Bytes = 0,\r
+            Content_Type = String.Empty,\r
+            Last_Modified = DateTime.MinValue,\r
+            Exists=false\r
+        };\r
+\r
+        private bool _exists=true;\r
+\r
+        public bool Exists\r
+        {\r
+            get {\r
+                return _exists;\r
+            }\r
+            set {\r
+                _exists = value;\r
+            }\r
+        }\r
+\r
+\r
+        public string RelativeUrlToFilePath(string currentAccount)\r
+        {\r
+            if (Name==null)\r
+                throw new InvalidOperationException("Name can't be null");\r
+            if (String.IsNullOrWhiteSpace(currentAccount))\r
+                throw new ArgumentNullException("currentAccount");\r
+            Contract.EndContractBlock();\r
+\r
+            if (this == Empty)\r
+                return String.Empty;\r
+\r
+            var unescaped = Uri.UnescapeDataString(Name);\r
+            var path = unescaped.Replace("/", "\\");\r
+            var pathParts=new Stack<string>();\r
+            pathParts.Push(path);\r
+            if (!String.IsNullOrWhiteSpace(Container) && !_knownContainers.Contains(Container))\r
+                pathParts.Push(Container);\r
+            if (!currentAccount.Equals(Account, StringComparison.InvariantCultureIgnoreCase))\r
+            {\r
+                if (Account != null)\r
+                {\r
+                    pathParts.Push(Account);\r
+                    pathParts.Push(FolderConstants.OthersFolder);\r
+                }\r
+            }\r
+            var finalPath=Path.Combine(pathParts.ToArray());\r
+            return finalPath;\r
+        }\r
+\r
+/*\r
+        public override bool TrySetMember(SetMemberBinder binder, object value)\r
+        {\r
+            if (binder.Name.StartsWith("x_object_meta"))\r
+            {\r
+                Tags[binder.Name] = value.ToString();\r
+            }\r
+            return false;\r
+        }\r
+*/\r
+\r
+        public string GetPermissionString()\r
+        {\r
+            if (Permissions==null)\r
+                throw new InvalidOperationException();\r
+            Contract.EndContractBlock();\r
+\r
+            if (Permissions.Count == 0)\r
+                return "~";\r
+            var permissionBuilder = new StringBuilder();\r
+            var groupings = Permissions.GroupBy(pair => pair.Value.Trim(), pair => pair.Key.Trim());\r
+            foreach (var grouping in groupings)\r
+            {\r
+                permissionBuilder.AppendFormat("{0}={1};", grouping.Key, String.Join(",", grouping));\r
+            }\r
+            var permissions = Uri.EscapeDataString(permissionBuilder.ToString().Trim(';'));\r
+            return permissions;\r
+        }\r
+\r
+        public void SetPermissions(string permissions)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(permissions))\r
+                return;\r
+            \r
+            Permissions = PermissionConverter.ParsePermissions(permissions);\r
+        }\r
+\r
+        //The previous values that correspond to a NoModification object\r
+        //have the same account, container and possibly the same folder\r
+        public bool CorrespondsTo(ObjectInfo other)\r
+        {\r
+            return other.Account == this.Account\r
+                   && other.Container == this.Container\r
+                   && (this.Name == null || other.Name.StartsWith(this.Name));\r
+        }\r
+\r
+        public bool IsWritable(string account)\r
+        {\r
+            //If the Allowed To header has no value, try to determine the Share permissions\r
+            if (AllowedTo == null)\r
+            {\r
+                //If this file has no permissions defined, we can probably write it\r
+                //This case should occur only when the info comes from a listing of the user's own files\r
+                if (Permissions == null || Permissions.Count == 0)\r
+                    return true;\r
+                string perms;\r
+\r
+                //Do we have an explicit write share to this account?\r
+                return Permissions.TryGetValue(account, out perms) \r
+                    && perms.Equals("write",StringComparison.InvariantCultureIgnoreCase);\r
+                \r
+            }\r
+            //Otherwise return the permissions specified by AllowedTo\r
+            return AllowedTo.Equals("write",StringComparison.InvariantCultureIgnoreCase) ;\r
+        }\r
+\r
+        public ObjectInfo Previous { get; private set; }\r
+\r
+        public bool IsDirectory\r
+        {\r
+            get\r
+            {\r
+                if (Content_Type == null)\r
+                    return false;\r
+                if (Content_Type.StartsWith(CONTENT_TYPE_DIRECTORY,StringComparison.InvariantCultureIgnoreCase))\r
+                    return true;\r
+                if (Content_Type.StartsWith(CONTENT_TYPE_FOLDER,StringComparison.InvariantCultureIgnoreCase))\r
+                    return true;\r
+                return false;\r
+            }\r
+        }\r
+\r
+        public Uri AccountKey\r
+        {\r
+            get { return new Uri(StorageUri,"../" + Account); }\r
+        }\r
+\r
+        public ObjectInfo SetPrevious(ObjectInfo previous)\r
+        {            \r
+            Previous = previous;\r
+            PreviousHash = previous.X_Object_Hash;\r
+            return this;\r
+        }\r
+\r
+        public bool? IsShared\r
+        {\r
+            get\r
+            {\r
+                if (Uri == null || StorageUri == null)\r
+                    return null;\r
+                var isShared = !Uri.ToString().StartsWith(StorageUri.ToString());\r
+                return isShared;\r
+            }\r
+        }\r
+    }\r
 }
\ No newline at end of file
index cfc2c64..b5bd98e 100644 (file)
@@ -412,8 +412,8 @@ namespace Pithos.Network
                                                       {\r
                                                           Account = objectInfo.Account,\r
                                                           Container = objectInfo.Container,\r
-                                                          Content_Type = @"application/directory",\r
-                                                          ETag = Signature.MD5_EMPTY,\r
+                                                          Content_Type = ObjectInfo.CONTENT_TYPE_DIRECTORY,\r
+                                                          ETag = Signature.MERKLE_EMPTY,\r
                                                           X_Object_Hash = Signature.MERKLE_EMPTY,\r
                                                           Name=parentName,\r
                                                           StorageUri=objectInfo.StorageUri,\r
@@ -941,7 +941,7 @@ namespace Pithos.Network
                     client.BaseAddress = GetAccountUrl(account);\r
 \r
                 client.Parameters.Clear();\r
-                client.Headers.Add("Content-Type", @"application/directory");\r
+                client.Headers.Add("Content-Type", ObjectInfo.CONTENT_TYPE_DIRECTORY);\r
                 client.Headers.Add("Content-Length", "0");\r
                 client.PutWithRetry(folderUrl, 3);\r
 \r
@@ -1137,7 +1137,7 @@ namespace Pithos.Network
             client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream";\r
             var jsonHash = hash.ToJson();\r
                         \r
-            client.Headers.Add("ETag",hash.MD5);\r
+            client.Headers.Add("ETag",hash.TopHash.ToHashString());\r
             var uploadTask=client.UploadStringTask(uri, "PUT", jsonHash);\r
             if (Log.IsDebugEnabled)\r
                 Log.DebugFormat("Hashes:\r\n{0}", jsonHash);\r
@@ -1378,7 +1378,7 @@ namespace Pithos.Network
         /// <param name="fileName"></param>\r
         /// <param name="hash">Optional hash value for the file. If no hash is provided, the method calculates a new hash</param>\r
         /// <remarks>>This method should have no timeout or a very long one</remarks>\r
-        public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream")\r
+        public async Task PutObject(string account, string container, string objectName, string fileName, string hash = Signature.MERKLE_EMPTY, string contentType = "application/octet-stream")\r
         {\r
             if (String.IsNullOrWhiteSpace(container))\r
                 throw new ArgumentNullException("container", "The container property can't be empty");\r
@@ -1402,10 +1402,11 @@ namespace Pithos.Network
                     var builder = client.GetAddressBuilder(container, objectName);\r
                     var uri = builder.Uri;\r
 \r
-                    string etag = hash ?? CalculateHash(fileName);\r
+                    string etag = hash ;\r
 \r
                     client.Headers.Add("Content-Type", contentType);\r
-                    client.Headers.Add("ETag", etag);\r
+                    if (contentType!=ObjectInfo.CONTENT_TYPE_DIRECTORY)\r
+                        client.Headers.Add("ETag", etag);\r
 \r
 \r
                     Log.InfoFormat("[PUT] START {0}", objectName);\r
@@ -1426,7 +1427,7 @@ namespace Pithos.Network
                                                               Log.InfoFormat("Completed {0}", fileName);\r
                                                           }\r
                                                       };                    \r
-                    if (contentType=="application/directory")\r
+                    if (contentType==ObjectInfo.CONTENT_TYPE_DIRECTORY)\r
                         await client.UploadDataTaskAsync(uri, "PUT", new byte[0]).ConfigureAwait(false);\r
                     else\r
                         await client.UploadFileTaskAsync(uri, "PUT", fileName).ConfigureAwait(false);\r
@@ -1443,6 +1444,7 @@ namespace Pithos.Network
         }\r
        \r
         \r
+/*\r
         private static string CalculateHash(string fileName)\r
         {\r
             Contract.Requires(!String.IsNullOrWhiteSpace(fileName));\r
@@ -1459,6 +1461,7 @@ namespace Pithos.Network
             }\r
             return hash;\r
         }\r
+*/\r
         \r
         public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName)\r
         {\r
index ae6425b..4a5992b 100644 (file)
@@ -51,12 +51,13 @@ using System.Threading;
 using System.Threading.Tasks;\r
 using System.Linq;\r
 using Pithos.Interfaces;\r
+using log4net;\r
 \r
 namespace Pithos.Network\r
 {\r
     public static class Signature\r
     {\r
-        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
         public const  int BufferSize = 16384;\r
 \r
         public const string MD5_EMPTY = "d41d8cd98f00b204e9800998ecf8427e";\r
@@ -89,7 +90,7 @@ namespace Pithos.Network
 \r
             string hash;\r
             using (var hasher = MD5.Create())\r
-            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, Signature.BufferSize, true))\r
+            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, true))\r
             {\r
                 var hashBytes = hasher.ComputeHash(stream);\r
                 hash = hashBytes.ToHashString();\r
@@ -97,6 +98,7 @@ namespace Pithos.Network
             return hash;\r
         }\r
 \r
+     \r
 /*\r
         public static string BytesToString(byte[] hashBytes)\r
         {\r
index 90e0e90..766400a 100644 (file)
-#region
-/* -----------------------------------------------------------------------
- * <copyright file="TreeHash.cs" company="GRNet">
- * 
- * Copyright 2011-2012 GRNET S.A. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- *   1. Redistributions of source code must retain the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above
- *      copyright notice, this list of conditions and the following
- *      disclaimer in the documentation and/or other materials
- *      provided with the distribution.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
- * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * The views and conclusions contained in the software and
- * documentation are those of the authors and should not be
- * interpreted as representing official policies, either expressed
- * or implied, of GRNET S.A.
- * </copyright>
- * -----------------------------------------------------------------------
- */
-#endregion
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-
-using System.Linq;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace Pithos.Network
-{
-    public class TreeHash
-    {
-        private const string DEFAULT_HASH_ALGORITHM = "sha256";
-        private const long DEFAULT_BLOCK_SIZE = 4*1024*1024;
-        public string BlockHash { get; set; }
-        public long BlockSize { get; set; }
-        
-        private long _bytes;
-        public long Bytes
-        {
-            get
-            {
-                Contract.Ensures(Contract.Result<long>() >= 0);
-                return _bytes;
-            }
-            set
-            {
-                if (value<0)
-                    throw new ArgumentOutOfRangeException("Bytes");
-                Contract.Requires(value >= 0);
-                Contract.EndContractBlock();
-                
-                _bytes = value;
-            }
-        }
-        
-
-
-        public Guid FileId { get; set; }
-
-        private readonly Lazy<byte[]> _topHash;        
-        public byte[] TopHash
-        {
-            get { return _topHash.Value; }
-        }
-
-        private IList<byte[]> _hashes=new List<byte[]>();
-
-        public IList<byte[]> Hashes
-        {
-            get { return _hashes; }
-            set
-            {
-                _hashes = value;
-                _topHash.Force();
-            }
-        }
-
-        [ContractInvariantMethod]
-        private void Invariants()
-        {
-            Contract.Invariant(_bytes>=0);
-        }
-        
-
-       public TreeHash(string algorithm)
-        {
-            BlockHash = algorithm;            
-            _topHash = new Lazy<byte[]>(() =>
-            {
-                //
-                //Cast the hashes to an IList or create a new list out of the hashes
-                var hashMap = (_hashes as IList<byte[]>)??_hashes.ToList();
-                return Signature.CalculateTopHash(hashMap, BlockHash);
-            });
-        }
-
-        //Returns a Json representation of the hashes, as required by Pithos
-        public string ToJson()
-        {
-            var value = new JObject();
-            //We avoid using JObject's dynamic features because they use exceptions to detect new properties.
-            value["block_hash"] = BlockHash;
-            //Create a string array for all the hashes           
-            
-            string[] hashes=null ;
-            if (Hashes!=null)
-                hashes= GetHashesAsStrings();
-            value["hashes"]= new JArray(hashes);
-            value["block_size"] = BlockSize;
-            value["bytes"] = Bytes;
-            
-            var json = JsonConvert.SerializeObject(value, Formatting.None);
-            return json;
-        }
-
-        private string[] _stringHashes;
-        //Returns the hashes as an array of hash strings. Used for serialization to Json
-        public string[] GetHashesAsStrings()
-        {
-            return _stringHashes 
-                ?? (_stringHashes = Hashes.Select(hash => hash.ToHashString()).ToArray());
-        }
-
-        ConcurrentDictionary<string, long> _blocks;
-        //Retruns the hashes as a dictionary to the block location. Used to locate blocks
-        public IDictionary<string,long> HashDictionary
-        {
-            get
-            {
-                Func<ConcurrentDictionary<string, long>> blocksInit = () =>
-                {
-                    var blocks =
-                        new ConcurrentDictionary<string, long>();
-                    if (Hashes == null)
-                        return blocks;
-
-                    var blockIndex = 0;
-                    foreach (var hash in this.Hashes)
-                    {
-                        blocks[hash.ToHashString()] = blockIndex++;
-                    }
-                    return blocks;
-                };
-
-                return _blocks ?? (_blocks = blocksInit());
-            }
-        }
-
-        public string MD5 { get; set; }
-
-        //Saves the Json representation to a file
-        public async Task Save(string filePath)
-        {
-            if (String.IsNullOrWhiteSpace(filePath))
-                throw new ArgumentNullException("filePath");
-
-            var fileName = FileId.ToString("N");
-            var path = Path.Combine(filePath, fileName);
-            if (!Directory.Exists(filePath))
-                Directory.CreateDirectory(filePath);
-
-            var json = await TaskEx.Run(() => ToJson()).ConfigureAwait(false);
-            await FileAsync.WriteAllText(path, json).ConfigureAwait(false);
-        }
-
-        public static async Task<TreeHash> LoadTreeHash(string dataPath,Guid fileId)
-        {
-            var fileName = fileId.ToString("N");
-            var path = Path.Combine(dataPath, fileName);
-
-            var json = await FileAsync.ReadAllText(path).ConfigureAwait(false);
-            var treeHash = Parse(json);
-            treeHash.FileId = fileId;
-            return treeHash;
-        }
-
-        public static TreeHash Empty = new TreeHash(DEFAULT_HASH_ALGORITHM)
-        {
-            BlockSize = DEFAULT_BLOCK_SIZE,
-            Bytes = 0
-        };
-
-        //Parse a json string and return a TreeHash
-        //Returns an empty TreeHash if the string is null or empty
-        public static TreeHash Parse(string json)
-        {
-            if (String.IsNullOrWhiteSpace(json))
-                return Empty;            
-
-            var value = JsonConvert.DeserializeObject<JObject>(json);
-            if (value==null)
-                throw new ArgumentException("The json parameter doesn't contain any json data","json");
-            Contract.Assume(value!=null);
-
-            var blockHash = (string) value["block_hash"];
-            var size = value.Value<int>("block_size");
-            var bytes = value.Value<long>("bytes");
-            var hashes = value.Value<JArray>("hashes");
-            var hashValues = from JToken token in hashes
-                             select token.Value<string>().ToBytes();
-
-            var treeHash = new TreeHash(blockHash)
-                               {
-                                   BlockSize = size,
-                                   Hashes = hashValues.ToList(),
-                                   Bytes = bytes
-                               };
-            return treeHash;
-        }
-    }
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="TreeHash.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System;\r
+using System.Collections.Concurrent;\r
+using System.Collections.Generic;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Text;\r
+using System.Threading.Tasks;\r
+\r
+using System.Linq;\r
+using Newtonsoft.Json;\r
+using Newtonsoft.Json.Linq;\r
+\r
+namespace Pithos.Network\r
+{\r
+    public class TreeHash\r
+    {\r
+        public const string DEFAULT_HASH_ALGORITHM = "sha256";\r
+        public const long DEFAULT_BLOCK_SIZE = 4*1024*1024;\r
+        public string BlockHash { get; set; }\r
+        public long BlockSize { get; set; }\r
+        \r
+        private long _bytes;\r
+        public long Bytes\r
+        {\r
+            get\r
+            {\r
+                Contract.Ensures(Contract.Result<long>() >= 0);\r
+                return _bytes;\r
+            }\r
+            set\r
+            {\r
+                if (value<0)\r
+                    throw new ArgumentOutOfRangeException("Bytes");\r
+                Contract.Requires(value >= 0);\r
+                Contract.EndContractBlock();\r
+                \r
+                _bytes = value;\r
+            }\r
+        }\r
+        \r
+\r
+\r
+        public Guid FileId { get; set; }\r
+\r
+        private readonly Lazy<byte[]> _topHash;        \r
+        public byte[] TopHash\r
+        {\r
+            get { return _topHash.Value; }\r
+        }\r
+\r
+        private IList<byte[]> _hashes=new List<byte[]>();\r
+\r
+        public IList<byte[]> Hashes\r
+        {\r
+            get { return _hashes; }\r
+            set\r
+            {\r
+                _hashes = value;\r
+                _topHash.Force();\r
+            }\r
+        }\r
+\r
+        [ContractInvariantMethod]\r
+        private void Invariants()\r
+        {\r
+            Contract.Invariant(_bytes>=0);\r
+        }\r
+        \r
+\r
+       public TreeHash(string algorithm)\r
+        {\r
+            BlockHash = algorithm;            \r
+            _topHash = new Lazy<byte[]>(() =>\r
+            {\r
+                //\r
+                //Cast the hashes to an IList or create a new list out of the hashes\r
+                var hashMap = (_hashes as IList<byte[]>)??_hashes.ToList();\r
+                return Signature.CalculateTopHash(hashMap, BlockHash);\r
+            });\r
+        }\r
+\r
+        //Returns a Json representation of the hashes, as required by Pithos\r
+        public string ToJson()\r
+        {\r
+            var value = new JObject();\r
+            //We avoid using JObject's dynamic features because they use exceptions to detect new properties.\r
+            value["block_hash"] = BlockHash;\r
+            //Create a string array for all the hashes           \r
+            \r
+            string[] hashes=null ;\r
+            if (Hashes!=null)\r
+                hashes= GetHashesAsStrings();\r
+            value["hashes"]= new JArray(hashes);\r
+            value["block_size"] = BlockSize;\r
+            value["bytes"] = Bytes;\r
+            \r
+            var json = JsonConvert.SerializeObject(value, Formatting.None);\r
+            return json;\r
+        }\r
+\r
+        private string[] _stringHashes;\r
+        //Returns the hashes as an array of hash strings. Used for serialization to Json\r
+        public string[] GetHashesAsStrings()\r
+        {\r
+            return _stringHashes \r
+                ?? (_stringHashes = Hashes.Select(hash => hash.ToHashString()).ToArray());\r
+        }\r
+\r
+        ConcurrentDictionary<string, long> _blocks;\r
+        \r
+        //Retruns the hashes as a dictionary to the block location. Used to locate blocks\r
+        public IDictionary<string,long> HashDictionary\r
+        {\r
+            get\r
+            {\r
+                Func<ConcurrentDictionary<string, long>> blocksInit = () =>\r
+                {\r
+                    var blocks =\r
+                        new ConcurrentDictionary<string, long>();\r
+                    if (Hashes == null)\r
+                        return blocks;\r
+\r
+                    var blockIndex = 0;\r
+                    foreach (var hash in this.Hashes)\r
+                    {\r
+                        blocks[hash.ToHashString()] = blockIndex++;\r
+                    }\r
+                    return blocks;\r
+                };\r
+\r
+                return _blocks ?? (_blocks = blocksInit());\r
+            }\r
+        }\r
+\r
+        private string _md5=Signature.MD5_EMPTY;\r
+        public string MD5\r
+        {\r
+            get { return _md5; }\r
+            set { _md5 = value; }\r
+        }\r
+\r
+        //Saves the Json representation to a file\r
+        public async Task Save(string filePath)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(filePath))\r
+                throw new ArgumentNullException("filePath");\r
+\r
+            var fileName = FileId.ToString("N");\r
+            var path = Path.Combine(filePath, fileName);\r
+            if (!Directory.Exists(filePath))\r
+                Directory.CreateDirectory(filePath);\r
+\r
+            var json = await TaskEx.Run(() => ToJson()).ConfigureAwait(false);\r
+            await FileAsync.WriteAllText(path, json).ConfigureAwait(false);\r
+        }\r
+\r
+        public static async Task<TreeHash> LoadTreeHash(string dataPath,Guid fileId)\r
+        {\r
+            var fileName = fileId.ToString("N");\r
+            var path = Path.Combine(dataPath, fileName);\r
+\r
+            var json = await FileAsync.ReadAllText(path).ConfigureAwait(false);\r
+            var treeHash = Parse(json);\r
+            treeHash.FileId = fileId;\r
+            return treeHash;\r
+        }\r
+\r
+        public static TreeHash Empty = new TreeHash(DEFAULT_HASH_ALGORITHM)\r
+        {\r
+            BlockSize = DEFAULT_BLOCK_SIZE,\r
+            Bytes = 0\r
+        };\r
+\r
+        //Parse a json string and return a TreeHash\r
+        //Returns an empty TreeHash if the string is null or empty\r
+        public static TreeHash Parse(string json)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(json))\r
+                return Empty;            \r
+\r
+            var value = JsonConvert.DeserializeObject<JObject>(json);\r
+            if (value==null)\r
+                throw new ArgumentException("The json parameter doesn't contain any json data","json");\r
+            Contract.Assume(value!=null);\r
+\r
+            var blockHash = (string) value["block_hash"];\r
+            var size = value.Value<int>("block_size");\r
+            var bytes = value.Value<long>("bytes");\r
+            var hashes = value.Value<JArray>("hashes");\r
+            var hashValues = from JToken token in hashes\r
+                             select token.Value<string>().ToBytes();\r
+\r
+            var treeHash = new TreeHash(blockHash)\r
+                               {\r
+                                   BlockSize = size,\r
+                                   Hashes = hashValues.ToList(),\r
+                                   Bytes = bytes\r
+                               };\r
+            return treeHash;\r
+        }\r
+    }\r
 }
\ No newline at end of file