From 5db08c84733a7725ffc7b420d9523ab683ac9768 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 12 Mar 2025 22:15:46 -0700 Subject: [PATCH 01/39] Add `rename` support for Windows --- pkgs/io_file/lib/src/file_system.dart | 13 +++ pkgs/io_file/lib/src/windows_file_system.dart | 84 +++++++++++++++++++ pkgs/io_file/lib/windows_file_system.dart | 5 ++ pkgs/io_file/pubspec.yaml | 1 + pkgs/io_file/test/rename_test.dart | 20 +++-- 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 pkgs/io_file/lib/src/windows_file_system.dart create mode 100644 pkgs/io_file/lib/windows_file_system.dart diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index 95432bb3..1c178619 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -2,6 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:io'; + +import '../posix_file_system.dart'; +import '../windows_file_system.dart'; + /// An abstract representation of a file system. base class FileSystem { /// Renames, and possibly moves a file system object from one path to another. @@ -24,3 +29,11 @@ base class FileSystem { throw UnsupportedError('rename'); } } + +FileSystem get fileSystem { + if (Platform.isWindows) { + return WindowsFileSystem(); + } else { + return PosixFileSystem(); + } +} diff --git a/pkgs/io_file/lib/src/windows_file_system.dart b/pkgs/io_file/lib/src/windows_file_system.dart new file mode 100644 index 00000000..2c2db12c --- /dev/null +++ b/pkgs/io_file/lib/src/windows_file_system.dart @@ -0,0 +1,84 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:ffi'; +import 'dart:io' as io; +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart' as win32; + +import 'file_system.dart'; + +String _formatMessage(int errorCode) { + final buffer = win32.wsalloc(1024); + try { + final result = win32.FormatMessage( + win32.FORMAT_MESSAGE_FROM_SYSTEM | win32.FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + errorCode, + 0, // default language + buffer, + 1024, + nullptr, + ); + if (result == 0) { + return ''; + } else { + return buffer.toDartString(); + } + } finally { + win32.free(buffer); + } +} + +Exception _getError(int errorCode, String message, String path) { + final osError = io.OSError(_formatMessage(errorCode), errorCode); + + switch (errorCode) { + case win32.ERROR_ACCESS_DENIED: + case win32.ERROR_CURRENT_DIRECTORY: + case win32.ERROR_WRITE_PROTECT: + case win32.ERROR_BAD_LENGTH: + case win32.ERROR_SHARING_VIOLATION: + case win32.ERROR_LOCK_VIOLATION: + case win32.ERROR_NETWORK_ACCESS_DENIED: + case win32.ERROR_DRIVE_LOCKED: + return io.PathAccessException(path, osError, message); + case win32.ERROR_FILE_EXISTS: + case win32.ERROR_ALREADY_EXISTS: + return io.PathExistsException(path, osError, message); + case win32.ERROR_FILE_NOT_FOUND: + case win32.ERROR_PATH_NOT_FOUND: + case win32.ERROR_INVALID_DRIVE: + case win32.ERROR_INVALID_NAME: + case win32.ERROR_NO_MORE_FILES: + case win32.ERROR_BAD_NETPATH: + case win32.ERROR_BAD_NET_NAME: + case win32.ERROR_BAD_PATHNAME: + return io.PathNotFoundException(path, osError, message); + default: + return io.FileSystemException(message, path, osError); + } +} + +/// A [FileSystem] implementation for Windows systems. +base class WindowsFileSystem extends FileSystem { + @override + void rename(String oldPath, String newPath) { + // Calling `GetLastError` for the first time causes the `GetLastError` + // symbol to be loaded, which resets `GetLastError`. So make a harmless + // call before the value is needed. + win32.GetLastError(); + + if (win32.MoveFileEx( + oldPath.toNativeUtf16(), + newPath.toNativeUtf16(), + win32.MOVEFILE_WRITE_THROUGH | win32.MOVEFILE_REPLACE_EXISTING, + ) == + win32.FALSE) { + final errorCode = win32.GetLastError(); + + throw _getError(errorCode, 'rename failed', oldPath); + } + } +} diff --git a/pkgs/io_file/lib/windows_file_system.dart b/pkgs/io_file/lib/windows_file_system.dart new file mode 100644 index 00000000..0bca6406 --- /dev/null +++ b/pkgs/io_file/lib/windows_file_system.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +export 'src/windows_file_system.dart'; diff --git a/pkgs/io_file/pubspec.yaml b/pkgs/io_file/pubspec.yaml index 677b1c13..62c8589b 100644 --- a/pkgs/io_file/pubspec.yaml +++ b/pkgs/io_file/pubspec.yaml @@ -8,6 +8,7 @@ environment: sdk: ^3.7.0 dependencies: + ffi: ^2.1.4 stdlibc: git: # Change this to a released version. diff --git a/pkgs/io_file/test/rename_test.dart b/pkgs/io_file/test/rename_test.dart index 0124bbbd..c71837ef 100644 --- a/pkgs/io_file/test/rename_test.dart +++ b/pkgs/io_file/test/rename_test.dart @@ -2,12 +2,12 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@TestOn('posix') +@TestOn('vm') library; import 'dart:io'; -import 'package:io_file/posix_file_system.dart'; +import 'package:io_file/io_file.dart'; import 'package:test/test.dart'; void main() { @@ -27,7 +27,7 @@ void main() { final path2 = '$tmp/file2'; File(path1).writeAsStringSync('Hello World'); - PosixFileSystem().rename(path1, path2); + fileSystem.rename(path1, path2); expect(File(path1).existsSync(), isFalse); expect(File(path2).existsSync(), isTrue); }); @@ -38,7 +38,7 @@ void main() { File(path1).writeAsStringSync('Hello World #1'); File(path2).writeAsStringSync('Hello World #2'); - PosixFileSystem().rename(path1, path2); + fileSystem.rename(path1, path2); expect(File(path1).existsSync(), isFalse); expect(File(path2).readAsStringSync(), 'Hello World #1'); }); @@ -48,7 +48,7 @@ void main() { final path2 = '$tmp/dir2'; Directory(path1).createSync(recursive: true); - PosixFileSystem().rename(path1, path2); + fileSystem.rename(path1, path2); expect(Directory(path1).existsSync(), isFalse); expect(Directory(path2).existsSync(), isTrue); }); @@ -58,14 +58,14 @@ void main() { final path2 = '$tmp/file2'; expect( - () => PosixFileSystem().rename(path1, path2), + () => fileSystem.rename(path1, path2), throwsA( isA() .having((e) => e.message, 'message', 'rename failed') .having( (e) => e.osError?.errorCode, 'errorCode', - 2, // ENOENT + 2, // ENOENT, ERROR_FILE_NOT_FOUND ), ), ); @@ -79,14 +79,16 @@ void main() { Directory(path2).createSync(recursive: true); expect( - () => PosixFileSystem().rename(path1, path2), + () => fileSystem.rename(path1, path2), throwsA( isA() .having((e) => e.message, 'message', 'rename failed') .having( (e) => e.osError?.errorCode, 'errorCode', - 21, // EISDIR + Platform.isWindows + ? 5 // ERROR_ACCESS_DENIED + : 31, // EISDIR ), ), ); From a6f5c1ca27fc764876eec9adc55b58e2076801f1 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 12 Mar 2025 22:19:03 -0700 Subject: [PATCH 02/39] Update rename_test.dart --- pkgs/io_file/test/rename_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/io_file/test/rename_test.dart b/pkgs/io_file/test/rename_test.dart index c71837ef..3366b548 100644 --- a/pkgs/io_file/test/rename_test.dart +++ b/pkgs/io_file/test/rename_test.dart @@ -88,7 +88,7 @@ void main() { 'errorCode', Platform.isWindows ? 5 // ERROR_ACCESS_DENIED - : 31, // EISDIR + : 21, // EISDIR ), ), ); From f89d06315e8ae10dd62950cb322d958344c69a60 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Fri, 14 Mar 2025 11:10:27 -0700 Subject: [PATCH 03/39] Some fixes --- pkgs/io_file/lib/io_file.dart | 2 ++ pkgs/io_file/lib/posix_file_system.dart | 3 ++- pkgs/io_file/lib/src/file_system.dart | 13 ------------- pkgs/io_file/lib/src/vm_file_system_property.dart | 13 +++++++++++++ ...x_file_system.dart => vm_posix_file_system.dart} | 0 ...file_system.dart => vm_windows_file_system.dart} | 8 +++++--- pkgs/io_file/lib/src/web_file_system_property.dart | 10 ++++++++++ pkgs/io_file/lib/src/web_posix_file_system.dart | 9 +++++++++ pkgs/io_file/lib/src/web_windows_file_system.dart | 8 ++++++++ pkgs/io_file/lib/windows_file_system.dart | 3 ++- 10 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 pkgs/io_file/lib/src/vm_file_system_property.dart rename pkgs/io_file/lib/src/{posix_file_system.dart => vm_posix_file_system.dart} (100%) rename pkgs/io_file/lib/src/{windows_file_system.dart => vm_windows_file_system.dart} (94%) create mode 100644 pkgs/io_file/lib/src/web_file_system_property.dart create mode 100644 pkgs/io_file/lib/src/web_posix_file_system.dart create mode 100644 pkgs/io_file/lib/src/web_windows_file_system.dart diff --git a/pkgs/io_file/lib/io_file.dart b/pkgs/io_file/lib/io_file.dart index 340d5e34..39028cc0 100644 --- a/pkgs/io_file/lib/io_file.dart +++ b/pkgs/io_file/lib/io_file.dart @@ -3,3 +3,5 @@ // BSD-style license that can be found in the LICENSE file. export 'src/file_system.dart'; +export 'src/vm_file_system_property.dart' + if (dart.library.html) 'src/web_file_system_property.dart'; diff --git a/pkgs/io_file/lib/posix_file_system.dart b/pkgs/io_file/lib/posix_file_system.dart index ac5ed286..f46b8ca8 100644 --- a/pkgs/io_file/lib/posix_file_system.dart +++ b/pkgs/io_file/lib/posix_file_system.dart @@ -2,4 +2,5 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -export 'src/posix_file_system.dart'; +export 'src/vm_posix_file_system.dart' + if (dart.library.html) 'src/web_posix_file_system.dart'; diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index 1c178619..95432bb3 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -2,11 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:io'; - -import '../posix_file_system.dart'; -import '../windows_file_system.dart'; - /// An abstract representation of a file system. base class FileSystem { /// Renames, and possibly moves a file system object from one path to another. @@ -29,11 +24,3 @@ base class FileSystem { throw UnsupportedError('rename'); } } - -FileSystem get fileSystem { - if (Platform.isWindows) { - return WindowsFileSystem(); - } else { - return PosixFileSystem(); - } -} diff --git a/pkgs/io_file/lib/src/vm_file_system_property.dart b/pkgs/io_file/lib/src/vm_file_system_property.dart new file mode 100644 index 00000000..d2193a89 --- /dev/null +++ b/pkgs/io_file/lib/src/vm_file_system_property.dart @@ -0,0 +1,13 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'file_system.dart'; +import 'vm_posix_file_system.dart'; +import 'vm_windows_file_system.dart'; + +/// Return the default [FileSystem] for the current platform. +FileSystem get fileSystem => + Platform.isWindows ? WindowsFileSystem() : PosixFileSystem(); diff --git a/pkgs/io_file/lib/src/posix_file_system.dart b/pkgs/io_file/lib/src/vm_posix_file_system.dart similarity index 100% rename from pkgs/io_file/lib/src/posix_file_system.dart rename to pkgs/io_file/lib/src/vm_posix_file_system.dart diff --git a/pkgs/io_file/lib/src/windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart similarity index 94% rename from pkgs/io_file/lib/src/windows_file_system.dart rename to pkgs/io_file/lib/src/vm_windows_file_system.dart index 2c2db12c..3528c2b6 100644 --- a/pkgs/io_file/lib/src/windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -68,16 +68,18 @@ base class WindowsFileSystem extends FileSystem { // Calling `GetLastError` for the first time causes the `GetLastError` // symbol to be loaded, which resets `GetLastError`. So make a harmless // call before the value is needed. - win32.GetLastError(); - + // win32.GetLastError(); + print('Premove'); if (win32.MoveFileEx( oldPath.toNativeUtf16(), newPath.toNativeUtf16(), win32.MOVEFILE_WRITE_THROUGH | win32.MOVEFILE_REPLACE_EXISTING, ) == win32.FALSE) { + print('GetLastError - in'); final errorCode = win32.GetLastError(); - + print('Errorcode: $errorCode'); + print('GetLastError - out'); throw _getError(errorCode, 'rename failed', oldPath); } } diff --git a/pkgs/io_file/lib/src/web_file_system_property.dart b/pkgs/io_file/lib/src/web_file_system_property.dart new file mode 100644 index 00000000..fd6ede7d --- /dev/null +++ b/pkgs/io_file/lib/src/web_file_system_property.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'file_system.dart'; + +/// Return the default [FileSystem] for the current platform. +FileSystem get fileSystem { + throw UnsupportedError('fileSystem'); +} diff --git a/pkgs/io_file/lib/src/web_posix_file_system.dart b/pkgs/io_file/lib/src/web_posix_file_system.dart new file mode 100644 index 00000000..b81ff001 --- /dev/null +++ b/pkgs/io_file/lib/src/web_posix_file_system.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'file_system.dart'; + +/// A [FileSystem] implementation for POSIX systems (e.g. Android, iOS, Linux, +/// macOS). +base class PosixFileSystem extends FileSystem {} diff --git a/pkgs/io_file/lib/src/web_windows_file_system.dart b/pkgs/io_file/lib/src/web_windows_file_system.dart new file mode 100644 index 00000000..fb154f48 --- /dev/null +++ b/pkgs/io_file/lib/src/web_windows_file_system.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'file_system.dart'; + +/// A [FileSystem] implementation for Windows systems. +base class WindowsFileSystem extends FileSystem {} diff --git a/pkgs/io_file/lib/windows_file_system.dart b/pkgs/io_file/lib/windows_file_system.dart index 0bca6406..a9236785 100644 --- a/pkgs/io_file/lib/windows_file_system.dart +++ b/pkgs/io_file/lib/windows_file_system.dart @@ -2,4 +2,5 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -export 'src/windows_file_system.dart'; +export 'src/vm_windows_file_system.dart' + if (dart.library.html) 'src/web_windows_file_system.dart'; From 0f81c95ff24b6ee59c47ed56bb803c78631bf36d Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Fri, 14 Mar 2025 11:45:23 -0700 Subject: [PATCH 04/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 3528c2b6..dbb71605 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -4,6 +4,7 @@ import 'dart:ffi'; import 'dart:io' as io; + import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart' as win32; From ed98ec4f9889a7067827e5d174f73e65ad142eeb Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Fri, 14 Mar 2025 11:57:52 -0700 Subject: [PATCH 05/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index dbb71605..5353879c 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -69,18 +69,14 @@ base class WindowsFileSystem extends FileSystem { // Calling `GetLastError` for the first time causes the `GetLastError` // symbol to be loaded, which resets `GetLastError`. So make a harmless // call before the value is needed. - // win32.GetLastError(); - print('Premove'); + win32.GetLastError(); if (win32.MoveFileEx( oldPath.toNativeUtf16(), newPath.toNativeUtf16(), win32.MOVEFILE_WRITE_THROUGH | win32.MOVEFILE_REPLACE_EXISTING, ) == win32.FALSE) { - print('GetLastError - in'); final errorCode = win32.GetLastError(); - print('Errorcode: $errorCode'); - print('GetLastError - out'); throw _getError(errorCode, 'rename failed', oldPath); } } From 2dfc83bf4ecc6cb55313482f3302bc1cab12de75 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Fri, 14 Mar 2025 12:05:37 -0700 Subject: [PATCH 06/39] Test utils --- pkgs/io_file/test/rename_test.dart | 8 ++++---- pkgs/io_file/test/test_utils.dart | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 pkgs/io_file/test/test_utils.dart diff --git a/pkgs/io_file/test/rename_test.dart b/pkgs/io_file/test/rename_test.dart index 3366b548..4deef9b6 100644 --- a/pkgs/io_file/test/rename_test.dart +++ b/pkgs/io_file/test/rename_test.dart @@ -10,15 +10,15 @@ import 'dart:io'; import 'package:io_file/io_file.dart'; import 'package:test/test.dart'; +import 'test_utils.dart'; + void main() { group('move', () { late String tmp; - setUp( - () => tmp = Directory.systemTemp.createTempSync('move').absolute.path, - ); + setUp(() => tmp = createTemp('move')); - tearDown(() => Directory(tmp).deleteSync(recursive: true)); + tearDown(() => deleteTemp(tmp)); //TODO(brianquinlan): test with a very long path. diff --git a/pkgs/io_file/test/test_utils.dart b/pkgs/io_file/test/test_utils.dart new file mode 100644 index 00000000..05b4e25e --- /dev/null +++ b/pkgs/io_file/test/test_utils.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +String createTemp(String testName) => + Directory.systemTemp.createTempSync('move').absolute.path; +void deleteTemp(String path) => Directory(path).deleteSync(recursive: true); From 8bf09fbf8be7672cc216dab6cb0d7714e1ebf78b Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Sat, 15 Mar 2025 18:52:36 -0700 Subject: [PATCH 07/39] Metadata --- pkgs/io_file/lib/src/file_system.dart | 27 ++++++ .../lib/src/vm_windows_file_system.dart | 91 +++++++++++++++++++ pkgs/io_file/pubspec.yaml | 8 +- pkgs/io_file/test/metadata_test.dart | 36 ++++++++ 4 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 pkgs/io_file/test/metadata_test.dart diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index 95432bb3..eab4f357 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -2,6 +2,29 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +base class Metadata { + final bool isFile; + final bool isDirectory; + final bool isLink; + + Metadata({ + this.isDirectory = false, + this.isFile = false, + this.isLink = false, + }) { + final count = (isDirectory ? 1 : 0) + (isFile ? 1 : 0) + (isLink ? 1 : 0); + if (count > 1) { + // TODO(brianquinlan): Decide whether a path must be a a file, directory + // or link and whether it can be more than one of these at once. + // Rust requires that at most one of these is true. Python has no such + // restriction. + throw ArgumentError( + 'only one of isDirectory, isFile, or isLink must be true', + ); + } + } +} + /// An abstract representation of a file system. base class FileSystem { /// Renames, and possibly moves a file system object from one path to another. @@ -23,4 +46,8 @@ base class FileSystem { void rename(String oldPath, String newPath) { throw UnsupportedError('rename'); } + + Metadata metadata(String path) { + throw UnsupportedError('metadata'); + } } diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 5353879c..89b82d31 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -10,6 +10,11 @@ import 'package:win32/win32.dart' as win32; import 'file_system.dart'; +DateTime fileTimeToDateTime(int t) { + final microseconds = t ~/ 10; + return DateTime.utc(1601, 1, 1).add(Duration(microseconds: microseconds)); +} + String _formatMessage(int errorCode) { final buffer = win32.wsalloc(1024); try { @@ -62,6 +67,47 @@ Exception _getError(int errorCode, String message, String path) { } } +final class WindowsMetadata extends Metadata { + final bool isReadOnly; + final bool isHidden; + final bool isSystem; + final bool isArchive; + final bool isTemporary; + final bool isSparse; + final bool isCompressed; + final bool isOffline; + + final int size; + final int creationTime100Nanos; + final int lastAccessTime100Nanos; + final int lastWriteTime100Nanos; + + DateTime get creationTime => fileTimeToDateTime(creationTime100Nanos); + DateTime get lastAccessTime => fileTimeToDateTime(lastAccessTime100Nanos); + DateTime get lastModificationTime => + fileTimeToDateTime(lastWriteTime100Nanos); + + WindowsMetadata({ + super.isDirectory = false, + super.isFile = false, + super.isLink = false, + + this.isReadOnly = false, + this.isHidden = false, + this.isSystem = false, + this.isArchive = false, + this.isTemporary = false, + this.isSparse = false, + this.isCompressed = false, + this.isOffline = false, + + this.size = 0, + this.creationTime100Nanos = 0, + this.lastAccessTime100Nanos = 0, + this.lastWriteTime100Nanos = 0, + }); +} + /// A [FileSystem] implementation for Windows systems. base class WindowsFileSystem extends FileSystem { @override @@ -80,4 +126,49 @@ base class WindowsFileSystem extends FileSystem { throw _getError(errorCode, 'rename failed', oldPath); } } + + @override + Metadata metadata(String path) => using((arena) { + final fileInfo = arena(); + if (win32.GetFileAttributesEx( + path.toNativeUtf16(), + win32.GetFileExInfoStandard, + fileInfo, + ) == + win32.FALSE) { + final errorCode = win32.GetLastError(); + throw _getError(errorCode, 'metadata failed', path); + } + final info = fileInfo.ref; + final attributes = info.dwFileAttributes; + + final isDirectory = attributes & win32.FILE_ATTRIBUTE_DIRECTORY > 0; + final isLink = attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT > 0; + final isFile = !(isDirectory || isLink); + + return WindowsMetadata( + isReadOnly: attributes & win32.FILE_ATTRIBUTE_READONLY > 0, + isHidden: attributes & win32.FILE_ATTRIBUTE_HIDDEN > 0, + isSystem: attributes & win32.FILE_ATTRIBUTE_SYSTEM > 0, + isDirectory: isDirectory, + isArchive: attributes & win32.FILE_ATTRIBUTE_ARCHIVE > 0, + isTemporary: attributes & win32.FILE_ATTRIBUTE_TEMPORARY > 0, + isSparse: attributes & win32.FILE_ATTRIBUTE_SPARSE_FILE > 0, + isLink: isLink, + isFile: isFile, + isCompressed: attributes & win32.FILE_ATTRIBUTE_COMPRESSED > 0, + isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE > 0, + + size: info.nFileSizeHigh << 32 | info.nFileSizeLow, + creationTime100Nanos: + info.ftCreationTime.dwHighDateTime << 32 | + info.ftCreationTime.dwLowDateTime, + lastAccessTime100Nanos: + info.ftLastAccessTime.dwHighDateTime << 32 | + info.ftLastAccessTime.dwLowDateTime, + lastWriteTime100Nanos: + info.ftLastWriteTime.dwHighDateTime << 32 | + info.ftLastWriteTime.dwLowDateTime, + ); + }); } diff --git a/pkgs/io_file/pubspec.yaml b/pkgs/io_file/pubspec.yaml index 62c8589b..d29c7d6c 100644 --- a/pkgs/io_file/pubspec.yaml +++ b/pkgs/io_file/pubspec.yaml @@ -11,9 +11,13 @@ dependencies: ffi: ^2.1.4 stdlibc: git: - # Change this to a released version. + # TODO(brianquinlan): Change this to a released version. url: https://github.com/canonical/stdlibc.dart.git - win32: ^5.11.0 + win32: + git: + # TODO(brianquinlan): Change this to a released version. + url: https://github.com/halildurmus/win32.git + path: packages/win32 dev_dependencies: dart_flutter_team_lints: ^3.4.0 diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart new file mode 100644 index 00000000..0535b5d9 --- /dev/null +++ b/pkgs/io_file/test/metadata_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:io_file/io_file.dart'; +import 'package:io_file/src/vm_windows_file_system.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + group('metadata', () { + late String tmp; + + setUp(() => tmp = createTemp('move')); + + tearDown(() => deleteTemp(tmp)); + + //TODO(brianquinlan): test with a very long path. + + test('move file absolute path', () { + final path = '$tmp/file1'; + + File(path).writeAsStringSync('Hello World'); + + final data = fileSystem.metadata(path) as WindowsMetadata; + expect(data.isFile, isTrue); + expect(data.lastAccessTime, DateTime.now().toUtc()); + }); + }); +} From 1dcd074eab5edc8fcd5efa8409f02df1ca7c3024 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Sun, 16 Mar 2025 20:56:22 -0700 Subject: [PATCH 08/39] Set metadata --- .../lib/src/vm_windows_file_system.dart | 62 +++++++++++++++++++ pkgs/io_file/test/metadata_test.dart | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 89b82d31..72e4e688 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -127,6 +127,68 @@ base class WindowsFileSystem extends FileSystem { } } + void setMetadata( + String path, { + bool? isReadOnly, + bool? isHidden, + bool? isSystem, + bool? isArchive, + bool? isTemporary, + bool? isContentNotIndexed, + bool? isOffline, + }) => using((arena) { + if ((isReadOnly ?? + isHidden ?? + isSystem ?? + isArchive ?? + isTemporary ?? + isContentNotIndexed ?? + isOffline) == + null) { + return; + } + final fileInfo = arena(); + final nativePath = path.toNativeUtf16(); + if (win32.GetFileAttributesEx( + nativePath, + win32.GetFileExInfoStandard, + fileInfo, + ) == + win32.FALSE) { + final errorCode = win32.GetLastError(); + throw _getError(errorCode, 'metadata failed', path); + } + + var attributes = fileInfo.ref.dwFileAttributes; + + int setBit(int base, int value, bool? x) { + if (x == null) { + return base; + } + if (x) { + return base | value; + } else { + return base; + } + } + + attributes = setBit(attributes, win32.FILE_ATTRIBUTE_READONLY, isReadOnly); + attributes = setBit(attributes, win32.FILE_ATTRIBUTE_HIDDEN, isHidden); + attributes = setBit(attributes, win32.FILE_ATTRIBUTE_SYSTEM, isSystem); + attributes = setBit(attributes, win32.FILE_ATTRIBUTE_ARCHIVE, isArchive); + attributes = setBit( + attributes, + win32.FILE_ATTRIBUTE_TEMPORARY, + isTemporary, + ); + attributes = setBit( + attributes, + win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + isContentNotIndexed, + ); + attributes = setBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); + }); + @override Metadata metadata(String path) => using((arena) { final fileInfo = arena(); diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart index 0535b5d9..d491d48f 100644 --- a/pkgs/io_file/test/metadata_test.dart +++ b/pkgs/io_file/test/metadata_test.dart @@ -17,7 +17,7 @@ void main() { group('metadata', () { late String tmp; - setUp(() => tmp = createTemp('move')); + setUp(() => tmp = createTemp('metadata')); tearDown(() => deleteTemp(tmp)); From 2877b5e321a9bc17a6b5dfdcf0f05500110bc14d Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Sun, 16 Mar 2025 21:25:00 -0700 Subject: [PATCH 09/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 72e4e688..00c9f515 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -156,7 +156,7 @@ base class WindowsFileSystem extends FileSystem { ) == win32.FALSE) { final errorCode = win32.GetLastError(); - throw _getError(errorCode, 'metadata failed', path); + throw _getError(errorCode, 'set metadata failed', path); } var attributes = fileInfo.ref.dwFileAttributes; @@ -167,9 +167,10 @@ base class WindowsFileSystem extends FileSystem { } if (x) { return base | value; - } else { - return base; + } else if (base | value != 0) { + return base - value; } + return base; } attributes = setBit(attributes, win32.FILE_ATTRIBUTE_READONLY, isReadOnly); @@ -187,6 +188,10 @@ base class WindowsFileSystem extends FileSystem { isContentNotIndexed, ); attributes = setBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); + if (win32.SetFileAttributes(nativePath, attributes) == win32.FALSE) { + final errorCode = win32.GetLastError(); + throw _getError(errorCode, 'set metadata failed', path); + } }); @override From 7e4717dc0b44496f77b4f4e2d7ea9284f1c920ea Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 10:42:05 -0700 Subject: [PATCH 10/39] Tests --- .../lib/src/vm_windows_file_system.dart | 18 ++++------ pkgs/io_file/test/metadata_test.dart | 35 ++++++++++++++----- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 00c9f515..a391ffc3 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -148,7 +148,7 @@ base class WindowsFileSystem extends FileSystem { return; } final fileInfo = arena(); - final nativePath = path.toNativeUtf16(); + final nativePath = path.toNativeUtf16(allocator: arena); if (win32.GetFileAttributesEx( nativePath, win32.GetFileExInfoStandard, @@ -161,17 +161,11 @@ base class WindowsFileSystem extends FileSystem { var attributes = fileInfo.ref.dwFileAttributes; - int setBit(int base, int value, bool? x) { - if (x == null) { - return base; - } - if (x) { - return base | value; - } else if (base | value != 0) { - return base - value; - } - return base; - } + int setBit(int base, int value, bool? bit) => switch (bit) { + null => base, + true => base | value, + false => base & ~value, + }; attributes = setBit(attributes, win32.FILE_ATTRIBUTE_READONLY, isReadOnly); attributes = setBit(attributes, win32.FILE_ATTRIBUTE_HIDDEN, isHidden); diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart index d491d48f..467f97b8 100644 --- a/pkgs/io_file/test/metadata_test.dart +++ b/pkgs/io_file/test/metadata_test.dart @@ -8,7 +8,6 @@ library; import 'dart:io'; import 'package:io_file/io_file.dart'; -import 'package:io_file/src/vm_windows_file_system.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; @@ -23,14 +22,32 @@ void main() { //TODO(brianquinlan): test with a very long path. - test('move file absolute path', () { - final path = '$tmp/file1'; - - File(path).writeAsStringSync('Hello World'); - - final data = fileSystem.metadata(path) as WindowsMetadata; - expect(data.isFile, isTrue); - expect(data.lastAccessTime, DateTime.now().toUtc()); + group('isDirectory/isFile/isLink', () { + test('directory', () { + final data = fileSystem.metadata(tmp); + expect(data.isDirectory, isTrue); + expect(data.isFile, isFalse); + expect(data.isLink, isFalse); + }); + test('file', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = fileSystem.metadata(path); + expect(data.isDirectory, isFalse); + expect(data.isFile, isTrue); + expect(data.isLink, isFalse); + }); + test('link', () { + File('$tmp/file1').writeAsStringSync('Hello World'); + final path = '$tmp/link'; + Link(path).createSync('$tmp/file1'); + + final data = fileSystem.metadata(path); + expect(data.isDirectory, isFalse); + expect(data.isFile, isFalse); + expect(data.isLink, isTrue); + }); }); }); } From 69490d511d39b054fd6e9672f5695d117bd72ac1 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 10:46:10 -0700 Subject: [PATCH 11/39] Fixes --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 2 +- pkgs/io_file/test/metadata_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index c0b8e4e9..f3a44a66 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -125,7 +125,7 @@ base class WindowsFileSystem extends FileSystem { final errorCode = win32.GetLastError(); throw _getError(errorCode, 'rename failed', oldPath); } - } + }); void setMetadata( String path, { diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart index 467f97b8..6c2e2ed7 100644 --- a/pkgs/io_file/test/metadata_test.dart +++ b/pkgs/io_file/test/metadata_test.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@TestOn('vm') +@TestOn('windows') library; import 'dart:io'; From 97047662c450854d56a90a027a8ea2764825b59b Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 11:06:03 -0700 Subject: [PATCH 12/39] Windows metadata tests --- .../lib/src/vm_windows_file_system.dart | 2 +- pkgs/io_file/test/metadata_windows_test.dart | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 pkgs/io_file/test/metadata_windows_test.dart diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index f3a44a66..bf2cf201 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -189,7 +189,7 @@ base class WindowsFileSystem extends FileSystem { }); @override - Metadata metadata(String path) => using((arena) { + WindowsMetadata metadata(String path) => using((arena) { final fileInfo = arena(); if (win32.GetFileAttributesEx( path.toNativeUtf16(), diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart new file mode 100644 index 00000000..6bed9459 --- /dev/null +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -0,0 +1,46 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('windows') +library; + +import 'dart:io'; + +import 'package:io_file/io_file.dart'; +import 'package:io_file/src/vm_windows_file_system.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; + +void main() { + final windowsFileSystem = WindowsFileSystem(); + + group('metadata', () { + late String tmp; + + setUp(() => tmp = createTemp('metadata')); + + tearDown(() => deleteTemp(tmp)); + + //TODO(brianquinlan): test with a very long path. + + group('isReadOnly', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isReadOnly: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + }); + }); + }); +} From 36647c2dfe99ce91fcaf60e41ac20243c714261d Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 11:19:08 -0700 Subject: [PATCH 13/39] More windows properties --- .../lib/src/vm_windows_file_system.dart | 4 + pkgs/io_file/test/metadata_windows_test.dart | 109 +++++++++++++++++- 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index bf2cf201..08eaaeb0 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -76,6 +76,7 @@ final class WindowsMetadata extends Metadata { final bool isSparse; final bool isCompressed; final bool isOffline; + final bool isContentNotIndexed; final int size; final int creationTime100Nanos; @@ -100,6 +101,7 @@ final class WindowsMetadata extends Metadata { this.isSparse = false, this.isCompressed = false, this.isOffline = false, + this.isContentNotIndexed = false, this.size = 0, this.creationTime100Nanos = 0, @@ -219,6 +221,8 @@ base class WindowsFileSystem extends FileSystem { isFile: isFile, isCompressed: attributes & win32.FILE_ATTRIBUTE_COMPRESSED > 0, isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE > 0, + isContentNotIndexed: + attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED > 0, size: info.nFileSizeHigh << 32 | info.nFileSizeLow, creationTime100Nanos: diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 6bed9459..14ab12d3 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -7,7 +7,6 @@ library; import 'dart:io'; -import 'package:io_file/io_file.dart'; import 'package:io_file/src/vm_windows_file_system.dart'; import 'package:test/test.dart'; @@ -42,5 +41,113 @@ void main() { expect(data.isReadOnly, isTrue); }); }); + + group('isHidden', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isHidden, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isHidden: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isHidden, isFalse); + }); + }); + + group('isSystem', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isSystem, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isSystem: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isSystem, isFalse); + }); + }); + + group('isArchive', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isArchive, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isArchive: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isArchive, isFalse); + }); + }); + + group('isTemporary', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isTemporary, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isTemporary: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isTemporary, isFalse); + }); + }); + + group('isContentNotIndexed', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isContentNotIndexed, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isContentNotIndexed: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isContentNotIndexed, isFalse); + }); + }); + + group('isOffline', () { + test('false', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + + final data = windowsFileSystem.metadata(path); + expect(data.isOffline, isFalse); + }); + test('true', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isOffline: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isOffline, isFalse); + }); + }); }); } From a363b49c1fb7a305bca56a9cf94fdfdb9a878943 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 11:22:59 -0700 Subject: [PATCH 14/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 08eaaeb0..076c29cd 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -162,7 +162,7 @@ base class WindowsFileSystem extends FileSystem { } var attributes = fileInfo.ref.dwFileAttributes; - + print('Initial bits: $attributes'); int setBit(int base, int value, bool? bit) => switch (bit) { null => base, true => base | value, @@ -184,6 +184,7 @@ base class WindowsFileSystem extends FileSystem { isContentNotIndexed, ); attributes = setBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); + print('Final bits: $attributes'); if (win32.SetFileAttributes(nativePath, attributes) == win32.FALSE) { final errorCode = win32.GetLastError(); throw _getError(errorCode, 'set metadata failed', path); From 305655cec031f7b4343e89460d57db36dd2cd98e Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 11:28:52 -0700 Subject: [PATCH 15/39] Test fixes --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 4 ++++ pkgs/io_file/test/metadata_windows_test.dart | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 076c29cd..93cbb98c 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -163,6 +163,10 @@ base class WindowsFileSystem extends FileSystem { var attributes = fileInfo.ref.dwFileAttributes; print('Initial bits: $attributes'); + if (attributes == win32.FILE_ATTRIBUTE_NORMAL) { + attributes = 0; + } + int setBit(int base, int value, bool? bit) => switch (bit) { null => base, true => base | value, diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 14ab12d3..c262ffb6 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -56,7 +56,7 @@ void main() { windowsFileSystem.setMetadata(path, isHidden: true); final data = windowsFileSystem.metadata(path); - expect(data.isHidden, isFalse); + expect(data.isHidden, isTrue); }); }); @@ -74,7 +74,7 @@ void main() { windowsFileSystem.setMetadata(path, isSystem: true); final data = windowsFileSystem.metadata(path); - expect(data.isSystem, isFalse); + expect(data.isSystem, isTrue); }); }); @@ -92,7 +92,7 @@ void main() { windowsFileSystem.setMetadata(path, isArchive: true); final data = windowsFileSystem.metadata(path); - expect(data.isArchive, isFalse); + expect(data.isArchive, isTrue); }); }); @@ -110,7 +110,7 @@ void main() { windowsFileSystem.setMetadata(path, isTemporary: true); final data = windowsFileSystem.metadata(path); - expect(data.isTemporary, isFalse); + expect(data.isTemporary, isTrue); }); }); @@ -128,7 +128,7 @@ void main() { windowsFileSystem.setMetadata(path, isContentNotIndexed: true); final data = windowsFileSystem.metadata(path); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentNotIndexed, isTrue); }); }); @@ -146,7 +146,7 @@ void main() { windowsFileSystem.setMetadata(path, isOffline: true); final data = windowsFileSystem.metadata(path); - expect(data.isOffline, isFalse); + expect(data.isOffline, isTrue); }); }); }); From 88dc69ea6fe3151a1be581ebfbeaa33a51004314 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 11:32:48 -0700 Subject: [PATCH 16/39] Update metadata_windows_test.dart --- pkgs/io_file/test/metadata_windows_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index c262ffb6..430a630b 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -82,6 +82,7 @@ void main() { test('false', () { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata(path, isArchive: false); final data = windowsFileSystem.metadata(path); expect(data.isArchive, isFalse); From 647a6a5a29b30d09bcc6b1ee12f898e3df6792c7 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 12:19:30 -0700 Subject: [PATCH 17/39] size --- pkgs/io_file/lib/src/file_system.dart | 2 ++ .../lib/src/vm_windows_file_system.dart | 10 ++----- pkgs/io_file/test/metadata_test.dart | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index eab4f357..7c603199 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -6,11 +6,13 @@ base class Metadata { final bool isFile; final bool isDirectory; final bool isLink; + final int size; Metadata({ this.isDirectory = false, this.isFile = false, this.isLink = false, + this.size = 0, }) { final count = (isDirectory ? 1 : 0) + (isFile ? 1 : 0) + (isLink ? 1 : 0); if (count > 1) { diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 93cbb98c..0c31232c 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -73,12 +73,9 @@ final class WindowsMetadata extends Metadata { final bool isSystem; final bool isArchive; final bool isTemporary; - final bool isSparse; - final bool isCompressed; final bool isOffline; final bool isContentNotIndexed; - final int size; final int creationTime100Nanos; final int lastAccessTime100Nanos; final int lastWriteTime100Nanos; @@ -93,17 +90,16 @@ final class WindowsMetadata extends Metadata { super.isFile = false, super.isLink = false, + super.size = 0, + this.isReadOnly = false, this.isHidden = false, this.isSystem = false, this.isArchive = false, this.isTemporary = false, - this.isSparse = false, - this.isCompressed = false, this.isOffline = false, this.isContentNotIndexed = false, - this.size = 0, this.creationTime100Nanos = 0, this.lastAccessTime100Nanos = 0, this.lastWriteTime100Nanos = 0, @@ -221,10 +217,8 @@ base class WindowsFileSystem extends FileSystem { isDirectory: isDirectory, isArchive: attributes & win32.FILE_ATTRIBUTE_ARCHIVE > 0, isTemporary: attributes & win32.FILE_ATTRIBUTE_TEMPORARY > 0, - isSparse: attributes & win32.FILE_ATTRIBUTE_SPARSE_FILE > 0, isLink: isLink, isFile: isFile, - isCompressed: attributes & win32.FILE_ATTRIBUTE_COMPRESSED > 0, isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE > 0, isContentNotIndexed: attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED > 0, diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart index 6c2e2ed7..4682c862 100644 --- a/pkgs/io_file/test/metadata_test.dart +++ b/pkgs/io_file/test/metadata_test.dart @@ -49,5 +49,34 @@ void main() { expect(data.isLink, isTrue); }); }); + + group('size', () { + test('directory', () { + final data = fileSystem.metadata(tmp); + expect(data.size, 0); + }); + test('empty file', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync(''); + + final data = fileSystem.metadata(path); + expect(data.size, 0); + }); + test('non-empty file', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World!'); + + final data = fileSystem.metadata(path); + expect(data.size, 12); + }); + test('link', () { + File('$tmp/file1').writeAsStringSync('Hello World'); + final path = '$tmp/link'; + Link(path).createSync('$tmp/file1'); + + final data = fileSystem.metadata(path); + expect(data.size, 0); + }); + }); }); } From f7c6d76458b37699b6ed62260bdb2874ca953276 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 12:28:34 -0700 Subject: [PATCH 18/39] creation time --- pkgs/io_file/test/metadata_windows_test.dart | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 430a630b..2fba9491 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -22,8 +22,6 @@ void main() { tearDown(() => deleteTemp(tmp)); - //TODO(brianquinlan): test with a very long path. - group('isReadOnly', () { test('false', () { final path = '$tmp/file1'; @@ -150,5 +148,20 @@ void main() { expect(data.isOffline, isTrue); }); }); + + group('creation time', () { + test('new file', () { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + final creationTime = DateTime.now().millisecondsSinceEpoch; + + final data = windowsFileSystem.metadata(path); + expect( + data.creationTime.microsecondsSinceEpoch, + // Creation time within 2 seconds. + inInclusiveRange(creationTime - 2000, creationTime), + ); + }); + }); }); } From dd677828feff51e90434cb528dd48f9da6e6db61 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 12:34:05 -0700 Subject: [PATCH 19/39] not such path --- pkgs/io_file/test/metadata_test.dart | 5 +++++ pkgs/io_file/test/metadata_windows_test.dart | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart index 4682c862..103407a6 100644 --- a/pkgs/io_file/test/metadata_test.dart +++ b/pkgs/io_file/test/metadata_test.dart @@ -22,6 +22,11 @@ void main() { //TODO(brianquinlan): test with a very long path. + test('path does not exist', () { + final data = fileSystem.metadata('$tmp/file1'); + expect(data.isFile, isFalse); + }); + group('isDirectory/isFile/isLink', () { test('directory', () { final data = fileSystem.metadata(tmp); diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 2fba9491..0cf44f83 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -157,7 +157,7 @@ void main() { final data = windowsFileSystem.metadata(path); expect( - data.creationTime.microsecondsSinceEpoch, + data.creationTime.millisecondsSinceEpoch, // Creation time within 2 seconds. inInclusiveRange(creationTime - 2000, creationTime), ); From 918569054aef45de5b15de37dde76d67967f7d63 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 12:46:11 -0700 Subject: [PATCH 20/39] GetLastError --- pkgs/io_file/example/io_file_example.dart | 20 ++++++++++++++++--- .../lib/src/vm_windows_file_system.dart | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/pkgs/io_file/example/io_file_example.dart b/pkgs/io_file/example/io_file_example.dart index 6f1e37f4..da0c3172 100644 --- a/pkgs/io_file/example/io_file_example.dart +++ b/pkgs/io_file/example/io_file_example.dart @@ -2,9 +2,23 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:io_file/posix_file_system.dart'; +import 'package:io_file/io_file.dart'; +import 'package:io_file/windows_file_system.dart'; + +import 'package:path/path.dart' as p; + +bool isHidden(String path) { + if (fileSystem case final WindowsFileSystem fs) { + return fs.metadata(path).isHidden; + } else { + // On POSIX, convention is that files starting with a period are hidden + // (except for the special files representing the current working directory + // and parent directory). + final name = p.basename(path); + return name.startsWith('.') && name != '.' && name != '..'; + } +} void main() { - // TODO(brianquinlan): Create a better example. - PosixFileSystem().rename('foo.txt', 'bar.txt'); + isHidden('somefile'); } diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 0c31232c..38842be6 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -135,6 +135,11 @@ base class WindowsFileSystem extends FileSystem { bool? isContentNotIndexed, bool? isOffline, }) => using((arena) { + // Calling `GetLastError` for the first time causes the `GetLastError` + // symbol to be loaded, which resets `GetLastError`. So make a harmless + // call before the value is needed. + win32.GetLastError(); + if ((isReadOnly ?? isHidden ?? isSystem ?? @@ -193,6 +198,11 @@ base class WindowsFileSystem extends FileSystem { @override WindowsMetadata metadata(String path) => using((arena) { + // Calling `GetLastError` for the first time causes the `GetLastError` + // symbol to be loaded, which resets `GetLastError`. So make a harmless + // call before the value is needed. + win32.GetLastError(); + final fileInfo = arena(); if (win32.GetFileAttributesEx( path.toNativeUtf16(), From a687b8495a3c4367e9cb331d5dd86fa8e39a459a Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 15:16:04 -0700 Subject: [PATCH 21/39] Update metadata_test.dart --- pkgs/io_file/test/metadata_test.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkgs/io_file/test/metadata_test.dart b/pkgs/io_file/test/metadata_test.dart index 103407a6..9b406ca3 100644 --- a/pkgs/io_file/test/metadata_test.dart +++ b/pkgs/io_file/test/metadata_test.dart @@ -23,8 +23,18 @@ void main() { //TODO(brianquinlan): test with a very long path. test('path does not exist', () { - final data = fileSystem.metadata('$tmp/file1'); - expect(data.isFile, isFalse); + expect( + () => fileSystem.metadata('$tmp/file1'), + throwsA( + isA() + .having((e) => e.message, 'message', 'metadata failed') + .having( + (e) => e.osError?.errorCode, + 'errorCode', + 2, // ENOENT, ERROR_FILE_NOT_FOUND + ), + ), + ); }); group('isDirectory/isFile/isLink', () { From e3fc41a674caa17b9f138a402e5a5b6743d2e231 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 15:29:59 -0700 Subject: [PATCH 22/39] Times --- .../lib/src/vm_windows_file_system.dart | 7 ++-- pkgs/io_file/pubspec.yaml | 1 + pkgs/io_file/test/metadata_windows_test.dart | 38 ++++++++++++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 38842be6..96ec0e65 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -80,10 +80,9 @@ final class WindowsMetadata extends Metadata { final int lastAccessTime100Nanos; final int lastWriteTime100Nanos; - DateTime get creationTime => fileTimeToDateTime(creationTime100Nanos); - DateTime get lastAccessTime => fileTimeToDateTime(lastAccessTime100Nanos); - DateTime get lastModificationTime => - fileTimeToDateTime(lastWriteTime100Nanos); + DateTime get creation => fileTimeToDateTime(creationTime100Nanos); + DateTime get access => fileTimeToDateTime(lastAccessTime100Nanos); + DateTime get modification => fileTimeToDateTime(lastWriteTime100Nanos); WindowsMetadata({ super.isDirectory = false, diff --git a/pkgs/io_file/pubspec.yaml b/pkgs/io_file/pubspec.yaml index d29c7d6c..1b9ed605 100644 --- a/pkgs/io_file/pubspec.yaml +++ b/pkgs/io_file/pubspec.yaml @@ -21,4 +21,5 @@ dependencies: dev_dependencies: dart_flutter_team_lints: ^3.4.0 + path: ^1.9.1 test: ^1.24.0 diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 0cf44f83..05a34eec 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -149,7 +149,7 @@ void main() { }); }); - group('creation time', () { + group('creation', () { test('new file', () { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); @@ -157,11 +157,45 @@ void main() { final data = windowsFileSystem.metadata(path); expect( - data.creationTime.millisecondsSinceEpoch, + data.creation.millisecondsSinceEpoch, // Creation time within 2 seconds. inInclusiveRange(creationTime - 2000, creationTime), ); }); }); + + group('modificiation', () { + test('new file', () async { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + final creationTime = DateTime.now().millisecondsSinceEpoch; + await Future.delayed(const Duration(seconds: 1)); + File(path).writeAsStringSync('How are you?'); + final modificationTime = DateTime.now().millisecondsSinceEpoch; + + final data = windowsFileSystem.metadata(path); + expect( + data.modification.millisecondsSinceEpoch, + inInclusiveRange(creationTime + 1000, modificationTime), + ); + }); + }); + + group('access', () { + test('new file', () async { + final path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + final creationTime = DateTime.now().millisecondsSinceEpoch; + await Future.delayed(const Duration(seconds: 1)); + File(path).readAsBytesSync(); + final accessTime = DateTime.now().millisecondsSinceEpoch; + + final data = windowsFileSystem.metadata(path); + expect( + data.access.millisecondsSinceEpoch, + inInclusiveRange(creationTime + 1000, accessTime), + ); + }); + }); }); } From 309c186eb488d764028b342cffbc850ac360e1b0 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 15:35:26 -0700 Subject: [PATCH 23/39] More fixes --- pkgs/io_file/lib/src/file_system.dart | 5 +++++ pkgs/io_file/lib/src/vm_windows_file_system.dart | 2 -- pkgs/io_file/test/metadata_windows_test.dart | 3 +-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index 7c603199..e162ab71 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +/// Information about a directory, link, etc. stored in the [FileSystem]. base class Metadata { final bool isFile; final bool isDirectory; @@ -49,6 +50,10 @@ base class FileSystem { throw UnsupportedError('rename'); } + /// Returns metadata for the given path. + /// + /// If `path` represents a symbolic link then metadata for the link is + /// returned. Metadata metadata(String path) { throw UnsupportedError('metadata'); } diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 96ec0e65..ef4298e5 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -162,7 +162,6 @@ base class WindowsFileSystem extends FileSystem { } var attributes = fileInfo.ref.dwFileAttributes; - print('Initial bits: $attributes'); if (attributes == win32.FILE_ATTRIBUTE_NORMAL) { attributes = 0; } @@ -188,7 +187,6 @@ base class WindowsFileSystem extends FileSystem { isContentNotIndexed, ); attributes = setBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); - print('Final bits: $attributes'); if (win32.SetFileAttributes(nativePath, attributes) == win32.FALSE) { final errorCode = win32.GetLastError(); throw _getError(errorCode, 'set metadata failed', path); diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 05a34eec..66a94b55 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -186,14 +186,13 @@ void main() { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); final creationTime = DateTime.now().millisecondsSinceEpoch; - await Future.delayed(const Duration(seconds: 1)); File(path).readAsBytesSync(); final accessTime = DateTime.now().millisecondsSinceEpoch; final data = windowsFileSystem.metadata(path); expect( data.access.millisecondsSinceEpoch, - inInclusiveRange(creationTime + 1000, accessTime), + inInclusiveRange(creationTime, accessTime), ); }); }); From 2c51b7d1f4799f90143b1092be59096060d54c16 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 16:18:03 -0700 Subject: [PATCH 24/39] More tests --- pkgs/io_file/lib/src/file_system.dart | 11 ++ .../lib/src/vm_windows_file_system.dart | 31 +++++ pkgs/io_file/test/metadata_windows_test.dart | 121 +++++++++++++++++- 3 files changed, 162 insertions(+), 1 deletion(-) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index e162ab71..4cc2efbe 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -26,6 +26,17 @@ base class Metadata { ); } } + + @override + bool operator ==(Object other) => + other is Metadata && + isDirectory == other.isDirectory && + isFile == other.isFile && + isLink == other.isLink && + size == other.size; + + @override + int get hashCode => (isDirectory, isFile, isLink, size).hashCode; } /// An abstract representation of a file system. diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index ef4298e5..68764717 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -103,6 +103,37 @@ final class WindowsMetadata extends Metadata { this.lastAccessTime100Nanos = 0, this.lastWriteTime100Nanos = 0, }); + + @override + bool operator ==(Object other) => + other is WindowsMetadata && + super == other && + isReadOnly == other.isReadOnly && + isHidden == other.isHidden && + isSystem == other.isSystem && + isArchive == other.isArchive && + isTemporary == other.isTemporary && + isOffline == other.isOffline && + isContentNotIndexed == other.isContentNotIndexed && + creationTime100Nanos == other.creationTime100Nanos && + lastAccessTime100Nanos == other.lastAccessTime100Nanos && + lastWriteTime100Nanos == other.lastWriteTime100Nanos; + + @override + int get hashCode => + ( + super.hashCode, + isReadOnly, + isHidden, + isSystem, + isArchive, + isTemporary, + isOffline, + isContentNotIndexed, + creationTime100Nanos, + lastAccessTime100Nanos, + lastWriteTime100Nanos, + ).hashCode; } /// A [FileSystem] implementation for Windows systems. diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 66a94b55..60d80bab 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -15,7 +15,126 @@ import 'test_utils.dart'; void main() { final windowsFileSystem = WindowsFileSystem(); - group('metadata', () { + group('set metadata', () { + late String tmp; + + setUp(() => tmp = createTemp('metadata')); + + tearDown(() => deleteTemp(tmp)); + + group('start with all file attributes set', () { + late String path; + late WindowsMetadata initialMetadata; + + setUp(() { + path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata( + path, + isReadOnly: true, + isHidden: true, + isSystem: true, + isArchive: true, + isTemporary: true, + isContentNotIndexed: true, + isOffline: true, + ); + initialMetadata = windowsFileSystem.metadata(path); + }); + + test('set none', () { + windowsFileSystem.setMetadata(path); + expect(windowsFileSystem.metadata(path), initialMetadata); + }); + test('unset isReadOnly', () { + windowsFileSystem.setMetadata(path, isReadOnly: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + + test('unset isHidden', () { + windowsFileSystem.setMetadata(path, isHidden: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isFalse); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isSystem', () { + windowsFileSystem.setMetadata(path, isSystem: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isFalse); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isArchive', () { + windowsFileSystem.setMetadata(path, isArchive: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isTemporary', () { + windowsFileSystem.setMetadata(path, isTemporary: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isContentNotIndexed', () { + windowsFileSystem.setMetadata(path, isContentNotIndexed: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isTrue); + }); + test('unset isOffline', () { + windowsFileSystem.setMetadata(path, isOffline: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isFalse); + }); + }); + }); + + group('windows metadata', () { late String tmp; setUp(() => tmp = createTemp('metadata')); From fa3e0a2b90d430d4dfd4b055c7700d7f3c69c218 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 16:33:47 -0700 Subject: [PATCH 25/39] Update metadata_windows_test.dart --- pkgs/io_file/test/metadata_windows_test.dart | 349 ++++++++++++------- 1 file changed, 230 insertions(+), 119 deletions(-) diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 60d80bab..05f714d7 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -15,125 +15,6 @@ import 'test_utils.dart'; void main() { final windowsFileSystem = WindowsFileSystem(); - group('set metadata', () { - late String tmp; - - setUp(() => tmp = createTemp('metadata')); - - tearDown(() => deleteTemp(tmp)); - - group('start with all file attributes set', () { - late String path; - late WindowsMetadata initialMetadata; - - setUp(() { - path = '$tmp/file1'; - File(path).writeAsStringSync('Hello World'); - windowsFileSystem.setMetadata( - path, - isReadOnly: true, - isHidden: true, - isSystem: true, - isArchive: true, - isTemporary: true, - isContentNotIndexed: true, - isOffline: true, - ); - initialMetadata = windowsFileSystem.metadata(path); - }); - - test('set none', () { - windowsFileSystem.setMetadata(path); - expect(windowsFileSystem.metadata(path), initialMetadata); - }); - test('unset isReadOnly', () { - windowsFileSystem.setMetadata(path, isReadOnly: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - - test('unset isHidden', () { - windowsFileSystem.setMetadata(path, isHidden: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isFalse); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isSystem', () { - windowsFileSystem.setMetadata(path, isSystem: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isFalse); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isArchive', () { - windowsFileSystem.setMetadata(path, isArchive: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isTemporary', () { - windowsFileSystem.setMetadata(path, isTemporary: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isContentNotIndexed', () { - windowsFileSystem.setMetadata(path, isContentNotIndexed: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isTrue); - }); - test('unset isOffline', () { - windowsFileSystem.setMetadata(path, isOffline: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isFalse); - }); - }); - }); - group('windows metadata', () { late String tmp; @@ -316,4 +197,234 @@ void main() { }); }); }); + + group('set metadata', () { + late String tmp; + + setUp(() => tmp = createTemp('metadata')); + + tearDown(() => deleteTemp(tmp)); + + group('start with all file attributes set', () { + late String path; + late WindowsMetadata initialMetadata; + + setUp(() { + path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata( + path, + isReadOnly: true, + isHidden: true, + isSystem: true, + isArchive: true, + isTemporary: true, + isContentNotIndexed: true, + isOffline: true, + ); + initialMetadata = windowsFileSystem.metadata(path); + }); + + test('set none', () { + windowsFileSystem.setMetadata(path); + expect(windowsFileSystem.metadata(path), initialMetadata); + }); + test('unset isReadOnly', () { + windowsFileSystem.setMetadata(path, isReadOnly: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + + test('unset isHidden', () { + windowsFileSystem.setMetadata(path, isHidden: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isFalse); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isSystem', () { + windowsFileSystem.setMetadata(path, isSystem: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isFalse); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isArchive', () { + windowsFileSystem.setMetadata(path, isArchive: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isTemporary', () { + windowsFileSystem.setMetadata(path, isTemporary: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isContentNotIndexed', () { + windowsFileSystem.setMetadata(path, isContentNotIndexed: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isTrue); + }); + test('unset isOffline', () { + windowsFileSystem.setMetadata(path, isOffline: false); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isFalse); + }); + }); + + group('start with no file attributes set', () { + late String path; + late WindowsMetadata initialMetadata; + + setUp(() { + path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata( + path, + isReadOnly: false, + isHidden: false, + isSystem: false, + isArchive: false, + isTemporary: false, + isContentNotIndexed: false, + isOffline: false, + ); + initialMetadata = windowsFileSystem.metadata(path); + }); + + test('set none', () { + windowsFileSystem.setMetadata(path); + expect(windowsFileSystem.metadata(path), initialMetadata); + }); + test('set isReadOnly', () { + windowsFileSystem.setMetadata(path, isReadOnly: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + + test('set isHidden', () { + windowsFileSystem.setMetadata(path, isHidden: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isTrue); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isSystem', () { + windowsFileSystem.setMetadata(path, isSystem: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isTrue); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isArchive', () { + windowsFileSystem.setMetadata(path, isArchive: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isTemporary', () { + windowsFileSystem.setMetadata(path, isTemporary: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isContentNotIndexed', () { + windowsFileSystem.setMetadata(path, isContentNotIndexed: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isFalse); + }); + test('set isOffline', () { + windowsFileSystem.setMetadata(path, isOffline: true); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isTrue); + }); + }); + }); } From b287897834fc13a7f2c7ef539788cca5d5b90425 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 16:41:54 -0700 Subject: [PATCH 26/39] Docs --- pkgs/io_file/lib/src/file_system.dart | 1 + .../lib/src/vm_windows_file_system.dart | 6 ++++++ pkgs/io_file/test/metadata_windows_test.dart | 19 ++++++++++--------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index 4cc2efbe..1b22d107 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -4,6 +4,7 @@ /// Information about a directory, link, etc. stored in the [FileSystem]. base class Metadata { + // TODO(brianquinlan): Document all public fields. final bool isFile; final bool isDirectory; final bool isLink; diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 68764717..a8957d34 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -68,6 +68,8 @@ Exception _getError(int errorCode, String message, String path) { } final class WindowsMetadata extends Metadata { + // TODO(brianquinlan): Document the public fields. + final bool isReadOnly; final bool isHidden; final bool isSystem; @@ -84,6 +86,7 @@ final class WindowsMetadata extends Metadata { DateTime get access => fileTimeToDateTime(lastAccessTime100Nanos); DateTime get modification => fileTimeToDateTime(lastWriteTime100Nanos); + /// TODO(bquinlan): Document this constructor. WindowsMetadata({ super.isDirectory = false, super.isFile = false, @@ -155,6 +158,9 @@ base class WindowsFileSystem extends FileSystem { } }); + /// Sets metadata for the file system entity. + /// + /// TODO(brianquinlan): Document the arguments. void setMetadata( String path, { bool? isReadOnly, diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 05f714d7..4e458f0f 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -153,13 +153,13 @@ void main() { test('new file', () { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); - final creationTime = DateTime.now().millisecondsSinceEpoch; + final maxCreationTime = DateTime.now().millisecondsSinceEpoch; final data = windowsFileSystem.metadata(path); expect( data.creation.millisecondsSinceEpoch, - // Creation time within 2 seconds. - inInclusiveRange(creationTime - 2000, creationTime), + // Creation time within 1 second. + inInclusiveRange(maxCreationTime - 1000, maxCreationTime), ); }); }); @@ -168,15 +168,17 @@ void main() { test('new file', () async { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); - final creationTime = DateTime.now().millisecondsSinceEpoch; await Future.delayed(const Duration(seconds: 1)); File(path).writeAsStringSync('How are you?'); - final modificationTime = DateTime.now().millisecondsSinceEpoch; + final maxModificationTime = DateTime.now().millisecondsSinceEpoch; final data = windowsFileSystem.metadata(path); expect( data.modification.millisecondsSinceEpoch, - inInclusiveRange(creationTime + 1000, modificationTime), + inInclusiveRange( + data.creation.millisecondsSinceEpoch + 1000, + maxModificationTime, + ), ); }); }); @@ -185,14 +187,13 @@ void main() { test('new file', () async { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); - final creationTime = DateTime.now().millisecondsSinceEpoch; File(path).readAsBytesSync(); - final accessTime = DateTime.now().millisecondsSinceEpoch; + final maxAccessTime = DateTime.now().millisecondsSinceEpoch; final data = windowsFileSystem.metadata(path); expect( data.access.millisecondsSinceEpoch, - inInclusiveRange(creationTime, accessTime), + inInclusiveRange(data.creation.millisecondsSinceEpoch, maxAccessTime), ); }); }); From b88bfca84f6ce021fc577adb86d8695518cfb5e5 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 17:10:44 -0700 Subject: [PATCH 27/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index a8957d34..e0907844 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -10,7 +10,7 @@ import 'package:win32/win32.dart' as win32; import 'file_system.dart'; -DateTime fileTimeToDateTime(int t) { +DateTime _fileTimeToDateTime(int t) { final microseconds = t ~/ 10; return DateTime.utc(1601, 1, 1).add(Duration(microseconds: microseconds)); } @@ -82,9 +82,9 @@ final class WindowsMetadata extends Metadata { final int lastAccessTime100Nanos; final int lastWriteTime100Nanos; - DateTime get creation => fileTimeToDateTime(creationTime100Nanos); - DateTime get access => fileTimeToDateTime(lastAccessTime100Nanos); - DateTime get modification => fileTimeToDateTime(lastWriteTime100Nanos); + DateTime get creation => _fileTimeToDateTime(creationTime100Nanos); + DateTime get access => _fileTimeToDateTime(lastAccessTime100Nanos); + DateTime get modification => _fileTimeToDateTime(lastWriteTime100Nanos); /// TODO(bquinlan): Document this constructor. WindowsMetadata({ From d14f1a9129509dff7e28203bb51148641caa5e2f Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Mon, 17 Mar 2025 17:50:18 -0700 Subject: [PATCH 28/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index e0907844..852bb4a8 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -67,7 +67,10 @@ Exception _getError(int errorCode, String message, String path) { } } +/// File system entity data available on Windows. final class WindowsMetadata extends Metadata { + // TODO(brianquinlan): Reoganize fields when the POSIX `metadata` is + // available. // TODO(brianquinlan): Document the public fields. final bool isReadOnly; From 4d8ccfdb1109acf9b8b8409f6c8b04b0d3b6a266 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Tue, 18 Mar 2025 13:04:02 -0700 Subject: [PATCH 29/39] Code review updates --- .../lib/src/vm_windows_file_system.dart | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 852bb4a8..84807f8e 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -144,12 +144,17 @@ final class WindowsMetadata extends Metadata { /// A [FileSystem] implementation for Windows systems. base class WindowsFileSystem extends FileSystem { - @override - void rename(String oldPath, String newPath) => using((arena) { + WindowsFileSystem() { // Calling `GetLastError` for the first time causes the `GetLastError` // symbol to be loaded, which resets `GetLastError`. So make a harmless // call before the value is needed. + // + // TODO(brianquinlan): Remove this after it is fixed in the Dart SDK. win32.GetLastError(); + } + + @override + void rename(String oldPath, String newPath) => using((arena) { if (win32.MoveFileEx( oldPath.toNativeUtf16(allocator: arena), newPath.toNativeUtf16(allocator: arena), @@ -174,11 +179,6 @@ base class WindowsFileSystem extends FileSystem { bool? isContentNotIndexed, bool? isOffline, }) => using((arena) { - // Calling `GetLastError` for the first time causes the `GetLastError` - // symbol to be loaded, which resets `GetLastError`. So make a harmless - // call before the value is needed. - win32.GetLastError(); - if ((isReadOnly ?? isHidden ?? isSystem ?? @@ -203,6 +203,8 @@ base class WindowsFileSystem extends FileSystem { var attributes = fileInfo.ref.dwFileAttributes; if (attributes == win32.FILE_ATTRIBUTE_NORMAL) { + // `FILE_ATTRIBUTE_NORMAL` indicates that no other attributes are set and + // is valid only when used alone. attributes = 0; } @@ -235,14 +237,9 @@ base class WindowsFileSystem extends FileSystem { @override WindowsMetadata metadata(String path) => using((arena) { - // Calling `GetLastError` for the first time causes the `GetLastError` - // symbol to be loaded, which resets `GetLastError`. So make a harmless - // call before the value is needed. - win32.GetLastError(); - final fileInfo = arena(); if (win32.GetFileAttributesEx( - path.toNativeUtf16(), + path.toNativeUtf16(allocator: arena), win32.GetFileExInfoStandard, fileInfo, ) == From 1aeb1523e565b49e2c7b54e122ee69c731c80c63 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 19 Mar 2025 11:48:41 -0700 Subject: [PATCH 30/39] Review feedback. --- pkgs/io_file/example/io_file_example.dart | 6 +-- pkgs/io_file/lib/src/file_system.dart | 7 ++- .../lib/src/vm_windows_file_system.dart | 52 +++++++++++-------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/pkgs/io_file/example/io_file_example.dart b/pkgs/io_file/example/io_file_example.dart index da0c3172..baf01bee 100644 --- a/pkgs/io_file/example/io_file_example.dart +++ b/pkgs/io_file/example/io_file_example.dart @@ -11,9 +11,9 @@ bool isHidden(String path) { if (fileSystem case final WindowsFileSystem fs) { return fs.metadata(path).isHidden; } else { - // On POSIX, convention is that files starting with a period are hidden - // (except for the special files representing the current working directory - // and parent directory). + // On POSIX, convention is that files and directories starting with a period + // are hidden (except for the special files representing the current working + // directory and parent directory). final name = p.basename(path); return name.startsWith('.') && name != '.' && name != '..'; } diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index 1b22d107..9b4b75f7 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -5,6 +5,7 @@ /// Information about a directory, link, etc. stored in the [FileSystem]. base class Metadata { // TODO(brianquinlan): Document all public fields. + final bool isFile; final bool isDirectory; final bool isLink; @@ -22,6 +23,8 @@ base class Metadata { // or link and whether it can be more than one of these at once. // Rust requires that at most one of these is true. Python has no such // restriction. + + // TODO(bquinlan): if we keep this logic, use `ArgumentError.value`. throw ArgumentError( 'only one of isDirectory, isFile, or isLink must be true', ); @@ -37,7 +40,7 @@ base class Metadata { size == other.size; @override - int get hashCode => (isDirectory, isFile, isLink, size).hashCode; + int get hashCode => Object.hash(isDirectory, isFile, isLink, size).hashCode; } /// An abstract representation of a file system. @@ -62,7 +65,7 @@ base class FileSystem { throw UnsupportedError('rename'); } - /// Returns metadata for the given path. + /// Metadata for the file system object at [path]. /// /// If `path` represents a symbolic link then metadata for the link is /// returned. diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 84807f8e..fc6ebddb 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -126,20 +126,19 @@ final class WindowsMetadata extends Metadata { lastWriteTime100Nanos == other.lastWriteTime100Nanos; @override - int get hashCode => - ( - super.hashCode, - isReadOnly, - isHidden, - isSystem, - isArchive, - isTemporary, - isOffline, - isContentNotIndexed, - creationTime100Nanos, - lastAccessTime100Nanos, - lastWriteTime100Nanos, - ).hashCode; + int get hashCode => Object.hash( + super.hashCode, + isReadOnly, + isHidden, + isSystem, + isArchive, + isTemporary, + isOffline, + isContentNotIndexed, + creationTime100Nanos, + lastAccessTime100Nanos, + lastWriteTime100Nanos, + ); } /// A [FileSystem] implementation for Windows systems. @@ -208,27 +207,36 @@ base class WindowsFileSystem extends FileSystem { attributes = 0; } - int setBit(int base, int value, bool? bit) => switch (bit) { + int updateBit(int base, int value, bool? bit) => switch (bit) { null => base, true => base | value, false => base & ~value, }; - attributes = setBit(attributes, win32.FILE_ATTRIBUTE_READONLY, isReadOnly); - attributes = setBit(attributes, win32.FILE_ATTRIBUTE_HIDDEN, isHidden); - attributes = setBit(attributes, win32.FILE_ATTRIBUTE_SYSTEM, isSystem); - attributes = setBit(attributes, win32.FILE_ATTRIBUTE_ARCHIVE, isArchive); - attributes = setBit( + attributes = updateBit( + attributes, + win32.FILE_ATTRIBUTE_READONLY, + isReadOnly, + ); + attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_HIDDEN, isHidden); + attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_SYSTEM, isSystem); + attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_ARCHIVE, isArchive); + attributes = updateBit( attributes, win32.FILE_ATTRIBUTE_TEMPORARY, isTemporary, ); - attributes = setBit( + attributes = updateBit( attributes, win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, isContentNotIndexed, ); - attributes = setBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); + attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); + if (attributes == 0) { + // `FILE_ATTRIBUTE_NORMAL` indicates that no other attributes are set and + // is valid only when used alone. + attributes = win32.FILE_ATTRIBUTE_NORMAL; + } if (win32.SetFileAttributes(nativePath, attributes) == win32.FALSE) { final errorCode = win32.GetLastError(); throw _getError(errorCode, 'set metadata failed', path); From b6ffeffa5363bb777b4d2ff9217b0b4d41ccae03 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 27 Mar 2025 09:49:08 -0700 Subject: [PATCH 31/39] Add library --- pkgs/io_file/lib/src/file_system.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index fac154cd..e01fe9e9 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -4,6 +4,8 @@ /// Information about a directory, link, etc. stored in the [FileSystem]. +library; + import 'dart:typed_data'; base class Metadata { From 8c95248046c492582755ca0df03605ad3e215538 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 27 Mar 2025 09:49:42 -0700 Subject: [PATCH 32/39] Update file_system.dart --- pkgs/io_file/lib/src/file_system.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index e01fe9e9..b6dec35e 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -2,12 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -/// Information about a directory, link, etc. stored in the [FileSystem]. - -library; - import 'dart:typed_data'; +/// Information about a directory, link, etc. stored in the [FileSystem]. base class Metadata { // TODO(brianquinlan): Document all public fields. From 18e6f8bfed65cecc1ecfb81b6d599c7429283afc Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 27 Mar 2025 11:05:00 -0700 Subject: [PATCH 33/39] Review comments --- pkgs/io_file/example/io_file_example.dart | 2 + pkgs/io_file/lib/src/file_system.dart | 41 ++------------- .../lib/src/vm_windows_file_system.dart | 52 +++++++++++++------ 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/pkgs/io_file/example/io_file_example.dart b/pkgs/io_file/example/io_file_example.dart index baf01bee..fa977684 100644 --- a/pkgs/io_file/example/io_file_example.dart +++ b/pkgs/io_file/example/io_file_example.dart @@ -14,6 +14,8 @@ bool isHidden(String path) { // On POSIX, convention is that files and directories starting with a period // are hidden (except for the special files representing the current working // directory and parent directory). + // + // In addition, macOS has the UF_HIDDEN flag. final name = p.basename(path); return name.startsWith('.') && name != '.' && name != '..'; } diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index b6dec35e..f33d5a36 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -5,44 +5,13 @@ import 'dart:typed_data'; /// Information about a directory, link, etc. stored in the [FileSystem]. -base class Metadata { +abstract interface class Metadata { // TODO(brianquinlan): Document all public fields. - final bool isFile; - final bool isDirectory; - final bool isLink; - final int size; - - Metadata({ - this.isDirectory = false, - this.isFile = false, - this.isLink = false, - this.size = 0, - }) { - final count = (isDirectory ? 1 : 0) + (isFile ? 1 : 0) + (isLink ? 1 : 0); - if (count > 1) { - // TODO(brianquinlan): Decide whether a path must be a a file, directory - // or link and whether it can be more than one of these at once. - // Rust requires that at most one of these is true. Python has no such - // restriction. - - // TODO(bquinlan): if we keep this logic, use `ArgumentError.value`. - throw ArgumentError( - 'only one of isDirectory, isFile, or isLink must be true', - ); - } - } - - @override - bool operator ==(Object other) => - other is Metadata && - isDirectory == other.isDirectory && - isFile == other.isFile && - isLink == other.isLink && - size == other.size; - - @override - int get hashCode => Object.hash(isDirectory, isFile, isLink, size).hashCode; + bool get isFile; + bool get isDirectory; + bool get isLink; + int get size; } /// An abstract representation of a file system. diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index fc6ebddb..0463355b 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -10,9 +10,11 @@ import 'package:win32/win32.dart' as win32; import 'file_system.dart'; +const _hundredsOfNanosecondsPerMicrosecond = 10; + DateTime _fileTimeToDateTime(int t) { - final microseconds = t ~/ 10; - return DateTime.utc(1601, 1, 1).add(Duration(microseconds: microseconds)); + final microseconds = t ~/ _hundredsOfNanosecondsPerMicrosecond; + return DateTime.utc(1601, 1, 1, 0, 0, 0, 0, microseconds); } String _formatMessage(int errorCode) { @@ -68,11 +70,23 @@ Exception _getError(int errorCode, String message, String path) { } /// File system entity data available on Windows. -final class WindowsMetadata extends Metadata { +final class WindowsMetadata implements Metadata { // TODO(brianquinlan): Reoganize fields when the POSIX `metadata` is // available. // TODO(brianquinlan): Document the public fields. + @override + final bool isDirectory; + + @override + final bool isFile; + + @override + final bool isLink; + + @override + final int size; + final bool isReadOnly; final bool isHidden; final bool isSystem; @@ -91,11 +105,11 @@ final class WindowsMetadata extends Metadata { /// TODO(bquinlan): Document this constructor. WindowsMetadata({ - super.isDirectory = false, - super.isFile = false, - super.isLink = false, + this.isDirectory = false, + this.isFile = false, + this.isLink = false, - super.size = 0, + this.size = 0, this.isReadOnly = false, this.isHidden = false, @@ -113,7 +127,10 @@ final class WindowsMetadata extends Metadata { @override bool operator ==(Object other) => other is WindowsMetadata && - super == other && + isDirectory == other.isDirectory && + isFile == other.isFile && + isLink == other.isLink && + size == other.size && isReadOnly == other.isReadOnly && isHidden == other.isHidden && isSystem == other.isSystem && @@ -127,7 +144,10 @@ final class WindowsMetadata extends Metadata { @override int get hashCode => Object.hash( - super.hashCode, + isDirectory, + isFile, + isLink, + size, isReadOnly, isHidden, isSystem, @@ -263,17 +283,17 @@ base class WindowsFileSystem extends FileSystem { final isFile = !(isDirectory || isLink); return WindowsMetadata( - isReadOnly: attributes & win32.FILE_ATTRIBUTE_READONLY > 0, - isHidden: attributes & win32.FILE_ATTRIBUTE_HIDDEN > 0, - isSystem: attributes & win32.FILE_ATTRIBUTE_SYSTEM > 0, + isReadOnly: attributes & win32.FILE_ATTRIBUTE_READONLY != 0, + isHidden: attributes & win32.FILE_ATTRIBUTE_HIDDEN != 0, + isSystem: attributes & win32.FILE_ATTRIBUTE_SYSTEM != 0, isDirectory: isDirectory, - isArchive: attributes & win32.FILE_ATTRIBUTE_ARCHIVE > 0, - isTemporary: attributes & win32.FILE_ATTRIBUTE_TEMPORARY > 0, + isArchive: attributes & win32.FILE_ATTRIBUTE_ARCHIVE != 0, + isTemporary: attributes & win32.FILE_ATTRIBUTE_TEMPORARY != 0, isLink: isLink, isFile: isFile, - isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE > 0, + isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE != 0, isContentNotIndexed: - attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED > 0, + attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED != 0, size: info.nFileSizeHigh << 32 | info.nFileSizeLow, creationTime100Nanos: From 186c228f3cae7116b15cf5fc25f8bd3563059e74 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 27 Mar 2025 11:15:29 -0700 Subject: [PATCH 34/39] Update file_system.dart --- pkgs/io_file/lib/src/file_system.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/io_file/lib/src/file_system.dart b/pkgs/io_file/lib/src/file_system.dart index f33d5a36..713b3bae 100644 --- a/pkgs/io_file/lib/src/file_system.dart +++ b/pkgs/io_file/lib/src/file_system.dart @@ -15,7 +15,7 @@ abstract interface class Metadata { } /// An abstract representation of a file system. -base class FileSystem { +abstract base class FileSystem { /// Renames, and possibly moves a file system object from one path to another. /// /// If `newPath` is a relative path, it is resolved against the current From 7d11d363e76efa16d3a117d756b363d9e8547686 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 27 Mar 2025 12:56:36 -0700 Subject: [PATCH 35/39] Update file_system_test.dart --- pkgs/io_file/test/file_system_test.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/io_file/test/file_system_test.dart b/pkgs/io_file/test/file_system_test.dart index cf8c9f63..da4d5f3f 100644 --- a/pkgs/io_file/test/file_system_test.dart +++ b/pkgs/io_file/test/file_system_test.dart @@ -2,13 +2,12 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:io_file/io_file.dart'; import 'package:test/test.dart'; void main() { group('FileSystem', () { - test('rename', () { - expect(() => FileSystem().rename('a', 'b'), throwsUnsupportedError); + test('TODO(brianquinlan)', () { + // Add tests. }); }); } From 90d4070517632ea3f355f8dace243710cefda27d Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Thu, 27 Mar 2025 14:58:12 -0700 Subject: [PATCH 36/39] Update vm_windows_file_system.dart --- pkgs/io_file/lib/src/vm_windows_file_system.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 0463355b..390f9a9c 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -278,8 +278,8 @@ base class WindowsFileSystem extends FileSystem { final info = fileInfo.ref; final attributes = info.dwFileAttributes; - final isDirectory = attributes & win32.FILE_ATTRIBUTE_DIRECTORY > 0; - final isLink = attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT > 0; + final isDirectory = attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0; + final isLink = attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT != 0; final isFile = !(isDirectory || isLink); return WindowsMetadata( From 0188da69964976f7fc18905b83e3ba0ade08d8b9 Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Fri, 4 Apr 2025 10:32:34 -0700 Subject: [PATCH 37/39] Allow if original metadata --- .../lib/src/vm_windows_file_system.dart | 133 ++--- pkgs/io_file/test/metadata_windows_test.dart | 508 ++++++++++-------- 2 files changed, 360 insertions(+), 281 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index cb724897..cbd88221 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -78,26 +78,28 @@ final class WindowsMetadata implements Metadata { // TODO(brianquinlan): Reoganize fields when the POSIX `metadata` is // available. // TODO(brianquinlan): Document the public fields. + int _attributes; @override - final bool isDirectory; + bool get isDirectory => _attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0; @override - final bool isFile; + bool get isFile => !isDirectory && !isLink; @override - final bool isLink; + bool get isLink => _attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT != 0; @override final int size; - final bool isReadOnly; - final bool isHidden; - final bool isSystem; - final bool isArchive; - final bool isTemporary; - final bool isOffline; - final bool isContentNotIndexed; + bool get isReadOnly => _attributes & win32.FILE_ATTRIBUTE_READONLY != 0; + bool get isHidden => _attributes & win32.FILE_ATTRIBUTE_HIDDEN != 0; + bool get isSystem => _attributes & win32.FILE_ATTRIBUTE_SYSTEM != 0; + bool get isArchive => _attributes & win32.FILE_ATTRIBUTE_ARCHIVE != 0; + bool get isTemporary => _attributes & win32.FILE_ATTRIBUTE_TEMPORARY != 0; + bool get isOffline => _attributes & win32.FILE_ATTRIBUTE_OFFLINE != 0; + bool get isContentNotIndexed => + _attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED != 0; final int creationTime100Nanos; final int lastAccessTime100Nanos; @@ -107,57 +109,61 @@ final class WindowsMetadata implements Metadata { DateTime get access => _fileTimeToDateTime(lastAccessTime100Nanos); DateTime get modification => _fileTimeToDateTime(lastWriteTime100Nanos); + WindowsMetadata._( + this._attributes, + this.size, + this.creationTime100Nanos, + this.lastAccessTime100Nanos, + this.lastWriteTime100Nanos, + ); + /// TODO(bquinlan): Document this constructor. - WindowsMetadata({ - this.isDirectory = false, - this.isFile = false, - this.isLink = false, - - this.size = 0, - - this.isReadOnly = false, - this.isHidden = false, - this.isSystem = false, - this.isArchive = false, - this.isTemporary = false, - this.isOffline = false, - this.isContentNotIndexed = false, - - this.creationTime100Nanos = 0, - this.lastAccessTime100Nanos = 0, - this.lastWriteTime100Nanos = 0, - }); + factory WindowsMetadata.fromProperties({ + bool isDirectory = false, + bool isLink = false, + + int size = 0, + + bool isReadOnly = false, + bool isHidden = false, + bool isSystem = false, + bool isArchive = false, + bool isTemporary = false, + bool isOffline = false, + bool isContentNotIndexed = false, + + int creationTime100Nanos = 0, + int lastAccessTime100Nanos = 0, + int lastWriteTime100Nanos = 0, + }) => WindowsMetadata._( + (isDirectory ? win32.FILE_ATTRIBUTE_DIRECTORY : 0) | + (isLink ? win32.FILE_ATTRIBUTE_REPARSE_POINT : 0) | + (isReadOnly ? win32.FILE_ATTRIBUTE_READONLY : 0) | + (isHidden ? win32.FILE_ATTRIBUTE_HIDDEN : 0) | + (isSystem ? win32.FILE_ATTRIBUTE_SYSTEM : 0) | + (isArchive ? win32.FILE_ATTRIBUTE_ARCHIVE : 0) | + (isTemporary ? win32.FILE_ATTRIBUTE_TEMPORARY : 0) | + (isOffline ? win32.FILE_ATTRIBUTE_OFFLINE : 0) | + (isContentNotIndexed ? win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED : 0), + size, + creationTime100Nanos, + lastAccessTime100Nanos, + lastWriteTime100Nanos, + ); @override bool operator ==(Object other) => other is WindowsMetadata && - isDirectory == other.isDirectory && - isFile == other.isFile && - isLink == other.isLink && + _attributes == other._attributes && size == other.size && - isReadOnly == other.isReadOnly && - isHidden == other.isHidden && - isSystem == other.isSystem && - isArchive == other.isArchive && - isTemporary == other.isTemporary && - isOffline == other.isOffline && - isContentNotIndexed == other.isContentNotIndexed && creationTime100Nanos == other.creationTime100Nanos && lastAccessTime100Nanos == other.lastAccessTime100Nanos && lastWriteTime100Nanos == other.lastWriteTime100Nanos; @override int get hashCode => Object.hash( - isDirectory, - isFile, - isLink, + _attributes, size, - isReadOnly, - isHidden, - isSystem, - isArchive, - isTemporary, - isOffline, isContentNotIndexed, creationTime100Nanos, lastAccessTime100Nanos, @@ -192,6 +198,9 @@ base class WindowsFileSystem extends FileSystem { /// Sets metadata for the file system entity. /// /// TODO(brianquinlan): Document the arguments. + /// Make sure to document that [original] should come from a call to + /// `metadata`. Creating your own `WindowsMetadata` will result in unsupported + /// fields being cleared. void setMetadata( String path, { bool? isReadOnly, @@ -201,6 +210,7 @@ base class WindowsFileSystem extends FileSystem { bool? isTemporary, bool? isContentNotIndexed, bool? isOffline, + WindowsMetadata? original, }) => using((arena) { if ((isReadOnly ?? isHidden ?? @@ -214,17 +224,22 @@ base class WindowsFileSystem extends FileSystem { } final fileInfo = arena(); final nativePath = path.toNativeUtf16(allocator: arena); - if (win32.GetFileAttributesEx( - nativePath, - win32.GetFileExInfoStandard, - fileInfo, - ) == - win32.FALSE) { - final errorCode = win32.GetLastError(); - throw _getError(errorCode, 'set metadata failed', path); + int attributes; + if (original == null) { + if (win32.GetFileAttributesEx( + nativePath, + win32.GetFileExInfoStandard, + fileInfo, + ) == + win32.FALSE) { + final errorCode = win32.GetLastError(); + throw _getError(errorCode, 'set metadata failed', path); + } + attributes = fileInfo.ref.dwFileAttributes; + } else { + attributes = original._attributes; } - var attributes = fileInfo.ref.dwFileAttributes; if (attributes == win32.FILE_ATTRIBUTE_NORMAL) { // `FILE_ATTRIBUTE_NORMAL` indicates that no other attributes are set and // is valid only when used alone. @@ -284,9 +299,8 @@ base class WindowsFileSystem extends FileSystem { final isDirectory = attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0; final isLink = attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT != 0; - final isFile = !(isDirectory || isLink); - return WindowsMetadata( + return WindowsMetadata.fromProperties( isReadOnly: attributes & win32.FILE_ATTRIBUTE_READONLY != 0, isHidden: attributes & win32.FILE_ATTRIBUTE_HIDDEN != 0, isSystem: attributes & win32.FILE_ATTRIBUTE_SYSTEM != 0, @@ -294,7 +308,6 @@ base class WindowsFileSystem extends FileSystem { isArchive: attributes & win32.FILE_ATTRIBUTE_ARCHIVE != 0, isTemporary: attributes & win32.FILE_ATTRIBUTE_TEMPORARY != 0, isLink: isLink, - isFile: isFile, isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE != 0, isContentNotIndexed: attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED != 0, diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index 4e458f0f..d08eae26 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -206,226 +206,292 @@ void main() { tearDown(() => deleteTemp(tmp)); - group('start with all file attributes set', () { - late String path; - late WindowsMetadata initialMetadata; - - setUp(() { - path = '$tmp/file1'; - File(path).writeAsStringSync('Hello World'); - windowsFileSystem.setMetadata( - path, - isReadOnly: true, - isHidden: true, - isSystem: true, - isArchive: true, - isTemporary: true, - isContentNotIndexed: true, - isOffline: true, - ); - initialMetadata = windowsFileSystem.metadata(path); - }); - - test('set none', () { - windowsFileSystem.setMetadata(path); - expect(windowsFileSystem.metadata(path), initialMetadata); - }); - test('unset isReadOnly', () { - windowsFileSystem.setMetadata(path, isReadOnly: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - - test('unset isHidden', () { - windowsFileSystem.setMetadata(path, isHidden: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isFalse); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isSystem', () { - windowsFileSystem.setMetadata(path, isSystem: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isFalse); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isArchive', () { - windowsFileSystem.setMetadata(path, isArchive: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isTemporary', () { - windowsFileSystem.setMetadata(path, isTemporary: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isTrue); - }); - test('unset isContentNotIndexed', () { - windowsFileSystem.setMetadata(path, isContentNotIndexed: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isTrue); - }); - test('unset isOffline', () { - windowsFileSystem.setMetadata(path, isOffline: false); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isTrue); - expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isFalse); - }); - }); - - group('start with no file attributes set', () { - late String path; - late WindowsMetadata initialMetadata; - - setUp(() { - path = '$tmp/file1'; - File(path).writeAsStringSync('Hello World'); - windowsFileSystem.setMetadata( - path, - isReadOnly: false, - isHidden: false, - isSystem: false, - isArchive: false, - isTemporary: false, - isContentNotIndexed: false, - isOffline: false, - ); - initialMetadata = windowsFileSystem.metadata(path); - }); - - test('set none', () { - windowsFileSystem.setMetadata(path); - expect(windowsFileSystem.metadata(path), initialMetadata); - }); - test('set isReadOnly', () { - windowsFileSystem.setMetadata(path, isReadOnly: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isTrue); - expect(data.isHidden, isFalse); - expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isFalse); - }); - - test('set isHidden', () { - windowsFileSystem.setMetadata(path, isHidden: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isTrue); - expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isFalse); - }); - test('set isSystem', () { - windowsFileSystem.setMetadata(path, isSystem: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isFalse); - expect(data.isSystem, isTrue); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isFalse); - }); - test('set isArchive', () { - windowsFileSystem.setMetadata(path, isArchive: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isFalse); - expect(data.isSystem, isFalse); - expect(data.isArchive, isTrue); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isFalse); - }); - test('set isTemporary', () { - windowsFileSystem.setMetadata(path, isTemporary: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isFalse); - expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isFalse); - }); - test('set isContentNotIndexed', () { - windowsFileSystem.setMetadata(path, isContentNotIndexed: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isFalse); - expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isTrue); - expect(data.isOffline, isFalse); - }); - test('set isOffline', () { - windowsFileSystem.setMetadata(path, isOffline: true); - - final data = windowsFileSystem.metadata(path); - expect(data.isReadOnly, isFalse); - expect(data.isHidden, isFalse); - expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); - expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); - expect(data.isOffline, isTrue); - }); - }); + for (var includeOriginalMetadata in [true, false]) { + group('(use original metadata: $includeOriginalMetadata)', () { + group('start with all file attributes set', () { + late String path; + late WindowsMetadata initialMetadata; + + setUp(() { + path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata( + path, + isReadOnly: true, + isHidden: true, + isSystem: true, + isArchive: true, + isTemporary: true, + isContentNotIndexed: true, + isOffline: true, + ); + initialMetadata = windowsFileSystem.metadata(path); + }); + + test('set none', () { + windowsFileSystem.setMetadata( + path, + original: includeOriginalMetadata ? initialMetadata : null, + ); + expect(windowsFileSystem.metadata(path), initialMetadata); + }); + test('unset isReadOnly', () { + windowsFileSystem.setMetadata( + path, + isReadOnly: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + + test('unset isHidden', () { + windowsFileSystem.setMetadata( + path, + isHidden: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isFalse); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isSystem', () { + windowsFileSystem.setMetadata( + path, + isSystem: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isFalse); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isArchive', () { + windowsFileSystem.setMetadata( + path, + isArchive: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isTemporary', () { + windowsFileSystem.setMetadata( + path, + isTemporary: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isTrue); + }); + test('unset isContentNotIndexed', () { + windowsFileSystem.setMetadata( + path, + isContentNotIndexed: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isTrue); + }); + test('unset isOffline', () { + windowsFileSystem.setMetadata( + path, + isOffline: false, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isTrue); + expect(data.isSystem, isTrue); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isFalse); + }); + }); + + group('start with no file attributes set', () { + late String path; + late WindowsMetadata initialMetadata; + + setUp(() { + path = '$tmp/file1'; + File(path).writeAsStringSync('Hello World'); + windowsFileSystem.setMetadata( + path, + isReadOnly: false, + isHidden: false, + isSystem: false, + isArchive: false, + isTemporary: false, + isContentNotIndexed: false, + isOffline: false, + ); + initialMetadata = windowsFileSystem.metadata(path); + }); + + test('set none', () { + windowsFileSystem.setMetadata( + path, + original: includeOriginalMetadata ? initialMetadata : null, + ); + expect(windowsFileSystem.metadata(path), initialMetadata); + }); + test('set isReadOnly', () { + windowsFileSystem.setMetadata( + path, + isReadOnly: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isTrue); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + + test('set isHidden', () { + windowsFileSystem.setMetadata( + path, + isHidden: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isTrue); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isSystem', () { + windowsFileSystem.setMetadata( + path, + isSystem: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isTrue); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isArchive', () { + windowsFileSystem.setMetadata( + path, + isArchive: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isTrue); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isTemporary', () { + windowsFileSystem.setMetadata( + path, + isTemporary: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isTrue); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isFalse); + }); + test('set isContentNotIndexed', () { + windowsFileSystem.setMetadata( + path, + isContentNotIndexed: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isTrue); + expect(data.isOffline, isFalse); + }); + test('set isOffline', () { + windowsFileSystem.setMetadata( + path, + isOffline: true, + original: includeOriginalMetadata ? initialMetadata : null, + ); + + final data = windowsFileSystem.metadata(path); + expect(data.isReadOnly, isFalse); + expect(data.isHidden, isFalse); + expect(data.isSystem, isFalse); + expect(data.isArchive, isFalse); + expect(data.isTemporary, isFalse); + expect(data.isContentNotIndexed, isFalse); + expect(data.isOffline, isTrue); + }); + }); + }); + } }); } From ea0946fea73c761c919679b9e0ee1a2441224d6e Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 16 Apr 2025 16:33:11 -0700 Subject: [PATCH 38/39] Update vm_windows_file_system.dart --- .../lib/src/vm_windows_file_system.dart | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index cbd88221..3378a2f5 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -78,6 +78,8 @@ final class WindowsMetadata implements Metadata { // TODO(brianquinlan): Reoganize fields when the POSIX `metadata` is // available. // TODO(brianquinlan): Document the public fields. + + /// Will never have the `FILE_ATTRIBUTE_NORMAL` bit set. int _attributes; @override @@ -118,7 +120,25 @@ final class WindowsMetadata implements Metadata { ); /// TODO(bquinlan): Document this constructor. - factory WindowsMetadata.fromProperties({ + /// + /// Make sure to reference: + /// [File Attribute Constants](https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants) + factory WindowsMetadata.fromFileAttributes({ + int attributes = 0, + int size = 0, + int creationTime100Nanos = 0, + int lastAccessTime100Nanos = 0, + int lastWriteTime100Nanos = 0, + }) => WindowsMetadata._( + attributes == win32.FILE_ATTRIBUTE_NORMAL ? 0 : attributes, + size, + creationTime100Nanos, + lastAccessTime100Nanos, + lastWriteTime100Nanos, + ); + + /// TODO(bquinlan): Document this constructor. + factory WindowsMetadata.fromLogicalProperties({ bool isDirectory = false, bool isLink = false, @@ -296,22 +316,8 @@ base class WindowsFileSystem extends FileSystem { } final info = fileInfo.ref; final attributes = info.dwFileAttributes; - - final isDirectory = attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0; - final isLink = attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT != 0; - - return WindowsMetadata.fromProperties( - isReadOnly: attributes & win32.FILE_ATTRIBUTE_READONLY != 0, - isHidden: attributes & win32.FILE_ATTRIBUTE_HIDDEN != 0, - isSystem: attributes & win32.FILE_ATTRIBUTE_SYSTEM != 0, - isDirectory: isDirectory, - isArchive: attributes & win32.FILE_ATTRIBUTE_ARCHIVE != 0, - isTemporary: attributes & win32.FILE_ATTRIBUTE_TEMPORARY != 0, - isLink: isLink, - isOffline: attributes & win32.FILE_ATTRIBUTE_OFFLINE != 0, - isContentNotIndexed: - attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED != 0, - + return WindowsMetadata.fromFileAttributes( + attributes: attributes, size: info.nFileSizeHigh << 32 | info.nFileSizeLow, creationTime100Nanos: info.ftCreationTime.dwHighDateTime << 32 | From e28568d302e2a10381cff00c100349911b2ab02c Mon Sep 17 00:00:00 2001 From: Brian Quinlan Date: Wed, 16 Apr 2025 17:23:43 -0700 Subject: [PATCH 39/39] Fixes --- .../lib/src/vm_windows_file_system.dart | 35 ++++--- pkgs/io_file/test/metadata_windows_test.dart | 92 +++++++++---------- 2 files changed, 67 insertions(+), 60 deletions(-) diff --git a/pkgs/io_file/lib/src/vm_windows_file_system.dart b/pkgs/io_file/lib/src/vm_windows_file_system.dart index 3378a2f5..6f7acfad 100644 --- a/pkgs/io_file/lib/src/vm_windows_file_system.dart +++ b/pkgs/io_file/lib/src/vm_windows_file_system.dart @@ -97,11 +97,14 @@ final class WindowsMetadata implements Metadata { bool get isReadOnly => _attributes & win32.FILE_ATTRIBUTE_READONLY != 0; bool get isHidden => _attributes & win32.FILE_ATTRIBUTE_HIDDEN != 0; bool get isSystem => _attributes & win32.FILE_ATTRIBUTE_SYSTEM != 0; - bool get isArchive => _attributes & win32.FILE_ATTRIBUTE_ARCHIVE != 0; + + // TODO(brianquinlan): Refer to + // https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/windows-scripting/5tx15443(v=vs.84)?redirectedfrom=MSDN + bool get needsArchive => _attributes & win32.FILE_ATTRIBUTE_ARCHIVE != 0; bool get isTemporary => _attributes & win32.FILE_ATTRIBUTE_TEMPORARY != 0; bool get isOffline => _attributes & win32.FILE_ATTRIBUTE_OFFLINE != 0; - bool get isContentNotIndexed => - _attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED != 0; + bool get isContentIndexed => + _attributes & win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED == 0; final int creationTime100Nanos; final int lastAccessTime100Nanos; @@ -147,10 +150,10 @@ final class WindowsMetadata implements Metadata { bool isReadOnly = false, bool isHidden = false, bool isSystem = false, - bool isArchive = false, + bool needsArchive = false, bool isTemporary = false, bool isOffline = false, - bool isContentNotIndexed = false, + bool isContentIndexed = false, int creationTime100Nanos = 0, int lastAccessTime100Nanos = 0, @@ -161,10 +164,10 @@ final class WindowsMetadata implements Metadata { (isReadOnly ? win32.FILE_ATTRIBUTE_READONLY : 0) | (isHidden ? win32.FILE_ATTRIBUTE_HIDDEN : 0) | (isSystem ? win32.FILE_ATTRIBUTE_SYSTEM : 0) | - (isArchive ? win32.FILE_ATTRIBUTE_ARCHIVE : 0) | + (needsArchive ? win32.FILE_ATTRIBUTE_ARCHIVE : 0) | (isTemporary ? win32.FILE_ATTRIBUTE_TEMPORARY : 0) | (isOffline ? win32.FILE_ATTRIBUTE_OFFLINE : 0) | - (isContentNotIndexed ? win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED : 0), + (!isContentIndexed ? win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED : 0), size, creationTime100Nanos, lastAccessTime100Nanos, @@ -184,7 +187,7 @@ final class WindowsMetadata implements Metadata { int get hashCode => Object.hash( _attributes, size, - isContentNotIndexed, + isContentIndexed, creationTime100Nanos, lastAccessTime100Nanos, lastWriteTime100Nanos, @@ -226,18 +229,18 @@ base class WindowsFileSystem extends FileSystem { bool? isReadOnly, bool? isHidden, bool? isSystem, - bool? isArchive, + bool? needsArchive, bool? isTemporary, - bool? isContentNotIndexed, + bool? isContentIndexed, bool? isOffline, WindowsMetadata? original, }) => using((arena) { if ((isReadOnly ?? isHidden ?? isSystem ?? - isArchive ?? + needsArchive ?? isTemporary ?? - isContentNotIndexed ?? + isContentIndexed ?? isOffline) == null) { return; @@ -279,7 +282,11 @@ base class WindowsFileSystem extends FileSystem { ); attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_HIDDEN, isHidden); attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_SYSTEM, isSystem); - attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_ARCHIVE, isArchive); + attributes = updateBit( + attributes, + win32.FILE_ATTRIBUTE_ARCHIVE, + needsArchive, + ); attributes = updateBit( attributes, win32.FILE_ATTRIBUTE_TEMPORARY, @@ -288,7 +295,7 @@ base class WindowsFileSystem extends FileSystem { attributes = updateBit( attributes, win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, - isContentNotIndexed, + isContentIndexed != null ? !isContentIndexed : null, ); attributes = updateBit(attributes, win32.FILE_ATTRIBUTE_OFFLINE, isOffline); if (attributes == 0) { diff --git a/pkgs/io_file/test/metadata_windows_test.dart b/pkgs/io_file/test/metadata_windows_test.dart index d08eae26..7ba83920 100644 --- a/pkgs/io_file/test/metadata_windows_test.dart +++ b/pkgs/io_file/test/metadata_windows_test.dart @@ -76,22 +76,22 @@ void main() { }); }); - group('isArchive', () { + group('needsArchive', () { test('false', () { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); - windowsFileSystem.setMetadata(path, isArchive: false); + windowsFileSystem.setMetadata(path, needsArchive: false); final data = windowsFileSystem.metadata(path); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); }); test('true', () { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); - windowsFileSystem.setMetadata(path, isArchive: true); + windowsFileSystem.setMetadata(path, needsArchive: true); final data = windowsFileSystem.metadata(path); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); }); }); @@ -119,15 +119,15 @@ void main() { File(path).writeAsStringSync('Hello World'); final data = windowsFileSystem.metadata(path); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); }); test('true', () { final path = '$tmp/file1'; File(path).writeAsStringSync('Hello World'); - windowsFileSystem.setMetadata(path, isContentNotIndexed: true); + windowsFileSystem.setMetadata(path, isContentIndexed: true); final data = windowsFileSystem.metadata(path); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); }); }); @@ -220,9 +220,9 @@ void main() { isReadOnly: true, isHidden: true, isSystem: true, - isArchive: true, + needsArchive: true, isTemporary: true, - isContentNotIndexed: true, + isContentIndexed: true, isOffline: true, ); initialMetadata = windowsFileSystem.metadata(path); @@ -246,9 +246,9 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isTrue); expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isTrue); }); @@ -263,9 +263,9 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isFalse); expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isTrue); }); test('unset isSystem', () { @@ -279,15 +279,15 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isTrue); expect(data.isSystem, isFalse); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isTrue); }); - test('unset isArchive', () { + test('unset needsArchive', () { windowsFileSystem.setMetadata( path, - isArchive: false, + needsArchive: false, original: includeOriginalMetadata ? initialMetadata : null, ); @@ -295,9 +295,9 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isTrue); expect(data.isSystem, isTrue); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isTrue); }); test('unset isTemporary', () { @@ -311,15 +311,15 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isTrue); expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isTrue); }); test('unset isContentNotIndexed', () { windowsFileSystem.setMetadata( path, - isContentNotIndexed: false, + isContentIndexed: false, original: includeOriginalMetadata ? initialMetadata : null, ); @@ -327,9 +327,9 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isTrue); expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isTrue); }); test('unset isOffline', () { @@ -343,9 +343,9 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isTrue); expect(data.isSystem, isTrue); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isFalse); }); }); @@ -362,9 +362,9 @@ void main() { isReadOnly: false, isHidden: false, isSystem: false, - isArchive: false, + needsArchive: false, isTemporary: false, - isContentNotIndexed: false, + isContentIndexed: false, isOffline: false, ); initialMetadata = windowsFileSystem.metadata(path); @@ -388,9 +388,9 @@ void main() { expect(data.isReadOnly, isTrue); expect(data.isHidden, isFalse); expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isFalse); }); @@ -405,9 +405,9 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isTrue); expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isFalse); }); test('set isSystem', () { @@ -421,15 +421,15 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isFalse); expect(data.isSystem, isTrue); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isFalse); }); - test('set isArchive', () { + test('set needsArchive', () { windowsFileSystem.setMetadata( path, - isArchive: true, + needsArchive: true, original: includeOriginalMetadata ? initialMetadata : null, ); @@ -437,9 +437,9 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isFalse); expect(data.isSystem, isFalse); - expect(data.isArchive, isTrue); + expect(data.needsArchive, isTrue); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isFalse); }); test('set isTemporary', () { @@ -453,15 +453,15 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isFalse); expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isTrue); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isFalse); }); test('set isContentNotIndexed', () { windowsFileSystem.setMetadata( path, - isContentNotIndexed: true, + isContentIndexed: true, original: includeOriginalMetadata ? initialMetadata : null, ); @@ -469,9 +469,9 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isFalse); expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isTrue); + expect(data.isContentIndexed, isTrue); expect(data.isOffline, isFalse); }); test('set isOffline', () { @@ -485,9 +485,9 @@ void main() { expect(data.isReadOnly, isFalse); expect(data.isHidden, isFalse); expect(data.isSystem, isFalse); - expect(data.isArchive, isFalse); + expect(data.needsArchive, isFalse); expect(data.isTemporary, isFalse); - expect(data.isContentNotIndexed, isFalse); + expect(data.isContentIndexed, isFalse); expect(data.isOffline, isTrue); }); });