diff --git a/CHANGELOG.md b/CHANGELOG.md index dfacb5c..92bcc47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,10 @@ ## 0.0.2 * fix: fix headers in code example sections + +## 0.1.0 + +* feat: Support for Android platform. Methods `getFileInfo` and `getFileIconInfo` now work on Android +* feat: `AndroidFileAttributes` enum for Android-specific file permissions +* refactor: Renamed `attributes` to `winAttributes` in `FileMetadata` +* feat: Added `androidAttributes` for Android-specific attributes in `FileMetadata` diff --git a/README.md b/README.md index c0b65da..2d051a1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A Flutter plugin for retrieving detailed file metadata, including native icons. | API | Android | iOS | Linux | macOS | Windows | Web | | --------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | | getFileIconInfo() | :heavy_check_mark: | :x: | :x: | :x: | :heavy_check_mark: | :x: | -| getFileInfo() | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | +| getFileInfo() | :heavy_check_mark: | :x: | :x: | :x: | :heavy_check_mark: | :x: | ## Getting Started @@ -59,6 +59,10 @@ if (_fileMetatdata != null) { ![windows_example](https://i.imgur.com/Yo0GhFM.gif) +### Android + +![android_example](https://i.imgur.com/EKQ3WDK.gif) + ## Contributing If you would like to contribute to the development of this plugin, please fork the repository and submit a pull request. For detailed contribution guidelines, please refer to the CONTRIBUTING.md file. diff --git a/android/src/main/kotlin/com/ecoala/flutter_file_info/FileMetadata.kt b/android/src/main/kotlin/com/ecoala/flutter_file_info/FileMetadata.kt new file mode 100644 index 0000000..c6b5263 --- /dev/null +++ b/android/src/main/kotlin/com/ecoala/flutter_file_info/FileMetadata.kt @@ -0,0 +1,31 @@ +package com.ecoala.flutter_file_info + +import java.nio.file.attribute.PosixFilePermission +import java.util.Date + +/** + * Represents metadata information about a file. + * + * @property filePath The path of the file. + * @property fileName The name of the file. + * @property fileExtension The extension of the file. + * @property fileType The type of the file. + * @property creationTime The creation time of the file. + * @property modifiedTime The last modified time of the file. + * @property accessedTime The last accessed time of the file. + * @property sizeBytes The size of the file in bytes. + * @property fileSize The size of the file as a human-readable string. + * @property attributes The set of POSIX file permissions for the file. + */ +data class FileMetadata( + val filePath: String, + val fileName: String?, + val fileExtension: String?, + val fileType: String?, + val creationTime: Date?, + val modifiedTime: Date?, + val accessedTime: Date?, + val sizeBytes: Long?, + val fileSize: String?, + val attributes: Set? +) diff --git a/android/src/main/kotlin/com/ecoala/flutter_file_info/FileMetadataProvider.kt b/android/src/main/kotlin/com/ecoala/flutter_file_info/FileMetadataProvider.kt new file mode 100644 index 0000000..3a95866 --- /dev/null +++ b/android/src/main/kotlin/com/ecoala/flutter_file_info/FileMetadataProvider.kt @@ -0,0 +1,92 @@ +package com.ecoala.flutter_file_info + +import android.annotation.TargetApi +import android.os.Build +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.attribute.PosixFilePermission +import java.text.DecimalFormat +import java.util.* + +/** + * This class is responsible for providing file metadata. + */ +class FileMetadataProvider { + + /** + * Retrieves the metadata of a file specified by the given file path. + * + * @param filePath The path of the file. + * @return The metadata of the file. + */ + @TargetApi(Build.VERSION_CODES.O) + fun getFileMetadata(filePath: String): FileMetadata { + val file = File(filePath) + val path = Paths.get(filePath) + val attributes = Files.readAttributes(path, BasicFileAttributes::class.java) + val posixAttributes: Set = try { + Files.getPosixFilePermissions(path) + } catch (e: UnsupportedOperationException) { + emptySet() + } + + val fileName = file.name + val fileExtension = file.extension.takeIf { it.isNotEmpty() } + val fileType = Files.probeContentType(path) + val creationTime = Date(attributes.creationTime().toMillis()) + val modifiedTime = Date(attributes.lastModifiedTime().toMillis()) + val accessedTime = Date(attributes.lastAccessTime().toMillis()) + val sizeBytes = attributes.size() + val fileSize = humanReadableByteCountSI(sizeBytes) + + return FileMetadata( + filePath = filePath, + fileName = fileName, + fileExtension = fileExtension, + fileType = fileType, + creationTime = creationTime, + modifiedTime = modifiedTime, + accessedTime = accessedTime, + sizeBytes = sizeBytes, + fileSize = fileSize, + attributes = posixAttributes + ) + } + + /** + * Converts the given [FileMetadata] object to a map representation. + * + * @param metadata The [FileMetadata] object to convert. + * @return A map containing the metadata of the file. + */ + fun fileMetadataToMap(metadata: FileMetadata): Map { + return mapOf( + "filePath" to metadata.filePath, + "fileName" to metadata.fileName, + "fileExtension" to metadata.fileExtension, + "fileType" to metadata.fileType, + "creationTime" to metadata.creationTime?.time, + "modifiedTime" to metadata.modifiedTime?.time, + "accessedTime" to metadata.accessedTime?.time, + "sizeBytes" to metadata.sizeBytes, + "fileSize" to metadata.fileSize, + "androidAttributes" to metadata.attributes?.map { it.name } + ) + } + + /** + * Converts the given number of bytes into a human-readable string representation using the International System of Units (SI). + * + * @param bytes The number of bytes to convert. + * @return A string representation of the given number of bytes in a human-readable format. + */ + private fun humanReadableByteCountSI(bytes: Long): String { + val unit = 1000 + if (bytes < unit) return "$bytes B" + val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt() + val pre = "kMGTPE"[exp - 1].toString() + return DecimalFormat("#,##0.#").format(bytes / Math.pow(unit.toDouble(), exp.toDouble())) + " " + pre + "B" + } +} diff --git a/android/src/main/kotlin/com/ecoala/flutter_file_info/FlutterFileInfoPlugin.kt b/android/src/main/kotlin/com/ecoala/flutter_file_info/FlutterFileInfoPlugin.kt index bfdc85e..568e133 100644 --- a/android/src/main/kotlin/com/ecoala/flutter_file_info/FlutterFileInfoPlugin.kt +++ b/android/src/main/kotlin/com/ecoala/flutter_file_info/FlutterFileInfoPlugin.kt @@ -1,10 +1,9 @@ package com.ecoala.flutter_file_info import com.ecoala.flutter_file_info.FileIconProvider - +import com.ecoala.flutter_file_info.FileMetadataProvider import androidx.annotation.NonNull - import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -16,26 +15,37 @@ import android.util.Base64 * FlutterFileInfoPlugin class is responsible for handling method calls from Flutter and implementing the FlutterPlugin interface. */ class FlutterFileInfoPlugin: FlutterPlugin, MethodCallHandler { - private lateinit var channel : MethodChannel + private lateinit var channel: MethodChannel private lateinit var fileIconProvider: FileIconProvider + private lateinit var fileMetadataProvider: FileMetadataProvider override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_file_info") channel.setMethodCallHandler(this) fileIconProvider = FileIconProvider(flutterPluginBinding.applicationContext, flutterPluginBinding.applicationContext.packageManager) + fileMetadataProvider = FileMetadataProvider() } override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "getFileIcon") { - val filePath = call.argument("filePath") - if (filePath != null) { - handleGetFileIcon(filePath, result) - } else { - result.error("INVALID_ARGUMENT", "File path is required", null) + when (call.method) { + "getFileIcon" -> { + val filePath = call.argument("filePath") + if (filePath != null) { + handleGetFileIcon(filePath, result) + } else { + result.error("INVALID_ARGUMENT", "File path is required", null) + } } - } else { - result.notImplemented() + "getFileInfo" -> { + val filePath = call.argument("filePath") + if (filePath != null) { + handleGetFileInfo(filePath, result) + } else { + result.error("INVALID_ARGUMENT", "File path is required", null) + } + } + else -> result.notImplemented() } } @@ -52,9 +62,9 @@ class FlutterFileInfoPlugin: FlutterPlugin, MethodCallHandler { val encodedImage = Base64.encodeToString(pixelData, Base64.DEFAULT) val iconData = mapOf( - "pixelData" to encodedImage, - "width" to width, - "height" to height + "pixelData" to encodedImage, + "width" to width, + "height" to height ) result.success(iconData) } else { @@ -62,6 +72,23 @@ class FlutterFileInfoPlugin: FlutterPlugin, MethodCallHandler { } } + /** + * Handles the retrieval of file metadata for the specified file path. + * + * @param filePath The path of the file for which to retrieve the metadata. + * @param result The result object used to communicate the file metadata back to the caller. + */ + private fun handleGetFileInfo(filePath: String, result: Result) { + try { + val metadata = fileMetadataProvider.getFileMetadata(filePath) + val metadataMap = fileMetadataProvider.fileMetadataToMap(metadata) + + result.success(metadataMap) + } catch (e: Exception) { + result.error("FILE_ERROR", "Failed to retrieve file metadata: ${e.message}", null) + } + } + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } diff --git a/example/lib/file_metadata_table.dart b/example/lib/file_metadata_table.dart index f57d894..d89b10f 100644 --- a/example/lib/file_metadata_table.dart +++ b/example/lib/file_metadata_table.dart @@ -27,8 +27,20 @@ class FileMetadataTable extends StatelessWidget { ); } + String? _formatAttributes(FileMetadata? mtadata, TargetPlatform platform) { + if (platform == TargetPlatform.windows) { + return mtadata?.winAttributes?.map((e) => e.name).join(', '); + } else if (platform == TargetPlatform.android) { + return mtadata?.androidAttributes?.map((e) => e.name).join(', '); + } else { + return null; + } + } + @override Widget build(BuildContext context) { + final TargetPlatform platform = Theme.of(context).platform; + return Padding( padding: const EdgeInsets.all(16.0), child: Table( @@ -46,7 +58,10 @@ class FileMetadataTable extends StatelessWidget { isHeader: true), _buildRow('Size (Bytes)', fileMetadata?.sizeBytes?.toString()), _buildRow('File Size', fileMetadata?.fileSize, isHeader: true), - _buildRow("Attributes", fileMetadata?.attributes?.toString()), + _buildRow( + "Attributes", + _formatAttributes(fileMetadata, platform), + ), ], ), ); diff --git a/lib/flutter_file_info.dart b/lib/flutter_file_info.dart index d8ca22f..ea5e7b1 100644 --- a/lib/flutter_file_info.dart +++ b/lib/flutter_file_info.dart @@ -4,7 +4,8 @@ export 'src/file_info.dart'; export 'src/models/icon_info.module.dart'; export 'src/models/file_metadata.module.dart'; -export 'src/windows/enum/file_attribiutes.dart'; +export 'src/windows/enum/windows_file_attribiutes.dart'; +export 'src/android/enum/android_file_attribiutes.dart'; export 'src/android/file_info_android.dart' show FileInfoAndroid; export 'src/windows/file_info_windows.dart' show FileInfoWindows; diff --git a/lib/src/android/android_method_channel.dart b/lib/src/android/android_method_channel.dart index 9682660..92335de 100644 --- a/lib/src/android/android_method_channel.dart +++ b/lib/src/android/android_method_channel.dart @@ -21,11 +21,20 @@ abstract class AndroidMethodChannel { /// /// Returns a [Future] that completes with an [IconInfo] object containing the icon information. Future getFileIcon(String filePath); + + /// Retrieves the file metadata for a file located at the specified [filePath]. + /// + /// Returns a [Future] that completes with a [FileMetadata] object containing the file metadata. + Future getFileInfo(String filePath); } class AndroidMethodChannelImpl implements AndroidMethodChannel { + AndroidMethodChannelImpl({MethodChannel? methodChannel}) + : methodChannel = + methodChannel ?? const MethodChannel('flutter_file_info'); + @visibleForTesting - final methodChannel = const MethodChannel('flutter_file_info'); + final MethodChannel methodChannel; @override Future getFileIcon(String filePath) async { @@ -54,4 +63,35 @@ class AndroidMethodChannelImpl implements AndroidMethodChannel { 'height': height, }); } + + @override + Future getFileInfo(String filePath) async { + final result = + await methodChannel.invokeMethod('getFileInfo', {'filePath': filePath}); + + final Map data = Map.from(result); + + return FileMetadata( + filePath: data['filePath'] as String, + fileName: data['fileName'] as String?, + fileExtension: data['fileExtension'] as String?, + fileType: data['fileType'] as String?, + creationTime: data['creationTime'] != null + ? DateTime.fromMillisecondsSinceEpoch(data['creationTime'] as int) + : null, + modifiedTime: data['modifiedTime'] != null + ? DateTime.fromMillisecondsSinceEpoch(data['modifiedTime'] as int) + : null, + accessedTime: data['accessedTime'] != null + ? DateTime.fromMillisecondsSinceEpoch(data['accessedTime'] as int) + : null, + sizeBytes: data['sizeBytes'] as int?, + fileSize: data['fileSize'] as String?, + androidAttributes: + AndroidFileAttributesUtility.parseAndroidFileAttributes( + List.from(data['androidAttributes'] as List)) + .toList(), + // Note: winAttributes is not being set here. You may need to handle it similarly if needed. + ); + } } diff --git a/lib/src/android/enum/android_file_attribiutes.dart b/lib/src/android/enum/android_file_attribiutes.dart new file mode 100644 index 0000000..07fb3ca --- /dev/null +++ b/lib/src/android/enum/android_file_attribiutes.dart @@ -0,0 +1,102 @@ +// android_file_attributes.dart + +// ignore_for_file: constant_identifier_names + +/// Enumeration representing file attributes specific to the Android operating system. +/// +/// This enum provides a set of constants that describe various permissions associated with files and directories on Android. +/// +/// Usage: +/// ```dart +/// final attribute = AndroidFileAttributes.ownerWrite; +/// print('Attribute: $attribute'); +/// ``` +enum AndroidFileAttributes { + /// Execute/search permission, group. + GROUP_EXECUTE, + + /// Read permission, group. + GROUP_READ, + + /// Write permission, group. + GROUP_WRITE, + + /// Execute/search permission, others. + OTHERS_EXECUTE, + + /// Read permission, others. + OTHERS_READ, + + /// Write permission, others. + OTHERS_WRITE, + + /// Execute/search permission, owner. + OWNER_EXECUTE, + + /// Read permission, owner. + OWNER_READ, + + /// Write permission, owner. + OWNER_WRITE +} + +/// Utility class for handling Android file attributes. +/// +/// This class provides methods for working with Android file attributes, including converting +/// attribute names from strings to their corresponding enum values. +/// +/// Example usage: +/// ```dart +/// List attributeNames = ['OWNER_WRITE', 'GROUP_READ']; +/// Set attributes = AndroidFileAttributesUtility.parseAndroidFileAttributes(attributeNames); +/// print(attributes); // Output: {AndroidFileAttributes.OWNER_WRITE, AndroidFileAttributes.GROUP_READ} +/// ``` +class AndroidFileAttributesUtility { + /// Converts a list of attribute names in string format to a set of [AndroidFileAttributes]. + /// + /// This method takes a list of attribute names as strings and converts each string to its corresponding + /// [AndroidFileAttributes] enum value. If a string does not match any of the defined attributes, it is ignored. + /// + /// [attributeNames] is a list of attribute names in string format. Each string should match one of the + /// enum names defined in [AndroidFileAttributes]. + /// + /// Returns a set of [AndroidFileAttributes] corresponding to the provided attribute names. The result is + /// a set containing only valid attributes that were found in the input list. + /// + /// Example usage: + /// ```dart + /// List names = ['OWNER_WRITE', 'GROUP_READ', 'INVALID']; + /// Set attributes = AndroidFileAttributesUtility.parseAndroidFileAttributes(names); + /// print(attributes); // Output: [AndroidFileAttributes.OWNER_WRITE, AndroidFileAttributes.GROUP_READ] + /// ``` + static Set parseAndroidFileAttributes( + List attributeNames) { + return attributeNames + .map((name) { + switch (name) { + case 'GROUP_EXECUTE': + return AndroidFileAttributes.GROUP_EXECUTE; + case 'GROUP_READ': + return AndroidFileAttributes.GROUP_READ; + case 'GROUP_WRITE': + return AndroidFileAttributes.GROUP_WRITE; + case 'OTHERS_EXECUTE': + return AndroidFileAttributes.OTHERS_EXECUTE; + case 'OTHERS_READ': + return AndroidFileAttributes.OTHERS_READ; + case 'OTHERS_WRITE': + return AndroidFileAttributes.OTHERS_WRITE; + case 'OWNER_EXECUTE': + return AndroidFileAttributes.OWNER_EXECUTE; + case 'OWNER_READ': + return AndroidFileAttributes.OWNER_READ; + case 'OWNER_WRITE': + return AndroidFileAttributes.OWNER_WRITE; + default: + return null; + } + }) + .whereType() + .toSet(); + } +} diff --git a/lib/src/android/file_info_android.dart b/lib/src/android/file_info_android.dart index da33f61..6059348 100644 --- a/lib/src/android/file_info_android.dart +++ b/lib/src/android/file_info_android.dart @@ -19,6 +19,7 @@ class FileInfoAndroid extends FileInfo { } @override - Future getFileInfo(String filePath) => - throw UnimplementedError('getFileInfo() has not been implemented.'); + Future getFileInfo(String filePath) async { + return await _androidMethodChannel.getFileInfo(filePath); + } } diff --git a/lib/src/models/file_metadata.module.dart b/lib/src/models/file_metadata.module.dart index 203dd11..02b5f9b 100644 --- a/lib/src/models/file_metadata.module.dart +++ b/lib/src/models/file_metadata.module.dart @@ -32,10 +32,19 @@ class FileMetadata extends Equatable { /// The Windows file attributes represented as a numerical bitmask. final int? dwFileAttributes; - /// The list of [FileAttributes] representing the file's attributes. - final List? attributes; + /// The list of Windows file attributes. + final List? winAttributes; - /// Creates a new instance of [FileMetadata] with the specified parameters. + /// The list of Android file attributes. + final List? androidAttributes; + + /// Represents the metadata of a file. + /// + /// This class contains information about the attributes of a file. + /// The [winAttributes] property is a list of [WindowsFileAttributes] that represents + /// the Windows-specific attributes of the file. + /// The [androidAttributes] property is a list of [AndroidFileAttributes] that represents + /// the Android-specific attributes of the file. const FileMetadata({ required this.filePath, this.fileName, @@ -47,12 +56,13 @@ class FileMetadata extends Equatable { this.sizeBytes, this.fileSize, this.dwFileAttributes, - this.attributes, + this.winAttributes, + this.androidAttributes, }); /// Returns a string representation of the [FileMetadata] object. @override - String toString() => 'FileInfo(' + String toString() => 'FileMetadata(' 'filePath: $filePath, ' 'fileName: $fileName, ' 'fileExtension: $fileExtension, ' @@ -63,7 +73,8 @@ class FileMetadata extends Equatable { 'sizeBytes: $sizeBytes, ' 'fileSize: $fileSize, ' 'dwFileAttributes: $dwFileAttributes, ' - 'attributes: $attributes)'; + 'winAttributes: $winAttributes, ' + 'androidAttributes: $androidAttributes)'; /// Creates a new [FileMetadata] object with updated values based on the provided parameters. /// @@ -79,7 +90,8 @@ class FileMetadata extends Equatable { int? sizeBytes, String? fileSize, int? dwFileAttributes, - List? attributes, + List? winAttributes, + List? androidAttributes, }) { return FileMetadata( filePath: filePath ?? this.filePath, @@ -92,7 +104,8 @@ class FileMetadata extends Equatable { sizeBytes: sizeBytes ?? this.sizeBytes, fileSize: fileSize ?? this.fileSize, dwFileAttributes: dwFileAttributes ?? this.dwFileAttributes, - attributes: attributes ?? this.attributes, + winAttributes: winAttributes ?? this.winAttributes, + androidAttributes: androidAttributes ?? this.androidAttributes, ); } @@ -109,6 +122,7 @@ class FileMetadata extends Equatable { sizeBytes, fileSize, dwFileAttributes, - attributes, + winAttributes, + androidAttributes, ]; } diff --git a/lib/src/windows/enum/file_attribiutes.dart b/lib/src/windows/enum/windows_file_attribiutes.dart similarity index 73% rename from lib/src/windows/enum/file_attribiutes.dart rename to lib/src/windows/enum/windows_file_attribiutes.dart index 6a2337b..d53d710 100644 --- a/lib/src/windows/enum/file_attribiutes.dart +++ b/lib/src/windows/enum/windows_file_attribiutes.dart @@ -2,18 +2,18 @@ import 'package:win32/win32.dart'; -/// Enumeration representing file attributes commonly used in Windows systems. +/// Enumeration representing file attributes specific to the Windows operating system. /// -/// This enum provides a set of constants that describe various attributes associated with files and directories on the Windows operating system. +/// This enum provides a set of constants that describe various attributes associated with files and directories on Windows. /// Each constant represents a specific attribute, such as read-only, hidden, system, etc. /// /// Usage: /// ```dart -/// FileAttributes attribute = FileAttributes.READ_ONLY; +/// WindowsFileAttributes attribute = WindowsFileAttributes.READ_ONLY; /// print('Value of READ_ONLY attribute: ${attribute.value}'); /// ``` -enum FileAttributes { - /// A file that is marked as read-only. Applications can read the file, but cannot write to it or delete it. +enum WindowsFileAttributes { + /// A file that is marked as read-only. Applications can read the file but cannot write to it or delete it. READ_ONLY, /// The file or directory is marked as hidden and is not included in an ordinary directory listing. @@ -74,53 +74,55 @@ enum FileAttributes { RECALL_ON_DATA_ACCESS, } -/// Extension methods for the [FileAttributes] enum. +/// Extension methods for the [WindowsFileAttributes] enum. +/// +/// This extension provides methods to get the corresponding numerical values for the file attributes defined in the [WindowsFileAttributes] enum. /// /// See also: /// - [File Attribute Constants](https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants) -extension FileAttributesExtension on FileAttributes { +extension WindowsFileAttributesExtension on WindowsFileAttributes { /// Returns the corresponding numerical value for the Windows file attribute. int get value { switch (this) { - case FileAttributes.READ_ONLY: + case WindowsFileAttributes.READ_ONLY: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY; - case FileAttributes.HIDDEN: + case WindowsFileAttributes.HIDDEN: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_HIDDEN; - case FileAttributes.SYSTEM: + case WindowsFileAttributes.SYSTEM: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_SYSTEM; - case FileAttributes.DIRECTORY: + case WindowsFileAttributes.DIRECTORY: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DIRECTORY; - case FileAttributes.ARCHIVE: + case WindowsFileAttributes.ARCHIVE: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_ARCHIVE; - case FileAttributes.DEVICE: + case WindowsFileAttributes.DEVICE: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DEVICE; - case FileAttributes.NORMAL: + case WindowsFileAttributes.NORMAL: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL; - case FileAttributes.TEMPORARY: + case WindowsFileAttributes.TEMPORARY: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_TEMPORARY; - case FileAttributes.SPARSE_FILE: + case WindowsFileAttributes.SPARSE_FILE: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_SPARSE_FILE; - case FileAttributes.REPARSE_POINT: + case WindowsFileAttributes.REPARSE_POINT: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_REPARSE_POINT; - case FileAttributes.COMPRESSED: + case WindowsFileAttributes.COMPRESSED: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_COMPRESSED; - case FileAttributes.OFFLINE: + case WindowsFileAttributes.OFFLINE: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_OFFLINE; - case FileAttributes.NOT_CONTENT_INDEXED: + case WindowsFileAttributes.NOT_CONTENT_INDEXED: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; - case FileAttributes.ENCRYPTED: + case WindowsFileAttributes.ENCRYPTED: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_ENCRYPTED; - case FileAttributes.VIRTUAL: + case WindowsFileAttributes.VIRTUAL: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_VIRTUAL; - case FileAttributes.EA: + case WindowsFileAttributes.EA: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_EA; - case FileAttributes.PINNED: + case WindowsFileAttributes.PINNED: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_PINNED; - case FileAttributes.UNPINNED: + case WindowsFileAttributes.UNPINNED: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_UNPINNED; - case FileAttributes.RECALL_ON_OPEN: + case WindowsFileAttributes.RECALL_ON_OPEN: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_RECALL_ON_OPEN; - case FileAttributes.RECALL_ON_DATA_ACCESS: + case WindowsFileAttributes.RECALL_ON_DATA_ACCESS: return FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS; } } diff --git a/lib/src/windows/file_info_windows.dart b/lib/src/windows/file_info_windows.dart index 2557274..d7d7c7e 100644 --- a/lib/src/windows/file_info_windows.dart +++ b/lib/src/windows/file_info_windows.dart @@ -231,7 +231,7 @@ class FileInfoWindows extends FileInfo { sizeBytes: findData.ref.nFileSizeLow, fileSize: fileSize, dwFileAttributes: findData.ref.dwFileAttributes, - attributes: attributes, + winAttributes: attributes, ); } } diff --git a/lib/src/windows/windows_utils.dart b/lib/src/windows/windows_utils.dart index ec3007c..cff2b52 100644 --- a/lib/src/windows/windows_utils.dart +++ b/lib/src/windows/windows_utils.dart @@ -1,7 +1,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import 'package:flutter_file_info/src/windows/enum/file_attribiutes.dart'; +import 'package:flutter_file_info/src/windows/enum/windows_file_attribiutes.dart'; import 'package:win32/win32.dart'; abstract class WindowsUtils { @@ -48,10 +48,10 @@ abstract class WindowsUtils { /// - [FILETIME documentation](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime) DateTime convertFileTimeToDateTime(FILETIME fileTime); - /// Converts a numerical attribute mask into a list of [FileAttributes]. + /// Converts a numerical attribute mask into a list of [WindowsFileAttributes]. /// - /// This function takes a numerical attribute mask as input and converts it into a list of [FileAttributes], - /// representing the individual attributes that are set in the mask. Each attribute in the [FileAttributes] enum + /// This function takes a numerical attribute mask as input and converts it into a list of [WindowsFileAttributes], + /// representing the individual attributes that are set in the mask. Each attribute in the [WindowsFileAttributes] enum /// corresponds to a specific bit in the attribute mask. /// /// Example usage: @@ -63,7 +63,7 @@ abstract class WindowsUtils { /// /// Note: This function is useful for extracting individual attributes from a numerical bitmask, providing a more /// convenient representation for understanding the file's characteristics. - List getFileAttributesFromMask(int attributeMask); + List getFileAttributesFromMask(int attributeMask); /// Formats a file size in bytes into a human-readable string representation. /// @@ -112,10 +112,10 @@ class WindowsUtilsImpl implements WindowsUtils { } @override - List getFileAttributesFromMask(int attributeMask) { - List attributes = []; + List getFileAttributesFromMask(int attributeMask) { + List attributes = []; - for (var attr in FileAttributes.values) { + for (var attr in WindowsFileAttributes.values) { if (attributeMask & attr.value != 0) { attributes.add(attr); } diff --git a/pubspec.yaml b/pubspec.yaml index 2c535c2..3e3983e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_file_info description: "A Flutter plugin for retrieving detailed file metadata, including native icons." -version: 0.0.2 +version: 0.1.0 homepage: https://github.com/makjac/flutter_file_info repository: https://github.com/makjac/flutter_file_info issue_tracker: https://github.com/makjac/flutter_file_info/issues diff --git a/test/src/android/android_method_channel_test.dart b/test/src/android/android_method_channel_test.dart new file mode 100644 index 0000000..b11ce2e --- /dev/null +++ b/test/src/android/android_method_channel_test.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:flutter_file_info/flutter_file_info.dart'; +import 'package:flutter_file_info/src/android/android_method_channel.dart'; + +import 'android_method_channel_test.mocks.dart'; + +// Generowanie mocków +@GenerateMocks([MethodChannel]) +void main() { + // Mocks + late MockMethodChannel mockMethodChannel; + late AndroidMethodChannelImpl androidMethodChannelImpl; + + setUp(() { + mockMethodChannel = MockMethodChannel(); + androidMethodChannelImpl = + AndroidMethodChannelImpl(methodChannel: mockMethodChannel); + }); + + group('AndroidMethodChannelImpl', () { + test('getFileIcon returns correct IconInfo', () async { + // Przygotowanie danych + const filePath = 'test/path'; + final pixelData = base64.encode(Uint8List.fromList([0, 1, 2, 3])); + final result = { + 'pixelData': pixelData, + 'width': 100.0, + 'height': 200.0, + }; + + // Mockowanie odpowiedzi + when(mockMethodChannel + .invokeMethod('getFileIcon', {'filePath': filePath})) + .thenAnswer((_) async => result); + + // Wywołanie metody + final iconInfo = await androidMethodChannelImpl.getFileIcon(filePath); + + // Sprawdzanie wyników + expect(iconInfo.pixelData, Uint8List.fromList([0, 1, 2, 3])); + expect(iconInfo.width, 100.0); + expect(iconInfo.height, 200.0); + }); + + test('getFileIcon throws an exception on invalid base64', () async { + // Przygotowanie danych + const filePath = 'test/path'; + final result = { + 'pixelData': 'invalid_base64', + 'width': 100.0, + 'height': 200.0, + }; + + // Mockowanie odpowiedzi + when(mockMethodChannel + .invokeMethod('getFileIcon', {'filePath': filePath})) + .thenAnswer((_) async => result); + + // Wywołanie metody i sprawdzanie, czy rzuca wyjątek + expect( + () async => await androidMethodChannelImpl.getFileIcon(filePath), + throwsA(isA()), + ); + }); + + test('getFileInfo returns correct FileMetadata', () async { + // Przygotowanie danych + const filePath = 'test/path'; + final result = { + 'filePath': filePath, + 'fileName': 'testFile', + 'fileExtension': 'txt', + 'fileType': 'Text Document', + 'creationTime': 1633036800000, + 'modifiedTime': 1633123200000, + 'accessedTime': 1633209600000, + 'sizeBytes': 12345, + 'fileSize': '12.3 KB', + 'androidAttributes': [ + 'OWNER_WRITE', + 'GROUP_READ', + ], + }; + + // Mockowanie odpowiedzi + when(mockMethodChannel + .invokeMethod('getFileInfo', {'filePath': filePath})) + .thenAnswer((_) async => result); + + // Wywołanie metody + final fileMetadata = await androidMethodChannelImpl.getFileInfo(filePath); + + // Sprawdzanie wyników + expect(fileMetadata.filePath, filePath); + expect(fileMetadata.fileName, 'testFile'); + expect(fileMetadata.fileExtension, 'txt'); + expect(fileMetadata.fileType, 'Text Document'); + expect(fileMetadata.creationTime, + DateTime.fromMillisecondsSinceEpoch(1633036800000)); + expect(fileMetadata.modifiedTime, + DateTime.fromMillisecondsSinceEpoch(1633123200000)); + expect(fileMetadata.accessedTime, + DateTime.fromMillisecondsSinceEpoch(1633209600000)); + expect(fileMetadata.sizeBytes, 12345); + expect(fileMetadata.fileSize, '12.3 KB'); + expect(fileMetadata.androidAttributes, [ + AndroidFileAttributes.OWNER_WRITE, + AndroidFileAttributes.GROUP_READ, + ]); + }); + }); +} diff --git a/test/src/android/android_method_channel_test.mocks.dart b/test/src/android/android_method_channel_test.mocks.dart new file mode 100644 index 0000000..b3a7976 --- /dev/null +++ b/test/src/android/android_method_channel_test.mocks.dart @@ -0,0 +1,141 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in flutter_file_info/test/src/android/android_method_channel_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i6; + +import 'package:flutter/src/services/binary_messenger.dart' as _i3; +import 'package:flutter/src/services/message_codec.dart' as _i2; +import 'package:flutter/src/services/platform_channel.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMethodCodec_0 extends _i1.SmartFake implements _i2.MethodCodec { + _FakeMethodCodec_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBinaryMessenger_1 extends _i1.SmartFake + implements _i3.BinaryMessenger { + _FakeBinaryMessenger_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [MethodChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMethodChannel extends _i1.Mock implements _i4.MethodChannel { + MockMethodChannel() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + _i2.MethodCodec get codec => (super.noSuchMethod( + Invocation.getter(#codec), + returnValue: _FakeMethodCodec_0( + this, + Invocation.getter(#codec), + ), + ) as _i2.MethodCodec); + + @override + _i3.BinaryMessenger get binaryMessenger => (super.noSuchMethod( + Invocation.getter(#binaryMessenger), + returnValue: _FakeBinaryMessenger_1( + this, + Invocation.getter(#binaryMessenger), + ), + ) as _i3.BinaryMessenger); + + @override + _i6.Future invokeMethod( + String? method, [ + dynamic arguments, + ]) => + (super.noSuchMethod( + Invocation.method( + #invokeMethod, + [ + method, + arguments, + ], + ), + returnValue: _i6.Future.value(), + ) as _i6.Future); + + @override + _i6.Future?> invokeListMethod( + String? method, [ + dynamic arguments, + ]) => + (super.noSuchMethod( + Invocation.method( + #invokeListMethod, + [ + method, + arguments, + ], + ), + returnValue: _i6.Future?>.value(), + ) as _i6.Future?>); + + @override + _i6.Future?> invokeMapMethod( + String? method, [ + dynamic arguments, + ]) => + (super.noSuchMethod( + Invocation.method( + #invokeMapMethod, + [ + method, + arguments, + ], + ), + returnValue: _i6.Future?>.value(), + ) as _i6.Future?>); + + @override + void setMethodCallHandler( + _i6.Future Function(_i2.MethodCall)? handler) => + super.noSuchMethod( + Invocation.method( + #setMethodCallHandler, + [handler], + ), + returnValueForMissingStub: null, + ); +} diff --git a/test/src/android/enum/android_file_attribiutes_test.dart b/test/src/android/enum/android_file_attribiutes_test.dart new file mode 100644 index 0000000..a13db7d --- /dev/null +++ b/test/src/android/enum/android_file_attribiutes_test.dart @@ -0,0 +1,94 @@ +import 'package:flutter_file_info/flutter_file_info.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AndroidFileAttributesUtility', () { + test('parses valid attribute names correctly', () { + final attributeNames = [ + 'GROUP_EXECUTE', + 'GROUP_READ', + 'GROUP_WRITE', + 'OTHERS_EXECUTE', + 'OTHERS_READ', + 'OTHERS_WRITE', + 'OWNER_EXECUTE', + 'OWNER_READ', + 'OWNER_WRITE' + ]; + + final result = AndroidFileAttributesUtility.parseAndroidFileAttributes( + attributeNames); + + expect( + result, + containsAll([ + AndroidFileAttributes.GROUP_EXECUTE, + AndroidFileAttributes.GROUP_READ, + AndroidFileAttributes.GROUP_WRITE, + AndroidFileAttributes.OTHERS_EXECUTE, + AndroidFileAttributes.OTHERS_READ, + AndroidFileAttributes.OTHERS_WRITE, + AndroidFileAttributes.OWNER_EXECUTE, + AndroidFileAttributes.OWNER_READ, + AndroidFileAttributes.OWNER_WRITE + ])); + expect(result.length, 9); + }); + + test('ignores invalid attribute names', () { + final attributeNames = [ + 'GROUP_EXECUTE', + 'INVALID_ATTRIBUTE', + 'OWNER_WRITE', + 'ANOTHER_INVALID' + ]; + + final result = AndroidFileAttributesUtility.parseAndroidFileAttributes( + attributeNames); + + expect( + result, + containsAll([ + AndroidFileAttributes.GROUP_EXECUTE, + AndroidFileAttributes.OWNER_WRITE + ])); + expect(result.length, 2); + }); + + test('returns empty set for empty input', () { + final result = + AndroidFileAttributesUtility.parseAndroidFileAttributes([]); + + expect(result, isEmpty); + }); + + test('returns empty set for all invalid names', () { + final attributeNames = ['INVALID', 'ANOTHER_INVALID']; + + final result = AndroidFileAttributesUtility.parseAndroidFileAttributes( + attributeNames); + + expect(result, isEmpty); + }); + + test('handles mixed valid and invalid attribute names', () { + final attributeNames = [ + 'OWNER_EXECUTE', + 'INVALID', + 'OTHERS_READ', + 'ANOTHER_INVALID' + ]; + + final result = AndroidFileAttributesUtility.parseAndroidFileAttributes( + attributeNames); + + expect( + result, + containsAll([ + AndroidFileAttributes.OWNER_EXECUTE, + AndroidFileAttributes.OTHERS_READ + ])); + expect(result.length, 2); + }); + }); +} diff --git a/test/src/android/file_info_android_test.dart b/test/src/android/file_info_android_test.dart new file mode 100644 index 0000000..ab1868a --- /dev/null +++ b/test/src/android/file_info_android_test.dart @@ -0,0 +1,68 @@ +import 'dart:typed_data'; + +import 'package:flutter_file_info/flutter_file_info.dart'; +import 'package:flutter_file_info/src/android/android_method_channel.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; + +import 'file_info_android_test.mocks.dart'; + +@GenerateMocks([AndroidMethodChannel]) +void main() { + late MockAndroidMethodChannel mockAndroidMethodChannel; + late FileInfoAndroid fileInfoAndroid; + + setUp(() { + mockAndroidMethodChannel = MockAndroidMethodChannel(); + fileInfoAndroid = + FileInfoAndroid(androidMethodChannel: mockAndroidMethodChannel); + }); + + group('FileInfoAndroid', () { + test('should return IconInfo from getFileIconInfo', () async { + const filePath = 'test/path'; + final iconInfo = IconInfo( + pixelData: Uint8List.fromList([1, 2, 3]), + width: 100, + height: 100, + colorDepth: 0, + ); + + when(mockAndroidMethodChannel.getFileIcon(filePath)) + .thenAnswer((_) async => iconInfo); + + final result = await fileInfoAndroid.getFileIconInfo(filePath); + + expect(result, isNotNull); + expect(result, iconInfo); + verify(mockAndroidMethodChannel.getFileIcon(filePath)).called(1); + }); + + test('should return FileMetadata from getFileInfo', () async { + const filePath = 'test/path'; + final fileMetadata = FileMetadata( + filePath: filePath, + fileName: 'file', + fileExtension: 'txt', + fileType: 'Text File', + creationTime: DateTime.now(), + modifiedTime: DateTime.now(), + accessedTime: DateTime.now(), + sizeBytes: 1234, + fileSize: '1.2 KB', + dwFileAttributes: 0, + winAttributes: const [], + ); + + when(mockAndroidMethodChannel.getFileInfo(filePath)) + .thenAnswer((_) async => fileMetadata); + + final result = await fileInfoAndroid.getFileInfo(filePath); + + expect(result, isNotNull); + expect(result, fileMetadata); + verify(mockAndroidMethodChannel.getFileInfo(filePath)).called(1); + }); + }); +} diff --git a/test/src/android/file_info_android_test.mocks.dart b/test/src/android/file_info_android_test.mocks.dart new file mode 100644 index 0000000..ad3e6ba --- /dev/null +++ b/test/src/android/file_info_android_test.mocks.dart @@ -0,0 +1,85 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in flutter_file_info/test/src/android/file_info_android_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:flutter_file_info/flutter_file_info.dart' as _i2; +import 'package:flutter_file_info/src/android/android_method_channel.dart' + as _i3; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeIconInfo_0 extends _i1.SmartFake implements _i2.IconInfo { + _FakeIconInfo_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFileMetadata_1 extends _i1.SmartFake implements _i2.FileMetadata { + _FakeFileMetadata_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AndroidMethodChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidMethodChannel extends _i1.Mock + implements _i3.AndroidMethodChannel { + MockAndroidMethodChannel() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future<_i2.IconInfo> getFileIcon(String? filePath) => (super.noSuchMethod( + Invocation.method( + #getFileIcon, + [filePath], + ), + returnValue: _i4.Future<_i2.IconInfo>.value(_FakeIconInfo_0( + this, + Invocation.method( + #getFileIcon, + [filePath], + ), + )), + ) as _i4.Future<_i2.IconInfo>); + + @override + _i4.Future<_i2.FileMetadata> getFileInfo(String? filePath) => + (super.noSuchMethod( + Invocation.method( + #getFileInfo, + [filePath], + ), + returnValue: _i4.Future<_i2.FileMetadata>.value(_FakeFileMetadata_1( + this, + Invocation.method( + #getFileInfo, + [filePath], + ), + )), + ) as _i4.Future<_i2.FileMetadata>); +} diff --git a/test/src/models/file_metadata.module_test.dart b/test/src/models/file_metadata.module_test.dart index 2ba016d..c75e6d0 100644 --- a/test/src/models/file_metadata.module_test.dart +++ b/test/src/models/file_metadata.module_test.dart @@ -16,7 +16,10 @@ void main() { sizeBytes: 1024, fileSize: '1 KB', dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY, - attributes: const [FileAttributes.READ_ONLY, FileAttributes.HIDDEN], + winAttributes: const [ + WindowsFileAttributes.READ_ONLY, + WindowsFileAttributes.HIDDEN + ], ); expect(fileInfo.filePath, 'C:/test/file.txt'); @@ -30,8 +33,8 @@ void main() { expect(fileInfo.fileSize, '1 KB'); expect(fileInfo.dwFileAttributes, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY); - expect(fileInfo.attributes, - [FileAttributes.READ_ONLY, FileAttributes.HIDDEN]); + expect(fileInfo.winAttributes, + [WindowsFileAttributes.READ_ONLY, WindowsFileAttributes.HIDDEN]); }); test('FileInfo copyWith creates a new instance with updated values', () { @@ -46,14 +49,17 @@ void main() { sizeBytes: 1024, fileSize: '1 KB', dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY, - attributes: const [FileAttributes.READ_ONLY], + winAttributes: const [WindowsFileAttributes.READ_ONLY], ); final updatedFileInfo = fileInfo.copyWith( fileName: 'new_file.txt', sizeBytes: 2048, dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_HIDDEN, - attributes: [FileAttributes.HIDDEN, FileAttributes.SYSTEM], + winAttributes: [ + WindowsFileAttributes.HIDDEN, + WindowsFileAttributes.SYSTEM + ], ); expect(updatedFileInfo.filePath, 'C:/test/file.txt'); @@ -67,8 +73,8 @@ void main() { expect(updatedFileInfo.fileSize, '1 KB'); expect(updatedFileInfo.dwFileAttributes, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_HIDDEN); - expect(updatedFileInfo.attributes, - [FileAttributes.HIDDEN, FileAttributes.SYSTEM]); + expect(updatedFileInfo.winAttributes, + [WindowsFileAttributes.HIDDEN, WindowsFileAttributes.SYSTEM]); }); test('FileInfo toString returns correct string representation', () { @@ -83,16 +89,20 @@ void main() { sizeBytes: 1024, fileSize: '1 KB', dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY, - attributes: const [FileAttributes.READ_ONLY, FileAttributes.HIDDEN], + winAttributes: const [ + WindowsFileAttributes.READ_ONLY, + WindowsFileAttributes.HIDDEN + ], ); expect( fileInfo.toString(), - 'FileInfo(filePath: C:/test/file.txt, fileName: file.txt, fileExtension: .txt, ' + 'FileMetadata(filePath: C:/test/file.txt, fileName: file.txt, fileExtension: .txt, ' 'fileType: Text Document, creationTime: 2023-08-10 00:00:00.000, ' 'modifiedTime: 2023-08-12 00:00:00.000, accessedTime: 2023-08-13 00:00:00.000, ' 'sizeBytes: 1024, fileSize: 1 KB, dwFileAttributes: ${FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY}, ' - 'attributes: [FileAttributes.READ_ONLY, FileAttributes.HIDDEN])', + 'winAttributes: [WindowsFileAttributes.READ_ONLY, WindowsFileAttributes.HIDDEN], ' + 'androidAttributes: null)', ); }); @@ -108,7 +118,7 @@ void main() { sizeBytes: 1024, fileSize: '1 KB', dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY, - attributes: const [FileAttributes.READ_ONLY], + winAttributes: const [WindowsFileAttributes.READ_ONLY], ); final fileInfo2 = FileMetadata( @@ -122,7 +132,7 @@ void main() { sizeBytes: 1024, fileSize: '1 KB', dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY, - attributes: const [FileAttributes.READ_ONLY], + winAttributes: const [WindowsFileAttributes.READ_ONLY], ); final fileInfo3 = FileMetadata( @@ -136,7 +146,7 @@ void main() { sizeBytes: 1024, fileSize: '1 KB', dwFileAttributes: FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_HIDDEN, - attributes: const [FileAttributes.HIDDEN], + winAttributes: const [WindowsFileAttributes.HIDDEN], ); expect(fileInfo1, equals(fileInfo2)); diff --git a/test/src/windows/enum/file_attribiutes_test.dart b/test/src/windows/enum/windows_file_attribiutes_test.dart similarity index 69% rename from test/src/windows/enum/file_attribiutes_test.dart rename to test/src/windows/enum/windows_file_attribiutes_test.dart index 0904438..6b83fdd 100644 --- a/test/src/windows/enum/file_attribiutes_test.dart +++ b/test/src/windows/enum/windows_file_attribiutes_test.dart @@ -1,106 +1,106 @@ -import 'package:flutter_file_info/src/windows/enum/file_attribiutes.dart'; +import 'package:flutter_file_info/src/windows/enum/windows_file_attribiutes.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:win32/win32.dart'; void main() { group('FileAttributes enum tests', () { test('FileAttributes.READ_ONLY has correct value', () { - expect(FileAttributes.READ_ONLY.value, + expect(WindowsFileAttributes.READ_ONLY.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_READONLY); }); test('FileAttributes.HIDDEN has correct value', () { - expect(FileAttributes.HIDDEN.value, + expect(WindowsFileAttributes.HIDDEN.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_HIDDEN); }); test('FileAttributes.SYSTEM has correct value', () { - expect(FileAttributes.SYSTEM.value, + expect(WindowsFileAttributes.SYSTEM.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_SYSTEM); }); test('FileAttributes.DIRECTORY has correct value', () { - expect(FileAttributes.DIRECTORY.value, + expect(WindowsFileAttributes.DIRECTORY.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DIRECTORY); }); test('FileAttributes.ARCHIVE has correct value', () { - expect(FileAttributes.ARCHIVE.value, + expect(WindowsFileAttributes.ARCHIVE.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_ARCHIVE); }); test('FileAttributes.DEVICE has correct value', () { - expect(FileAttributes.DEVICE.value, + expect(WindowsFileAttributes.DEVICE.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_DEVICE); }); test('FileAttributes.NORMAL has correct value', () { - expect(FileAttributes.NORMAL.value, + expect(WindowsFileAttributes.NORMAL.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL); }); test('FileAttributes.TEMPORARY has correct value', () { - expect(FileAttributes.TEMPORARY.value, + expect(WindowsFileAttributes.TEMPORARY.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_TEMPORARY); }); test('FileAttributes.SPARSE_FILE has correct value', () { - expect(FileAttributes.SPARSE_FILE.value, + expect(WindowsFileAttributes.SPARSE_FILE.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_SPARSE_FILE); }); test('FileAttributes.REPARSE_POINT has correct value', () { - expect(FileAttributes.REPARSE_POINT.value, + expect(WindowsFileAttributes.REPARSE_POINT.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_REPARSE_POINT); }); test('FileAttributes.COMPRESSED has correct value', () { - expect(FileAttributes.COMPRESSED.value, + expect(WindowsFileAttributes.COMPRESSED.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_COMPRESSED); }); test('FileAttributes.OFFLINE has correct value', () { - expect(FileAttributes.OFFLINE.value, + expect(WindowsFileAttributes.OFFLINE.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_OFFLINE); }); test('FileAttributes.NOT_CONTENT_INDEXED has correct value', () { - expect(FileAttributes.NOT_CONTENT_INDEXED.value, + expect(WindowsFileAttributes.NOT_CONTENT_INDEXED.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); }); test('FileAttributes.ENCRYPTED has correct value', () { - expect(FileAttributes.ENCRYPTED.value, + expect(WindowsFileAttributes.ENCRYPTED.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_ENCRYPTED); }); test('FileAttributes.VIRTUAL has correct value', () { - expect(FileAttributes.VIRTUAL.value, + expect(WindowsFileAttributes.VIRTUAL.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_VIRTUAL); }); test('FileAttributes.EA has correct value', () { - expect( - FileAttributes.EA.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_EA); + expect(WindowsFileAttributes.EA.value, + FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_EA); }); test('FileAttributes.PINNED has correct value', () { - expect(FileAttributes.PINNED.value, + expect(WindowsFileAttributes.PINNED.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_PINNED); }); test('FileAttributes.UNPINNED has correct value', () { - expect(FileAttributes.UNPINNED.value, + expect(WindowsFileAttributes.UNPINNED.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_UNPINNED); }); test('FileAttributes.RECALL_ON_OPEN has correct value', () { - expect(FileAttributes.RECALL_ON_OPEN.value, + expect(WindowsFileAttributes.RECALL_ON_OPEN.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_RECALL_ON_OPEN); }); test('FileAttributes.RECALL_ON_DATA_ACCESS has correct value', () { - expect(FileAttributes.RECALL_ON_DATA_ACCESS.value, + expect(WindowsFileAttributes.RECALL_ON_DATA_ACCESS.value, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS); }); }); diff --git a/test/src/windows/file_info_windows_test.dart b/test/src/windows/file_info_windows_test.dart index 7ae065f..f3d562e 100644 --- a/test/src/windows/file_info_windows_test.dart +++ b/test/src/windows/file_info_windows_test.dart @@ -80,7 +80,7 @@ void main() { when(mockWindowsUtils.convertFileTimeToDateTime(any)).thenReturn(tDate); when(mockWindowsUtils.formatFileSize(any)).thenReturn('1 KB'); when(mockWindowsUtils.getFileAttributesFromMask(any)) - .thenReturn([FileAttributes.ARCHIVE]); + .thenReturn([WindowsFileAttributes.ARCHIVE]); final result = await tService.getFileInfo(filePath); @@ -92,7 +92,7 @@ void main() { expect(result.creationTime, equals(tDate)); expect(result.modifiedTime, equals(tDate)); expect(result.accessedTime, equals(tDate)); - expect(result.attributes, equals([FileAttributes.ARCHIVE])); + expect(result.winAttributes, equals([WindowsFileAttributes.ARCHIVE])); }); test('getFileInfo returns null when FindFirstFile fails', () async { diff --git a/test/src/windows/file_info_windows_test.mocks.dart b/test/src/windows/file_info_windows_test.mocks.dart index 011bf4f..bc9a71c 100644 --- a/test/src/windows/file_info_windows_test.mocks.dart +++ b/test/src/windows/file_info_windows_test.mocks.dart @@ -6,7 +6,7 @@ import 'dart:ffi' as _i3; import 'package:ffi/ffi.dart' as _i4; -import 'package:flutter_file_info/src/windows/enum/file_attribiutes.dart' +import 'package:flutter_file_info/src/windows/enum/windows_file_attribiutes.dart' as _i8; import 'package:flutter_file_info/src/windows/file_info_windows_ffi_types.dart' as _i2; @@ -201,14 +201,15 @@ class MockWindowsUtils extends _i1.Mock implements _i7.WindowsUtils { ) as DateTime); @override - List<_i8.FileAttributes> getFileAttributesFromMask(int? attributeMask) => + List<_i8.WindowsFileAttributes> getFileAttributesFromMask( + int? attributeMask) => (super.noSuchMethod( Invocation.method( #getFileAttributesFromMask, [attributeMask], ), - returnValue: <_i8.FileAttributes>[], - ) as List<_i8.FileAttributes>); + returnValue: <_i8.WindowsFileAttributes>[], + ) as List<_i8.WindowsFileAttributes>); @override String formatFileSize(int? sizeInBytes) => (super.noSuchMethod( diff --git a/test/src/windows/windows_utils_test.dart b/test/src/windows/windows_utils_test.dart index b915223..9f1e154 100644 --- a/test/src/windows/windows_utils_test.dart +++ b/test/src/windows/windows_utils_test.dart @@ -1,4 +1,4 @@ -import 'package:flutter_file_info/src/windows/enum/file_attribiutes.dart'; +import 'package:flutter_file_info/src/windows/enum/windows_file_attribiutes.dart'; import 'package:flutter_file_info/src/windows/windows_utils.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -22,11 +22,11 @@ void main() { expect( attributes, containsAll([ - FileAttributes.READ_ONLY, - FileAttributes.SYSTEM, - FileAttributes.ARCHIVE, + WindowsFileAttributes.READ_ONLY, + WindowsFileAttributes.SYSTEM, + WindowsFileAttributes.ARCHIVE, ])); - expect(attributes, isNot(contains(FileAttributes.HIDDEN))); + expect(attributes, isNot(contains(WindowsFileAttributes.HIDDEN))); }); test('formatFileSize formats file size correctly', () {