update: updating ports from the symfony http foundation component
This commit is contained in:
parent
830fcdf0da
commit
0c7bc94cc3
12 changed files with 315 additions and 10 deletions
|
@ -9,4 +9,5 @@
|
|||
|
||||
library;
|
||||
|
||||
export "src/foundation/file/file.dart";
|
||||
export 'src/foundation/file/upload_file.dart';
|
||||
|
|
|
@ -16,5 +16,5 @@ export 'src/foundation/file/exception/file_not_found_exception.dart';
|
|||
export 'src/foundation/file/exception/form_size_file_exception.dart';
|
||||
export 'src/foundation/file/exception/ini_size_file_exception.dart';
|
||||
export 'src/foundation/file/exception/no_file_exception.dart';
|
||||
export 'src/foundation/file/exception/no_tmpdir_file_exception.dart';
|
||||
export 'src/foundation/file/exception/no_tmp_dir_file_exception.dart';
|
||||
export 'src/foundation/file/exception/partial_file_exception.dart';
|
||||
|
|
|
@ -12,5 +12,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class CannotWriteFileException extends FileException {
|
||||
/// Creates a new [CannotWriteFileException] with the given [message].
|
||||
CannotWriteFileException([String message = '']) : super(message);
|
||||
CannotWriteFileException([super.message]);
|
||||
}
|
||||
|
|
|
@ -12,5 +12,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class ExtensionFileException extends FileException {
|
||||
// The constructor is empty as it inherits from FileException
|
||||
ExtensionFileException([String message = '']) : super(message);
|
||||
ExtensionFileException([super.message]);
|
||||
}
|
||||
|
|
|
@ -11,5 +11,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
///
|
||||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class FormSizeFileException extends FileException {
|
||||
FormSizeFileException([String message = '']) : super(message);
|
||||
FormSizeFileException([super.message]);
|
||||
}
|
||||
|
|
|
@ -11,5 +11,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
///
|
||||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class IniSizeFileException extends FileException {
|
||||
// No additional functionality needed, as this exception is just a specific type of FileException
|
||||
IniSizeFileException(super.message);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
///
|
||||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class NoFileException extends FileException {
|
||||
// In Dart, we don't need to explicitly declare an empty class body
|
||||
// if there are no additional members or methods.
|
||||
NoFileException(super.message);
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
///
|
||||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class NoTmpDirFileException extends FileException {
|
||||
// The constructor is implicit in this case, as it doesn't require any additional setup
|
||||
NoTmpDirFileException(super.message);
|
||||
}
|
|
@ -14,6 +14,5 @@ import 'package:protevus_http/foundation_file_exception.dart';
|
|||
///
|
||||
/// @author Florent Mata <florentmata@gmail.com>
|
||||
class PartialFileException extends FileException {
|
||||
// In Dart, we don't need to declare an empty class body if there are no additional
|
||||
// properties or methods. The class will inherit everything from FileException.
|
||||
PartialFileException(super.message);
|
||||
}
|
||||
|
|
174
packages/http/lib/src/foundation/file/file.dart
Normal file
174
packages/http/lib/src/foundation/file/file.dart
Normal file
|
@ -0,0 +1,174 @@
|
|||
import 'dart:io' as io;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:protevus_http/foundation_file_exception.dart';
|
||||
import 'package:protevus_mime/mime.dart';
|
||||
|
||||
/// A file in the file system.
|
||||
class File extends io.FileSystemEntity {
|
||||
late final io.File _dartFile;
|
||||
@override
|
||||
final String path;
|
||||
|
||||
/// Constructs a new file from the given path.
|
||||
///
|
||||
/// [path] The path to the file
|
||||
/// [checkPath] Whether to check the path or not
|
||||
///
|
||||
/// Throws [FileNotFoundException] If the given path is not a file
|
||||
File(this.path, {bool checkPath = true}) {
|
||||
if (checkPath && !io.FileSystemEntity.isFileSync(path)) {
|
||||
throw FileNotFoundException(path);
|
||||
}
|
||||
_dartFile = io.File(path);
|
||||
}
|
||||
|
||||
/// Returns the extension based on the mime type.
|
||||
///
|
||||
/// If the mime type is unknown, returns null.
|
||||
///
|
||||
/// This method uses the mime type as guessed by getMimeType()
|
||||
/// to guess the file extension.
|
||||
Future<String?> guessExtension() async {
|
||||
final mimeType = await getMimeType();
|
||||
if (mimeType == null) return null;
|
||||
|
||||
final extensions = MimeTypes.getDefault().getExtensions(mimeType);
|
||||
return extensions.isNotEmpty ? extensions.first : null;
|
||||
}
|
||||
|
||||
/// Returns the mime type of the file.
|
||||
///
|
||||
/// The mime type is guessed using the MimeTypes class.
|
||||
Future<String?> getMimeType() {
|
||||
return MimeTypes.getDefault().guessMimeType(path);
|
||||
}
|
||||
|
||||
/// Moves the file to a new location.
|
||||
///
|
||||
/// Throws [FileException] if the target file could not be created
|
||||
File move(String directory, [String? name]) {
|
||||
final target = getTargetFile(directory, name);
|
||||
|
||||
try {
|
||||
final newPath = target.path;
|
||||
_dartFile.renameSync(newPath);
|
||||
chmod(newPath, '0666');
|
||||
return target;
|
||||
} catch (e) {
|
||||
throw FileException(
|
||||
'Could not move the file "$path" to "${target.path}" ($e).');
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the content of the file.
|
||||
String getContent() {
|
||||
try {
|
||||
return _dartFile.readAsStringSync();
|
||||
} catch (e) {
|
||||
throw FileException('Could not get the content of the file "$path".');
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the target file for a move operation.
|
||||
File getTargetFile(String directory, [String? name]) {
|
||||
final dir = io.Directory(directory);
|
||||
if (!dir.existsSync()) {
|
||||
try {
|
||||
dir.createSync(recursive: true);
|
||||
} catch (e) {
|
||||
throw FileException('Unable to create the "$directory" directory.');
|
||||
}
|
||||
} else if (!dir.statSync().modeString().contains('w')) {
|
||||
throw FileException('Unable to write in the "$directory" directory.');
|
||||
}
|
||||
|
||||
final targetPath = p.join(directory, name ?? p.basename(path));
|
||||
return File(targetPath, checkPath: false);
|
||||
}
|
||||
|
||||
/// Returns locale independent base name of the given path.
|
||||
String getName(String name) {
|
||||
final normalizedName = name.replaceAll('\\', '/');
|
||||
final pos = normalizedName.lastIndexOf('/');
|
||||
return pos == -1 ? normalizedName : normalizedName.substring(pos + 1);
|
||||
}
|
||||
|
||||
/// Changes the file permissions.
|
||||
///
|
||||
/// [filePath] is the path to the file whose permissions should be changed.
|
||||
/// [mode] should be an octal string like '0644' for Unix-like systems.
|
||||
/// For Windows, use 'read' for read-only, 'write' for read/write, or 'full' for full control.
|
||||
static Future<void> chmod(String filePath, String mode) async {
|
||||
if (io.Platform.isWindows) {
|
||||
await _chmodWindows(filePath, mode);
|
||||
} else {
|
||||
await _chmodUnix(filePath, mode);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _chmodUnix(String filePath, String mode) async {
|
||||
try {
|
||||
final result = await io.Process.run('chmod', [mode, filePath]);
|
||||
if (result.exitCode != 0) {
|
||||
throw FileException(
|
||||
'Failed to change permissions for $filePath: ${result.stderr}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.toString().contains('Permission denied')) {
|
||||
// Optionally, you could try with sudo here, but that requires user interaction
|
||||
throw FileException(
|
||||
'Permission denied. You may need to run this with elevated privileges.');
|
||||
} else {
|
||||
throw FileException('Failed to change permissions for $filePath: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _chmodWindows(String filePath, String mode) async {
|
||||
String permission;
|
||||
switch (mode.toLowerCase()) {
|
||||
case 'read':
|
||||
permission = '(R)';
|
||||
break;
|
||||
case 'write':
|
||||
permission = '(R,W)';
|
||||
break;
|
||||
case 'full':
|
||||
permission = '(F)';
|
||||
break;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Invalid mode for Windows. Use "read", "write", or "full".');
|
||||
}
|
||||
|
||||
final result = await io.Process.run(
|
||||
'icacls', [filePath, '/grant', '*S-1-1-0:$permission']);
|
||||
if (result.exitCode != 0) {
|
||||
throw FileException(
|
||||
'Failed to change permissions for $filePath: ${result.stderr}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
io.FileSystemEntity get absolute => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<bool> exists() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
bool existsSync() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<io.FileSystemEntity> rename(String newPath) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
io.FileSystemEntity renameSync(String newPath) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'package:protevus_http/foundation_file_exception.dart';
|
||||
import 'package:protevus_http/foundation_file.dart';
|
||||
import 'package:protevus_mime/mime.dart';
|
||||
|
||||
/// A file uploaded through a form.
|
||||
class UploadedFile extends File {
|
||||
late String _originalName;
|
||||
late String _mimeType;
|
||||
late int _error;
|
||||
late String _originalPath;
|
||||
late bool _test;
|
||||
|
||||
UploadedFile(
|
||||
super.path,
|
||||
String originalName,
|
||||
String? mimeType,
|
||||
int? error,
|
||||
bool test,
|
||||
) {
|
||||
_originalName = _getName(originalName);
|
||||
_originalPath = originalName.replaceAll('\\', '/');
|
||||
_mimeType = mimeType ?? 'application/octet-stream';
|
||||
_error = error ?? 0; // UPLOAD_ERR_OK
|
||||
_test = test;
|
||||
}
|
||||
|
||||
String getClientOriginalName() {
|
||||
return _originalName;
|
||||
}
|
||||
|
||||
String getClientOriginalExtension() {
|
||||
return p.extension(_originalName);
|
||||
}
|
||||
|
||||
String getClientOriginalPath() {
|
||||
return _originalPath;
|
||||
}
|
||||
|
||||
String getClientMimeType() => _mimeType;
|
||||
|
||||
String? guessClientExtension() {
|
||||
try {
|
||||
List<String> extensions =
|
||||
MimeTypes.getDefault().getExtensions(getClientMimeType());
|
||||
return extensions.isNotEmpty ? extensions[0] : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int getError() {
|
||||
return _error;
|
||||
}
|
||||
|
||||
bool isValid() {
|
||||
bool isOk = _error == 0; // UPLOAD_ERR_OK
|
||||
return _test ? isOk : isOk && File(path).existsSync();
|
||||
}
|
||||
|
||||
@override
|
||||
File move(String directory, [String? name]) {
|
||||
if (isValid()) {
|
||||
if (_test) {
|
||||
return File(p.join(directory, name ?? p.basename(path)));
|
||||
}
|
||||
|
||||
File target = _getTargetFile(directory, name);
|
||||
try {
|
||||
return super.move(target.path);
|
||||
} catch (e) {
|
||||
throw FileException(
|
||||
'Could not move the file "$path" to "${target.path}" ($e).');
|
||||
}
|
||||
}
|
||||
|
||||
switch (_error) {
|
||||
case 1: // UPLOAD_ERR_INI_SIZE
|
||||
throw IniSizeFileException(_getErrorMessage());
|
||||
case 2: // UPLOAD_ERR_FORM_SIZE
|
||||
throw FormSizeFileException(_getErrorMessage());
|
||||
case 3: // UPLOAD_ERR_PARTIAL
|
||||
throw PartialFileException(_getErrorMessage());
|
||||
case 4: // UPLOAD_ERR_NO_FILE
|
||||
throw NoFileException(_getErrorMessage());
|
||||
case 6: // UPLOAD_ERR_NO_TMP_DIR
|
||||
throw NoTmpDirFileException(_getErrorMessage());
|
||||
case 7: // UPLOAD_ERR_CANT_WRITE
|
||||
throw CannotWriteFileException(_getErrorMessage());
|
||||
case 8: // UPLOAD_ERR_EXTENSION
|
||||
throw ExtensionFileException(_getErrorMessage());
|
||||
default:
|
||||
throw FileException(_getErrorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static int getMaxFilesize() {
|
||||
// Note: This is a placeholder. Implement according to your needs.
|
||||
return 2 * 1024 * 1024; // 2MB as an example
|
||||
}
|
||||
|
||||
String _getErrorMessage() {
|
||||
Map<int, String> errors = {
|
||||
1: 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
|
||||
2: 'The file "%s" exceeds the upload limit defined in your form.',
|
||||
3: 'The file "%s" was only partially uploaded.',
|
||||
4: 'No file was uploaded.',
|
||||
6: 'File could not be uploaded: missing temporary directory.',
|
||||
7: 'The file "%s" could not be written on disk.',
|
||||
8: 'File upload was stopped by a PHP extension.',
|
||||
};
|
||||
|
||||
int maxFilesize = _error == 1 ? getMaxFilesize() ~/ 1024 : 0;
|
||||
String message = errors[_error] ??
|
||||
'The file "%s" was not uploaded due to an unknown error.';
|
||||
|
||||
return message
|
||||
.replaceAll('%s', _originalName)
|
||||
.replaceAll('%d', maxFilesize.toString());
|
||||
}
|
||||
|
||||
String _getName(String name) {
|
||||
return p.basename(name);
|
||||
}
|
||||
|
||||
File _getTargetFile(String directory, String? name) {
|
||||
name ??= p.basename(path);
|
||||
return File(p.join(directory, name));
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ dependencies:
|
|||
email_validator: ^3.0.0
|
||||
validator_dart: ^0.1.0
|
||||
protevus_mime: ^0.0.1
|
||||
path: ^1.9.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^3.0.0
|
||||
|
|
Loading…
Reference in a new issue