Uninote
Uninote
用户根目录
brdr
common
programming
docs
tests
后端试题
问题讨论
SyncClient.runSync({ automatic, protectedPaths })
│
├─ 1. await this.initialize()
│    └─ LocalSyncClientMethods.initialize()
│         ├─ fs.realpath(rootPath)
│         ├─ fs.stat(rootPath)
│         ├─ localLibraryName(rootPath)
│         ├─ ensureSafeMetadataDirectory(metadataPath, stateNamespace)
│         ├─ loadOrCreateIdentity(library.json, 'libraryId')
│         ├─ loadOrCreateIdentity(device.json, 'deviceId')
│         ├─ new SyncStateStore(state.json, { libraryId, deviceId })
│         │    └─ store.load()
│         │         ├─ fs.lstat(stateFile)
│         │         ├─ JSON.parse(fs.readFile(stateFile))
│         │         ├─ logStore.recent(200)
│         │         └─ 若 status === 'syncing' → 标记 error + save()
│         ├─ new SyncHistoryStore(history-v1, { libraryId })
│         ├─ new ManagedPathStore(rootPath)
│         └─ managedPathStore.add(已有 state.files + state.queue 路径)
│
├─ 2. this.beginSyncDiagnostic(state, automatic ? 'auto' : 'manual')
│    └─ SyncDiagnosticMethods.beginSyncDiagnostic()
│         ├─ 构造 activeSyncDiagnostic
│         └─ store.log('info', ...)
│
├─ 3. await this.unresolvedSyncConflictPaths()
│    └─ LocalSyncClientMethods.unresolvedSyncConflictPaths()
│         ├─ this.scanFiles({ recordManagedPaths: false })
│         │    ├─ this.initialize()
│         │    ├─ loadMyNoteIgnore(rootPath)
│         │    ├─ managedPathStore.paths()
│         │    ├─ 递归 walk(rootPath)
│         │    │    ├─ fs.readdir
│         │    │    ├─ ignorePolicy.ignores()
│         │    │    ├─ contentEncoding(relativePath)
│         │    │    ├─ fs.readFile + fs.stat
│         │    │    └─ contentHash(content, encoding)
│         │    └─ managedPathStore.add(files.keys())
│         └─ 对每个 .md 检查 syncConflictRelativePaths()
│              ├─ .base
│              ├─ .local
│              └─ .remote
│
├─ 4. 若存在冲突:
│    ├─ await this.logUnresolvedSyncConflicts(conflictPaths)
│    │    └─ SyncDiagnosticMethods.logUnresolvedSyncConflicts()
│    │         └─ this.describeUnresolvedSyncConflict()
│    │              ├─ fs.readFile(original/.base/.local/.remote)
│    │              └─ contentHash()
│    ├─ await this.store.save()
│    ├─ this.activeSyncDiagnostic = null
│    └─ throw new Error(...)
│
├─ 5. state.status = 'syncing'
├─ 6. state.lastError = null
├─ 7. this.setProgress({ phase: 'starting', ... })
│    └─ LocalSyncClientMethods.setProgress()
│         ├─ store.state.progress = value
│         └─ 通知 progressListeners
│
├─ 8. await this.store.save()
│    └─ SyncStateStore.save()
│         ├─ flush pendingLogs → SyncLogStore.append()
│         └─ atomicWriteJson(state.json)
│
├─ 9. await this.ensureFileModifiedTimes(state)
│    └─ SyncTransferMethods.ensureFileModifiedTimes()
│         ├─ 若 state.fileHistoryTimeVersion === 1 → return
│         ├─ remote.pull(libraryId, 0)
│         ├─ this.observeRemoteVersion(response)
│         │    └─ SyncDiagnosticMethods.observeRemoteVersion()
│         ├─ 用 response.changes 补齐 tracked.modifiedAt
│         ├─ state.fileHistoryTimeVersion = 1
│         └─ await this.store.save()
│
├─ 10. 若 protectedPaths 非空:
│    ├─ remote.pull(libraryId, state.cursor)
│    ├─ this.remoteChangeTouchesProtectedPath(
│    │     response.changes,
│    │     state,
│    │     protectedPaths
│    │   )
│    │    └─ SyncAutomaticMethods.remoteChangeTouchesProtectedPath()
│    │         ├─ currentProtectedPaths(protectedPaths)
│    │         └─ 检查 change.relativePath / previousPath
│    └─ 若命中 → throw SafeSyncBlockedError('dirty-open-document')
│
├─ 11. 若 state.cursor === 0
│     && state.queue.length === 0
│     && Object.keys(state.files).length === 0:
│    └─ await this.pullChanges({ protectedPaths })
│         └─ SyncTransferMethods.pullChanges()
│              ├─ remote.pull(libraryId, startingCursor)
│              ├─ observeRemoteVersion()
│              ├─ 合并 finalByFileId
│              ├─ 排序 changes
│              ├─ remoteChangeTouchesProtectedPath()
│              ├─ 预读 localSnapshots
│              │    └─ this.readSyncFile(tracked.relativePath)
│              │         └─ SyncFileMethods.readSyncFile()
│              │              ├─ assertSafeTarget(relativePath)
│              │              ├─ fs.readFile
│              │              └─ contentHash()
│              ├─ 对每个 change:
│              │    └─ this.applyRemoteFile(change, {
│              │         compacted: true,
│              │         localFileSnapshot,
│              │         batchSourcePaths,
│              │         safe
│              │       })
│              │         └─ SyncFileMethods.applyRemoteFile()
│              │              ├─ state.files[fileId]
│              │              ├─ readSyncFile()
│              │              ├─ tryAutoMerge()
│              │              │    └─ SyncMergeMethods.tryAutoMerge()
│              │              │         ├─ loadMergeBase()
│              │              │         │    └─ historyStore.versionDetails() / remote.historyPage()
│              │              │         ├─ hasConflictSidecars()
│              │              │         ├─ writeConflictSidecars()
│              │              │         │    └─ atomicWriteBuffer(.base/.local/.remote)
│              │              │         └─ mergeText()
│              │              ├─ preserveConflict()
│              │              ├─ moveToSyncTrash()
│              │              │    ├─ fs.rename → .mynote/sync-trash
│              │              │    └─ removePublishedMarkdown()
│              │              ├─ writeFile()
│              │              │    ├─ assertSafeTarget()
│              │              │    ├─ fs.mkdir
│              │              │    └─ atomicWriteBuffer()
│              │              ├─ state.files[fileId] = publicFileRecord()
│              │              └─ managedPathStore.add/move/remove
│              ├─ managedPathStore.replace(...)
│              ├─ state.cursor = response.cursor
│              ├─ await this.store.save()
│              └─ await this.cleanupMergeBasesAfterCheckpoint()
│
├─ 12. 两轮同步循环:
│     for (let pass = 0; pass < 2; pass += 1) {
│       await this.reconcileLocal();
│       await this.pushQueue();
│       await this.pullChanges({ protectedPaths });
│     }
│
│     ├─ 12.1 await this.reconcileLocal()
│     │    └─ LocalSyncClientMethods.reconcileLocal()
│     │         ├─ this.initialize()
│     │         ├─ loadMyNoteIgnore(rootPath)
│     │         ├─ 清理 isAlwaysExcludedPath 的 state.files / state.queue
│     │         │    └─ managedPathStore.remove()
│     │         ├─ 若 state.queue.length → return state.queue.length
│     │         ├─ this.setProgress({ phase: 'scanning', ... })
│     │         ├─ managedPathStore.paths()
│     │         ├─ 把 state.files / state.queue 路径加入 managedPaths
│     │         ├─ this.scanFiles({ reportProgress: true, ignorePolicy, managedPaths })
│     │         │    ├─ 递归 walk
│     │         │    │    ├─ fs.readdir
│     │         │    │    ├─ ignorePolicy.ignores()
│     │         │    │    ├─ contentEncoding()
│     │         │    │    ├─ fs.readFile + fs.stat
│     │         │    │    └─ contentHash()
│     │         │    └─ managedPathStore.add(files.keys())
│     │         ├─ 若 checkRemotePaths:
│     │         │    └─ this.reconcileRemotePathOwners(state, diskFiles)
│     │         │         ├─ this.remotePathOwners(untrackedPaths)
│     │         │         │    └─ remote.pull(libraryId, 0)
│     │         │         └─ 对每个 owner:
│     │         │              ├─ 若 hash 相同 → 采纳 remote fileId
│     │         │              │    ├─ state.files[fileId] = publicFileRecord()
│     │         │              │    └─ managedPathStore.add()
│     │         │              └─ 否则 → this.applyRemoteFile(remoteFile)
│     │         ├─ this.setProgress({ phase: 'reconciling', ... })
│     │         ├─ 比较 activeFiles vs diskFiles
│     │         │    ├─ 内容变化 → this.operation('upsert', file, disk)
│     │         │    ├─ 唯一同内容新路径 → this.operation('rename', file, { relativePath })
│     │         │    ├─ 消失且无重命名候选 → this.operation('delete', file, { relativePath: null })
│     │         │    └─ 新路径 → this.operation('upsert', { fileId: uuid, revision: 0 }, disk)
│     │         │         └─ LocalSyncClientMethods.operation()
│     │         │              ├─ crypto.randomUUID()
│     │         │              ├─ PROTOCOL_VERSION
│     │         │              ├─ contentEncoding(relativePath)
│     │         │              └─ 构造 Operation
│     │         ├─ state.queue.push(...queued)
│     │         ├─ store.log('info', ...)
│     │         └─ await this.store.save()
│     │
│     ├─ 12.2 await this.pushQueue()
│     │    └─ SyncTransferMethods.pushQueue()
│     │         ├─ state.queue[0]
│     │         ├─ this.setProgress({ phase: 'uploading', ... })
│     │         ├─ await this.remote.applyOperation(operation)
│     │         │    └─ HostedSyncRemote.applyOperation()
│     │         │         └─ this.request('POST', `/v1/libraries/${libraryId}/operations`, { body: operation })
│     │         │              └─ HostedSyncRemote.request()
│     │         │                   ├─ AbortController + timeout
│     │         │                   ├─ fetchImpl(endpoint + pathname, { method, headers, body, signal })
│     │         │                   ├─ response.json()
│     │         │                   └─ 若 !response.ok → 抛出 HostedRequestError
│     │         ├─ 若 result.status === 'accepted':
│     │         │    └─ state.files[operation.fileId] = publicFileRecord(result.file)
│     │         ├─ 若 result.status === 'conflict':
│     │         │    ├─ 若 safe → 记录日志、save、cleanupMergeBasesAfterCheckpoint、return false
│     │         │    └─ 否则 this.resolvePushConflict(operation, result)
│     │         │         └─ SyncFileMethods.resolvePushConflict()
│     │         │              ├─ state.files[fileId]
│     │         │              ├─ 判断 equivalentPathOwner
│     │         │              ├─ this.readSyncFile()
│     │         │              ├─ this.tryAutoMerge()
│     │         │              │    └─ SyncMergeMethods.tryAutoMerge()
│     │         │              ├─ this.applyRemoteFile()
│     │         │              ├─ this.preserveConflict()
│     │         │              ├─ this.moveToSyncTrash()
│     │         │              └─ 返回 { rebasedOperation, cleanupConflictPath }
│     │         ├─ 若 conflictResolution.rebasedOperation:
│     │         │    ├─ state.queue[0] = rebasedOperation
│     │         │    ├─ await this.store.save()
│     │         │    ├─ await this.removeConflictSidecars(...)
│     │         │    └─ continue
│     │         ├─ state.queue.shift()
│     │         ├─ this.setProgress(...)
│     │         ├─ await this.store.save()
│     │         ├─ 若 deduplicated && cleanupConflictPath:
│     │         │    └─ this.removeConflictSidecars()
│     │         ├─ 若 result.status === 'accepted':
│     │         │    ├─ 删除 → managedPathStore.remove()
│     │         │    ├─ rename → managedPathStore.move()
│     │         │    └─ 其他 → managedPathStore.add()
│     │         └─ 循环结束:await this.cleanupMergeBasesAfterCheckpoint()
│     │
│     └─ 12.3 await this.pullChanges({ protectedPaths })
│          └─ 同 11,但 providedResponse 可能为空,重新 remote.pull()
│
├─ 13. await syncVersionHistory(this)
│    └─ src/main/sync/history-store.ts
│         ├─ client.initialize()
│         ├─ client.historyStore.contiguousRevision()
│         ├─ 若 cachedRevision === throughRevision → return
│         ├─ client.setProgress({ phase: 'history', ... })
│         ├─ 循环:
│         │    ├─ client.remote.historyPage(libraryId, afterRevision, throughRevision, 100)
│         │    │    └─ HostedSyncRemote.historyPage()
│         │    │         └─ this.request('GET', `/v1/libraries/${libraryId}/versions?...`)
│         │    ├─ 校验 response
│         │    ├─ rebuilding ? replacementDetails.push(...) : client.historyStore.appendContiguous(...)
│         │    │    ├─ atomicWriteJson(versions/{revision}.json)
│         │    │    └─ atomicWriteJson(manifest.json)
│         │    └─ client.setProgress(...)
│         └─ 若 rebuilding → client.historyStore.replace(replacementDetails)
│              ├─ atomicWriteJson(versions/{revision}.json)
│              └─ atomicWriteJson(manifest.json)
│
├─ 14. state.status = 'idle'
├─ 15. this.setProgress(null)
├─ 16. state.lastSyncedAt = this.now().toISOString()
├─ 17. this.store.log('info', '同步完成。', this.syncVersionDetails(state))
│    └─ SyncDiagnosticMethods.syncVersionDetails()
├─ 18. await this.store.save()
│    └─ SyncStateStore.save()
├─ 19. return this.status()
│    └─ SyncTransferMethods.status()
│         ├─ this.initialize()
│         └─ 返回 protocolVersion / libraryId / deviceId / status / progress / pendingCount / cursor / lastError / lastSyncedAt / logs
│
└─ catch (error)
     ├─ 若 error instanceof SafeSyncBlockedError && (automatic || reason === 'dirty-open-document'):
     │    ├─ state.status = 'idle'
     │    ├─ this.setProgress(null)
     │    ├─ state.lastError = null
     │    ├─ this.logSyncOutcome('info', '同步已暂缓...', state, { reason })
     │    ├─ await this.store.save()
     │    └─ return { ...status(), syncSkipped: true, automaticSync: 'skipped', reason }
     ├─ 否则:
     │    ├─ state.status = 'error'
     │    ├─ this.setProgress(null)
     │    ├─ state.lastError = error.message
     │    ├─ this.logSyncOutcome('error', '同步失败...', state, { message })
     │    ├─ await this.store.save()
     │    └─ throw error
     └─ finally
          └─ this.activeSyncDiagnostic = null

snippet-form

test-unit

点赞(0) 阅读(11) 举报
目录
标题