Skip to content

Commit ac124c0

Browse files
authored
flutter: improve address book pull error handling (rustdesk#14813)
* flutter: improve address book pull error handling Summary: - Show error messages when fetching the address book list fails. - After the initial fetch, switching back to the AB tab no longer re-fetches it, even if an error occurred or the error banner was dismissed. Tested: - Self-hosted server: - normal - 403 responses - legacy address book mode - Public server - Verified that switching tabs no longer re-fetches AB after the initial fetch, regardless of whether an error occurred or the error banner was cleared. Signed-off-by: 21pages <sunboeasy@gmail.com> * use resp.statusCode in address book json decoding Signed-off-by: 21pages <sunboeasy@gmail.com> * flutter: clear address book list errors on reset Signed-off-by: 21pages <sunboeasy@gmail.com> * flutter: clear address book pull errors consistently Signed-off-by: 21pages <sunboeasy@gmail.com> --------- Signed-off-by: 21pages <sunboeasy@gmail.com>
1 parent 91aff3f commit ac124c0

3 files changed

Lines changed: 74 additions & 23 deletions

File tree

flutter/lib/common/widgets/address_book.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ class _AddressBookState extends State<AddressBook> {
5454
const LinearProgressIndicator(),
5555
buildErrorBanner(context,
5656
loading: gFFI.abModel.currentAbLoading,
57-
err: gFFI.abModel.currentAbPullError,
57+
err: gFFI.abModel.abPullError,
5858
retry: null,
59-
close: () => gFFI.abModel.currentAbPullError.value = ''),
59+
close: gFFI.abModel.clearPullErrors),
6060
buildErrorBanner(context,
6161
loading: gFFI.abModel.currentAbLoading,
6262
err: gFFI.abModel.currentAbPushError,

flutter/lib/models/ab_model.dart

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dart:async';
22
import 'dart:convert';
3-
import 'dart:io';
43

54
import 'package:flutter/material.dart';
65
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
@@ -53,7 +52,9 @@ class AbModel {
5352

5453
RxBool get currentAbLoading => current.abLoading;
5554
bool get currentAbEmpty => current.peers.isEmpty && current.tags.isEmpty;
56-
RxString get currentAbPullError => current.pullError;
55+
final _listPullError = ''.obs;
56+
RxString get abPullError =>
57+
_listPullError.value.isNotEmpty ? _listPullError : current.pullError;
5758
RxString get currentAbPushError => current.pushError;
5859
String? _personalAbGuid;
5960
RxBool legacyMode = false.obs;
@@ -68,6 +69,7 @@ class AbModel {
6869
var _syncFromRecentLock = false;
6970
var _timerCounter = 0;
7071
var _cacheLoadOnceFlag = false;
72+
var _pulledOnce = false;
7173
var listInitialized = false;
7274
var _maxPeerOneAb = 0;
7375

@@ -97,10 +99,17 @@ class AbModel {
9799
print("reset ab model");
98100
addressbooks.clear();
99101
_currentName.value = '';
102+
_listPullError.value = '';
103+
_pulledOnce = false;
100104
await bind.mainClearAb();
101105
listInitialized = false;
102106
}
103107

108+
void clearPullErrors() {
109+
_listPullError.value = '';
110+
current.pullError.value = '';
111+
}
112+
104113
// #region ab
105114
/// Pulls the address book data from the server.
106115
///
@@ -110,39 +119,49 @@ class AbModel {
110119
var _pulling = false;
111120
Future<void> pullAb(
112121
{required ForcePullAb? force, required bool quiet}) async {
122+
if (bind.isDisableAb()) return;
123+
if (!gFFI.userModel.isLogin) return;
124+
if (gFFI.userModel.networkError.isNotEmpty) return;
113125
if (_pulling) return;
126+
if (force == null && _pulledOnce) {
127+
return;
128+
}
114129
_pulling = true;
130+
if (!quiet) {
131+
_listPullError.value = '';
132+
current.pullError.value = '';
133+
}
115134
try {
116135
await _pullAb(force: force, quiet: quiet);
117136
_refreshTab();
118137
} catch (_) {}
119138
_pulling = false;
139+
_pulledOnce = true;
120140
}
121141

122142
Future<void> _pullAb(
123143
{required ForcePullAb? force, required bool quiet}) async {
124-
if (bind.isDisableAb()) return;
125-
if (!gFFI.userModel.isLogin) return;
126-
if (gFFI.userModel.networkError.isNotEmpty) return;
127144
if (force == null && listInitialized && current.initialized) return;
128145
debugPrint("pullAb, force: $force, quiet: $quiet");
129146
if (!listInitialized || force == ForcePullAb.listAndCurrent) {
130147
try {
131148
// Read personal guid every time to avoid upgrading the server without closing the main window
132149
_personalAbGuid = null;
133-
await _getPersonalAbGuid();
134-
// Determine legacy mode based on whether _personalAbGuid is null
150+
// `true`: continue init. `false`: stop, error already recorded.
151+
if (!await _getPersonalAbGuid(quiet: quiet)) {
152+
return;
153+
}
135154
legacyMode.value = _personalAbGuid == null;
136155
if (!legacyMode.value && _maxPeerOneAb == 0) {
137-
await _getAbSettings();
156+
await _getAbSettings(quiet: quiet);
138157
}
139158
if (_personalAbGuid != null) {
140159
debugPrint("pull ab list");
141160
List<AbProfile> abProfiles = List.empty(growable: true);
142161
abProfiles.add(AbProfile(_personalAbGuid!, _personalAddressBookName,
143162
gFFI.userModel.userName.value, null, ShareRule.read.value, null));
144163
// get all address book name
145-
await _getSharedAbProfiles(abProfiles);
164+
await _getSharedAbProfiles(abProfiles, quiet: quiet);
146165
addressbooks.removeWhere((key, value) =>
147166
abProfiles.firstWhereOrNull((e) => e.name == key) == null);
148167
for (int i = 0; i < abProfiles.length; i++) {
@@ -182,6 +201,7 @@ class AbModel {
182201
}
183202
} catch (e) {
184203
debugPrint("pull ab list error: $e");
204+
_setListPullError(e, quiet: quiet);
185205
}
186206
} else if (listInitialized &&
187207
(!current.initialized || force == ForcePullAb.current)) {
@@ -197,14 +217,26 @@ class AbModel {
197217
}
198218
}
199219

200-
Future<bool> _getAbSettings() async {
220+
void _setListPullError(Object err, {required bool quiet, int? statusCode}) {
221+
if (!quiet) {
222+
_listPullError.value =
223+
'${translate('pull_ab_failed_tip')}: ${translate(err.toString())}';
224+
}
225+
if (statusCode == 401) {
226+
gFFI.userModel.reset(resetOther: true);
227+
}
228+
}
229+
230+
Future<bool> _getAbSettings({required bool quiet}) async {
231+
int? statusCode;
201232
try {
202233
final api = "${await bind.mainGetApiServer()}/api/ab/settings";
203234
var headers = getHttpHeaders();
204235
headers['Content-Type'] = "application/json";
205236
_setEmptyBody(headers);
206237
final resp = await http.post(Uri.parse(api), headers: headers);
207-
if (resp.statusCode == 404) {
238+
statusCode = resp.statusCode;
239+
if (statusCode == 404) {
208240
debugPrint("HTTP 404, api server doesn't support shared address book");
209241
return false;
210242
}
@@ -213,46 +245,57 @@ class AbModel {
213245
if (json.containsKey('error')) {
214246
throw json['error'];
215247
}
216-
if (resp.statusCode != 200) {
217-
throw 'HTTP ${resp.statusCode}';
248+
if (statusCode != 200) {
249+
throw 'HTTP $statusCode';
218250
}
219251
_maxPeerOneAb = json['max_peer_one_ab'] ?? 0;
220252
return true;
221253
} catch (err) {
222254
debugPrint('get ab settings err: ${err.toString()}');
255+
_setListPullError(err, quiet: quiet, statusCode: statusCode);
223256
}
224257
return false;
225258
}
226259

227-
Future<bool> _getPersonalAbGuid() async {
260+
/// Loads `/api/ab/personal`.
261+
/// Returns `true` to continue init, `false` to stop after a real error.
262+
Future<bool> _getPersonalAbGuid({required bool quiet}) async {
263+
int? statusCode;
228264
try {
229265
final api = "${await bind.mainGetApiServer()}/api/ab/personal";
230266
var headers = getHttpHeaders();
231267
headers['Content-Type'] = "application/json";
232268
_setEmptyBody(headers);
233269
final resp = await http.post(Uri.parse(api), headers: headers);
234-
if (resp.statusCode == 404) {
270+
statusCode = resp.statusCode;
271+
if (statusCode == 404) {
235272
debugPrint("HTTP 404, current api server is legacy mode");
236-
return false;
273+
// Old server: keep `_personalAbGuid` null and continue in legacy mode.
274+
return true;
237275
}
238276
Map<String, dynamic> json =
239277
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
240278
if (json.containsKey('error')) {
241279
throw json['error'];
242280
}
243-
if (resp.statusCode != 200) {
244-
throw 'HTTP ${resp.statusCode}';
281+
if (statusCode != 200) {
282+
throw 'HTTP $statusCode';
245283
}
246284
_personalAbGuid = json['guid'];
285+
// New server: guid is available, continue in non-legacy mode.
247286
return true;
248287
} catch (err) {
249288
debugPrint('get personal ab err: ${err.toString()}');
289+
_setListPullError(err, quiet: quiet, statusCode: statusCode);
250290
}
291+
// Real error: stop the current pull.
251292
return false;
252293
}
253294

254-
Future<bool> _getSharedAbProfiles(List<AbProfile> profiles) async {
295+
Future<bool> _getSharedAbProfiles(List<AbProfile> profiles,
296+
{required bool quiet}) async {
255297
final api = "${await bind.mainGetApiServer()}/api/ab/shared/profiles";
298+
int? statusCode;
256299
try {
257300
var uri0 = Uri.parse(api);
258301
final pageSize = 100;
@@ -273,13 +316,19 @@ class AbModel {
273316
headers['Content-Type'] = "application/json";
274317
_setEmptyBody(headers);
275318
final resp = await http.post(uri, headers: headers);
319+
statusCode = resp.statusCode;
320+
if (statusCode == 404) {
321+
debugPrint(
322+
"HTTP 404, api server doesn't support shared address book");
323+
return false;
324+
}
276325
Map<String, dynamic> json =
277326
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
278327
if (json.containsKey('error')) {
279328
throw json['error'];
280329
}
281-
if (resp.statusCode != 200) {
282-
throw 'HTTP ${resp.statusCode}';
330+
if (statusCode != 200) {
331+
throw 'HTTP $statusCode';
283332
}
284333
if (json.containsKey('total')) {
285334
if (total == 0) total = json['total'];
@@ -302,6 +351,7 @@ class AbModel {
302351
return true;
303352
} catch (err) {
304353
debugPrint('_getSharedAbProfiles err: ${err.toString()}');
354+
_setListPullError(err, quiet: quiet, statusCode: statusCode);
305355
}
306356
return false;
307357
}

flutter/lib/models/group_model.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ class GroupModel {
343343
}
344344

345345
reset() async {
346+
initialized = false;
346347
groupLoadError.value = '';
347348
deviceGroups.clear();
348349
users.clear();

0 commit comments

Comments
 (0)