Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Linux desktop platform #164

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2.1.3
* Base support for `Linux`

## 2.1.2

* UPDATE: document comments #158
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ Flutter has [fatal crash issee for loading info.plist on macOS](https://github.c

We recommend that you update [Visual Studio](https://visualstudio.microsoft.com) to the latest version for using `C++ 20`.

### Linux
The following command executables are required to use this plugin:
1. `mkdir`
2. `cp`
3. `rm`
4. `wget`


## ✅ Usage

Expand Down
2 changes: 1 addition & 1 deletion lib/src/gal.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart' show immutable, Uint8List;
import 'package:gal/src/gal_exception.dart';
import 'package:gal/src/gal_platform.dart';

Expand Down
7 changes: 4 additions & 3 deletions lib/src/gal_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ class GalException implements Exception {
orElse: () => GalExceptionType.unexpected,
);
return GalException(
type: type,
platformException: platformException,
stackTrace: stackTrace);
type: type,
platformException: platformException,
stackTrace: stackTrace,
);
}
@override
String toString() => "[GalException/${type.code}]: ${type.message}";
Expand Down
46 changes: 42 additions & 4 deletions lib/src/gal_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:gal/gal.dart';

import 'linux/gal_linux_impl.dart';

const _channel = MethodChannel('gal');

/// Plugin Communication
Expand All @@ -15,37 +17,73 @@ final class GalPlatform {
return await _channel.invokeMethod<T>(method, args);
} on PlatformException catch (error, stackTrace) {
throw GalException.fromCode(
code: error.code, platformException: error, stackTrace: stackTrace);
code: error.code,
platformException: error,
stackTrace: stackTrace,
);
}
}

static Future<void> putVideo(String path, {String? album}) async {
await requestAccess(toAlbum: album != null);
if (GalLinuxImpl.isLinux) {
await GalLinuxImpl.putVideo(
path,
album: album,
);
return;
}
await _invokeMethod<void>('putVideo', {'path': path, 'album': album});
}

static Future<void> putImage(String path, {String? album}) async {
await requestAccess(toAlbum: album != null);
if (GalLinuxImpl.isLinux) {
await GalLinuxImpl.putImage(
path,
album: album,
);
return;
}
await _invokeMethod<void>('putImage', {'path': path, 'album': album});
}

static Future<void> putImageBytes(Uint8List bytes, {String? album}) async {
await requestAccess(toAlbum: album != null);
if (GalLinuxImpl.isLinux) {
await GalLinuxImpl.putImageBytes(
bytes,
album: album,
);
return;
}
await _invokeMethod<void>(
'putImageBytes', {'bytes': bytes, 'album': album});
'putImageBytes',
{'bytes': bytes, 'album': album},
);
}

static Future<void> open() async => _invokeMethod<void>('open', {});

static Future<bool> hasAccess({bool toAlbum = false}) async {
if (GalLinuxImpl.isLinux) {
final result = await GalLinuxImpl.hasAccess(toAlbum: toAlbum);
return result;
}
final hasAccess =
await _invokeMethod<bool>('hasAccess', {'toAlbum': toAlbum});
return hasAccess ?? false;
}

static Future<bool> requestAccess({bool toAlbum = false}) async {
final granted =
await _invokeMethod<bool>('requestAccess', {'toAlbum': toAlbum});
if (GalLinuxImpl.isLinux) {
final result = await GalLinuxImpl.requestAccess(toAlbum: toAlbum);
return result;
}
final granted = await _invokeMethod<bool>(
'requestAccess',
{'toAlbum': toAlbum},
);
return granted ?? false;
}
}
234 changes: 234 additions & 0 deletions lib/src/linux/gal_linux_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import 'dart:io' show Directory, File, Platform, ProcessException;

import 'package:flutter/foundation.dart' show Uint8List, immutable, kIsWeb;
import 'package:flutter/services.dart' show PlatformException;
import 'package:gal/src/gal_exception.dart';
import 'package:gal/src/utils/command_line.dart';
import 'package:gal/src/utils/extensions/uri.dart';

enum _FileType {
image,
video,
}

