@@ -9,6 +9,19 @@ import '../models/ischool_plus/announcement.dart';
99import '../models/ischool_plus/announcement_detail.dart' ;
1010import '../models/ischool_plus/course_file.dart' ;
1111
12+ /// 鎖請求項目
13+ class _LockRequest {
14+ final Completer <void > completer;
15+ final bool highPriority;
16+ final String courseId; // 用於調試
17+
18+ _LockRequest ({
19+ required this .completer,
20+ required this .highPriority,
21+ required this .courseId,
22+ });
23+ }
24+
1225/// i學院連接器 - 參考 TAT 實作
1326class ISchoolPlusConnector {
1427 static const String _baseUrl = 'https://istudy.ntut.edu.tw/' ;
@@ -17,8 +30,8 @@ class ISchoolPlusConnector {
1730 final Dio _dio;
1831
1932 // 用於防止並發請求導致課程選擇混亂的互斥鎖
20- Completer < void > ? _currentLock ; // 當前正在執行的鎖
21- final List < Completer < void >> _waitingQueue = [] ; // 等待隊列
33+ final List < _LockRequest > _lockQueue = [] ; // 鎖請求隊列(等待執行的請求)
34+ bool _isLocked = false ; // 是否有請求正在執行(簡單的布爾標記)
2235
2336 ISchoolPlusConnector ({required Dio dio}) : _dio = dio {
2437 // 不覆蓋傳入的 Dio 配置,保持共享的設置
@@ -201,48 +214,89 @@ class ISchoolPlusConnector {
201214 }
202215
203216 /// 獲取鎖(支持優先級)
204- Future <void > _acquireLock ({bool highPriority = false }) async {
205- // 如果有當前正在執行的鎖,先等它完成
206- if (_currentLock != null ) {
207- final completer = Completer <void >();
208-
209- if (highPriority) {
210- // 高優先級(手動操作)插入到等待隊列前面
211- _waitingQueue.insert (0 , completer);
212- } else {
213- // 低優先級(背景同步)加到等待隊列末尾
214- _waitingQueue.add (completer);
217+ Future <void > _acquireLock (String courseId, {bool highPriority = false }) async {
218+ final completer = Completer <void >();
219+ final request = _LockRequest (
220+ completer: completer,
221+ highPriority: highPriority,
222+ courseId: courseId,
223+ );
224+
225+ final queueSize = _lockQueue.length;
226+
227+ // 將請求加入隊列
228+ if (highPriority && (_isLocked || _lockQueue.isNotEmpty)) {
229+ // 高優先級:插入到第一個低優先級請求之前
230+ int insertIndex = 0 ;
231+ while (insertIndex < _lockQueue.length && _lockQueue[insertIndex].highPriority) {
232+ insertIndex++ ;
215233 }
216-
217- // 等待輪到自己
218- await completer.future;
234+ _lockQueue.insert (insertIndex, request);
235+ print ('[ISchoolPlus] 高優先級請求 $courseId 插隊到位置 $insertIndex (隊列: ${_lockQueue .length }, 鎖定: $_isLocked , 原隊列: $queueSize )' );
236+ } else {
237+ // 低優先級:加到隊列末尾
238+ _lockQueue.add (request);
239+ print ('[ISchoolPlus] 低優先級請求 $courseId 加入隊列 (隊列: ${_lockQueue .length }, 鎖定: $_isLocked , 原隊列: $queueSize )' );
219240 }
241+
242+ // 嘗試處理隊列
243+ _processQueue ();
244+
245+ // 等待輪到自己
246+ await completer.future;
220247
221- // 創建自己的鎖
222- _currentLock = Completer < void >( );
248+ // completer 完成時,_isLocked 已經在 _processQueue 中設置為 true 了
249+ print ( '[ISchoolPlus] 請求 $ courseId 獲得鎖並開始執行' );
223250 }
224-
251+
252+ /// 處理鎖隊列
253+ void _processQueue () {
254+ // 如果正在處理或隊列為空,則不處理
255+ if (_isLocked || _lockQueue.isEmpty) {
256+ return ;
257+ }
258+
259+ // 移除並獲取隊列中的第一個請求
260+ final first = _lockQueue.removeAt (0 );
261+
262+ // 先設置鎖定狀態(在 complete 之前!)
263+ _isLocked = true ;
264+ print ('[ISchoolPlus] 準備處理請求 ${first .courseId } (隊列剩餘: ${_lockQueue .length })' );
265+
266+ // 完成請求(讓它開始執行)
267+ first.completer.complete ();
268+ }
269+
225270 /// 釋放鎖
226271 void _releaseLock () {
227- // 完成當前鎖
228- _currentLock? .complete ();
229- _currentLock = null ;
272+ if (! _isLocked) {
273+ print ('[ISchoolPlus] 警告:嘗試釋放鎖但當前未鎖定' );
274+ return ;
275+ }
276+
277+ print ('[ISchoolPlus] 釋放鎖 (隊列剩餘: ${_lockQueue .length })' );
230278
231- // 處理等待隊列中的下一個請求
232- if (_waitingQueue.isNotEmpty) {
233- final next = _waitingQueue.removeAt (0 );
234- next.complete ();
279+ // 先解鎖(在處理下一個請求之前)
280+ _isLocked = false ;
281+
282+ // 處理下一個請求
283+ if (_lockQueue.isNotEmpty) {
284+ _processQueue ();
235285 }
236286 }
237287
238288 /// 取得課程公告列表
239289 /// [highPriority] 是否為高優先級請求(手動操作)
240290 Future <List <ISchoolPlusAnnouncement >> getCourseAnnouncements (
241291 String courseId, {bool highPriority = false }) async {
292+ print ('[ISchoolPlus] 請求公告列表: $courseId (高優先級: $highPriority )' );
293+
242294 // 獲取鎖,高優先級請求會插隊
243- await _acquireLock (highPriority: highPriority);
295+ await _acquireLock (courseId, highPriority: highPriority);
244296
245297 try {
298+ print ('[ISchoolPlus] 開始處理公告列表: $courseId ' );
299+
246300 // 先選擇課程
247301 if (! await _selectCourse (courseId)) {
248302 throw Exception ('無法選擇課程' );
@@ -349,9 +403,24 @@ class ISchoolPlusConnector {
349403 }
350404
351405 /// 取得公告詳細內容
406+ /// [courseId] 課程 ID (6位數,必須提供)
407+ /// [highPriority] 是否為高優先級請求(手動操作)
352408 Future <ISchoolPlusAnnouncementDetail ?> getAnnouncementDetail (
353- ISchoolPlusAnnouncement announcement) async {
409+ ISchoolPlusAnnouncement announcement, {required String courseId, bool highPriority = false }) async {
410+ print ('[ISchoolPlus] 請求公告詳情: $courseId (高優先級: $highPriority )' );
411+
412+ // 獲取鎖,高優先級請求會插隊
413+ await _acquireLock (courseId, highPriority: highPriority);
414+
354415 try {
416+ print ('[ISchoolPlus] 開始處理公告詳情: $courseId ' );
417+
418+ // 先選擇課程
419+ if (! await _selectCourse (courseId)) {
420+ print ('[ISchoolPlus] 無法選擇課程: $courseId ' );
421+ return null ;
422+ }
423+
355424 final data = {
356425 'token' : announcement.token ?? '' ,
357426 'cid' : announcement.cid ?? '' ,
@@ -404,16 +473,23 @@ class ISchoolPlusConnector {
404473 dev.log ('[ISchoolPlus] Get announcement detail error: $e ' ,
405474 stackTrace: stackTrace);
406475 return null ;
476+ } finally {
477+ // 釋放鎖
478+ _releaseLock ();
407479 }
408480 }
409481
410482 /// 取得課程檔案列表
411483 /// [highPriority] 是否為高優先級請求(手動操作)
412484 Future <List <ISchoolPlusCourseFile >> getCourseFiles (String courseId, {bool highPriority = false }) async {
485+ print ('[ISchoolPlus] 請求檔案列表: $courseId (高優先級: $highPriority )' );
486+
413487 // 獲取鎖,高優先級請求會插隊
414- await _acquireLock (highPriority: highPriority);
488+ await _acquireLock (courseId, highPriority: highPriority);
415489
416490 try {
491+ print ('[ISchoolPlus] 開始處理檔案列表: $courseId ' );
492+
417493 // 先選擇課程
418494 if (! await _selectCourse (courseId)) {
419495 return [];
0 commit comments