Skip to content

Commit

Permalink
Merge pull request #1141 from ardriveapp/dev
Browse files Browse the repository at this point in the history
PE-4001: Release Flutter App v2.3.0
  • Loading branch information
matibat authored Jun 12, 2023
2 parents 14dff08 + 613abd4 commit 0cd471e
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 21 deletions.
1 change: 1 addition & 0 deletions android/fastlane/metadata/android/en-US/changelogs/46.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Utilize cached metadata for Snapshot authoring
18 changes: 17 additions & 1 deletion lib/authentication/ardrive_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import 'package:ardrive/user/repositories/user_repository.dart';
import 'package:ardrive/user/user.dart';
import 'package:ardrive/utils/constants.dart';
import 'package:ardrive/utils/logger/logger.dart';
import 'package:ardrive/utils/metadata_cache.dart';
import 'package:ardrive/utils/secure_key_value_store.dart';
import 'package:arweave/arweave.dart';
import 'package:cryptography/cryptography.dart';
import 'package:stash_shared_preferences/stash_shared_preferences.dart';

import '../core/crypto/crypto.dart';

Expand All @@ -34,6 +36,7 @@ abstract class ArDriveAuth {
required SecureKeyValueStore secureKeyValueStore,
required ArConnectService arConnectService,
required DatabaseHelpers databaseHelpers,
MetadataCache? metadataCache,
}) =>
_ArDriveAuth(
arweave: arweave,
Expand All @@ -43,6 +46,7 @@ abstract class ArDriveAuth {
biometricAuthentication: biometricAuthentication,
secureKeyValueStore: secureKeyValueStore,
arConnectService: arConnectService,
metadataCache: metadataCache,
);
}

Expand All @@ -55,13 +59,15 @@ class _ArDriveAuth implements ArDriveAuth {
required SecureKeyValueStore secureKeyValueStore,
required ArConnectService arConnectService,
required DatabaseHelpers databaseHelpers,
MetadataCache? metadataCache,
}) : _arweave = arweave,
_crypto = crypto,
_databaseHelpers = databaseHelpers,
_arConnectService = arConnectService,
_secureKeyValueStore = secureKeyValueStore,
_biometricAuthentication = biometricAuthentication,
_userRepository = userRepository;
_userRepository = userRepository,
_maybeMetadataCache = metadataCache;

final UserRepository _userRepository;
final ArweaveService _arweave;
Expand All @@ -70,6 +76,7 @@ class _ArDriveAuth implements ArDriveAuth {
final SecureKeyValueStore _secureKeyValueStore;
final ArConnectService _arConnectService;
final DatabaseHelpers _databaseHelpers;
MetadataCache? _maybeMetadataCache;

User? _currentUser;

Expand All @@ -87,6 +94,13 @@ class _ArDriveAuth implements ArDriveAuth {
_currentUser = user;
}

Future<MetadataCache> get _metadataCache async {
_maybeMetadataCache ??= await MetadataCache.fromCacheStore(
await newSharedPreferencesCacheStore(),
);
return _maybeMetadataCache!;
}

final StreamController<User?> _userStreamController =
StreamController<User?>.broadcast();

Expand Down Expand Up @@ -185,6 +199,8 @@ class _ArDriveAuth implements ArDriveAuth {
}

await _databaseHelpers.deleteAllTables();

(await _metadataCache).clear();
} catch (e) {
logger.e('Failed to logout user', e);
throw AuthenticationFailedException('Failed to logout user');
Expand Down
4 changes: 3 additions & 1 deletion lib/authentication/login/blocs/login_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
}

