HTTP engine now controlled by pref

This commit is contained in:
Ming Ming 2024-08-06 01:22:46 +08:00
parent f33d6f0744
commit 8ce1456811
13 changed files with 289 additions and 58 deletions

View file

@ -74,7 +74,10 @@ Future<void> init(InitIsolateType isolateType) async {
if (features.isSupportSelfSignedCert) {
await _initSelfSignedCertManager();
}
await initHttp(k.versionStr);
await initHttp(
appVersion: k.versionStr,
isNewHttpEngine: Pref().isNewHttpEngine() ?? false,
);
await _initDiContainer(isolateType);
_initVisibilityDetector();
initGpsMap();

View file

@ -170,6 +170,12 @@ class PrefController {
value: value,
);
Future<bool> setNewHttpEngine(bool value) => _set<bool>(
controller: _isNewHttpEngineController,
setter: (pref, value) => pref.setNewHttpEngine(value),
value: value,
);
Future<bool> _set<T>({
required BehaviorSubject<T> controller,
required Future<bool> Function(Pref pref, T value) setter,
@ -275,6 +281,9 @@ class PrefController {
.getMapBrowserPrevPosition()
?.let(tryJsonDecode)
?.let(_tryMapCoordFromJson));
@npSubjectAccessor
late final _isNewHttpEngineController =
BehaviorSubject.seeded(_c.pref.isNewHttpEngine() ?? false);
}
@npSubjectAccessor

View file

@ -166,6 +166,11 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
mapBrowserPrevPosition.distinct().skip(1);
MapCoord? get mapBrowserPrevPositionValue =>
_mapBrowserPrevPositionController.value;
// _isNewHttpEngineController
ValueStream<bool> get isNewHttpEngine => _isNewHttpEngineController.stream;
Stream<bool> get isNewHttpEngineNew => isNewHttpEngine.skip(1);
Stream<bool> get isNewHttpEngineChange => isNewHttpEngine.distinct().skip(1);
bool get isNewHttpEngineValue => _isNewHttpEngineController.value;
}
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {

View file

@ -98,6 +98,9 @@ extension on Pref {
return provider.setString(PrefKey.mapBrowserPrevPosition, value);
}
}
Future<bool> setNewHttpEngine(bool value) =>
provider.setBool(PrefKey.isNewHttpEngine, value);
}
MapCoord? _tryMapCoordFromJson(dynamic json) {

View file

@ -114,6 +114,7 @@ enum PrefKey implements PrefKeyInterface {
protectedPageAuthPassword,
dontShowVideoPreviewHint,
mapBrowserPrevPosition,
isNewHttpEngine,
;
@override
@ -202,6 +203,8 @@ enum PrefKey implements PrefKeyInterface {
return "dontShowVideoPreviewHint";
case PrefKey.mapBrowserPrevPosition:
return "mapBrowserPrevPosition";
case PrefKey.isNewHttpEngine:
return "isNewHttpEngine";
}
}
}

View file

