remove incremental syncs and move to full syncs with revision checks

This commit is contained in:
Kyle Spearrin 2017-02-06 23:40:24 -05:00
parent 007ebadf16
commit 463b0fa28a
11 changed files with 99 additions and 101 deletions

View File

@ -1,5 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models.Api; using Bit.App.Models.Api;
using System;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
@ -7,5 +8,6 @@ namespace Bit.App.Abstractions
{ {
Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj); Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj);
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj); Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj);
Task<ApiResult<DateTime?>> GetAccountRevisionDate();
} }
} }

View File

@ -9,8 +9,7 @@ namespace Bit.App.Abstractions
Task<bool> SyncAsync(string id); Task<bool> SyncAsync(string id);
Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate); Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate);
Task<bool> SyncDeleteLoginAsync(string id); Task<bool> SyncDeleteLoginAsync(string id);
Task<bool> FullSyncAsync(); Task<bool> FullSyncAsync(bool forceSync = false);
Task<bool> IncrementalSyncAsync(TimeSpan syncThreshold); Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false);
Task<bool> IncrementalSyncAsync();
} }
} }

View File

@ -81,7 +81,7 @@ namespace Bit.App
MessagingCenter.Subscribe<Application, bool>(Current, "Resumed", async (sender, args) => MessagingCenter.Subscribe<Application, bool>(Current, "Resumed", async (sender, args) =>
{ {
await CheckLockAsync(args); await CheckLockAsync(args);
await Task.Run(() => IncrementalSyncAsync()).ConfigureAwait(false); await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
}); });
MessagingCenter.Subscribe<Application, bool>(Current, "Lock", (sender, args) => MessagingCenter.Subscribe<Application, bool>(Current, "Lock", (sender, args) =>
@ -153,6 +153,11 @@ namespace Bit.App
{ {
lockPinPage.PinControl.Entry.FocusWithDelay(); lockPinPage.PinControl.Entry.FocusWithDelay();
} }
if(Device.OS == TargetPlatform.Android)
{
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
}
} }
private void SetMainPageFromAutofill() private void SetMainPageFromAutofill()
@ -166,44 +171,6 @@ namespace Bit.App
_uri = null; _uri = null;
} }
private async Task IncrementalSyncAsync()
{
if(_connectivity.IsConnected)
{
var attempt = 0;
do
{
try
{
await _syncService.IncrementalSyncAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
break;
}
catch(WebException)
{
Debug.WriteLine("Failed to incremental sync.");
if(attempt >= 1)
{
break;
}
else
{
await Task.Delay(1000);
}
attempt++;
}
catch(Exception e) when(e is TaskCanceledException || e is OperationCanceledException)
{
Debug.WriteLine("Cancellation exception.");
break;
}
} while(attempt <= 1);
}
else
{
Debug.WriteLine("Not connected.");
}
}
private async Task FullSyncAsync() private async Task FullSyncAsync()
{ {
if(_connectivity.IsConnected) if(_connectivity.IsConnected)
@ -213,7 +180,7 @@ namespace Bit.App
{ {
try try
{ {
await _syncService.FullSyncAsync().ConfigureAwait(false); await _syncService.FullSyncAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
break; break;
} }
catch(WebException) catch(WebException)

View File

@ -218,7 +218,7 @@ namespace Bit.App.Pages
_pushNotification.Register(); _pushNotification.Register();
} }
var task = Task.Run(async () => await _syncService.FullSyncAsync()); var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new MainPage(); Application.Current.MainPage = new MainPage();
} }
} }

View File

@ -183,7 +183,7 @@ namespace Bit.App.Pages
_pushNotification.Register(); _pushNotification.Register();
} }
var task = Task.Run(async () => await _syncService.FullSyncAsync()); var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new MainPage(); Application.Current.MainPage = new MainPage();
} }
} }

View File

