file_system.dart 5.11 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:file/memory.dart';
8
import 'package:file/record_replay.dart';
9

10

11
import 'common.dart' show throwToolExit;
12
import 'context.dart';
13
import 'process.dart';
14

15 16
export 'package:file/file.dart';
export 'package:file/local.dart';
yjbanov's avatar
yjbanov committed
17

18
const String _kRecordingType = 'file';
19 20
const FileSystem _kLocalFs = const LocalFileSystem();

21
/// Currently active implementation of the file system.
yjbanov's avatar
yjbanov committed
22 23 24
///
/// By default it uses local disk-based implementation. Override this in tests
/// with [MemoryFileSystem].
25
FileSystem get fs => context == null ? _kLocalFs : context[FileSystem];
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
/// Enables recording of file system activity to the specified base recording
/// [location].
///
/// This sets the [active file system](fs) to one that records all invocation
/// activity before delegating to a [LocalFileSystem].
///
/// Activity will be recorded in a subdirectory of [location] named `"file"`.
/// It is permissible for [location] to represent an existing non-empty
/// directory as long as there is no collision with the `"file"` subdirectory.
void enableRecordingFileSystem(String location) {
  Directory dir = getRecordingSink(location, _kRecordingType);
  RecordingFileSystem fileSystem = new RecordingFileSystem(
      delegate: _kLocalFs, destination: dir);
  addShutdownHook(() => fileSystem.recording.flush());
  context.setVariable(FileSystem, fileSystem);
}

/// Enables file system replay mode.
///
/// This sets the [active file system](fs) to one that replays invocation
/// activity from a previously recorded set of invocations.
///
/// [location] must represent a directory to which file system activity has
/// been recorded (i.e. the result of having been previously passed to
/// [enableRecordingFileSystem]), or a [ToolExit] will be thrown.
void enableReplayFileSystem(String location) {
  Directory dir = getReplaySource(location, _kRecordingType);
  context.setVariable(FileSystem, new ReplayFileSystem(recording: dir));
}

57 58
/// Create the ancestor directories of a file path if they do not already exist.
void ensureDirectoryExists(String filePath) {
59
  String dirPath = fs.path.dirname(filePath);
60
  if (fs.isDirectorySync(dirPath))
61
    return;
62 63 64
  try {
    fs.directory(dirPath).createSync(recursive: true);
  } on FileSystemException catch (e) {
65
    throwToolExit('Failed to create directory "$dirPath": ${e.osError.message}');
66
  }
67
}
68

69 70 71 72
/// Recursively copies `srcDir` to `destDir`.
///
/// Creates `destDir` if needed.
void copyDirectorySync(Directory srcDir, Directory destDir) {
73 74 75 76 77 78
  if (!srcDir.existsSync())
    throw new Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');

  if (!destDir.existsSync())
    destDir.createSync(recursive: true);

79
  srcDir.listSync().forEach((FileSystemEntity entity) {
80
    String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
81
    if (entity is File) {
82
      File newFile = destDir.fileSystem.file(newPath);
83
      newFile.writeAsBytesSync(entity.readAsBytesSync());
84
    } else if (entity is Directory) {
85 86
      copyDirectorySync(
        entity, destDir.fileSystem.directory(newPath));
87 88 89 90 91
    } else {
      throw new Exception('${entity.path} is neither File nor Directory');
    }
  });
}
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132

/// Gets a directory to act as a recording destination, creating the directory
/// as necessary.
///
/// The directory will exist in the local file system, be named [basename], and
/// be a child of the directory identified by [dirname].
///
/// If the target directory already exists as a directory, the existing
/// directory must be empty, or a [ToolExit] will be thrown. If the target
/// directory exists as an entity other than a directory, a [ToolExit] will
/// also be thrown.
Directory getRecordingSink(String dirname, String basename) {
  String location = _kLocalFs.path.join(dirname, basename);
  switch (_kLocalFs.typeSync(location, followLinks: false)) {
    case FileSystemEntityType.FILE:
    case FileSystemEntityType.LINK:
      throwToolExit('Invalid record-to location: $dirname ("$basename" exists as non-directory)');
      break;
    case FileSystemEntityType.DIRECTORY:
      if (_kLocalFs.directory(location).listSync(followLinks: false).isNotEmpty)
        throwToolExit('Invalid record-to location: $dirname ("$basename" is not empty)');
      break;
    case FileSystemEntityType.NOT_FOUND:
      _kLocalFs.directory(location).createSync(recursive: true);
  }
  return _kLocalFs.directory(location);
}

/// Gets a directory that holds a saved recording to be used for the purpose of
/// replay.
///
/// The directory will exist in the local file system, be named [basename], and
/// be a child of the directory identified by [dirname].
///
/// If the target directory does not exist, a [ToolExit] will be thrown.
Directory getReplaySource(String dirname, String basename) {
  Directory dir = _kLocalFs.directory(_kLocalFs.path.join(dirname, basename));
  if (!dir.existsSync())
    throwToolExit('Invalid replay-from location: $dirname ("$basename" does not exist)');
  return dir;
}