106 lines
4.0 KiB
Dart
106 lines
4.0 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import '../core/file_ops.dart' show driveType, samePath, uncRoot, windowsDrives;
|
|
import '../core/vfs.dart' show DavPath, isDav;
|
|
import '../platform/android/android_storage.dart' show AndroidAccess;
|
|
|
|
/// 약속 (E5a): 이 기기의 저장 장치 (경로, 이름) 와 드라이브 상태.
|
|
/// - [localVolumes]: Windows 드라이브 · Android 저장소 · 그 밖은 "/". 입력 없음, 출력 = 목록 (비지 않게 - 없으면 C:\ 또는 /).
|
|
/// - [volumeOf]: 경로 → 그 경로가 든 맨 위 (WebDAV 는 서버, \\NAS\공유 는 그 공유, 로컬은 가장 긴 장치). 오류 없음.
|
|
/// - [reachable] · [reachableAsync]: 다시 열 수 있는지 (비동기는 2초 넘게 걸리면 있다고 보고 열어 봄, WebDAV 는 서버가 설정에 있으면).
|
|
/// - [DriveStatus]: 드라이브마다 열 수 있는지 (null = 확인 중) · 네트워크 · 꺼내는 장치. 바뀌면 알림 (닫은 뒤는 알리지 않음).
|
|
/// 확인 실패는 false (던지지 않음).
|
|
Future<List<(String, String)>> localVolumes() async {
|
|
final List<(String, String)> v;
|
|
if (Platform.isWindows) {
|
|
v = windowsDrives();
|
|
} else if (Platform.isAndroid) {
|
|
v = [for (final x in await AndroidAccess.volumes()) (x.$1, x.$2.isEmpty ? p.basename(x.$1) : x.$2)];
|
|
} else {
|
|
v = [('/', '/')];
|
|
}
|
|
return v.isEmpty ? [(Platform.isWindows ? r'C:\' : '/', Platform.isWindows ? 'C:' : '/')] : v;
|
|
}
|
|
|
|
/// [path] 가 든 맨 위 ([local] 은 비지 않은 장치 목록)
|
|
String volumeOf(String path, List<(String, String)> local) {
|
|
if (isDav(path)) {
|
|
final d = DavPath.parse(path);
|
|
return '${d.scheme}${d.server}/';
|
|
}
|
|
// 37: Windows 네트워크 공유 (\\NAS\공유) 는 그 공유가 맨 위
|
|
if (Platform.isWindows && path.startsWith(r'\\')) return uncRoot(path);
|
|
var best = local.first.$1;
|
|
var len = -1;
|
|
for (final (v, _) in local) {
|
|
final inside =
|
|
samePath(v, path) || p.isWithin(v, path) || (Platform.isWindows && p.isWithin(v.toLowerCase(), path.toLowerCase()));
|
|
if (inside && v.length > len) {
|
|
best = v;
|
|
len = v.length;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/// 다시 열 수 있는 폴더: 로컬은 있으면, WebDAV 는 그 서버가 아직 설정에 있으면 (짧은 확인 - 내역 목록 등)
|
|
bool reachable(String path) => isDav(path) ? DavPath.parse(path).serverKnown : Directory(path).existsSync();
|
|
|
|
/// [reachable] 을 화면을 멈추지 않고
|
|
Future<bool> reachableAsync(String path) async {
|
|
if (isDav(path)) return reachable(path);
|
|
try {
|
|
return await Directory(path).exists().timeout(const Duration(seconds: 2), onTimeout: () => true);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 165: 드라이브마다 따로 뒤에서 확인한 상태 (느린 드라이브가 있어도 다른 드라이브 · 창은 바로)
|
|
class DriveStatus extends ChangeNotifier {
|
|
/// null = 확인 중, true = 열 수 있음, false = 연결 안 됨
|
|
final ok = <String, bool?>{};
|
|
final network = <String>{};
|
|
|
|
/// 꺼내는 장치 · CD (카드 리더 · USB): 열 수 없으면 "비어 있음" (연결 안 됨이 아니라 매체가 없는 것)
|
|
final removable = <String>{};
|
|
|
|
/// Windows 드라이브 종류를 적는다 (네트워크 4 · 꺼내는 장치 2 · CD 5)
|
|
void classify(Iterable<String> drives) {
|
|
for (final d in drives) {
|
|
final t = driveType(d);
|
|
if (t == 4) network.add(d);
|
|
if (t == 2 || t == 5) removable.add(d);
|
|
}
|
|
}
|
|
|
|
/// [probe] 로 [path] 를 열 수 있는지 확인 (실패 · 예외는 false). 닫은 뒤 끝나면 알리지 않는다
|
|
Future<bool> check(String path, Future<bool> Function(String path) probe) async {
|
|
ok[path] = null;
|
|
_changed();
|
|
bool r;
|
|
try {
|
|
r = await probe(path);
|
|
} catch (_) {
|
|
r = false;
|
|
}
|
|
ok[path] = r;
|
|
_changed();
|
|
return r;
|
|
}
|
|
|
|
bool _disposed = false;
|
|
void _changed() {
|
|
if (!_disposed) notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_disposed = true;
|
|
super.dispose();
|
|
}
|
|
}
|