writeAsBytesCompat method

Future writeAsBytesCompat(
  1. Uint8List binary, {
  2. int blockSize = 1024 * 256,
  3. int retry = 10,
})

Dart SDKのFile書き込み不具合を避け、可能な限り確実にファイルを書き込む.

大容量ファイルの書き込みを分割チェックして行うため、適切な blockSize を指定する.

NOTE. 書き込みの仕組み上、この処理は上書き保存のみをサポートする. 追記を確実に行いたい場合は別途確実性の高い手段を考えること.

NOTE. 経験則として概ね1回目のリトライで書き込みは成功する.

Dart SDK issue: https://github.com/dart-lang/sdk/issues/36087

Implementation

Future writeAsBytesCompat(
  Uint8List binary, {
  int blockSize = 1024 * 256,
  int retry = 10,
}) async {
  Future<bool> writeImpl() async {
    // 既にファイルが存在していたら消す
    if (await exists()) {
      await delete();
    }
    var cursor = 0;
    try {
      final sink = await open(mode: io.FileMode.writeOnly);
      try {
        while (cursor < binary.length) {
          final length = min(blockSize, binary.length - cursor);
          await sink.writeFrom(binary, cursor, cursor + length);
          await Future<void>.delayed(const Duration(milliseconds: 1));
          await sink.flush();
          cursor += length;

          // ファイルの存在と長さをチェック
          if (!(await exists()) || (await this.length()) != cursor) {
            return false;
          }
        }
      } finally {
        await sink.close();
      }
    } on io.FileSystemException catch (e, stackTrace) {
      if (kDebugMode) {
        debugPrint('FileExtensions.writeAsBytesCompat() failed: $e');
        debugPrint(stackTrace.toString());
      }
      return false;
    }
    return true;
  }

  // 失敗したのでリトライ
  for (var i = 0; i < (retry + 1); ++i) {
    if (await writeImpl()) {
      // 無事完了
      return;
    }
    debugPrint('Retry write[${i + 1}] -> $path');
  }
  throw io.FileSystemException('Write failed: $path');
}