/// Impl for Linux platform
///
/// currently the support for Linux is limitied
/// we will always use [GalExceptionType.unexpected]
///
/// it's not 100% spesefic to Linux, it could work for Unix based OS
@immutable
final class GalLinuxImpl {
const GalLinuxImpl._();

static bool get isLinux {
if (kIsWeb) {
return false;
}
return Platform.isLinux;
}

static String _baseName(String path) {
return File(path).uri.pathSegments.last;
}

/// Save a video to the gallery from file [path].
///
/// Specify the album with [album]. If it does not exist, it will be created.
/// [path] must include the file extension.
/// ```dart
/// await Gal.putVideo('${Directory.systemTemp.path}/video.mp4');
/// ```
/// Throws an [GalException] If you do not have access premission or
/// if an error occurs during saving.
/// See: [Formats](https://github.com/natsuk4ze/gal/wiki/Formats)
static Future<void> putVideo(String path, {String? album}) async {
await _downloadFileToAlbum(
path,
fileType: _FileType.video,
album: album,
);
}

/// Save a image to the gallery from file [path].
///
/// Specify the album with [album]. If it does not exist, it will be created.
/// [path] must include the file extension.
/// ```dart
/// await Gal.putImage('${Directory.systemTemp.path}/image.jpg');
/// ```
/// Throws an [GalException] If you do not have access premission or
/// if an error occurs during saving.
/// See: [Formats](https://github.com/natsuk4ze/gal/wiki/Formats)
static Future<void> putImage(String path, {String? album}) async {
await _downloadFileToAlbum(
path,
fileType: _FileType.image,
album: album,
);
}

static Future<void> _downloadFileToAlbum(
String path, {
required _FileType fileType,
String? album,
}) async {
try {
final file = File(path);
final fileExists = await file.exists();
var filePath = path;

String? downloadedFilePath;
if (!fileExists) {
final fileName = _baseName(path);
final uri = Uri.parse(path);
if (!uri.isHttpBasedUrl()) {
throw UnsupportedError(
'You are trying to put file with path `$path` that does not exists '
'locally, Also it does not start with `http` or `https`',
);
}
await executeCommand(
executalbe: 'wget',
args: [
'-O',
fileName,
path,
],
);
final workingDir = Directory.current.path;
downloadedFilePath = '$workingDir/$fileName';
filePath = downloadedFilePath;
}
final fileName = _baseName(filePath);
if (album != null) {
final newFileLocation = _getNewFileLocationWithAlbum(
fileType: fileType,
album: album,
fileName: fileName,
);
await executeCommand(
executalbe: 'mkdir',
args: [
'-p',
newFileLocation,
],
);
await executeCommand(
executalbe: 'cp',
args: [
filePath,
newFileLocation,
],
);
} else {
final newLocation = _getNewFileLocation(fileName: fileName);
await executeCommand(
executalbe: 'mkdir',
args: [
'-p',
newLocation,
],
);
await executeCommand(
executalbe: 'cp',
args: [
filePath,
newLocation,
],
);
}
// Remove the downloaded file from the network if it exists
if (downloadedFilePath != null) {
await executeCommand(
executalbe: 'rm',
args: [
downloadedFilePath,
],
);
}
} on ProcessException catch (e) {
throw GalException(
type: GalExceptionType.unexpected,
platformException: PlatformException(
code: e.errorCode.toString(),
message: e.toString(),
details: e.message.toString(),
stacktrace: StackTrace.current.toString(),
),
stackTrace: StackTrace.current,
);
} catch (e) {
throw GalException(
type: GalExceptionType.unexpected,
platformException: PlatformException(
code: e.toString(),
message: e.toString(),
details: e.toString(),
stacktrace: StackTrace.current.toString(),
),
stackTrace: StackTrace.current,
);
}
}

static String _getNewFileLocationWithAlbum({
required _FileType fileType,
required String album,
required String fileName,
}) {
final newFileLocation = switch (fileType) {
_FileType.image => '~/Pictures/$album/$fileName',
_FileType.video => '~/Videos/$album/$fileName',
};
return newFileLocation;
}

static String _getNewFileLocation({
required String fileName,
}) {
return '${Directory.systemTemp.path}/$fileName';
}

/// Save a image to the gallery from [Uint8List].
///
/// Specify the album with [album]. If it does not exist, it will be created.
/// Throws an [GalException] If you do not have access premission or
/// if an error occurs during saving.
/// See: [Formats](https://github.com/natsuk4ze/gal/wiki/Formats)
static Future<void> putImageBytes(Uint8List bytes, {String? album}) async {
final fileName = '${DateTime.now().toIso8601String()}.png';
final newFileLocation = album == null
? _getNewFileLocation(fileName: DateTime.now().toIso8601String())
: _getNewFileLocationWithAlbum(
fileType: _FileType.image,
album: album,
fileName: fileName,
);
final file = File(newFileLocation);
await file.writeAsBytes(bytes);
}

/// Open gallery app.
///
/// If there are multiple gallery apps, App selection sheet may be displayed.
static Future<void> open() async => throw UnsupportedError(
'Linux is not supported yet.',
);

/// Check if the app has access permissions.
///
/// Use the [toAlbum] for additional permissions to save to an album.
/// If you want to save to an album other than the one created by your app
/// See: [Permissions](https://github.com/natsuk4ze/gal/wiki/Permissions)
static Future<bool> hasAccess({bool toAlbum = false}) async => true;

/// Request access permissions.
///
/// Returns [true] if access is granted, [false] if denied.
/// If access was already granted, the dialog is not displayed and returns true.
/// Use the [toAlbum] for additional permissions to save to an album.
/// If you want to save to an album other than the one created by your app
/// See: [Permissions](https://github.com/natsuk4ze/gal/wiki/Permissions)
static Future<bool> requestAccess({bool toAlbum = false}) async => true;
}
Loading