Skip to content

Commit 1ebc338

Browse files
authored
Implement same on win32. (#213)
1 parent 77d1df2 commit 1ebc338

File tree

3 files changed

+102
-22
lines changed

3 files changed

+102
-22
lines changed

pkgs/io_file/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ See
2323
| enum dir contents | | | | | | | |
2424
| exists | | | | | | | |
2525
| get metadata (stat) | | | | | | | |
26-
| identity (same file) | |||| | | |
26+
| identity (same file) | |||| | | |
2727
| open | | | | | | | |
2828
| read file (bytes) | ||||| | |
2929
| read file (lines) | | | | | | | |

pkgs/io_file/lib/src/vm_windows_file_system.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,51 @@ Exception _getError(int errorCode, String message, String path) {
7575

7676
/// A [FileSystem] implementation for Windows systems.
7777
base class WindowsFileSystem extends FileSystem {
78+
@override
79+
bool same(String path1, String path2) => using((arena) {
80+
// Calling `GetLastError` for the first time causes the `GetLastError`
81+
// symbol to be loaded, which resets `GetLastError`. So make a harmless
82+
// call before the value is needed.
83+
win32.GetLastError();
84+
85+
final info1 = _byHandleFileInformation(path1, arena);
86+
final info2 = _byHandleFileInformation(path2, arena);
87+
88+
return info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber &&
89+
info1.nFileIndexHigh == info2.nFileIndexHigh &&
90+
info1.nFileIndexLow == info2.nFileIndexLow;
91+
});
92+
93+
// NOTE: the return value is allocated in the given arena!
94+
static win32.BY_HANDLE_FILE_INFORMATION _byHandleFileInformation(
95+
String path,
96+
ffi.Arena arena,
97+
) {
98+
final h = win32.CreateFile(
99+
path.toNativeUtf16(allocator: arena),
100+
0,
101+
win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE | win32.FILE_SHARE_DELETE,
102+
nullptr,
103+
win32.OPEN_EXISTING,
104+
win32.FILE_FLAG_BACKUP_SEMANTICS,
105+
win32.NULL,
106+
);
107+
if (h == win32.INVALID_HANDLE_VALUE) {
108+
final errorCode = win32.GetLastError();
109+
throw _getError(errorCode, 'CreateFile failed', path);
110+
}
111+
try {
112+
final info = arena<win32.BY_HANDLE_FILE_INFORMATION>();
113+
if (win32.GetFileInformationByHandle(h, info) == win32.FALSE) {
114+
final errorCode = win32.GetLastError();
115+
throw _getError(errorCode, 'GetFileInformationByHandle failed', path);
116+
}
117+
return info.ref;
118+
} finally {
119+
win32.CloseHandle(h);
120+
}
121+
}
122+
78123
@override
79124
void createDirectory(String path) => using((arena) {
80125
_primeGetLastError();
@@ -237,7 +282,7 @@ base class WindowsFileSystem extends FileSystem {
237282
};
238283

239284
final f = win32.CreateFile(
240-
path.toNativeUtf16(),
285+
path.toNativeUtf16(allocator: arena),
241286
mode == WriteMode.appendExisting
242287
? win32.FILE_APPEND_DATA
243288
: win32.FILE_GENERIC_WRITE,

pkgs/io_file/test/same_test.dart

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
@TestOn('posix')
5+
@TestOn('vm')
66
library;
77

88
import 'dart:io';
99

1010
import 'package:io_file/io_file.dart';
1111
import 'package:stdlibc/stdlibc.dart' as stdlibc;
1212
import 'package:test/test.dart';
13+
import 'package:win32/win32.dart' as win32;
1314

1415
import 'test_utils.dart';
1516

@@ -39,9 +40,14 @@ void main() {
3940
() => fileSystem.same(path1, path2),
4041
throwsA(
4142
isA<PathNotFoundException>()
42-
.having((e) => e.message, 'message', 'stat failed')
4343
.having((e) => e.path, 'path', path1)
44-
.having((e) => e.osError?.errorCode, 'errorCode', stdlibc.ENOENT),
44+
.having(
45+
(e) => e.osError?.errorCode,
46+
'errorCode',
47+
Platform.isWindows
48+
? win32.ERROR_FILE_NOT_FOUND
49+
: stdlibc.ENOENT,
50+
),
4551
),
4652
);
4753
});
@@ -55,9 +61,14 @@ void main() {
5561
() => fileSystem.same(path1, path2),
5662
throwsA(
5763
isA<PathNotFoundException>()
58-
.having((e) => e.message, 'message', 'stat failed')
5964
.having((e) => e.path, 'path', path2)
60-
.having((e) => e.osError?.errorCode, 'errorCode', stdlibc.ENOENT),
65+
.having(
66+
(e) => e.osError?.errorCode,
67+
'errorCode',
68+
Platform.isWindows
69+
? win32.ERROR_FILE_NOT_FOUND
70+
: stdlibc.ENOENT,
71+
),
6172
),
6273
);
6374
});
@@ -70,9 +81,14 @@ void main() {
7081
() => fileSystem.same(path1, path2),
7182
throwsA(
7283
isA<PathNotFoundException>()
73-
.having((e) => e.message, 'message', 'stat failed')
7484
.having((e) => e.path, 'path', path1)
75-
.having((e) => e.osError?.errorCode, 'errorCode', stdlibc.ENOENT),
85+
.having(
86+
(e) => e.osError?.errorCode,
87+
'errorCode',
88+
Platform.isWindows
89+
? win32.ERROR_FILE_NOT_FOUND
90+
: stdlibc.ENOENT,
91+
),
7692
),
7793
);
7894
});
@@ -87,9 +103,14 @@ void main() {
87103
() => fileSystem.same(path1, path2),
88104
throwsA(
89105
isA<PathNotFoundException>()
90-
.having((e) => e.message, 'message', 'stat failed')
91106
.having((e) => e.path, 'path', path1)
92-
.having((e) => e.osError?.errorCode, 'errorCode', stdlibc.ENOENT),
107+
.having(
108+
(e) => e.osError?.errorCode,
109+
'errorCode',
110+
Platform.isWindows
111+
? win32.ERROR_FILE_NOT_FOUND
112+
: stdlibc.ENOENT,
113+
),
93114
),
94115
);
95116
});
@@ -104,9 +125,14 @@ void main() {
104125
() => fileSystem.same(path1, path2),
105126
throwsA(
106127
isA<PathNotFoundException>()
107-
.having((e) => e.message, 'message', 'stat failed')
108128
.having((e) => e.path, 'path', path2)
109-
.having((e) => e.osError?.errorCode, 'errorCode', stdlibc.ENOENT),
129+
.having(
130+
(e) => e.osError?.errorCode,
131+
'errorCode',
132+
Platform.isWindows
133+
? win32.ERROR_FILE_NOT_FOUND
134+
: stdlibc.ENOENT,
135+
),
110136
),
111137
);
112138
});
@@ -120,9 +146,14 @@ void main() {
120146
() => fileSystem.same(path1, path2),
121147
throwsA(
122148
isA<PathNotFoundException>()
123-
.having((e) => e.message, 'message', 'stat failed')
124149
.having((e) => e.path, 'path', path1)
125-
.having((e) => e.osError?.errorCode, 'errorCode', stdlibc.ENOENT),
150+
.having(
151+
(e) => e.osError?.errorCode,
152+
'errorCode',
153+
Platform.isWindows
154+
? win32.ERROR_FILE_NOT_FOUND
155+
: stdlibc.ENOENT,
156+
),
126157
),
127158
);
128159
});
@@ -174,13 +205,17 @@ void main() {
174205
expect(fileSystem.same(path1, path2), isTrue);
175206
});
176207

177-
test('hard links to same file', () {
178-
final path1 = '$tmp/file1';
179-
final path2 = '$tmp/file2';
180-
File(path1).writeAsStringSync('Hello World');
181-
stdlibc.link(path1, path2);
182-
expect(fileSystem.same(path1, path2), isTrue);
183-
});
208+
test(
209+
'hard links to same file',
210+
() {
211+
final path1 = '$tmp/file1';
212+
final path2 = '$tmp/file2';
213+
File(path1).writeAsStringSync('Hello World');
214+
stdlibc.link(path1, path2);
215+
expect(fileSystem.same(path1, path2), isTrue);
216+
},
217+
skip: Platform.isWindows ? 'hard links not supported' : false,
218+
);
184219

185220
test('different directories, same content', () {
186221
final path1 = '$tmp/dir1';

0 commit comments

Comments
 (0)