mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
refactor: remove localLamport completely from sync system
- Remove localLamport and lastSyncedLamport from all interfaces and models - Simplify sync logic to use vector clocks exclusively for change tracking - Remove Lamport-based conflict detection and sync status logic - Update UI components to remove Lamport display elements - Clean up backwards compatibility utilities - Fix getVectorClock import error in sync.service.ts - Remove debug alert from sync-safety-backup.service.ts This simplifies the sync system by eliminating the dual tracking mechanism that was causing complications and false conflicts. Vector clocks provide better causality tracking for distributed sync.
This commit is contained in:
parent
48bff6b46d
commit
658c29a8d7
12 changed files with 91 additions and 668 deletions
|
|
@ -106,31 +106,23 @@ if (comparison === VectorClockComparison.CONCURRENT) {
|
|||
2. **SyncService**: Merges vector clocks during download, includes in upload
|
||||
3. **getSyncStatusFromMetaFiles**: Uses vector clocks for conflict detection
|
||||
|
||||
## Migration from Lamport Timestamps
|
||||
## Vector Clock Implementation
|
||||
|
||||
The system maintains backwards compatibility with existing Lamport timestamps:
|
||||
The system uses vector clocks exclusively for conflict detection:
|
||||
|
||||
### Automatic Migration
|
||||
### How It Works
|
||||
|
||||
```typescript
|
||||
// If no vector clock exists, create from Lamport timestamp
|
||||
if (!meta.vectorClock && meta.localLamport > 0) {
|
||||
meta.vectorClock = { [clientId]: meta.localLamport };
|
||||
}
|
||||
```
|
||||
- Each client maintains its own counter in the vector clock
|
||||
- Counters increment on local changes only
|
||||
- Vector clocks are compared to detect concurrent changes
|
||||
- No false conflicts from timestamp-based comparisons
|
||||
|
||||
### Dual Support
|
||||
### Current Fields
|
||||
|
||||
- New installations use vector clocks immediately
|
||||
- Existing installations migrate transparently
|
||||
- Both systems coexist during transition period
|
||||
|
||||
### Field Mapping
|
||||
|
||||
| Old Field | New Field | Purpose |
|
||||
| ------------------- | ----------------------- | ------------------- |
|
||||
| `localLamport` | `vectorClock[clientId]` | Track local changes |
|
||||
| `lastSyncedLamport` | `lastSyncedVectorClock` | Track sync state |
|
||||
| Field | Purpose |
|
||||
| ----------------------- | -------------------------------- |
|
||||
| `vectorClock` | Track changes across all clients |
|
||||
| `lastSyncedVectorClock` | Track last synced state |
|
||||
|
||||
## API Reference
|
||||
|
||||
|
|
|
|||
|
|
@ -66,18 +66,11 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ T.F.SYNC.D_CONFLICT.LAMPORT_CLOCK | translate }}</th>
|
||||
<th>{{ T.F.SYNC.D_CONFLICT.LAST_WRITE | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>{{ T.F.SYNC.D_CONFLICT.REMOTE | translate }}</td>
|
||||
<td
|
||||
[matTooltip]="remote.localLamport"
|
||||
[attr.data-label]="T.F.SYNC.D_CONFLICT.LAMPORT_CLOCK | translate"
|
||||
>
|
||||
{{ shortenLamport(remote.localLamport) }}
|
||||
</td>
|
||||
<td
|
||||
[matTooltip]="remote.lastUpdateAction"
|
||||
[attr.data-label]="T.F.SYNC.D_CONFLICT.LAST_WRITE | translate"
|
||||
|
|
@ -87,12 +80,6 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{{ T.F.SYNC.D_CONFLICT.LOCAL | translate }}</td>
|
||||
<td
|
||||
[matTooltip]="local.localLamport"
|
||||
[attr.data-label]="T.F.SYNC.D_CONFLICT.LAMPORT_CLOCK | translate"
|
||||
>
|
||||
{{ shortenLamport(local.localLamport) }}
|
||||
</td>
|
||||
<td
|
||||
[matTooltip]="local.lastUpdateAction"
|
||||
[attr.data-label]="T.F.SYNC.D_CONFLICT.LAST_WRITE | translate"
|
||||
|
|
|
|||
|
|
@ -88,11 +88,6 @@ export class DialogSyncConflictComponent {
|
|||
}
|
||||
}
|
||||
|
||||
shortenLamport(lamport?: number | null): string {
|
||||
if (!lamport) return '-';
|
||||
return lamport.toString().slice(-5);
|
||||
}
|
||||
|
||||
shortenAction(actionStr?: string | null): string {
|
||||
if (!actionStr) return '?';
|
||||
return actionStr.trim().split(/\s+/)[0];
|
||||
|
|
@ -149,11 +144,8 @@ export class DialogSyncConflictComponent {
|
|||
return changeCount;
|
||||
}
|
||||
|
||||
// Fallback to Lamport clock
|
||||
const lastSyncedLamport = this.local.lastSyncedLamport || 0;
|
||||
const currentLamport =
|
||||
side === 'remote' ? this.remote.localLamport : this.local.localLamport;
|
||||
return Math.max(0, currentLamport - lastSyncedLamport);
|
||||
// No vector clock available
|
||||
return 0;
|
||||
}
|
||||
|
||||
private shouldConfirmOverwrite(resolution: DialogConflictResolutionResult): boolean {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ export class SyncSafetyBackupService {
|
|||
constructor() {
|
||||
// Subscribe to the onBeforeUpdateLocal event
|
||||
this._pfapiService.pf.ev.on('onBeforeUpdateLocal', async (eventData) => {
|
||||
alert(2);
|
||||
try {
|
||||
pfLog(1, 'SyncSafetyBackupService: Received onBeforeUpdateLocal event', {
|
||||
modelsToUpdate: eventData.modelsToUpdate,
|
||||
|
|
|
|||
|
|
@ -139,8 +139,6 @@ export class SyncWrapperService {
|
|||
localVectorClock: r.conflictData?.local.vectorClock,
|
||||
remoteVectorClock: r.conflictData?.remote.vectorClock,
|
||||
localLastSyncedVectorClock: r.conflictData?.local.lastSyncedVectorClock,
|
||||
localLamport: r.conflictData?.local.localLamport,
|
||||
remoteLamport: r.conflictData?.remote.localLamport,
|
||||
conflictReason: r.conflictData?.reason,
|
||||
additional: r.conflictData?.additional,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { validateLocalMeta } from '../util/validate-local-meta';
|
|||
import { PFEventEmitter } from '../util/events';
|
||||
import { devError } from '../../../util/dev-error';
|
||||
import { incrementVectorClock, limitVectorClockSize } from '../util/vector-clock';
|
||||
import { getVectorClock, withVectorClock } from '../util/backwards-compat';
|
||||
|
||||
export const DEFAULT_META_MODEL: LocalMeta = {
|
||||
crossModelVersion: 1,
|
||||
|
|
@ -20,8 +19,6 @@ export const DEFAULT_META_MODEL: LocalMeta = {
|
|||
lastUpdate: 0,
|
||||
metaRev: null,
|
||||
lastSyncedUpdate: null,
|
||||
localLamport: 0,
|
||||
lastSyncedLamport: null,
|
||||
vectorClock: {},
|
||||
lastSyncedVectorClock: null,
|
||||
};
|
||||
|
|
@ -92,32 +89,18 @@ export class MetaModelCtrl {
|
|||
const lastUpdateAction =
|
||||
actionStr.length > 100 ? actionStr.substring(0, 97) + '...' : actionStr;
|
||||
|
||||
// Update vector clock - migrate from Lamport if needed
|
||||
let currentVectorClock = getVectorClock(metaModel, clientId);
|
||||
if (!currentVectorClock && metaModel.localLamport > 0) {
|
||||
// First time creating vector clock - migrate from Lamport timestamp
|
||||
currentVectorClock = { [clientId]: metaModel.localLamport };
|
||||
pfLog(
|
||||
2,
|
||||
`${MetaModelCtrl.L}.${this.updateRevForModel.name}() migrating Lamport to vector clock`,
|
||||
{
|
||||
clientId,
|
||||
localLamport: metaModel.localLamport,
|
||||
newVectorClock: currentVectorClock,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let newVectorClock = incrementVectorClock(currentVectorClock || {}, clientId);
|
||||
// Update vector clock
|
||||
const currentVectorClock = metaModel.vectorClock || {};
|
||||
let newVectorClock = incrementVectorClock(currentVectorClock, clientId);
|
||||
|
||||
// Apply size limiting to prevent unbounded growth
|
||||
newVectorClock = limitVectorClockSize(newVectorClock, clientId);
|
||||
|
||||
const baseUpdatedMeta = {
|
||||
const updatedMeta = {
|
||||
...metaModel,
|
||||
lastUpdate: timestamp,
|
||||
lastUpdateAction,
|
||||
localLamport: this._incrementLamport(metaModel.localLamport || 0),
|
||||
vectorClock: newVectorClock,
|
||||
|
||||
...(modelCfg.isMainFileModel
|
||||
? {}
|
||||
|
|
@ -131,9 +114,6 @@ export class MetaModelCtrl {
|
|||
crossModelVersion: this.crossModelVersion,
|
||||
};
|
||||
|
||||
// Create final meta with vector clock using pure function
|
||||
const updatedMeta = withVectorClock(baseUpdatedMeta, newVectorClock, clientId);
|
||||
|
||||
await this.save(updatedMeta, isIgnoreDBLock);
|
||||
}
|
||||
|
||||
|
|
@ -152,14 +132,6 @@ export class MetaModelCtrl {
|
|||
lastUpdate: metaModel.lastUpdate,
|
||||
isIgnoreDBLock,
|
||||
});
|
||||
if (typeof metaModel.lastUpdate !== 'number') {
|
||||
return Promise.reject(
|
||||
new InvalidMetaError(
|
||||
`${MetaModelCtrl.L}.${this.save.name}()`,
|
||||
'lastUpdate not found',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: in order to not mess up separate model updates started at the same time, we need to update synchronously as well
|
||||
this._metaModelInMemory = validateLocalMeta(metaModel);
|
||||
|
|
@ -186,7 +158,6 @@ export class MetaModelCtrl {
|
|||
pfLog(
|
||||
2,
|
||||
`${MetaModelCtrl.L}.${this.save.name}() DB save completed successfully`,
|
||||
metaModel.localLamport,
|
||||
metaModel,
|
||||
);
|
||||
})
|
||||
|
|
@ -247,14 +218,6 @@ export class MetaModelCtrl {
|
|||
vectorClockKeys: data.vectorClock ? Object.keys(data.vectorClock) : [],
|
||||
});
|
||||
|
||||
// Ensure Lamport fields are initialized for old data
|
||||
if (data.localLamport === undefined) {
|
||||
data.localLamport = 0;
|
||||
}
|
||||
if (data.lastSyncedLamport === undefined) {
|
||||
data.lastSyncedLamport = null;
|
||||
}
|
||||
|
||||
// Ensure vector clock fields are initialized for old data
|
||||
if (data.vectorClock === undefined) {
|
||||
data.vectorClock = {};
|
||||
|
|
@ -335,11 +298,6 @@ export class MetaModelCtrl {
|
|||
// Increment this client's vector clock component
|
||||
metaData.vectorClock[clientId] = (metaData.vectorClock[clientId] || 0) + 1;
|
||||
|
||||
// Also increment the Lamport timestamp for backwards compatibility
|
||||
if (typeof metaData.localLamport === 'number') {
|
||||
metaData.localLamport = this._incrementLamport(metaData.localLamport);
|
||||
}
|
||||
|
||||
// Save the updated metadata
|
||||
await this.save({
|
||||
...metaData,
|
||||
|
|
@ -389,19 +347,4 @@ export class MetaModelCtrl {
|
|||
pfLog(2, `${MetaModelCtrl.L}.${this._generateClientId.name}()`);
|
||||
return getEnvironmentId() + '_' + Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely increments Lamport timestamp with overflow protection
|
||||
*
|
||||
* @param current Current Lamport value
|
||||
* @returns Incremented value
|
||||
*/
|
||||
private _incrementLamport(current: number): number {
|
||||
// Reset if approaching max safe integer
|
||||
if (current >= Number.MAX_SAFE_INTEGER - 1000) {
|
||||
pfLog(1, `${MetaModelCtrl.L} Lamport counter overflow protection triggered`);
|
||||
return 1;
|
||||
}
|
||||
return current + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,12 +91,6 @@ export interface MetaFileBase {
|
|||
lastUpdateAction?: string;
|
||||
revMap: RevMap;
|
||||
crossModelVersion: number;
|
||||
// Change tracking fields - support both old and new names for backwards compatibility
|
||||
localLamport: number;
|
||||
lastSyncedLamport: number | null;
|
||||
// New field names (will be migrated to gradually)
|
||||
localChangeCounter?: number;
|
||||
lastSyncedChangeCounter?: number | null;
|
||||
lastSyncedAction?: string;
|
||||
// Vector clock fields for improved conflict detection
|
||||
vectorClock?: VectorClock;
|
||||
|
|
@ -112,13 +106,9 @@ export interface RemoteMeta extends MetaFileBase {
|
|||
isFullData?: boolean;
|
||||
}
|
||||
|
||||
export interface UploadMeta
|
||||
extends Omit<
|
||||
RemoteMeta,
|
||||
'lastSyncedLamport' | 'lastSyncedChangeCounter' | 'lastSyncedVectorClock'
|
||||
> {
|
||||
lastSyncedLamport: null;
|
||||
lastSyncedChangeCounter?: null;
|
||||
export interface UploadMeta extends Omit<RemoteMeta, 'lastSyncedVectorClock'> {
|
||||
// Vector clock should not be synced, only used locally
|
||||
lastSyncedVectorClock?: null;
|
||||
}
|
||||
|
||||
export interface LocalMeta extends MetaFileBase {
|
||||
|
|
|
|||
|
|
@ -21,12 +21,11 @@ Located in `sync-providers/`:
|
|||
|
||||
### Sync Algorithm
|
||||
|
||||
The sync system uses a hybrid approach combining:
|
||||
The sync system uses vector clocks for accurate conflict detection:
|
||||
|
||||
1. **Physical Timestamps** (`lastUpdate`) - For ordering events
|
||||
2. **Change Counters** (`localLamport`) - For detecting local modifications
|
||||
3. **Vector Clocks** (`vectorClock`) - For accurate causality tracking
|
||||
4. **Sync State** (`lastSyncedUpdate`, `lastSyncedLamport`) - To track last successful sync
|
||||
2. **Vector Clocks** (`vectorClock`) - For accurate causality tracking and conflict detection
|
||||
3. **Sync State** (`lastSyncedUpdate`, `lastSyncedVectorClock`) - To track last successful sync
|
||||
|
||||
## How Sync Works
|
||||
|
||||
|
|
@ -37,7 +36,6 @@ When a user modifies data:
|
|||
```typescript
|
||||
// In meta-model-ctrl.ts
|
||||
lastUpdate = Date.now();
|
||||
localLamport = localLamport + 1;
|
||||
vectorClock[clientId] = vectorClock[clientId] + 1;
|
||||
```
|
||||
|
||||
|
|
@ -75,9 +73,8 @@ if (comparison === VectorClockComparison.CONCURRENT) {
|
|||
interface LocalMeta {
|
||||
lastUpdate: number; // Physical timestamp
|
||||
lastSyncedUpdate: number; // Last synced timestamp
|
||||
localLamport: number; // Change counter
|
||||
lastSyncedLamport: number; // Last synced counter
|
||||
vectorClock?: VectorClock; // Causality tracking
|
||||
lastSyncedVectorClock?: VectorClock; // Last synced vector clock
|
||||
revMap: RevMap; // Model revision map
|
||||
crossModelVersion: number; // Schema version
|
||||
}
|
||||
|
|
@ -85,9 +82,9 @@ interface LocalMeta {
|
|||
|
||||
### Important Considerations
|
||||
|
||||
1. **NOT Pure Lamport Clocks**: We don't increment on receive to prevent sync loops
|
||||
1. **Vector Clocks**: Each client maintains its own counter for accurate causality tracking
|
||||
2. **Backwards Compatibility**: Supports migration from older versions
|
||||
3. **Conflict Minimization**: Vector clocks reduce false conflicts
|
||||
3. **Conflict Minimization**: Vector clocks eliminate false conflicts
|
||||
4. **Atomic Operations**: Meta file serves as transaction coordinator
|
||||
|
||||
## Common Sync Scenarios
|
||||
|
|
|
|||
|
|
@ -35,26 +35,16 @@ import {
|
|||
limitVectorClockSize,
|
||||
sanitizeVectorClock,
|
||||
} from '../util/vector-clock';
|
||||
import {
|
||||
getVectorClock,
|
||||
withVectorClock,
|
||||
withLastSyncedVectorClock,
|
||||
} from '../util/backwards-compat';
|
||||
import { getVectorClock } from '../util/backwards-compat';
|
||||
|
||||
/**
|
||||
* Sync Service for Super Productivity
|
||||
*
|
||||
* Change Detection System:
|
||||
* This is NOT a pure Lamport timestamp implementation!
|
||||
* We use a hybrid approach that combines:
|
||||
* - Physical timestamps (lastUpdate) for ordering
|
||||
* - Change counters (localLamport) for detecting local modifications
|
||||
* - Sync tracking (lastSyncedLamport) to detect when sync is needed
|
||||
*
|
||||
* Key difference from Lamport clocks:
|
||||
* - We DON'T increment on receive (prevents sync loops)
|
||||
* - We DO increment on local changes
|
||||
* - We track the last synced state separately
|
||||
* Uses vector clocks for detecting concurrent changes and conflicts between devices.
|
||||
* Each client maintains its own counter in the vector clock which is incremented
|
||||
* on local changes. This allows proper conflict detection when changes happen
|
||||
* on multiple devices simultaneously.
|
||||
*/
|
||||
export class SyncService<const MD extends ModelCfgs> {
|
||||
public readonly IS_DO_CROSS_MODEL_MIGRATIONS: boolean;
|
||||
|
|
@ -198,36 +188,24 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
return { status };
|
||||
case SyncStatus.InSync:
|
||||
// Ensure lastSyncedUpdate is set even when already in sync
|
||||
if (
|
||||
localMeta.lastSyncedUpdate !== localMeta.lastUpdate ||
|
||||
localMeta.lastSyncedLamport !== localMeta.localLamport
|
||||
) {
|
||||
pfLog(2, 'InSync but lastSyncedUpdate/Lamport needs update', {
|
||||
if (localMeta.lastSyncedUpdate !== localMeta.lastUpdate) {
|
||||
pfLog(2, 'InSync but lastSyncedUpdate needs update', {
|
||||
lastSyncedUpdate: localMeta.lastSyncedUpdate,
|
||||
lastUpdate: localMeta.lastUpdate,
|
||||
lastSyncedLamport: localMeta.lastSyncedLamport,
|
||||
localLamport: localMeta.localLamport,
|
||||
});
|
||||
|
||||
// Get client ID for vector clock operations
|
||||
const clientId = await this._metaModelCtrl.loadClientId();
|
||||
const localVector = getVectorClock(localMeta, clientId);
|
||||
// Get the local vector clock
|
||||
const localVector = localMeta.vectorClock;
|
||||
|
||||
let updatedMeta = {
|
||||
const updatedMeta = {
|
||||
...localMeta,
|
||||
lastSyncedUpdate: localMeta.lastUpdate,
|
||||
lastSyncedLamport: localMeta.localLamport || 0,
|
||||
metaRev: remoteMetaRev,
|
||||
// Ensure vector clock fields are always present
|
||||
vectorClock: localMeta.vectorClock || {},
|
||||
lastSyncedVectorClock: localMeta.lastSyncedVectorClock || null,
|
||||
lastSyncedVectorClock: localVector || null,
|
||||
};
|
||||
|
||||
// Update vector clock if available
|
||||
if (localVector) {
|
||||
updatedMeta = withLastSyncedVectorClock(updatedMeta, localVector, clientId);
|
||||
}
|
||||
|
||||
await this._metaFileSyncService.saveLocal(updatedMeta);
|
||||
}
|
||||
return { status };
|
||||
|
|
@ -278,47 +256,29 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
if (isForceUpload) {
|
||||
// Get client ID for vector clock operations
|
||||
const clientId = await this._metaModelCtrl.loadClientId();
|
||||
let localVector = getVectorClock(local, clientId) || {};
|
||||
let localVector = local.vectorClock || {};
|
||||
|
||||
// For conflict resolution, fetch remote metadata once and handle both Lamport and vector clocks
|
||||
// For conflict resolution, fetch remote metadata once and handle vector clocks
|
||||
let remoteMeta: RemoteMeta | null = null;
|
||||
let remoteLamport = 0;
|
||||
|
||||
try {
|
||||
const result = await this._metaFileSyncService.download();
|
||||
remoteMeta = result.remoteMeta;
|
||||
remoteLamport = remoteMeta.localLamport || 0;
|
||||
} catch (e) {
|
||||
pfLog(1, 'Warning: Cannot fetch remote metadata during force upload', e);
|
||||
// If download fails, use local values as baseline
|
||||
remoteLamport = local.localLamport || 0;
|
||||
}
|
||||
|
||||
// Set our Lamport to be higher than both local and remote
|
||||
const currentLamport = Math.max(local.localLamport || 0, remoteLamport);
|
||||
// Reset if approaching max safe integer (same logic as MetaModelCtrl)
|
||||
const nextLamport =
|
||||
currentLamport >= Number.MAX_SAFE_INTEGER - 1000 ? 1 : currentLamport + 1;
|
||||
|
||||
pfLog(
|
||||
2,
|
||||
`${SyncService.L}.${this.uploadAll.name}(): Incrementing change counter for conflict resolution`,
|
||||
{ localLamport: local.localLamport, remoteLamport, currentLamport, nextLamport },
|
||||
);
|
||||
|
||||
// Merge vector clocks if remote metadata was successfully fetched
|
||||
if (remoteMeta) {
|
||||
let remoteVector = getVectorClock(remoteMeta, clientId);
|
||||
if (remoteVector) {
|
||||
// Sanitize remote vector clock before merging
|
||||
remoteVector = sanitizeVectorClock(remoteVector);
|
||||
localVector = mergeVectorClocks(localVector, remoteVector);
|
||||
pfLog(2, 'Merged remote vector clock for force upload', {
|
||||
localOriginal: getVectorClock(local, clientId),
|
||||
remote: remoteVector,
|
||||
merged: localVector,
|
||||
});
|
||||
}
|
||||
if (remoteMeta && remoteMeta.vectorClock) {
|
||||
let remoteVector = remoteMeta.vectorClock;
|
||||
// Sanitize remote vector clock before merging
|
||||
remoteVector = sanitizeVectorClock(remoteVector);
|
||||
localVector = mergeVectorClocks(localVector, remoteVector);
|
||||
pfLog(2, 'Merged remote vector clock for force upload', {
|
||||
localOriginal: local.vectorClock,
|
||||
remote: remoteVector,
|
||||
merged: localVector,
|
||||
});
|
||||
} else {
|
||||
pfLog(1, 'Proceeding with force upload without remote vector clock merge');
|
||||
}
|
||||
|
|
@ -328,17 +288,12 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
// Apply size limiting to prevent unbounded growth
|
||||
newVector = limitVectorClockSize(newVector, clientId);
|
||||
|
||||
let updatedMeta = {
|
||||
const updatedMeta = {
|
||||
...local,
|
||||
lastUpdate: Date.now(),
|
||||
localLamport: nextLamport,
|
||||
// Important: Don't update lastSyncedLamport yet
|
||||
// It will be updated after successful upload
|
||||
vectorClock: newVector,
|
||||
};
|
||||
|
||||
// Update vector clock
|
||||
updatedMeta = withVectorClock(updatedMeta, newVector, clientId);
|
||||
|
||||
await this._metaModelCtrl.save(
|
||||
updatedMeta,
|
||||
// NOTE we always ignore db lock while syncing
|
||||
|
|
@ -348,9 +303,8 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
}
|
||||
|
||||
try {
|
||||
// Get client ID for vector clock operations
|
||||
const clientId = await this._metaModelCtrl.loadClientId();
|
||||
const localVector = getVectorClock(local, clientId);
|
||||
// Get the local vector clock
|
||||
const localVector = local.vectorClock;
|
||||
|
||||
return await this.uploadToRemote(
|
||||
{
|
||||
|
|
@ -359,8 +313,6 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
revMap: {},
|
||||
// Will be assigned later
|
||||
mainModelData: {},
|
||||
localLamport: local.localLamport || 0,
|
||||
lastSyncedLamport: null,
|
||||
vectorClock: localVector,
|
||||
},
|
||||
{
|
||||
|
|
@ -368,7 +320,6 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
revMap: this._fakeFullRevMap(),
|
||||
// Ensure lastSyncedUpdate matches lastUpdate to prevent false conflicts
|
||||
lastSyncedUpdate: local.lastUpdate,
|
||||
lastSyncedLamport: local.localLamport || 0,
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
|
@ -400,8 +351,6 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
lastSyncedUpdate: null,
|
||||
metaRev: null,
|
||||
revMap: {},
|
||||
localLamport: 0,
|
||||
lastSyncedLamport: null,
|
||||
// Include vector clock fields to prevent comparison issues
|
||||
vectorClock: {},
|
||||
lastSyncedVectorClock: null,
|
||||
|
|
@ -471,29 +420,20 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
mergedVector = limitVectorClockSize(mergedVector, clientId);
|
||||
}
|
||||
|
||||
let updatedMeta = {
|
||||
const updatedMeta = {
|
||||
// shared
|
||||
lastUpdate: remote.lastUpdate,
|
||||
crossModelVersion: remote.crossModelVersion,
|
||||
revMap: remote.revMap,
|
||||
// Don't increment localLamport during download - only take the max
|
||||
// localLamport tracks LOCAL changes only
|
||||
localLamport: Math.max(local.localLamport || 0, remote.localLamport || 0),
|
||||
// local meta
|
||||
lastSyncedUpdate: remote.lastUpdate,
|
||||
lastSyncedLamport: Math.max(local.localLamport || 0, remote.localLamport || 0),
|
||||
metaRev: remoteRev,
|
||||
// Always include vector clock fields to prevent them from being lost
|
||||
vectorClock: mergedVector || local.vectorClock || remote.vectorClock || {},
|
||||
lastSyncedVectorClock: null,
|
||||
lastSyncedVectorClock:
|
||||
mergedVector || local.vectorClock || remote.vectorClock || {},
|
||||
};
|
||||
|
||||
// Update vector clocks if we have them
|
||||
if (mergedVector) {
|
||||
updatedMeta = withVectorClock(updatedMeta, mergedVector, clientId);
|
||||
updatedMeta = withLastSyncedVectorClock(updatedMeta, mergedVector, clientId);
|
||||
}
|
||||
|
||||
await this._metaFileSyncService.saveLocal(updatedMeta);
|
||||
return;
|
||||
}
|
||||
|
|
@ -586,14 +526,10 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
mergedVector = limitVectorClockSize(mergedVector, clientId);
|
||||
}
|
||||
|
||||
let updatedMeta = {
|
||||
const updatedMeta = {
|
||||
metaRev: remoteRev,
|
||||
lastSyncedUpdate: remote.lastUpdate,
|
||||
lastUpdate: remote.lastUpdate,
|
||||
// Don't increment localLamport during download - only take the max
|
||||
// localLamport tracks LOCAL changes only
|
||||
localLamport: Math.max(local.localLamport || 0, remote.localLamport || 0),
|
||||
lastSyncedLamport: Math.max(local.localLamport || 0, remote.localLamport || 0),
|
||||
lastSyncedAction: `Downloaded ${isDownloadAll ? 'all data' : `${toUpdate.length} models`} at ${new Date().toISOString()}`,
|
||||
revMap: validateRevMap({
|
||||
...local.revMap,
|
||||
|
|
@ -602,15 +538,10 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
crossModelVersion: remote.crossModelVersion,
|
||||
// Always include vector clock fields to prevent them from being lost
|
||||
vectorClock: mergedVector || local.vectorClock || remote.vectorClock || {},
|
||||
lastSyncedVectorClock: null,
|
||||
lastSyncedVectorClock:
|
||||
mergedVector || local.vectorClock || remote.vectorClock || {},
|
||||
};
|
||||
|
||||
// Update vector clocks if we have them
|
||||
if (mergedVector) {
|
||||
updatedMeta = withVectorClock(updatedMeta, mergedVector, clientId);
|
||||
updatedMeta = withLastSyncedVectorClock(updatedMeta, mergedVector, clientId);
|
||||
}
|
||||
|
||||
await this._metaFileSyncService.saveLocal(updatedMeta);
|
||||
}
|
||||
|
||||
|
|
@ -646,19 +577,13 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
? await this._pfapiMain.getAllSyncModelData()
|
||||
: await this._modelSyncService.getMainFileModelDataForUpload();
|
||||
|
||||
// Get client ID for vector clock operations
|
||||
const clientId = await this._metaModelCtrl.loadClientId();
|
||||
const localVector = getVectorClock(local, clientId);
|
||||
|
||||
const uploadMeta: UploadMeta = {
|
||||
revMap: local.revMap,
|
||||
lastUpdate: local.lastUpdate,
|
||||
lastUpdateAction: local.lastUpdateAction,
|
||||
crossModelVersion: local.crossModelVersion,
|
||||
mainModelData,
|
||||
localLamport: local.localLamport || 0,
|
||||
lastSyncedLamport: null,
|
||||
vectorClock: localVector,
|
||||
vectorClock: local.vectorClock,
|
||||
...(syncProvider.isLimitedToSingleFileSync ? { isFullData: true } : {}),
|
||||
};
|
||||
|
||||
|
|
@ -679,19 +604,14 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
},
|
||||
);
|
||||
|
||||
let updatedMeta = {
|
||||
const updatedMeta = {
|
||||
...local,
|
||||
lastSyncedUpdate: local.lastUpdate,
|
||||
lastSyncedLamport: local.localLamport || 0,
|
||||
lastSyncedAction: `Uploaded single file at ${new Date().toISOString()}`,
|
||||
metaRev: metaRevAfterUpdate,
|
||||
lastSyncedVectorClock: local.vectorClock || null,
|
||||
};
|
||||
|
||||
// Update vector clock if available
|
||||
if (localVector) {
|
||||
updatedMeta = withLastSyncedVectorClock(updatedMeta, localVector, clientId);
|
||||
}
|
||||
|
||||
await this._metaFileSyncService.saveLocal(updatedMeta);
|
||||
|
||||
pfLog(
|
||||
|
|
@ -759,10 +679,6 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
// Validate and upload the final revMap
|
||||
const validatedRevMap = validateRevMap(realRevMap);
|
||||
|
||||
// Get client ID for vector clock operations
|
||||
const clientId = await this._metaModelCtrl.loadClientId();
|
||||
const localVector = getVectorClock(local, clientId);
|
||||
|
||||
const uploadMeta: UploadMeta = {
|
||||
revMap: validatedRevMap,
|
||||
lastUpdate: local.lastUpdate,
|
||||
|
|
@ -770,36 +686,27 @@ export class SyncService<const MD extends ModelCfgs> {
|
|||
crossModelVersion: local.crossModelVersion,
|
||||
mainModelData:
|
||||
await this._modelSyncService.getMainFileModelDataForUpload(completeData),
|
||||
localLamport: local.localLamport || 0,
|
||||
lastSyncedLamport: null,
|
||||
vectorClock: localVector,
|
||||
vectorClock: local.vectorClock,
|
||||
};
|
||||
|
||||
const metaRevAfterUpload = await this._metaFileSyncService.upload(uploadMeta);
|
||||
|
||||
// Update local after successful upload
|
||||
let updatedMeta = {
|
||||
const updatedMeta = {
|
||||
// leave as is basically
|
||||
lastUpdate: local.lastUpdate,
|
||||
crossModelVersion: local.crossModelVersion,
|
||||
localLamport: local.localLamport || 0,
|
||||
// Always include vector clock fields to prevent them from being lost
|
||||
vectorClock: local.vectorClock || {},
|
||||
lastSyncedVectorClock: local.lastSyncedVectorClock || null,
|
||||
lastSyncedVectorClock: local.vectorClock || null,
|
||||
|
||||
// actual updates
|
||||
lastSyncedUpdate: local.lastUpdate,
|
||||
lastSyncedLamport: local.localLamport || 0,
|
||||
lastSyncedAction: `Uploaded ${toUpdate.length} models at ${new Date().toISOString()}`,
|
||||
revMap: validatedRevMap,
|
||||
metaRev: metaRevAfterUpload,
|
||||
};
|
||||
|
||||
// Update vector clock if available
|
||||
if (localVector) {
|
||||
updatedMeta = withLastSyncedVectorClock(updatedMeta, localVector, clientId);
|
||||
}
|
||||
|
||||
await this._metaFileSyncService.saveLocal(updatedMeta);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,271 +1,11 @@
|
|||
import { LocalMeta, RemoteMeta, VectorClock } from '../pfapi.model';
|
||||
import { lamportToVectorClock, isVectorClockEmpty } from './vector-clock';
|
||||
import { pfLog } from './log';
|
||||
import { LocalMeta, RemoteMeta } from '../pfapi.model';
|
||||
import { isVectorClockEmpty } from './vector-clock';
|
||||
|
||||
/**
|
||||
* Utility functions for backwards compatibility with old field names.
|
||||
* This allows gradual migration from localLamport/lastSyncedLamport to
|
||||
* localChangeCounter/lastSyncedChangeCounter and to vector clocks.
|
||||
* Utility functions for backwards compatibility.
|
||||
* Now focused on vector clock utilities only.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the local change counter value, checking both old and new field names
|
||||
*/
|
||||
export const getLocalChangeCounter = (meta: LocalMeta | RemoteMeta): number => {
|
||||
// Prefer new field name if available
|
||||
if (meta.localChangeCounter !== undefined) {
|
||||
return meta.localChangeCounter;
|
||||
}
|
||||
// Fall back to old field name
|
||||
return meta.localLamport || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the last synced change counter value, checking both old and new field names
|
||||
*/
|
||||
export const getLastSyncedChangeCounter = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
): number | null => {
|
||||
// Prefer new field name if available
|
||||
if (meta.lastSyncedChangeCounter !== undefined) {
|
||||
return meta.lastSyncedChangeCounter;
|
||||
}
|
||||
// Fall back to old field name, ensuring we return null if undefined
|
||||
return meta.lastSyncedLamport ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new metadata object with the local change counter value set,
|
||||
* updating both old and new field names for backwards compatibility
|
||||
*/
|
||||
export const withLocalChangeCounter = <T extends LocalMeta | RemoteMeta>(
|
||||
meta: T,
|
||||
value: number,
|
||||
): T => {
|
||||
return {
|
||||
...meta,
|
||||
localLamport: value,
|
||||
localChangeCounter: value,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new metadata object with the last synced change counter value set,
|
||||
* updating both old and new field names for backwards compatibility
|
||||
*/
|
||||
export const withLastSyncedChangeCounter = <T extends LocalMeta | RemoteMeta>(
|
||||
meta: T,
|
||||
value: number | null,
|
||||
): T => {
|
||||
return {
|
||||
...meta,
|
||||
lastSyncedLamport: value,
|
||||
lastSyncedChangeCounter: value,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use withLocalChangeCounter instead - this mutates the object
|
||||
*/
|
||||
export const setLocalChangeCounter = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
value: number,
|
||||
): void => {
|
||||
const updated = withLocalChangeCounter(meta, value);
|
||||
Object.assign(meta, updated);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use withLastSyncedChangeCounter instead - this mutates the object
|
||||
*/
|
||||
export const setLastSyncedChangeCounter = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
value: number | null,
|
||||
): void => {
|
||||
const updated = withLastSyncedChangeCounter(meta, value);
|
||||
Object.assign(meta, updated);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a metadata object with both old and new field names populated
|
||||
*/
|
||||
export const createBackwardsCompatibleMeta = <T extends LocalMeta | RemoteMeta>(
|
||||
meta: T,
|
||||
): T => {
|
||||
const result = { ...meta };
|
||||
|
||||
// Ensure both field names are populated
|
||||
if (result.localChangeCounter !== undefined && result.localLamport === undefined) {
|
||||
result.localLamport = result.localChangeCounter;
|
||||
} else if (
|
||||
result.localLamport !== undefined &&
|
||||
result.localChangeCounter === undefined
|
||||
) {
|
||||
result.localChangeCounter = result.localLamport;
|
||||
} else if (
|
||||
result.localChangeCounter !== undefined &&
|
||||
result.localLamport !== undefined &&
|
||||
result.localChangeCounter !== result.localLamport
|
||||
) {
|
||||
// Warn about field mismatch but use the newer field
|
||||
pfLog(1, 'WARN: Mismatch between localChangeCounter and localLamport fields', {
|
||||
localChangeCounter: result.localChangeCounter,
|
||||
localLamport: result.localLamport,
|
||||
using: 'localChangeCounter',
|
||||
});
|
||||
result.localLamport = result.localChangeCounter;
|
||||
}
|
||||
|
||||
if (
|
||||
result.lastSyncedChangeCounter !== undefined &&
|
||||
result.lastSyncedLamport === undefined
|
||||
) {
|
||||
result.lastSyncedLamport = result.lastSyncedChangeCounter;
|
||||
} else if (
|
||||
result.lastSyncedLamport !== undefined &&
|
||||
result.lastSyncedChangeCounter === undefined
|
||||
) {
|
||||
result.lastSyncedChangeCounter = result.lastSyncedLamport;
|
||||
} else if (
|
||||
result.lastSyncedChangeCounter !== undefined &&
|
||||
result.lastSyncedLamport !== undefined &&
|
||||
result.lastSyncedChangeCounter !== result.lastSyncedLamport
|
||||
) {
|
||||
// Warn about field mismatch but use the newer field
|
||||
pfLog(
|
||||
1,
|
||||
'WARN: Mismatch between lastSyncedChangeCounter and lastSyncedLamport fields',
|
||||
{
|
||||
lastSyncedChangeCounter: result.lastSyncedChangeCounter,
|
||||
lastSyncedLamport: result.lastSyncedLamport,
|
||||
using: 'lastSyncedChangeCounter',
|
||||
},
|
||||
);
|
||||
result.lastSyncedLamport = result.lastSyncedChangeCounter;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the vector clock, creating it from Lamport timestamp if needed
|
||||
* @param meta The metadata object
|
||||
* @param clientId The client ID to use for migration
|
||||
* @returns The vector clock
|
||||
*/
|
||||
export const getVectorClock = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
clientId: string,
|
||||
): VectorClock | undefined => {
|
||||
// Return existing vector clock if available
|
||||
if (meta.vectorClock && !isVectorClockEmpty(meta.vectorClock)) {
|
||||
return meta.vectorClock;
|
||||
}
|
||||
|
||||
// Migrate from Lamport timestamp if available
|
||||
const changeCounter = getLocalChangeCounter(meta);
|
||||
if (changeCounter > 0) {
|
||||
return lamportToVectorClock(changeCounter, clientId);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the last synced vector clock, creating it from Lamport timestamp if needed
|
||||
* @param meta The metadata object
|
||||
* @param clientId The client ID to use for migration
|
||||
* @returns The last synced vector clock
|
||||
*/
|
||||
export const getLastSyncedVectorClock = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
clientId: string,
|
||||
): VectorClock | null => {
|
||||
// Return existing vector clock if available
|
||||
if (meta.lastSyncedVectorClock && !isVectorClockEmpty(meta.lastSyncedVectorClock)) {
|
||||
return meta.lastSyncedVectorClock;
|
||||
}
|
||||
|
||||
// Migrate from Lamport timestamp if available
|
||||
const lastSyncedCounter = getLastSyncedChangeCounter(meta);
|
||||
if (lastSyncedCounter != null && lastSyncedCounter > 0) {
|
||||
return lamportToVectorClock(lastSyncedCounter, clientId);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new metadata object with the vector clock set and Lamport timestamps updated
|
||||
* @param meta The metadata object
|
||||
* @param vectorClock The vector clock to set
|
||||
* @param clientId The client ID for this instance
|
||||
* @returns A new metadata object with updated vector clock
|
||||
*/
|
||||
export const withVectorClock = <T extends LocalMeta | RemoteMeta>(
|
||||
meta: T,
|
||||
vectorClock: VectorClock,
|
||||
clientId: string,
|
||||
): T => {
|
||||
// Update Lamport timestamps for backwards compatibility
|
||||
// Use this client's component value
|
||||
const clientValue = vectorClock[clientId] || 0;
|
||||
|
||||
return {
|
||||
...meta,
|
||||
vectorClock,
|
||||
localLamport: clientValue,
|
||||
localChangeCounter: clientValue,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new metadata object with the last synced vector clock set and Lamport timestamps updated
|
||||
* @param meta The metadata object
|
||||
* @param vectorClock The vector clock to set (can be null)
|
||||
* @param clientId The client ID for this instance
|
||||
* @returns A new metadata object with updated last synced vector clock
|
||||
*/
|
||||
export const withLastSyncedVectorClock = <T extends LocalMeta | RemoteMeta>(
|
||||
meta: T,
|
||||
vectorClock: VectorClock | null,
|
||||
clientId: string,
|
||||
): T => {
|
||||
// Update Lamport timestamps for backwards compatibility
|
||||
const lastSyncedValue = vectorClock ? vectorClock[clientId] || 0 : null;
|
||||
|
||||
return {
|
||||
...meta,
|
||||
lastSyncedVectorClock: vectorClock,
|
||||
lastSyncedLamport: lastSyncedValue,
|
||||
lastSyncedChangeCounter: lastSyncedValue,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use withVectorClock instead - this mutates the object
|
||||
*/
|
||||
export const setVectorClock = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
vectorClock: VectorClock,
|
||||
clientId: string,
|
||||
): void => {
|
||||
const updated = withVectorClock(meta, vectorClock, clientId);
|
||||
Object.assign(meta, updated);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use withLastSyncedVectorClock instead - this mutates the object
|
||||
*/
|
||||
export const setLastSyncedVectorClock = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
vectorClock: VectorClock | null,
|
||||
clientId: string,
|
||||
): void => {
|
||||
const updated = withLastSyncedVectorClock(meta, vectorClock, clientId);
|
||||
Object.assign(meta, updated);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if both metadata objects have vector clocks
|
||||
* @param local Local metadata
|
||||
|
|
@ -277,3 +17,16 @@ export const hasVectorClocks = (local: LocalMeta, remote: RemoteMeta): boolean =
|
|||
!isVectorClockEmpty(local.vectorClock) && !isVectorClockEmpty(remote.vectorClock)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the vector clock from metadata
|
||||
* @param meta Metadata object
|
||||
* @param clientId Client ID (unused, kept for compatibility)
|
||||
* @returns Vector clock or null if not present
|
||||
*/
|
||||
export const getVectorClock = (
|
||||
meta: LocalMeta | RemoteMeta,
|
||||
clientId?: string,
|
||||
): Record<string, number> | null => {
|
||||
return meta.vectorClock || null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ import {
|
|||
SyncInvalidTimeValuesError,
|
||||
} from '../errors/errors';
|
||||
import { pfLog } from './log';
|
||||
import {
|
||||
getLocalChangeCounter,
|
||||
getLastSyncedChangeCounter,
|
||||
hasVectorClocks,
|
||||
} from './backwards-compat';
|
||||
import { hasVectorClocks } from './backwards-compat';
|
||||
import {
|
||||
compareVectorClocks,
|
||||
VectorClockComparison,
|
||||
|
|
@ -123,67 +119,20 @@ export const getSyncStatusFromMetaFiles = (
|
|||
}
|
||||
}
|
||||
|
||||
// Enhanced fallback: Try to create hybrid comparison using available data
|
||||
const localChangeCounter = getLocalChangeCounter(local);
|
||||
const remoteChangeCounter = getLocalChangeCounter(remote);
|
||||
const lastSyncedChangeCounter = getLastSyncedChangeCounter(local);
|
||||
|
||||
// Handle mixed vector clock states gracefully for migration
|
||||
if (localHasVectorClock !== remoteHasVectorClock) {
|
||||
pfLog(
|
||||
2,
|
||||
'Mixed vector clock state detected - using migration-friendly comparison',
|
||||
'Mixed vector clock state detected - using timestamp comparison for migration',
|
||||
{
|
||||
localHasVectorClock,
|
||||
remoteHasVectorClock,
|
||||
localChangeCounter,
|
||||
remoteChangeCounter,
|
||||
lastSyncedChangeCounter,
|
||||
localLastUpdate: local.lastUpdate,
|
||||
remoteLastUpdate: remote.lastUpdate,
|
||||
localLastSyncedUpdate: local.lastSyncedUpdate,
|
||||
},
|
||||
);
|
||||
|
||||
// If we have valid change counters (non-zero), use them for comparison during migration
|
||||
const hasValidChangeCounters =
|
||||
typeof localChangeCounter === 'number' &&
|
||||
typeof remoteChangeCounter === 'number' &&
|
||||
typeof lastSyncedChangeCounter === 'number' &&
|
||||
(localChangeCounter > 0 ||
|
||||
remoteChangeCounter > 0 ||
|
||||
lastSyncedChangeCounter > 0);
|
||||
|
||||
if (hasValidChangeCounters) {
|
||||
// Use Lamport comparison logic for mixed states
|
||||
const hasLocalChanges = localChangeCounter > lastSyncedChangeCounter;
|
||||
const hasRemoteChanges = remoteChangeCounter > lastSyncedChangeCounter;
|
||||
|
||||
if (!hasLocalChanges && !hasRemoteChanges) {
|
||||
return { status: SyncStatus.InSync };
|
||||
} else if (hasLocalChanges && !hasRemoteChanges) {
|
||||
return { status: SyncStatus.UpdateRemote };
|
||||
} else if (!hasLocalChanges && hasRemoteChanges) {
|
||||
return { status: SyncStatus.UpdateLocal };
|
||||
} else {
|
||||
// Both have changes - compare values
|
||||
if (localChangeCounter > remoteChangeCounter) {
|
||||
return { status: SyncStatus.UpdateRemote };
|
||||
} else if (remoteChangeCounter > localChangeCounter) {
|
||||
return { status: SyncStatus.UpdateLocal };
|
||||
} else {
|
||||
// Equal change counters with changes - likely same changes
|
||||
return { status: SyncStatus.InSync };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid change counters, fall back to timestamp comparison
|
||||
pfLog(2, 'No valid change counters in mixed state - using timestamp comparison', {
|
||||
localHasVectorClock,
|
||||
remoteHasVectorClock,
|
||||
localLastUpdate: local.lastUpdate,
|
||||
remoteLastUpdate: remote.lastUpdate,
|
||||
localLastSyncedUpdate: local.lastSyncedUpdate,
|
||||
});
|
||||
|
||||
// If we have lastSyncedUpdate, check for changes
|
||||
if (typeof local.lastSyncedUpdate === 'number') {
|
||||
const hasLocalChanges = local.lastUpdate > local.lastSyncedUpdate;
|
||||
|
|
@ -225,55 +174,7 @@ export const getSyncStatusFromMetaFiles = (
|
|||
}
|
||||
}
|
||||
|
||||
// Standard Lamport fallback when both sides lack vector clocks
|
||||
|
||||
if (
|
||||
typeof localChangeCounter === 'number' &&
|
||||
typeof remoteChangeCounter === 'number' &&
|
||||
typeof lastSyncedChangeCounter === 'number'
|
||||
) {
|
||||
const lamportResult = _checkForUpdateLamport({
|
||||
remoteLocalLamport: remoteChangeCounter,
|
||||
localLamport: localChangeCounter,
|
||||
lastSyncedLamport: lastSyncedChangeCounter,
|
||||
});
|
||||
|
||||
pfLog(2, 'Using change counters for sync status', {
|
||||
localChangeCounter,
|
||||
remoteChangeCounter,
|
||||
lastSyncedChangeCounter,
|
||||
result: lamportResult,
|
||||
hasLocalChanges: localChangeCounter > lastSyncedChangeCounter,
|
||||
hasRemoteChanges: remoteChangeCounter > lastSyncedChangeCounter,
|
||||
});
|
||||
|
||||
switch (lamportResult) {
|
||||
case UpdateCheckResult.InSync:
|
||||
return {
|
||||
status: SyncStatus.InSync,
|
||||
};
|
||||
case UpdateCheckResult.LocalUpdateRequired:
|
||||
return {
|
||||
status: SyncStatus.UpdateLocal,
|
||||
};
|
||||
case UpdateCheckResult.RemoteUpdateRequired:
|
||||
return {
|
||||
status: SyncStatus.UpdateRemote,
|
||||
};
|
||||
case UpdateCheckResult.DataDiverged:
|
||||
return {
|
||||
status: SyncStatus.Conflict,
|
||||
conflictData: {
|
||||
reason: ConflictReason.BothNewerLastSync,
|
||||
remote,
|
||||
local,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove later once it is likely that all running apps have lamport clocks
|
||||
// Final fallback to timestamp-based checking
|
||||
// Fallback to timestamp-based checking when vector clocks are not available
|
||||
|
||||
if (typeof local.lastSyncedUpdate === 'number') {
|
||||
const r = _checkForUpdate({
|
||||
|
|
@ -425,42 +326,6 @@ const _checkForUpdateVectorClock = (params: {
|
|||
}
|
||||
};
|
||||
|
||||
const _checkForUpdateLamport = (params: {
|
||||
remoteLocalLamport: number;
|
||||
localLamport: number;
|
||||
lastSyncedLamport: number;
|
||||
}): UpdateCheckResult => {
|
||||
const { remoteLocalLamport, localLamport, lastSyncedLamport } = params;
|
||||
|
||||
pfLog(2, 'Lamport timestamp check', {
|
||||
localLamport,
|
||||
remoteLocalLamport,
|
||||
lastSyncedLamport,
|
||||
hasLocalChanges: localLamport > lastSyncedLamport,
|
||||
hasRemoteChanges: remoteLocalLamport > lastSyncedLamport,
|
||||
});
|
||||
|
||||
// Check if there have been changes since last sync
|
||||
const hasLocalChanges = localLamport > lastSyncedLamport;
|
||||
const hasRemoteChanges = remoteLocalLamport > lastSyncedLamport;
|
||||
|
||||
if (!hasLocalChanges && !hasRemoteChanges) {
|
||||
return UpdateCheckResult.InSync;
|
||||
} else if (hasLocalChanges && !hasRemoteChanges) {
|
||||
return UpdateCheckResult.RemoteUpdateRequired;
|
||||
} else if (!hasLocalChanges && hasRemoteChanges) {
|
||||
return UpdateCheckResult.LocalUpdateRequired;
|
||||
} else {
|
||||
// Both have changes - check if they're the same
|
||||
if (localLamport === remoteLocalLamport) {
|
||||
// Both made the same changes - they're in sync
|
||||
return UpdateCheckResult.InSync;
|
||||
}
|
||||
// Different changes - conflict
|
||||
return UpdateCheckResult.DataDiverged;
|
||||
}
|
||||
};
|
||||
|
||||
const _checkForUpdate = (params: {
|
||||
remote: number;
|
||||
local: number;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { environment } from '../../../../environments/environment';
|
|||
2: normal
|
||||
3: verbose
|
||||
*/
|
||||
const LOG_LEVEL = environment.production ? 2 : 0;
|
||||
const LOG_LEVEL = environment.production ? 2 : 2;
|
||||
|
||||
/**
|
||||
* Safe logging function that prevents crashes during console access
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue