withFileProtection<R> function

Future<R> withFileProtection<R>(
  1. List<String> protected,
  2. R action(), {
  3. String? workingDirectory,
})

EXPERIMENTAL - use with caution and the api may change.

Allows you to nominate a list of files to be backed up before an operation commences and then restored once the operation completes.

Currently the files a protected by making a copy of each file/directory into a unique system temp directory and then moved back once the action has completed.

withFileProtection is safe to use in a nested fashion as each call to withFileProtection creates its own separate backup area.

If the VM aborts during execution of the action you will find the backed up files in the system temp directory under a directory named .withFileProtection'. You may need to use the time stamp to determine which directory is the right one if you have had mulitple failures. Under normal circumstances the temp directory is delete once the action completes.

The protected list can contain files, directories.

If the entry is a directory then all children (files and directories) are protected.

Entries in the protected list may be relative or absolute.

If protected contains a file or directory that doesn't exist and the action subsequently creates those entities, then those files and/or directories will be deleted after action completes.

This function can be useful for doing dry-run operations where you need to ensure the filesystem is restore to its prior state after the dry-run completes.

TODO: make this work for other than current drive under Windows

Implementation

///
/// [withFileProtection] is safe to use in a nested fashion as each call
/// to [withFileProtection] creates its own separate backup area.
///
/// If the VM aborts during execution of the [action] you will find
/// the backed up files in the system temp directory under a directory named
/// .withFileProtection'. You may need to use the time stamp to determine which
/// directory is the right one if you have had mulitple failures.
/// Under normal circumstances the temp directory is delete once the action
/// completes.
///
/// The [protected] list can contain files, directories.
///
/// If the entry is a directory then all children (files and directories)
/// are protected.
///
/// Entries in the [protected] list may be relative or absolute.
///
/// If [protected] contains a file or directory that doesn't exist
/// and the [action] subsequently creates those entities, then those files
/// and/or directories will be deleted after [action] completes.
///
/// This function can be useful for doing dry-run operations
/// where you need to ensure the filesystem is restore to its
/// prior state after the dry-run completes.
///
// ignore: flutter_style_todos
/// TODO: make this work for other than current drive under Windows
///
Future<R> withFileProtection<R>(
  List<String> protected,
  R Function() action, {
  String? workingDirectory,
}) async {
  // removed glob support for the moment.
  // This is because if one of the protected entriese is missing
  // then we are assuming its a glob.
  // We should probably change to accepting a Pattern
  // and the have the user pass an actual Glob.
  // Problem with this is that find uses a subset of Glob.
  // so for the moment, no glob support
  // a glob pattern as supported by the [find] command.
  // We only support searching for files by the glob pattern (not directories).
  // If the entry is a glob pattern then it is applied recusively.

  final _workingDirectory = workingDirectory ?? pwd;
  final result = await withTempDir(
    (backupDir) async {
      verbose(() => 'withFileProtection: backing up to $backupDir');

      /// backup the protected files
      /// to a backupDir
      for (final path in protected) {
        final paths = _determinePaths(
          path: path,
          workingDirectory: _workingDirectory,
          backupDir: backupDir,
        );

        if (!exists(paths.sourcePath)) {
          /// the file/directory doesn't exist.
          /// During the restore process this path will be deleted
          /// so that once again they don't exist.
          continue;
        }

        if (isFile(paths.sourcePath)) {
          if (!exists(dirname(paths.backupPath))) {
            await createDir(dirname(paths.backupPath), recursive: true);
          }

          /// the entity is a simple file.
          await copy(paths.sourcePath, paths.backupPath);
        } else if (isDirectory(paths.sourcePath)) {
          /// the entity is a directory so copy the whole tree
          /// recursively.
          if (!exists(paths.backupPath)) {
            await createDir(paths.backupPath, recursive: true);
          }
          await copyTree(paths.sourcePath, paths.backupPath,
              includeHidden: true);
        } else {
          throw BackupFileException(
            'Unsupported entity type for ${paths.sourcePath}. '
            'Only files and directories are supported',
          );
        }
        // else {
        //   /// Must be a glob.
        //   for (final file in find(paths.source, includeHidden: true)
        //        .toList()) {
        //     // we need to determine the paths for each [file]
        //     // as the can have a different relative path as we
        //     // do a recursive search.
        //     final paths = _determinePaths(
        //         path: file, sourceDir: sourceDir, backupDir: backupDir);

        //     if (!exists(dirname(paths.target))) {
        //       createDir(dirname(paths.target), recursive: true);
        //     }
        //     copy(paths.source, paths.target);
        //   }
        // }
      }
      final result = action();

      /// restore the protected entities
      for (final path in protected) {
        final paths = _determinePaths(
          path: path,
          workingDirectory: _workingDirectory,
          backupDir: backupDir,
        );
        {
          if (!exists(paths.backupPath)) {
            /// If the protected entity didn't exist before we started
            /// the make certain it doesn't exist now.
            await _deleteEntity(paths.sourcePath);
          }

          if (isFile(paths.backupPath)) {
            await _restoreFile(paths);
          }

          if (isDirectory(paths.backupPath)) {
            await _restoreDirectory(paths);
          }
        }
      }

      return result;
    },
    keep: true,
  );

  return result;
}