Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/log refactor #20

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:async';
import 'package:example/models/user.dart';
import 'package:flutter/material.dart';
import 'package:loon/loon.dart';
import 'package:loon/persistor/file_persistor/file_persistor.dart';
import 'package:loon/persistor/sqlite_persistor/sqlite_persistor.dart';
import 'package:uuid/uuid.dart';

const uuid = Uuid();
Expand All @@ -12,7 +14,8 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();

Loon.configure(
persistor: Persistor.current(),
persistor: SqlitePersistor(),
enableLogging: true,
);

await logger.measure('Hydrate', () => Loon.hydrate());
Expand Down Expand Up @@ -191,9 +194,9 @@ class _MyHomePageState extends State<MyHomePage> {
),
const SizedBox(width: 24),
FloatingActionButton.extended(
label: const Text('Load test (10000)'),
label: const Text('Load test (50000)'),
onPressed: () {
for (int i = 0; i < 10000; i++) {
for (int i = 0; i < 50000; i++) {
final id = uuid.v4();
UserModel.store.doc(id).create(UserModel(name: 'User $id'));
}
Expand Down
18 changes: 14 additions & 4 deletions lib/collection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ class _RootCollection extends StoreReference {
final String path = _rootKey;
}

class Collection<T> implements Queryable<T>, StoreReference {
class Collection<T> implements StoreReference, Queryable<T> {
final String parent;
final String name;
final FromJson<T>? fromJson;
final ToJson<T>? toJson;
final PersistorSettings? persistorSettings;

late final PersistorSettings? persistorSettings;

/// Returns the set of documents that the document associated with the given
/// [DocumentSnapshot] is dependent on.
Expand All @@ -28,8 +29,17 @@ class Collection<T> implements Queryable<T>, StoreReference {
this.fromJson,
this.toJson,
this.dependenciesBuilder,
this.persistorSettings,
});
PersistorSettings? persistorSettings,
}) {
this.persistorSettings = switch (persistorSettings) {
PathPersistorSettings _ => persistorSettings,
// If the persistor settings are not yet associated with a path, then if a value key
// is provided, then the settings are updated to having been applied at the collection's path.
PersistorSettings(key: final PersistorValueKey _) =>
PathPersistorSettings(settings: persistorSettings, ref: this),
_ => persistorSettings,
};
}

static Collection<S> fromPath<S>(
String path, {
Expand Down
11 changes: 7 additions & 4 deletions lib/document.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class Document<T> implements StoreReference {
final FromJson<T>? fromJson;
final ToJson<T>? toJson;
final DependenciesBuilder<T>? dependenciesBuilder;
late final DocumentPersistorSettings? persistorSettings;

late final PathPersistorSettings? persistorSettings;

Document(
this.parent,
Expand All @@ -17,10 +18,12 @@ class Document<T> implements StoreReference {
PersistorSettings? persistorSettings,
}) {
this.persistorSettings = switch (persistorSettings) {
DocumentPersistorSettings _ => persistorSettings,
PathPersistorSettings _ => persistorSettings,
// If the persistor settings are not yet associated with a path, then the settings
// are updated to having been applied at the document's path.
PersistorSettings _ =>
DocumentPersistorSettings(settings: persistorSettings, doc: this),
_ => null,
PathPersistorSettings(settings: persistorSettings, ref: this),
_ => persistorSettings,
};
}

Expand Down
22 changes: 5 additions & 17 deletions lib/loon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart' hide Key;
import 'package:loon/persistor/file_persistor/file_persistor.dart';
import 'package:loon/persistor/indexed_db_persistor/indexed_db_persistor.dart';
import 'package:loon/persistor/sqlite_persistor/sqlite_persistor.dart';
import 'package:uuid/uuid.dart';
import 'dart:collection';

Expand Down Expand Up @@ -38,14 +39,9 @@ part 'extensions/iterable.dart';
class Loon {
static final Loon _instance = Loon._();

Loon._() {
_logger = Logger('Loon', output: (message) {
if (_isLoggingEnabled && kDebugMode) {
// ignore: avoid_print
print(message);
}
});
}
Loon._();

static final Logger logger = Logger('Loon');

/// The store of document snapshots indexed by document path.
final documentStore = ValueStore<DocumentSnapshot>();
Expand All @@ -56,10 +52,6 @@ class Loon {

PersistManager? persistManager;

late final Logger _logger;

bool _isLoggingEnabled = false;

bool get _isGlobalPersistenceEnabled {
return persistManager?.settings.enabled ?? false;
}
Expand Down Expand Up @@ -222,7 +214,7 @@ class Loon {
Persistor? persistor,
bool enableLogging = false,
}) {
_instance._isLoggingEnabled = enableLogging;
logger.enabled = kDebugMode && enableLogging;

if (persistor != null) {
_instance.persistManager = PersistManager(persistor: persistor);
Expand Down Expand Up @@ -318,10 +310,6 @@ class Loon {
_instance.broadcastManager.unsubscribe();
}

static Logger get logger {
return _instance._logger;
}

static PersistorSettings? get persistorSettings {
return _instance.persistManager?.settings;
}
Expand Down
14 changes: 12 additions & 2 deletions lib/persistor/data_store.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:convert';

import 'package:loon/loon.dart';
import 'package:loon/persistor/data_store_encrypter.dart';

Expand All @@ -17,8 +19,8 @@ abstract class DataStoreConfig {
required this.delete,
required bool encrypted,
required DataStoreEncrypter encrypter,
final Logger? logger,
}) : logger = logger ?? Logger('DataStore:$name');
required Logger logger,
}) : logger = logger.child('DataStore:$name');
}

class DataStore {
Expand All @@ -38,6 +40,10 @@ class DataStore {
return _store.isEmpty;
}

int get size {
return (utf8.encode(jsonEncode(_store.inspect())).length / 1000).round();
}

/// Returns a map of the subset of documents in the store under the given path.
/// Data for the given path can exist in two different places:
/// 1. It necessarily exists in all of the value stores resolved under the given path.
Expand Down Expand Up @@ -143,6 +149,10 @@ class DataStore {

if (hydratedStore != null) {
_store = hydratedStore;

if (logger.enabled) {
logger.log('Hydrated ${size}KB');
}
}

isHydrated = true;
Expand Down
88 changes: 72 additions & 16 deletions lib/persistor/data_store_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:async';
import 'package:loon/loon.dart';
import 'package:loon/persistor/data_store.dart';
import 'package:loon/persistor/data_store_encrypter.dart';
import 'package:loon/persistor/data_store_persistence_payload.dart';
import 'package:loon/persistor/data_store_resolver.dart';
import 'package:loon/persistor/lock.dart';

Expand All @@ -15,8 +14,7 @@ class DataStoreManager {

final void Function()? onSync;

final void Function(String text) onLog;

final Logger logger;
final DataStoreFactory factory;

/// Persistors generally have more optimal ways of clearing all stores at once than just iterating
Expand All @@ -33,8 +31,6 @@ class DataStoreManager {
/// operation and conversely blocks a sync from starting until the ongoing operation holding the lock has finished.
final _syncLock = Lock();

late final _logger = Logger('DataStoreManager', output: onLog);

/// The sync timer is used to throttle syncing changes to the file system using
/// the given [persistenceThrottle]. After an that mutates the file system operation runs, it schedules
/// a sync to run on a timer. When the sync runs, it acquires the [_syncLock], blocking any operations
Expand All @@ -51,15 +47,16 @@ class DataStoreManager {
DataStoreManager({
required this.persistenceThrottle,
required this.onSync,
required this.onLog,
required this.settings,
required this.factory,
required this.resolverConfig,
required Future<void> Function() clearAll,
required Future<List<String>> Function() getAll,
required Logger logger,
}) : _clearAll = clearAll,
_getAll = getAll,
resolver = DataStoreResolver(resolverConfig);
logger = logger.child('DataStoreManager'),
resolver = DataStoreResolver(resolverConfig)..logger = logger;

void _cancelSync() {
_syncTimer?.cancel();
Expand All @@ -70,10 +67,69 @@ class DataStoreManager {
_syncTimer ??= Timer(persistenceThrottle, _sync);
}

// Builds a local resolver for the set of documents being persisted in the current batch.
// Constructing a local resolver ensures that all documents in the batch can lookup accurate persistence keys.
//
// Ex. If an update to users__1__friends__1 which resolves to persistence key "users" at resolver path "users"
// is made redundant by a subsequent update to document users__1 that specifies that the persistence key at
// resolver path "users" is now "other_users", then the previous update to users__1__friends__1 is incorrect.
ValueStore<String> _buildLocalResolver(List<Document> docs) {
final resolver = ValueStore<String>();
final globalPersistorSettings = Loon.persistorSettings;

final defaultKey = switch (globalPersistorSettings) {
PersistorSettings(key: PersistorValueKey key) => key,
_ => Persistor.defaultKey,
};
resolver.write(ValueStore.root, defaultKey.value);

for (final doc in docs) {
final pathSettings = doc.persistorSettings;

if (pathSettings != null) {
switch (pathSettings) {
case PathPersistorSettings(
ref: final ref,
key: PersistorValueKey key
):
resolver.write(ref.path, key.value);
break;
case PathPersistorSettings(
ref: Document doc,
key: PersistorBuilderKey keyBuilder,
):
final path = doc.path;
final snap = doc.get();

if (snap != null) {
resolver.write(path, (keyBuilder as dynamic)(snap));
}

break;
}
} else if (globalPersistorSettings is PersistorSettings) {
switch (globalPersistorSettings) {
case PersistorSettings(key: PersistorBuilderKey keyBuilder):
final path = doc.path;
final snap = doc.get();

if (snap != null) {
resolver.write(path, (keyBuilder as dynamic)(snap));
}
break;
default:
break;
}
}
}

return resolver;
}

/// Syncs all data stores, persisting dirty ones and deleting ones that can now be removed.
Future<void> _sync() {
return _syncLock.run(() {
return _logger.measure('Sync', () async {
return logger.measure('Sync', () async {
final dirtyStores =
index.values.where((dataStore) => dataStore.isDirty);

Expand Down Expand Up @@ -186,20 +242,18 @@ class DataStoreManager {
);
}

Future<void> persist(DataStorePersistencePayload payload) {
Future<void> persist(List<Document> docs) {
return _syncLock.run(() async {
final localResolver = payload.resolver;
final docs = payload.persistenceDocs;

Set<DualDataStore> dataStores = {};
Map<String, List<DualDataStore>> pathDataStores = {};

final localResolver = _buildLocalResolver(docs);

// Pre-calculate and hydrate all resolved file data stores relevant to the updated documents.
for (final doc in docs) {
final docPath = doc.path;
final docData = doc.data;

if (docData == null) {
if (!doc.exists()) {
final stores = pathDataStores[docPath] = _resolveDataStores(docPath);
dataStores.addAll(stores);
} else {
Expand All @@ -223,8 +277,10 @@ class DataStoreManager {

for (final doc in docs) {
final docPath = doc.path;
final docData = doc.data;
final encrypted = doc.encrypted;
final docData = doc.getSerialized();
final encrypted = doc.persistorSettings?.encrypted ??
Loon.persistorSettings?.encrypted ??
false;

// If the document has been deleted, then clear its data recursively from each of its
// resolved data stores.
Expand Down
Loading
Loading