JJ_MKVMaker/lib/app/volumes.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();
}
}