Future<void> _handleForgetWalletEvent(
ForgetWallet event, Emitter<LoginState> emit) async {
ForgetWallet event,
Emitter<LoginState> emit,
) async {
if (await _arDriveAuth.isUserLoggedIn()) {
await _arDriveAuth.logout();
}
Expand Down
21 changes: 17 additions & 4 deletions lib/blocs/create_snapshot/create_snapshot_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:ardrive/services/arweave/arweave.dart';
import 'package:ardrive/services/pst/pst.dart';
import 'package:ardrive/utils/ar_cost_to_usd.dart';
import 'package:ardrive/utils/html/html_util.dart';
import 'package:ardrive/utils/metadata_cache.dart';
import 'package:ardrive/utils/snapshots/height_range.dart';
import 'package:ardrive/utils/snapshots/range.dart';
import 'package:ardrive/utils/snapshots/snapshot_item_to_be_created.dart';
Expand All @@ -20,6 +21,7 @@ import 'package:arweave/utils.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:stash_shared_preferences/stash_shared_preferences.dart';
import 'package:uuid/uuid.dart';

part 'create_snapshot_state.dart';
Expand Down Expand Up @@ -366,12 +368,23 @@ class CreateSnapshotCubit extends Cubit<CreateSnapshotState> {
await _driveDao.driveById(driveId: _driveId).getSingleOrNull();
final isPrivate = drive != null && drive.privacy != DrivePrivacy.public;

// gather from arweave if not cached
final Uint8List entityJsonData = await _arweave.dataFromTxId(
txId,
null, // key is null because we don't re-encrypt the snapshot data
final metadataCache = await MetadataCache.fromCacheStore(
await newSharedPreferencesCacheStore(),
);

final Uint8List? cachedMetadata = await metadataCache.get(txId);

final Uint8List entityJsonData = cachedMetadata ??
await _arweave.dataFromTxId(
txId,
null, // key is null because we don't re-encrypt the snapshot data
);

if (cachedMetadata == null) {
// Write to the cache the data we just fetched
await metadataCache.put(txId, entityJsonData);
}

if (isPrivate) {
final safeEntityDataFromArweave = Uint8List.fromList(
utf8.encode(base64Encode(entityJsonData)),
Expand Down
38 changes: 25 additions & 13 deletions lib/services/arweave/arweave_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:ardrive/utils/extensions.dart';
import 'package:ardrive/utils/graphql_retry.dart';
import 'package:ardrive/utils/http_retry.dart';
import 'package:ardrive/utils/internet_checker.dart';
import 'package:ardrive/utils/logger/logger.dart';
import 'package:ardrive/utils/metadata_cache.dart';
import 'package:ardrive/utils/snapshots/snapshot_drive_history.dart';
import 'package:ardrive/utils/snapshots/snapshot_item.dart';
import 'package:ardrive_http/ardrive_http.dart';
Expand All @@ -21,6 +23,7 @@ import 'package:drift/drift.dart';
import 'package:http/http.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:retry/retry.dart';
import 'package:stash_shared_preferences/stash_shared_preferences.dart';

import 'error/gateway_response_handler.dart';

Expand Down Expand Up @@ -51,15 +54,15 @@ class ArweaveService {
GatewayResponseHandler(),
HttpRetryOptions(onRetry: (exception) {
if (exception is GatewayError) {
print(
logger.i(
'Retrying for ${exception.runtimeType} exception\n'
'for route ${exception.requestUrl}\n'
'and status code ${exception.statusCode}',
);
return;
}

print('Retrying for unknown exception: ${exception.toString()}');
logger.w('Retrying for unknown exception: ${exception.toString()}');
}, retryIf: (exception) {
return exception is! RateLimitError;
}));
Expand Down Expand Up @@ -242,7 +245,7 @@ class ArweaveService {

// don't fetch data for snapshots
if (isSnapshot) {
print('skipping unnecessary request for snapshot data');
logger.d('skipping unnecessary request for snapshot data');
return Uint8List(0);
}

Expand All @@ -255,6 +258,10 @@ class ArweaveService {
),
);

final metadataCache = await MetadataCache.fromCacheStore(
await newSharedPreferencesCacheStore(),
);

final blockHistory = <BlockEntities>[];

for (var i = 0; i < entityTxs.length; i++) {
Expand All @@ -276,6 +283,8 @@ class ArweaveService {
final entityResponse = responses[i];
final rawEntityData = entityResponse;

await metadataCache.put(transaction.id, rawEntityData);

Entity? entity;
if (entityType == EntityType.drive) {
entity = await DriveEntity.fromTransaction(
Expand Down Expand Up @@ -304,12 +313,12 @@ class ArweaveService {

// If there are errors in parsing the entity, ignore it.
} on EntityTransactionParseException catch (parseException) {
print(
logger.e(
'Failed to parse transaction '
'with id ${parseException.transactionId}',
);
} on GatewayError catch (fetchException) {
print(
logger.e(
'Failed to fetch entity data with the exception ${fetchException.runtimeType}'
'for transaction ${transaction.id}, '
'with status ${fetchException.statusCode} '
Expand Down Expand Up @@ -461,8 +470,9 @@ class ArweaveService {
() async => await Future.wait(
driveTxs.map((e) => client.api.getSandboxedTx(e.id)),
), onRetry: (Exception err) {
print(
'Retrying for get unique user drive entities on Exception: ${err.toString()}');
logger.i(
'Retrying for get unique user drive entities on Exception: ${err.toString()}',
);
});

final drivesById = <String?, DriveEntity>{};
Expand Down Expand Up @@ -497,16 +507,18 @@ class ArweaveService {

// If there's an error parsing the drive entity, just ignore it.
} on EntityTransactionParseException catch (parseException) {
print(
logger.e(
'Failed to parse transaction '
'with id ${parseException.transactionId}',
);
}
}
return drivesWithKey;
} catch (e, stacktrace) {
print(
'An error occurs getting the unique user drive entities. Exception: ${e.toString()} stacktrace: ${stacktrace.toString()}');
logger.e(
'An error occurred when getting the unique user drive entities.'
' Exception: ${e.toString()} stacktrace: ${stacktrace.toString()}',
);
rethrow;
}
}
Expand Down Expand Up @@ -557,7 +569,7 @@ class ArweaveService {
return await DriveEntity.fromTransaction(
fileTx, _crypto, fileDataRes.bodyBytes, driveKey);
} on EntityTransactionParseException catch (parseException) {
print(
logger.e(
'Failed to parse transaction '
'with id ${parseException.transactionId}',
);
Expand Down Expand Up @@ -719,7 +731,7 @@ class ArweaveService {
crypto: _crypto,
);
} on EntityTransactionParseException catch (parseException) {
print(
logger.e(
'Failed to parse transaction '
'with id ${parseException.transactionId}',
);
Expand Down Expand Up @@ -836,7 +848,7 @@ class ArweaveService {
try {
await Future.wait(confirmationFutures);
} catch (e) {
print('Error getting transactions confirmations on exception: $e');
logger.e('Error getting transactions confirmations on exception: $e');
rethrow;
}

Expand Down
99 changes: 99 additions & 0 deletions lib/utils/metadata_cache.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'dart:typed_data';

import 'package:ardrive/utils/logger/logger.dart';
import 'package:stash/stash_api.dart';

const defaultMaxEntries = 550;
const defaultCacheName = 'metadata-cache';

class MetadataCache {
final Cache<Uint8List> _cache;
final int _maxEntries;

MetadataCache(this._cache, {int maxEntries = defaultMaxEntries})
: _maxEntries = maxEntries;

static Future<MetadataCache> fromCacheStore(
CacheStore store, {
int maxEntries = defaultMaxEntries,
}) async {
final cache = await _newCacheFromStore(store, maxEntries: maxEntries);
return MetadataCache(cache, maxEntries: maxEntries);
}

Future<bool> put(String key, Uint8List data) async {
if (await isFull) {
return false;
}

// FIXME: check for quota before attempting to write to cache
try {
logger.d('Putting $key in metadata cache');
await _cache.putIfAbsent(key, data);
} catch (e, s) {
logger.e('Failed to put $key in metadata cache', e, s);
return false;
}

if (await isFull) {
logger.i('Metadata cache is now full and will not accept new entries');
}

return true;
}

Future<Uint8List?> get(String key) async {
try {
final value = await _cache.get(key);
if (value != null) {
logger.d('Cache hit for $key in metadata cache');
} else {
logger.d('Cache miss for $key in metadata cache');
}
return value;
} catch (e, s) {
logger.e('Failed to get $key from metadata cache', e, s);
return null;
}
}

Future<void> remove(String key) async {
logger.d('Removing $key from metadata cache');
return _cache.remove(key);
}

Future<void> clear() async {
logger.d('Clearing metadata cache');
return _cache.clear();
}

Future<Iterable<String>> get keys async {
return _cache.keys;
}

Future<bool> get isFull async {
final size = await this.size;
final isFull = size >= _maxEntries;

return isFull;
}

Future<int> get size async {
return _cache.size;
}

static Future<Cache<Uint8List>> _newCacheFromStore(
CacheStore store, {
required int maxEntries,
}) async {
logger.d('Creating metadata cache with max entries: $maxEntries');

return store.cache<Uint8List>(
name: defaultCacheName,
maxEntries: maxEntries,

// See: https://pub.dev/packages/stash#eviction-policies
evictionPolicy: null,
);
}
}
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.3"
stash_shared_preferences:
dependency: "direct main"
description:
name: stash_shared_preferences
sha256: be81ec8f69c0a67475b77509558f78385dd09ee16493bb985312aad0442d4d59
url: "https://pub.dev"
source: hosted
version: "4.6.2"
stream_channel:
dependency: transitive
description:
Expand Down
Loading

0 comments on commit 0cd471e

Please sign in to comment.