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; import 'package:path_provider/path_provider.dart'; class SSHDataProvider extends DataProvider { final Future host; final Future port; final Future username; final Future password; final Future initialPath; SftpClient? sftpClient; SSHClient? sshClient; SSHDataProvider( {required this.host, required this.port, required this.initialPath, required this.password, required this.username}); @override Future connect() async { if (sshClient != null && !sshClient!.isClosed) return; sshClient = SSHClient( await SSHSocket.connect(await host, await port), username: await username, onPasswordRequest: () => password, ); await sshClient?.authenticated; sftpClient = await sshClient?.sftp(); } @override Future listOfFiles({Uri? uri}) async { await connect(); if (sftpClient == null) throw const FormatException(""); final dir = uri != null ? Directory.fromUri(uri) : Directory(await initialPath); final items = await sftpClient!.listdir(dir.path); List res = []; for (final val in items) { if (val.filename == "." || val.filename == "..") continue; 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)); } } res.sort( (a, b) => b.isFolder ? 1 : -1, ); return Folder(res, dir.uri, dir.parent.uri); } @override ImageProvider getImageProvider(Uri uri) { return _SSHImageProvider(uri, sftpClient!); } } class _SSHImageProvider extends ImageProvider<_SSHImageProvider> { const _SSHImageProvider(this.uri, this.sftpClient); final Uri uri; final SftpClient sftpClient; @override Future<_SSHImageProvider> obtainKey(ImageConfiguration configuration) { return SynchronousFuture<_SSHImageProvider>(this); } @override ImageStreamCompleter loadBuffer( _SSHImageProvider key, DecoderBufferCallback decode) { return MultiFrameImageStreamCompleter( codec: _loadAsync(key, decode), scale: 1.0, debugLabel: key.uri.path, informationCollector: () => [ ErrorDescription('Path: ${uri.path}'), ], ); } Future _loadAsync( _SSHImageProvider key, DecoderBufferCallback decode) async { assert(key == this); 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()); bytes = await file.readBytes(); () async { await tmpPic.create(recursive: true); await tmpPic.writeAsBytes(bytes); }(); } if (bytes.lengthInBytes == 0) { // The file may become available later. PaintingBinding.instance.imageCache.evict(key); throw StateError( 'bytes are empty is empty and cannot be loaded as an image.'); } return decode(await ui.ImmutableBuffer.fromUint8List(bytes)); } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is _SSHImageProvider && other.uri.path == uri.path; } @override int get hashCode => uri.path.hashCode; @override String toString() => '${objectRuntimeType(this, 'FileImage')}("${uri.path}")'; }