2022-09-25 22:12:57 +00:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:dartssh2/dartssh2.dart';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:gallery/data_provider/data_provider.dart';
|
|
|
|
|
|
|
|
import 'dart:ui' as ui show Codec, ImmutableBuffer;
|
|
|
|
|
2022-09-26 22:30:13 +00:00
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
|
2022-09-25 22:12:57 +00:00
|
|
|
class SSHDataProvider extends DataProvider {
|
|
|
|
final String host;
|
|
|
|
final int port;
|
|
|
|
final String username;
|
|
|
|
final String password;
|
|
|
|
final String initialPath;
|
|
|
|
|
|
|
|
SftpClient? sftpClient;
|
|
|
|
SSHClient? sshClient;
|
|
|
|
|
|
|
|
SSHDataProvider(
|
|
|
|
{required this.host,
|
|
|
|
required this.port,
|
|
|
|
required this.initialPath,
|
|
|
|
required this.password,
|
|
|
|
required this.username});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> connect() async {
|
|
|
|
if (sshClient != null && !sshClient!.isClosed) return;
|
|
|
|
|
|
|
|
sshClient = SSHClient(
|
|
|
|
await SSHSocket.connect(host, port),
|
|
|
|
username: username,
|
|
|
|
onPasswordRequest: () => password,
|
|
|
|
);
|
|
|
|
|
|
|
|
sftpClient = await sshClient?.sftp();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<Folder> listOfFiles({Uri? uri}) async {
|
|
|
|
await connect();
|
|
|
|
if (sftpClient == null) throw const FormatException("");
|
|
|
|
|
|
|
|
final dir = uri != null ? Directory.fromUri(uri) : Directory(initialPath);
|
|
|
|
|
|
|
|
final items = await sftpClient!.listdir(dir.path);
|
|
|
|
List<Item> res = [];
|
|
|
|
for (final val in items) {
|
2022-09-26 22:30:13 +00:00
|
|
|
if (val.filename == "." || val.filename == "..") continue;
|
|
|
|
|
2022-09-25 22:12:57 +00:00
|
|
|
if (validSuffix.any((suff) => val.filename.endsWith(suff))) {
|
|
|
|
res.add(Item(false, Uri.file(dir.path + val.filename), val.filename));
|
|
|
|
} else if (val.attr.isDirectory) {
|
|
|
|
res.add(
|
|
|
|
Item(true, Uri.directory(dir.path + val.filename), val.filename));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-26 22:30:13 +00:00
|
|
|
res.sort(
|
|
|
|
(a, b) => b.isFolder ? 1 : -1,
|
|
|
|
);
|
|
|
|
|
2022-09-25 22:12:57 +00:00
|
|
|
return Folder(res, dir.uri, dir.parent.uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
ImageProvider getImageProvider(Uri uri) {
|
|
|
|
return _SSHImageProvider(uri, sftpClient!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _SSHImageProvider extends ImageProvider<_SSHImageProvider> {
|
2022-09-26 22:30:13 +00:00
|
|
|
const _SSHImageProvider(this.uri, this.sftpClient, {this.scale = 1.0});
|
2022-09-25 22:12:57 +00:00
|
|
|
|
|
|
|
final Uri uri;
|
|
|
|
final SftpClient sftpClient;
|
|
|
|
|
|
|
|
/// The scale to place in the [ImageInfo] object of the image.
|
|
|
|
final double scale;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<_SSHImageProvider> obtainKey(ImageConfiguration configuration) {
|
|
|
|
return SynchronousFuture<_SSHImageProvider>(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
ImageStreamCompleter load(_SSHImageProvider key, DecoderCallback decode) {
|
|
|
|
return MultiFrameImageStreamCompleter(
|
|
|
|
codec: _loadAsync(key, null, decode),
|
|
|
|
scale: key.scale,
|
|
|
|
debugLabel: key.uri.path,
|
|
|
|
informationCollector: () => <DiagnosticsNode>[
|
|
|
|
ErrorDescription('Path: ${uri.path}'),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
ImageStreamCompleter loadBuffer(
|
|
|
|
_SSHImageProvider key, DecoderBufferCallback decode) {
|
|
|
|
return MultiFrameImageStreamCompleter(
|
|
|
|
codec: _loadAsync(key, decode, null),
|
|
|
|
scale: key.scale,
|
|
|
|
debugLabel: key.uri.path,
|
|
|
|
informationCollector: () => <DiagnosticsNode>[
|
|
|
|
ErrorDescription('Path: ${uri.path}'),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<ui.Codec> _loadAsync(_SSHImageProvider key,
|
|
|
|
DecoderBufferCallback? decode, DecoderCallback? decodeDeprecated) async {
|
|
|
|
assert(key == this);
|
|
|
|
|
2022-09-26 22:30:13 +00:00
|
|
|
Directory tempDir = await getTemporaryDirectory();
|
|
|
|
String tempPath = "${tempDir.path}/gallery";
|
|
|
|
// check if temp file exists
|
|
|
|
Uint8List bytes;
|
|
|
|
final File tmpPic = File(tempPath + uri.toFilePath());
|
|
|
|
if (await tmpPic.exists()) {
|
|
|
|
// use temp file
|
|
|
|
bytes = await tmpPic.readAsBytes();
|
|
|
|
} else {
|
|
|
|
final file = await sftpClient.open(uri.toFilePath());
|
|
|
|
// todo do not load whole image in ram, create tempfile instead.
|
|
|
|
bytes = await file.readBytes();
|
|
|
|
await tmpPic.create(recursive: true);
|
|
|
|
await tmpPic.writeAsBytes(bytes);
|
|
|
|
}
|
2022-09-25 22:12:57 +00:00
|
|
|
|
|
|
|
if (bytes.lengthInBytes == 0) {
|
|
|
|
// The file may become available later.
|
|
|
|
PaintingBinding.instance.imageCache.evict(key);
|
2022-09-26 22:30:13 +00:00
|
|
|
throw StateError(
|
|
|
|
'bytes are empty is empty and cannot be loaded as an image.');
|
2022-09-25 22:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (decode != null) {
|
|
|
|
return decode(await ui.ImmutableBuffer.fromUint8List(bytes));
|
|
|
|
}
|
|
|
|
return decodeDeprecated!(bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool operator ==(Object other) {
|
|
|
|
if (other.runtimeType != runtimeType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return other is _SSHImageProvider &&
|
|
|
|
other.uri.path == uri.path &&
|
|
|
|
other.scale == scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
int get hashCode => Object.hash(uri.path, scale);
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() =>
|
|
|
|
'${objectRuntimeType(this, 'FileImage')}("${uri.path}", scale: $scale)';
|
|
|
|
}
|