@ -94,7 +94,7 @@ namespace Bit.App.Pages
} }
_userDialogs.ShowLoading(AppResources.Syncing, MaskType.Black); _userDialogs.ShowLoading(AppResources.Syncing, MaskType.Black);
var succeeded = await _syncService.FullSyncAsync(); var succeeded = await _syncService.FullSyncAsync(true);
_userDialogs.HideLoading(); _userDialogs.HideLoading();
if(succeeded) if(succeeded)
{ {

View File

@ -10,6 +10,8 @@ namespace Bit.App.Repositories
{ {
public class AccountsApiRepository : BaseApiRepository, IAccountsApiRepository public class AccountsApiRepository : BaseApiRepository, IAccountsApiRepository
{ {
private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public AccountsApiRepository( public AccountsApiRepository(
IConnectivity connectivity, IConnectivity connectivity,
IHttpService httpService, IHttpService httpService,
@ -82,5 +84,54 @@ namespace Bit.App.Repositories
} }
} }
} }
public virtual async Task<ApiResult<DateTime?>> GetAccountRevisionDate()
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<DateTime?>();
}
var tokenStateResponse = await HandleTokenStateAsync<DateTime?>();
if(!tokenStateResponse.Succeeded)
{
return tokenStateResponse;
}
using(var client = HttpService.Client)
{
var requestMessage = new TokenHttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/revision-date")),
};
try
{
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return await HandleErrorAsync<DateTime?>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if(responseContent.Contains("null"))
{
return ApiResult<DateTime?>.Success(null, response.StatusCode);
}
long ms;
if(!long.TryParse(responseContent, out ms))
{
return await HandleErrorAsync<DateTime?>(response).ConfigureAwait(false);
}
return ApiResult<DateTime?>.Success(_epoc.AddMilliseconds(ms), response.StatusCode);
}
catch
{
return HandledWebException<DateTime?>();
}
}
}
} }
} }

View File