@ -223,6 +223,8 @@ extension PrefExtension on Pref {
PrefKey.isVideoPlayerLoop,
value,
(key, value) => provider.setBool(key, value));
bool? isNewHttpEngine() => provider.getBool(PrefKey.isNewHttpEngine);
}
extension AccountPrefExtension on AccountPref {

View file

@ -454,6 +454,8 @@
"settingsClearCacheDatabaseDescription": "Clear cached file info and trigger a complete resync with the server",
"settingsClearCacheDatabaseSuccessNotification": "Database cleared successfully. You are suggested to restart the app",
"settingsManageTrustedCertificateTitle": "Manage trusted certificates",
"settingsUseNewHttpEngine": "Use new HTTP engine",
"settingsUseNewHttpEngineDescription": "New HTTP engine based on Chromium, supporting new standards like HTTP/2* and HTTP/3 QUIC*.\n\nLimitations:\nSelf-signed certs can no longer be managed by us. You must import your CA certs to the system trust store for them to work.\n\n* HTTPS is required for HTTP/2 and HTTP/3",
"settingsAboutSectionTitle": "About",
"@settingsAboutSectionTitle": {
"description": "Title of the about section in settings widget"
@ -483,6 +485,7 @@
"@settingsTranslatorTitle": {
"description": "Title of the translator item"
},
"settingsRestartNeededDialog": "Please restart app to apply change",
"writePreferenceFailureNotification": "Failed setting preference",
"@writePreferenceFailureNotification": {
"description": "Inform user that the preference file cannot be modified"

View file

@ -38,6 +38,8 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsAboutSectionTitle",
"settingsVersionTitle",
"settingsServerVersionTitle",
@ -46,6 +48,7 @@
"settingsCaptureLogsTitle",
"settingsCaptureLogsDescription",
"settingsTranslatorTitle",
"settingsRestartNeededDialog",
"writePreferenceFailureNotification",
"enableButtonLabel",
"exifSupportDetails",
@ -280,6 +283,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"appLockUnlockHint",
"appLockUnlockWrongPassword",
"enabledText",
@ -309,6 +315,9 @@
"settingsThemePresets",
"settingsAppLockSetupBiometricFallbackDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"fileLastSharedByOthersDescription",
"multipleFilesLinkShareDialogContent",
"unshareLinkShareDirDialogContent",
@ -385,7 +394,10 @@
"settingsClearCacheDatabaseDescription",
"settingsClearCacheDatabaseSuccessNotification",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsServerVersionTitle",
"settingsRestartNeededDialog",
"slideshowSetupDialogReverseTitle",
"shareMethodPreviewTitle",
"shareMethodPreviewDescription",
@ -497,6 +509,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"appLockUnlockHint",
"appLockUnlockWrongPassword",
"enabledText",
@ -531,6 +546,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"appLockUnlockHint",
"appLockUnlockWrongPassword",
"enabledText",
@ -565,6 +583,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"appLockUnlockHint",
"appLockUnlockWrongPassword",
"enabledText",
@ -601,6 +622,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"unmuteTooltip",
"slideshowTooltip",
"enhanceColorPopTitle",
@ -721,6 +745,8 @@
"settingsClearCacheDatabaseDescription",
"settingsClearCacheDatabaseSuccessNotification",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsAboutSectionTitle",
"settingsVersionTitle",
"settingsServerVersionTitle",
@ -729,6 +755,7 @@
"settingsCaptureLogsTitle",
"settingsCaptureLogsDescription",
"settingsTranslatorTitle",
"settingsRestartNeededDialog",
"writePreferenceFailureNotification",
"enableButtonLabel",
"exifSupportDetails",
@ -1022,6 +1049,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"enhanceColorPopTitle",
"imageEditTransformOrientationClockwise",
"imageEditTransformOrientationCounterclockwise",
@ -1064,7 +1094,10 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsServerVersionTitle",
"settingsRestartNeededDialog",
"searchLandingPeopleListEmptyText2",
"createCollectionFailureNotification",
"addItemToCollectionTooltip",
@ -1113,6 +1146,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"appLockUnlockHint",
"appLockUnlockWrongPassword",
"enabledText",
@ -1133,6 +1169,9 @@
],
"tr": [
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"mapBrowserDateRangeLabel",
"mapBrowserDateRangeThisMonth",
"mapBrowserDateRangePrevMonth",
@ -1163,6 +1202,9 @@
"settingsAppLockSetupPasswordDialogTitle",
"settingsAppLockConfirmPasswordDialogTitle",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsRestartNeededDialog",
"slideshowSetupDialogReverseTitle",
"enhanceColorPopTitle",
"enhanceRetouchTitle",
@ -1255,7 +1297,10 @@
"settingsClearCacheDatabaseDescription",
"settingsClearCacheDatabaseSuccessNotification",
"settingsManageTrustedCertificateTitle",
"settingsUseNewHttpEngine",
"settingsUseNewHttpEngineDescription",
"settingsServerVersionTitle",
"settingsRestartNeededDialog",
"sortOptionFilenameAscendingLabel",
"sortOptionFilenameDescendingLabel",
"slideshowSetupDialogReverseTitle",

View file

@ -9,13 +9,19 @@ class _Error {
}
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc(
DiContainer c, {
required this.db,
required this.prefController,
}) : _c = c,
super(const _State()) {
super(_State.init(
isNewHttpEngine: prefController.isNewHttpEngineValue,
)) {
on<_Init>(_onInit);
on<_ClearCacheDatabase>(_onClearCacheDatabase);
on<_SetNewHttpEngine>(_onSetNewHttpEngine);
}
@override
@ -23,8 +29,18 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Stream<_Error> errorStream() => _errorStream.stream;
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
return forEach(
emit,
prefController.isNewHttpEngineChange,
onData: (data) => state.copyWith(isNewHttpEngine: data),
);
}
Future<void> _onClearCacheDatabase(
_ClearCacheDatabase ev, Emitter<_State> emit) async {
_log.info(ev);
try {
final accounts = _c.pref.getAccounts3Or([]);
await db.clearAndInitWithAccounts(accounts.toDb());
@ -35,7 +51,14 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
}
}
void _onSetNewHttpEngine(_SetNewHttpEngine ev, Emitter<_State> emit) {
_log.info(ev);
prefController.setNewHttpEngine(ev.value);
}
final DiContainer _c;
final NpDb db;
final PrefController prefController;
final _errorStream = StreamController<_Error>.broadcast();
}

View file

@ -4,23 +4,50 @@ part of '../expert_settings.dart';
@toString
class _State {
const _State({
required this.isNewHttpEngine,
this.lastSuccessful,
});
factory _State.init({
required bool isNewHttpEngine,
}) {
return _State(
isNewHttpEngine: isNewHttpEngine,
);
}
@override
String toString() => _$toString();
final bool isNewHttpEngine;
final _Event? lastSuccessful;
}
abstract class _Event {
const _Event();
}
abstract class _Event {}
@toString
class _ClearCacheDatabase extends _Event {
_ClearCacheDatabase();
class _Init implements _Event {
const _Init();
@override
String toString() => _$toString();
}
@toString
class _ClearCacheDatabase implements _Event {
const _ClearCacheDatabase();
@override
String toString() => _$toString();
}
@toString
class _SetNewHttpEngine implements _Event {
const _SetNewHttpEngine(this.value);
@override
String toString() => _$toString();
final bool value;
}

View file

@ -7,6 +7,7 @@ import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc_util.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/pref.dart';
@ -29,7 +30,8 @@ class ExpertSettings extends StatelessWidget {
create: (_) => _Bloc(
KiwiContainer().resolve<DiContainer>(),
db: context.read(),
),
prefController: context.read(),
)..add(const _Init()),
child: const _WrappedExpertSettings(),
);
}
@ -64,29 +66,53 @@ class _WrappedExpertSettingsState extends State<_WrappedExpertSettings> {
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => BlocListener<_Bloc, _State>(
listenWhen: (previous, current) =>
!identical(previous.lastSuccessful, current.lastSuccessful),
listener: (context, state) {
if (state.lastSuccessful is _ClearCacheDatabase) {
showDialog(
context: context,
builder: (_) => AlertDialog(
content: Text(L10n.global()
.settingsClearCacheDatabaseSuccessNotification),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
MaterialLocalizations.of(context).closeButtonLabel),
builder: (context) => MultiBlocListener(
listeners: [
_BlocListener(
listenWhen: (previous, current) =>
!identical(previous.lastSuccessful, current.lastSuccessful),
listener: (context, state) {
if (state.lastSuccessful is _ClearCacheDatabase) {
showDialog(
context: context,
builder: (_) => AlertDialog(
content: Text(L10n.global()
.settingsClearCacheDatabaseSuccessNotification),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(MaterialLocalizations.of(context)
.closeButtonLabel),
),
],
),
],
),
);
}
},
);
}
},
),
_BlocListenerT<bool>(
selector: (state) => state.isNewHttpEngine,
listener: (context, isNewHttpEngine) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(L10n.global().settingsRestartNeededDialog),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
MaterialLocalizations.of(context).closeButtonLabel),
),
],
),
);
},
),
],
child: CustomScrollView(
slivers: [
SliverAppBar(
@ -119,7 +145,7 @@ class _WrappedExpertSettingsState extends State<_WrappedExpertSettings> {
subtitle: Text(
L10n.global().settingsClearCacheDatabaseDescription),
onTap: () {
context.read<_Bloc>().add(_ClearCacheDatabase());
context.read<_Bloc>().add(const _ClearCacheDatabase());
},
),
ListTile(
@ -130,6 +156,27 @@ class _WrappedExpertSettingsState extends State<_WrappedExpertSettings> {
.pushNamed(TrustedCertManager.routeName);
},
),
_BlocSelector<bool>(
selector: (state) => state.isNewHttpEngine,
builder: (context, isNewHttpEngine) => CheckboxListTile(
title: Text(L10n.global().settingsUseNewHttpEngine),
value: isNewHttpEngine,
onChanged: (value) async {
if (value == true) {
final result = await showDialog<bool>(
context: context,
builder: (context) =>
const _NewHttpEngineDialog(),
);
if (context.mounted && result == true) {
context.addEvent(const _SetNewHttpEngine(true));
}
} else {
context.addEvent(const _SetNewHttpEngine(false));
}
},
),
),
],
),
),
@ -142,3 +189,40 @@ class _WrappedExpertSettingsState extends State<_WrappedExpertSettings> {
late final StreamSubscription _errorSubscription;
}
class _NewHttpEngineDialog extends StatelessWidget {
const _NewHttpEngineDialog();
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(L10n.global().settingsUseNewHttpEngine),
content: Text(L10n.global().settingsUseNewHttpEngineDescription),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(L10n.global().enableButtonLabel),
),
],
);
}
}
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
typedef _BlocListener = BlocListener<_Bloc, _State>;
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
extension on BuildContext {
_Bloc get bloc => read<_Bloc>();
// _State get state => bloc.state;
void addEvent(_Event event) => bloc.add(event);
}

