mirror of
https://github.com/bitwarden/android.git
synced 2024-12-18 23:31:52 +03:00
remove incremental syncs and move to full syncs with revision checks
This commit is contained in:
parent
007ebadf16
commit
463b0fa28a
11 changed files with 99 additions and 101 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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?>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ namespace Bit.App.Services
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_syncService.FullSyncAsync();
|
_syncService.FullSyncAsync(true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue