Files
GitJournal/lib/core/file/file_storage.dart
Vishesh Handa 657721adc6 Update dart-git and stop using the Result class
Instead we're going to move back to standard exceptions.

Using a custom Result class has created far far more problems
- The Stacktraces aren't always right
- Sometimes one forgets to check the Result error
- All other exception throwing code needing to be converted to Results
- Non idiomatic Dart code

I think it's better to just go back to exceptions. They have their
problems, but overall, I think it's a better approach.
2023-11-24 14:03:30 +01:00

202 lines
5.2 KiB
Dart

/*
* SPDX-FileCopyrightText: 2019-2021 Vishesh Handa <me@vhanda.in>
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import 'dart:isolate';
import 'package:dart_git/blob_ctime_builder.dart';
import 'package:dart_git/dart_git.dart';
import 'package:dart_git/exceptions.dart';
import 'package:dart_git/file_mtime_builder.dart';
import 'package:flutter/foundation.dart';
import 'package:gitjournal/error_reporting.dart';
import 'package:gitjournal/logger/logger.dart';
import 'package:path/path.dart' as p;
import 'package:tuple/tuple.dart';
import 'package:universal_io/io.dart' as io;
import 'file.dart';
class FileStorage with ChangeNotifier {
late final String repoPath;
final BlobCTimeBuilder blobCTimeBuilder;
final FileMTimeBuilder fileMTimeBuilder;
var _dateTime = DateTime.now();
DateTime get dateTime => _dateTime;
var head = GitHash.zero();
FileStorage({
required String repoPath,
required this.blobCTimeBuilder,
required this.fileMTimeBuilder,
}) {
this.repoPath =
repoPath.endsWith(p.separator) ? repoPath : repoPath + p.separator;
}
Future<File> load(String filePath) async {
assert(!filePath.startsWith(p.separator));
var fullFilePath = p.join(repoPath, filePath);
assert(fileMTimeBuilder.map.isNotEmpty, "Trying to load $filePath");
assert(blobCTimeBuilder.map.isNotEmpty, "Trying to load $filePath");
var ioFile = io.File(fullFilePath);
var stat = ioFile.statSync();
if (stat.type == io.FileSystemEntityType.notFound) {
throw Exception("File note found - $fullFilePath");
}
if (stat.type != io.FileSystemEntityType.file) {
// FIXME: Better error!
throw Exception('File is not file. Is ${stat.type}');
}
var mTimeInfo = fileMTimeBuilder.info(filePath);
if (mTimeInfo == null) {
Log.e("Failed to build path: $filePath");
throw FileStorageCacheIncomplete(filePath);
}
var oid = mTimeInfo.hash;
var modified = mTimeInfo.dt;
assert(oid.isNotEmpty);
var created = blobCTimeBuilder.cTime(oid);
if (created == null) {
throw Exception('when can this happen?');
}
return File(
oid: oid,
filePath: filePath,
repoPath: repoPath,
fileLastModified: stat.modified,
created: created,
modified: modified,
);
}
Future<void> fill() async {
var rp = ReceivePort();
rp.listen((d) {
if (d is DateTime) {
_dateTime = d;
notifyListeners();
}
});
var resp = await compute(
_fillFileStorage,
_FillFileStorageParams(
rp.sendPort,
repoPath,
blobCTimeBuilder,
fileMTimeBuilder,
),
);
rp.close();
// FIXME: Handle this case of having an error!
assert(resp != null);
if (resp == null) return;
blobCTimeBuilder.update(resp.item1);
fileMTimeBuilder.update(resp.item2);
head = resp.item3;
notifyListeners();
}
@visibleForTesting
static Future<FileStorage> fake(String rootFolder) async {
assert(rootFolder.startsWith(p.separator));
GitRepository.init(rootFolder);
var blobVisitor = BlobCTimeBuilder();
var mTimeBuilder = FileMTimeBuilder();
try {
var repo = GitRepository.load(rootFolder);
var headHash = repo.headHash();
var multi = MultiTreeEntryVisitor([blobVisitor, mTimeBuilder]);
repo.visitTree(fromCommitHash: headHash, visitor: multi);
} catch (ex, stackTrace) {
Log.e("Failed to load repo or get headHash",
ex: ex, stacktrace: stackTrace);
}
// assert(!headHashR.isFailure, "Failed to get head hash");
var repoPath = rootFolder.endsWith(p.separator)
? rootFolder
: rootFolder + p.separator;
return FileStorage(
repoPath: repoPath,
blobCTimeBuilder: blobVisitor,
fileMTimeBuilder: mTimeBuilder,
);
}
@visibleForTesting
Future<void> reload() async {
await fill();
}
}
class FileStorageCacheIncomplete implements Exception {
final String path;
FileStorageCacheIncomplete(this.path);
}
typedef _FillFileStorageParams
= Tuple4<SendPort, String, BlobCTimeBuilder, FileMTimeBuilder>;
typedef _FillFileStorageOutput
= Tuple3<BlobCTimeBuilder, FileMTimeBuilder, GitHash>;
_FillFileStorageOutput? _fillFileStorage(_FillFileStorageParams params) {
var sendPort = params.item1;
var repoPath = params.item2;
var blobCTimeBuilder = params.item3;
var fileMTimeBuilder = params.item4;
var dateTime = DateTime.now();
var visitor = MultiTreeEntryVisitor(
[blobCTimeBuilder, fileMTimeBuilder],
afterCommitCallback: (commit) {
var commitDt = commit.author.date;
if (commitDt.isBefore(dateTime)) {
dateTime = commitDt;
sendPort.send(dateTime);
}
},
);
var gitRepo = GitRepository.load(repoPath);
try {
var head = gitRepo.headHash();
Log.d("Got HEAD: $head");
gitRepo.visitTree(fromCommitHash: head, visitor: visitor);
return _FillFileStorageOutput(blobCTimeBuilder, fileMTimeBuilder, head);
} catch (ex, st) {
if (ex is GitRefNotFound) {
return _FillFileStorageOutput(
blobCTimeBuilder,
fileMTimeBuilder,
GitHash.zero(),
);
}
logException(ex, st);
return null;
}
}