From e0ea449c1706ab69c5880bf8dda3ef16681584b8 Mon Sep 17 00:00:00 2001 From: nyne Date: Mon, 6 Oct 2025 10:17:01 +0800 Subject: [PATCH] Improve updating following and fix potential crash. --- lib/foundation/comic_source/parser.dart | 1 + lib/foundation/follow_updates.dart | 55 +++++++++--- lib/utils/channel.dart | 58 ++++++++++++ test/channel_test.dart | 115 ++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 13 deletions(-) create mode 100644 lib/utils/channel.dart create mode 100644 test/channel_test.dart diff --git a/lib/foundation/comic_source/parser.dart b/lib/foundation/comic_source/parser.dart index c0da172..05e93bc 100644 --- a/lib/foundation/comic_source/parser.dart +++ b/lib/foundation/comic_source/parser.dart @@ -759,6 +759,7 @@ class ComicSourceParser { LoadComicFunc? _parseLoadComicFunc() { return (id) async { try { + print("Loading comic $id from source $_key. Stacktrace:\n${StackTrace.current}"); var res = await JsEngine().runCode(""" ComicSource.sources.$_key.comic.loadInfo(${jsonEncode(id)}) """); diff --git a/lib/foundation/follow_updates.dart b/lib/foundation/follow_updates.dart index 98144c3..f50ee3a 100644 --- a/lib/foundation/follow_updates.dart +++ b/lib/foundation/follow_updates.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:venera/foundation/favorites.dart'; import 'package:venera/foundation/log.dart'; +import 'package:venera/utils/channel.dart'; class ComicUpdateResult { final bool updated; @@ -62,6 +63,7 @@ Future updateComic( return ComicUpdateResult(updated, null); } catch (e, s) { Log.error("Check Updates", e, s); + await Future.delayed(const Duration(seconds: 2)); retries--; if (retries == 0) { return ComicUpdateResult(false, e.toString()); @@ -114,23 +116,50 @@ void updateFolderBase( current = 0; stream.add(UpdateProgress(total, current, errors, updated)); - var futures = []; - for (var comic in comicsToUpdate) { - var future = updateComic(comic, folder).then((result) { - current++; - if (result.updated) { - updated++; + var channel = Channel(10); + + // Producer + () async { + var c = 0; + for (var comic in comicsToUpdate) { + await channel.push(comic); + c++; + // Throttle + if (c % 5 == 0) { + var delay = c % 100 + 1; + if (delay > 10) { + delay = 10; + } + await Future.delayed(Duration(seconds: delay)); } - if (result.errorMessage != null) { - errors++; + } + channel.close(); + }(); + + // Consumers + var updateFutures = []; + for (var i = 0; i < 5; i++) { + var f = () async { + while (true) { + var comic = await channel.pop(); + if (comic == null) { + break; + } + var result = await updateComic(comic, folder); + current++; + if (result.updated) { + updated++; + } + if (result.errorMessage != null) { + errors++; + } + stream.add(UpdateProgress(total, current, errors, updated, comic, result.errorMessage)); } - stream.add( - UpdateProgress(total, current, errors, updated, comic, result.errorMessage)); - }); - futures.add(future); + }(); + updateFutures.add(f); } - await Future.wait(futures); + await Future.wait(updateFutures); if (updated > 0) { LocalFavoritesManager().notifyChanges(); diff --git a/lib/utils/channel.dart b/lib/utils/channel.dart new file mode 100644 index 0000000..2731b39 --- /dev/null +++ b/lib/utils/channel.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'dart:collection'; + +class Channel { + final Queue _queue; + + final int size; + + Channel(this.size) : _queue = Queue(); + + Completer? _releaseCompleter; + + Completer? _pushCompleter; + + var currentSize = 0; + + var isClosed = false; + + Future push(T item) async { + if (currentSize >= size) { + _releaseCompleter ??= Completer(); + return _releaseCompleter!.future.then((_) { + if (isClosed) { + return; + } + _queue.addLast(item); + currentSize++; + }); + } + _queue.addLast(item); + currentSize++; + _pushCompleter?.complete(); + _pushCompleter = null; + } + + Future pop() async { + while (_queue.isEmpty) { + if (isClosed) { + return null; + } + _pushCompleter ??= Completer(); + await _pushCompleter!.future; + } + var item = _queue.removeFirst(); + currentSize--; + if (_releaseCompleter != null && currentSize < size) { + _releaseCompleter!.complete(); + _releaseCompleter = null; + } + return item; + } + + void close() { + isClosed = true; + _pushCompleter?.complete(); + _releaseCompleter?.complete(); + } +} \ No newline at end of file diff --git a/test/channel_test.dart b/test/channel_test.dart new file mode 100644 index 0000000..b906738 --- /dev/null +++ b/test/channel_test.dart @@ -0,0 +1,115 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:venera/utils/channel.dart'; + +void main() { + test("1-1-1", () async { + var channel = Channel(1); + await channel.push(1); + var item = await channel.pop(); + expect(item, 1); + }); + + test("1-3-1", () async { + var channel = Channel(1); + + // producer + () async { + await channel.push(1); + }(); + () async { + await channel.push(2); + }(); + () async { + await channel.push(3); + }(); + + // consumer + var results = []; + for (var i = 0; i < 3; i++) { + var item = await channel.pop(); + if (item != null) { + results.add(item); + } + } + expect(results.length, 3); + }); + + test("2-3-1", () async { + var channel = Channel(2); + + // producer + () async { + await channel.push(1); + }(); + () async { + await channel.push(2); + }(); + () async { + await channel.push(3); + }(); + + // consumer + var results = []; + for (var i = 0; i < 3; i++) { + var item = await channel.pop(); + if (item != null) { + results.add(item); + } + } + expect(results.length, 3); + }); + + test("1-1-3", () async { + var channel = Channel(1); + + // producer + () async { + print("push 1"); + await channel.push(1); + print("push 2"); + await channel.push(2); + print("push 3"); + await channel.push(3); + print("push done"); + channel.close(); + }(); + + // consumer + var consumers = []; + var results = []; + for (var i = 0; i < 3; i++) { + consumers.add(() async { + while (true) { + var item = await channel.pop(); + if (item == null) { + break; + } + print("pop $item"); + results.add(item); + } + }()); + } + + await Future.wait(consumers); + expect(results.length, 3); + }); + + test("close", () async { + var channel = Channel(2); + + // producer + () async { + await channel.push(1); + await channel.push(2); + await channel.push(3); + channel.close(); + }(); + + // consumer + await channel.pop(); + await channel.pop(); + await channel.pop(); + var item4 = await channel.pop(); + expect(item4, null); + }); +} \ No newline at end of file