Skip to content

Commit

Permalink
fix(Project): set autoSaveToCloudState to Saved when retrying aut…
Browse files Browse the repository at this point in the history
…o-save without unsynced changes

Signed-off-by: Aofei Sheng <aofei@aofeisheng.com>
  • Loading branch information
aofei committed Aug 23, 2024
1 parent f1714cd commit 49c18b5
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 11 deletions.
58 changes: 56 additions & 2 deletions spx-gui/src/models/project/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { describe, it, expect, vi } from 'vitest'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { Sprite } from '../sprite'
import { Animation } from '../animation'
import { Sound } from '../sound'
import { Costume } from '../costume'
import { fromText, type Files } from '../common/file'
import { Project } from '.'
import { AutoSaveMode, AutoSaveToCloudState, Project } from '.'
import * as cloudHelper from '../common/cloud'
import * as localHelper from '../common/local'

function mockFile(name = 'mocked') {
return fromText(name, Math.random() + '')
Expand Down Expand Up @@ -137,3 +140,54 @@ describe('Project', () => {
expect(saveToLocalCacheMethod).toHaveBeenCalledWith('key')
})
})

describe('ProjectAutoSave', () => {
beforeEach(() => {
vi.useFakeTimers()
})

afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
})

// https://github.com/goplus/builder/pull/794#discussion_r1728120369
it('should handle failed auto-save correctly', async () => {
const project = makeProject()

const cloudSaveMock = vi.spyOn(cloudHelper, 'save').mockRejectedValue(new Error('save failed'))
const localSaveMock = vi.spyOn(localHelper, 'save').mockResolvedValue(undefined)
const localClearMock = vi.spyOn(localHelper, 'clear').mockResolvedValue(undefined)

await project.startEditing('localCacheKey')
project.setAutoSaveMode(AutoSaveMode.Cloud)

const newSprite = new Sprite('newSprite')
project.addSprite(newSprite)
await flushPromises()
await vi.advanceTimersByTimeAsync(1000) // wait for changes to be picked up
await flushPromises()
expect(project.hasUnsyncedChanges).toBe(true)

await vi.advanceTimersByTimeAsync(1500) // wait for auto-save to trigger
await flushPromises()
expect(project.autoSaveToCloudState).toBe(AutoSaveToCloudState.Failed)
expect(project.hasUnsyncedChanges).toBe(true)
expect(cloudSaveMock).toHaveBeenCalledTimes(1)
expect(localSaveMock).toHaveBeenCalledTimes(1)

project.removeSprite(newSprite.name)
await flushPromises()
await vi.advanceTimersByTimeAsync(1000) // wait for changes to be picked up
await flushPromises()
expect(project.hasUnsyncedChanges).toBe(false)

await vi.advanceTimersByTimeAsync(5000) // wait for auto-retry to trigger
await flushPromises()
expect(project.autoSaveToCloudState).toBe(AutoSaveToCloudState.Saved)
expect(project.hasUnsyncedChanges).toBe(false)
expect(cloudSaveMock).toHaveBeenCalledTimes(1)
expect(localSaveMock).toHaveBeenCalledTimes(1)
expect(localClearMock).toHaveBeenCalledTimes(1)
})
})
21 changes: 12 additions & 9 deletions spx-gui/src/models/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,25 +430,28 @@ export class Project extends Disposable {
try {
if (this.hasUnsyncedChanges) await this.saveToCloud()
this.autoSaveToCloudState = AutoSaveToCloudState.Saved
if (this.hasUnsyncedChanges) autoSaveToCloud()
else await localHelper.clear(localCacheKey)
} catch (e) {
await this.saveToLocalCache(localCacheKey) // prevent data loss
this.autoSaveToCloudState = AutoSaveToCloudState.Failed
startRetry()
throw e
await this.saveToLocalCache(localCacheKey) // prevent data loss
console.error('failed to auto save to cloud', e)
return
}

if (this.hasUnsyncedChanges) autoSaveToCloud()
else await localHelper.clear(localCacheKey)
}, 1500)

let retryTimeoutId: ReturnType<typeof setTimeout>
const startRetry = () => {
stopRetry()
retryTimeoutId = setTimeout(() => {
if (
this.autoSaveToCloudState === AutoSaveToCloudState.Failed &&
this.hasUnsyncedChanges
) {
retryTimeoutId = setTimeout(async () => {
if (this.autoSaveToCloudState !== AutoSaveToCloudState.Failed) return
if (this.hasUnsyncedChanges) {
autoSaveToCloud()
} else {
this.autoSaveToCloudState = AutoSaveToCloudState.Saved
await localHelper.clear(localCacheKey)
}
}, 5000)
}
Expand Down

0 comments on commit 49c18b5

Please sign in to comment.