sync: v3 compatible sync

This commit is contained in:
Abdullah Atta
2024-05-16 13:12:37 +05:00
parent 7ad546a863
commit 98b5143bfe
6 changed files with 682 additions and 49 deletions
+168
View File
@@ -0,0 +1,168 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Notesnook.API.Services
{
public class SyncDeviceService
{
private static string UserSyncDirectoryPath(string userId) => Path.Join("sync", userId);
private static string UserDeviceDirectoryPath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserSyncDirectoryPath(userId), deviceId);
private static string PendingIdsFilePath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserDeviceDirectoryPath(userId, deviceId), "pending");
private static string UnsyncedIdsFilePath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserDeviceDirectoryPath(userId, deviceId), "unsynced");
private static string ResetSyncFilePath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserDeviceDirectoryPath(userId, deviceId), "reset-sync");
public static async Task<string[]> GetUnsyncedIdsAsync(string userId, string deviceId)
{
try
{
return await File.ReadAllLinesAsync(UnsyncedIdsFilePath(userId, deviceId));
}
catch
{
return Array.Empty<string>();
}
}
public static async Task<string[]> FetchUnsyncedIdsAsync(string userId, string deviceId)
{
if (IsSyncReset(userId, deviceId)) return Array.Empty<string>();
if (UnsyncedIdsFileLocks.TryGetValue(deviceId, out SemaphoreSlim fileLock) && fileLock.CurrentCount == 0)
await fileLock.WaitAsync();
try
{
var unsyncedIds = await GetUnsyncedIdsAsync(userId, deviceId);
if (IsSyncPending(userId, deviceId))
{
unsyncedIds = unsyncedIds.Union(await File.ReadAllLinesAsync(PendingIdsFilePath(userId, deviceId))).ToArray();
}
if (unsyncedIds.Length == 0) return Array.Empty<string>();
File.Delete(UnsyncedIdsFilePath(userId, deviceId));
await File.WriteAllLinesAsync(PendingIdsFilePath(userId, deviceId), unsyncedIds);
return unsyncedIds;
}
catch
{
return Array.Empty<string>();
}
finally
{
if (fileLock != null && fileLock.CurrentCount == 0) fileLock.Release();
}
}
public static async Task WritePendingIdsAsync(string userId, string deviceId, IEnumerable<string> ids)
{
await File.WriteAllLinesAsync(PendingIdsFilePath(userId, deviceId), ids);
}
public static bool IsSyncReset(string userId, string deviceId)
{
return File.Exists(ResetSyncFilePath(userId, deviceId));
}
public static bool IsSyncPending(string userId, string deviceId)
{
return File.Exists(PendingIdsFilePath(userId, deviceId));
}
public static bool IsUnsynced(string userId, string deviceId)
{
return File.Exists(UnsyncedIdsFilePath(userId, deviceId));
}
public static void Reset(string userId, string deviceId)
{
File.Delete(ResetSyncFilePath(userId, deviceId));
File.Delete(PendingIdsFilePath(userId, deviceId));
}
public static bool IsDeviceRegistered(string userId, string deviceId)
{
return Directory.Exists(UserDeviceDirectoryPath(userId, deviceId));
}
public static IEnumerable<string> ListDevices(string userId)
{
return Directory.EnumerateDirectories(UserSyncDirectoryPath(userId)).Select((path) => Path.GetFileName(path));
}
public static void ResetDevices(string userId)
{
if (File.Exists(UserSyncDirectoryPath(userId))) File.Delete(UserSyncDirectoryPath(userId));
Directory.CreateDirectory(UserSyncDirectoryPath(userId));
}
private static readonly Dictionary<string, SemaphoreSlim> UnsyncedIdsFileLocks = new();
public static async Task AddIdsToOtherDevicesAsync(string userId, string deviceId, List<string> ids)
{
foreach (var id in ListDevices(userId))
{
if (id == deviceId || IsSyncReset(userId, id)) continue;
if (!UnsyncedIdsFileLocks.TryGetValue(id, out SemaphoreSlim fileLock))
{
fileLock = new SemaphoreSlim(1, 1);
UnsyncedIdsFileLocks.Add(id, fileLock);
}
await fileLock.WaitAsync();
try
{
if (!IsDeviceRegistered(userId, id)) Directory.CreateDirectory(UserDeviceDirectoryPath(userId, id));
var oldIds = await GetUnsyncedIdsAsync(userId, id);
await File.WriteAllLinesAsync(UnsyncedIdsFilePath(userId, id), ids.Union(oldIds));
}
finally
{
fileLock.Release();
}
}
}
public static void RegisterDevice(string userId, string deviceId)
{
Directory.CreateDirectory(UserDeviceDirectoryPath(userId, deviceId));
File.Create(ResetSyncFilePath(userId, deviceId)).Close();
}
public static void UnregisterDevice(string userId, string deviceId)
{
try
{
Directory.Delete(UserDeviceDirectoryPath(userId, deviceId), true);
}
catch { }
}
}
}
+48 -21
View File
@@ -174,40 +174,68 @@ namespace Notesnook.API.Services
public async Task<bool> DeleteUserAsync(string userId, string jti)
{
var cc = new CancellationTokenSource();
try
{
await Slogger<UserService>.Info(nameof(DeleteUserAsync), "Deleting user account", userId);
SyncDeviceService.ResetDevices(userId);
var cc = new CancellationTokenSource();
Repositories.Notes.DeleteByUserId(userId);
Repositories.Notebooks.DeleteByUserId(userId);
Repositories.Shortcuts.DeleteByUserId(userId);
Repositories.Contents.DeleteByUserId(userId);
Repositories.Settings.DeleteByUserId(userId);
Repositories.LegacySettings.DeleteByUserId(userId);
Repositories.Attachments.DeleteByUserId(userId);
Repositories.Reminders.DeleteByUserId(userId);
Repositories.Relations.DeleteByUserId(userId);
Repositories.Colors.DeleteByUserId(userId);
Repositories.Tags.DeleteByUserId(userId);
Repositories.Vaults.DeleteByUserId(userId);
Repositories.UsersSettings.Delete((u) => u.UserId == userId);
Repositories.Monographs.DeleteMany((m) => m.UserId == userId);
if (!Constants.IS_SELF_HOSTED)
{
await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
UserId = userId
});
}
var result = await unit.Commit();
await Slogger<UserService>.Info(nameof(DeleteUserAsync), "User account deleted", userId, result.ToString());
if (!result) return false;
await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
SendToAll = false,
OriginTokenId = jti,
UserId = userId,
Message = new Message
if (!Constants.IS_SELF_HOSTED)
{
Type = "logout",
Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
UserId = userId
});
}
});
await S3Service.DeleteDirectoryAsync(userId);
await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
SendToAll = false,
OriginTokenId = jti,
UserId = userId,
Message = new Message
{
Type = "logout",
Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
}
});
return await unit.Commit();
await S3Service.DeleteDirectoryAsync(userId);
return result;
}
catch (Exception ex)
{
await Slogger<UserService>.Error(nameof(DeleteUserAsync), "User account not deleted", userId, ex.ToString());
}
return false;
}
public async Task<bool> ResetUserAsync(string userId, bool removeAttachments)
{
SyncDeviceService.ResetDevices(userId);
var cc = new CancellationTokenSource();
Repositories.Notes.DeleteByUserId(userId);
@@ -229,7 +257,6 @@ namespace Notesnook.API.Services
userSettings.AttachmentsKey = null;
userSettings.VaultKey = null;
userSettings.Profile = null;
userSettings.LastSynced = 0;
await Repositories.UsersSettings.UpsertAsync(userSettings, (s) => s.UserId == userId);