Skip to content

Commit

Permalink
Merge pull request #12 from makjac/makjac/chore-add-changelog-0.1.0
Browse files Browse the repository at this point in the history
Makjac/chore add changelog 0.1.0
  • Loading branch information
makjac authored Aug 22, 2024
2 parents 78abb0d + dfa0551 commit 9310c7d
Show file tree
Hide file tree
Showing 25 changed files with 964 additions and 112 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PosixFilePermission>?
)
Original file line number Diff line number Diff line change
@@ -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<PosixFilePermission> = 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<String, Any?> {
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"
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<String>("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<String>("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<String>("filePath")
if (filePath != null) {
handleGetFileInfo(filePath, result)
} else {
result.error("INVALID_ARGUMENT", "File path is required", null)
}
}
else -> result.notImplemented()
}
}

Expand All @@ -52,16 +62,33 @@ 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 {
result.error("FILE_NOT_FOUND", "Icon not found for the specified file", null)
}
}

/**
* 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)
}
Expand Down
17 changes: 16 additions & 1 deletion example/lib/file_metadata_table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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),
),
],
),
);
Expand Down
3 changes: 2 additions & 1 deletion lib/flutter_file_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
42 changes: 41 additions & 1 deletion lib/src/android/android_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@ abstract class AndroidMethodChannel {
///
/// Returns a [Future] that completes with an [IconInfo] object containing the icon information.
Future<IconInfo> 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<FileMetadata> 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<IconInfo> getFileIcon(String filePath) async {
Expand Down Expand Up @@ -54,4 +63,35 @@ class AndroidMethodChannelImpl implements AndroidMethodChannel {
'height': height,
});
}

@override
Future<FileMetadata> getFileInfo(String filePath) async {
final result =
await methodChannel.invokeMethod('getFileInfo', {'filePath': filePath});

final Map<String, dynamic> data = Map<String, dynamic>.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<String>.from(data['androidAttributes'] as List<dynamic>))
.toList(),
// Note: winAttributes is not being set here. You may need to handle it similarly if needed.
);
}
}
Loading

0 comments on commit 9310c7d

Please sign in to comment.