-<?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
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
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
_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
}\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
-#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
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
\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
{\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
}\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
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
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
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
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
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
: 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
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
-#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
{\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
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
{\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
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
\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
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
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
\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
\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
\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
\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
\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
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
_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
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
_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
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
.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
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
\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
\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
_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
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
Contract.EndContractBlock();\r
\r
_persistenceAgent.Post(() => FileState.UpdateLastMD5(file, etag));\r
- }\r
+ }*/\r
\r
\r
public void CleanupOrphanStates()\r
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
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
\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
\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
//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
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
\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
}\r
}\r
\r
+\r
private static void MakeFileReadOnly(string fullFileName)\r
{\r
var attributes = File.GetAttributes(fullFileName);\r
}\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
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
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
\r
}\r
\r
+/*\r
\r
public static void UpdateLastMD5(FileInfo file, string md5)\r
{\r
}, null);\r
\r
}\r
+*/\r
\r
public static void ChangeRootPath(string oldPath, string newPath)\r
{\r
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
\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
[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
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
\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
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
-#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
{\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
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
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
/// <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
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
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
}\r
\r
\r
+/*\r
private static string CalculateHash(string fileName)\r
{\r
Contract.Requires(!String.IsNullOrWhiteSpace(fileName));\r
}\r
return hash;\r
}\r
+*/\r
\r
public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName)\r
{\r
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
\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
return hash;\r
}\r
\r
+ \r
/*\r
public static string BytesToString(byte[] hashBytes)\r
{\r
-#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