Avoid showing the "Reading Git History .." screen

This way startup feels much faster.
This commit is contained in:
Vishesh Handa
2021-12-10 10:57:20 +01:00
parent 99f215ce1f
commit 542fb8c36a
7 changed files with 101 additions and 31 deletions

View File

@ -4,8 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:dart_git/plumbing/git_hash.dart';
@ -123,7 +121,7 @@ class File {
static File fromProtoBuf(pb.File pbFile) {
return File(
repoPath: pbFile.repoPath,
oid: GitHash.fromBytes(Uint8List.fromList(pbFile.hash)),
oid: GitHash.fromBytes(pbFile.hash),
filePath: pbFile.filePath,
created: pbFile.created.toDateTime(),
modified: pbFile.modified.toDateTime(),

View File

@ -4,14 +4,13 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import 'dart:typed_data';
import 'package:dart_git/blob_ctime_builder.dart';
import 'package:dart_git/dart_git.dart';
import 'package:dart_git/file_mtime_builder.dart';
import 'package:dart_git/utils/date_time.dart';
import 'package:fixnum/fixnum.dart';
import 'package:path/path.dart' as p;
import 'package:tuple/tuple.dart';
import 'package:universal_io/io.dart' as io;
import 'package:gitjournal/core/file/file.dart';
@ -21,6 +20,7 @@ import 'package:gitjournal/logger/logger.dart';
class FileStorageCache {
final String cacheFolderPath;
var lastProcessedHead = GitHash.zero();
FileStorageCache(this.cacheFolderPath) {
assert(cacheFolderPath.startsWith(p.separator));
@ -42,18 +42,26 @@ class FileStorageCache {
}
Future<FileStorage> load(GitRepository gitRepo) async {
var blobVisitor = await _buildCTimeBuilder();
var mTimeBuilder = await _buildMTimeBuilder();
var blobVisitorTuple = await _buildCTimeBuilder();
var mTimeBuilderTuple = await _buildMTimeBuilder();
lastProcessedHead = blobVisitorTuple.item2;
if (mTimeBuilderTuple.item2 != lastProcessedHead) {
lastProcessedHead = GitHash.zero();
}
return FileStorage(
gitRepo: gitRepo,
blobCTimeBuilder: blobVisitor,
fileMTimeBuilder: mTimeBuilder,
blobCTimeBuilder: blobVisitorTuple.item1,
fileMTimeBuilder: mTimeBuilderTuple.item1,
);
}
Future<Result<void>> save(FileStorage fileStorage) async {
return catchAll(() async {
var headR = await fileStorage.gitRepo.headHash();
lastProcessedHead = headR.isFailure ? GitHash.zero() : headR.getOrThrow();
await _saveCTime(fileStorage.blobCTimeBuilder);
await _saveMTime(fileStorage.fileMTimeBuilder);
return Result(null);
@ -63,12 +71,12 @@ class FileStorageCache {
String get _cTimeFilePath => p.join(cacheFolderPath, 'blob_ctime_v1');
String get _mTimeFilePath => p.join(cacheFolderPath, 'file_mtime_v1');
Future<BlobCTimeBuilder> _buildCTimeBuilder() async {
Future<Tuple2<BlobCTimeBuilder, GitHash>> _buildCTimeBuilder() async {
var file = io.File(_cTimeFilePath);
var stat = file.statSync();
if (stat.type == io.FileSystemEntityType.notFound) {
return BlobCTimeBuilder();
return Tuple2(BlobCTimeBuilder(), GitHash.zero());
}
var size = (stat.size / 1024).toStringAsFixed(2);
@ -77,30 +85,29 @@ class FileStorageCache {
var buffer = await file.readAsBytes();
var data = pb.BlobCTimeBuilderData.fromBuffer(buffer);
var commitHashes = data.commitHashes
.map((bytes) => GitHash.fromBytes(Uint8List.fromList(bytes)))
.toSet();
var commitHashes =
data.commitHashes.map((bytes) => GitHash.fromBytes(bytes)).toSet();
var treeHashes = data.treeHashes
.map((bytes) => GitHash.fromBytes(Uint8List.fromList(bytes)))
.toSet();
var treeHashes =
data.treeHashes.map((bytes) => GitHash.fromBytes(bytes)).toSet();
var map = data.map
.map((hashStr, pbDt) => MapEntry(GitHash(hashStr), _fromProto(pbDt)));
return BlobCTimeBuilder(
var builder = BlobCTimeBuilder(
processedCommits: commitHashes,
processedTrees: treeHashes,
map: map,
);
return Tuple2(builder, GitHash.fromBytes(data.headHash));
}
Future<FileMTimeBuilder> _buildMTimeBuilder() async {
Future<Tuple2<FileMTimeBuilder, GitHash>> _buildMTimeBuilder() async {
var file = io.File(_mTimeFilePath);
var stat = file.statSync();
if (stat.type == io.FileSystemEntityType.notFound) {
return FileMTimeBuilder();
return Tuple2(FileMTimeBuilder(), GitHash.zero());
}
var size = (stat.size / 1024).toStringAsFixed(2);
@ -109,19 +116,19 @@ class FileStorageCache {
var buffer = await file.readAsBytes();
var data = pb.FileMTimeBuilderData.fromBuffer(buffer);
var commitHashes = data.commitHashes
.map((bytes) => GitHash.fromBytes(Uint8List.fromList(bytes)))
.toSet();
var commitHashes =
data.commitHashes.map((bytes) => GitHash.fromBytes(bytes)).toSet();
var map = data.map.map((filePath, pbInfo) {
var hash = GitHash.fromBytes(Uint8List.fromList(pbInfo.hash));
var hash = GitHash.fromBytes(pbInfo.hash);
var dt = _fromProto(pbInfo.dt);
var info = FileMTimeInfo(pbInfo.filePath, hash, dt);
return MapEntry(filePath, info);
});
return FileMTimeBuilder(processedCommits: commitHashes, map: map);
var builder = FileMTimeBuilder(processedCommits: commitHashes, map: map);
return Tuple2(builder, GitHash.fromBytes(data.headHash));
}
Future<void> _saveCTime(BlobCTimeBuilder builder) async {
@ -136,6 +143,7 @@ class FileStorageCache {
});
var data = pb.BlobCTimeBuilderData(
headHash: lastProcessedHead.bytes,
commitHashes: commitHashes,
treeHashes: treeHashes,
map: map,
@ -162,7 +170,11 @@ class FileStorageCache {
return MapEntry(filePath, info);
});
var data = pb.FileMTimeBuilderData(commitHashes: commitHashes, map: map);
var data = pb.FileMTimeBuilderData(
headHash: lastProcessedHead.bytes,
commitHashes: commitHashes,
map: map,
);
var file = io.File(_mTimeFilePath);
var _ = await file.writeAsBytes(data.writeToBuffer());

View File

@ -48,6 +48,13 @@ class BlobCTimeBuilderData extends $pb.GeneratedMessage {
valueFieldType: $pb.PbFieldType.OM,
valueCreator: TzDateTime.create,
packageName: const $pb.PackageName('gitjournal'))
..a<$core.List<$core.int>>(
4,
const $core.bool.fromEnvironment('protobuf.omit_field_names')
? ''
: 'headHash',
$pb.PbFieldType.OY,
protoName: 'headHash')
..hasRequiredFields = false;
BlobCTimeBuilderData._() : super();
@ -55,6 +62,7 @@ class BlobCTimeBuilderData extends $pb.GeneratedMessage {
$core.Iterable<$core.List<$core.int>>? commitHashes,
$core.Iterable<$core.List<$core.int>>? treeHashes,
$core.Map<$core.String, TzDateTime>? map,
$core.List<$core.int>? headHash,
}) {
final _result = create();
if (commitHashes != null) {
@ -66,6 +74,9 @@ class BlobCTimeBuilderData extends $pb.GeneratedMessage {
if (map != null) {
_result.map.addAll(map);
}
if (headHash != null) {
_result.headHash = headHash;
}
return _result;
}
factory BlobCTimeBuilderData.fromBuffer($core.List<$core.int> i,
@ -104,6 +115,18 @@ class BlobCTimeBuilderData extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
$core.Map<$core.String, TzDateTime> get map => $_getMap(2);
@$pb.TagNumber(4)
$core.List<$core.int> get headHash => $_getN(3);
@$pb.TagNumber(4)
set headHash($core.List<$core.int> v) {
$_setBytes(3, v);
}
@$pb.TagNumber(4)
$core.bool hasHeadHash() => $_has(3);
@$pb.TagNumber(4)
void clearHeadHash() => clearField(4);
}
class FileMTimeBuilderData extends $pb.GeneratedMessage {
@ -133,12 +156,20 @@ class FileMTimeBuilderData extends $pb.GeneratedMessage {
valueFieldType: $pb.PbFieldType.OM,
valueCreator: FileMTimeInfo.create,
packageName: const $pb.PackageName('gitjournal'))
..a<$core.List<$core.int>>(
4,
const $core.bool.fromEnvironment('protobuf.omit_field_names')
? ''
: 'headHash',
$pb.PbFieldType.OY,
protoName: 'headHash')
..hasRequiredFields = false;
FileMTimeBuilderData._() : super();
factory FileMTimeBuilderData({
$core.Iterable<$core.List<$core.int>>? commitHashes,
$core.Map<$core.String, FileMTimeInfo>? map,
$core.List<$core.int>? headHash,
}) {
final _result = create();
if (commitHashes != null) {
@ -147,6 +178,9 @@ class FileMTimeBuilderData extends $pb.GeneratedMessage {
if (map != null) {
_result.map.addAll(map);
}
if (headHash != null) {
_result.headHash = headHash;
}
return _result;
}
factory FileMTimeBuilderData.fromBuffer($core.List<$core.int> i,
@ -182,6 +216,18 @@ class FileMTimeBuilderData extends $pb.GeneratedMessage {
@$pb.TagNumber(3)
$core.Map<$core.String, FileMTimeInfo> get map => $_getMap(1);
@$pb.TagNumber(4)
$core.List<$core.int> get headHash => $_getN(2);
@$pb.TagNumber(4)
set headHash($core.List<$core.int> v) {
$_setBytes(2, v);
}
@$pb.TagNumber(4)
$core.bool hasHeadHash() => $_has(2);
@$pb.TagNumber(4)
void clearHeadHash() => clearField(4);
}
class TzDateTime extends $pb.GeneratedMessage {

View File

@ -27,6 +27,7 @@ const BlobCTimeBuilderData$json = const {
'6': '.gitjournal.BlobCTimeBuilderData.MapEntry',
'10': 'map'
},
const {'1': 'headHash', '3': 4, '4': 1, '5': 12, '10': 'headHash'},
],
'3': const [BlobCTimeBuilderData_MapEntry$json],
};
@ -50,7 +51,7 @@ const BlobCTimeBuilderData_MapEntry$json = const {
/// Descriptor for `BlobCTimeBuilderData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List blobCTimeBuilderDataDescriptor = $convert.base64Decode(
'ChRCbG9iQ1RpbWVCdWlsZGVyRGF0YRIiCgxjb21taXRIYXNoZXMYASADKAxSDGNvbW1pdEhhc2hlcxIeCgp0cmVlSGFzaGVzGAIgAygMUgp0cmVlSGFzaGVzEjsKA21hcBgDIAMoCzIpLmdpdGpvdXJuYWwuQmxvYkNUaW1lQnVpbGRlckRhdGEuTWFwRW50cnlSA21hcBpOCghNYXBFbnRyeRIQCgNrZXkYASABKAlSA2tleRIsCgV2YWx1ZRgCIAEoCzIWLmdpdGpvdXJuYWwuVHpEYXRlVGltZVIFdmFsdWU6AjgB');
'ChRCbG9iQ1RpbWVCdWlsZGVyRGF0YRIiCgxjb21taXRIYXNoZXMYASADKAxSDGNvbW1pdEhhc2hlcxIeCgp0cmVlSGFzaGVzGAIgAygMUgp0cmVlSGFzaGVzEjsKA21hcBgDIAMoCzIpLmdpdGpvdXJuYWwuQmxvYkNUaW1lQnVpbGRlckRhdGEuTWFwRW50cnlSA21hcBIaCghoZWFkSGFzaBgEIAEoDFIIaGVhZEhhc2gaTgoITWFwRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSLAoFdmFsdWUYAiABKAsyFi5naXRqb3VybmFsLlR6RGF0ZVRpbWVSBXZhbHVlOgI4AQ==');
@$core.Deprecated('Use fileMTimeBuilderDataDescriptor instead')
const FileMTimeBuilderData$json = const {
'1': 'FileMTimeBuilderData',
@ -64,6 +65,7 @@ const FileMTimeBuilderData$json = const {
'6': '.gitjournal.FileMTimeBuilderData.MapEntry',
'10': 'map'
},
const {'1': 'headHash', '3': 4, '4': 1, '5': 12, '10': 'headHash'},
],
'3': const [FileMTimeBuilderData_MapEntry$json],
};
@ -87,7 +89,7 @@ const FileMTimeBuilderData_MapEntry$json = const {
/// Descriptor for `FileMTimeBuilderData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List fileMTimeBuilderDataDescriptor = $convert.base64Decode(
'ChRGaWxlTVRpbWVCdWlsZGVyRGF0YRIiCgxjb21taXRIYXNoZXMYASADKAxSDGNvbW1pdEhhc2hlcxI7CgNtYXAYAyADKAsyKS5naXRqb3VybmFsLkZpbGVNVGltZUJ1aWxkZXJEYXRhLk1hcEVudHJ5UgNtYXAaUQoITWFwRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSLwoFdmFsdWUYAiABKAsyGS5naXRqb3VybmFsLkZpbGVNVGltZUluZm9SBXZhbHVlOgI4AQ==');
'ChRGaWxlTVRpbWVCdWlsZGVyRGF0YRIiCgxjb21taXRIYXNoZXMYASADKAxSDGNvbW1pdEhhc2hlcxI7CgNtYXAYAyADKAsyKS5naXRqb3VybmFsLkZpbGVNVGltZUJ1aWxkZXJEYXRhLk1hcEVudHJ5UgNtYXASGgoIaGVhZEhhc2gYBCABKAxSCGhlYWRIYXNoGlEKCE1hcEVudHJ5EhAKA2tleRgBIAEoCVIDa2V5Ei8KBXZhbHVlGAIgASgLMhkuZ2l0am91cm5hbC5GaWxlTVRpbWVJbmZvUgV2YWx1ZToCOAE=');
@$core.Deprecated('Use tzDateTimeDescriptor instead')
const TzDateTime$json = const {
'1': 'TzDateTime',

View File

@ -13,6 +13,7 @@ import 'package:collection/collection.dart';
import 'package:dart_git/config.dart';
import 'package:dart_git/dart_git.dart';
import 'package:dart_git/exceptions.dart';
import 'package:dart_git/plumbing/git_hash.dart';
import 'package:git_bindings/git_bindings.dart';
import 'package:path/path.dart' as p;
import 'package:sentry_flutter/sentry_flutter.dart';
@ -78,7 +79,7 @@ class GitJournalRepo with ChangeNotifier {
late NotesFolderFS notesFolder;
bool remoteGitRepoConfigured = false;
bool fileStorageCacheReady = false;
late bool fileStorageCacheReady;
static Future<GitJournalRepo> load(
{required String gitBaseDir,
@ -152,6 +153,9 @@ class GitJournalRepo with ChangeNotifier {
var fileStorageCache = FileStorageCache(cacheDir);
var fileStorage = await fileStorageCache.load(repo);
var headR = await repo.headHash();
var head = headR.isFailure ? GitHash.zero() : headR.getOrThrow();
return GitJournalRepo._internal(
repoPath: repoPath,
gitBaseDirectory: gitBaseDir,
@ -165,6 +169,7 @@ class GitJournalRepo with ChangeNotifier {
fileStorage: fileStorage,
fileStorageCache: fileStorageCache,
currentBranch: await repo.currentBranch().getOrThrow(),
headHash: head,
);
}
@ -181,6 +186,7 @@ class GitJournalRepo with ChangeNotifier {
required this.fileStorage,
required this.fileStorageCache,
required String? currentBranch,
required GitHash headHash,
}) {
_gitRepo = GitNoteRepository(gitRepoPath: repoPath, config: gitConfig);
notesFolder = NotesFolderFS.root(folderConfig, fileStorage);
@ -202,6 +208,8 @@ class GitJournalRepo with ChangeNotifier {
fileStorage: fileStorage,
);
fileStorageCacheReady = headHash == fileStorageCache.lastProcessedHead;
_loadFromCache();
_syncNotes();
}
@ -277,7 +285,7 @@ class GitJournalRepo with ChangeNotifier {
// Notify that the cache is ready
Log.i("Done building the FileStorageCache");
fileStorageCacheReady = true;
fileStorageCacheReady = fileStorageCache.lastProcessedHead == head;
notifyListeners();
}

View File

@ -12,11 +12,15 @@ message BlobCTimeBuilderData {
repeated bytes commitHashes = 1;
repeated bytes treeHashes = 2;
map<string, TzDateTime> map = 3;
bytes headHash = 4;
}
message FileMTimeBuilderData {
repeated bytes commitHashes = 1;
map<string, FileMTimeInfo> map = 3;
bytes headHash = 4;
}
message TzDateTime {

View File

@ -328,7 +328,7 @@ packages:
description:
path: "."
ref: HEAD
resolved-ref: "0369ae80dfde703b3a22ade5adf350800d649153"
resolved-ref: "6d4b7f26bc2c7b8c83ec7f608fe05eec561d6757"
url: "https://github.com/GitJournal/dart-git.git"
source: git
version: "0.0.2"