Skip to content

Commit 74d7f67

Browse files
committed
fix(mobile): persist web cache and stabilize plugins
- keep last successful crossbar.web response for widgets - set app data dir for foreground and background workers - guard plugin discovery against transient empty scans - bump version to 1.8.0+15
1 parent da3961b commit 74d7f67

File tree

7 files changed

+124
-13
lines changed

7 files changed

+124
-13
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Quando terminar as tarefas solicitadas faça as seguintes etapas:
8080
## 2. Identidade do Projeto
8181

8282
- **Nome**: Crossbar (Universal Plugin System)
83-
- **Versão Atual**: `1.7.2+14` (atualize ao final de cada sessão).
83+
- **Versão Atual**: `1.8.0+15` (atualize ao final de cada sessão).
8484
- **Stack**: Flutter `3.38.3` (CI), Dart `3.10+`.
8585
- **Objetivo**: Sistema de plugins compatível com BitBar/Argos para Linux, Windows, macOS, Android e iOS.
8686
- **Status**: Estável (v1.0+). Todas as fases do plano original concluídas.

lib/core/plugin_manager.dart

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class PluginManager {
2121
final PluginExecutor _pluginExecutor = PluginExecutor();
2222
final PluginConfigService _configService = PluginConfigService();
2323
static const int maxConcurrent = 10;
24+
int _emptyDiscoveryStreak = 0;
2425

2526
static const List<String> supportedLanguages = [
2627
'bash',
@@ -85,21 +86,29 @@ class PluginManager {
8586
}
8687

8788
Future<void> discoverPlugins() async {
88-
_plugins.clear();
89-
9089
final pluginsDirPath = await pluginsDirectory;
9190
final pluginsDir = Directory(pluginsDirPath);
9291

9392
if (!await pluginsDir.exists()) {
93+
if (_plugins.isNotEmpty) {
94+
_emptyDiscoveryStreak++;
95+
if (_emptyDiscoveryStreak < 2) {
96+
return;
97+
}
98+
}
99+
_emptyDiscoveryStreak = 0;
100+
_plugins.clear();
94101
return;
95102
}
96103

97104
// Map to group plugins by their directory and base name
98105
// Key: directory_path:base_name
99106
final groups = <String, List<File>>{};
107+
var foundFiles = 0;
100108

101109
await for (final entity in pluginsDir.list(recursive: true)) {
102110
if (entity is File && _isValidPluginFile(entity.path)) {
111+
foundFiles++;
103112
final dir = path.dirname(entity.path);
104113
final fileName = path.basename(entity.path);
105114

@@ -112,16 +121,34 @@ class PluginManager {
112121
}
113122
}
114123

124+
final discovered = <Plugin>[];
125+
115126
for (final entry in groups.entries) {
116127
final files = entry.value;
117128
if (files.isEmpty) continue;
118129

119130
// Create a single plugin representing this group
120131
final plugin = await _createPluginFromGroup(files);
121132
if (plugin != null) {
122-
_plugins.add(plugin);
133+
discovered.add(plugin);
123134
}
124135
}
136+
137+
if (_plugins.isNotEmpty) {
138+
final emptyDiscovery =
139+
foundFiles == 0 || (foundFiles > 0 && discovered.isEmpty);
140+
if (emptyDiscovery) {
141+
_emptyDiscoveryStreak++;
142+
if (_emptyDiscoveryStreak < 2) {
143+
return;
144+
}
145+
}
146+
}
147+
148+
_emptyDiscoveryStreak = 0;
149+
_plugins
150+
..clear()
151+
..addAll(discovered);
125152
}
126153

127154
String _extractPluginBaseName(String fileName) {
@@ -479,5 +506,6 @@ class PluginManager {
479506

480507
void clear() {
481508
_plugins.clear();
509+
_emptyDiscoveryStreak = 0;
482510
}
483511
}

lib/main.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'dart:io';
22
import 'dart:ui';
33

4+
import 'package:crossbar_core/crossbar_core.dart';
45
import 'package:flutter/material.dart';
56
import 'package:flutter/services.dart';
6-
7-
import 'package:crossbar_core/crossbar_core.dart';
7+
import 'package:path_provider/path_provider.dart';
88
import 'core/api/android_native_bridge.dart';
99
import 'core/plugin_manager.dart';
1010
import 'services/background_service.dart';
@@ -47,6 +47,11 @@ void main(List<String> args) async {
4747
CrossbarBridge.instance.androidBridge = AndroidNativeBridge();
4848
}
4949

50+
if (Platform.isAndroid || Platform.isIOS) {
51+
final appDir = await getApplicationDocumentsDirectory();
52+
CrossbarBridge.instance.appDataDir = appDir.path;
53+
}
54+
5055
// Register Android widget refresh handler EARLY
5156
// This catches refresh requests even before services are fully initialized
5257
if (Platform.isAndroid) {

lib/services/background_service.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import 'dart:convert';
22
import 'dart:io';
33

4+
import 'package:crossbar_core/crossbar_core.dart';
5+
import 'package:flutter/widgets.dart';
46
import 'package:home_widget/home_widget.dart';
7+
import 'package:path_provider/path_provider.dart';
58
import 'package:workmanager/workmanager.dart';
69

710
import '../core/plugin_executor.dart';
@@ -34,6 +37,10 @@ void callbackDispatcher() {
3437

3538
/// Updates widgets in background without full app initialization
3639
Future<void> _updateWidgetsInBackground() async {
40+
WidgetsFlutterBinding.ensureInitialized();
41+
final appDir = await getApplicationDocumentsDirectory();
42+
CrossbarBridge.instance.appDataDir = appDir.path;
43+
3744
// Initialize HomeWidget
3845
await HomeWidget.setAppGroupId('group.crossbar.widgets');
3946

packages/crossbar_core/lib/src/core/bridge/crossbar_bridge.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ class CrossbarBridge {
2525
AndroidBridgeInterface _androidBridge =
2626
AndroidNativeBridge(); // Default to stub
2727

28+
String? _appDataDir;
29+
30+
/// Optional app-private data directory (mobile).
31+
set appDataDir(String? value) => _appDataDir = value;
32+
String? get appDataDir => _appDataDir;
33+
2834
/// Inject a platform-specific implementation (e.g. from Flutter)
2935
set androidBridge(AndroidBridgeInterface bridge) => _androidBridge = bridge;
3036

packages/crossbar_core/lib/src/core/lua_runner.dart

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class LuaRunner {
103103
_registerConfigTable(lua, configValues);
104104
_registerConfigGet(lua, configValues);
105105
_registerStorage(lua, pluginId);
106-
_registerWeb(lua);
106+
_registerWeb(lua, pluginId);
107107
_registerSyncNoArgFunc(lua, 'platform', () => _bridge.platform);
108108
_registerSyncNoArgFunc(lua, 'homeDir', () => _bridge.homeDir);
109109
_registerSyncNoArgBool(lua, 'isMobile', () => _bridge.isMobile);
@@ -336,7 +336,7 @@ class LuaRunner {
336336
lua.setField(-2, 'execResult');
337337
}
338338

339-
void _registerWeb(LuaState lua) {
339+
void _registerWeb(LuaState lua, String pluginId) {
340340
lua.pushDartFunction((ls) {
341341
final url = ls.getTop() >= 1 ? ls.toStr(1) : null;
342342
if (url == null || url.isEmpty) {
@@ -361,18 +361,26 @@ class LuaRunner {
361361
}
362362

363363
if (_bridge.isMobile) {
364-
final cacheKey = _buildWebCacheKey(
364+
final cacheKey = '$pluginId|${_buildWebCacheKey(
365365
url,
366366
method: method,
367367
headers: headers,
368368
body: body,
369369
timeout: timeout,
370370
raw: raw,
371-
);
371+
)}';
372+
373+
var cached = _webCache[cacheKey];
374+
if (cached == null) {
375+
cached = _readWebCache(pluginId, cacheKey);
376+
if (cached != null) {
377+
_webCache[cacheKey] = cached;
378+
}
379+
}
372380

373-
final cached = _webCache[cacheKey];
374381
if (!_webInFlight.contains(cacheKey)) {
375382
_webInFlight.add(cacheKey);
383+
final cachedSnapshot = cached;
376384
_bridge
377385
.web(
378386
url,
@@ -382,8 +390,14 @@ class LuaRunner {
382390
timeout: timeout ?? 30,
383391
)
384392
.then((response) {
385-
_webCache[cacheKey] = response;
393+
if (_isSuccessfulWebResponse(response)) {
394+
_webCache[cacheKey] = response;
395+
_writeWebCache(pluginId, cacheKey, response);
396+
} else if (cachedSnapshot == null) {
397+
_webCache[cacheKey] = response;
398+
}
386399
}).catchError((e) {
400+
if (cachedSnapshot != null) return;
387401
_webCache[cacheKey] = {
388402
'error': true,
389403
'message': e.toString(),
@@ -500,6 +514,57 @@ class LuaRunner {
500514
}
501515
}
502516

517+
String _resolveWebCacheBaseDir() {
518+
final appDir = _bridge.appDataDir;
519+
if (appDir != null && appDir.isNotEmpty && appDir != '~') {
520+
return appDir;
521+
}
522+
if (_bridge.isMobile) {
523+
return Directory.systemTemp.path;
524+
}
525+
return _bridge.homeDir;
526+
}
527+
528+
File _webCacheFile(String pluginId, String cacheKey) {
529+
final safeId = _sanitizePluginId(pluginId.isEmpty ? 'unknown' : pluginId);
530+
final baseDir = _resolveWebCacheBaseDir();
531+
final dir = Directory(path.join(baseDir, 'crossbar_web_cache', safeId));
532+
final fileName = _bridge.hash(cacheKey);
533+
return File(path.join(dir.path, '$fileName.json'));
534+
}
535+
536+
dynamic _readWebCache(String pluginId, String cacheKey) {
537+
final file = _webCacheFile(pluginId, cacheKey);
538+
if (!file.existsSync()) return null;
539+
try {
540+
return jsonDecode(file.readAsStringSync());
541+
} catch (_) {
542+
return null;
543+
}
544+
}
545+
546+
void _writeWebCache(String pluginId, String cacheKey, dynamic value) {
547+
try {
548+
final file = _webCacheFile(pluginId, cacheKey);
549+
final dir = file.parent;
550+
if (!dir.existsSync()) {
551+
dir.createSync(recursive: true);
552+
}
553+
file.writeAsStringSync(jsonEncode(value));
554+
} catch (_) {
555+
// Ignore cache write errors
556+
}
557+
}
558+
559+
bool _isSuccessfulWebResponse(dynamic response) {
560+
if (response is Map) {
561+
if (response['error'] == true) return false;
562+
final status = response['status'];
563+
if (status is int && status >= 400) return false;
564+
}
565+
return true;
566+
}
567+
503568
void _registerStorage(LuaState lua, String pluginId) {
504569
lua.pushDartFunction((ls) {
505570
final key = ls.getTop() >= 1 ? ls.toStr(1) : null;

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: crossbar
22
description: Universal Plugin System for Taskbar/Menu Bar - Cross-platform BitBar/Argos alternative
33
publish_to: "none"
4-
version: 1.7.2+14
4+
version: 1.8.0+15
55

66
environment:
77
sdk: ^3.10.0

0 commit comments

Comments
 (0)