View file

@ -13,15 +13,17 @@ part of 'expert_settings.dart';
// **************************************************************************
abstract class $_StateCopyWithWorker {
_State call({_Event? lastSuccessful});
_State call({bool? isNewHttpEngine, _Event? lastSuccessful});
}
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
_$_StateCopyWithWorkerImpl(this.that);
@override
_State call({dynamic lastSuccessful = copyWithNull}) {
_State call(
{dynamic isNewHttpEngine, dynamic lastSuccessful = copyWithNull}) {
return _State(
isNewHttpEngine: isNewHttpEngine as bool? ?? that.isNewHttpEngine,
lastSuccessful: lastSuccessful == copyWithNull
? that.lastSuccessful
: lastSuccessful as _Event?);
@ -61,7 +63,14 @@ extension _$_BlocNpLog on _Bloc {
extension _$_StateToString on _State {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_State {lastSuccessful: $lastSuccessful}";
return "_State {isNewHttpEngine: $isNewHttpEngine, lastSuccessful: $lastSuccessful}";
}
}
extension _$_InitToString on _Init {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_Init {}";
}
}
@ -71,3 +80,10 @@ extension _$_ClearCacheDatabaseToString on _ClearCacheDatabase {
return "_ClearCacheDatabase {}";
}
}
extension _$_SetNewHttpEngineToString on _SetNewHttpEngine {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "_SetNewHttpEngine {value: $value}";
}
}

