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/>.
*/
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.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/user/data/models/tor_settings.dart';
import 'package:weblibre/features/user/domain/repositories/tor_settings.dart';
part 'proxy_settings_replication.g.dart';
@Riverpod(keepAlive: true)
class ProxySettingsReplication extends _$ProxySettingsReplication {
final _service = GeckoContainerProxyService();
@override
void build() {
ref.listen(
@@ -40,7 +39,9 @@ class ProxySettingsReplication extends _$ProxySettingsReplication {
torProxyServiceProvider.select((data) => data.value),
(previous, next) async {
if (next != null) {
await _service.setProxyPort(next);
await ref
.read(torProxyRepositoryProvider.notifier)
.setProxyPort(next);
}
},
onError: (error, stackTrace) {
@@ -62,13 +63,15 @@ class ProxySettingsReplication extends _$ProxySettingsReplication {
for (final container in next.value!) {
if (container.metadata.contextualIdentity.isNotEmpty) {
if (container.metadata.useProxy) {
await _service.addContainerProxy(
container.metadata.contextualIdentity!,
);
await ref
.read(torProxyRepositoryProvider.notifier)
.addContainerProxy(container.metadata.contextualIdentity!);
} else {
await _service.removeContainerProxy(
container.metadata.contextualIdentity!,
);
await ref
.read(torProxyRepositoryProvider.notifier)
.removeContainerProxy(
container.metadata.contextualIdentity!,
);
}
}
}
@@ -90,9 +93,39 @@ class ProxySettingsReplication extends _$ProxySettingsReplication {
),
(previous, next) async {
if (next) {
await _service.addContainerProxy('private');
await ref
.read(torProxyRepositoryProvider.notifier)
.addContainerProxy('private');
} 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) {
@@ -42,7 +42,7 @@ final class ProxySettingsReplicationProvider
}
String _$proxySettingsReplicationHash() =>
r'787545f882b97274dc67007a6404f170d40bdb64';
r'46063662e3aca4f38a6876f4265ada9a87bde6dc';
abstract class _$ProxySettingsReplication extends $Notifier<void> {
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 TorRegularTabProxyMode { container, all }
@CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
class TorSettings with FastEquatable {
final TorRegularTabProxyMode proxyRegularTabsMode;
final bool proxyPrivateTabsTor;
final TorConnectionConfig config;
final bool requireBridge;
final bool fetchRemoteBridges;
TorSettings({
required this.proxyRegularTabsMode,
required this.proxyPrivateTabsTor,
required this.config,
required this.requireBridge,
@@ -41,11 +45,14 @@ class TorSettings with FastEquatable {
});
TorSettings.withDefaults({
TorRegularTabProxyMode? proxyRegularTabsMode,
bool? proxyPrivateTabsTor,
TorConnectionConfig? config,
bool? requireBridge,
bool? fetchRemoteBridges,
}) : proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
}) : proxyRegularTabsMode =
proxyRegularTabsMode ?? TorRegularTabProxyMode.container,
proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
config = config ?? TorConnectionConfig.auto,
requireBridge = requireBridge ?? false,
fetchRemoteBridges = fetchRemoteBridges ?? true;
@@ -57,6 +64,7 @@ class TorSettings with FastEquatable {
@override
List<Object?> get hashParameters => [
proxyRegularTabsMode,
proxyPrivateTabsTor,
config,
requireBridge,
@@ -7,6 +7,8 @@ part of 'tor_settings.dart';
// **************************************************************************
abstract class _$TorSettingsCWProxy {
TorSettings proxyRegularTabsMode(TorRegularTabProxyMode proxyRegularTabsMode);
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor);
TorSettings config(TorConnectionConfig config);
@@ -23,6 +25,7 @@ abstract class _$TorSettingsCWProxy {
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ```
TorSettings call({
TorRegularTabProxyMode proxyRegularTabsMode,
bool proxyPrivateTabsTor,
TorConnectionConfig config,
bool requireBridge,
@@ -37,6 +40,11 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
final TorSettings _value;
@override
TorSettings proxyRegularTabsMode(
TorRegularTabProxyMode proxyRegularTabsMode,
) => call(proxyRegularTabsMode: proxyRegularTabsMode);
@override
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) =>
call(proxyPrivateTabsTor: proxyPrivateTabsTor);
@@ -61,12 +69,19 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ```
TorSettings call({
Object? proxyRegularTabsMode = const $CopyWithPlaceholder(),
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? config = const $CopyWithPlaceholder(),
Object? requireBridge = const $CopyWithPlaceholder(),
Object? fetchRemoteBridges = const $CopyWithPlaceholder(),
}) {
return TorSettings(
proxyRegularTabsMode:
proxyRegularTabsMode == const $CopyWithPlaceholder() ||
proxyRegularTabsMode == null
? _value.proxyRegularTabsMode
// ignore: cast_nullable_to_non_nullable
: proxyRegularTabsMode as TorRegularTabProxyMode,
proxyPrivateTabsTor:
proxyPrivateTabsTor == const $CopyWithPlaceholder() ||
proxyPrivateTabsTor == null
@@ -105,6 +120,10 @@ extension $TorSettingsCopyWith on TorSettings {
TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
TorSettings.withDefaults(
proxyRegularTabsMode: $enumDecodeNullable(
_$TorRegularTabProxyModeEnumMap,
json['proxyRegularTabsMode'],
),
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']),
requireBridge: json['requireBridge'] as bool?,
@@ -113,12 +132,19 @@ TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) =>
<String, dynamic>{
'proxyRegularTabsMode':
_$TorRegularTabProxyModeEnumMap[instance.proxyRegularTabsMode]!,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
'config': _$TorConnectionConfigEnumMap[instance.config]!,
'requireBridge': instance.requireBridge,
'fetchRemoteBridges': instance.fetchRemoteBridges,
};
const _$TorRegularTabProxyModeEnumMap = {
TorRegularTabProxyMode.container: 'container',
TorRegularTabProxyMode.all: 'all',
};
const _$TorConnectionConfigEnumMap = {
TorConnectionConfig.auto: 'auto',
TorConnectionConfig.direct: 'direct',