Skip to content

Commit 262c824

Browse files
authored
Add the ability to create directories (#215)
1 parent 0097d9e commit 262c824

File tree

5 files changed

+130
-1
lines changed

5 files changed

+130
-1
lines changed

pkgs/io_file/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ See
1212
| :--- | :---: | :---: | :---: | :---: | :----: | :--------: | :----------: |
1313
| canonicalize path | | | | | | | |
1414
| copy file | | | | | | | |
15-
| create directory | | | | | | | |
15+
| create directory | | | | | | | |
1616
| create hard link | | | | | | | |
1717
| create symbolic link | | | | | | | |
1818
| create tmp directory | | | | | | | |

pkgs/io_file/lib/src/file_system.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
import 'dart:typed_data';
66

7+
// TODO(brianquinlan): When we switch to using exception types outside of
8+
// `dart:io` then change the doc strings to use reference syntax rather than
9+
// code syntax e.g. `PathExistsException` => [PathExistsException].
10+
711
/// The modes in which a File can be written.
812
class WriteMode {
913
/// Open the file for writing such that data can only be appended to the end
@@ -30,6 +34,15 @@ class WriteMode {
3034

3135
/// An abstract representation of a file system.
3236
base class FileSystem {
37+
/// Create a directory at the given path.
38+
///
39+
/// If the directory already exists, then `PathExistsException` is thrown.
40+
///
41+
/// If the parent path does not exist, then `PathNotFoundException` is thrown.
42+
void createDirectory(String path) {
43+
throw UnsupportedError('createDirectory');
44+
}
45+
3346
/// Renames, and possibly moves a file system object from one path to another.
3447
///
3548
/// If `newPath` is a relative path, it is resolved against the current

pkgs/io_file/lib/src/vm_posix_file_system.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import 'internal_constants.dart';
1616
/// The default `mode` to use with `open` calls that may create a file.
1717
const _defaultMode = 438; // => 0666 => rw-rw-rw-
1818

19+
/// The default `mode` to use when creating a directory.
20+
const _defaultDirectoryMode = 511; // => 0777 => rwxrwxrwx
21+
1922
Exception _getError(int errno, String message, String path) {
2023
//TODO(brianquinlan): In the long-term, do we need to avoid exceptions that
2124
// are part of `dart:io`? Can we move those exceptions into a different
@@ -57,6 +60,14 @@ external int write(int fd, Pointer<Uint8> buf, int count);
5760
/// A [FileSystem] implementation for POSIX systems (e.g. Android, iOS, Linux,
5861
/// macOS).
5962
base class PosixFileSystem extends FileSystem {
63+
@override
64+
void createDirectory(String path) {
65+
if (stdlibc.mkdir(path, _defaultDirectoryMode) == -1) {
66+
final errno = stdlibc.errno;
67+
throw _getError(errno, 'create directory failed', path);
68+
}
69+
}
70+
6071
@override
6172
void rename(String oldPath, String newPath) {
6273
// See https://pubs.opengroup.org/onlinepubs/000095399/functions/rename.html

pkgs/io_file/lib/src/vm_windows_file_system.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ Exception _getError(int errorCode, String message, String path) {
6868

6969
/// A [FileSystem] implementation for Windows systems.
7070
base class WindowsFileSystem extends FileSystem {
71+
@override
72+
void createDirectory(String path) => using((arena) {
73+
// Calling `GetLastError` for the first time causes the `GetLastError`
74+
// symbol to be loaded, which resets `GetLastError`. So make a harmless
75+
// call before the value is needed.
76+
win32.GetLastError();
77+
if (win32.CreateDirectory(path.toNativeUtf16(), nullptr) == win32.FALSE) {
78+
final errorCode = win32.GetLastError();
79+
throw _getError(errorCode, 'create directory failed', path);
80+
}
81+
});
82+
7183
@override
7284
void rename(String oldPath, String newPath) => using((arena) {
7385
// Calling `GetLastError` for the first time causes the `GetLastError`
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@TestOn('vm')
6+
library;
7+
8+
import 'dart:io';
9+
10+
import 'package:io_file/io_file.dart';
11+
import 'package:stdlibc/stdlibc.dart' as stdlibc;
12+
import 'package:test/test.dart';
13+
import 'package:win32/win32.dart' as win32;
14+
15+
import 'test_utils.dart';
16+
17+
void main() {
18+
group('createDirectory', () {
19+
late String tmp;
20+
21+
setUp(() => tmp = createTemp('createDirectory'));
22+
23+
tearDown(() => deleteTemp(tmp));
24+
25+
//TODO(brianquinlan): test with a very long path.
26+
27+
test('success', () {
28+
final path = '$tmp/dir';
29+
30+
fileSystem.createDirectory(path);
31+
expect(FileSystemEntity.isDirectorySync(path), isTrue);
32+
});
33+
34+
test('create in non-existent directory', () {
35+
final path = '$tmp/foo/dir';
36+
37+
expect(
38+
() => fileSystem.createDirectory(path),
39+
throwsA(
40+
isA<PathNotFoundException>()
41+
.having((e) => e.message, 'message', 'create directory failed')
42+
.having(
43+
(e) => e.osError?.errorCode,
44+
'errorCode',
45+
Platform.isWindows
46+
? win32.ERROR_PATH_NOT_FOUND
47+
: stdlibc.ENOENT,
48+
),
49+
),
50+
);
51+
});
52+
53+
test('create over existing directory', () {
54+
final path = '$tmp/dir';
55+
Directory(path).createSync();
56+
57+
expect(
58+
() => fileSystem.createDirectory(path),
59+
throwsA(
60+
isA<PathExistsException>()
61+
.having((e) => e.message, 'message', 'create directory failed')
62+
.having(
63+
(e) => e.osError?.errorCode,
64+
'errorCode',
65+
Platform.isWindows
66+
? win32.ERROR_ALREADY_EXISTS
67+
: stdlibc.EEXIST,
68+
),
69+
),
70+
);
71+
});
72+
73+
test('create over existing file', () {
74+
final path = '$tmp/file';
75+
File(path).createSync();
76+
77+
expect(
78+
() => fileSystem.createDirectory(path),
79+
throwsA(
80+
isA<PathExistsException>()
81+
.having((e) => e.message, 'message', 'create directory failed')
82+
.having(
83+
(e) => e.osError?.errorCode,
84+
'errorCode',
85+
Platform.isWindows
86+
? win32.ERROR_ALREADY_EXISTS
87+
: stdlibc.EEXIST,
88+
),
89+
),
90+
);
91+
});
92+
});
93+
}

0 commit comments

Comments
 (0)