From 5b2e4db44ee763804b13cb1bfcb543f787648a61 Mon Sep 17 00:00:00 2001 From: pkanavos Date: Wed, 26 Sep 2012 12:19:29 +0300 Subject: [PATCH] Fixed blocking issue --- .../Preferences/AddAccountViewModel.cs | 9 +- .../Preferences/PreferencesView.xaml | 2 - .../Preferences/PreferencesViewModel.cs | 66 +++++----- .../SelectiveSynch/SelectiveSynchViewModel.cs | 11 +- trunk/Pithos.Core/Agents/PollAgent.cs | 3 +- trunk/Pithos.Core/Agents/SelectiveUris.cs | 2 +- trunk/Pithos.Core/PithosMonitor.cs | 10 +- trunk/Pithos.Network/CloudFilesClient.cs | 130 +++++++++++++------- trunk/Pithos.Network/ICloudClient.cs | 12 +- trunk/Pithos.Network/WebExtensions.cs | 24 ++-- 10 files changed, 162 insertions(+), 107 deletions(-) diff --git a/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs b/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs index a39bd34..f4f43b1 100644 --- a/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs +++ b/trunk/Pithos.Client.WPF/Preferences/AddAccountViewModel.cs @@ -373,12 +373,9 @@ namespace Pithos.Client.WPF.Preferences 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); + var client = new CloudFilesClient(AccountName, Token) { AuthenticationUrl = CurrentServer,/*Proxy=Proxy */}; + await client.Authenticate().ConfigureAwait(false); + await client.ListContainers(AccountName).ConfigureAwait(false); HasValidCredentials = true; ValidationMessage = "Credentials Validated"; } diff --git a/trunk/Pithos.Client.WPF/Preferences/PreferencesView.xaml b/trunk/Pithos.Client.WPF/Preferences/PreferencesView.xaml index d5fe64f..01031ff 100644 --- a/trunk/Pithos.Client.WPF/Preferences/PreferencesView.xaml +++ b/trunk/Pithos.Client.WPF/Preferences/PreferencesView.xaml @@ -1,8 +1,6 @@  _accountsToAdd=new List(); - public void AddAccount() + public async void AddAccount() { var wizard = new AddAccountViewModel(); if (_windowManager.ShowDialog(wizard) == true) @@ -450,7 +450,7 @@ namespace Pithos.Client.WPF.Preferences AuthenticationUrl = newAccount.ServerUrl, UsePithos = true }; - InitializeSelectiveFolders(newAccount, client); + await InitializeSelectiveFolders(newAccount, client); //TODO:Add the "pithos" container as a default selection @@ -475,37 +475,41 @@ namespace Pithos.Client.WPF.Preferences } - private void InitializeSelectiveFolders(AccountSettings newAccount, CloudFilesClient client,int retries=3) + private async Task InitializeSelectiveFolders(AccountSettings newAccount, CloudFilesClient client,int retries=3) { - try - { - client.Authenticate(); - - var containers = client.ListContainers(newAccount.AccountName); - var containerUris = from container in containers - select String.Format(@"{0}/v1/{1}/{2}", - newAccount.ServerUrl, newAccount.AccountName, - container.Name); - - newAccount.SelectiveFolders.AddRange(containerUris.ToArray()); - - var objectInfos = (from container in containers - from dir in client.ListObjects(newAccount.AccountName, container.Name) - where container.Name.ToString() != "trash" - select dir).ToList(); - var tree = objectInfos.ToTree(); - - var selected = (from root in tree - from child in root - select child.Uri.ToString()).ToArray(); - newAccount.SelectiveFolders.AddRange(selected); - } - catch (WebException) + while(true) { - if (retries>0) - InitializeSelectiveFolders(newAccount,client,retries-1); - else - throw; + try + { + await client.Authenticate().ConfigureAwait(false); + + var containers = await client.ListContainers(newAccount.AccountName).ConfigureAwait(false); + var containerUris = from container in containers + select String.Format(@"{0}/v1/{1}/{2}", + newAccount.ServerUrl, newAccount.AccountName, + container.Name); + + newAccount.SelectiveFolders.AddRange(containerUris.ToArray()); + + var objectInfos = (from container in containers + from dir in client.ListObjects(newAccount.AccountName, container.Name) + where container.Name.ToString() != "trash" + select dir).ToList(); + var tree = objectInfos.ToTree(); + + var selected = (from root in tree + from child in root + select child.Uri.ToString()).ToArray(); + newAccount.SelectiveFolders.AddRange(selected); + return; + } + catch (WebException) + { + if (retries > 0) + retries--; + else + throw; + } } } diff --git a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs index e6dff26..05eaec8 100644 --- a/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs +++ b/trunk/Pithos.Client.WPF/SelectiveSynch/SelectiveSynchViewModel.cs @@ -94,21 +94,22 @@ namespace Pithos.Client.WPF.SelectiveSynch //_monitor = monitor; _events = events; _apiKey = apiKey; - //IsEnabled = account.SelectiveSyncEnabled; + //IsEnabled = account.SelectiveSyncEnabled; TaskEx.Run(()=>LoadRootNode(forSelectiveActivation)); } - private void LoadRootNode(bool forSelectiveActivation) + private async void LoadRootNode(bool forSelectiveActivation) { //TODO: Check this var client = new CloudFilesClient(AccountName,_apiKey){AuthenticationUrl=Account.ServerUrl,UsePithos=true}; - client.Authenticate(); + await client.Authenticate().ConfigureAwait(false); //NEED to get the local folders here as well, // and combine them with the cloud folders - var dirs = from container in client.ListContainers(AccountName) + var containerInfos = await client.ListContainers(AccountName).ConfigureAwait(false); + var dirs = from container in containerInfos where container.Name.ToUnescapedString() != "trash" select new DirectoryRecord { @@ -126,7 +127,7 @@ namespace Pithos.Client.WPF.SelectiveSynch { DisplayName=account.name, Uri=client.StorageUrl.Combine("../"+ account.name), - Directories=(from container in client.ListContainers(account.name) + Directories=(from container in client.ListContainers(account.name).Result select new DirectoryRecord { DisplayName=container.Name.ToUnescapedString(), diff --git a/trunk/Pithos.Core/Agents/PollAgent.cs b/trunk/Pithos.Core/Agents/PollAgent.cs index 240040a..c9c5773 100644 --- a/trunk/Pithos.Core/Agents/PollAgent.cs +++ b/trunk/Pithos.Core/Agents/PollAgent.cs @@ -297,7 +297,8 @@ namespace Pithos.Core.Agents var client = new CloudFilesClient(accountInfo); //We don't need to check the trash container - var containers = client.ListContainers(accountInfo.UserName) + var allContainers=await client.ListContainers(accountInfo.UserName).ConfigureAwait(false); + var containers = allContainers .Where(c=>c.Name.ToString()!="trash") .ToList(); diff --git a/trunk/Pithos.Core/Agents/SelectiveUris.cs b/trunk/Pithos.Core/Agents/SelectiveUris.cs index facd42f..94b6ac2 100644 --- a/trunk/Pithos.Core/Agents/SelectiveUris.cs +++ b/trunk/Pithos.Core/Agents/SelectiveUris.cs @@ -28,7 +28,7 @@ namespace Pithos.Core.Agents SelectivePaths = new ConcurrentDictionary>(); } - readonly Dictionary _selectiveEnabled = new Dictionary(); + readonly ConcurrentDictionary _selectiveEnabled = new ConcurrentDictionary(); /* public void SetIsSelectiveEnabled(AccountInfo account,bool value) diff --git a/trunk/Pithos.Core/PithosMonitor.cs b/trunk/Pithos.Core/PithosMonitor.cs index 5bc606b..ec51f4f 100644 --- a/trunk/Pithos.Core/PithosMonitor.cs +++ b/trunk/Pithos.Core/PithosMonitor.cs @@ -227,12 +227,10 @@ namespace Pithos.Core } _cancellationSource = new CancellationTokenSource(); - lock (this) - { - CloudClient = new CloudFilesClient(UserName, ApiKey) - {UsePithos = true, AuthenticationUrl = AuthenticationUrl}; - _accountInfo = CloudClient.Authenticate(); - } + CloudClient = new CloudFilesClient(UserName, ApiKey) + {UsePithos = true, AuthenticationUrl = AuthenticationUrl}; + + _accountInfo = await CloudClient.Authenticate().ConfigureAwait(false); _accountInfo.SiteUri = AuthenticationUrl; _accountInfo.AccountPath = RootPath; diff --git a/trunk/Pithos.Network/CloudFilesClient.cs b/trunk/Pithos.Network/CloudFilesClient.cs index 22aeb0a..7680b3c 100644 --- a/trunk/Pithos.Network/CloudFilesClient.cs +++ b/trunk/Pithos.Network/CloudFilesClient.cs @@ -68,7 +68,7 @@ namespace Pithos.Network { [Export(typeof(ICloudClient))] - public class CloudFilesClient:ICloudClient + public class CloudFilesClient:ICloudClient,IDisposable { private const string TOKEN_HEADER = "X-Auth-Token"; private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -93,6 +93,13 @@ namespace Pithos.Network private readonly string _emptyGuid = Guid.Empty.ToString(); private readonly Uri _emptyUri = new Uri("",UriKind.Relative); + private HttpClientHandler _httpClientHandler = new HttpClientHandler + { + AllowAutoRedirect = true, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + UseCookies = true, + }; + public string Token { @@ -216,7 +223,7 @@ namespace Pithos.Network throw new WebException(String.Format("{0} with code {1} - {2}", message, statusCode, response.ReasonPhrase)); } - public AccountInfo Authenticate() + public async Task Authenticate() { if (String.IsNullOrWhiteSpace(UserName)) throw new InvalidOperationException("UserName is empty"); @@ -235,14 +242,7 @@ namespace Pithos.Network var groups = new List(); - var httpClientHandler = new HttpClientHandler - { - AllowAutoRedirect = true, - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - UseCookies = true, - }; - - using (var authClient = new HttpClient(httpClientHandler){ BaseAddress = new Uri(AuthenticationUrl),Timeout=TimeSpan.FromSeconds(30) }) + using (var authClient = new HttpClient(_httpClientHandler,false){ BaseAddress = new Uri(AuthenticationUrl),Timeout=TimeSpan.FromSeconds(30) }) //using (var authClient = new RestClient{BaseAddress=AuthenticationUrl}) { /* if (Proxy != null) @@ -257,7 +257,7 @@ namespace Pithos.Network string storageUrl; string token; - using (var response = authClient.GetAsyncWithRetries(new Uri(VersionPath, UriKind.Relative),3).Result) // .DownloadStringWithRetryRelative(new Uri(VersionPath, UriKind.Relative), 3); + using (var response = await authClient.GetAsyncWithRetries(new Uri(VersionPath, UriKind.Relative),3).ConfigureAwait(false)) // .DownloadStringWithRetryRelative(new Uri(VersionPath, UriKind.Relative), 3); { AssertStatusOK(response,"Authentication failed"); @@ -292,14 +292,14 @@ namespace Pithos.Network RootAddressUri = new Uri(rootUrl); - _baseHttpClient = new HttpClient(httpClientHandler) + _baseHttpClient = new HttpClient(_httpClientHandler,false) { BaseAddress = StorageUrl, Timeout = TimeSpan.FromSeconds(30) }; _baseHttpClient.DefaultRequestHeaders.Add(TOKEN_HEADER, token); - _baseHttpClientNoTimeout = new HttpClient(httpClientHandler) + _baseHttpClientNoTimeout = new HttpClient(_httpClientHandler,false) { BaseAddress = StorageUrl, Timeout = TimeSpan.FromMilliseconds(-1) @@ -349,42 +349,58 @@ namespace Pithos.Network } } - public IList ListContainers(string account) + public async Task> ListContainers(string account) { var targetUrl = GetTargetUrl(account); - var targetUri=new Uri(String.Format("{0}?format=json",targetUrl)); - var result = GetStringAsync(targetUri, "List Containers failed").Result; + var targetUri = new Uri(String.Format("{0}?format=json", targetUrl)); + var result = await GetStringAsync(targetUri, "List Containers failed").ConfigureAwait(false); if (String.IsNullOrWhiteSpace(result)) return new List(); - var infos = JsonConvert.DeserializeObject>(result); + var infos = JsonConvert.DeserializeObject>(result); foreach (var info in infos) { info.Account = account; } return infos; - /* 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.DownloadStringWithRetryRelative(_emptyUri, 3); - client.AssertStatusOK("List Containers failed"); + } - if (client.StatusCode == HttpStatusCode.NoContent) - return new List(); - var infos = JsonConvert.DeserializeObject>(content); + //public IList ListContainers(string account) + //{ + + // var targetUrl = GetTargetUrl(account); + // var targetUri=new Uri(String.Format("{0}?format=json",targetUrl)); + // var result = GetStringAsync(targetUri, "List Containers failed").Result; + // if (String.IsNullOrWhiteSpace(result)) + // return new List(); + // var infos = JsonConvert.DeserializeObject>(result); + // foreach (var info in infos) + // { + // info.Account = account; + // } + // return infos; + // /* using (var client = new RestClient(_baseClient)) + // { + // if (!String.IsNullOrWhiteSpace(account)) + // client.BaseAddress = GetAccountUrl(account); - foreach (var info in infos) - { - info.Account = account; - } - return infos; - } */ + // client.Parameters.Clear(); + // client.Parameters.Add("format", "json"); + // var content = client.DownloadStringWithRetryRelative(_emptyUri, 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) { @@ -398,7 +414,7 @@ namespace Pithos.Network if (Log.IsDebugEnabled) Log.DebugFormat("START"); var targetUri = new Uri(String.Format("{0}?format=json", RootAddressUri), UriKind.Absolute); - var content=GetStringAsync(targetUri, "ListSharingAccounts failed", since).Result; + var content=TaskEx.Run(async ()=>await GetStringAsync(targetUri, "ListSharingAccounts failed", since).ConfigureAwait(false)).Result; //If the result is empty, return an empty list, var infos = String.IsNullOrWhiteSpace(content) @@ -456,9 +472,8 @@ namespace Pithos.Network 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) + var containers = (from account in ListSharingAccounts() + let conts = ListContainers(account.name).Result from container in conts select container).ToList(); var items = from container in containers @@ -848,7 +863,8 @@ namespace Pithos.Network var containerUri = GetTargetUri(account).Combine(container); var targetUri = new Uri(String.Format("{0}?format=json", containerUri), UriKind.Absolute); - var content = GetStringAsync(targetUri, "ListObjects failed", since).Result; + + var content =TaskEx.Run(async ()=>await GetStringAsync(targetUri, "ListObjects failed", since).ConfigureAwait(false)).Result; //304 will result in an empty string. Empty containers return an empty json array if (String.IsNullOrWhiteSpace(content)) @@ -920,7 +936,7 @@ namespace Pithos.Network var containerUri = GetTargetUri(account).Combine(container); var targetUri = new Uri(String.Format("{0}?format=json&path={1}", containerUri,folder), UriKind.Absolute); - var content = GetStringAsync(targetUri, "ListObjects failed", since).Result; + var content = TaskEx.Run(async ()=>await GetStringAsync(targetUri, "ListObjects failed", since).ConfigureAwait(false)).Result; //304 will result in an empty string. Empty containers return an empty json array if (String.IsNullOrWhiteSpace(content)) @@ -2065,6 +2081,36 @@ namespace Pithos.Network } } } + + ~CloudFilesClient() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_httpClientHandler!=null) + _httpClientHandler.Dispose(); + if (_baseClient!=null) + _baseClient.Dispose(); + if(_baseHttpClient!=null) + _baseHttpClient.Dispose(); + if (_baseHttpClientNoTimeout!=null) + _baseHttpClientNoTimeout.Dispose(); + } + _httpClientHandler = null; + _baseClient = null; + _baseHttpClient = null; + _baseHttpClientNoTimeout = null; + } } public class ShareAccountInfo diff --git a/trunk/Pithos.Network/ICloudClient.cs b/trunk/Pithos.Network/ICloudClient.cs index ebef1ee..526bfa5 100644 --- a/trunk/Pithos.Network/ICloudClient.cs +++ b/trunk/Pithos.Network/ICloudClient.cs @@ -60,7 +60,7 @@ namespace Pithos.Network string Token { get; set; } bool UsePithos { get; set; } - AccountInfo Authenticate(); + Task Authenticate(); //WebProxy Proxy { get; set; } double DownloadPercentLimit { get; set; } double UploadPercentLimit { get; set; } @@ -68,7 +68,7 @@ namespace Pithos.Network #region Container operations - IList ListContainers(string account); + Task> ListContainers(string account); IList ListObjects(string account, Uri container, DateTime? since = null); IList ListObjects(string account, Uri container, Uri folder, DateTime? since = null); bool ContainerExists(string account, Uri container); @@ -126,20 +126,20 @@ namespace Pithos.Network public bool UsePithos { get; set; } - public AccountInfo Authenticate() + public Task Authenticate() { Contract.Requires(!String.IsNullOrWhiteSpace(ApiKey), "ApiKey must be filled before calling Authenticate"); Contract.Requires(!String.IsNullOrWhiteSpace(UserName), "UserName must be filled before calling Authenticate"); - return default(AccountInfo); + return default(Task); } - public IList ListContainers(string account) + public Task> ListContainers(string account) { Contract.Requires(!String.IsNullOrWhiteSpace(Token)); Contract.Requires(StorageUrl!=null); - return default(IList); + return default(Task>); } public IList ListSharedObjects(HashSet knownContainers, DateTime? since) diff --git a/trunk/Pithos.Network/WebExtensions.cs b/trunk/Pithos.Network/WebExtensions.cs index 5755144..5c55803 100644 --- a/trunk/Pithos.Network/WebExtensions.cs +++ b/trunk/Pithos.Network/WebExtensions.cs @@ -122,7 +122,7 @@ namespace Pithos.Network { try { - var result = await func(); + var result = await func().ConfigureAwait(false); return result; } catch (Exception exc) @@ -141,7 +141,7 @@ namespace Pithos.Network HttpStatusCode.RedirectMethod,HttpStatusCode.NotModified,HttpStatusCode.TemporaryRedirect,HttpStatusCode.RedirectKeepVerb}; while (retries > 0) { - var result = await func(); + var result = await func().ConfigureAwait(false); if (result.IsSuccessStatusCode || acceptedCodes.Contains(result.StatusCode)) return result; @@ -152,7 +152,7 @@ namespace Pithos.Network if (result.StatusCode == HttpStatusCode.ServiceUnavailable) { Log.InfoFormat("[UNAVAILABLE] Waiting before retry: {0}",result.ReasonPhrase); - await TaskEx.Delay(waitTime); + await TaskEx.Delay(waitTime).ConfigureAwait(false); //increase the timeout for repeated timeouts if (waitTime> call = () => _baseHttpClient.SendAsync(request); - using (var response = await client.SendAsyncWithRetries(request,3)) + using (var response = await client.SendAsyncWithRetries(request,3).ConfigureAwait(false)) { if (response.StatusCode == HttpStatusCode.NoContent) return String.Empty; - var content = await response.Content.ReadAsStringAsync(); + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return content; } @@ -219,7 +219,17 @@ namespace Pithos.Network { if (Log.IsDebugEnabled) Log.DebugFormat("[REQUEST] {0}",message); - var result = await client.SendAsync(message,completionOption,cancellationToken); + HttpResponseMessage result; + try + { + result = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); + } + catch (Exception exc) + { + Log.FatalFormat("Unexpected error while sending:\n{0}\n{1}",message,exc); + throw; + } + if (result.IsSuccessStatusCode || acceptedCodes.Contains(result.StatusCode)) { if (Log.IsDebugEnabled) @@ -238,7 +248,7 @@ namespace Pithos.Network { Log.WarnFormat("[UNAVAILABLE] Waiting before retrying [{0}]:[{1}] due to [{2}]",message.Method, message.RequestUri,result.ReasonPhrase); - await TaskEx.Delay(waitTime); + await TaskEx.Delay(waitTime).ConfigureAwait(false); //increase the timeout for repeated timeouts if (waitTime