merge ambiguously cased directories

This commit is contained in:
Thibault Deckers 2022-05-30 18:39:08 +09:00
parent 9fb221d549
commit 220342f9bc
2 changed files with 73 additions and 3 deletions

View file

@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:aves/geo/countries.dart'; import 'package:aves/geo/countries.dart';
import 'package:aves/model/entry_cache.dart'; import 'package:aves/model/entry_cache.dart';
import 'package:aves/model/entry_dirs.dart';
import 'package:aves/model/favourites.dart'; import 'package:aves/model/favourites.dart';
import 'package:aves/model/geotiff.dart'; import 'package:aves/model/geotiff.dart';
import 'package:aves/model/metadata/address.dart'; import 'package:aves/model/metadata/address.dart';
@ -31,7 +32,8 @@ class AvesEntry {
// `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode // `sizeBytes`, `dateModifiedSecs` can be missing in viewer mode
int id; int id;
String uri; String uri;
String? _path, _directory, _filename, _extension, _sourceTitle; String? _path, _filename, _extension, _sourceTitle;
EntryDir? _directory;
int? pageId, contentId; int? pageId, contentId;
final String sourceMimeType; final String sourceMimeType;
int width, height, sourceRotationDegrees; int width, height, sourceRotationDegrees;
@ -175,8 +177,8 @@ class AvesEntry {
// directory path, without the trailing separator // directory path, without the trailing separator
String? get directory { String? get directory {
_directory ??= path != null ? pContext.dirname(path!) : null; _directory ??= entryDirRepo.getOrCreate(path != null ? pContext.dirname(path!) : null);
return _directory; return _directory!.resolved;
} }
String? get filenameWithoutExtension { String? get filenameWithoutExtension {

68
lib/model/entry_dirs.dart Normal file
View file

@ -0,0 +1,68 @@
import 'dart:async';
import 'dart:io';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:collection/collection.dart';
final entryDirRepo = EntryDirRepo._private();
class EntryDirRepo {
EntryDirRepo._private();
// mapping between the raw entry directory path to a resolvable directory
final Map<String?, EntryDir> _dirs = {};
final StreamController<EntryDir> _ambiguousDirStreamController = StreamController.broadcast();
Stream<EntryDir> get ambiguousDirStream => _ambiguousDirStreamController.stream;
// get a resolvable directory for a raw entry directory path
EntryDir getOrCreate(String? asIs) {
var entryDir = _dirs[asIs];
if (entryDir != null) return entryDir;
final asIsLower = asIs?.toLowerCase();
entryDir = _dirs.values.firstWhereOrNull((dir) => dir.asIsLower == asIsLower);
if (entryDir != null && !entryDir.ambiguous) {
entryDir.ambiguous = true;
_ambiguousDirStreamController.add(entryDir);
}
return _dirs.putIfAbsent(asIs, () => entryDir ?? EntryDir(asIs));
}
}
// Some directories are ambiguous because they use different cases,
// but the OS merge and present them as one directory.
// This class resolves ambiguous directories to get the directory path
// with the right case, as presented by the OS.
class EntryDir {
final String? asIs, asIsLower;
bool ambiguous = false;
String? _resolved;
EntryDir(this.asIs) : asIsLower = asIs?.toLowerCase();
String? get resolved {
if (!ambiguous) return asIs;
if (asIs == null) return null;
_resolved ??= _resolve();
return _resolved;
}
String? _resolve() {
final vrl = VolumeRelativeDirectory.fromPath(asIs!);
if (vrl == null || vrl.relativeDir.isEmpty) return asIs;
var resolved = vrl.volumePath;
final parts = pContext.split(vrl.relativeDir);
for (final part in parts) {
final partLower = part.toLowerCase();
final childrenDirs = Directory(resolved).listSync().where((v) => v.absolute is Directory).toSet();
final found = childrenDirs.firstWhereOrNull((v) => pContext.basename(v.path).toLowerCase() == partLower);
resolved = found?.path ?? '$resolved${pContext.separator}$part';
}
return resolved;
}
}