write_ method
Safely writes content
to the File. Passed content
may be String or Uint8List.
-
Does not modify the contents of the original File, but creates a new File, writes the
content
to it & then renames it to the original File's path if writing is successful. This ensures atomicity of the operation. -
Uses Completers to ensure to concurrent transactions to the same file path do not conflict. This ensures mutual exclusion of the operation.
Two Files are created, one for keeping history of the transaction & the other is renamed original File's path.
The creation of transaction history File may be disabled by passing history
as false
.
Implementation
Future<void> write_(
dynamic content, {
bool history = false,
}) async {
locks[clean(path)] ??= Lock();
return locks[clean(path)]?.synchronized(() async {
// Create the [File] if it does not exist.
if (!await File(clean(path)).exists_()) {
await File(clean(path)).create();
}
try {
final id = DateTime.now().millisecondsSinceEpoch;
final files = [
// [File] used for keeping history of the transaction.
File(
join(
clean(parent.path),
'Temp',
'${basename(path)}.$id',
),
),
// [File] used for renaming to the original [File]'s path after successful write.
File(
join(
clean(parent.path),
'Temp',
'${basename(path)}.$id.src',
),
)
];
await Future.wait(
files.asMap().entries.map((e) async {
if (history || e.key == 1) {
await e.value.create_();
if (content is String) {
await e.value.writeAsString(
content,
flush: true,
);
} else if (content is Uint8List) {
await e.value.writeAsBytes(
content,
flush: true,
);
} else {
throw FileSystemException(
'Unsupported content type: ${content.runtimeType}',
path,
);
}
}
}),
);
// To ensure atomicity of the transaction.
await files.last.rename_(clean(path));
} catch (exception, stacktrace) {
print(exception.toString());
print(stacktrace.toString());
}
});
}