Skip to content

Commit 0cb5e78

Browse files
authored
Partial implementation of in-memory file system. (flutter#10)
This implements most of the in-memory file system. The only part that's not complete is memory_file.dart. The rest of the impl, plus tests, will follow for both the in-memory implementation and the local backend in an upcoming change.
1 parent a4c6c7f commit 0cb5e78

25 files changed

+1079
-614
lines changed

lib/memory.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'src/backends/memory.dart';

lib/src/backends/local/local_directory.dart

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
part of file.src.backends.local;
22

3-
class _LocalDirectory extends _LocalFileSystemEntity<
4-
_LocalDirectory,
5-
io.Directory> implements Directory {
6-
3+
class _LocalDirectory
4+
extends _LocalFileSystemEntity<_LocalDirectory, io.Directory>
5+
implements Directory {
76
_LocalDirectory(FileSystem fileSystem, io.Directory delegate)
87
: super(fileSystem, delegate);
98

@@ -31,16 +30,18 @@ class _LocalDirectory extends _LocalFileSystemEntity<
3130
Stream<FileSystemEntity> list({
3231
bool recursive: false,
3332
bool followLinks: true,
34-
}) => _delegate.list(recursive: recursive, followLinks: followLinks)
35-
.map(_wrap);
33+
}) =>
34+
_delegate.list(recursive: recursive, followLinks: followLinks).map(_wrap);
3635

3736
@override
3837
List<FileSystemEntity> listSync({
3938
bool recursive: false,
4039
bool followLinks: true,
41-
}) => _delegate.listSync(recursive: recursive, followLinks: followLinks)
42-
.map((io.FileSystemEntity entity) => _wrap(entity))
43-
.toList();
40+
}) =>
41+
_delegate
42+
.listSync(recursive: recursive, followLinks: followLinks)
43+
.map((io.FileSystemEntity entity) => _wrap(entity))
44+
.toList();
4445

4546
FileSystemEntity _wrap(io.FileSystemEntity entity) {
4647
if (entity is io.File) {

lib/src/backends/local/local_file.dart

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class _LocalFile extends _LocalFileSystemEntity<File, io.File> implements File {
3838
@override
3939
Future<io.RandomAccessFile> open({
4040
io.FileMode mode: io.FileMode.READ,
41-
}) async => _delegate.open(mode: mode);
41+
}) async =>
42+
_delegate.open(mode: mode);
4243

4344
@override
4445
io.RandomAccessFile openSync({io.FileMode mode: io.FileMode.READ}) =>
@@ -52,7 +53,8 @@ class _LocalFile extends _LocalFileSystemEntity<File, io.File> implements File {
5253
io.IOSink openWrite({
5354
io.FileMode mode: io.FileMode.WRITE,
5455
Encoding encoding: UTF8,
55-
}) => _delegate.openWrite(mode: mode, encoding: encoding);
56+
}) =>
57+
_delegate.openWrite(mode: mode, encoding: encoding);
5658

5759
@override
5860
Future<List<int>> readAsBytes() => _delegate.readAsBytes();
@@ -81,42 +83,46 @@ class _LocalFile extends _LocalFileSystemEntity<File, io.File> implements File {
8183
List<int> bytes, {
8284
io.FileMode mode: io.FileMode.WRITE,
8385
bool flush: false,
84-
}) async => _createNew(await _delegate.writeAsBytes(
85-
bytes,
86-
mode: mode,
87-
flush: flush,
88-
));
86+
}) async =>
87+
_createNew(await _delegate.writeAsBytes(
88+
bytes,
89+
mode: mode,
90+
flush: flush,
91+
));
8992

9093
@override
9194
void writeAsBytesSync(
9295
List<int> bytes, {
9396
io.FileMode mode: io.FileMode.WRITE,
9497
bool flush: false,
95-
}) => _delegate.writeAsBytesSync(bytes, mode: mode, flush: flush);
98+
}) =>
99+
_delegate.writeAsBytesSync(bytes, mode: mode, flush: flush);
96100

97101
@override
98102
Future<File> writeAsString(
99103
String contents, {
100104
io.FileMode mode: io.FileMode.WRITE,
101105
Encoding encoding: UTF8,
102106
bool flush: false,
103-
}) async => _createNew(await _delegate.writeAsString(
104-
contents,
105-
mode: mode,
106-
encoding: encoding,
107-
flush: flush,
108-
));
107+
}) async =>
108+
_createNew(await _delegate.writeAsString(
109+
contents,
110+
mode: mode,
111+
encoding: encoding,
112+
flush: flush,
113+
));
109114

