@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
168 lines (167 loc) • 5.32 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as objects from '@sussudio/base/common/objects.mjs';
import { SYNC_SERVICE_URL_TYPE } from './userDataSync.mjs';
export function merge(localStorage, remoteStorage, baseStorage, storageKeys, logService) {
if (!remoteStorage) {
return {
remote: {
added: Object.keys(localStorage),
removed: [],
updated: [],
all: Object.keys(localStorage).length > 0 ? localStorage : null,
},
local: { added: {}, removed: [], updated: {} },
};
}
const localToRemote = compare(localStorage, remoteStorage);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return {
remote: { added: [], removed: [], updated: [], all: null },
local: { added: {}, removed: [], updated: {} },
};
}
const baseToRemote = baseStorage
? compare(baseStorage, remoteStorage)
: {
added: Object.keys(remoteStorage).reduce((r, k) => {
r.add(k);
return r;
}, new Set()),
removed: new Set(),
updated: new Set(),
};
const baseToLocal = baseStorage
? compare(baseStorage, localStorage)
: {
added: Object.keys(localStorage).reduce((r, k) => {
r.add(k);
return r;
}, new Set()),
removed: new Set(),
updated: new Set(),
};
const local = { added: {}, removed: [], updated: {} };
const remote = objects.deepClone(remoteStorage);
const isFirstTimeSync = !baseStorage;
// Added in local
for (const key of baseToLocal.added.values()) {
// If syncing for first time remote value gets precedence always,
// except for sync service type key - local value takes precedence for this key
if (key !== SYNC_SERVICE_URL_TYPE && isFirstTimeSync && baseToRemote.added.has(key)) {
continue;
}
remote[key] = localStorage[key];
}
// Updated in local
for (const key of baseToLocal.updated.values()) {
remote[key] = localStorage[key];
}
// Removed in local
for (const key of baseToLocal.removed.values()) {
// Do not remove from remote if key is not registered.
if (storageKeys.unregistered.includes(key)) {
continue;
}
delete remote[key];
}
// Added in remote
for (const key of baseToRemote.added.values()) {
const remoteValue = remoteStorage[key];
if (storageKeys.machine.includes(key)) {
logService.info(`GlobalState: Skipped adding ${key} in local storage because it is declared as machine scoped.`);
continue;
}
// Skip if the value is also added in local from the time it is last synced
if (baseStorage && baseToLocal.added.has(key)) {
continue;
}
const localValue = localStorage[key];
if (localValue && localValue.value === remoteValue.value) {
continue;
}
// Local sync service type value takes precedence if syncing for first time
if (key === SYNC_SERVICE_URL_TYPE && isFirstTimeSync && baseToLocal.added.has(key)) {
continue;
}
if (localValue) {
local.updated[key] = remoteValue;
} else {
local.added[key] = remoteValue;
}
}
// Updated in Remote
for (const key of baseToRemote.updated.values()) {
const remoteValue = remoteStorage[key];
if (storageKeys.machine.includes(key)) {
logService.info(
`GlobalState: Skipped updating ${key} in local storage because it is declared as machine scoped.`,
);
continue;
}
// Skip if the value is also updated or removed in local
if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) {
continue;
}
const localValue = localStorage[key];
if (localValue && localValue.value === remoteValue.value) {
continue;
}
local.updated[key] = remoteValue;
}
// Removed in remote
for (const key of baseToRemote.removed.values()) {
if (storageKeys.machine.includes(key)) {
logService.trace(
`GlobalState: Skipped removing ${key} in local storage because it is declared as machine scoped.`,
);
continue;
}
// Skip if the value is also updated or removed in local
if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) {
continue;
}
local.removed.push(key);
}
const result = compare(remoteStorage, remote);
return {
local,
remote: {
added: [...result.added],
updated: [...result.updated],
removed: [...result.removed],
all: result.added.size === 0 && result.removed.size === 0 && result.updated.size === 0 ? null : remote,
},
};
}
function compare(from, to) {
const fromKeys = Object.keys(from);
const toKeys = Object.keys(to);
const added = toKeys
.filter((key) => fromKeys.indexOf(key) === -1)
.reduce((r, key) => {
r.add(key);
return r;
}, new Set());
const removed = fromKeys
.filter((key) => toKeys.indexOf(key) === -1)
.reduce((r, key) => {
r.add(key);
return r;
}, new Set());
const updated = new Set();
for (const key of fromKeys) {
if (removed.has(key)) {
continue;
}
const value1 = from[key];
const value2 = to[key];
if (!objects.equals(value1, value2)) {
updated.add(key);
}
}
return { added, removed, updated };
}