Skip to content

Commit 58ae825

Browse files
committed
fix(sticker): update sticker handling and improve job scheduling logic
1 parent 7490462 commit 58ae825

File tree

6 files changed

+115
-45
lines changed

6 files changed

+115
-45
lines changed

lib/db/dao/job_dao.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,11 @@ class JobDao extends DatabaseAccessor<MixinDatabase> with _$JobDaoMixin {
130130
..limit(100);
131131

132132
SimpleSelectStatement<Jobs, Job> updateStickerJobs() => select(db.jobs)
133-
..where((Jobs row) => row.action.equals(kUpdateSticker))
133+
..where(
134+
(Jobs row) =>
135+
row.action.equals(kUpdateSticker) & row.blazeMessage.isNotNull(),
136+
)
137+
..orderBy([(tbl) => OrderingTerm.asc(tbl.createdAt)])
134138
..limit(100);
135139

136140
SimpleSelectStatement<Jobs, Job> migrateFtsJobs() =>

lib/widgets/message/item/quote_message.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
99

1010
import '../../../constants/resources.dart';
1111
import '../../../db/dao/message_dao.dart';
12+
import '../../../db/database_event_bus.dart';
1213
import '../../../db/extension/message.dart';
1314
import '../../../db/mixin_database.dart';
1415
import '../../../enum/message_category.dart';
@@ -115,6 +116,27 @@ class QuoteMessage extends HookConsumerWidget {
115116
sharedUserIdentityNumber = quote.sharedUserIdentityNumber;
116117
}
117118

119+
final quoteSticker = useMemoizedStream(
120+
() {
121+
if (!type.isSticker) return const Stream<Sticker?>.empty();
122+
if (stickerId == null || stickerId.isEmpty) {
123+
return const Stream<Sticker?>.empty();
124+
}
125+
126+
return context.database.stickerDao
127+
.sticker(stickerId)
128+
.watchSingleOrNullWithStream(
129+
eventStreams: [
130+
DataBaseEventBus.instance.watchUpdateStickerStream(
131+
stickerIds: [stickerId],
132+
),
133+
],
134+
duration: kDefaultThrottleDuration,
135+
);
136+
},
137+
keys: [type, stickerId],
138+
).data;
139+
118140
if (quoteContent != null &&
119141
(type == null || type.isIllegalMessageCategory)) {
120142
return _QuoteMessageBase(
@@ -287,15 +309,17 @@ class QuoteMessage extends HookConsumerWidget {
287309
);
288310
}
289311
if (type.isSticker) {
312+
final shownAssetUrl = quoteSticker?.assetUrl ?? assetUrl;
313+
final shownAssetType = quoteSticker?.assetType ?? assetType;
290314
return _QuoteMessageBase(
291315
messageId: messageId,
292316
quoteMessageId: quoteMessageId!,
293317
userId: userId,
294318
name: userFullName,
295319
image: StickerItem(
296320
stickerId: stickerId,
297-
assetUrl: assetUrl ?? '',
298-
assetType: assetType,
321+
assetUrl: shownAssetUrl ?? '',
322+
assetType: shownAssetType,
299323
),
300324
icon: SvgPicture.asset(
301325
Resources.assetsImagesStickerSvg,

lib/widgets/message/item/sticker_message.dart

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,20 @@ class StickerMessageWidget extends HookConsumerWidget {
6363
}, [stickerId, messageAssetUrl]);
6464

6565
final stickerData =
66-
useMemoizedStream(() {
67-
if (messageStickerData != null) {
68-
return const Stream<_StickerData>.empty();
69-
} else {
66+
useMemoizedStream(
67+
() async* {
68+
if (messageStickerData != null) {
69+
yield messageStickerData;
70+
}
71+
7072
assert(
7173
stickerId != null,
7274
'stickerId is null. ${context.message.messageId}',
7375
);
74-
if (stickerId == null) return const Stream<_StickerData?>.empty();
75-
d('stickerData2: $stickerId, ${context.message.messageId}');
76-
return context.database.stickerDao
76+
if (stickerId == null) return;
77+
78+
d('stickerData watch: $stickerId, ${context.message.messageId}');
79+
yield* context.database.stickerDao
7780
.sticker(stickerId)
7881
.watchSingleOrNullWithStream(
7982
eventStreams: [
@@ -92,8 +95,9 @@ class StickerMessageWidget extends HookConsumerWidget {
9295
assetType: event.assetType,
9396
),
9497
);
95-
}
96-
}, keys: [messageStickerData, stickerId]).data ??
98+
},
99+
keys: [messageStickerData, stickerId],
100+
).data ??
97101
messageStickerData;
98102

99103
final assetType = stickerData?.assetType;

lib/widgets/sticker_page/sticker_item.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:flutter/widgets.dart';
24
import 'package:flutter_hooks/flutter_hooks.dart';
35
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -13,7 +15,13 @@ import '../mixin_image.dart';
1315

1416
void _triggerRefreshJob(BuildContext context, String? stickerId) {
1517
if (stickerId == null || stickerId.isEmpty) return;
16-
context.accountServer.addUpdateStickerJob(createUpdateStickerJob(stickerId));
18+
19+
scheduleMicrotask(() {
20+
if (!context.mounted) return;
21+
context.accountServer.addUpdateStickerJob(
22+
createUpdateStickerJob(stickerId),
23+
);
24+
});
1725
}
1826

1927
class StickerItem extends HookConsumerWidget {

lib/workers/job/update_sticker_job.dart

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,38 +39,68 @@ class UpdateStickerJob extends JobQueue<Job, List<Job>> {
3939
}
4040

4141
@override
42-
Future<List<Job>?> run(List<Job> jobs) async {
43-
final list = await Future.wait(
44-
jobs.map((Job job) async {
45-
try {
46-
final stickerId = job.blazeMessage;
47-
if (stickerId != null) {
48-
final sticker = (await client.accountApi.getStickerById(
49-
stickerId,
50-
)).data;
51-
await database.stickerDao.insert(sticker.asStickersCompanion);
42+
Future<void> run(List<Job> jobs) async {
43+
if (jobs.isEmpty) return;
44+
45+
final now = DateTime.now();
46+
final first = jobs.first;
47+
final wait = first.createdAt.difference(now);
48+
if (wait.inMilliseconds > 0) {
49+
await Future.delayed(
50+
wait > const Duration(seconds: 10) ? const Duration(seconds: 10) : wait,
51+
);
52+
return;
53+
}
54+
55+
for (final job in jobs) {
56+
final dueIn = job.createdAt.difference(DateTime.now());
57+
if (dueIn.inMilliseconds > 0) {
58+
break;
59+
}
60+
61+
try {
62+
final stickerId = job.blazeMessage;
63+
if (stickerId != null && stickerId.isNotEmpty) {
64+
final sticker = (await client.accountApi.getStickerById(
65+
stickerId,
66+
)).data;
67+
await database.stickerDao.insert(sticker.asStickersCompanion);
68+
}
69+
await database.jobDao.deleteJobById(job.jobId);
70+
} catch (e, s) {
71+
if (e is MixinApiError) {
72+
var code = e.response?.statusCode;
73+
final error = e.error;
74+
if (code != 404 && error != null && error is MixinError) {
75+
code = error.code;
5276
}
53-
await database.jobDao.deleteJobById(job.jobId);
54-
} catch (e, s) {
55-
if (e is MixinApiError) {
56-
var code = e.response?.statusCode;
57-
final error = e.error;
58-
if (code != 404 && error != null && error is MixinError) {
59-
code = error.code;
60-
}
61-
if (code == 404) {
62-
i('Sticker not found: ${job.blazeMessage}');
63-
await database.jobDao.deleteJobById(job.jobId);
64-
return null;
65-
}
77+
if (code == 404) {
78+
i('Sticker not found: ${job.blazeMessage}');
79+
await database.jobDao.deleteJobById(job.jobId);
80+
continue;
6681
}
67-
w('Update sticker job error: $e, stack: $s');
68-
await Future.delayed(const Duration(seconds: 1));
69-
return job;
7082
}
71-
}),
72-
);
7383

74-
return list.where((element) => element != null).cast<Job>().toList();
84+
w('Update sticker job error: $e, stack: $s');
85+
86+
final nextRunAt = DateTime.now().add(_backoffDuration(job.runCount));
87+
await (database.mixinDatabase.update(
88+
database.mixinDatabase.jobs,
89+
)..where((tbl) => tbl.jobId.equals(job.jobId))).write(
90+
JobsCompanion(
91+
createdAt: Value(nextRunAt),
92+
runCount: Value(job.runCount + 1),
93+
),
94+
);
95+
}
96+
}
7597
}
7698
}
99+
100+
Duration _backoffDuration(int runCount) {
101+
if (runCount <= 0) return const Duration(minutes: 1);
102+
if (runCount == 1) return const Duration(minutes: 5);
103+
if (runCount == 2) return const Duration(minutes: 15);
104+
if (runCount == 3) return const Duration(hours: 1);
105+
return const Duration(hours: 6);
106+
}

macos/Runner.xcodeproj/project.pbxproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
archiveVersion = 1;
44
classes = {
55
};
6-
objectVersion = 60;
6+
objectVersion = 54;
77
objects = {
88

99
/* Begin PBXAggregateTarget section */
@@ -287,7 +287,7 @@
287287
);
288288
runOnlyForDeploymentPostprocessing = 0;
289289
shellPath = /bin/sh;
290-
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
290+
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n\n# Xcode may invoke this script with CURRENT_ARCH unset or as 'undefined_arch'.\n# Normalize it so Flutter can locate/build the correct artifacts.\nif [ -z \"${CURRENT_ARCH:-}\" ] || [ \"$CURRENT_ARCH\" = \"undefined_arch\" ]; then\n export CURRENT_ARCH=\"$(uname -m)\"\nfi\n\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
291291
};
292292
33CC111E2044C6BF0003C045 /* ShellScript */ = {
293293
isa = PBXShellScriptBuildPhase;
@@ -307,7 +307,7 @@
307307
);
308308
runOnlyForDeploymentPostprocessing = 0;
309309
shellPath = /bin/sh;
310-
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n";
310+
shellScript = "# Xcode may invoke this script with CURRENT_ARCH unset or as 'undefined_arch'.\n# Normalize it so Flutter can produce the correct App.framework.\nif [ -z \"${CURRENT_ARCH:-}\" ] || [ \"$CURRENT_ARCH\" = \"undefined_arch\" ]; then\n export CURRENT_ARCH=\"$(uname -m)\"\nfi\n\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n";
311311
};
312312
3A5B6F3B8685204095528592 /* [CP] Check Pods Manifest.lock */ = {
313313
isa = PBXShellScriptBuildPhase;

0 commit comments

Comments
 (0)