mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 18:08:26 +03:00
sync service
This commit is contained in:
parent
808fcea655
commit
818414eb37
7 changed files with 262 additions and 7 deletions
|
@ -22,7 +22,7 @@ namespace Bit.Core.Abstractions
|
|||
Task<CipherResponse> GetCipherAsync(string id);
|
||||
Task<FolderResponse> GetFolderAsync(string id);
|
||||
Task<ProfileResponse> GetProfileAsync();
|
||||
Task<SyncResponse> GetSyncAsync(string id);
|
||||
Task<SyncResponse> GetSyncAsync();
|
||||
Task PostAccountKeysAsync(KeysRequest request);
|
||||
Task<CipherResponse> PostCipherAsync(CipherRequest request);
|
||||
Task<CipherResponse> PostCipherCreateAsync(CipherCreateRequest request);
|
||||
|
|
19
src/Core/Abstractions/ISyncService.cs
Normal file
19
src/Core/Abstractions/ISyncService.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Response;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface ISyncService
|
||||
{
|
||||
bool SyncInProgress { get; set; }
|
||||
|
||||
Task<bool> FullSyncAsync(bool forceSync);
|
||||
Task<DateTime?> GetLastSyncAsync();
|
||||
Task SetLastSyncAsync(DateTime date);
|
||||
Task<bool> SyncDeleteCipherAsync(SyncCipherNotification notification);
|
||||
Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification);
|
||||
Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
|
||||
Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
|
||||
}
|
||||
}
|
20
src/Core/Enums/NotificationType.cs
Normal file
20
src/Core/Enums/NotificationType.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum NotificationType : byte
|
||||
{
|
||||
SyncCipherUpdate = 0,
|
||||
SyncCipherCreate = 1,
|
||||
SyncLoginDelete = 2,
|
||||
SyncFolderDelete = 3,
|
||||
SyncCiphers = 4,
|
||||
|
||||
SyncVault = 5,
|
||||
SyncOrgKeys = 6,
|
||||
SyncFolderCreate = 7,
|
||||
SyncFolderUpdate = 8,
|
||||
SyncCipherDelete = 9,
|
||||
SyncSettings = 10,
|
||||
|
||||
LogOut = 11,
|
||||
}
|
||||
}
|
34
src/Core/Models/Response/NotificationResponse.cs
Normal file
34
src/Core/Models/Response/NotificationResponse.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using Bit.Core.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
{
|
||||
public class PushNotificationResponse
|
||||
{
|
||||
public NotificationType Type { get; set; }
|
||||
public string Payload { get; set; }
|
||||
}
|
||||
|
||||
public class SyncCipherNotification
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public HashSet<string> CollectionIds { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
|
||||
public class SyncFolderNotification
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
}
|
||||
|
||||
public class UserNotification
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ namespace Bit.Core.Models.Response
|
|||
{
|
||||
public ProfileResponse Profile { get; set; }
|
||||
public List<FolderResponse> Folders { get; set; } = new List<FolderResponse>();
|
||||
public List<CollectionResponse> Collections { get; set; } = new List<CollectionResponse>();
|
||||
public List<CollectionDetailsResponse> Collections { get; set; } = new List<CollectionDetailsResponse>();
|
||||
public List<CipherResponse> Ciphers { get; set; } = new List<CipherResponse>();
|
||||
public DomainsResponse Domains { get; set; }
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ namespace Bit.Core.Services
|
|||
|
||||
#region Sync APIs
|
||||
|
||||
public Task<SyncResponse> GetSyncAsync(string id)
|
||||
public Task<SyncResponse> GetSyncAsync()
|
||||
{
|
||||
return SendAsync<object, SyncResponse>(HttpMethod.Get, "/sync", null, true, true);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class SyncService
|
||||
public class SyncService : ISyncService
|
||||
{
|
||||
private const string Keys_LastSyncFormat = "lastSync_{0}";
|
||||
|
||||
|
@ -58,7 +58,7 @@ namespace Bit.Core.Services
|
|||
return await _storageService.GetAsync<DateTime?>(string.Format(Keys_LastSyncFormat, userId));
|
||||
}
|
||||
|
||||
public async Task SetLastSync(DateTime date)
|
||||
public async Task SetLastSyncAsync(DateTime date)
|
||||
{
|
||||
var userId = await _userService.GetUserIdAsync();
|
||||
if(userId == null)
|
||||
|
@ -68,6 +68,188 @@ namespace Bit.Core.Services
|
|||
await _storageService.SaveAsync(string.Format(Keys_LastSyncFormat, userId), date);
|
||||
}
|
||||
|
||||
public async Task<bool> FullSyncAsync(bool forceSync)
|
||||
{
|
||||
SyncStarted();
|
||||
var isAuthenticated = await _userService.IsAuthenticatedAsync();
|
||||
if(!isAuthenticated)
|
||||
{
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
var now = DateTime.UtcNow;
|
||||
var needsSyncResult = await NeedsSyncingAsync(forceSync);
|
||||
var needsSync = needsSyncResult.Item1;
|
||||
var skipped = needsSyncResult.Item2;
|
||||
if(skipped)
|
||||
{
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
if(!needsSync)
|
||||
{
|
||||
await SetLastSyncAsync(now);
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
var userId = await _userService.GetUserIdAsync();
|
||||
try
|
||||
{
|
||||
var response = await _apiService.GetSyncAsync();
|
||||
await SyncProfileAsync(response.Profile);
|
||||
await SyncFoldersAsync(userId, response.Folders);
|
||||
await SyncCollectionsAsync(response.Collections);
|
||||
await SyncCiphersAsync(userId, response.Ciphers);
|
||||
await SyncSettingsAsync(userId, response.Domains);
|
||||
await SetLastSyncAsync(now);
|
||||
return SyncCompleted(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit)
|
||||
{
|
||||
SyncStarted();
|
||||
if(await _userService.IsAuthenticatedAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
var localFolder = await _folderService.GetAsync(notification.Id);
|
||||
if((!isEdit && localFolder == null) ||
|
||||
(isEdit && localFolder != null && localFolder.RevisionDate < notification.RevisionDate))
|
||||
{
|
||||
var remoteFolder = await _apiService.GetFolderAsync(notification.Id);
|
||||
if(remoteFolder != null)
|
||||
{
|
||||
var userId = await _userService.GetUserIdAsync();
|
||||
await _folderService.UpsertAsync(new FolderData(remoteFolder, userId));
|
||||
_messagingService.Send("syncedUpsertedFolder", new Dictionary<string, string>
|
||||
{
|
||||
["folderId"] = notification.Id
|
||||
});
|
||||
return SyncCompleted(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification)
|
||||
{
|
||||
SyncStarted();
|
||||
if(await _userService.IsAuthenticatedAsync())
|
||||
{
|
||||
await _folderService.DeleteAsync(notification.Id);
|
||||
_messagingService.Send("syncedDeletedFolder", new Dictionary<string, string>
|
||||
{
|
||||
["folderId"] = notification.Id
|
||||
});
|
||||
return SyncCompleted(true);
|
||||
}
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit)
|
||||
{
|
||||
SyncStarted();
|
||||
if(await _userService.IsAuthenticatedAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
var shouldUpdate = true;
|
||||
var localCipher = await _cipherService.GetAsync(notification.Id);
|
||||
if(localCipher != null && localCipher.RevisionDate >= notification.RevisionDate)
|
||||
{
|
||||
shouldUpdate = false;
|
||||
}
|
||||
|
||||
var checkCollections = false;
|
||||
if(shouldUpdate)
|
||||
{
|
||||
if(isEdit)
|
||||
{
|
||||
shouldUpdate = localCipher != null;
|
||||
checkCollections = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(notification.CollectionIds == null || notification.OrganizationId == null)
|
||||
{
|
||||
shouldUpdate = localCipher == null;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldUpdate = false;
|
||||
checkCollections = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!shouldUpdate && checkCollections && notification.OrganizationId != null &&
|
||||
notification.CollectionIds != null && notification.CollectionIds.Any())
|
||||
{
|
||||
var collections = await _collectionService.GetAllAsync();
|
||||
if(collections != null)
|
||||
{
|
||||
foreach(var c in collections)
|
||||
{
|
||||
if(notification.CollectionIds.Contains(c.Id))
|
||||
{
|
||||
shouldUpdate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(shouldUpdate)
|
||||
{
|
||||
var remoteCipher = await _apiService.GetCipherAsync(notification.Id);
|
||||
if(remoteCipher != null)
|
||||
{
|
||||
var userId = await _userService.GetUserIdAsync();
|
||||
await _cipherService.UpsertAsync(new CipherData(remoteCipher, userId));
|
||||
_messagingService.Send("syncedUpsertedCipher", new Dictionary<string, string>
|
||||
{
|
||||
["cipherId"] = notification.Id
|
||||
});
|
||||
return SyncCompleted(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(ApiException e)
|
||||
{
|
||||
if(e.Error != null && e.Error.StatusCode == System.Net.HttpStatusCode.NotFound && isEdit)
|
||||
{
|
||||
await _cipherService.DeleteAsync(notification.Id);
|
||||
_messagingService.Send("syncedDeletedCipher", new Dictionary<string, string>
|
||||
{
|
||||
["cipherId"] = notification.Id
|
||||
});
|
||||
return SyncCompleted(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SyncDeleteCipherAsync(SyncCipherNotification notification)
|
||||
{
|
||||
SyncStarted();
|
||||
if(await _userService.IsAuthenticatedAsync())
|
||||
{
|
||||
await _cipherService.DeleteAsync(notification.Id);
|
||||
_messagingService.Send("syncedDeletedCipher", new Dictionary<string, string>
|
||||
{
|
||||
["cipherId"] = notification.Id
|
||||
});
|
||||
return SyncCompleted(true);
|
||||
}
|
||||
return SyncCompleted(false);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private void SyncStarted()
|
||||
|
@ -76,7 +258,7 @@ namespace Bit.Core.Services
|
|||
_messagingService.Send("syncStarted");
|
||||
}
|
||||
|
||||
private bool SyncCompelted(bool successfully)
|
||||
private bool SyncCompleted(bool successfully)
|
||||
{
|
||||
SyncInProgress = false;
|
||||
_messagingService.Send("syncCompleted", new Dictionary<string, object> { ["successfully"] = successfully });
|
||||
|
|
Loading…
Reference in a new issue