diff --git a/packages/core/lib/state.dart b/packages/core/lib/state.dart index 8c376f7..69d9794 100644 --- a/packages/core/lib/state.dart +++ b/packages/core/lib/state.dart @@ -165,23 +165,34 @@ abstract class PersistedState implements AsyncStateNotifier { if (_persistance != null) { _hasUpdated = true; } else { - _persistance = storageJson + _persistance = storageJson ? _store - .setPersisted(_key, toJson(state)) - .whenComplete(_whenPersistenceComplete) + .setPersisted(_key, toJson(state)) + .whenComplete(_whenPersistenceComplete) : null; } }); _store.ready.then((_) async { - final rawV = await _store.getPersisted(_key); + Map? rawV; + try { + rawV = await _store.getPersisted(_key); + } on FormatException catch (e) { + // Addressing https://github.com/segmentio/analytics_flutter/issues/74 + // File corruption should be less likely with removal of async code in writes + // Existing corrupted files are cleaned up here without failing initialization + _store.setPersisted(_key, {}); + log("Clean file $_key with format error", kind: LogFilterKind.warning); + final wrappedError = ErrorLoadingStorage(e); + errorHandler(wrappedError); + } T v; if (rawV == null) { final init = await _initialiser(); - _persistance = storageJson + _persistance = storageJson ? _store - .setPersisted(_key, toJson(init)) - .whenComplete(_whenPersistenceComplete) + .setPersisted(_key, toJson(init)) + .whenComplete(_whenPersistenceComplete) : null; _notifier.nonNullState = init; v = init; @@ -201,16 +212,9 @@ abstract class PersistedState implements AsyncStateNotifier { return; }).catchError((e) { _error = e; - // Clean file if exist a format error - if(_error.toString().contains("FormatException")) { - _store.setPersisted(_key, {}); - log("Clean file $_key with format error", - kind: LogFilterKind.warning); - } else { - final wrappedError = ErrorLoadingStorage(e); - errorHandler(wrappedError); - throw wrappedError; - } + final wrappedError = ErrorLoadingStorage(e); + errorHandler(wrappedError); + throw wrappedError; }); } @@ -552,8 +556,7 @@ class Configuration { this.debug = false, this.maxBatchSize, this.storageJson = true, - this.token - }); + this.token}); } typedef ErrorHandler = void Function(Exception); diff --git a/packages/core/lib/utils/store/io.dart b/packages/core/lib/utils/store/io.dart index 5bfeeed..e4314f9 100644 --- a/packages/core/lib/utils/store/io.dart +++ b/packages/core/lib/utils/store/io.dart @@ -27,11 +27,12 @@ class StoreImpl with Store { final serialized = json.encode(data); final buffer = utf8.encode(serialized); - file = await file.lock(); - file = await file.setPosition(0); - file = await file.writeFrom(buffer); - file = await file.truncate(buffer.length); - await file.unlock(); + file.lockSync(FileLock.blockingExclusive); + file.setPositionSync(0); + file.writeFromSync(buffer); + file.truncateSync(buffer.length); + file.unlockSync(); + file.closeSync(); } Future?> _readFile(String fileKey) async { @@ -39,10 +40,13 @@ class StoreImpl with Store { if (file == null) { return null; } - final length = await file.length(); - file = await file.setPosition(0); + file = await file.lock(FileLock.blockingShared); + final length = file.lengthSync(); + file.setPositionSync(0); final buffer = Uint8List(length); - await file.readInto(buffer); + file.readIntoSync(buffer); + file.unlockSync(); + file.closeSync(); final contentText = utf8.decode(buffer); if (contentText == "{}") { return null; // Prefer null to empty map, because we'll want to initialise a valid empty value.