View file

@ -8,31 +8,39 @@ import 'http_stub.dart'
if (dart.library.js_interop) 'http_browser.dart'
if (dart.library.io) 'http_io.dart';
Future<void> initHttp(String appVersion) async {
Future<void> initHttp({
required String appVersion,
required bool isNewHttpEngine,
}) async {
final userAgent = "nc-photos $appVersion";
Client? client;
if (getRawPlatform() == NpPlatform.android) {
try {
final cronetEngine = CronetEngine.build(
enableHttp2: true,
userAgent: userAgent,
);
client = CronetClient.fromCronetEngine(
cronetEngine,
closeEngine: true,
);
_log.info("Using cronet backend");
} catch (e, stackTrace) {
_log.severe("Failed creating CronetEngine", e, stackTrace);
}
} else if (getRawPlatform().isApple) {
try {
final urlConfig = URLSessionConfiguration.ephemeralSessionConfiguration()
..httpAdditionalHeaders = {"User-Agent": userAgent};
client = CupertinoClient.fromSessionConfiguration(urlConfig);
_log.info("Using cupertino backend");
} catch (e, stackTrace) {
_log.severe("Failed creating URLSessionConfiguration", e, stackTrace);
if (isNewHttpEngine) {
if (getRawPlatform() == NpPlatform.android) {
try {
final cronetEngine = CronetEngine.build(
enableHttp2: true,
enableQuic: true,
enableBrotli: true,
userAgent: userAgent,
);
client = CronetClient.fromCronetEngine(
cronetEngine,
closeEngine: true,
);
_log.info("Using cronet backend");
} catch (e, stackTrace) {
_log.severe("Failed creating CronetEngine", e, stackTrace);
}
} else if (getRawPlatform().isApple) {
try {
final urlConfig =
URLSessionConfiguration.ephemeralSessionConfiguration()
..httpAdditionalHeaders = {"User-Agent": userAgent};
client = CupertinoClient.fromSessionConfiguration(urlConfig);
_log.info("Using cupertino backend");
} catch (e, stackTrace) {
_log.severe("Failed creating URLSessionConfiguration", e, stackTrace);
}
}
}
if (client == null) {