110115
@override
111116
void writeAsStringSync(
112117
String contents, {
113118
io.FileMode mode: io.FileMode.WRITE,
114119
Encoding encoding: UTF8,
115120
bool flush: false,
116-
}) => _delegate.writeAsStringSync(
117-
contents,
118-
mode: mode,
119-
encoding: encoding,
120-
flush: flush,
121-
);
121+
}) =>
122+
_delegate.writeAsStringSync(
123+
contents,
124+
mode: mode,
125+
encoding: encoding,
126+
flush: flush,
127+
);
122128
}

lib/src/backends/local/local_file_system.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
part of file.src.backends.local;
22

33
/// A wrapper implementation around `dart:io`'s implementation.
4+
///
5+
/// Since this implementation of the [FileSystem] interface delegates to
6+
/// `dart:io`, is is not suitable for use in the browser.
47
class LocalFileSystem extends FileSystem {
58
const LocalFileSystem();
69

lib/src/backends/local/local_file_system_entity.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
part of file.src.backends.local;
22

3-
abstract class _LocalFileSystemEntity<
4-
T extends FileSystemEntity,
5-
D extends io.FileSystemEntity
6-
> implements FileSystemEntity {
7-
3+
abstract class _LocalFileSystemEntity<T extends FileSystemEntity,
4+
D extends io.FileSystemEntity> implements FileSystemEntity {
85
@override
96
final FileSystem fileSystem;
107

@@ -57,7 +54,8 @@ abstract class _LocalFileSystemEntity<
5754
Stream<io.FileSystemEvent> watch({
5855
int events: io.FileSystemEvent.ALL,
5956
bool recursive: false,
60-
}) => _delegate.watch(events: events, recursive: recursive);
57+
}) =>
58+
_delegate.watch(events: events, recursive: recursive);
6159

6260
@override
6361
bool get isAbsolute => _delegate.isAbsolute;

lib/src/backends/memory.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
library file.src.backends.memory;
22

33
import 'dart:async';
4+
import 'dart:convert';
5+
import 'dart:io' as io;
46

5-
import 'package:file/src/interface.dart';
6-
7-
import '../memory_utils.dart';
7+
import 'package:file/file.dart';
8+
import 'package:path/path.dart' as p;
89

910
part 'memory/memory_directory.dart';
1011
part 'memory/memory_file.dart';
12+
part 'memory/memory_file_stat.dart';
1113
part 'memory/memory_file_system.dart';
1214
part 'memory/memory_file_system_entity.dart';
15+
part 'memory/memory_link.dart';
16+
part 'memory/node.dart';
17+
part 'memory/utils.dart';
Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,134 @@
11
part of file.src.backends.memory;
22

3-
class _MemoryDirectory extends _MemoryFileSystemEntity with Directory {
3+
class _MemoryDirectory extends _MemoryFileSystemEntity implements Directory {
4+
static int _tempCounter = 0;
5+
46
_MemoryDirectory(MemoryFileSystem fileSystem, String path)
57
: super(fileSystem, path);
6-
// Create an object representing a directory with no files.
8+
9+
@override
10+
io.FileSystemEntityType get expectedType => io.FileSystemEntityType.DIRECTORY;
11+
12+
@override
13+
Uri get uri => new Uri.directory(path);
14+
15+
@override
16+
bool existsSync() => backingOrNull?.stat?.type == expectedType;
17+
718
@override
8-
Object _createImpl() => {};
19+
Future<Directory> create({bool recursive: false}) async {
20+
createSync(recursive: recursive);
21+
return this;
22+
}
923

1024
@override
11-
Stream<FileSystemEntity> list({bool recursive: false}) async* {
12-
var directory = _resolve(false);
13-
if (directory == null) {
14-
throw new FileSystemEntityException('Not found', path);
25+
void createSync({bool recursive: false}) {
26+
_Node node = _createSync(
27+
(_DirectoryNode parent, bool isFinalSegment) {
28+
if (recursive || isFinalSegment) {
29+
return new _DirectoryNode(parent);
30+
}
31+
return null;
32+
},
33+
);
34+
if (node.type != expectedType) {
35+
// There was an existing non-directory node at this object's path
36+
throw new io.FileSystemException('Creation failed', path);
1537
}
16-
if (name != '') {
17-
directory = directory[name] as Map<String, Object>;
38+
}
39+
40+
@override
41+
Future<Directory> createTemp([String prefix]) async => createTempSync(prefix);
42+
43+
@override
44+
Directory createTempSync([String prefix]) {
45+
prefix ??= '';
46+
String fullPath = '$path$_separator$prefix';
47+
String dirname = fileSystem._context.dirname(fullPath);
48+
String basename = fileSystem._context.basename(fullPath);
49+
_DirectoryNode node = fileSystem._findNode(dirname);
50+
_checkExists(node, () => dirname);
51+
var name = () => '$basename$_tempCounter';
52+
while (node.children.containsKey(name())) {
53+
_tempCounter++;
1854
}
19-
// This could be optimized heavily, right now it makes a lot of extra
20-
// lookups and gets more and more expensive as you traverse downwards.
21-
for (var name in directory.keys) {
22-
var entityPath = '$path/$name';
23-
if (await fileSystem.type(entityPath) == FileSystemEntityType.FILE) {
24-
yield fileSystem.file(entityPath);
25-
} else {
26-
yield fileSystem.directory(entityPath);
27-
if (recursive) {
28-
yield* fileSystem.directory(entityPath).list(recursive: true);
55+
_DirectoryNode tempDir = new _DirectoryNode(node);
56+
node.children[name()] = tempDir;
57+
return new _MemoryDirectory(fileSystem, '$dirname$_separator${name()}');
58+
}
59+
60+
@override
61+
Future<Directory> rename(String newPath) async => renameSync(newPath);
62+
63+
@override
64+
Directory renameSync(String newPath) => _renameSync(
65+
newPath,
66+
validateOverwriteExistingEntity: (_DirectoryNode existingNode) {
67+
if (existingNode.children.isNotEmpty) {
68+
throw new io.FileSystemException('Directory not empty', newPath);
69+
}
70+
},
71+
);
72+
73+
@override
74+
Directory get parent =>
75+
(backingOrNull?.isRoot ?? false) ? this : super.parent;
76+
77+
@override
78+
Directory get absolute => super.absolute;
79+
80+
@override
81+
Stream<FileSystemEntity> list({
82+
bool recursive: false,
83+
bool followLinks: true,
84+
}) =>
85+
new Stream.fromIterable(listSync(
86+
recursive: recursive,
87+
followLinks: followLinks,
88+
));
89+
90+
@override
91+
List<FileSystemEntity> listSync({
92+
bool recursive: false,
93+
bool followLinks: true,
94+
}) {
95+
_DirectoryNode node = backing;
96+
List<FileSystemEntity> listing = <FileSystemEntity>[];
97+
Set<_LinkNode> visitedLinks = new Set<_LinkNode>();
98+
List<_PendingListTask> tasks = <_PendingListTask>[
99+
new _PendingListTask(
100+
node,
101+
path.endsWith(_separator) ? path.substring(0, path.length - 1) : path,
102+
),
103+
];
104+
while (tasks.isNotEmpty) {
105+
_PendingListTask task = tasks.removeLast();
106+
task.dir.children.forEach((String name, _Node child) {
107+
String childPath = '${task.path}$_separator$name';
108+
if (followLinks && _isLink(child) && visitedLinks.add(child)) {
109+
child = (child as _LinkNode).referent;
29110
}
30-
}
111+
if (_isDirectory(child)) {
112+
listing.add(new _MemoryDirectory(fileSystem, childPath));
113+
if (recursive) {
114+
tasks.add(new _PendingListTask(child, childPath));
115+
}
116+
} else if (_isLink(child)) {
117+
listing.add(new _MemoryLink(fileSystem, childPath));
118+
} else if (_isFile(child)) {
119+
listing.add(new _MemoryFile(fileSystem, childPath));
120+
}
121+
});
31122
}
123+
return listing;
32124
}
33125

34126
@override
35-
final FileSystemEntityType _type = FileSystemEntityType.DIRECTORY;
127+
Directory _clone(String path) => new _MemoryDirectory(fileSystem, path);
128+
}
129+
130+
class _PendingListTask {
131+
final _DirectoryNode dir;
132+
final String path;
133+
_PendingListTask(this.dir, this.path);
36134
}

0 commit comments

Comments
 (0)