From c561991c5d879822668e8997210c77f87119baec Mon Sep 17 00:00:00 2001 From: pkanavos Date: Mon, 27 Aug 2012 11:06:08 +0300 Subject: [PATCH] Move to 2012 and the Async Targeting pack --- trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj | 1298 ++++---- .../Preferences/AddAccountViewModel.cs | 796 ++--- trunk/Pithos.Client.WPF/packages.config | 13 +- trunk/Pithos.Core.Test/Pithos.Core.Test.csproj | 514 ++-- trunk/Pithos.Core.Test/packages.config | 7 +- trunk/Pithos.Core/Agents/StatusAgent.cs | 2063 ++++++------- trunk/Pithos.Core/Pithos.Core.csproj | 936 +++--- trunk/Pithos.Core/packages.config | 23 +- .../Pithos.IntegrationTests.csproj | 150 +- trunk/Pithos.IntegrationTests/packages.config | 7 +- .../Pithos.Network.Test/Pithos.Network.Test.csproj | 446 +-- trunk/Pithos.Network.Test/packages.config | 9 +- trunk/Pithos.Network/CloudFilesClient.cs | 3152 ++++++++++---------- trunk/Pithos.Network/Pithos.Network.csproj | 553 ++-- trunk/Pithos.Network/RestClient.cs | 1258 ++++---- .../Pithos.ShellExtensions.csproj | 861 +++--- trunk/packages/repositories.config | 24 +- 17 files changed, 6074 insertions(+), 6036 deletions(-) diff --git a/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj index dca77ef..273b295 100644 --- a/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj +++ b/trunk/Pithos.Client.WPF/Pithos.Client.WPF.csproj @@ -1,650 +1,650 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {4D9406A3-50ED-4672-BB97-A0B3EA4946FE} - WinExe - Properties - Pithos.Client.WPF - PithosPlus - v4.0 - Client - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - false - 0 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 0.7.0.%2a - false - true - - - x86 - true - full - true - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - bin\Release\ - TRACE - true - pdbonly - AnyCPU - bin\Release\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - - - pithos.snk - - - Images\pithos_logo-icon.ico - - - true - bin\x86\Premium Debug\ - DEBUG;TRACE - full - x86 - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - True - False - True - False - False - True - True - True - True - True - True - False - True - False - False - - - - - - - True - Full - %28none%29 - 0 - - - true - bin\Premium Debug\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x86\Debug All\ - DEBUG;TRACE - full - x86 - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\Debug All\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - bin\Release\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - true - bin\x64\Premium Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug All\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - - - - - - ..\Libraries\AsyncCtpLibrary.dll - - - ..\Libraries\Caliburn.Micro.dll - - - - ..\Libraries\Castle.Components.Validator.dll - - - ..\Libraries\Castle.Core.dll - - - ..\Libraries\log4net.dll - - - ..\Libraries\NHibernate.ByteCode.Castle.dll - - - - - - - False - ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.dll - - - False - ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.Linq.dll - - - - - - - ..\packages\Caliburn.Micro.1.2.0\lib\Net40\System.Windows.Interactivity.dll - - - - - - - - 4.0 - - - - - - False - ..\packages\Extended.Wpf.Toolkit.1.5.0\lib\net40\WPFToolkit.Extended.dll - - - - - MSBuild:Compile - Designer - - - - - - - - - - - ConflictsView.xaml - - - - - NewContainerView.xaml - - - - LogConsoleView.xaml - - - - - - AddAccountView.xaml - - - - - GroupsView.xaml - - - - LoginView.xaml - - - ProxyAccountView.xaml - - - - - AboutView.xaml - - - - - - - ContainerPropertiesView.xaml - - - - FilePropertiesView.xaml - - - - - - - - - FeedbackView.xaml - - - - MessageView.xaml - - - - MiniStatusView.xaml - - - - - - PreferencesView.xaml - - - - - - SelectiveSynchView.xaml - - - - - - PithosBalloon.xaml - - - - - - - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - - - MainWindow.xaml - Code - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - - - Pithos.licenseheader - - - Designer - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - - - - - - {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} - ParallelExtensionsExtras - - - {74635A21-2BAD-4522-AB95-E3E5703CD301} - NetSparkle2010 - - - {7AC63864-7638-41C4-969C-D3197EF2BED9} - NotifyIconWpf - - - {142AF135-DF30-4563-B0AC-B604235AE874} - Pithos.Core - - - {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} - Pithos.Interfaces - - - {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} - Pithos.Network - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4 Client Profile %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - - - - - - - - - - - - - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {4D9406A3-50ED-4672-BB97-A0B3EA4946FE} + WinExe + Properties + Pithos.Client.WPF + PithosPlus + v4.0 + Client + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + false + 0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 0.7.0.%2a + false + true + + + x86 + true + full + true + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + bin\Release\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + + + pithos.snk + + + Images\pithos_logo-icon.ico + + + true + bin\x86\Premium Debug\ + DEBUG;TRACE + full + x86 + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + True + False + True + False + False + True + True + True + True + True + True + False + True + False + False + + + + + + + True + Full + %28none%29 + 0 + + + true + bin\Premium Debug\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x86\Debug All\ + DEBUG;TRACE + full + x86 + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\Debug All\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + true + bin\x64\Premium Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug All\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Client.WPF.exe.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + + + + + + ..\Libraries\Caliburn.Micro.dll + + + + ..\Libraries\Castle.Components.Validator.dll + + + ..\Libraries\Castle.Core.dll + + + ..\Libraries\log4net.dll + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + ..\Libraries\NHibernate.ByteCode.Castle.dll + + + + + + + False + ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.dll + + + False + ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.Linq.dll + + + + + + + ..\packages\Caliburn.Micro.1.2.0\lib\Net40\System.Windows.Interactivity.dll + + + + + + + + 4.0 + + + + + + False + ..\packages\Extended.Wpf.Toolkit.1.5.0\lib\net40\WPFToolkit.Extended.dll + + + + + MSBuild:Compile + Designer + + + + + + + + + + + ConflictsView.xaml + + + + + NewContainerView.xaml + + + + LogConsoleView.xaml + + + + + + AddAccountView.xaml + + + + + GroupsView.xaml + + + + LoginView.xaml + + + ProxyAccountView.xaml + + + + + AboutView.xaml + + + + + + + ContainerPropertiesView.xaml + + + + FilePropertiesView.xaml + + + + + + + + + FeedbackView.xaml + + + + MessageView.xaml + + + + MiniStatusView.xaml + + + + + + PreferencesView.xaml + + + + + + SelectiveSynchView.xaml + + + + + + PithosBalloon.xaml + + + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + Pithos.licenseheader + + + Designer + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + + + + + {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} + ParallelExtensionsExtras + + + {74635A21-2BAD-4522-AB95-E3E5703CD301} + NetSparkle2010 + + + {7AC63864-7638-41C4-969C-D3197EF2BED9} + NotifyIconWpf + + + {142AF135-DF30-4563-B0AC-B604235AE874} + Pithos.Core + + + {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} + Pithos.Interfaces + + + {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} + Pithos.Network + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4 Client Profile %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs b/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs index fcca045..c3a0905 100644 --- a/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs +++ b/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs @@ -1,398 +1,398 @@ -#region -/* ----------------------------------------------------------------------- - * - * - * 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. - * - * ----------------------------------------------------------------------- - */ -#endregion -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Forms; -using Pithos.Client.WPF.Properties; -using Pithos.Interfaces; -using Pithos.Network; -using MessageBox = System.Windows.MessageBox; -using Screen = Caliburn.Micro.Screen; - -namespace Pithos.Client.WPF.Preferences -{ - [Export(typeof(AddAccountViewModel))] - public class AddAccountViewModel:Screen - { - - private readonly List _servers; - - public List Servers - { - get { return Settings.Default.Servers.Select(server => server.ServerUri).ToList(); } - } - - private bool _isValidServer; - public bool IsValidServer - { - get { return _isValidServer; } - set - { - _isValidServer = value; - NotifyOfPropertyChange(()=>IsValidServer); - } - } - - - private string _currentServer; - public string CurrentServer - { - get { return _currentServer; } - set - { - if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) - { - IsValidServer = false; - throw new UriFormatException(); - } - _currentServer = value; - IsValidServer = true; - HasValidCredentials = false; - IsConfirmed = false; - NotifyOfPropertyChange(()=>CurrentServer); - } - } - - private string _accountName; - public string AccountName - { - get { return _accountName; } - set - { - _accountName = value; - NotifyOfPropertyChange(()=>AccountName); - NotifyOfPropertyChange(() => HasCredentials); - } - } - - private string _token; - public string Token - { - get { return _token; } - set - { - _token = value; - NotifyOfPropertyChange(()=>Token); - NotifyOfPropertyChange(() => HasCredentials); - } - } - - private string _accountPath; - public string AccountPath - { - get { return _accountPath; } - set - { - _accountPath = value; - NotifyOfPropertyChange(() => AccountPath); - NotifyOfPropertyChange(() => HasAccountPath); - } - } - - - public bool HasAccountPath - { - get { return !String.IsNullOrWhiteSpace(AccountPath); } - } - - public bool HasCredentials - { - get { return !(String.IsNullOrWhiteSpace(AccountName) || String.IsNullOrWhiteSpace(Token) ) ; } - } - - - private bool _isConfirmed; - - public bool IsConfirmed - { - get { return _isConfirmed; } - set - { - _isConfirmed = value; - HasValidCredentials = false; - NotifyOfPropertyChange(() => IsConfirmed); - } - } - - - private bool _isAccountActive; - - public bool IsAccountActive - { - get { return _isAccountActive; } - set - { - _isAccountActive = value; - NotifyOfPropertyChange(() => IsAccountActive); - } - } - - - private bool _shouldCreateOkeanosFolder; - public bool ShouldCreateOkeanosFolder - { - get { return _shouldCreateOkeanosFolder; } - set - { - _shouldCreateOkeanosFolder = value; - NotifyOfPropertyChange(()=>ShouldCreateOkeanosFolder); - } - } - - bool HasNoParentWithCache(DirectoryInfo info) - { - if (info == null) - return false; - var parent = info.Parent; - if (parent == null) - return true; - if (parent.EnumerateDirectories(FolderConstants.CacheFolder).Any()) - { - return false; - } - return HasNoParentWithCache(parent); - } - - string GetLowestAboveCache(string path) - { - if (String.IsNullOrWhiteSpace(path)) - return null; - var info = new DirectoryInfo(path); - if (!info.Exists) - return path; - while (!HasNoParentWithCache(info)) - { - info = info.Parent; - } - if (info == null) - return null; - else - return info.FullName; - } - - public void SelectAccount() - { - using (var dlg = new FolderBrowserDialog{Description=Resources.AddAccountViewModel_SelectAccount_Please_select_a_folder}) - { - //Ask the user to select a folder - //Note: We need a parent window here, which we retrieve with GetView - var view = (Window)GetView(); - if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view))) - return; - - //If the selected path is under an account folder, ie a folder containint a .pithos.cache subfolder, try to locate - //the cache's parent folder - var selectedPath = GetLowestAboveCache(dlg.SelectedPath); - - //If the path ends with /okeanos#/pithos, replace with /okeanos# - var match = _okeanosRegex.Match(selectedPath); - if (match.Success) - { - selectedPath = match.Groups[1].Value; - } - - AccountPath= selectedPath; - - - - ShouldCreateOkeanosFolder=Directory.EnumerateFileSystemEntries(AccountPath).Any(); - } - } - - - public void RetrieveCredentials() - { - SetBusy("Waiting for credentials.", "Please enter your credentials in the Pithos logon page"); - IsConfirmed = false; - - try - { - var loginUri=new Uri(new Uri(CurrentServer) , "login"); - var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString()); - if (credentials == null) - return; - AccountName = credentials.UserName; - Token = credentials.Password; - - IsConfirmed = true; - - } - catch (PithosException exc) - { - ClearBusy(); - MessageBox.Show(exc.Message, "Unable to retrieve credentials"); - } - catch (Exception exc) - { - IsConfirmed = false; - MessageBox.Show(exc.ToString(), "Error"); - throw; - } - finally - { - ClearBusy(); - - ((Window) GetView()).Activate(); - } - - if (IsConfirmed) - TaskEx.Run(TestAccount); - - } - - public AddAccountViewModel() - { - _servers=new List - { - Settings.Default.ProductionServer, - Settings.Default.DevelopmentServer - }; - CurrentServer = _servers[0]; - DisplayName = "Add Pithos+ Account"; - } - - private bool _hasValidCredentials; - public bool HasValidCredentials - { - get { return _hasValidCredentials; } - set - { - _hasValidCredentials = value; - NotifyOfPropertyChange(()=>HasValidCredentials); - } - } - - private string _validationMessage; - public string ValidationMessage - { - get { return _validationMessage; } - set - { - _validationMessage = value; - NotifyOfPropertyChange(()=>ValidationMessage); - } - } - - private bool _isWorking; - public bool IsWorking - { - get { return _isWorking; } - set - { - _isWorking = value; - NotifyOfPropertyChange(()=>IsWorking); - } - } - - private string _busyTitle; - public string BusyTitle - { - get { return _busyTitle; } - set - { - _busyTitle = value; - NotifyOfPropertyChange(()=>BusyTitle); - } - } - - private string _busyDetail; - private Regex _okeanosRegex = new Regex(@"(.*okeanos[0-9]*)\\pithos$"); - - public string BusyDetail - { - get { return _busyDetail; } - set - { - _busyDetail = value; - NotifyOfPropertyChange(()=>BusyDetail); - } - } - - private void SetBusy(string title,string detail) - { - IsWorking = true; - BusyTitle = title; - BusyDetail = detail; - } - - private void ClearBusy() - { - IsWorking = false; - BusyTitle = ""; - BusyDetail = ""; - - } - - public async void TestAccount() - { - try - { - SetBusy("Validating Credentials", ""); - var client = new CloudFilesClient(AccountName, Token) { AuthenticationUrl = CurrentServer,/*Proxy=Proxy */}; - await TaskEx.Run(() => - { - client.Authenticate(); - return client.ListContainers(AccountName); - }).ConfigureAwait(false); - HasValidCredentials = true; - ValidationMessage = "Credentials Validated"; - } - catch - { - HasValidCredentials = false; - MessageBox.Show("The account is not valid", "Account Error", MessageBoxButton.OK, MessageBoxImage.Stop); - ValidationMessage = "Credentials validation failed"; - } - finally - { - ClearBusy(); - } - } - - } -} +#region +/* ----------------------------------------------------------------------- + * + * + * 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. + * + * ----------------------------------------------------------------------- + */ +#endregion +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Forms; +using Pithos.Client.WPF.Properties; +using Pithos.Interfaces; +using Pithos.Network; +using MessageBox = System.Windows.MessageBox; +using Screen = Caliburn.Micro.Screen; + +namespace Pithos.Client.WPF.Preferences +{ + [Export(typeof(AddAccountViewModel))] + public class AddAccountViewModel:Screen + { + + private readonly List _servers; + + public List Servers + { + get { return Settings.Default.Servers.Select(server => server.ServerUri).ToList(); } + } + + private bool _isValidServer; + public bool IsValidServer + { + get { return _isValidServer; } + set + { + _isValidServer = value; + NotifyOfPropertyChange(()=>IsValidServer); + } + } + + + private string _currentServer; + public string CurrentServer + { + get { return _currentServer; } + set + { + if (!Uri.IsWellFormedUriString(value, UriKind.Absolute)) + { + IsValidServer = false; + throw new UriFormatException(); + } + _currentServer = value; + IsValidServer = true; + HasValidCredentials = false; + IsConfirmed = false; + NotifyOfPropertyChange(()=>CurrentServer); + } + } + + private string _accountName; + public string AccountName + { + get { return _accountName; } + set + { + _accountName = value; + NotifyOfPropertyChange(()=>AccountName); + NotifyOfPropertyChange(() => HasCredentials); + } + } + + private string _token; + public string Token + { + get { return _token; } + set + { + _token = value; + NotifyOfPropertyChange(()=>Token); + NotifyOfPropertyChange(() => HasCredentials); + } + } + + private string _accountPath; + public string AccountPath + { + get { return _accountPath; } + set + { + _accountPath = value; + NotifyOfPropertyChange(() => AccountPath); + NotifyOfPropertyChange(() => HasAccountPath); + } + } + + + public bool HasAccountPath + { + get { return !String.IsNullOrWhiteSpace(AccountPath); } + } + + public bool HasCredentials + { + get { return !(String.IsNullOrWhiteSpace(AccountName) || String.IsNullOrWhiteSpace(Token) ) ; } + } + + + private bool _isConfirmed; + + public bool IsConfirmed + { + get { return _isConfirmed; } + set + { + _isConfirmed = value; + HasValidCredentials = false; + NotifyOfPropertyChange(() => IsConfirmed); + } + } + + + private bool _isAccountActive; + + public bool IsAccountActive + { + get { return _isAccountActive; } + set + { + _isAccountActive = value; + NotifyOfPropertyChange(() => IsAccountActive); + } + } + + + private bool _shouldCreateOkeanosFolder; + public bool ShouldCreateOkeanosFolder + { + get { return _shouldCreateOkeanosFolder; } + set + { + _shouldCreateOkeanosFolder = value; + NotifyOfPropertyChange(()=>ShouldCreateOkeanosFolder); + } + } + + bool HasNoParentWithCache(DirectoryInfo info) + { + if (info == null) + return false; + var parent = info.Parent; + if (parent == null) + return true; + if (parent.EnumerateDirectories(FolderConstants.CacheFolder).Any()) + { + return false; + } + return HasNoParentWithCache(parent); + } + + string GetLowestAboveCache(string path) + { + if (String.IsNullOrWhiteSpace(path)) + return null; + var info = new DirectoryInfo(path); + if (!info.Exists) + return path; + while (!HasNoParentWithCache(info)) + { + info = info.Parent; + } + if (info == null) + return null; + else + return info.FullName; + } + + public void SelectAccount() + { + using (var dlg = new FolderBrowserDialog{Description=Resources.AddAccountViewModel_SelectAccount_Please_select_a_folder}) + { + //Ask the user to select a folder + //Note: We need a parent window here, which we retrieve with GetView + var view = (Window)GetView(); + if (DialogResult.OK != dlg.ShowDialog(new Wpf32Window(view))) + return; + + //If the selected path is under an account folder, ie a folder containint a .pithos.cache subfolder, try to locate + //the cache's parent folder + var selectedPath = GetLowestAboveCache(dlg.SelectedPath); + + //If the path ends with /okeanos#/pithos, replace with /okeanos# + var match = _okeanosRegex.Match(selectedPath); + if (match.Success) + { + selectedPath = match.Groups[1].Value; + } + + AccountPath= selectedPath; + + + + ShouldCreateOkeanosFolder=Directory.EnumerateFileSystemEntries(AccountPath).Any(); + } + } + + + public void RetrieveCredentials() + { + SetBusy("Waiting for credentials.", "Please enter your credentials in the Pithos logon page"); + IsConfirmed = false; + + try + { + var loginUri=new Uri(new Uri(CurrentServer) , "login"); + var credentials = PithosAccount.RetrieveCredentials(loginUri.ToString()); + if (credentials == null) + return; + AccountName = credentials.UserName; + Token = credentials.Password; + + IsConfirmed = true; + + } + catch (PithosException exc) + { + ClearBusy(); + MessageBox.Show(exc.Message, "Unable to retrieve credentials"); + } + catch (Exception exc) + { + IsConfirmed = false; + MessageBox.Show(exc.ToString(), "Error"); + throw; + } + finally + { + ClearBusy(); + + ((Window) GetView()).Activate(); + } + + if (IsConfirmed) + TaskEx.Run(()=>TestAccount()); + + } + + public AddAccountViewModel() + { + _servers=new List + { + Settings.Default.ProductionServer, + Settings.Default.DevelopmentServer + }; + CurrentServer = _servers[0]; + DisplayName = "Add Pithos+ Account"; + } + + private bool _hasValidCredentials; + public bool HasValidCredentials + { + get { return _hasValidCredentials; } + set + { + _hasValidCredentials = value; + NotifyOfPropertyChange(()=>HasValidCredentials); + } + } + + private string _validationMessage; + public string ValidationMessage + { + get { return _validationMessage; } + set + { + _validationMessage = value; + NotifyOfPropertyChange(()=>ValidationMessage); + } + } + + private bool _isWorking; + public bool IsWorking + { + get { return _isWorking; } + set + { + _isWorking = value; + NotifyOfPropertyChange(()=>IsWorking); + } + } + + private string _busyTitle; + public string BusyTitle + { + get { return _busyTitle; } + set + { + _busyTitle = value; + NotifyOfPropertyChange(()=>BusyTitle); + } + } + + private string _busyDetail; + private Regex _okeanosRegex = new Regex(@"(.*okeanos[0-9]*)\\pithos$"); + + public string BusyDetail + { + get { return _busyDetail; } + set + { + _busyDetail = value; + NotifyOfPropertyChange(()=>BusyDetail); + } + } + + private void SetBusy(string title,string detail) + { + IsWorking = true; + BusyTitle = title; + BusyDetail = detail; + } + + private void ClearBusy() + { + IsWorking = false; + BusyTitle = ""; + BusyDetail = ""; + + } + + public async Task TestAccount() + { + try + { + SetBusy("Validating Credentials", ""); + var client = new CloudFilesClient(AccountName, Token) { AuthenticationUrl = CurrentServer,/*Proxy=Proxy */}; + await TaskEx.Run(() => + { + client.Authenticate(); + return client.ListContainers(AccountName); + }).ConfigureAwait(false); + HasValidCredentials = true; + ValidationMessage = "Credentials Validated"; + } + catch + { + HasValidCredentials = false; + MessageBox.Show("The account is not valid", "Account Error", MessageBoxButton.OK, MessageBoxImage.Stop); + ValidationMessage = "Credentials validation failed"; + } + finally + { + ClearBusy(); + } + } + + } +} diff --git a/trunk/Pithos.Client.WPF/packages.config b/trunk/Pithos.Client.WPF/packages.config index 428eb02..03d9443 100644 --- a/trunk/Pithos.Client.WPF/packages.config +++ b/trunk/Pithos.Client.WPF/packages.config @@ -1,7 +1,8 @@ - - - - - - + + + + + + + \ No newline at end of file diff --git a/trunk/Pithos.Core.Test/Pithos.Core.Test.csproj b/trunk/Pithos.Core.Test/Pithos.Core.Test.csproj index 57d16b3..79788b9 100644 --- a/trunk/Pithos.Core.Test/Pithos.Core.Test.csproj +++ b/trunk/Pithos.Core.Test/Pithos.Core.Test.csproj @@ -1,258 +1,258 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F9AF3E97-BCB7-46B7-8014-7FC858AEE9BA} - Library - Properties - Pithos.Core.Test - Pithos.Core.Test - v4.0 - 512 - 0 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True - False - True - False - False - False - False - False - False - False - False - True - False - False - False - - - - - - - False - Full - %28none%29 - 0 - x86 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Test\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\Premium Debug\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - - - true - bin\Debug All\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - bin\Release\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - true - bin\x64\Test\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Premium Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - true - bin\x64\Debug All\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - - ..\Libraries\AsyncCtpLibrary.dll - - - - ..\Libraries\Castle.Core.dll - - - ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll - - - ..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll - - - ..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {A9AE40FF-1A21-414A-9FE7-3BE13644CC6D} - Newtonsoft.Json - - - {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} - ParallelExtensionsExtras - - - {142AF135-DF30-4563-B0AC-B604235AE874} - Pithos.Core - - - {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} - Pithos.Interfaces - - - {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} - Pithos.Network - - - - - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {F9AF3E97-BCB7-46B7-8014-7FC858AEE9BA} + Library + Properties + Pithos.Core.Test + Pithos.Core.Test + v4.0 + 512 + 0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + False + True + False + False + False + False + False + False + False + False + True + False + False + False + + + + + + + False + Full + %28none%29 + 0 + x86 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Test\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\Premium Debug\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + true + bin\Debug All\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + true + bin\x64\Test\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Premium Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + true + bin\x64\Debug All\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + + + ..\Libraries\Castle.Core.dll + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll + + + ..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll + + + ..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {A9AE40FF-1A21-414A-9FE7-3BE13644CC6D} + Newtonsoft.Json + + + {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} + ParallelExtensionsExtras + + + {142AF135-DF30-4563-B0AC-B604235AE874} + Pithos.Core + + + {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} + Pithos.Interfaces + + + {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} + Pithos.Network + + + + + + + + + + + \ No newline at end of file diff --git a/trunk/Pithos.Core.Test/packages.config b/trunk/Pithos.Core.Test/packages.config index 0c82178..2fca03c 100644 --- a/trunk/Pithos.Core.Test/packages.config +++ b/trunk/Pithos.Core.Test/packages.config @@ -1,4 +1,5 @@ - - - + + + + \ No newline at end of file diff --git a/trunk/Pithos.Core/Agents/StatusAgent.cs b/trunk/Pithos.Core/Agents/StatusAgent.cs index f6cc452..63f1b3b 100644 --- a/trunk/Pithos.Core/Agents/StatusAgent.cs +++ b/trunk/Pithos.Core/Agents/StatusAgent.cs @@ -1,1029 +1,1034 @@ -#region -/* ----------------------------------------------------------------------- - * - * - * 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. - * - * ----------------------------------------------------------------------- - */ -#endregion -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Data; -using System.Data.SQLite; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Castle.ActiveRecord; -using Castle.ActiveRecord.Framework; -using Castle.ActiveRecord.Framework.Config; -using Castle.ActiveRecord.Queries; -using NHibernate; -using NHibernate.ByteCode.Castle; -using NHibernate.Cfg; -using NHibernate.Cfg.Loquacious; -using NHibernate.Dialect; -using NHibernate.Exceptions; -using Pithos.Interfaces; -using Pithos.Network; -using log4net; -using Environment = System.Environment; - -namespace Pithos.Core.Agents -{ - [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))] - public class StatusAgent:IStatusChecker,IStatusKeeper - { - private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - [System.ComponentModel.Composition.Import] - public IPithosSettings Settings { get; set; } - - [System.ComponentModel.Composition.Import] - public IStatusNotification StatusNotification { get; set; } - - private Agent _persistenceAgent; - - - - public StatusAgent() - { - var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - - _pithosDataPath = Path.Combine(appDataPath , "GRNET\\PITHOS"); - if (!Directory.Exists(_pithosDataPath)) - Directory.CreateDirectory(_pithosDataPath); - - var dbPath = Path.Combine(_pithosDataPath, "pithos.db"); - - MigrateOldDb(dbPath, appDataPath); - - - var source = GetConfiguration(_pithosDataPath); - ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag)); - - UpgradeDatabase(); - - - - if (!File.Exists(dbPath)) - ActiveRecordStarter.CreateSchema(); - - CreateTrigger(); - - } - - - private static void MigrateOldDb(string dbPath, string appDataPath) - { - if(String.IsNullOrWhiteSpace(dbPath)) - throw new ArgumentNullException("dbPath"); - if(String.IsNullOrWhiteSpace(appDataPath)) - throw new ArgumentNullException("appDataPath"); - Contract.EndContractBlock(); - - var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db"); - var oldDbInfo = new FileInfo(oldDbPath); - if (oldDbInfo.Exists && !File.Exists(dbPath)) - { - Log.InfoFormat("Moving database from {0} to {1}",oldDbInfo.FullName,dbPath); - var oldDirectory = oldDbInfo.Directory; - oldDbInfo.MoveTo(dbPath); - - if (Log.IsDebugEnabled) - Log.DebugFormat("Deleting {0}",oldDirectory.FullName); - - oldDirectory.Delete(true); - } - } - - private T? GetNull(string commandText,SQLiteConnection connection) where T:struct - { - using (var command= new SQLiteCommand(commandText, connection)) - { - var result = command.ExecuteScalar(); - if (result == null) - return null; - return (T)result; - } - } - - private T Get(string commandText,SQLiteConnection connection) - { - using (var command= new SQLiteCommand(commandText, connection)) - { - var result = command.ExecuteScalar(); - if (result == null) - return default(T); - return (T)result; - } - } - - private int Run(string commandText,SQLiteConnection connection) - { - using (var command= new SQLiteCommand(commandText, connection)) - { - var result=command.ExecuteNonQuery(); - return result; - } - } - - private void UpgradeDatabase() - { - const string hasVersionText = "select 1 from sqlite_master where name='Version'"; - - const string hasFilestateText = "select 1 from sqlite_master where name='FileState'"; - - const string getVersionCmd = "select Version from version where Id=1"; - - const string createVersionCmd = "create table Version(Id integer,Version TEXT);\n" + - "INSERT INTO VERSION (Id,Version) VALUES(1,'0.0.0.0');"; - const string createFileStateCmd = - "CREATE TABLE FileState (Id UNIQUEIDENTIFIER not null, ObjectID TEXT COLLATE NOCASE, FilePath TEXT unique COLLATE NOCASE, OverlayStatus INTEGER, FileStatus INTEGER, ConflictReason TEXT, Checksum TEXT COLLATE NOCASE, ETag TEXT not null COLLATE NOCASE, LastMD5 TEXT not null COLLATE NOCASE, LastWriteDate DATETIME, LastLength INTEGER, Version INTEGER, VersionTimeStamp DATETIME, IsShared INTEGER, SharedBy TEXT, ShareWrite INTEGER, IsFolder INTEGER, Modified DATETIME, primary key (Id),unique (FilePath))"; - const string upgradeText = "PRAGMA writable_schema = 1;\n" + - "UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE FileState (Id UNIQUEIDENTIFIER not null, ObjectID TEXT COLLATE NOCASE, FilePath TEXT unique COLLATE NOCASE, OverlayStatus INTEGER, FileStatus INTEGER, ConflictReason TEXT, Checksum TEXT COLLATE NOCASE, ETag TEXT not null COLLATE NOCASE, LastMD5 TEXT not null COLLATE NOCASE, LastWriteDate DATETIME, LastLength INTEGER, Version INTEGER, VersionTimeStamp DATETIME, IsShared INTEGER, SharedBy TEXT, ShareWrite INTEGER, IsFolder INTEGER, Modified DATETIME, primary key (Id),unique (FilePath))' WHERE NAME = 'FileState';\n" + - "PRAGMA writable_schema = 0;\n" + - "VACUUM;"; - - using (var connection = GetConnection()) - { - var hasVersion = false; - hasVersion = GetNull(hasVersionText, connection).HasValue; - - var storedVersion = new Version(); - - if (hasVersion) - { - var versionTxt = Get(getVersionCmd, connection); - storedVersion = new Version(versionTxt); - } - else - Run(createVersionCmd, connection); - - var hasFileState = false; - hasFileState = GetNull(hasFilestateText, connection).HasValue; - if (!hasFileState) - { - Run(createFileStateCmd, connection); - } - - var actualVersion = Assembly.GetEntryAssembly().GetName().Version; - if (!hasVersion || actualVersion > storedVersion) - Run(upgradeText, connection); - - if (actualVersion != storedVersion) - using (var updateVersionCmd = new SQLiteCommand("UPDATE VERSION SET Version=:version where ID=1", - connection)) - { - updateVersionCmd.Parameters.AddWithValue(":version", actualVersion.ToString()); - var result = updateVersionCmd.ExecuteNonQuery(); - Debug.Assert(result > 0); - } - } - } - - private void CreateTrigger() - { - using (var connection = GetConnection()) - using (var triggerCommand = connection.CreateCommand()) - { - var cmdText = new StringBuilder() - .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW") - .AppendLine("BEGIN") - .AppendLine("UPDATE FileState SET Modified=datetime('now') WHERE Id=old.Id;") - .AppendLine("END;") - .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW") - .AppendLine("BEGIN") - .AppendLine("UPDATE FileState SET Modified=datetime('now') WHERE Id=new.Id;") - .AppendLine("END;") - .ToString(); - triggerCommand.CommandText = cmdText; - triggerCommand.ExecuteNonQuery(); - } - } - - - private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath) - { - if (String.IsNullOrWhiteSpace(pithosDbPath)) - throw new ArgumentNullException("pithosDbPath"); - if (!Path.IsPathRooted(pithosDbPath)) - throw new ArgumentException("path must be a rooted path", "pithosDbPath"); - Contract.EndContractBlock(); - - var properties = new Dictionary - { - {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"}, - {"dialect", "NHibernate.Dialect.SQLiteDialect"}, - {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"}, - { - "proxyfactory.factory_class", - "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" - }, - }; - - var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath); - properties.Add("connection.connection_string", connectionString); - - var source = new InPlaceConfigurationSource(); - source.Add(typeof (ActiveRecordBase), properties); - source.SetDebugFlag(false); - return source; - } - - public void StartProcessing(CancellationToken token) - { - _persistenceAgent = Agent.Start(queue => - { - Action loop = null; - loop = () => - { - var job = queue.Receive(); - job.ContinueWith(t => - { - var action = job.Result; - try - { - action(); - } - catch (SQLiteException ex) - { - Log.ErrorFormat("[ERROR] SQL \n{0}", ex); - } - catch (Exception ex) - { - Log.ErrorFormat("[ERROR] STATE \n{0}", ex); - } - queue.NotifyComplete(action); -// ReSharper disable AccessToModifiedClosure - queue.DoAsync(loop); -// ReSharper restore AccessToModifiedClosure - }); - }; - loop(); - }); - - } - - - - public void Stop() - { - _persistenceAgent.Stop(); - } - - - public void ProcessExistingFiles(IEnumerable existingFiles) - { - if (existingFiles == null) - throw new ArgumentNullException("existingFiles"); - Contract.EndContractBlock(); - - //Find new or matching files with a left join to the stored states - var fileStates = FileState.Queryable.ToList(); - var currentFiles = from file in existingFiles - join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into - gs - from substate in gs.DefaultIfEmpty() - select Tuple.Create(file, substate); - - //To get the deleted files we must get the states that have no corresponding - //files. - //We can't use the File.Exists method inside a query, so we get all file paths from the states - var statePaths = (from state in fileStates - select new {state.Id, state.FilePath}).ToList(); - //and check each one - var missingStates = (from path in statePaths - where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath) - select path.Id).ToList(); - //Finally, retrieve the states that correspond to the deleted files - var deletedFiles = from state in fileStates - where missingStates.Contains(state.Id) - select Tuple.Create(default(FileInfo), state); - - var pairs = currentFiles.Union(deletedFiles).ToList(); - - i = 1; - var total = pairs.Count; - foreach (var pair in pairs) - { - ProcessFile(total, pair); - } - } - - int i = 1; - - private void ProcessFile(int total, Tuple pair) - { - var idx = Interlocked.Increment(ref i); - using (StatusNotification.GetNotifier("Indexing file {0} of {1}", "Indexed file {0} of {1} ", idx, total)) - { - var fileState = pair.Item2; - var file = pair.Item1; - if (fileState == null) - { - //This is a new file - var createState = FileState.CreateFor(file,StatusNotification); - _persistenceAgent.Post(createState.Create); - } - else if (file == null) - { - //This file was deleted while we were down. We should mark it as deleted - //We have to go through UpdateStatus here because the state object we are using - //was created by a different ORM session. - _persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Deleted)); - } - else - { - //This file has a matching state. Need to check for possible changes - //To check for changes, we use the cheap (in CPU terms) MD5 algorithm - //on the entire file. - - var hashString = file.ComputeShortHash(StatusNotification); - Debug.Assert(hashString.Length==32); - - - //TODO: Need a way to attach the hashes to the filestate so we don't - //recalculate them each time a call to calculate has is made - //We can either store them to the filestate or add them to a - //dictionary - - //If the hashes don't match the file was changed - if (fileState.ETag != hashString) - { - _persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Modified)); - } - } - } - } - - - private int UpdateStatusDirect(Guid id, FileStatus status) - { - using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) - { - - try - { - using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) - { - var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); - walquery.List(); - - //var updatecmd = session.CreateSQLQuery( - var updatecmd = session.CreateQuery( - "update FileState set FileStatus= :fileStatus where Id = :id ") - .SetGuid("id", id) - .SetEnum("fileStatus", status); - var affected = updatecmd.ExecuteUpdate(); - - return affected; - } - - } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } - } - - private int UpdateStatusDirect(string path, FileStatus status) - { - using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) - { - - try - { - using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) - using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted)) - { - - //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); - var walquery = session.CreateQuery("PRAGMA journal_mode=WAL"); - walquery.List(); - - //var updatecmd = session.CreateSQLQuery( - var updatecmd = session.CreateQuery( - "update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE") - .SetString("path", path) - .SetEnum("fileStatus", status); - var affected = updatecmd.ExecuteUpdate(); - - if (affected == 0) - { - var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(path), StatusNotification); - createdState.FileStatus = status; - session.Save(createdState); - } - tx.Commit(); - return affected; - } - } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } - } - - private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason) - { - using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) - { - - try - { - - using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) - using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted)) - { - - //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); - var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); - walquery.List(); - - - //var updatecmd = session.CreateSQLQuery("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path COLLATE NOCASE") - var updatecmd = session.CreateQuery("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path") - .SetString("path", absolutePath) - .SetEnum("fileStatus", fileStatus) - .SetEnum("overlayStatus", overlayStatus) - .SetString("conflictReason", conflictReason); - var affected = updatecmd.ExecuteUpdate(); - - if (affected == 0) - { - var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(absolutePath), StatusNotification); - createdState.FileStatus = fileStatus; - createdState.OverlayStatus = overlayStatus; - createdState.ConflictReason = conflictReason; - createdState.LastMD5 = String.Empty; - session.Save(createdState); - //createdState.Create(); - } - tx.Commit(); - return affected; - } - } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } - } - - - - public string BlockHash { get; set; } - - public int BlockSize { get; set; } - public void ChangeRoots(string oldPath, string newPath) - { - if (String.IsNullOrWhiteSpace(oldPath)) - throw new ArgumentNullException("oldPath"); - if (!Path.IsPathRooted(oldPath)) - throw new ArgumentException("oldPath must be an absolute path", "oldPath"); - if (string.IsNullOrWhiteSpace(newPath)) - throw new ArgumentNullException("newPath"); - if (!Path.IsPathRooted(newPath)) - throw new ArgumentException("newPath must be an absolute path", "newPath"); - Contract.EndContractBlock(); - - FileState.ChangeRootPath(oldPath,newPath); - - } - - - - private readonly string _pithosDataPath; - - - public FileState GetStateByFilePath(string path) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - try - { - - using (var connection = GetConnection()) - using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,ETag,Version ,VersionTimeStamp,IsShared ,SharedBy ,ShareWrite, LastMD5,LastLength,LastWriteDate from FileState where FilePath=:path COLLATE NOCASE", connection)) - { - - command.Parameters.AddWithValue("path", path); - - using (var reader = command.ExecuteReader()) - { - if (reader.Read()) - { - //var values = new object[reader.FieldCount]; - //reader.GetValues(values); - var state = new FileState - { - Id = reader.GetGuid(0), - FilePath = reader.IsDBNull(1)?"":reader.GetString(1), - OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2), - FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3), - Checksum = reader.IsDBNull(4)?"":reader.GetString(4), - ETag= reader.IsDBNull(5)?"":reader.GetString(5), - Version = reader.IsDBNull(6)?default(long):reader.GetInt64(6), - VersionTimeStamp = reader.IsDBNull(7)?default(DateTime):reader.GetDateTime(7), - IsShared = !reader.IsDBNull(8) && reader.GetBoolean(8), - SharedBy = reader.IsDBNull(9)?"":reader.GetString(9), - ShareWrite = !reader.IsDBNull(10) && reader.GetBoolean(10), - LastMD5=reader.GetString(11), - LastLength=reader.IsDBNull(12)? default(long):reader.GetInt64(12), - LastWriteDate=reader.IsDBNull(13)?default(DateTime):reader.GetDateTime(13) - }; - - return state; - } - else - { - return null; - } - - } - } - } - catch (Exception exc) - { - Log.ErrorFormat(exc.ToString()); - throw; - } - } - - public FileOverlayStatus GetFileOverlayStatus(string path) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - try - { - - using (var connection = GetConnection()) - using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path COLLATE NOCASE", connection)) - { - - command.Parameters.AddWithValue("path", path); - - var s = command.ExecuteScalar(); - return (FileOverlayStatus) Convert.ToInt32(s); - } - } - catch (Exception exc) - { - Log.ErrorFormat(exc.ToString()); - return FileOverlayStatus.Unversioned; - } - } - - private string GetConnectionString() - { - var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath); - return connectionString; - } - - private SQLiteConnection GetConnection() - { - var connectionString = GetConnectionString(); - var connection = new SQLiteConnection(connectionString); - connection.Open(); - using(var cmd =connection.CreateCommand()) - { - cmd.CommandText = "PRAGMA journal_mode=WAL"; - cmd.ExecuteNonQuery(); - } - return connection; - } - - /* public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted","path"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus)); - }*/ - - public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string etag = null) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted","path"); - Contract.EndContractBlock(); - - return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,etag)); - } - - /* public void RenameFileOverlayStatus(string oldPath, string newPath) - { - if (String.IsNullOrWhiteSpace(oldPath)) - throw new ArgumentNullException("oldPath"); - if (!Path.IsPathRooted(oldPath)) - throw new ArgumentException("The oldPath must be rooted", "oldPath"); - if (String.IsNullOrWhiteSpace(newPath)) - throw new ArgumentNullException("newPath"); - if (!Path.IsPathRooted(newPath)) - throw new ArgumentException("The newPath must be rooted", "newPath"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath)); - }*/ - - public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - Debug.Assert(!path.Contains(FolderConstants.CacheFolder)); - Debug.Assert(!path.EndsWith(".ignore")); - - _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus, conflictReason)); - } - - - public void StoreInfo(string path, ObjectInfo objectInfo) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - if (objectInfo == null) - throw new ArgumentNullException("objectInfo", "objectInfo can't be empty"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo)); - - } - - private void StoreInfoDirect(string path, ObjectInfo objectInfo) - { - try - { - using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) - using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted)) - { - - //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); - var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); - walquery.List(); - - //An entry for the new path may exist, - IQuery deletecmd = session.CreateQuery( - "delete from FileState where FilePath=:path and ObjectID is null") - .SetString("path",path); - deletecmd.ExecuteUpdate(); - - - Func setCriteria = q => q - .SetString("path", path) - .SetString("checksum",objectInfo.X_Object_Hash) - .SetString("etag", objectInfo.ETag) - .SetInt64("version", objectInfo.Version.GetValueOrDefault()) - .SetDateTime("versionTimeStamp",objectInfo.VersionTimestamp.GetValueOrDefault()) - .SetEnum("fileStatus", FileStatus.Unchanged) - .SetEnum("overlayStatus",FileOverlayStatus.Normal) - .SetString("objectID", objectInfo.UUID); - //IQuery updatecmd = session.CreateSQLQuery( - IQuery updatecmd = session.CreateQuery( - "update FileState set FilePath=:path,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where ObjectID = :objectID "); - updatecmd = setCriteria(updatecmd); - var affected = updatecmd.ExecuteUpdate(); - - //If the ID exists, update the status - if (affected == 0) - { - //If the ID doesn't exist, try to update using the path, and store the ID as well. - //updatecmd = session.CreateSQLQuery( - updatecmd = session.CreateQuery( - // "update FileState set FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path COLLATE NOCASE "); - "update FileState set FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path"); - updatecmd=setCriteria(updatecmd); - affected = updatecmd.ExecuteUpdate(); - } - if (affected==0) - { - //IQuery insertCmd=session.CreateSQLQuery( - IQuery insertCmd = session.CreateSQLQuery( - "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ETag,LastMD5,FileStatus,OverlayStatus,ObjectID) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:etag,:etag,:fileStatus,:overlayStatus,:objectID)"); - insertCmd=setCriteria(insertCmd).SetGuid("id", Guid.NewGuid()); - affected = insertCmd.ExecuteUpdate(); - } - tx.Commit(); - } - } - catch (Exception exc) - { - Log.ErrorFormat("Failed to update [{0}]:[{1}]\r\n{2}",path,objectInfo.UUID, exc); - throw; - } - } - - private bool StateExists(string filePath,SQLiteConnection connection) - { - using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path COLLATE NOCASE", connection)) - { - command.Parameters.AddWithValue("path", filePath); - var result = command.ExecuteScalar(); - return ((long)result >= 1); - } - - } - - private bool StateExistsByID(string objectId,SQLiteConnection connection) - { - using (var command = new SQLiteCommand("Select count(*) from FileState where ObjectId=:id", connection)) - { - command.Parameters.AddWithValue("id", objectId); - var result = command.ExecuteScalar(); - return ((long)result >= 1); - } - - } - - public void SetFileStatus(string path, FileStatus status) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() => UpdateStatusDirect(path, status)); - } - - public FileStatus GetFileStatus(string path) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - - using (var connection = GetConnection()) - { - var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path COLLATE NOCASE", connection); - command.Parameters.AddWithValue("path", path); - - var statusValue = command.ExecuteScalar(); - if (statusValue==null) - return FileStatus.Missing; - return (FileStatus)Convert.ToInt32(statusValue); - } - } - - /// - /// Deletes the status of the specified file - /// - /// - public void ClearFileStatus(string path) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() => DeleteDirect(path)); - } - - /// - /// Deletes the status of the specified folder and all its contents - /// - /// - public void ClearFolderStatus(string path) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - //TODO: May throw if the agent is cleared for some reason. Should never happen - _persistenceAgent.Post(() => DeleteFolderDirect(path)); - } - - public IEnumerable GetChildren(FileState fileState) - { - if (fileState == null) - throw new ArgumentNullException("fileState"); - Contract.EndContractBlock(); - - var children = from state in FileState.Queryable - where state.FilePath.StartsWith(fileState.FilePath + "\\") - select state; - return children; - } - - public void EnsureFileState(string path) - { - var existingState = GetStateByFilePath(path); - if (existingState != null) - return; - var fileInfo = FileInfoExtensions.FromPath(path); - using (new SessionScope()) - { - var newState = FileState.CreateFor(fileInfo,StatusNotification); - newState.FileStatus=FileStatus.Missing; - _persistenceAgent.PostAndAwait(newState.CreateAndFlush).Wait(); - } - - } - - private int DeleteDirect(string filePath) - { - using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) - { - - try - { - - - using (var connection = GetConnection()) - { - var command = new SQLiteCommand("delete from FileState where FilePath = :path COLLATE NOCASE", - connection); - - command.Parameters.AddWithValue("path", filePath); - - var affected = command.ExecuteNonQuery(); - return affected; - } - } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } - } - - private int DeleteFolderDirect(string filePath) - { - using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) - { - - try - { - - - using (var connection = GetConnection()) - { - var command = new SQLiteCommand(@"delete from FileState where FilePath = :path or FilePath like :path || '\%' COLLATE NOCASE", - connection); - - command.Parameters.AddWithValue("path", filePath); - - var affected = command.ExecuteNonQuery(); - return affected; - } - } - catch (Exception exc) - { - Log.Error(exc.ToString()); - throw; - } - } - } - - public void UpdateFileChecksum(string path, string etag, string checksum) - { - if (String.IsNullOrWhiteSpace(path)) - throw new ArgumentNullException("path"); - if (!Path.IsPathRooted(path)) - throw new ArgumentException("The path must be rooted", "path"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() => FileState.UpdateChecksum(path, etag,checksum)); - } - - public void UpdateLastMD5(FileInfo file, string etag) - { - if (file==null) - throw new ArgumentNullException("file"); - if (String.IsNullOrWhiteSpace(etag)) - throw new ArgumentNullException("etag"); - Contract.EndContractBlock(); - - _persistenceAgent.Post(() => FileState.UpdateLastMD5(file, etag)); - } - - - public void CleanupOrphanStates() - { - //Orphan states are those that do not correspond to an account, ie. their paths - //do not start with the root path of any registered account - - var roots=(from account in Settings.Accounts - select account.RootPath).ToList(); - - var allStates = from state in FileState.Queryable - select state.FilePath; - - foreach (var statePath in allStates) - { - if (!roots.Any(root=>statePath.StartsWith(root,StringComparison.InvariantCultureIgnoreCase))) - this.DeleteDirect(statePath); - } - } - - public void CleanupStaleStates(AccountInfo accountInfo, List objectInfos) - { - if (accountInfo == null) - throw new ArgumentNullException("accountInfo"); - if (objectInfos == null) - throw new ArgumentNullException("objectInfos"); - Contract.EndContractBlock(); - - - - //Stale states are those that have no corresponding local or server file - - - var agent=FileAgent.GetFileAgent(accountInfo); - - var localFiles=agent.EnumerateFiles(); - var localSet = new HashSet(localFiles); - - //RelativeUrlToFilePath will fail for - //infos of accounts, containers which have no Name - - var serverFiles = from info in objectInfos - where info.Name != null - select Path.Combine(accountInfo.AccountPath,info.RelativeUrlToFilePath(accountInfo.UserName)); - var serverSet = new HashSet(serverFiles); - - var allStates = from state in FileState.Queryable - where state.FilePath.StartsWith(agent.RootPath) - select state.FilePath; - var stateSet = new HashSet(allStates); - stateSet.ExceptWith(serverSet); - stateSet.ExceptWith(localSet); - - foreach (var remainder in stateSet) - { - DeleteDirect(remainder); - } - - - } - } - - -} +#region +/* ----------------------------------------------------------------------- + * + * + * 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. + * + * ----------------------------------------------------------------------- + */ +#endregion +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Data; +using System.Data.SQLite; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Castle.ActiveRecord; +using Castle.ActiveRecord.Framework; +using Castle.ActiveRecord.Framework.Config; +using Castle.ActiveRecord.Queries; +using NHibernate; +using NHibernate.ByteCode.Castle; +using NHibernate.Cfg; +using NHibernate.Cfg.Loquacious; +using NHibernate.Dialect; +using NHibernate.Exceptions; +using Pithos.Interfaces; +using Pithos.Network; +using log4net; +using Environment = System.Environment; + +namespace Pithos.Core.Agents +{ + [Export(typeof(IStatusChecker)),Export(typeof(IStatusKeeper))] + public class StatusAgent:IStatusChecker,IStatusKeeper + { + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + [System.ComponentModel.Composition.Import] + public IPithosSettings Settings { get; set; } + + [System.ComponentModel.Composition.Import] + public IStatusNotification StatusNotification { get; set; } + + private Agent _persistenceAgent; + + + + public StatusAgent() + { + var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + _pithosDataPath = Path.Combine(appDataPath , "GRNET\\PITHOS"); + if (!Directory.Exists(_pithosDataPath)) + Directory.CreateDirectory(_pithosDataPath); + + var dbPath = Path.Combine(_pithosDataPath, "pithos.db"); + + MigrateOldDb(dbPath, appDataPath); + + + var source = GetConfiguration(_pithosDataPath); + ActiveRecordStarter.Initialize(source,typeof(FileState),typeof(FileTag)); + + UpgradeDatabase(); + + + + if (!File.Exists(dbPath)) + ActiveRecordStarter.CreateSchema(); + + CreateTrigger(); + + } + + + private static void MigrateOldDb(string dbPath, string appDataPath) + { + if(String.IsNullOrWhiteSpace(dbPath)) + throw new ArgumentNullException("dbPath"); + if(String.IsNullOrWhiteSpace(appDataPath)) + throw new ArgumentNullException("appDataPath"); + Contract.EndContractBlock(); + + var oldDbPath = Path.Combine(appDataPath, "Pithos", "pithos.db"); + var oldDbInfo = new FileInfo(oldDbPath); + if (oldDbInfo.Exists && !File.Exists(dbPath)) + { + Log.InfoFormat("Moving database from {0} to {1}",oldDbInfo.FullName,dbPath); + var oldDirectory = oldDbInfo.Directory; + oldDbInfo.MoveTo(dbPath); + + if (Log.IsDebugEnabled) + Log.DebugFormat("Deleting {0}",oldDirectory.FullName); + + oldDirectory.Delete(true); + } + } + + private T? GetNull(string commandText,SQLiteConnection connection) where T:struct + { + using (var command= new SQLiteCommand(commandText, connection)) + { + var result = command.ExecuteScalar(); + if (result == null) + return null; + return (T)result; + } + } + + private T Get(string commandText,SQLiteConnection connection) + { + using (var command= new SQLiteCommand(commandText, connection)) + { + var result = command.ExecuteScalar(); + if (result == null) + return default(T); + return (T)result; + } + } + + private int Run(string commandText,SQLiteConnection connection) + { + using (var command= new SQLiteCommand(commandText, connection)) + { + var result=command.ExecuteNonQuery(); + return result; + } + } + + private void UpgradeDatabase() + { + const string hasVersionText = "select 1 from sqlite_master where name='Version'"; + + const string hasFilestateText = "select 1 from sqlite_master where name='FileState'"; + + const string getVersionCmd = "select Version from version where Id=1"; + + const string createVersionCmd = "create table Version(Id integer,Version TEXT);\n" + + "INSERT INTO VERSION (Id,Version) VALUES(1,'0.0.0.0');"; + const string createFileStateCmd = + "CREATE TABLE FileState (Id UNIQUEIDENTIFIER not null, ObjectID TEXT COLLATE NOCASE, FilePath TEXT unique COLLATE NOCASE, OverlayStatus INTEGER, FileStatus INTEGER, ConflictReason TEXT, Checksum TEXT COLLATE NOCASE, ETag TEXT not null COLLATE NOCASE, LastMD5 TEXT not null COLLATE NOCASE, LastWriteDate DATETIME, LastLength INTEGER, Version INTEGER, VersionTimeStamp DATETIME, IsShared INTEGER, SharedBy TEXT, ShareWrite INTEGER, IsFolder INTEGER, Modified DATETIME, primary key (Id),unique (FilePath))"; + const string upgradeText = "PRAGMA writable_schema = 1;\n" + + "UPDATE SQLITE_MASTER SET SQL = 'CREATE TABLE FileState (Id UNIQUEIDENTIFIER not null, ObjectID TEXT COLLATE NOCASE, FilePath TEXT unique COLLATE NOCASE, OverlayStatus INTEGER, FileStatus INTEGER, ConflictReason TEXT, Checksum TEXT COLLATE NOCASE, ETag TEXT not null COLLATE NOCASE, LastMD5 TEXT not null COLLATE NOCASE, LastWriteDate DATETIME, LastLength INTEGER, Version INTEGER, VersionTimeStamp DATETIME, IsShared INTEGER, SharedBy TEXT, ShareWrite INTEGER, IsFolder INTEGER, Modified DATETIME, primary key (Id),unique (FilePath))' WHERE NAME = 'FileState';\n" + + "PRAGMA writable_schema = 0;\n" + + "VACUUM;"; + + using (var connection = GetConnection()) + { + var hasVersion = false; + hasVersion = GetNull(hasVersionText, connection).HasValue; + + var storedVersion = new Version(); + + if (hasVersion) + { + var versionTxt = Get(getVersionCmd, connection); + storedVersion = new Version(versionTxt); + } + else + Run(createVersionCmd, connection); + + var hasFileState = false; + hasFileState = GetNull(hasFilestateText, connection).HasValue; + if (!hasFileState) + { + Run(createFileStateCmd, connection); + } + + var actualVersion = Assembly.GetEntryAssembly().GetName().Version; + if (!hasVersion || actualVersion > storedVersion) + Run(upgradeText, connection); + + if (actualVersion != storedVersion) + using (var updateVersionCmd = new SQLiteCommand("UPDATE VERSION SET Version=:version where ID=1", + connection)) + { + updateVersionCmd.Parameters.AddWithValue(":version", actualVersion.ToString()); + var result = updateVersionCmd.ExecuteNonQuery(); + Debug.Assert(result > 0); + } + } + } + + private void CreateTrigger() + { + using (var connection = GetConnection()) + using (var triggerCommand = connection.CreateCommand()) + { + var cmdText = new StringBuilder() + .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW") + .AppendLine("BEGIN") + .AppendLine("UPDATE FileState SET Modified=datetime('now') WHERE Id=old.Id;") + .AppendLine("END;") + .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW") + .AppendLine("BEGIN") + .AppendLine("UPDATE FileState SET Modified=datetime('now') WHERE Id=new.Id;") + .AppendLine("END;") + .ToString(); + triggerCommand.CommandText = cmdText; + triggerCommand.ExecuteNonQuery(); + } + } + + + private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath) + { + if (String.IsNullOrWhiteSpace(pithosDbPath)) + throw new ArgumentNullException("pithosDbPath"); + if (!Path.IsPathRooted(pithosDbPath)) + throw new ArgumentException("path must be a rooted path", "pithosDbPath"); + Contract.EndContractBlock(); + + var properties = new Dictionary + { + {"connection.driver_class", "NHibernate.Driver.SQLite20Driver"}, + {"dialect", "NHibernate.Dialect.SQLiteDialect"}, + {"connection.provider", "NHibernate.Connection.DriverConnectionProvider"}, + { + "proxyfactory.factory_class", + "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" + }, + }; + + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath); + properties.Add("connection.connection_string", connectionString); + + var source = new InPlaceConfigurationSource(); + source.Add(typeof (ActiveRecordBase), properties); + source.SetDebugFlag(false); + return source; + } + + public void StartProcessing(CancellationToken token) + { + _persistenceAgent = Agent.Start(queue => + { + Action loop = null; + loop = () => + { + var job = queue.Receive(); + job.ContinueWith(t => + { + var action = job.Result; + try + { + action(); + } + catch (SQLiteException ex) + { + Log.ErrorFormat("[ERROR] SQL \n{0}", ex); + } + catch (Exception ex) + { + Log.ErrorFormat("[ERROR] STATE \n{0}", ex); + } + queue.NotifyComplete(action); +// ReSharper disable AccessToModifiedClosure + queue.DoAsync(loop); +// ReSharper restore AccessToModifiedClosure + }); + }; + loop(); + }); + + } + + + + public void Stop() + { + _persistenceAgent.Stop(); + } + + + public void ProcessExistingFiles(IEnumerable existingFiles) + { + if (existingFiles == null) + throw new ArgumentNullException("existingFiles"); + Contract.EndContractBlock(); + + //Find new or matching files with a left join to the stored states + var fileStates = FileState.Queryable.ToList(); + var currentFiles = from file in existingFiles + join state in fileStates on file.FullName.ToLower() equals state.FilePath.ToLower() into + gs + from substate in gs.DefaultIfEmpty() + select Tuple.Create(file, substate); + + //To get the deleted files we must get the states that have no corresponding + //files. + //We can't use the File.Exists method inside a query, so we get all file paths from the states + var statePaths = (from state in fileStates + select new {state.Id, state.FilePath}).ToList(); + //and check each one + var missingStates = (from path in statePaths + where !File.Exists(path.FilePath) && !Directory.Exists(path.FilePath) + select path.Id).ToList(); + //Finally, retrieve the states that correspond to the deleted files + var deletedFiles = from state in fileStates + where missingStates.Contains(state.Id) + select Tuple.Create(default(FileInfo), state); + + var pairs = currentFiles.Union(deletedFiles).ToList(); + + i = 1; + var total = pairs.Count; + foreach (var pair in pairs) + { + ProcessFile(total, pair); + } + } + + int i = 1; + + private void ProcessFile(int total, Tuple pair) + { + var idx = Interlocked.Increment(ref i); + using (StatusNotification.GetNotifier("Indexing file {0} of {1}", "Indexed file {0} of {1} ", idx, total)) + { + var fileState = pair.Item2; + var file = pair.Item1; + if (fileState == null) + { + //This is a new file + var createState = FileState.CreateFor(file,StatusNotification); + _persistenceAgent.Post(createState.Create); + } + else if (file == null) + { + //This file was deleted while we were down. We should mark it as deleted + //We have to go through UpdateStatus here because the state object we are using + //was created by a different ORM session. + _persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Deleted)); + } + else + { + //This file has a matching state. Need to check for possible changes + //To check for changes, we use the cheap (in CPU terms) MD5 algorithm + //on the entire file. + + var hashString = file.ComputeShortHash(StatusNotification); + Debug.Assert(hashString.Length==32); + + + //TODO: Need a way to attach the hashes to the filestate so we don't + //recalculate them each time a call to calculate has is made + //We can either store them to the filestate or add them to a + //dictionary + + //If the hashes don't match the file was changed + if (fileState.ETag != hashString) + { + _persistenceAgent.Post(() => UpdateStatusDirect((Guid) fileState.Id, FileStatus.Modified)); + } + } + } + } + + + private int UpdateStatusDirect(Guid id, FileStatus status) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) + { + + try + { + using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) + { + var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); + walquery.List(); + + //var updatecmd = session.CreateSQLQuery( + var updatecmd = session.CreateQuery( + "update FileState set FileStatus= :fileStatus where Id = :id ") + .SetGuid("id", id) + .SetEnum("fileStatus", status); + var affected = updatecmd.ExecuteUpdate(); + + return affected; + } + + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + private int UpdateStatusDirect(string path, FileStatus status) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) + { + + try + { + using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) + using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted)) + { + + //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); + var walquery = session.CreateQuery("PRAGMA journal_mode=WAL"); + walquery.List(); + + //var updatecmd = session.CreateSQLQuery( + var updatecmd = session.CreateQuery( + "update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE") + .SetString("path", path) + .SetEnum("fileStatus", status); + var affected = updatecmd.ExecuteUpdate(); + + if (affected == 0) + { + var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(path), StatusNotification); + createdState.FileStatus = status; + session.Save(createdState); + } + tx.Commit(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) + { + + try + { + + using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) + using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted)) + { + + //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); + var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); + walquery.List(); + + + //var updatecmd = session.CreateSQLQuery("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path COLLATE NOCASE") + var updatecmd = session.CreateQuery("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus,ConflictReason= :conflictReason where FilePath = :path") + .SetString("path", absolutePath) + .SetEnum("fileStatus", fileStatus) + .SetEnum("overlayStatus", overlayStatus) + .SetString("conflictReason", conflictReason); + var affected = updatecmd.ExecuteUpdate(); + + if (affected == 0) + { + var createdState = FileState.CreateFor(FileInfoExtensions.FromPath(absolutePath), StatusNotification); + createdState.FileStatus = fileStatus; + createdState.OverlayStatus = overlayStatus; + createdState.ConflictReason = conflictReason; + createdState.LastMD5 = String.Empty; + session.Save(createdState); + //createdState.Create(); + } + tx.Commit(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + + + public string BlockHash { get; set; } + + public int BlockSize { get; set; } + public void ChangeRoots(string oldPath, string newPath) + { + if (String.IsNullOrWhiteSpace(oldPath)) + throw new ArgumentNullException("oldPath"); + if (!Path.IsPathRooted(oldPath)) + throw new ArgumentException("oldPath must be an absolute path", "oldPath"); + if (string.IsNullOrWhiteSpace(newPath)) + throw new ArgumentNullException("newPath"); + if (!Path.IsPathRooted(newPath)) + throw new ArgumentException("newPath must be an absolute path", "newPath"); + Contract.EndContractBlock(); + + FileState.ChangeRootPath(oldPath,newPath); + + } + + + + private readonly string _pithosDataPath; + + + public FileState GetStateByFilePath(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + try + { + + using (var connection = GetConnection()) + using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,ETag,Version ,VersionTimeStamp,IsShared ,SharedBy ,ShareWrite, LastMD5,LastLength,LastWriteDate from FileState where FilePath=:path COLLATE NOCASE", connection)) + { + + command.Parameters.AddWithValue("path", path); + + using (var reader = command.ExecuteReader()) + { + if (reader.Read()) + { + //var values = new object[reader.FieldCount]; + //reader.GetValues(values); + var state = new FileState + { + Id = reader.GetGuid(0), + FilePath = reader.IsDBNull(1)?"":reader.GetString(1), + OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2), + FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3), + Checksum = reader.IsDBNull(4)?"":reader.GetString(4), + ETag= reader.IsDBNull(5)?"":reader.GetString(5), + Version = reader.IsDBNull(6)?default(long):reader.GetInt64(6), + VersionTimeStamp = reader.IsDBNull(7)?default(DateTime):reader.GetDateTime(7), + IsShared = !reader.IsDBNull(8) && reader.GetBoolean(8), + SharedBy = reader.IsDBNull(9)?"":reader.GetString(9), + ShareWrite = !reader.IsDBNull(10) && reader.GetBoolean(10), + LastMD5=reader.GetString(11), + LastLength=reader.IsDBNull(12)? default(long):reader.GetInt64(12), + LastWriteDate=reader.IsDBNull(13)?default(DateTime):reader.GetDateTime(13) + }; + + return state; + } + else + { + return null; + } + + } + } + } + catch (Exception exc) + { + Log.ErrorFormat(exc.ToString()); + throw; + } + } + + public FileOverlayStatus GetFileOverlayStatus(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + try + { + + using (var connection = GetConnection()) + using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path COLLATE NOCASE", connection)) + { + + command.Parameters.AddWithValue("path", path); + + var s = command.ExecuteScalar(); + return (FileOverlayStatus) Convert.ToInt32(s); + } + } + catch (Exception exc) + { + Log.ErrorFormat(exc.ToString()); + return FileOverlayStatus.Unversioned; + } + } + + private string GetConnectionString() + { + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True", _pithosDataPath); + return connectionString; + } + + private SQLiteConnection GetConnection() + { + var connectionString = GetConnectionString(); + var connection = new SQLiteConnection(connectionString); + connection.Open(); + using(var cmd =connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA journal_mode=WAL"; + cmd.ExecuteNonQuery(); + } + return connection; + } + + /* public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted","path"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus)); + }*/ + + public Task SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus, string etag = null) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted","path"); + Contract.EndContractBlock(); + + return _persistenceAgent.PostAndAwait(() => FileState.StoreOverlayStatus(path,overlayStatus,etag)); + } + + /* public void RenameFileOverlayStatus(string oldPath, string newPath) + { + if (String.IsNullOrWhiteSpace(oldPath)) + throw new ArgumentNullException("oldPath"); + if (!Path.IsPathRooted(oldPath)) + throw new ArgumentException("The oldPath must be rooted", "oldPath"); + if (String.IsNullOrWhiteSpace(newPath)) + throw new ArgumentNullException("newPath"); + if (!Path.IsPathRooted(newPath)) + throw new ArgumentException("The newPath must be rooted", "newPath"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() =>FileState.RenameState(oldPath, newPath)); + }*/ + + public void SetFileState(string path, FileStatus fileStatus, FileOverlayStatus overlayStatus, string conflictReason) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + Debug.Assert(!path.Contains(FolderConstants.CacheFolder)); + Debug.Assert(!path.EndsWith(".ignore")); + + _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus, conflictReason)); + } + + + public void StoreInfo(string path, ObjectInfo objectInfo) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + if (objectInfo == null) + throw new ArgumentNullException("objectInfo", "objectInfo can't be empty"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo)); + + } + + private void StoreInfoDirect(string path, ObjectInfo objectInfo) + { + try + { + using (var session = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileState))) + using (var tx=session.BeginTransaction(IsolationLevel.ReadCommitted)) + { + + //var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); + var walquery = session.CreateSQLQuery("PRAGMA journal_mode=WAL"); + walquery.List(); + + //An entry for the new path may exist, + IQuery deletecmd = session.CreateQuery( + "delete from FileState where FilePath=:path and ObjectID is null") + .SetString("path",path); + deletecmd.ExecuteUpdate(); + + + Func setCriteria = q => q + .SetString("path", path) + .SetString("checksum",objectInfo.X_Object_Hash) + .SetString("etag", objectInfo.ETag) + .SetInt64("version", objectInfo.Version.GetValueOrDefault()) + .SetDateTime("versionTimeStamp",objectInfo.VersionTimestamp.GetValueOrDefault()) + .SetEnum("fileStatus", FileStatus.Unchanged) + .SetEnum("overlayStatus",FileOverlayStatus.Normal) + .SetString("objectID", objectInfo.UUID); + //IQuery updatecmd = session.CreateSQLQuery( + IQuery updatecmd = session.CreateQuery( + "update FileState set FilePath=:path,FileStatus= :fileStatus,OverlayStatus= :overlayStatus, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where ObjectID = :objectID "); + updatecmd = setCriteria(updatecmd); + var affected = updatecmd.ExecuteUpdate(); + + //If the ID exists, update the status + if (affected == 0) + { + //If the ID doesn't exist, try to update using the path, and store the ID as well. + //updatecmd = session.CreateSQLQuery( + updatecmd = session.CreateQuery( + // "update FileState set FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path COLLATE NOCASE "); + "update FileState set FileStatus= :fileStatus,OverlayStatus= :overlayStatus, ObjectID=:objectID, Checksum=:checksum, ETag=:etag,LastMD5=:etag,Version=:version,VersionTimeStamp=:versionTimeStamp where FilePath = :path"); + updatecmd=setCriteria(updatecmd); + affected = updatecmd.ExecuteUpdate(); + } + if (affected==0) + { + //IQuery insertCmd=session.CreateSQLQuery( + IQuery insertCmd = session.CreateSQLQuery( + "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,ETag,LastMD5,FileStatus,OverlayStatus,ObjectID) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:etag,:etag,:fileStatus,:overlayStatus,:objectID)"); + insertCmd=setCriteria(insertCmd).SetGuid("id", Guid.NewGuid()); + affected = insertCmd.ExecuteUpdate(); + } + tx.Commit(); + } + } + catch (Exception exc) + { + Log.ErrorFormat("Failed to update [{0}]:[{1}]\r\n{2}",path,objectInfo.UUID, exc); + throw; + } + } + + private bool StateExists(string filePath,SQLiteConnection connection) + { + using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path COLLATE NOCASE", connection)) + { + command.Parameters.AddWithValue("path", filePath); + var result = command.ExecuteScalar(); + return ((long)result >= 1); + } + + } + + private bool StateExistsByID(string objectId,SQLiteConnection connection) + { + using (var command = new SQLiteCommand("Select count(*) from FileState where ObjectId=:id", connection)) + { + command.Parameters.AddWithValue("id", objectId); + var result = command.ExecuteScalar(); + return ((long)result >= 1); + } + + } + + public void SetFileStatus(string path, FileStatus status) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => UpdateStatusDirect(path, status)); + } + + public FileStatus GetFileStatus(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path COLLATE NOCASE", connection); + command.Parameters.AddWithValue("path", path); + + var statusValue = command.ExecuteScalar(); + if (statusValue==null) + return FileStatus.Missing; + return (FileStatus)Convert.ToInt32(statusValue); + } + } + + /// + /// Deletes the status of the specified file + /// + /// + public void ClearFileStatus(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => DeleteDirect(path)); + } + + /// + /// Deletes the status of the specified folder and all its contents + /// + /// + public void ClearFolderStatus(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + //The agent may be null when removing an invalid/expired account from Settings, in which case processing hasn't yet started. + if (_persistenceAgent == null) + //In this case remove the folder immediatelly + DeleteFolderDirect(path); + else + //Otherwise schedule a delete + _persistenceAgent.Post(() => DeleteFolderDirect(path)); + } + + public IEnumerable GetChildren(FileState fileState) + { + if (fileState == null) + throw new ArgumentNullException("fileState"); + Contract.EndContractBlock(); + + var children = from state in FileState.Queryable + where state.FilePath.StartsWith(fileState.FilePath + "\\") + select state; + return children; + } + + public void EnsureFileState(string path) + { + var existingState = GetStateByFilePath(path); + if (existingState != null) + return; + var fileInfo = FileInfoExtensions.FromPath(path); + using (new SessionScope()) + { + var newState = FileState.CreateFor(fileInfo,StatusNotification); + newState.FileStatus=FileStatus.Missing; + _persistenceAgent.PostAndAwait(newState.CreateAndFlush).Wait(); + } + + } + + private int DeleteDirect(string filePath) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) + { + + try + { + + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand("delete from FileState where FilePath = :path COLLATE NOCASE", + connection); + + command.Parameters.AddWithValue("path", filePath); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + private int DeleteFolderDirect(string filePath) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) + { + + try + { + + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand(@"delete from FileState where FilePath = :path or FilePath like :path || '\%' COLLATE NOCASE", + connection); + + command.Parameters.AddWithValue("path", filePath); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + public void UpdateFileChecksum(string path, string etag, string checksum) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => FileState.UpdateChecksum(path, etag,checksum)); + } + + public void UpdateLastMD5(FileInfo file, string etag) + { + if (file==null) + throw new ArgumentNullException("file"); + if (String.IsNullOrWhiteSpace(etag)) + throw new ArgumentNullException("etag"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => FileState.UpdateLastMD5(file, etag)); + } + + + public void CleanupOrphanStates() + { + //Orphan states are those that do not correspond to an account, ie. their paths + //do not start with the root path of any registered account + + var roots=(from account in Settings.Accounts + select account.RootPath).ToList(); + + var allStates = from state in FileState.Queryable + select state.FilePath; + + foreach (var statePath in allStates) + { + if (!roots.Any(root=>statePath.StartsWith(root,StringComparison.InvariantCultureIgnoreCase))) + this.DeleteDirect(statePath); + } + } + + public void CleanupStaleStates(AccountInfo accountInfo, List objectInfos) + { + if (accountInfo == null) + throw new ArgumentNullException("accountInfo"); + if (objectInfos == null) + throw new ArgumentNullException("objectInfos"); + Contract.EndContractBlock(); + + + + //Stale states are those that have no corresponding local or server file + + + var agent=FileAgent.GetFileAgent(accountInfo); + + var localFiles=agent.EnumerateFiles(); + var localSet = new HashSet(localFiles); + + //RelativeUrlToFilePath will fail for + //infos of accounts, containers which have no Name + + var serverFiles = from info in objectInfos + where info.Name != null + select Path.Combine(accountInfo.AccountPath,info.RelativeUrlToFilePath(accountInfo.UserName)); + var serverSet = new HashSet(serverFiles); + + var allStates = from state in FileState.Queryable + where state.FilePath.StartsWith(agent.RootPath) + select state.FilePath; + var stateSet = new HashSet(allStates); + stateSet.ExceptWith(serverSet); + stateSet.ExceptWith(localSet); + + foreach (var remainder in stateSet) + { + DeleteDirect(remainder); + } + + + } + } + + +} diff --git a/trunk/Pithos.Core/Pithos.Core.csproj b/trunk/Pithos.Core/Pithos.Core.csproj index 3d82aa9..22ee10b 100644 --- a/trunk/Pithos.Core/Pithos.Core.csproj +++ b/trunk/Pithos.Core/Pithos.Core.csproj @@ -1,469 +1,469 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {142AF135-DF30-4563-B0AC-B604235AE874} - Library - Properties - Pithos.Core - Pithos.Core - v4.0 - 512 - 0 - Client - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True - False - True - False - False - False - False - False - False - False - False - True - False - False - True - - - - - - - False - Full - Build - 0 - AnyCPU - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Test\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - - - pithos.snk - - - true - bin\Premium Debug\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - True - False - True - False - False - True - True - True - True - True - False - False - True - False - True - - - - - - - True - Full - Build - 0 - - - true - bin\Debug All\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug\ - TRACE;DEBUG;CONTRACTS_FULL - full - x64 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - bin\Release\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - true - bin\x64\Test\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - true - bin\x64\Premium Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug All\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - - - true - bin\x86\Debug\ - TRACE;DEBUG - full - x86 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - False - False - True - False - False - False - False - False - False - False - False - True - False - False - True - - - - - - - False - Full - Build - 0 - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - bin\Release\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - true - bin\x86\Test\ - DEBUG;TRACE - full - x86 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - - - true - bin\x86\Premium Debug\ - DEBUG;TRACE - full - x86 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - true - bin\x86\Debug All\ - DEBUG;TRACE - full - x86 - bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - - - - ..\Libraries\AsyncCtpLibrary.dll - - - ..\Libraries\Caliburn.Micro.dll - - - ..\Libraries\Castle.ActiveRecord.dll - - - ..\Libraries\Castle.Components.Validator.dll - - - ..\Libraries\Castle.Core.dll - - - ..\Libraries\Iesi.Collections.dll - - - ..\Libraries\log4net.dll - - - False - ..\Libraries\Microsoft.WindowsAPICodePack.dll - - - ..\Libraries\Microsoft.WindowsAPICodePack.Shell.dll - - - ..\Libraries\NHibernate.dll - - - ..\Libraries\NHibernate.ByteCode.Castle.dll - - - ..\Libraries\NHibernate.Search.dll - - - - - - False - ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.dll - - - False - ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.Linq.dll - - - - ..\Libraries\System.Threading.Tasks.Dataflow.dll - - - - - - - - - - AssemblyVersion.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} - ParallelExtensionsExtras - - - {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} - Pithos.Interfaces - - - {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} - Pithos.Network - - - - - Pithos.licenseheader - - - Designer - - - - - - PreserveNewest - Designer - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {142AF135-DF30-4563-B0AC-B604235AE874} + Library + Properties + Pithos.Core + Pithos.Core + v4.0 + 512 + 0 + Client + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + False + True + False + False + False + False + False + False + False + False + True + False + False + True + + + + + + + False + Full + Build + 0 + AnyCPU + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Test\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + + + pithos.snk + + + true + bin\Premium Debug\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + True + False + True + False + False + True + True + True + True + True + False + False + True + False + True + + + + + + + True + Full + Build + 0 + + + true + bin\Debug All\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug\ + TRACE;DEBUG;CONTRACTS_FULL + full + x64 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + true + bin\x64\Test\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + true + bin\x64\Premium Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug All\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + true + bin\x86\Debug\ + TRACE;DEBUG + full + x86 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + False + False + True + False + False + False + False + False + False + False + False + True + False + False + True + + + + + + + False + Full + Build + 0 + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + bin\Release\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + true + bin\x86\Test\ + DEBUG;TRACE + full + x86 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + true + bin\x86\Premium Debug\ + DEBUG;TRACE + full + x86 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + true + bin\x86\Debug All\ + DEBUG;TRACE + full + x86 + bin\Debug\Pithos.Core.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + + ..\Libraries\Caliburn.Micro.dll + + + ..\Libraries\Castle.ActiveRecord.dll + + + ..\Libraries\Castle.Components.Validator.dll + + + ..\Libraries\Castle.Core.dll + + + ..\Libraries\Iesi.Collections.dll + + + ..\Libraries\log4net.dll + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + False + ..\Libraries\Microsoft.WindowsAPICodePack.dll + + + ..\Libraries\Microsoft.WindowsAPICodePack.Shell.dll + + + ..\Libraries\NHibernate.dll + + + ..\Libraries\NHibernate.ByteCode.Castle.dll + + + ..\Libraries\NHibernate.Search.dll + + + + + + False + ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.dll + + + False + ..\packages\System.Data.SQLite.1.0.80.0\lib\net40\System.Data.SQLite.Linq.dll + + + + ..\Libraries\System.Threading.Tasks.Dataflow.dll + + + + + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} + ParallelExtensionsExtras + + + {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} + Pithos.Interfaces + + + {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} + Pithos.Network + + + + + Pithos.licenseheader + + + Designer + + + + + + PreserveNewest + Designer + + + + \ No newline at end of file diff --git a/trunk/Pithos.Core/packages.config b/trunk/Pithos.Core/packages.config index eda12a2..3fb5b39 100644 --- a/trunk/Pithos.Core/packages.config +++ b/trunk/Pithos.Core/packages.config @@ -1,12 +1,13 @@ - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/trunk/Pithos.IntegrationTests/Pithos.IntegrationTests.csproj b/trunk/Pithos.IntegrationTests/Pithos.IntegrationTests.csproj index abe9669..32f5701 100644 --- a/trunk/Pithos.IntegrationTests/Pithos.IntegrationTests.csproj +++ b/trunk/Pithos.IntegrationTests/Pithos.IntegrationTests.csproj @@ -1,76 +1,76 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {86524E19-7C1C-4A90-93AA-2316A150DC52} - Library - Properties - Pithos.IntegrationTests - Pithos.IntegrationTests - v4.0 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\Libraries\AsyncCtpLibrary.dll - - - ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll - - - ..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll - - - ..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll - - - - - - - - - - - - - - - - - - - - {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} - Pithos.Network - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {86524E19-7C1C-4A90-93AA-2316A150DC52} + Library + Properties + Pithos.IntegrationTests + Pithos.IntegrationTests + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll + + + ..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll + + + ..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll + + + + + + + + + + + + + + + + + + + + {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} + Pithos.Network + + + + \ No newline at end of file diff --git a/trunk/Pithos.IntegrationTests/packages.config b/trunk/Pithos.IntegrationTests/packages.config index 0c82178..2fca03c 100644 --- a/trunk/Pithos.IntegrationTests/packages.config +++ b/trunk/Pithos.IntegrationTests/packages.config @@ -1,4 +1,5 @@ - - - + + + + \ No newline at end of file diff --git a/trunk/Pithos.Network.Test/Pithos.Network.Test.csproj b/trunk/Pithos.Network.Test/Pithos.Network.Test.csproj index 20d00f6..49f9ba2 100644 --- a/trunk/Pithos.Network.Test/Pithos.Network.Test.csproj +++ b/trunk/Pithos.Network.Test/Pithos.Network.Test.csproj @@ -1,224 +1,224 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {E027200B-C26A-4877-BFD9-1A18CF5DF2F4} - Library - Properties - Pithos.Network.Test - Pithos.Network.Test - v4.0 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Test\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\Premium Debug\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\Debug All\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - bin\Release\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - true - bin\x64\Test\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Premium Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug All\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - - ..\Libraries\AsyncCtpLibrary.dll - - - False - ..\Pithos.Network\bin\Debug\Newtonsoft.Json.dll - - - ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll - - - ..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll - - - ..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll - - - - - - - - - - - - - - - - - - - - - {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} - Pithos.Interfaces - - - {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} - Pithos.Network - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {E027200B-C26A-4877-BFD9-1A18CF5DF2F4} + Library + Properties + Pithos.Network.Test + Pithos.Network.Test + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Test\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\Premium Debug\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\Debug All\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + true + bin\x64\Test\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Premium Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug All\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.Test.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + False + ..\Pithos.Network\bin\Debug\Newtonsoft.Json.dll + + + ..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll + + + ..\packages\NUnit.2.5.10.11092\lib\nunit.mocks.dll + + + ..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll + + + + + + + + + + + + + + + + + + + + + {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} + Pithos.Interfaces + + + {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} + Pithos.Network + + + + + PreserveNewest + + + + + + PreserveNewest + + + + + + + + \ No newline at end of file diff --git a/trunk/Pithos.Network.Test/packages.config b/trunk/Pithos.Network.Test/packages.config index b1f2e13..a1ae9eb 100644 --- a/trunk/Pithos.Network.Test/packages.config +++ b/trunk/Pithos.Network.Test/packages.config @@ -1,5 +1,6 @@ - - - - + + + + + \ No newline at end of file diff --git a/trunk/Pithos.Network/CloudFilesClient.cs b/trunk/Pithos.Network/CloudFilesClient.cs index 505fae2..9032fc3 100644 --- a/trunk/Pithos.Network/CloudFilesClient.cs +++ b/trunk/Pithos.Network/CloudFilesClient.cs @@ -1,1576 +1,1576 @@ -#region -/* ----------------------------------------------------------------------- - * - * - * 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. - * - * ----------------------------------------------------------------------- - */ -#endregion - -// **CloudFilesClient** provides a simple client interface to CloudFiles and Pithos -// -// The class provides methods to upload/download files, delete files, manage containers - - -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Pithos.Interfaces; -using log4net; - -namespace Pithos.Network -{ - [Export(typeof(ICloudClient))] - public class CloudFilesClient:ICloudClient - { - private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - //CloudFilesClient uses *_baseClient* internally to communicate with the server - //RestClient provides a REST-friendly interface over the standard WebClient. - private RestClient _baseClient; - - - //During authentication the client provides a UserName - public string UserName { get; set; } - - //and and ApiKey to the server - public string ApiKey { get; set; } - - //And receives an authentication Token. This token must be provided in ALL other operations, - //in the X-Auth-Token header - private string _token; - private readonly string _emptyGuid = Guid.Empty.ToString(); - - - public string Token - { - get { return _token; } - set - { - _token = value; - _baseClient.Headers["X-Auth-Token"] = value; - } - } - - //The client also receives a StorageUrl after authentication. All subsequent operations must - //use this url - public Uri StorageUrl { get; set; } - - - public Uri RootAddressUri { get; set; } - - /* private WebProxy _proxy; - public WebProxy Proxy - { - get { return _proxy; } - set - { - _proxy = value; - if (_baseClient != null) - _baseClient.Proxy = value; - } - } -*/ - - /* private Uri _proxy; - public Uri Proxy - { - get { return _proxy; } - set - { - _proxy = value; - if (_baseClient != null) - _baseClient.Proxy = new WebProxy(value); - } - }*/ - - public double DownloadPercentLimit { get; set; } - public double UploadPercentLimit { get; set; } - - public string AuthenticationUrl { get; set; } - - - public string VersionPath - { - get { return UsePithos ? "v1" : "v1.0"; } - } - - public bool UsePithos { get; set; } - - - - public CloudFilesClient(string userName, string apiKey) - { - UserName = userName; - ApiKey = apiKey; - } - - public CloudFilesClient(AccountInfo accountInfo) - { - if (accountInfo==null) - throw new ArgumentNullException("accountInfo"); - Contract.Ensures(!String.IsNullOrWhiteSpace(Token)); - Contract.Ensures(StorageUrl != null); - Contract.Ensures(_baseClient != null); - Contract.Ensures(RootAddressUri != null); - Contract.EndContractBlock(); - - _baseClient = new RestClient - { - BaseAddress = accountInfo.StorageUri.ToString(), - Timeout = 30000, - Retries = 3, - }; - StorageUrl = accountInfo.StorageUri; - Token = accountInfo.Token; - UserName = accountInfo.UserName; - - //Get the root address (StorageUrl without the account) - var storageUrl = StorageUrl.AbsoluteUri; - var usernameIndex = storageUrl.LastIndexOf(UserName); - var rootUrl = storageUrl.Substring(0, usernameIndex); - RootAddressUri = new Uri(rootUrl); - } - - - public AccountInfo Authenticate() - { - if (String.IsNullOrWhiteSpace(UserName)) - throw new InvalidOperationException("UserName is empty"); - if (String.IsNullOrWhiteSpace(ApiKey)) - throw new InvalidOperationException("ApiKey is empty"); - if (String.IsNullOrWhiteSpace(AuthenticationUrl)) - throw new InvalidOperationException("AuthenticationUrl is empty"); - Contract.Ensures(!String.IsNullOrWhiteSpace(Token)); - Contract.Ensures(StorageUrl != null); - Contract.Ensures(_baseClient != null); - Contract.Ensures(RootAddressUri != null); - Contract.EndContractBlock(); - - - Log.InfoFormat("[AUTHENTICATE] Start for {0}", UserName); - - var groups = new List(); - - using (var authClient = new RestClient{BaseAddress=AuthenticationUrl}) - { - /* if (Proxy != null) - authClient.Proxy = Proxy;*/ - - Contract.Assume(authClient.Headers!=null); - - authClient.Headers.Add("X-Auth-User", UserName); - authClient.Headers.Add("X-Auth-Key", ApiKey); - //TODO: Remove after testing. Added to overcome server auth bug - //authClient.Headers.Add("X-Auth-Token", ApiKey); - - authClient.DownloadStringWithRetry(VersionPath, 3); - - authClient.AssertStatusOK("Authentication failed"); - - var storageUrl = authClient.GetHeaderValue("X-Storage-Url"); - if (String.IsNullOrWhiteSpace(storageUrl)) - throw new InvalidOperationException("Failed to obtain storage url"); - - _baseClient = new RestClient - { - BaseAddress = storageUrl, - Timeout = 10000, - Retries = 3, - //Proxy=Proxy - }; - - StorageUrl = new Uri(storageUrl); - - //Get the root address (StorageUrl without the account) - var usernameIndex=storageUrl.LastIndexOf(UserName); - var rootUrl = storageUrl.Substring(0, usernameIndex); - RootAddressUri = new Uri(rootUrl); - - var token = authClient.GetHeaderValue("X-Auth-Token"); - if (String.IsNullOrWhiteSpace(token)) - throw new InvalidOperationException("Failed to obtain token url"); - Token = token; - - /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable(); - groups = (from key in keys - where key.StartsWith("X-Account-Group-") - let name = key.Substring(16) - select new Group(name, authClient.ResponseHeaders[key])) - .ToList(); - -*/ - } - - Log.InfoFormat("[AUTHENTICATE] End for {0}", UserName); - Debug.Assert(_baseClient!=null); - - return new AccountInfo {StorageUri = StorageUrl, Token = Token, UserName = UserName,Groups=groups}; - - } - - - - public IList ListContainers(string account) - { - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Parameters.Add("format", "json"); - var content = client.DownloadStringWithRetry("", 3); - client.AssertStatusOK("List Containers failed"); - - if (client.StatusCode == HttpStatusCode.NoContent) - return new List(); - var infos = JsonConvert.DeserializeObject>(content); - - foreach (var info in infos) - { - info.Account = account; - } - return infos; - } - - } - - private string GetAccountUrl(string account) - { - return new Uri(RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri; - } - - public IList ListSharingAccounts(DateTime? since=null) - { - using (ThreadContext.Stacks["Share"].Push("List Accounts")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - using (var client = new RestClient(_baseClient)) - { - client.Parameters.Clear(); - client.Parameters.Add("format", "json"); - client.IfModifiedSince = since; - - //Extract the username from the base address - client.BaseAddress = RootAddressUri.AbsoluteUri; - - var content = client.DownloadStringWithRetry(@"", 3); - - client.AssertStatusOK("ListSharingAccounts failed"); - - //If the result is empty, return an empty list, - var infos = String.IsNullOrWhiteSpace(content) - ? new List() - //Otherwise deserialize the account list into a list of ShareAccountInfos - : JsonConvert.DeserializeObject>(content); - - Log.DebugFormat("END"); - return infos; - } - } - } - - - /// - /// Request listing of all objects in a container modified since a specific time. - /// If the *since* value is missing, return all objects - /// - /// Use the since variable only for the containers listed in knownContainers. Unknown containers are considered new - /// and should be polled anyway - /// - /// - /// - public IList ListSharedObjects(HashSet knownContainers,DateTime? since = null ) - { - - using (ThreadContext.Stacks["Share"].Push("List Objects")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - //'since' is not used here because we need to have ListObjects return a NoChange result - //for all shared accounts,containers - - Func GetKey = c => String.Format("{0}\\{1}", c.Account, c.Name); - - var accounts = ListSharingAccounts(); - var containers = (from account in accounts - let conts = ListContainers(account.name) - from container in conts - select container).ToList(); - var items = from container in containers - let actualSince=knownContainers.Contains(GetKey(container))?since:null - select ListObjects(container.Account , container.Name, actualSince); - var objects=items.SelectMany(r=> r).ToList(); - - //For each object - //Check parents recursively up to (but not including) the container. - //If parents are missing, add them to the list - //Need function to calculate all parent URLs - objects = AddMissingParents(objects); - - //Store any new containers - foreach (var container in containers) - { - knownContainers.Add(GetKey(container)); - } - - - - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - return objects; - } - } - - private List AddMissingParents(List objects) - { - //TODO: Remove short-circuit when we decide to use Missing Parents functionality - //return objects; - - var existingUris = objects.ToDictionary(o => o.Uri, o => o); - foreach (var objectInfo in objects) - { - //Can be null when retrieving objects to show in selective sync - if (objectInfo.Name == null) - continue; - - var parts = objectInfo.Name.Split(new[]{'/'},StringSplitOptions.RemoveEmptyEntries); - //If there is no parent, skip - if (parts.Length == 1) - continue; - var baseParts = new[] - { - objectInfo.Uri.Host, objectInfo.Uri.Segments[1].TrimEnd('/'),objectInfo.Account,objectInfo.Container - }; - for (var partIdx = 0; partIdx < parts.Length - 1; partIdx++) - { - var nameparts = parts.Range(0, partIdx).ToArray(); - var parentName= String.Join("/", nameparts); - - var parentParts = baseParts.Concat(nameparts); - var parentUrl = objectInfo.Uri.Scheme+ "://" + String.Join("/", parentParts); - - var parentUri = new Uri(parentUrl, UriKind.Absolute); - - ObjectInfo existingInfo; - if (!existingUris.TryGetValue(parentUri,out existingInfo)) - { - var h = parentUrl.GetHashCode(); - var reverse = new string(parentUrl.Reverse().ToArray()); - var rh = reverse.GetHashCode(); - var b1 = BitConverter.GetBytes(h); - var b2 = BitConverter.GetBytes(rh); - var g = new Guid(0,0,0,b1.Concat(b2).ToArray()); - - - existingUris[parentUri] = new ObjectInfo - { - Account = objectInfo.Account, - Container = objectInfo.Container, - Content_Type = @"application/directory", - ETag = Signature.MD5_EMPTY, - X_Object_Hash = Signature.MERKLE_EMPTY, - Name=parentName, - StorageUri=objectInfo.StorageUri, - Bytes = 0, - UUID=g.ToString(), - }; - } - } - } - return existingUris.Values.ToList(); - } - - public void SetTags(ObjectInfo target,IDictionary tags) - { - if (String.IsNullOrWhiteSpace(Token)) - throw new InvalidOperationException("The Token is not set"); - if (StorageUrl == null) - throw new InvalidOperationException("The StorageUrl is not set"); - if (target == null) - throw new ArgumentNullException("target"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Share"].Push("Share Object")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - using (var client = new RestClient(_baseClient)) - { - - client.BaseAddress = GetAccountUrl(target.Account); - - client.Parameters.Clear(); - client.Parameters.Add("update", ""); - - foreach (var tag in tags) - { - var headerTag = String.Format("X-Object-Meta-{0}", tag.Key); - client.Headers.Add(headerTag, tag.Value); - } - - client.DownloadStringWithRetry(target.Container, 3); - - - client.AssertStatusOK("SetTags failed"); - //If the status is NOT ACCEPTED we have a problem - if (client.StatusCode != HttpStatusCode.Accepted) - { - Log.Error("Failed to set tags"); - throw new Exception("Failed to set tags"); - } - - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - } - } - - - } - - public void ShareObject(string account, string container, string objectName, string shareTo, bool read, bool write) - { - if (String.IsNullOrWhiteSpace(Token)) - throw new InvalidOperationException("The Token is not set"); - if (StorageUrl==null) - throw new InvalidOperationException("The StorageUrl is not set"); - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName"); - if (String.IsNullOrWhiteSpace(account)) - throw new ArgumentNullException("account"); - if (String.IsNullOrWhiteSpace(shareTo)) - throw new ArgumentNullException("shareTo"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Share"].Push("Share Object")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - using (var client = new RestClient(_baseClient)) - { - - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Parameters.Add("format", "json"); - - string permission = ""; - if (write) - permission = String.Format("write={0}", shareTo); - else if (read) - permission = String.Format("read={0}", shareTo); - client.Headers.Add("X-Object-Sharing", permission); - - var content = client.DownloadStringWithRetry(container, 3); - - client.AssertStatusOK("ShareObject failed"); - - //If the result is empty, return an empty list, - var infos = String.IsNullOrWhiteSpace(content) - ? new List() - //Otherwise deserialize the object list into a list of ObjectInfos - : JsonConvert.DeserializeObject>(content); - - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - } - } - - - } - - public AccountInfo GetAccountPolicies(AccountInfo accountInfo) - { - if (accountInfo==null) - throw new ArgumentNullException("accountInfo"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Account"].Push("GetPolicies")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - if (_baseClient == null) - { - _baseClient = new RestClient - { - BaseAddress = accountInfo.StorageUri.ToString(), - Timeout = 10000, - Retries = 3, - }; - } - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(accountInfo.UserName)) - client.BaseAddress = GetAccountUrl(accountInfo.UserName); - - client.Parameters.Clear(); - client.Parameters.Add("format", "json"); - client.Head(String.Empty, 3); - - var quotaValue=client.ResponseHeaders["X-Account-Policy-Quota"]; - var bytesValue= client.ResponseHeaders["X-Account-Bytes-Used"]; - - long quota, bytes; - if (long.TryParse(quotaValue, out quota)) - accountInfo.Quota = quota; - if (long.TryParse(bytesValue, out bytes)) - accountInfo.BytesUsed = bytes; - - return accountInfo; - - } - - } - } - - public void UpdateMetadata(ObjectInfo objectInfo) - { - if (objectInfo == null) - throw new ArgumentNullException("objectInfo"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - - using(var client=new RestClient(_baseClient)) - { - - client.BaseAddress = GetAccountUrl(objectInfo.Account); - - client.Parameters.Clear(); - - - //Set Tags - foreach (var tag in objectInfo.Tags) - { - var headerTag = String.Format("X-Object-Meta-{0}", tag.Key); - client.Headers.Add(headerTag, tag.Value); - } - - //Set Permissions - - var permissions=objectInfo.GetPermissionString(); - client.SetNonEmptyHeaderValue("X-Object-Sharing",permissions); - - client.SetNonEmptyHeaderValue("Content-Disposition",objectInfo.ContendDisposition); - client.SetNonEmptyHeaderValue("Content-Encoding",objectInfo.ContentEncoding); - client.SetNonEmptyHeaderValue("X-Object-Manifest",objectInfo.Manifest); - var isPublic = objectInfo.IsPublic.ToString().ToLower(); - client.Headers.Add("X-Object-Public", isPublic); - - - /*var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name); - uriBuilder.Query = "update="; - var uri = uriBuilder.Uri.MakeRelativeUri(this.RootAddressUri);*/ - var address = String.Format("{0}/{1}?update=",objectInfo.Container, objectInfo.Name); - client.PostWithRetry(address,"application/xml"); - - //client.UploadValues(uri,new NameValueCollection()); - - - client.AssertStatusOK("UpdateMetadata failed"); - //If the status is NOT ACCEPTED or OK we have a problem - if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK)) - { - Log.Error("Failed to update metadata"); - throw new Exception("Failed to update metadata"); - } - - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - } - } - - } - - public void UpdateMetadata(ContainerInfo containerInfo) - { - if (containerInfo == null) - throw new ArgumentNullException("containerInfo"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - - using(var client=new RestClient(_baseClient)) - { - - client.BaseAddress = GetAccountUrl(containerInfo.Account); - - client.Parameters.Clear(); - - - //Set Tags - foreach (var tag in containerInfo.Tags) - { - var headerTag = String.Format("X-Container-Meta-{0}", tag.Key); - client.Headers.Add(headerTag, tag.Value); - } - - - //Set Policies - foreach (var policy in containerInfo.Policies) - { - var headerPolicy = String.Format("X-Container-Policy-{0}", policy.Key); - client.Headers.Add(headerPolicy, policy.Value); - } - - - var uriBuilder = client.GetAddressBuilder(containerInfo.Name,""); - var uri = uriBuilder.Uri; - - client.UploadValues(uri,new NameValueCollection()); - - - client.AssertStatusOK("UpdateMetadata failed"); - //If the status is NOT ACCEPTED or OK we have a problem - if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK)) - { - Log.Error("Failed to update metadata"); - throw new Exception("Failed to update metadata"); - } - - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - } - } - - } - - - public IList ListObjects(string account, string container, DateTime? since = null) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Objects"].Push("List")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Parameters.Add("format", "json"); - client.IfModifiedSince = since; - var content = client.DownloadStringWithRetry(container, 3); - - client.AssertStatusOK("ListObjects failed"); - - if (client.StatusCode==HttpStatusCode.NotModified) - return new[]{new NoModificationInfo(account,container)}; - //If the result is empty, return an empty list, - var infos = String.IsNullOrWhiteSpace(content) - ? new List() - //Otherwise deserialize the object list into a list of ObjectInfos - : JsonConvert.DeserializeObject>(content); - - foreach (var info in infos) - { - info.Container = container; - info.Account = account; - info.StorageUri = this.StorageUrl; - } - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - return infos; - } - } - } - - public IList ListObjects(string account, string container, string folder, DateTime? since = null) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); -/* - if (String.IsNullOrWhiteSpace(folder)) - throw new ArgumentNullException("folder"); -*/ - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Objects"].Push("List")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Parameters.Add("format", "json"); - client.Parameters.Add("path", folder); - client.IfModifiedSince = since; - var content = client.DownloadStringWithRetry(container, 3); - client.AssertStatusOK("ListObjects failed"); - - if (client.StatusCode==HttpStatusCode.NotModified) - return new[]{new NoModificationInfo(account,container,folder)}; - - var infos = JsonConvert.DeserializeObject>(content); - foreach (var info in infos) - { - info.Account = account; - if (info.Container == null) - info.Container = container; - info.StorageUri = this.StorageUrl; - } - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - return infos; - } - } - } - - - public bool ContainerExists(string account, string container) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Containters"].Push("Exists")) - { - if (Log.IsDebugEnabled) Log.DebugFormat("START"); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Head(container, 3); - - bool result; - switch (client.StatusCode) - { - case HttpStatusCode.OK: - case HttpStatusCode.NoContent: - result=true; - break; - case HttpStatusCode.NotFound: - result=false; - break; - default: - throw CreateWebException("ContainerExists", client.StatusCode); - } - if (Log.IsDebugEnabled) Log.DebugFormat("END"); - - return result; - } - - } - } - - public bool ObjectExists(string account, string container, string objectName) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName", "The objectName property can't be empty"); - Contract.EndContractBlock(); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Head(container + "/" + objectName, 3); - - switch (client.StatusCode) - { - case HttpStatusCode.OK: - case HttpStatusCode.NoContent: - return true; - case HttpStatusCode.NotFound: - return false; - default: - throw CreateWebException("ObjectExists", client.StatusCode); - } - } - - } - - public ObjectInfo GetObjectInfo(string account, string container, string objectName) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName", "The objectName property can't be empty"); - Contract.EndContractBlock(); - - using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo")) - { - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - try - { - client.Parameters.Clear(); - - client.Head(container + "/" + objectName, 3); - - if (client.TimedOut) - return ObjectInfo.Empty; - - switch (client.StatusCode) - { - case HttpStatusCode.OK: - case HttpStatusCode.NoContent: - var keys = client.ResponseHeaders.AllKeys.AsQueryable(); - var tags = client.GetMeta("X-Object-Meta-"); - var extensions = (from key in keys - where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-") - select new {Name = key, Value = client.ResponseHeaders[key]}) - .ToDictionary(t => t.Name, t => t.Value); - - var permissions=client.GetHeaderValue("X-Object-Sharing", true); - - - var info = new ObjectInfo - { - Account = account, - Container = container, - Name = objectName, - ETag = client.GetHeaderValue("ETag"), - UUID=client.GetHeaderValue("X-Object-UUID"), - X_Object_Hash = client.GetHeaderValue("X-Object-Hash"), - Content_Type = client.GetHeaderValue("Content-Type"), - Bytes = Convert.ToInt64(client.GetHeaderValue("Content-Length",true)), - Tags = tags, - Last_Modified = client.LastModified, - Extensions = extensions, - ContentEncoding=client.GetHeaderValue("Content-Encoding",true), - ContendDisposition = client.GetHeaderValue("Content-Disposition",true), - Manifest=client.GetHeaderValue("X-Object-Manifest",true), - PublicUrl=client.GetHeaderValue("X-Object-Public",true), - StorageUri=this.StorageUrl, - }; - info.SetPermissions(permissions); - return info; - case HttpStatusCode.NotFound: - return ObjectInfo.Empty; - default: - throw new WebException( - String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", - objectName, client.StatusCode)); - } - - } - catch (RetryException) - { - Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName); - return ObjectInfo.Empty; - } - catch (WebException e) - { - Log.Error( - String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", - objectName, client.StatusCode), e); - throw; - } - } - } - - } - - public void CreateFolder(string account, string container, string folder) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(folder)) - throw new ArgumentNullException("folder", "The folder property can't be empty"); - Contract.EndContractBlock(); - - var folderUrl=String.Format("{0}/{1}",container,folder); - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Parameters.Clear(); - client.Headers.Add("Content-Type", @"application/directory"); - client.Headers.Add("Content-Length", "0"); - client.PutWithRetry(folderUrl, 3); - - if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted) - throw CreateWebException("CreateFolder", client.StatusCode); - } - } - - - - public ContainerInfo GetContainerInfo(string account, string container) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - Contract.EndContractBlock(); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Head(container); - switch (client.StatusCode) - { - case HttpStatusCode.OK: - case HttpStatusCode.NoContent: - var tags = client.GetMeta("X-Container-Meta-"); - var policies = client.GetMeta("X-Container-Policy-"); - - var containerInfo = new ContainerInfo - { - Account=account, - Name = container, - StorageUrl=this.StorageUrl.ToString(), - Count = - long.Parse(client.GetHeaderValue("X-Container-Object-Count")), - Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")), - BlockHash = client.GetHeaderValue("X-Container-Block-Hash"), - BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")), - Last_Modified=client.LastModified, - Tags=tags, - Policies=policies - }; - - - return containerInfo; - case HttpStatusCode.NotFound: - return ContainerInfo.Empty; - default: - throw CreateWebException("GetContainerInfo", client.StatusCode); - } - } - } - - public void CreateContainer(string account, string container) - { - if (String.IsNullOrWhiteSpace(account)) - throw new ArgumentNullException("account"); - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - Contract.EndContractBlock(); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.PutWithRetry(container, 3); - var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK}; - if (!expectedCodes.Contains(client.StatusCode)) - throw CreateWebException("CreateContainer", client.StatusCode); - } - } - - public void DeleteContainer(string account, string container) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - Contract.EndContractBlock(); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.DeleteWithRetry(container, 3); - var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent}; - if (!expectedCodes.Contains(client.StatusCode)) - throw CreateWebException("DeleteContainer", client.StatusCode); - } - - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// This method should have no timeout or a very long one - //Asynchronously download the object specified by *objectName* in a specific *container* to - // a local file - public async Task GetObject(string account, string container, string objectName, string fileName,CancellationToken cancellationToken) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName", "The objectName property can't be empty"); - Contract.EndContractBlock(); - - try - { - //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient - //object to avoid concurrency errors. - // - //Download operations take a long time therefore they have no timeout. - using(var client = new RestClient(_baseClient) { Timeout = 0 }) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - //The container and objectName are relative names. They are joined with the client's - //BaseAddress to create the object's absolute address - var builder = client.GetAddressBuilder(container, objectName); - var uri = builder.Uri; - - //Download progress is reported to the Trace log - Log.InfoFormat("[GET] START {0}", objectName); - /*client.DownloadProgressChanged += (sender, args) => - Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}", - fileName, args.ProgressPercentage, - args.BytesReceived, - args.TotalBytesToReceive);*/ - var progress = new Progress(args => - { - Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}", - fileName, args.ProgressPercentage, - args.BytesReceived, - args.TotalBytesToReceive); - if (DownloadProgressChanged!=null) - DownloadProgressChanged(this, args); - }); - - //Start downloading the object asynchronously - await client.DownloadFileTaskAsync(uri, fileName, cancellationToken,progress).ConfigureAwait(false); - - //Once the download completes - //Delete the local client object - } - //And report failure or completion - } - catch (Exception exc) - { - Log.ErrorFormat("[GET] FAIL {0} with {1}", objectName, exc); - throw; - } - - Log.InfoFormat("[GET] END {0}", objectName); - - - } - - public Task> PutHashMap(string account, string container, string objectName, TreeHash hash) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName"); - if (hash==null) - throw new ArgumentNullException("hash"); - if (String.IsNullOrWhiteSpace(Token)) - throw new InvalidOperationException("Invalid Token"); - if (StorageUrl == null) - throw new InvalidOperationException("Invalid Storage Url"); - Contract.EndContractBlock(); - - - //Don't use a timeout because putting the hashmap may be a long process - var client = new RestClient(_baseClient) { Timeout = 0 }; - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - //The container and objectName are relative names. They are joined with the client's - //BaseAddress to create the object's absolute address - var builder = client.GetAddressBuilder(container, objectName); - builder.Query = "format=json&hashmap"; - var uri = builder.Uri; - - - //Send the tree hash as Json to the server - client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream"; - var jsonHash = hash.ToJson(); - - client.Headers.Add("ETag",hash.MD5); - var uploadTask=client.UploadStringTask(uri, "PUT", jsonHash); - if (Log.IsDebugEnabled) - Log.DebugFormat("Hashes:\r\n{0}", jsonHash); - return uploadTask.ContinueWith(t => - { - - var empty = (IList)new List(); - - - //The server will respond either with 201-created if all blocks were already on the server - if (client.StatusCode == HttpStatusCode.Created) - { - //in which case we return an empty hash list - return empty; - } - //or with a 409-conflict and return the list of missing parts - //A 409 will cause an exception so we need to check t.IsFaulted to avoid propagating the exception - if (t.IsFaulted) - { - var ex = t.Exception.InnerException; - var we = ex as WebException; - var response = we.Response as HttpWebResponse; - if (response!=null && response.StatusCode==HttpStatusCode.Conflict) - { - //In case of 409 the missing parts will be in the response content - using (var stream = response.GetResponseStream()) - using(var reader=stream.GetLoggedReader(Log)) - { - //We used to have to cleanup the content before returning it because it contains - //error content after the list of hashes - // - //As of 30/1/2012, the result is a proper Json array so we don't need to read the content - //line by line - - var serializer = new JsonSerializer(); - serializer.Error += (sender, args) => Log.ErrorFormat("Deserialization error at [{0}] [{1}]", args.ErrorContext.Error, args.ErrorContext.Member); - var hashes = (List)serializer.Deserialize(reader, typeof(List)); - return hashes; - } - } - //Any other status code is unexpected and the exception should be rethrown - Log.LogError(response); - throw ex; - - } - - //Any other status code is unexpected but there was no exception. We can probably continue processing - Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription); - - return empty; - }); - - } - - - public async Task GetBlock(string account, string container, Uri relativeUrl, long start, long? end, CancellationToken cancellationToken) - { - if (String.IsNullOrWhiteSpace(Token)) - throw new InvalidOperationException("Invalid Token"); - if (StorageUrl == null) - throw new InvalidOperationException("Invalid Storage Url"); - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - if (relativeUrl == null) - throw new ArgumentNullException("relativeUrl"); - if (end.HasValue && end < 0) - throw new ArgumentOutOfRangeException("end"); - if (start < 0) - throw new ArgumentOutOfRangeException("start"); - Contract.EndContractBlock(); - - //Don't use a timeout because putting the hashmap may be a long process - using (var client = new RestClient(_baseClient) {Timeout = 0, RangeFrom = start, RangeTo = end}) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - var builder = client.GetAddressBuilder(container, relativeUrl.ToString()); - var uri = builder.Uri; - -/* client.DownloadProgressChanged += (sender, args) => - { - Log.DebugFormat("[GET PROGRESS] {0} {1}% {2} of {3}", - uri.Segments.Last(), args.ProgressPercentage, - args.BytesReceived, - args.TotalBytesToReceive); - DownloadProgressChanged(sender, args); - };*/ - var progress = new Progress(args => - { - Log.DebugFormat("[GET PROGRESS] {0} {1}% {2} of {3}", - uri.Segments.Last(), args.ProgressPercentage, - args.BytesReceived, - args.TotalBytesToReceive); - if (DownloadProgressChanged!=null) - DownloadProgressChanged(this, args); - }); - - - var result = await client.DownloadDataTaskAsync(uri, cancellationToken,progress).ConfigureAwait(false); - return result; - } - } - - public event UploadProgressChangedEventHandler UploadProgressChanged; - public event DownloadProgressChangedEventHandler DownloadProgressChanged; - - public async Task PostBlock(string account, string container, byte[] block, int offset, int count,CancellationToken token) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - if (block == null) - throw new ArgumentNullException("block"); - if (offset < 0 || offset >= block.Length) - throw new ArgumentOutOfRangeException("offset"); - if (count < 0 || count > block.Length) - throw new ArgumentOutOfRangeException("count"); - if (String.IsNullOrWhiteSpace(Token)) - throw new InvalidOperationException("Invalid Token"); - if (StorageUrl == null) - throw new InvalidOperationException("Invalid Storage Url"); - Contract.EndContractBlock(); - - - try - { - - //Don't use a timeout because putting the hashmap may be a long process - using (var client = new RestClient(_baseClient) { Timeout = 0 }) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - var builder = client.GetAddressBuilder(container, ""); - //We are doing an update - builder.Query = "update"; - var uri = builder.Uri; - - client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream"; - - Log.InfoFormat("[BLOCK POST] START"); - -/* - client.UploadProgressChanged += (sender, args) => - { - Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}", - args.ProgressPercentage, args.BytesSent, - args.TotalBytesToSend); - UploadProgressChanged(sender, args); - }; -*/ - client.UploadFileCompleted += (sender, args) => - Log.InfoFormat("[BLOCK POST PROGRESS] Completed "); - - var progress=new Progress(args=> - { - Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}", - args.ProgressPercentage, args.BytesSent, - args.TotalBytesToSend); - if (UploadProgressChanged!=null) - UploadProgressChanged(this, args); - }); - var buffer = new byte[count]; - Buffer.BlockCopy(block, offset, buffer, 0, count); - //Send the block - await client.UploadDataTaskAsync(uri, "POST", buffer,token,progress).ConfigureAwait(false); - Log.InfoFormat("[BLOCK POST] END"); - } - } - catch (TaskCanceledException ) - { - Log.Info("Aborting block"); - throw; - } - catch (Exception exc) - { - Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc); - throw; - } - } - - - public async Task GetHashMap(string account, string container, string objectName) - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName"); - if (String.IsNullOrWhiteSpace(Token)) - throw new InvalidOperationException("Invalid Token"); - if (StorageUrl == null) - throw new InvalidOperationException("Invalid Storage Url"); - Contract.EndContractBlock(); - - try - { - //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient - //object to avoid concurrency errors. - // - //Download operations take a long time therefore they have no timeout. - //TODO: Do they really? this is a hashmap operation, not a download - - //Start downloading the object asynchronously - using (var client = new RestClient(_baseClient) { Timeout = 0 }) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - //The container and objectName are relative names. They are joined with the client's - //BaseAddress to create the object's absolute address - var builder = client.GetAddressBuilder(container, objectName); - builder.Query = "format=json&hashmap"; - var uri = builder.Uri; - - - var json = await client.DownloadStringTaskAsync(uri).ConfigureAwait(false); - var treeHash = TreeHash.Parse(json); - Log.InfoFormat("[GET HASH] END {0}", objectName); - return treeHash; - } - } - catch (Exception exc) - { - Log.ErrorFormat("[GET HASH] END {0} with {1}", objectName, exc); - throw; - } - - } - - - /// - /// - /// - /// - /// - /// - /// - /// Optional hash value for the file. If no hash is provided, the method calculates a new hash - /// >This method should have no timeout or a very long one - public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream") - { - if (String.IsNullOrWhiteSpace(container)) - throw new ArgumentNullException("container", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName", "The objectName property can't be empty"); - if (String.IsNullOrWhiteSpace(fileName)) - throw new ArgumentNullException("fileName", "The fileName property can't be empty"); -/* - if (!File.Exists(fileName) && !Directory.Exists(fileName)) - throw new FileNotFoundException("The file or directory does not exist",fileName); -*/ - - try - { - - using (var client = new RestClient(_baseClient) { Timeout = 0 }) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - var builder = client.GetAddressBuilder(container, objectName); - var uri = builder.Uri; - - string etag = hash ?? CalculateHash(fileName); - - client.Headers.Add("Content-Type", contentType); - client.Headers.Add("ETag", etag); - - - Log.InfoFormat("[PUT] START {0}", objectName); - client.UploadProgressChanged += (sender, args) => - { - using (ThreadContext.Stacks["PUT"].Push("Progress")) - { - Log.InfoFormat("{0} {1}% {2} of {3}", fileName, - args.ProgressPercentage, - args.BytesSent, args.TotalBytesToSend); - } - }; - - client.UploadFileCompleted += (sender, args) => - { - using (ThreadContext.Stacks["PUT"].Push("Progress")) - { - Log.InfoFormat("Completed {0}", fileName); - } - }; - if (contentType=="application/directory") - await client.UploadDataTaskAsync(uri, "PUT", new byte[0]).ConfigureAwait(false); - else - await client.UploadFileTaskAsync(uri, "PUT", fileName).ConfigureAwait(false); - } - - Log.InfoFormat("[PUT] END {0}", objectName); - } - catch (Exception exc) - { - Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc); - throw; - } - - } - - - private static string CalculateHash(string fileName) - { - Contract.Requires(!String.IsNullOrWhiteSpace(fileName)); - Contract.EndContractBlock(); - - string hash; - using (var hasher = MD5.Create()) - using(var stream=File.OpenRead(fileName)) - { - var hashBuilder=new StringBuilder(); - foreach (byte b in hasher.ComputeHash(stream)) - hashBuilder.Append(b.ToString("x2").ToLower()); - hash = hashBuilder.ToString(); - } - return hash; - } - - public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName) - { - if (String.IsNullOrWhiteSpace(sourceContainer)) - throw new ArgumentNullException("sourceContainer", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(oldObjectName)) - throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty"); - if (String.IsNullOrWhiteSpace(targetContainer)) - throw new ArgumentNullException("targetContainer", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(newObjectName)) - throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty"); - Contract.EndContractBlock(); - - var targetUrl = targetContainer + "/" + newObjectName; - var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Headers.Add("X-Move-From", Uri.EscapeUriString(sourceUrl)); - client.PutWithRetry(targetUrl, 3); - - var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created}; - if (!expectedCodes.Contains(client.StatusCode)) - throw CreateWebException("MoveObject", client.StatusCode); - } - } - - public void DeleteObject(string account, string sourceContainer, string objectName, bool isDirectory) - { - if (String.IsNullOrWhiteSpace(sourceContainer)) - throw new ArgumentNullException("sourceContainer", "The container property can't be empty"); - if (String.IsNullOrWhiteSpace(objectName)) - throw new ArgumentNullException("objectName", "The oldObjectName property can't be empty"); - Contract.EndContractBlock(); - - var targetUrl = FolderConstants.TrashContainer + "/" + objectName; -/* - if (isDirectory) - targetUrl = targetUrl + "?delimiter=/"; -*/ - - var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - client.Headers.Add("X-Move-From", Uri.EscapeUriString(sourceUrl)); - client.AllowedStatusCodes.Add(HttpStatusCode.NotFound); - Log.InfoFormat("[TRASH] [{0}] to [{1}]",sourceUrl,targetUrl); - client.PutWithRetry(targetUrl, 3); - - var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound}; - if (!expectedCodes.Contains(client.StatusCode)) - throw CreateWebException("DeleteObject", client.StatusCode); - } - } - - - private static WebException CreateWebException(string operation, HttpStatusCode statusCode) - { - return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode)); - } - - -/* - public IEnumerable ListDirectories(ContainerInfo container) - { - var directories=this.ListObjects(container.Account, container.Name, "/"); - } -*/ - - public bool CanUpload(string account, ObjectInfo cloudFile) - { - Contract.Requires(!String.IsNullOrWhiteSpace(account)); - Contract.Requires(cloudFile!=null); - - using (var client = new RestClient(_baseClient)) - { - if (!String.IsNullOrWhiteSpace(account)) - client.BaseAddress = GetAccountUrl(account); - - - var parts = cloudFile.Name.Split('/'); - var folder = String.Join("/", parts,0,parts.Length-1); - - var fileUrl=String.Format("{0}/{1}/{2}.pithos.ignore",cloudFile.Container,folder,Guid.NewGuid()); - - client.Parameters.Clear(); - try - { - client.PutWithRetry(fileUrl, 3, @"application/octet-stream"); - - var expectedCodes = new[] { HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created}; - var result=(expectedCodes.Contains(client.StatusCode)); - DeleteObject(account, cloudFile.Container, fileUrl, cloudFile.IsDirectory); - return result; - } - catch - { - return false; - } - } - } - } - - public class ShareAccountInfo - { - public DateTime? last_modified { get; set; } - public string name { get; set; } - } -} +#region +/* ----------------------------------------------------------------------- + * + * + * 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. + * + * ----------------------------------------------------------------------- + */ +#endregion + +// **CloudFilesClient** provides a simple client interface to CloudFiles and Pithos +// +// The class provides methods to upload/download files, delete files, manage containers + + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Pithos.Interfaces; +using log4net; + +namespace Pithos.Network +{ + [Export(typeof(ICloudClient))] + public class CloudFilesClient:ICloudClient + { + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + //CloudFilesClient uses *_baseClient* internally to communicate with the server + //RestClient provides a REST-friendly interface over the standard WebClient. + private RestClient _baseClient; + + + //During authentication the client provides a UserName + public string UserName { get; set; } + + //and and ApiKey to the server + public string ApiKey { get; set; } + + //And receives an authentication Token. This token must be provided in ALL other operations, + //in the X-Auth-Token header + private string _token; + private readonly string _emptyGuid = Guid.Empty.ToString(); + + + public string Token + { + get { return _token; } + set + { + _token = value; + _baseClient.Headers["X-Auth-Token"] = value; + } + } + + //The client also receives a StorageUrl after authentication. All subsequent operations must + //use this url + public Uri StorageUrl { get; set; } + + + public Uri RootAddressUri { get; set; } + + /* private WebProxy _proxy; + public WebProxy Proxy + { + get { return _proxy; } + set + { + _proxy = value; + if (_baseClient != null) + _baseClient.Proxy = value; + } + } +*/ + + /* private Uri _proxy; + public Uri Proxy + { + get { return _proxy; } + set + { + _proxy = value; + if (_baseClient != null) + _baseClient.Proxy = new WebProxy(value); + } + }*/ + + public double DownloadPercentLimit { get; set; } + public double UploadPercentLimit { get; set; } + + public string AuthenticationUrl { get; set; } + + + public string VersionPath + { + get { return UsePithos ? "v1" : "v1.0"; } + } + + public bool UsePithos { get; set; } + + + + public CloudFilesClient(string userName, string apiKey) + { + UserName = userName; + ApiKey = apiKey; + } + + public CloudFilesClient(AccountInfo accountInfo) + { + if (accountInfo==null) + throw new ArgumentNullException("accountInfo"); + Contract.Ensures(!String.IsNullOrWhiteSpace(Token)); + Contract.Ensures(StorageUrl != null); + Contract.Ensures(_baseClient != null); + Contract.Ensures(RootAddressUri != null); + Contract.EndContractBlock(); + + _baseClient = new RestClient + { + BaseAddress = accountInfo.StorageUri.ToString(), + Timeout = 30000, + Retries = 3, + }; + StorageUrl = accountInfo.StorageUri; + Token = accountInfo.Token; + UserName = accountInfo.UserName; + + //Get the root address (StorageUrl without the account) + var storageUrl = StorageUrl.AbsoluteUri; + var usernameIndex = storageUrl.LastIndexOf(UserName); + var rootUrl = storageUrl.Substring(0, usernameIndex); + RootAddressUri = new Uri(rootUrl); + } + + + public AccountInfo Authenticate() + { + if (String.IsNullOrWhiteSpace(UserName)) + throw new InvalidOperationException("UserName is empty"); + if (String.IsNullOrWhiteSpace(ApiKey)) + throw new InvalidOperationException("ApiKey is empty"); + if (String.IsNullOrWhiteSpace(AuthenticationUrl)) + throw new InvalidOperationException("AuthenticationUrl is empty"); + Contract.Ensures(!String.IsNullOrWhiteSpace(Token)); + Contract.Ensures(StorageUrl != null); + Contract.Ensures(_baseClient != null); + Contract.Ensures(RootAddressUri != null); + Contract.EndContractBlock(); + + + Log.InfoFormat("[AUTHENTICATE] Start for {0}", UserName); + + var groups = new List(); + + using (var authClient = new RestClient{BaseAddress=AuthenticationUrl}) + { + /* if (Proxy != null) + authClient.Proxy = Proxy;*/ + + Contract.Assume(authClient.Headers!=null); + + authClient.Headers.Add("X-Auth-User", UserName); + authClient.Headers.Add("X-Auth-Key", ApiKey); + //TODO: Remove after testing. Added to overcome server auth bug + //authClient.Headers.Add("X-Auth-Token", ApiKey); + + authClient.DownloadStringWithRetry(VersionPath, 3); + + authClient.AssertStatusOK("Authentication failed"); + + var storageUrl = authClient.GetHeaderValue("X-Storage-Url"); + if (String.IsNullOrWhiteSpace(storageUrl)) + throw new InvalidOperationException("Failed to obtain storage url"); + + _baseClient = new RestClient + { + BaseAddress = storageUrl, + Timeout = 10000, + Retries = 3, + //Proxy=Proxy + }; + + StorageUrl = new Uri(storageUrl); + + //Get the root address (StorageUrl without the account) + var usernameIndex=storageUrl.LastIndexOf(UserName); + var rootUrl = storageUrl.Substring(0, usernameIndex); + RootAddressUri = new Uri(rootUrl); + + var token = authClient.GetHeaderValue("X-Auth-Token"); + if (String.IsNullOrWhiteSpace(token)) + throw new InvalidOperationException("Failed to obtain token url"); + Token = token; + + /* var keys = authClient.ResponseHeaders.AllKeys.AsQueryable(); + groups = (from key in keys + where key.StartsWith("X-Account-Group-") + let name = key.Substring(16) + select new Group(name, authClient.ResponseHeaders[key])) + .ToList(); + +*/ + } + + Log.InfoFormat("[AUTHENTICATE] End for {0}", UserName); + Debug.Assert(_baseClient!=null); + + return new AccountInfo {StorageUri = StorageUrl, Token = Token, UserName = UserName,Groups=groups}; + + } + + + + public IList ListContainers(string account) + { + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Parameters.Add("format", "json"); + var content = client.DownloadStringWithRetry("", 3); + client.AssertStatusOK("List Containers failed"); + + if (client.StatusCode == HttpStatusCode.NoContent) + return new List(); + var infos = JsonConvert.DeserializeObject>(content); + + foreach (var info in infos) + { + info.Account = account; + } + return infos; + } + + } + + private string GetAccountUrl(string account) + { + return new Uri(RootAddressUri, new Uri(account,UriKind.Relative)).AbsoluteUri; + } + + public IList ListSharingAccounts(DateTime? since=null) + { + using (ThreadContext.Stacks["Share"].Push("List Accounts")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + using (var client = new RestClient(_baseClient)) + { + client.Parameters.Clear(); + client.Parameters.Add("format", "json"); + client.IfModifiedSince = since; + + //Extract the username from the base address + client.BaseAddress = RootAddressUri.AbsoluteUri; + + var content = client.DownloadStringWithRetry(@"", 3); + + client.AssertStatusOK("ListSharingAccounts failed"); + + //If the result is empty, return an empty list, + var infos = String.IsNullOrWhiteSpace(content) + ? new List() + //Otherwise deserialize the account list into a list of ShareAccountInfos + : JsonConvert.DeserializeObject>(content); + + Log.DebugFormat("END"); + return infos; + } + } + } + + + /// + /// Request listing of all objects in a container modified since a specific time. + /// If the *since* value is missing, return all objects + /// + /// Use the since variable only for the containers listed in knownContainers. Unknown containers are considered new + /// and should be polled anyway + /// + /// + /// + public IList ListSharedObjects(HashSet knownContainers,DateTime? since = null ) + { + + using (ThreadContext.Stacks["Share"].Push("List Objects")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + //'since' is not used here because we need to have ListObjects return a NoChange result + //for all shared accounts,containers + + Func GetKey = c => String.Format("{0}\\{1}", c.Account, c.Name); + + var accounts = ListSharingAccounts(); + var containers = (from account in accounts + let conts = ListContainers(account.name) + from container in conts + select container).ToList(); + var items = from container in containers + let actualSince=knownContainers.Contains(GetKey(container))?since:null + select ListObjects(container.Account , container.Name, actualSince); + var objects=items.SelectMany(r=> r).ToList(); + + //For each object + //Check parents recursively up to (but not including) the container. + //If parents are missing, add them to the list + //Need function to calculate all parent URLs + objects = AddMissingParents(objects); + + //Store any new containers + foreach (var container in containers) + { + knownContainers.Add(GetKey(container)); + } + + + + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + return objects; + } + } + + private List AddMissingParents(List objects) + { + //TODO: Remove short-circuit when we decide to use Missing Parents functionality + //return objects; + + var existingUris = objects.ToDictionary(o => o.Uri, o => o); + foreach (var objectInfo in objects) + { + //Can be null when retrieving objects to show in selective sync + if (objectInfo.Name == null) + continue; + + var parts = objectInfo.Name.Split(new[]{'/'},StringSplitOptions.RemoveEmptyEntries); + //If there is no parent, skip + if (parts.Length == 1) + continue; + var baseParts = new[] + { + objectInfo.Uri.Host, objectInfo.Uri.Segments[1].TrimEnd('/'),objectInfo.Account,objectInfo.Container + }; + for (var partIdx = 0; partIdx < parts.Length - 1; partIdx++) + { + var nameparts = parts.Range(0, partIdx).ToArray(); + var parentName= String.Join("/", nameparts); + + var parentParts = baseParts.Concat(nameparts); + var parentUrl = objectInfo.Uri.Scheme+ "://" + String.Join("/", parentParts); + + var parentUri = new Uri(parentUrl, UriKind.Absolute); + + ObjectInfo existingInfo; + if (!existingUris.TryGetValue(parentUri,out existingInfo)) + { + var h = parentUrl.GetHashCode(); + var reverse = new string(parentUrl.Reverse().ToArray()); + var rh = reverse.GetHashCode(); + var b1 = BitConverter.GetBytes(h); + var b2 = BitConverter.GetBytes(rh); + var g = new Guid(0,0,0,b1.Concat(b2).ToArray()); + + + existingUris[parentUri] = new ObjectInfo + { + Account = objectInfo.Account, + Container = objectInfo.Container, + Content_Type = @"application/directory", + ETag = Signature.MD5_EMPTY, + X_Object_Hash = Signature.MERKLE_EMPTY, + Name=parentName, + StorageUri=objectInfo.StorageUri, + Bytes = 0, + UUID=g.ToString(), + }; + } + } + } + return existingUris.Values.ToList(); + } + + public void SetTags(ObjectInfo target,IDictionary tags) + { + if (String.IsNullOrWhiteSpace(Token)) + throw new InvalidOperationException("The Token is not set"); + if (StorageUrl == null) + throw new InvalidOperationException("The StorageUrl is not set"); + if (target == null) + throw new ArgumentNullException("target"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Share"].Push("Share Object")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + using (var client = new RestClient(_baseClient)) + { + + client.BaseAddress = GetAccountUrl(target.Account); + + client.Parameters.Clear(); + client.Parameters.Add("update", ""); + + foreach (var tag in tags) + { + var headerTag = String.Format("X-Object-Meta-{0}", tag.Key); + client.Headers.Add(headerTag, tag.Value); + } + + client.DownloadStringWithRetry(target.Container, 3); + + + client.AssertStatusOK("SetTags failed"); + //If the status is NOT ACCEPTED we have a problem + if (client.StatusCode != HttpStatusCode.Accepted) + { + Log.Error("Failed to set tags"); + throw new Exception("Failed to set tags"); + } + + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + } + } + + + } + + public void ShareObject(string account, string container, string objectName, string shareTo, bool read, bool write) + { + if (String.IsNullOrWhiteSpace(Token)) + throw new InvalidOperationException("The Token is not set"); + if (StorageUrl==null) + throw new InvalidOperationException("The StorageUrl is not set"); + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName"); + if (String.IsNullOrWhiteSpace(account)) + throw new ArgumentNullException("account"); + if (String.IsNullOrWhiteSpace(shareTo)) + throw new ArgumentNullException("shareTo"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Share"].Push("Share Object")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + using (var client = new RestClient(_baseClient)) + { + + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Parameters.Add("format", "json"); + + string permission = ""; + if (write) + permission = String.Format("write={0}", shareTo); + else if (read) + permission = String.Format("read={0}", shareTo); + client.Headers.Add("X-Object-Sharing", permission); + + var content = client.DownloadStringWithRetry(container, 3); + + client.AssertStatusOK("ShareObject failed"); + + //If the result is empty, return an empty list, + var infos = String.IsNullOrWhiteSpace(content) + ? new List() + //Otherwise deserialize the object list into a list of ObjectInfos + : JsonConvert.DeserializeObject>(content); + + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + } + } + + + } + + public AccountInfo GetAccountPolicies(AccountInfo accountInfo) + { + if (accountInfo==null) + throw new ArgumentNullException("accountInfo"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Account"].Push("GetPolicies")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + if (_baseClient == null) + { + _baseClient = new RestClient + { + BaseAddress = accountInfo.StorageUri.ToString(), + Timeout = 10000, + Retries = 3, + }; + } + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(accountInfo.UserName)) + client.BaseAddress = GetAccountUrl(accountInfo.UserName); + + client.Parameters.Clear(); + client.Parameters.Add("format", "json"); + client.Head(String.Empty, 3); + + var quotaValue=client.ResponseHeaders["X-Account-Policy-Quota"]; + var bytesValue= client.ResponseHeaders["X-Account-Bytes-Used"]; + + long quota, bytes; + if (long.TryParse(quotaValue, out quota)) + accountInfo.Quota = quota; + if (long.TryParse(bytesValue, out bytes)) + accountInfo.BytesUsed = bytes; + + return accountInfo; + + } + + } + } + + public void UpdateMetadata(ObjectInfo objectInfo) + { + if (objectInfo == null) + throw new ArgumentNullException("objectInfo"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Objects"].Push("UpdateMetadata")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + + using(var client=new RestClient(_baseClient)) + { + + client.BaseAddress = GetAccountUrl(objectInfo.Account); + + client.Parameters.Clear(); + + + //Set Tags + foreach (var tag in objectInfo.Tags) + { + var headerTag = String.Format("X-Object-Meta-{0}", tag.Key); + client.Headers.Add(headerTag, tag.Value); + } + + //Set Permissions + + var permissions=objectInfo.GetPermissionString(); + client.SetNonEmptyHeaderValue("X-Object-Sharing",permissions); + + client.SetNonEmptyHeaderValue("Content-Disposition",objectInfo.ContendDisposition); + client.SetNonEmptyHeaderValue("Content-Encoding",objectInfo.ContentEncoding); + client.SetNonEmptyHeaderValue("X-Object-Manifest",objectInfo.Manifest); + var isPublic = objectInfo.IsPublic.ToString().ToLower(); + client.Headers.Add("X-Object-Public", isPublic); + + + /*var uriBuilder = client.GetAddressBuilder(objectInfo.Container, objectInfo.Name); + uriBuilder.Query = "update="; + var uri = uriBuilder.Uri.MakeRelativeUri(this.RootAddressUri);*/ + var address = String.Format("{0}/{1}?update=",objectInfo.Container, objectInfo.Name); + client.PostWithRetry(address,"application/xml"); + + //client.UploadValues(uri,new NameValueCollection()); + + + client.AssertStatusOK("UpdateMetadata failed"); + //If the status is NOT ACCEPTED or OK we have a problem + if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK)) + { + Log.Error("Failed to update metadata"); + throw new Exception("Failed to update metadata"); + } + + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + } + } + + } + + public void UpdateMetadata(ContainerInfo containerInfo) + { + if (containerInfo == null) + throw new ArgumentNullException("containerInfo"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Containers"].Push("UpdateMetadata")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + + using(var client=new RestClient(_baseClient)) + { + + client.BaseAddress = GetAccountUrl(containerInfo.Account); + + client.Parameters.Clear(); + + + //Set Tags + foreach (var tag in containerInfo.Tags) + { + var headerTag = String.Format("X-Container-Meta-{0}", tag.Key); + client.Headers.Add(headerTag, tag.Value); + } + + + //Set Policies + foreach (var policy in containerInfo.Policies) + { + var headerPolicy = String.Format("X-Container-Policy-{0}", policy.Key); + client.Headers.Add(headerPolicy, policy.Value); + } + + + var uriBuilder = client.GetAddressBuilder(containerInfo.Name,""); + var uri = uriBuilder.Uri; + + client.UploadValues(uri,new NameValueCollection()); + + + client.AssertStatusOK("UpdateMetadata failed"); + //If the status is NOT ACCEPTED or OK we have a problem + if (!(client.StatusCode == HttpStatusCode.Accepted || client.StatusCode == HttpStatusCode.OK)) + { + Log.Error("Failed to update metadata"); + throw new Exception("Failed to update metadata"); + } + + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + } + } + + } + + + public IList ListObjects(string account, string container, DateTime? since = null) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Objects"].Push("List")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Parameters.Add("format", "json"); + client.IfModifiedSince = since; + var content = client.DownloadStringWithRetry(container, 3); + + client.AssertStatusOK("ListObjects failed"); + + if (client.StatusCode==HttpStatusCode.NotModified) + return new[]{new NoModificationInfo(account,container)}; + //If the result is empty, return an empty list, + var infos = String.IsNullOrWhiteSpace(content) + ? new List() + //Otherwise deserialize the object list into a list of ObjectInfos + : JsonConvert.DeserializeObject>(content); + + foreach (var info in infos) + { + info.Container = container; + info.Account = account; + info.StorageUri = this.StorageUrl; + } + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + return infos; + } + } + } + + public IList ListObjects(string account, string container, string folder, DateTime? since = null) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); +/* + if (String.IsNullOrWhiteSpace(folder)) + throw new ArgumentNullException("folder"); +*/ + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Objects"].Push("List")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Parameters.Add("format", "json"); + client.Parameters.Add("path", folder); + client.IfModifiedSince = since; + var content = client.DownloadStringWithRetry(container, 3); + client.AssertStatusOK("ListObjects failed"); + + if (client.StatusCode==HttpStatusCode.NotModified) + return new[]{new NoModificationInfo(account,container,folder)}; + + var infos = JsonConvert.DeserializeObject>(content); + foreach (var info in infos) + { + info.Account = account; + if (info.Container == null) + info.Container = container; + info.StorageUri = this.StorageUrl; + } + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + return infos; + } + } + } + + + public bool ContainerExists(string account, string container) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Containters"].Push("Exists")) + { + if (Log.IsDebugEnabled) Log.DebugFormat("START"); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Head(container, 3); + + bool result; + switch (client.StatusCode) + { + case HttpStatusCode.OK: + case HttpStatusCode.NoContent: + result=true; + break; + case HttpStatusCode.NotFound: + result=false; + break; + default: + throw CreateWebException("ContainerExists", client.StatusCode); + } + if (Log.IsDebugEnabled) Log.DebugFormat("END"); + + return result; + } + + } + } + + public bool ObjectExists(string account, string container, string objectName) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName", "The objectName property can't be empty"); + Contract.EndContractBlock(); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Head(container + "/" + objectName, 3); + + switch (client.StatusCode) + { + case HttpStatusCode.OK: + case HttpStatusCode.NoContent: + return true; + case HttpStatusCode.NotFound: + return false; + default: + throw CreateWebException("ObjectExists", client.StatusCode); + } + } + + } + + public ObjectInfo GetObjectInfo(string account, string container, string objectName) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName", "The objectName property can't be empty"); + Contract.EndContractBlock(); + + using (ThreadContext.Stacks["Objects"].Push("GetObjectInfo")) + { + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + try + { + client.Parameters.Clear(); + + client.Head(container + "/" + objectName, 3); + + if (client.TimedOut) + return ObjectInfo.Empty; + + switch (client.StatusCode) + { + case HttpStatusCode.OK: + case HttpStatusCode.NoContent: + var keys = client.ResponseHeaders.AllKeys.AsQueryable(); + var tags = client.GetMeta("X-Object-Meta-"); + var extensions = (from key in keys + where key.StartsWith("X-Object-") && !key.StartsWith("X-Object-Meta-") + select new {Name = key, Value = client.ResponseHeaders[key]}) + .ToDictionary(t => t.Name, t => t.Value); + + var permissions=client.GetHeaderValue("X-Object-Sharing", true); + + + var info = new ObjectInfo + { + Account = account, + Container = container, + Name = objectName, + ETag = client.GetHeaderValue("ETag"), + UUID=client.GetHeaderValue("X-Object-UUID"), + X_Object_Hash = client.GetHeaderValue("X-Object-Hash"), + Content_Type = client.GetHeaderValue("Content-Type"), + Bytes = Convert.ToInt64(client.GetHeaderValue("Content-Length",true)), + Tags = tags, + Last_Modified = client.LastModified, + Extensions = extensions, + ContentEncoding=client.GetHeaderValue("Content-Encoding",true), + ContendDisposition = client.GetHeaderValue("Content-Disposition",true), + Manifest=client.GetHeaderValue("X-Object-Manifest",true), + PublicUrl=client.GetHeaderValue("X-Object-Public",true), + StorageUri=this.StorageUrl, + }; + info.SetPermissions(permissions); + return info; + case HttpStatusCode.NotFound: + return ObjectInfo.Empty; + default: + throw new WebException( + String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", + objectName, client.StatusCode)); + } + + } + catch (RetryException) + { + Log.WarnFormat("[RETRY FAIL] GetObjectInfo for {0} failed.",objectName); + return ObjectInfo.Empty; + } + catch (WebException e) + { + Log.Error( + String.Format("[FAIL] GetObjectInfo for {0} failed with unexpected status code {1}", + objectName, client.StatusCode), e); + throw; + } + } + } + + } + + public void CreateFolder(string account, string container, string folder) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(folder)) + throw new ArgumentNullException("folder", "The folder property can't be empty"); + Contract.EndContractBlock(); + + var folderUrl=String.Format("{0}/{1}",container,folder); + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Parameters.Clear(); + client.Headers.Add("Content-Type", @"application/directory"); + client.Headers.Add("Content-Length", "0"); + client.PutWithRetry(folderUrl, 3); + + if (client.StatusCode != HttpStatusCode.Created && client.StatusCode != HttpStatusCode.Accepted) + throw CreateWebException("CreateFolder", client.StatusCode); + } + } + + + + public ContainerInfo GetContainerInfo(string account, string container) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + Contract.EndContractBlock(); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Head(container); + switch (client.StatusCode) + { + case HttpStatusCode.OK: + case HttpStatusCode.NoContent: + var tags = client.GetMeta("X-Container-Meta-"); + var policies = client.GetMeta("X-Container-Policy-"); + + var containerInfo = new ContainerInfo + { + Account=account, + Name = container, + StorageUrl=this.StorageUrl.ToString(), + Count = + long.Parse(client.GetHeaderValue("X-Container-Object-Count")), + Bytes = long.Parse(client.GetHeaderValue("X-Container-Bytes-Used")), + BlockHash = client.GetHeaderValue("X-Container-Block-Hash"), + BlockSize=int.Parse(client.GetHeaderValue("X-Container-Block-Size")), + Last_Modified=client.LastModified, + Tags=tags, + Policies=policies + }; + + + return containerInfo; + case HttpStatusCode.NotFound: + return ContainerInfo.Empty; + default: + throw CreateWebException("GetContainerInfo", client.StatusCode); + } + } + } + + public void CreateContainer(string account, string container) + { + if (String.IsNullOrWhiteSpace(account)) + throw new ArgumentNullException("account"); + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + Contract.EndContractBlock(); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.PutWithRetry(container, 3); + var expectedCodes = new[] {HttpStatusCode.Created, HttpStatusCode.Accepted, HttpStatusCode.OK}; + if (!expectedCodes.Contains(client.StatusCode)) + throw CreateWebException("CreateContainer", client.StatusCode); + } + } + + public void DeleteContainer(string account, string container) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + Contract.EndContractBlock(); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.DeleteWithRetry(container, 3); + var expectedCodes = new[] {HttpStatusCode.NotFound, HttpStatusCode.NoContent}; + if (!expectedCodes.Contains(client.StatusCode)) + throw CreateWebException("DeleteContainer", client.StatusCode); + } + + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// This method should have no timeout or a very long one + //Asynchronously download the object specified by *objectName* in a specific *container* to + // a local file + public async Task GetObject(string account, string container, string objectName, string fileName,CancellationToken cancellationToken) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName", "The objectName property can't be empty"); + Contract.EndContractBlock(); + + try + { + //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient + //object to avoid concurrency errors. + // + //Download operations take a long time therefore they have no timeout. + using(var client = new RestClient(_baseClient) { Timeout = 0 }) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + //The container and objectName are relative names. They are joined with the client's + //BaseAddress to create the object's absolute address + var builder = client.GetAddressBuilder(container, objectName); + var uri = builder.Uri; + + //Download progress is reported to the Trace log + Log.InfoFormat("[GET] START {0}", objectName); + /*client.DownloadProgressChanged += (sender, args) => + Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}", + fileName, args.ProgressPercentage, + args.BytesReceived, + args.TotalBytesToReceive);*/ + var progress = new Progress(args => + { + Log.InfoFormat("[GET PROGRESS] {0} {1}% {2} of {3}", + fileName, args.ProgressPercentage, + args.BytesReceived, + args.TotalBytesToReceive); + if (DownloadProgressChanged!=null) + DownloadProgressChanged(this, args); + }); + + //Start downloading the object asynchronously + await client.DownloadFileTaskAsync(uri, fileName, cancellationToken,progress).ConfigureAwait(false); + + //Once the download completes + //Delete the local client object + } + //And report failure or completion + } + catch (Exception exc) + { + Log.ErrorFormat("[GET] FAIL {0} with {1}", objectName, exc); + throw; + } + + Log.InfoFormat("[GET] END {0}", objectName); + + + } + + public Task> PutHashMap(string account, string container, string objectName, TreeHash hash) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName"); + if (hash==null) + throw new ArgumentNullException("hash"); + if (String.IsNullOrWhiteSpace(Token)) + throw new InvalidOperationException("Invalid Token"); + if (StorageUrl == null) + throw new InvalidOperationException("Invalid Storage Url"); + Contract.EndContractBlock(); + + + //Don't use a timeout because putting the hashmap may be a long process + var client = new RestClient(_baseClient) { Timeout = 0 }; + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + //The container and objectName are relative names. They are joined with the client's + //BaseAddress to create the object's absolute address + var builder = client.GetAddressBuilder(container, objectName); + builder.Query = "format=json&hashmap"; + var uri = builder.Uri; + + + //Send the tree hash as Json to the server + client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream"; + var jsonHash = hash.ToJson(); + + client.Headers.Add("ETag",hash.MD5); + var uploadTask=client.UploadStringTask(uri, "PUT", jsonHash); + if (Log.IsDebugEnabled) + Log.DebugFormat("Hashes:\r\n{0}", jsonHash); + return uploadTask.ContinueWith(t => + { + + var empty = (IList)new List(); + + + //The server will respond either with 201-created if all blocks were already on the server + if (client.StatusCode == HttpStatusCode.Created) + { + //in which case we return an empty hash list + return empty; + } + //or with a 409-conflict and return the list of missing parts + //A 409 will cause an exception so we need to check t.IsFaulted to avoid propagating the exception + if (t.IsFaulted) + { + var ex = t.Exception.InnerException; + var we = ex as WebException; + var response = we.Response as HttpWebResponse; + if (response!=null && response.StatusCode==HttpStatusCode.Conflict) + { + //In case of 409 the missing parts will be in the response content + using (var stream = response.GetResponseStream()) + using(var reader=stream.GetLoggedReader(Log)) + { + //We used to have to cleanup the content before returning it because it contains + //error content after the list of hashes + // + //As of 30/1/2012, the result is a proper Json array so we don't need to read the content + //line by line + + var serializer = new JsonSerializer(); + serializer.Error += (sender, args) => Log.ErrorFormat("Deserialization error at [{0}] [{1}]", args.ErrorContext.Error, args.ErrorContext.Member); + var hashes = (List)serializer.Deserialize(reader, typeof(List)); + return hashes; + } + } + //Any other status code is unexpected and the exception should be rethrown + Log.LogError(response); + throw ex; + + } + + //Any other status code is unexpected but there was no exception. We can probably continue processing + Log.WarnFormat("Unexcpected status code when putting map: {0} - {1}",client.StatusCode,client.StatusDescription); + + return empty; + }); + + } + + + public async Task GetBlock(string account, string container, Uri relativeUrl, long start, long? end, CancellationToken cancellationToken) + { + if (String.IsNullOrWhiteSpace(Token)) + throw new InvalidOperationException("Invalid Token"); + if (StorageUrl == null) + throw new InvalidOperationException("Invalid Storage Url"); + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + if (relativeUrl == null) + throw new ArgumentNullException("relativeUrl"); + if (end.HasValue && end < 0) + throw new ArgumentOutOfRangeException("end"); + if (start < 0) + throw new ArgumentOutOfRangeException("start"); + Contract.EndContractBlock(); + + //Don't use a timeout because putting the hashmap may be a long process + using (var client = new RestClient(_baseClient) {Timeout = 0, RangeFrom = start, RangeTo = end}) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + var builder = client.GetAddressBuilder(container, relativeUrl.ToString()); + var uri = builder.Uri; + +/* client.DownloadProgressChanged += (sender, args) => + { + Log.DebugFormat("[GET PROGRESS] {0} {1}% {2} of {3}", + uri.Segments.Last(), args.ProgressPercentage, + args.BytesReceived, + args.TotalBytesToReceive); + DownloadProgressChanged(sender, args); + };*/ + var progress = new Progress(args => + { + Log.DebugFormat("[GET PROGRESS] {0} {1}% {2} of {3}", + uri.Segments.Last(), args.ProgressPercentage, + args.BytesReceived, + args.TotalBytesToReceive); + if (DownloadProgressChanged!=null) + DownloadProgressChanged(this, args); + }); + + + var result = await client.DownloadDataTaskAsync(uri, cancellationToken,progress).ConfigureAwait(false); + return result; + } + } + + public event UploadProgressChangedEventHandler UploadProgressChanged; + public event DownloadProgressChangedEventHandler DownloadProgressChanged; + + public async Task PostBlock(string account, string container, byte[] block, int offset, int count,CancellationToken token) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + if (block == null) + throw new ArgumentNullException("block"); + if (offset < 0 || offset >= block.Length) + throw new ArgumentOutOfRangeException("offset"); + if (count < 0 || count > block.Length) + throw new ArgumentOutOfRangeException("count"); + if (String.IsNullOrWhiteSpace(Token)) + throw new InvalidOperationException("Invalid Token"); + if (StorageUrl == null) + throw new InvalidOperationException("Invalid Storage Url"); + Contract.EndContractBlock(); + + + try + { + + //Don't use a timeout because putting the hashmap may be a long process + using (var client = new RestClient(_baseClient) { Timeout = 0 }) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + var builder = client.GetAddressBuilder(container, ""); + //We are doing an update + builder.Query = "update"; + var uri = builder.Uri; + + client.Headers[HttpRequestHeader.ContentType] = "application/octet-stream"; + + Log.InfoFormat("[BLOCK POST] START"); + +/* + client.UploadProgressChanged += (sender, args) => + { + Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}", + args.ProgressPercentage, args.BytesSent, + args.TotalBytesToSend); + UploadProgressChanged(sender, args); + }; +*/ + client.UploadFileCompleted += (sender, args) => + Log.InfoFormat("[BLOCK POST PROGRESS] Completed "); + + var progress=new Progress(args=> + { + Log.InfoFormat("[BLOCK POST PROGRESS] {0}% {1} of {2}", + args.ProgressPercentage, args.BytesSent, + args.TotalBytesToSend); + if (UploadProgressChanged!=null) + UploadProgressChanged(this, args); + }); + var buffer = new byte[count]; + Buffer.BlockCopy(block, offset, buffer, 0, count); + //Send the block + await client.UploadDataTaskAsync(uri, "POST", buffer,token,progress).ConfigureAwait(false); + Log.InfoFormat("[BLOCK POST] END"); + } + } + catch (TaskCanceledException ) + { + Log.Info("Aborting block"); + throw; + } + catch (Exception exc) + { + Log.ErrorFormat("[BLOCK POST] FAIL with \r{0}", exc); + throw; + } + } + + + public async Task GetHashMap(string account, string container, string objectName) + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName"); + if (String.IsNullOrWhiteSpace(Token)) + throw new InvalidOperationException("Invalid Token"); + if (StorageUrl == null) + throw new InvalidOperationException("Invalid Storage Url"); + Contract.EndContractBlock(); + + try + { + //WebClient, and by extension RestClient, are not thread-safe. Create a new RestClient + //object to avoid concurrency errors. + // + //Download operations take a long time therefore they have no timeout. + //TODO: Do they really? this is a hashmap operation, not a download + + //Start downloading the object asynchronously + using (var client = new RestClient(_baseClient) { Timeout = 0 }) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + //The container and objectName are relative names. They are joined with the client's + //BaseAddress to create the object's absolute address + var builder = client.GetAddressBuilder(container, objectName); + builder.Query = "format=json&hashmap"; + var uri = builder.Uri; + + + var json = await client.DownloadStringTaskAsync(uri).ConfigureAwait(false); + var treeHash = TreeHash.Parse(json); + Log.InfoFormat("[GET HASH] END {0}", objectName); + return treeHash; + } + } + catch (Exception exc) + { + Log.ErrorFormat("[GET HASH] END {0} with {1}", objectName, exc); + throw; + } + + } + + + /// + /// + /// + /// + /// + /// + /// + /// Optional hash value for the file. If no hash is provided, the method calculates a new hash + /// >This method should have no timeout or a very long one + public async Task PutObject(string account, string container, string objectName, string fileName, string hash = null, string contentType = "application/octet-stream") + { + if (String.IsNullOrWhiteSpace(container)) + throw new ArgumentNullException("container", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName", "The objectName property can't be empty"); + if (String.IsNullOrWhiteSpace(fileName)) + throw new ArgumentNullException("fileName", "The fileName property can't be empty"); +/* + if (!File.Exists(fileName) && !Directory.Exists(fileName)) + throw new FileNotFoundException("The file or directory does not exist",fileName); +*/ + + try + { + + using (var client = new RestClient(_baseClient) { Timeout = 0 }) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + var builder = client.GetAddressBuilder(container, objectName); + var uri = builder.Uri; + + string etag = hash ?? CalculateHash(fileName); + + client.Headers.Add("Content-Type", contentType); + client.Headers.Add("ETag", etag); + + + Log.InfoFormat("[PUT] START {0}", objectName); + client.UploadProgressChanged += (sender, args) => + { + using (ThreadContext.Stacks["PUT"].Push("Progress")) + { + Log.InfoFormat("{0} {1}% {2} of {3}", fileName, + args.ProgressPercentage, + args.BytesSent, args.TotalBytesToSend); + } + }; + + client.UploadFileCompleted += (sender, args) => + { + using (ThreadContext.Stacks["PUT"].Push("Progress")) + { + Log.InfoFormat("Completed {0}", fileName); + } + }; + if (contentType=="application/directory") + await client.UploadDataTaskAsync(uri, "PUT", new byte[0]).ConfigureAwait(false); + else + await client.UploadFileTaskAsync(uri, "PUT", fileName).ConfigureAwait(false); + } + + Log.InfoFormat("[PUT] END {0}", objectName); + } + catch (Exception exc) + { + Log.ErrorFormat("[PUT] END {0} with {1}", objectName, exc); + throw; + } + + } + + + private static string CalculateHash(string fileName) + { + Contract.Requires(!String.IsNullOrWhiteSpace(fileName)); + Contract.EndContractBlock(); + + string hash; + using (var hasher = MD5.Create()) + using(var stream=File.OpenRead(fileName)) + { + var hashBuilder=new StringBuilder(); + foreach (byte b in hasher.ComputeHash(stream)) + hashBuilder.Append(b.ToString("x2").ToLower()); + hash = hashBuilder.ToString(); + } + return hash; + } + + public void MoveObject(string account, string sourceContainer, string oldObjectName, string targetContainer, string newObjectName) + { + if (String.IsNullOrWhiteSpace(sourceContainer)) + throw new ArgumentNullException("sourceContainer", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(oldObjectName)) + throw new ArgumentNullException("oldObjectName", "The oldObjectName property can't be empty"); + if (String.IsNullOrWhiteSpace(targetContainer)) + throw new ArgumentNullException("targetContainer", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(newObjectName)) + throw new ArgumentNullException("newObjectName", "The newObjectName property can't be empty"); + Contract.EndContractBlock(); + + var targetUrl = targetContainer + "/" + newObjectName; + var sourceUrl = String.Format("/{0}/{1}", sourceContainer, oldObjectName); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Headers.Add("X-Move-From", Uri.EscapeUriString(sourceUrl)); + client.PutWithRetry(targetUrl, 3); + + var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created}; + if (!expectedCodes.Contains(client.StatusCode)) + throw CreateWebException("MoveObject", client.StatusCode); + } + } + + public void DeleteObject(string account, string sourceContainer, string objectName, bool isDirectory) + { + if (String.IsNullOrWhiteSpace(sourceContainer)) + throw new ArgumentNullException("sourceContainer", "The container property can't be empty"); + if (String.IsNullOrWhiteSpace(objectName)) + throw new ArgumentNullException("objectName", "The oldObjectName property can't be empty"); + Contract.EndContractBlock(); + + var targetUrl = FolderConstants.TrashContainer + "/" + objectName; +/* + if (isDirectory) + targetUrl = targetUrl + "?delimiter=/"; +*/ + + var sourceUrl = String.Format("/{0}/{1}", sourceContainer, objectName); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + client.Headers.Add("X-Move-From", Uri.EscapeUriString(sourceUrl)); + client.AllowedStatusCodes.Add(HttpStatusCode.NotFound); + Log.InfoFormat("[TRASH] [{0}] to [{1}]",sourceUrl,targetUrl); + client.PutWithRetry(targetUrl, 3); + + var expectedCodes = new[] {HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created,HttpStatusCode.NotFound}; + if (!expectedCodes.Contains(client.StatusCode)) + throw CreateWebException("DeleteObject", client.StatusCode); + } + } + + + private static WebException CreateWebException(string operation, HttpStatusCode statusCode) + { + return new WebException(String.Format("{0} failed with unexpected status code {1}", operation, statusCode)); + } + + +/* + public IEnumerable ListDirectories(ContainerInfo container) + { + var directories=this.ListObjects(container.Account, container.Name, "/"); + } +*/ + + public bool CanUpload(string account, ObjectInfo cloudFile) + { + Contract.Requires(!String.IsNullOrWhiteSpace(account)); + Contract.Requires(cloudFile!=null); + + using (var client = new RestClient(_baseClient)) + { + if (!String.IsNullOrWhiteSpace(account)) + client.BaseAddress = GetAccountUrl(account); + + + var parts = cloudFile.Name.Split('/'); + var folder = String.Join("/", parts,0,parts.Length-1); + + var fileUrl=String.Format("{0}/{1}/{2}.pithos.ignore",cloudFile.Container,folder,Guid.NewGuid()); + + client.Parameters.Clear(); + try + { + client.PutWithRetry(fileUrl, 3, @"application/octet-stream"); + + var expectedCodes = new[] { HttpStatusCode.OK, HttpStatusCode.NoContent, HttpStatusCode.Created}; + var result=(expectedCodes.Contains(client.StatusCode)); + DeleteObject(account, cloudFile.Container, fileUrl, cloudFile.IsDirectory); + return result; + } + catch + { + return false; + } + } + } + } + + public class ShareAccountInfo + { + public DateTime? last_modified { get; set; } + public string name { get; set; } + } +} diff --git a/trunk/Pithos.Network/Pithos.Network.csproj b/trunk/Pithos.Network/Pithos.Network.csproj index 0ede169..3c31936 100644 --- a/trunk/Pithos.Network/Pithos.Network.csproj +++ b/trunk/Pithos.Network/Pithos.Network.csproj @@ -1,277 +1,278 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} - Library - Properties - Pithos.Network - Pithos.Network - v4.0 - 512 - 0 - Client - - - true - full - true - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True - False - True - False - False - False - False - False - False - False - False - True - False - False - False - - - - - - - False - Full - Build - 0 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Test\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - - - pithos.snk - - - true - bin\Premium Debug\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - True - False - True - False - False - True - True - True - True - True - False - False - True - False - True - - - - - - - True - Full - Build - 2 - - - true - bin\Debug All\ - DEBUG;TRACE - full - AnyCPU - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - bin\Release\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Test\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - true - bin\x64\Premium Debug\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug All\ - DEBUG;TRACE - full - x64 - bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - false - - - - ..\Libraries\AsyncCtpLibrary.dll - - - ..\Libraries\log4net.dll - - - - - - - ..\Libraries\System.Threading.Tasks.Dataflow.dll - - - - - - - - - AssemblyVersion.cs - - - - - - - - - - Component - - - - - - - - - {A9AE40FF-1A21-414A-9FE7-3BE13644CC6D} - Newtonsoft.Json - - - {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} - ParallelExtensionsExtras - - - {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} - Pithos.Interfaces - - - - - Pithos.licenseheader - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {C8E2BC8B-C7F1-4222-855C-4B04A57FFDFD} + Library + Properties + Pithos.Network + Pithos.Network + v4.0 + 512 + 0 + Client + + + true + full + true + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + False + True + False + False + False + False + False + False + False + False + True + False + False + False + + + + + + + False + Full + Build + 0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Test\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + + + pithos.snk + + + true + bin\Premium Debug\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + True + False + True + False + False + True + True + True + True + True + False + False + True + False + True + + + + + + + True + Full + Build + 2 + + + true + bin\Debug All\ + DEBUG;TRACE + full + AnyCPU + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Test\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + true + bin\x64\Premium Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug All\ + DEBUG;TRACE + full + x64 + bin\Debug\Pithos.Network.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + false + + + + ..\Libraries\log4net.dll + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + + + + + ..\Libraries\System.Threading.Tasks.Dataflow.dll + + + + + + + + + AssemblyVersion.cs + + + + + + + + + + Component + + + + + + + + + {A9AE40FF-1A21-414A-9FE7-3BE13644CC6D} + Newtonsoft.Json + + + {C45218F8-09E7-4F57-85BC-5D8D2AC736A3} + ParallelExtensionsExtras + + + {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} + Pithos.Interfaces + + + + + Pithos.licenseheader + + + + + + \ No newline at end of file diff --git a/trunk/Pithos.Network/RestClient.cs b/trunk/Pithos.Network/RestClient.cs index 8eb30c8..9ba0e7b 100644 --- a/trunk/Pithos.Network/RestClient.cs +++ b/trunk/Pithos.Network/RestClient.cs @@ -1,617 +1,641 @@ -#region -/* ----------------------------------------------------------------------- - * - * - * 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. - * - * ----------------------------------------------------------------------- - */ -#endregion -using System.Collections.Specialized; -using System.Diagnostics; -using System.Diagnostics.Contracts; -using System.IO; -using System.Net; -using System.Reflection; -using System.Runtime.Serialization; -using System.Threading.Tasks; -using log4net; - - -namespace Pithos.Network -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - /// - /// TODO: Update summary. - /// - public class RestClient:WebClient - { - private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - public int Timeout { get; set; } - - public bool TimedOut { get; set; } - - public HttpStatusCode StatusCode { get; private set; } - - public string StatusDescription { get; set; } - - public long? RangeFrom { get; set; } - public long? RangeTo { get; set; } - - public int Retries { get; set; } - - private readonly Dictionary _parameters=new Dictionary(); - public Dictionary Parameters - { - get - { - Contract.Ensures(_parameters!=null); - return _parameters; - } - } - - - [ContractInvariantMethod] - private void Invariants() - { - Contract.Invariant(Headers!=null); - } - - public RestClient():base() - { - //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response - //Any value above 2^21-1 will result in an empty response. - //-1 essentially ignores the maximum length - HttpWebRequest.DefaultMaximumErrorResponseLength = -1; - } - - - public RestClient(RestClient other) - : base() - { - if (other==null) - //Log.ErrorFormat("[ERROR] No parameters provided to the rest client. \n{0}\n", other); - throw new ArgumentNullException("other"); - Contract.EndContractBlock(); - - //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response - //Any value above 2^21-1 will result in an empty response. - //-1 essentially ignores the maximum length - HttpWebRequest.DefaultMaximumErrorResponseLength = -1; - - - CopyHeaders(other); - Timeout = other.Timeout; - Retries = other.Retries; - BaseAddress = other.BaseAddress; - - foreach (var parameter in other.Parameters) - { - Parameters.Add(parameter.Key,parameter.Value); - } - - this.Proxy = other.Proxy; - } - - - protected override WebRequest GetWebRequest(Uri address) - { - TimedOut = false; - var webRequest = base.GetWebRequest(address); - var request = (HttpWebRequest)webRequest; - request.CookieContainer=new CookieContainer(); - request.ServicePoint.ConnectionLimit = 50; - if (IfModifiedSince.HasValue) - request.IfModifiedSince = IfModifiedSince.Value; - request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; - if(Timeout>0) - request.Timeout = Timeout; - - if (RangeFrom.HasValue) - { - if (RangeTo.HasValue) - request.AddRange(RangeFrom.Value, RangeTo.Value); - else - request.AddRange(RangeFrom.Value); - } - return request; - } - - public DateTime? IfModifiedSince { get; set; } - - //Asynchronous version - protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result) - { - Log.InfoFormat("[{0}] {1}", request.Method, request.RequestUri); - HttpWebResponse response = null; - - try - { - response = (HttpWebResponse)base.GetWebResponse(request, result); - } - catch (WebException exc) - { - if (!TryGetResponse(exc, request,out response)) - throw; - } - - StatusCode = response.StatusCode; - LastModified = response.LastModified; - StatusDescription = response.StatusDescription; - return response; - - } - - - //Synchronous version - protected override WebResponse GetWebResponse(WebRequest request) - { - HttpWebResponse response = null; - try - { - Log.InfoFormat("[{0}] {1}",request.Method,request.RequestUri); - response = (HttpWebResponse)base.GetWebResponse(request); - } - catch (WebException exc) - { - if (!TryGetResponse(exc, request,out response)) - throw; - } - - StatusCode = response.StatusCode; - LastModified = response.LastModified; - StatusDescription = response.StatusDescription; - return response; - } - - private bool TryGetResponse(WebException exc, WebRequest request,out HttpWebResponse response) - { - response = null; - //Fail on empty response - if (exc.Response == null) - { - Log.WarnFormat("[{0}] {1} {2}", request.Method, exc.Status, request.RequestUri); - return false; - } - - response = (exc.Response as HttpWebResponse); - var statusCode = (int)response.StatusCode; - //Succeed on allowed status codes - if (AllowedStatusCodes.Contains(response.StatusCode)) - { - if (Log.IsDebugEnabled) - Log.DebugFormat("[{0}] {1} {2}", request.Method, statusCode, request.RequestUri); - return true; - } - - Log.WarnFormat("[{0}] {1} {2}", request.Method, statusCode, request.RequestUri); - - //Does the response have any content to log? - if (exc.Response.ContentLength > 0) - { - var content = LogContent(exc.Response); - Log.ErrorFormat(content); - } - return false; - } - - private readonly List _allowedStatusCodes=new List{HttpStatusCode.NotModified}; - - public List AllowedStatusCodes - { - get - { - return _allowedStatusCodes; - } - } - - public DateTime LastModified { get; private set; } - - private static string LogContent(WebResponse webResponse) - { - if (webResponse == null) - throw new ArgumentNullException("webResponse"); - Contract.EndContractBlock(); - - //The response stream must be copied to avoid affecting other code by disposing of the - //original response stream. - var stream = webResponse.GetResponseStream(); - using(var memStream=new MemoryStream()) - using (var reader = new StreamReader(memStream)) - { - stream.CopyTo(memStream); - string content = reader.ReadToEnd(); - - stream.Seek(0,SeekOrigin.Begin); - return content; - } - } - - public string DownloadStringWithRetry(string address,int retries=0) - { - - if (address == null) - throw new ArgumentNullException("address"); - - var actualAddress = GetActualAddress(address); - - TraceStart("GET",actualAddress); - - var actualRetries = (retries == 0) ? Retries : retries; - - var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress); - - var task = Retry(() => - { - var content = DownloadString(uriString); - - if (StatusCode == HttpStatusCode.NoContent) - return String.Empty; - return content; - - }, actualRetries); - - try - { - var result = task.Result; - return result; - - } - catch (AggregateException exc) - { - //If the task fails, propagate the original exception - if (exc.InnerException!=null) - throw exc.InnerException; - throw; - } - } - - public void Head(string address,int retries=0) - { - AllowedStatusCodes.Add(HttpStatusCode.NotFound); - RetryWithoutContent(address, retries, "HEAD"); - } - - public void PutWithRetry(string address, int retries = 0, string contentType=null) - { - RetryWithoutContent(address, retries, "PUT",contentType); - } - - public void PostWithRetry(string address,string contentType) - { - RetryWithoutContent(address, 0, "POST",contentType); - } - - public void DeleteWithRetry(string address,int retries=0) - { - RetryWithoutContent(address, retries, "DELETE"); - } - - public string GetHeaderValue(string headerName,bool optional=false) - { - if (this.ResponseHeaders==null) - throw new InvalidOperationException("ResponseHeaders are null"); - Contract.EndContractBlock(); - - var values=this.ResponseHeaders.GetValues(headerName); - if (values != null) - return values[0]; - - if (optional) - return null; - //A required header was not found - throw new WebException(String.Format("The {0} header is missing", headerName)); - } - - public void SetNonEmptyHeaderValue(string headerName, string value) - { - if (String.IsNullOrWhiteSpace(value)) - return; - Headers.Add(headerName,value); - } - - private void RetryWithoutContent(string address, int retries, string method,string contentType=null) - { - if (address == null) - throw new ArgumentNullException("address"); - - var actualAddress = GetActualAddress(address); - var actualRetries = (retries == 0) ? Retries : retries; - - var task = Retry(() => - { - var uriString = String.Join("/",BaseAddress ,actualAddress); - var uri = new Uri(uriString); - var request = GetWebRequest(uri); - if (contentType!=null) - { - request.ContentType = contentType; - request.ContentLength = 0; - } - request.Method = method; - if (ResponseHeaders!=null) - ResponseHeaders.Clear(); - - TraceStart(method, uriString); - if (method == "PUT") - request.ContentLength = 0; - - //Have to use try/finally instead of using here, because WebClient needs a valid WebResponse object - //in order to return response headers - var response = (HttpWebResponse)GetWebResponse(request); - try - { - LastModified = response.LastModified; - StatusCode = response.StatusCode; - StatusDescription = response.StatusDescription; - } - finally - { - response.Close(); - } - - - return 0; - }, actualRetries); - - try - { - task.Wait(); - } - catch (AggregateException ex) - { - var exc = ex.InnerException; - if (exc is RetryException) - { - Log.ErrorFormat("[{0}] RETRY FAILED for {1} after {2} retries",method,address,retries); - } - else - { - Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, exc); - } - throw exc; - - } - catch(Exception ex) - { - Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, ex); - throw; - } - } - - private static void TraceStart(string method, string actualAddress) - { - Log.InfoFormat("[{0}] {1} {2}", method, DateTime.Now, actualAddress); - } - - private string GetActualAddress(string address) - { - if (Parameters.Count == 0) - return address; - var addressBuilder=new StringBuilder(address); - - bool isFirst = true; - foreach (var parameter in Parameters) - { - if(isFirst) - addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value); - else - addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value); - isFirst = false; - } - return addressBuilder.ToString(); - } - - public string DownloadStringWithRetry(Uri address,int retries=0) - { - if (address == null) - throw new ArgumentNullException("address"); - - var actualRetries = (retries == 0) ? Retries : retries; - var task = Retry(() => - { - var content = base.DownloadString(address); - - if (StatusCode == HttpStatusCode.NoContent) - return String.Empty; - return content; - - }, actualRetries); - - var result = task.Result; - return result; - } - - - /// - /// Copies headers from another RestClient - /// - /// The RestClient from which the headers are copied - public void CopyHeaders(RestClient source) - { - if (source == null) - throw new ArgumentNullException("source", "source can't be null"); - Contract.EndContractBlock(); - //The Headers getter initializes the property, it is never null - Contract.Assume(Headers!=null); - - CopyHeaders(source.Headers,Headers); - } - - /// - /// Copies headers from one header collection to another - /// - /// The source collection from which the headers are copied - /// The target collection to which the headers are copied - public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target) - { - if (source == null) - throw new ArgumentNullException("source", "source can't be null"); - if (target == null) - throw new ArgumentNullException("target", "target can't be null"); - Contract.EndContractBlock(); - - for (int i = 0; i < source.Count; i++) - { - target.Add(source.GetKey(i), source[i]); - } - } - - public void AssertStatusOK(string message) - { - if (StatusCode >= HttpStatusCode.BadRequest) - throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription)); - } - - - private Task Retry(Func original, int retryCount, TaskCompletionSource tcs = null) - { - if (original==null) - throw new ArgumentNullException("original"); - Contract.EndContractBlock(); - - if (tcs == null) - tcs = new TaskCompletionSource(); - Task.Factory.StartNew(original).ContinueWith(_original => - { - if (!_original.IsFaulted) - tcs.SetFromTask(_original); - else - { - var e = _original.Exception.InnerException; - var we = (e as WebException); - if (we==null) - tcs.SetException(e); - else - { - var statusCode = GetStatusCode(we); - - //Return null for 404 - if (statusCode == HttpStatusCode.NotFound) - tcs.SetResult(default(T)); - //Retry for timeouts and service unavailable - else if (we.Status == WebExceptionStatus.Timeout || - (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable)) - { - TimedOut = true; - if (retryCount == 0) - { - Log.ErrorFormat("[ERROR] Timed out too many times. \n{0}\n",e); - tcs.SetException(new RetryException("Timed out too many times.", e)); - } - else - { - Log.ErrorFormat( - "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout, - retryCount, e); - Retry(original, retryCount - 1, tcs); - } - } - else - tcs.SetException(e); - } - }; - }); - return tcs.Task; - } - - private HttpStatusCode GetStatusCode(WebException we) - { - if (we==null) - throw new ArgumentNullException("we"); - var statusCode = HttpStatusCode.RequestTimeout; - if (we.Response != null) - { - statusCode = ((HttpWebResponse) we.Response).StatusCode; - this.StatusCode = statusCode; - } - return statusCode; - } - - public UriBuilder GetAddressBuilder(string container, string objectName) - { - var builder = new UriBuilder(String.Join("/", BaseAddress, container, objectName)); - return builder; - } - - public Dictionary GetMeta(string metaPrefix) - { - if (String.IsNullOrWhiteSpace(metaPrefix)) - throw new ArgumentNullException("metaPrefix"); - Contract.EndContractBlock(); - - var keys = ResponseHeaders.AllKeys.AsQueryable(); - var dict = (from key in keys - where key.StartsWith(metaPrefix) - let name = key.Substring(metaPrefix.Length) - select new { Name = name, Value = ResponseHeaders[key] }) - .ToDictionary(t => t.Name, t => t.Value); - return dict; - } - - } - - public class RetryException:Exception - { - public RetryException() - :base() - { - - } - - public RetryException(string message) - :base(message) - { - - } - - public RetryException(string message,Exception innerException) - :base(message,innerException) - { - - } - - public RetryException(SerializationInfo info,StreamingContext context) - :base(info,context) - { - - } - } -} +#region +/* ----------------------------------------------------------------------- + * + * + * 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. + * + * ----------------------------------------------------------------------- + */ +#endregion +using System.Collections.Specialized; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.IO; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using log4net; + + +namespace Pithos.Network +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// + /// TODO: Update summary. + /// + public class RestClient:WebClient + { + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public int Timeout { get; set; } + + public bool TimedOut { get; set; } + + public HttpStatusCode StatusCode { get; private set; } + + public string StatusDescription { get; set; } + + public long? RangeFrom { get; set; } + public long? RangeTo { get; set; } + + public int Retries { get; set; } + + private readonly Dictionary _parameters=new Dictionary(); + public Dictionary Parameters + { + get + { + Contract.Ensures(_parameters!=null); + return _parameters; + } + } + + + [ContractInvariantMethod] + private void Invariants() + { + Contract.Invariant(Headers!=null); + } + + public RestClient():base() + { + //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response + //Any value above 2^21-1 will result in an empty response. + //-1 essentially ignores the maximum length + HttpWebRequest.DefaultMaximumErrorResponseLength = -1; + } + + + public RestClient(RestClient other) + : base() + { + if (other==null) + //Log.ErrorFormat("[ERROR] No parameters provided to the rest client. \n{0}\n", other); + throw new ArgumentNullException("other"); + Contract.EndContractBlock(); + + //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response + //Any value above 2^21-1 will result in an empty response. + //-1 essentially ignores the maximum length + HttpWebRequest.DefaultMaximumErrorResponseLength = -1; + + + CopyHeaders(other); + Timeout = other.Timeout; + Retries = other.Retries; + BaseAddress = other.BaseAddress; + + foreach (var parameter in other.Parameters) + { + Parameters.Add(parameter.Key,parameter.Value); + } + + this.Proxy = other.Proxy; + } + + + protected override WebRequest GetWebRequest(Uri address) + { + TimedOut = false; + var webRequest = base.GetWebRequest(address); + var request = (HttpWebRequest)webRequest; + request.CookieContainer=new CookieContainer(); + request.ServicePoint.ConnectionLimit = 50; + if (IfModifiedSince.HasValue) + request.IfModifiedSince = IfModifiedSince.Value; + request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; + if(Timeout>0) + request.Timeout = Timeout; + + if (RangeFrom.HasValue) + { + if (RangeTo.HasValue) + request.AddRange(RangeFrom.Value, RangeTo.Value); + else + request.AddRange(RangeFrom.Value); + } + return request; + } + + public DateTime? IfModifiedSince { get; set; } + + //Asynchronous version + protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result) + { + Log.InfoFormat("[{0}] {1}", request.Method, request.RequestUri); + HttpWebResponse response = null; + + try + { + response = (HttpWebResponse)base.GetWebResponse(request, result); + } + catch (WebException exc) + { + if (!TryGetResponse(exc, request,out response)) + throw; + } + + StatusCode = response.StatusCode; + LastModified = response.LastModified; + StatusDescription = response.StatusDescription; + return response; + + } + + + //Synchronous version + protected override WebResponse GetWebResponse(WebRequest request) + { + HttpWebResponse response = null; + try + { + Log.InfoFormat("[{0}] {1}",request.Method,request.RequestUri); + response = (HttpWebResponse)base.GetWebResponse(request); + } + catch (WebException exc) + { + if (!TryGetResponse(exc, request,out response)) + throw; + } + + StatusCode = response.StatusCode; + LastModified = response.LastModified; + StatusDescription = response.StatusDescription; + return response; + } + + private bool TryGetResponse(WebException exc, WebRequest request,out HttpWebResponse response) + { + response = null; + //Fail on empty response + if (exc.Response == null) + { + Log.WarnFormat("[{0}] {1} {2}", request.Method, exc.Status, request.RequestUri); + return false; + } + + response = (exc.Response as HttpWebResponse); + var statusCode = (int)response.StatusCode; + //Succeed on allowed status codes + if (AllowedStatusCodes.Contains(response.StatusCode)) + { + if (Log.IsDebugEnabled) + Log.DebugFormat("[{0}] {1} {2}", request.Method, statusCode, request.RequestUri); + return true; + } + + Log.WarnFormat("[{0}] {1} {2}", request.Method, statusCode, request.RequestUri); + + //Does the response have any content to log? + if (exc.Response.ContentLength > 0) + { + var content = LogContent(exc.Response); + Log.ErrorFormat(content); + } + return false; + } + + private readonly List _allowedStatusCodes=new List{HttpStatusCode.NotModified}; + + public List AllowedStatusCodes + { + get + { + return _allowedStatusCodes; + } + } + + public DateTime LastModified { get; private set; } + + private static string LogContent(WebResponse webResponse) + { + if (webResponse == null) + throw new ArgumentNullException("webResponse"); + Contract.EndContractBlock(); + + //The response stream must be copied to avoid affecting other code by disposing of the + //original response stream. + var stream = webResponse.GetResponseStream(); + using(var memStream=new MemoryStream()) + using (var reader = new StreamReader(memStream)) + { + stream.CopyTo(memStream); + string content = reader.ReadToEnd(); + + stream.Seek(0,SeekOrigin.Begin); + return content; + } + } + + public string DownloadStringWithRetry(string address,int retries=0) + { + + if (address == null) + throw new ArgumentNullException("address"); + + var actualAddress = GetActualAddress(address); + + TraceStart("GET",actualAddress); + + var actualRetries = (retries == 0) ? Retries : retries; + + var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress); + + var task = Retry(() => + { + var content = DownloadString(uriString); + + if (StatusCode == HttpStatusCode.NoContent) + return String.Empty; + return content; + + }, actualRetries); + + try + { + var result = task.Result; + return result; + + } + catch (AggregateException exc) + { + //If the task fails, propagate the original exception + if (exc.InnerException!=null) + throw exc.InnerException; + throw; + } + } + + public void Head(string address,int retries=0) + { + AllowedStatusCodes.Add(HttpStatusCode.NotFound); + RetryWithoutContent(address, retries, "HEAD"); + } + + public void PutWithRetry(string address, int retries = 0, string contentType=null) + { + RetryWithoutContent(address, retries, "PUT",contentType); + } + + public void PostWithRetry(string address,string contentType) + { + RetryWithoutContent(address, 0, "POST",contentType); + } + + public void DeleteWithRetry(string address,int retries=0) + { + RetryWithoutContent(address, retries, "DELETE"); + } + + public string GetHeaderValue(string headerName,bool optional=false) + { + if (this.ResponseHeaders==null) + throw new InvalidOperationException("ResponseHeaders are null"); + Contract.EndContractBlock(); + + var values=this.ResponseHeaders.GetValues(headerName); + if (values != null) + return values[0]; + + if (optional) + return null; + //A required header was not found + throw new WebException(String.Format("The {0} header is missing", headerName)); + } + + public void SetNonEmptyHeaderValue(string headerName, string value) + { + if (String.IsNullOrWhiteSpace(value)) + return; + Headers.Add(headerName,value); + } + + private void RetryWithoutContent(string address, int retries, string method,string contentType=null) + { + if (address == null) + throw new ArgumentNullException("address"); + + var actualAddress = GetActualAddress(address); + var actualRetries = (retries == 0) ? Retries : retries; + + var task = Retry(() => + { + var uriString = String.Join("/",BaseAddress ,actualAddress); + var uri = new Uri(uriString); + var request = GetWebRequest(uri); + if (contentType!=null) + { + request.ContentType = contentType; + request.ContentLength = 0; + } + request.Method = method; + if (ResponseHeaders!=null) + ResponseHeaders.Clear(); + + TraceStart(method, uriString); + if (method == "PUT") + request.ContentLength = 0; + + //Have to use try/finally instead of using here, because WebClient needs a valid WebResponse object + //in order to return response headers + var response = (HttpWebResponse)GetWebResponse(request); + try + { + LastModified = response.LastModified; + StatusCode = response.StatusCode; + StatusDescription = response.StatusDescription; + } + finally + { + response.Close(); + } + + + return 0; + }, actualRetries); + + try + { + task.Wait(); + } + catch (AggregateException ex) + { + var exc = ex.InnerException; + if (exc is RetryException) + { + Log.ErrorFormat("[{0}] RETRY FAILED for {1} after {2} retries",method,address,retries); + } + else + { + Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, exc); + } + throw exc; + + } + catch(Exception ex) + { + Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, ex); + throw; + } + } + + private static void TraceStart(string method, string actualAddress) + { + Log.InfoFormat("[{0}] {1} {2}", method, DateTime.Now, actualAddress); + } + + private string GetActualAddress(string address) + { + if (Parameters.Count == 0) + return address; + var addressBuilder=new StringBuilder(address); + + bool isFirst = true; + foreach (var parameter in Parameters) + { + if(isFirst) + addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value); + else + addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value); + isFirst = false; + } + return addressBuilder.ToString(); + } + + public string DownloadStringWithRetry(Uri address,int retries=0) + { + if (address == null) + throw new ArgumentNullException("address"); + + var actualRetries = (retries == 0) ? Retries : retries; + var task = Retry(() => + { + var content = base.DownloadString(address); + + if (StatusCode == HttpStatusCode.NoContent) + return String.Empty; + return content; + + }, actualRetries); + + var result = task.Result; + return result; + } + + + /// + /// Copies headers from another RestClient + /// + /// The RestClient from which the headers are copied + public void CopyHeaders(RestClient source) + { + if (source == null) + throw new ArgumentNullException("source", "source can't be null"); + Contract.EndContractBlock(); + //The Headers getter initializes the property, it is never null + Contract.Assume(Headers!=null); + + CopyHeaders(source.Headers,Headers); + } + + /// + /// Copies headers from one header collection to another + /// + /// The source collection from which the headers are copied + /// The target collection to which the headers are copied + public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target) + { + if (source == null) + throw new ArgumentNullException("source", "source can't be null"); + if (target == null) + throw new ArgumentNullException("target", "target can't be null"); + Contract.EndContractBlock(); + + for (int i = 0; i < source.Count; i++) + { + target.Add(source.GetKey(i), source[i]); + } + } + + public void AssertStatusOK(string message) + { + if (StatusCode >= HttpStatusCode.BadRequest) + throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription)); + } + + + private Task Retry(Func original, int retryCount, TaskCompletionSource tcs = null) + { + if (original==null) + throw new ArgumentNullException("original"); + Contract.EndContractBlock(); + + if (tcs == null) + tcs = new TaskCompletionSource(); + Task.Factory.StartNew(original).ContinueWith(_original => + { + if (!_original.IsFaulted) + tcs.SetFromTask(_original); + else + { + var e = _original.Exception.InnerException; + var we = (e as WebException); + if (we==null) + tcs.SetException(e); + else + { + var statusCode = GetStatusCode(we); + + //Return null for 404 + if (statusCode == HttpStatusCode.NotFound) + tcs.SetResult(default(T)); + //Retry for timeouts and service unavailable + else if (we.Status == WebExceptionStatus.Timeout || + (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable)) + { + TimedOut = true; + if (retryCount == 0) + { + Log.ErrorFormat("[ERROR] Timed out too many times. \n{0}\n",e); + tcs.SetException(new RetryException("Timed out too many times.", e)); + } + else + { + Log.ErrorFormat( + "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout, + retryCount, e); + Retry(original, retryCount - 1, tcs); + } + } + else + tcs.SetException(e); + } + }; + }); + return tcs.Task; + } + + private HttpStatusCode GetStatusCode(WebException we) + { + if (we==null) + throw new ArgumentNullException("we"); + var statusCode = HttpStatusCode.RequestTimeout; + if (we.Response != null) + { + statusCode = ((HttpWebResponse) we.Response).StatusCode; + this.StatusCode = statusCode; + } + return statusCode; + } + + public UriBuilder GetAddressBuilder(string container, string objectName) + { + var builder = new UriBuilder(String.Join("/", BaseAddress, container, objectName)); + return builder; + } + + public Dictionary GetMeta(string metaPrefix) + { + if (String.IsNullOrWhiteSpace(metaPrefix)) + throw new ArgumentNullException("metaPrefix"); + Contract.EndContractBlock(); + + var keys = ResponseHeaders.AllKeys.AsQueryable(); + var dict = (from key in keys + where key.StartsWith(metaPrefix) + let name = key.Substring(metaPrefix.Length) + select new { Name = name, Value = ResponseHeaders[key] }) + .ToDictionary(t => t.Name, t => t.Value); + return dict; + } + + + internal Task DownloadFileTaskAsync(Uri uri, string fileName, CancellationToken cancellationToken, IProgress progress) + { + cancellationToken.Register(CancelAsync); + DownloadProgressChangedEventHandler onDownloadProgressChanged = (o, e) => progress.Report(e); + this.DownloadProgressChanged += onDownloadProgressChanged; + return this.DownloadFileTaskAsync(uri, fileName).ContinueWith(t=> + { + this.DownloadProgressChanged -= onDownloadProgressChanged; + }); + } + + internal Task DownloadDataTaskAsync(Uri uri, CancellationToken cancellationToken, IProgress progress) + { + cancellationToken.Register(CancelAsync); + DownloadProgressChangedEventHandler onDownloadProgressChanged = (o, e) => progress.Report(e); + this.DownloadProgressChanged += onDownloadProgressChanged; + return this.DownloadDataTaskAsync(uri).ContinueWith(t => + { + this.DownloadProgressChanged -= onDownloadProgressChanged; + return t.Result; + }); + } + } + + public class RetryException:Exception + { + public RetryException() + :base() + { + + } + + public RetryException(string message) + :base(message) + { + + } + + public RetryException(string message,Exception innerException) + :base(message,innerException) + { + + } + + public RetryException(SerializationInfo info,StreamingContext context) + :base(info,context) + { + + } + } +} diff --git a/trunk/Pithos.ShellExtensions/Pithos.ShellExtensions.csproj b/trunk/Pithos.ShellExtensions/Pithos.ShellExtensions.csproj index 46868b6..961ffc6 100644 --- a/trunk/Pithos.ShellExtensions/Pithos.ShellExtensions.csproj +++ b/trunk/Pithos.ShellExtensions/Pithos.ShellExtensions.csproj @@ -1,431 +1,432 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {240B432F-1030-4623-BCC3-FF351D6C1B63} - Library - Properties - Pithos.ShellExtensions - Pithos.ShellExtensions - v4.0 - 512 - 0 - Client - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - False - False - True - False - False - False - False - False - False - False - False - True - False - False - True - - - - - - - False - Full - Build - 0 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - - - pithos.snk - - - true - bin\Test\ - DEBUG;TRACE - true - full - AnyCPU - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\Premium Debug\ - DEBUG;TRACE - true - full - AnyCPU - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\Debug All\ - DEBUG;TRACE - true - full - AnyCPU - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x86\Debug\ - DEBUG;TRACE - false - full - x86 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - true - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - bin\Release\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x86\Test\ - DEBUG;TRACE - true - full - x86 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x86\Premium Debug\ - DEBUG;TRACE - true - full - x86 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x86\Debug All\ - DEBUG;TRACE - true - full - x86 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - true - bin\x64\Debug\ - DEBUG;TRACE - false - full - x64 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - bin\Release\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x64\Test\ - DEBUG;TRACE - true - full - x64 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x64\Premium Debug\ - DEBUG;TRACE - true - full - x64 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - false - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - false - - - true - bin\x64\Debug All\ - DEBUG;TRACE - true - full - x64 - bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml - true - GlobalSuppressions.cs - prompt - MinimumRecommendedRules.ruleset - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets - ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules - true - false - - - - ..\Libraries\AsyncCtpLibrary.dll - - - ..\Libraries\log4net.dll - - - - - - - - - - - - - - - - - - AssemblyVersion.cs - - - - - - - - - - - - - - - - - - - Component - - - ProjectInstaller.cs - - - - True - True - Resources.resx - - - True - True - Reference.svcmap - - - - - - - - - - Pithos.licenseheader - - - - - - - Designer - - - - Designer - - - - Designer - - - Designer - - - Designer - - - Designer - - - - - - {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} - Pithos.Interfaces - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - WCF Proxy Generator - Reference.cs - - - - - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {240B432F-1030-4623-BCC3-FF351D6C1B63} + Library + Properties + Pithos.ShellExtensions + Pithos.ShellExtensions + v4.0 + 512 + 0 + Client + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + False + False + True + False + False + False + False + False + False + False + False + True + False + False + True + + + + + + + False + Full + Build + 0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + pithos.snk + + + true + bin\Test\ + DEBUG;TRACE + true + full + AnyCPU + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\Premium Debug\ + DEBUG;TRACE + true + full + AnyCPU + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\Debug All\ + DEBUG;TRACE + true + full + AnyCPU + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x86\Debug\ + DEBUG;TRACE + false + full + x86 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + bin\Release\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x86\Test\ + DEBUG;TRACE + true + full + x86 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x86\Premium Debug\ + DEBUG;TRACE + true + full + x86 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x86\Debug All\ + DEBUG;TRACE + true + full + x86 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + true + bin\x64\Debug\ + DEBUG;TRACE + false + full + x64 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x64\Test\ + DEBUG;TRACE + true + full + x64 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x64\Premium Debug\ + DEBUG;TRACE + true + full + x64 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + false + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + true + bin\x64\Debug All\ + DEBUG;TRACE + true + full + x64 + bin\Debug\Pithos.ShellExtensions.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + MinimumRecommendedRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + false + + + + ..\Libraries\log4net.dll + + + ..\packages\Microsoft.CompilerServices.AsyncTargetingPack.1.0.0\lib\net40\Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll + + + + + + + + + + + + + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + + + + Component + + + ProjectInstaller.cs + + + + True + True + Resources.resx + + + True + True + Reference.svcmap + + + + + + + + + + Pithos.licenseheader + + + + + + + + Designer + + + + Designer + + + + Designer + + + Designer + + + Designer + + + Designer + + + + + + {7EEFF32F-CCF8-436A-9E0B-F40434C09AF4} + Pithos.Interfaces + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + WCF Proxy Generator + Reference.cs + + + + + + + + + + + \ No newline at end of file diff --git a/trunk/packages/repositories.config b/trunk/packages/repositories.config index 5aaaf7c..a23d4b9 100644 --- a/trunk/packages/repositories.config +++ b/trunk/packages/repositories.config @@ -1,12 +1,14 @@ - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file -- 1.7.10.4