@ -60,7 +60,7 @@ namespace Bit.App.Repositories
var requestMessage = new HttpRequestMessage var requestMessage = new HttpRequestMessage
{ {
Method = HttpMethod.Post, Method = HttpMethod.Post,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token")), RequestUri = new Uri(client.BaseAddress, "connect/token"),
Content = new FormUrlEncodedContent(new TokenRequest Content = new FormUrlEncodedContent(new TokenRequest
{ {
Email = "abcdefgh", Email = "abcdefgh",
@ -97,7 +97,7 @@ namespace Bit.App.Repositories
var requestMessage = new HttpRequestMessage var requestMessage = new HttpRequestMessage
{ {
Method = HttpMethod.Post, Method = HttpMethod.Post,
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/token")), RequestUri = new Uri(client.BaseAddress, "connect/token"),
Content = new FormUrlEncodedContent(new Dictionary<string, string> Content = new FormUrlEncodedContent(new Dictionary<string, string>
{ {
{ "grant_type", "refresh_token" }, { "grant_type", "refresh_token" },
@ -119,7 +119,7 @@ namespace Bit.App.Repositories
TokenService.Token = tokenResponse.AccessToken; TokenService.Token = tokenResponse.AccessToken;
TokenService.RefreshToken = tokenResponse.RefreshToken; TokenService.RefreshToken = tokenResponse.RefreshToken;
} }
catch catch(Exception ee)
{ {
return webException.Invoke(); return webException.Invoke();
} }

View File

@ -88,7 +88,7 @@ namespace Bit.App.Services
{ {
break; break;
} }
_syncService.FullSyncAsync(); _syncService.FullSyncAsync(true);
break; break;
default: default:
break; break;

View File

@ -15,6 +15,7 @@ namespace Bit.App.Services
private readonly ICipherApiRepository _cipherApiRepository; private readonly ICipherApiRepository _cipherApiRepository;
private readonly IFolderApiRepository _folderApiRepository; private readonly IFolderApiRepository _folderApiRepository;
private readonly ILoginApiRepository _loginApiRepository; private readonly ILoginApiRepository _loginApiRepository;
private readonly IAccountsApiRepository _accountsApiRepository;
private readonly IFolderRepository _folderRepository; private readonly IFolderRepository _folderRepository;
private readonly ILoginRepository _loginRepository; private readonly ILoginRepository _loginRepository;
private readonly IAuthService _authService; private readonly IAuthService _authService;
@ -24,6 +25,7 @@ namespace Bit.App.Services
ICipherApiRepository cipherApiRepository, ICipherApiRepository cipherApiRepository,
IFolderApiRepository folderApiRepository, IFolderApiRepository folderApiRepository,
ILoginApiRepository loginApiRepository, ILoginApiRepository loginApiRepository,
IAccountsApiRepository accountsApiRepository,
IFolderRepository folderRepository, IFolderRepository folderRepository,
ILoginRepository loginRepository, ILoginRepository loginRepository,
IAuthService authService, IAuthService authService,
@ -32,6 +34,7 @@ namespace Bit.App.Services
_cipherApiRepository = cipherApiRepository; _cipherApiRepository = cipherApiRepository;
_folderApiRepository = folderApiRepository; _folderApiRepository = folderApiRepository;
_loginApiRepository = loginApiRepository; _loginApiRepository = loginApiRepository;
_accountsApiRepository = accountsApiRepository;
_folderRepository = folderRepository; _folderRepository = folderRepository;
_loginRepository = loginRepository; _loginRepository = loginRepository;
_authService = authService; _authService = authService;
@ -126,13 +129,30 @@ namespace Bit.App.Services
return true; return true;
} }
public async Task<bool> FullSyncAsync() public async Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false)
{
DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null);
if(lastSync != null && DateTime.UtcNow - lastSync.Value < syncThreshold)
{
return false;
}
return await FullSyncAsync(forceSync).ConfigureAwait(false);
}
public async Task<bool> FullSyncAsync(bool forceSync = false)
{ {
if(!_authService.IsAuthenticated) if(!_authService.IsAuthenticated)
{ {
return false; return false;
} }
if(!forceSync && !(await NeedsToSyncAsync()))
{
_settings.AddOrUpdateValue(Constants.LastSync, DateTime.UtcNow);
return false;
}
SyncStarted(); SyncStarted();
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -168,64 +188,22 @@ namespace Bit.App.Services
return true; return true;
} }
public async Task<bool> IncrementalSyncAsync(TimeSpan syncThreshold) private async Task<bool> NeedsToSyncAsync()
{ {
DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null); DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null);
if(lastSync != null && DateTime.UtcNow - lastSync.Value < syncThreshold) if(!lastSync.HasValue)
{ {
return false; return true;
} }
return await IncrementalSyncAsync().ConfigureAwait(false); var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDate();
} if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue &&
accountRevisionDate.Result.Value > lastSync)
public async Task<bool> IncrementalSyncAsync()
{
if(!_authService.IsAuthenticated)
{ {
return false; return true;
} }
var now = DateTime.UtcNow; return false;
DateTime? lastSync = _settings.GetValueOrDefault<DateTime?>(Constants.LastSync, null);
if(lastSync == null)
{
return await FullSyncAsync().ConfigureAwait(false);
}
SyncStarted();
var ciphers = await _cipherApiRepository.GetByRevisionDateWithHistoryAsync(lastSync.Value).ConfigureAwait(false);
if(!ciphers.Succeeded)
{
SyncCompleted(false);
if(Application.Current != null && (ciphers.StatusCode == System.Net.HttpStatusCode.Forbidden
|| ciphers.StatusCode == System.Net.HttpStatusCode.Unauthorized))
{
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
return false;
}
var logins = ciphers.Result.Revised.Where(c => c.Type == Enums.CipherType.Login).ToDictionary(s => s.Id);
var folders = ciphers.Result.Revised.Where(c => c.Type == Enums.CipherType.Folder).ToDictionary(f => f.Id);
var loginTask = SyncLoginsAsync(logins, false);
var folderTask = SyncFoldersAsync(folders, false);
var deleteTask = DeleteCiphersAsync(ciphers.Result.Deleted);
await Task.WhenAll(loginTask, folderTask, deleteTask).ConfigureAwait(false);
if(folderTask.Exception != null || loginTask.Exception != null || deleteTask.Exception != null)
{
SyncCompleted(false);
return false;
}
_settings.AddOrUpdateValue(Constants.LastSync, now);
SyncCompleted(true);
return true;
} }
private async Task SyncFoldersAsync(IDictionary<string, CipherResponse> serverFolders, bool deleteMissing) private async Task SyncFoldersAsync(IDictionary<string, CipherResponse> serverFolders, bool deleteMissing)

View File

@ -19,6 +19,7 @@ namespace Bit.App
private void Init() private void Init()
{ {
//BaseAddress = new Uri("http://192.168.1.3:4000");
BaseAddress = new Uri("https://api.bitwarden.com"); BaseAddress = new Uri("https://api.bitwarden.com");
DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
} }