improved settings replication

This commit is contained in:
Fabian Freund
2025-09-25 09:36:39 +02:00
parent 853aa1ec16
commit e0deb5b75c
6 changed files with 203 additions and 14 deletions
@@ -18,21 +18,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/tor/domain/repositories/tor_proxy.dart';
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
import 'package:weblibre/features/user/data/models/tor_settings.dart';
import 'package:weblibre/features/user/domain/repositories/tor_settings.dart'; import 'package:weblibre/features/user/domain/repositories/tor_settings.dart';
part 'proxy_settings_replication.g.dart'; part 'proxy_settings_replication.g.dart';
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
class ProxySettingsReplication extends _$ProxySettingsReplication { class ProxySettingsReplication extends _$ProxySettingsReplication {
final _service = GeckoContainerProxyService();
@override @override
void build() { void build() {
ref.listen( ref.listen(
@@ -40,7 +39,9 @@ class ProxySettingsReplication extends _$ProxySettingsReplication {
torProxyServiceProvider.select((data) => data.value), torProxyServiceProvider.select((data) => data.value),
(previous, next) async { (previous, next) async {
if (next != null) { if (next != null) {
await _service.setProxyPort(next); await ref
.read(torProxyRepositoryProvider.notifier)
.setProxyPort(next);
} }
}, },
onError: (error, stackTrace) { onError: (error, stackTrace) {
@@ -62,13 +63,15 @@ class ProxySettingsReplication extends _$ProxySettingsReplication {
for (final container in next.value!) { for (final container in next.value!) {
if (container.metadata.contextualIdentity.isNotEmpty) { if (container.metadata.contextualIdentity.isNotEmpty) {
if (container.metadata.useProxy) { if (container.metadata.useProxy) {
await _service.addContainerProxy( await ref
container.metadata.contextualIdentity!, .read(torProxyRepositoryProvider.notifier)
); .addContainerProxy(container.metadata.contextualIdentity!);
} else { } else {
await _service.removeContainerProxy( await ref
container.metadata.contextualIdentity!, .read(torProxyRepositoryProvider.notifier)
); .removeContainerProxy(
container.metadata.contextualIdentity!,
);
} }
} }
} }
@@ -90,9 +93,39 @@ class ProxySettingsReplication extends _$ProxySettingsReplication {
), ),
(previous, next) async { (previous, next) async {
if (next) { if (next) {
await _service.addContainerProxy('private'); await ref
.read(torProxyRepositoryProvider.notifier)
.addContainerProxy('private');
} else { } else {
await _service.removeContainerProxy('private'); await ref
.read(torProxyRepositoryProvider.notifier)
.removeContainerProxy('private');
}
},
onError: (error, stackTrace) {
logger.e(
'Error listening to generalSettingsRepositoryProvider',
error: error,
stackTrace: stackTrace,
);
},
);
ref.listen(
fireImmediately: true,
torSettingsWithDefaultsProvider.select(
(value) => value.proxyRegularTabsMode,
),
(previous, next) async {
switch (next) {
case TorRegularTabProxyMode.container:
await ref
.read(torProxyRepositoryProvider.notifier)
.removeContainerProxy('general');
case TorRegularTabProxyMode.all:
await ref
.read(torProxyRepositoryProvider.notifier)
.addContainerProxy('general');
} }
}, },
onError: (error, stackTrace) { onError: (error, stackTrace) {
@@ -42,7 +42,7 @@ final class ProxySettingsReplicationProvider
} }
String _$proxySettingsReplicationHash() => String _$proxySettingsReplicationHash() =>
r'787545f882b97274dc67007a6404f170d40bdb64'; r'46063662e3aca4f38a6876f4265ada9a87bde6dc';
abstract class _$ProxySettingsReplication extends $Notifier<void> { abstract class _$ProxySettingsReplication extends $Notifier<void> {
void build(); void build();
@@ -0,0 +1,58 @@
import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart';
part 'tor_proxy.g.dart';
@Riverpod(keepAlive: true)
class TorProxyRepository extends _$TorProxyRepository {
final _service = GeckoContainerProxyService();
final _serviceLock = Lock();
Future<void> setProxyPort(int port) {
return _serviceLock.synchronized(() async {
await _waitHealthcheck().timeout(const Duration(seconds: 10));
return _service.setProxyPort(port);
});
}
Future<void> addContainerProxy(String contextId) {
return _serviceLock.synchronized(() async {
await _waitHealthcheck().timeout(const Duration(seconds: 10));
return _service.addContainerProxy(contextId);
});
}
Future<void> removeContainerProxy(String contextId) {
return _serviceLock.synchronized(() async {
await _waitHealthcheck().timeout(const Duration(seconds: 10));
return _service.removeContainerProxy(contextId);
});
}
Future<void> _waitHealthcheck({
Duration timeout = const Duration(seconds: 15),
}) async {
final startTime = DateTime.now();
var healthy = await _service.healthcheck();
while (!healthy) {
if (DateTime.now().difference(startTime) > timeout) {
throw TimeoutException('Timed out waiting for proxy service');
}
await Future.delayed(const Duration(milliseconds: 25));
healthy = await _service.healthcheck();
}
}
@override
void build() {
return;
}
}
@@ -0,0 +1,64 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tor_proxy.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(TorProxyRepository)
const torProxyRepositoryProvider = TorProxyRepositoryProvider._();
final class TorProxyRepositoryProvider
extends $NotifierProvider<TorProxyRepository, void> {
const TorProxyRepositoryProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'torProxyRepositoryProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$torProxyRepositoryHash();
@$internal
@override
TorProxyRepository create() => TorProxyRepository();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
}
String _$torProxyRepositoryHash() =>
r'dd565140620a1f66090e84e541d5ea8e34ab0094';
abstract class _$TorProxyRepository extends $Notifier<void> {
void build();
@$mustCallSuper
@override
void runBuild() {
build();
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleValue(ref, null);
}
}
@@ -25,15 +25,19 @@ part 'tor_settings.g.dart';
enum TorConnectionConfig { auto, direct, obfs4, snowflake } enum TorConnectionConfig { auto, direct, obfs4, snowflake }
enum TorRegularTabProxyMode { container, all }
@CopyWith() @CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults') @JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
class TorSettings with FastEquatable { class TorSettings with FastEquatable {
final TorRegularTabProxyMode proxyRegularTabsMode;
final bool proxyPrivateTabsTor; final bool proxyPrivateTabsTor;
final TorConnectionConfig config; final TorConnectionConfig config;
final bool requireBridge; final bool requireBridge;
final bool fetchRemoteBridges; final bool fetchRemoteBridges;
TorSettings({ TorSettings({
required this.proxyRegularTabsMode,
required this.proxyPrivateTabsTor, required this.proxyPrivateTabsTor,
required this.config, required this.config,
required this.requireBridge, required this.requireBridge,
@@ -41,11 +45,14 @@ class TorSettings with FastEquatable {
}); });
TorSettings.withDefaults({ TorSettings.withDefaults({
TorRegularTabProxyMode? proxyRegularTabsMode,
bool? proxyPrivateTabsTor, bool? proxyPrivateTabsTor,
TorConnectionConfig? config, TorConnectionConfig? config,
bool? requireBridge, bool? requireBridge,
bool? fetchRemoteBridges, bool? fetchRemoteBridges,
}) : proxyPrivateTabsTor = proxyPrivateTabsTor ?? false, }) : proxyRegularTabsMode =
proxyRegularTabsMode ?? TorRegularTabProxyMode.container,
proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
config = config ?? TorConnectionConfig.auto, config = config ?? TorConnectionConfig.auto,
requireBridge = requireBridge ?? false, requireBridge = requireBridge ?? false,
fetchRemoteBridges = fetchRemoteBridges ?? true; fetchRemoteBridges = fetchRemoteBridges ?? true;
@@ -57,6 +64,7 @@ class TorSettings with FastEquatable {
@override @override
List<Object?> get hashParameters => [ List<Object?> get hashParameters => [
proxyRegularTabsMode,
proxyPrivateTabsTor, proxyPrivateTabsTor,
config, config,
requireBridge, requireBridge,
@@ -7,6 +7,8 @@ part of 'tor_settings.dart';
// ************************************************************************** // **************************************************************************
abstract class _$TorSettingsCWProxy { abstract class _$TorSettingsCWProxy {
TorSettings proxyRegularTabsMode(TorRegularTabProxyMode proxyRegularTabsMode);
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor); TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor);
TorSettings config(TorConnectionConfig config); TorSettings config(TorConnectionConfig config);
@@ -23,6 +25,7 @@ abstract class _$TorSettingsCWProxy {
/// TorSettings(...).copyWith(id: 12, name: "My name") /// TorSettings(...).copyWith(id: 12, name: "My name")
/// ``` /// ```
TorSettings call({ TorSettings call({
TorRegularTabProxyMode proxyRegularTabsMode,
bool proxyPrivateTabsTor, bool proxyPrivateTabsTor,
TorConnectionConfig config, TorConnectionConfig config,
bool requireBridge, bool requireBridge,
@@ -37,6 +40,11 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
final TorSettings _value; final TorSettings _value;
@override
TorSettings proxyRegularTabsMode(
TorRegularTabProxyMode proxyRegularTabsMode,
) => call(proxyRegularTabsMode: proxyRegularTabsMode);
@override @override
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) => TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) =>
call(proxyPrivateTabsTor: proxyPrivateTabsTor); call(proxyPrivateTabsTor: proxyPrivateTabsTor);
@@ -61,12 +69,19 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
/// TorSettings(...).copyWith(id: 12, name: "My name") /// TorSettings(...).copyWith(id: 12, name: "My name")
/// ``` /// ```
TorSettings call({ TorSettings call({
Object? proxyRegularTabsMode = const $CopyWithPlaceholder(),
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(), Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? config = const $CopyWithPlaceholder(), Object? config = const $CopyWithPlaceholder(),
Object? requireBridge = const $CopyWithPlaceholder(), Object? requireBridge = const $CopyWithPlaceholder(),
Object? fetchRemoteBridges = const $CopyWithPlaceholder(), Object? fetchRemoteBridges = const $CopyWithPlaceholder(),
}) { }) {
return TorSettings( return TorSettings(
proxyRegularTabsMode:
proxyRegularTabsMode == const $CopyWithPlaceholder() ||
proxyRegularTabsMode == null
? _value.proxyRegularTabsMode
// ignore: cast_nullable_to_non_nullable
: proxyRegularTabsMode as TorRegularTabProxyMode,
proxyPrivateTabsTor: proxyPrivateTabsTor:
proxyPrivateTabsTor == const $CopyWithPlaceholder() || proxyPrivateTabsTor == const $CopyWithPlaceholder() ||
proxyPrivateTabsTor == null proxyPrivateTabsTor == null
@@ -105,6 +120,10 @@ extension $TorSettingsCopyWith on TorSettings {
TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) => TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
TorSettings.withDefaults( TorSettings.withDefaults(
proxyRegularTabsMode: $enumDecodeNullable(
_$TorRegularTabProxyModeEnumMap,
json['proxyRegularTabsMode'],
),
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?, proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']), config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']),
requireBridge: json['requireBridge'] as bool?, requireBridge: json['requireBridge'] as bool?,
@@ -113,12 +132,19 @@ TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) => Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) =>
<String, dynamic>{ <String, dynamic>{
'proxyRegularTabsMode':
_$TorRegularTabProxyModeEnumMap[instance.proxyRegularTabsMode]!,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor, 'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
'config': _$TorConnectionConfigEnumMap[instance.config]!, 'config': _$TorConnectionConfigEnumMap[instance.config]!,
'requireBridge': instance.requireBridge, 'requireBridge': instance.requireBridge,
'fetchRemoteBridges': instance.fetchRemoteBridges, 'fetchRemoteBridges': instance.fetchRemoteBridges,
}; };
const _$TorRegularTabProxyModeEnumMap = {
TorRegularTabProxyMode.container: 'container',
TorRegularTabProxyMode.all: 'all',
};
const _$TorConnectionConfigEnumMap = { const _$TorConnectionConfigEnumMap = {
TorConnectionConfig.auto: 'auto', TorConnectionConfig.auto: 'auto',
TorConnectionConfig.direct: 'direct', TorConnectionConfig.direct: 'direct',