11import 'dart:async' ;
22import 'dart:convert' ;
3- import 'dart:io' ;
43
54import 'package:flutter/material.dart' ;
65import '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 }
0 commit comments