@@ -7,13 +7,18 @@ import 'package:papyrus/auth/auth_repository.dart';
77import 'package:papyrus/auth/papyrus_api_config.dart' ;
88import 'package:papyrus/auth/token_store.dart' ;
99import 'package:papyrus/data/data_store.dart' ;
10+ import 'package:papyrus/media/media_cache_service.dart' ;
11+ import 'package:papyrus/media/media_models.dart' ;
12+ import 'package:papyrus/media/media_upload_queue.dart' ;
1013import 'package:papyrus/powersync/powersync_service.dart' ;
1114import 'package:papyrus/powersync/papyrus_powersync_connector.dart' ;
1215import 'package:papyrus/powersync/sync_state.dart' ;
1316import 'package:papyrus/providers/auth_provider.dart' ;
1417import 'package:papyrus/providers/library_provider.dart' ;
1518import 'package:papyrus/providers/preferences_provider.dart' ;
1619import 'package:papyrus/providers/sync_settings_provider.dart' ;
20+ import 'package:papyrus/services/book_import_service_stub.dart'
21+ if (dart.library.js_interop) 'package:papyrus/services/book_import_service.dart' ;
1722import 'package:papyrus/providers/sidebar_provider.dart' ;
1823import 'package:papyrus/themes/app_theme.dart' ;
1924import 'package:provider/provider.dart' ;
@@ -42,6 +47,8 @@ class _PapyrusState extends State<Papyrus> {
4247 late final DataStore _dataStore;
4348 late final AuthProvider _authProvider;
4449 late final SyncSettingsProvider _syncSettingsProvider;
50+ late final MediaUploadQueue _mediaUploadQueue;
51+ late final BookImportService _bookImportService;
4552 late final PapyrusPowerSyncService _powerSyncService;
4653 late final PapyrusApiConfig _officialApiConfig;
4754 late AuthRepository _authRepository;
@@ -59,10 +66,15 @@ class _PapyrusState extends State<Papyrus> {
5966 _authRepository = _buildAuthRepository (_syncSettingsProvider.activeApiConfig, _activeProfileKey);
6067
6168 _dataStore = DataStore ();
69+ _mediaUploadQueue = MediaUploadQueue (widget.prefs);
70+ _bookImportService = BookImportService ();
6271 _authProvider = AuthProvider (widget.prefs, repository: _authRepository);
6372 _powerSyncService = PapyrusPowerSyncService (
64- connectorFactory: () =>
65- PapyrusPowerSyncConnector (authRepository: _authRepository, config: _syncSettingsProvider.activeApiConfig),
73+ connectorFactory: () => PapyrusPowerSyncConnector (
74+ authRepository: _authRepository,
75+ config: _syncSettingsProvider.activeApiConfig,
76+ onUploadComplete: _processMediaUploads,
77+ ),
6678 );
6779 unawaited (_dataStore.attachBookRepository (_powerSyncService));
6880 _appRouter = AppRouter (authProvider: _authProvider);
@@ -76,6 +88,7 @@ class _PapyrusState extends State<Papyrus> {
7688 _authProvider.removeListener (_syncPowerSyncAuthState);
7789 _syncSettingsProvider.removeListener (_handleSyncSettingsChanged);
7890 unawaited (_disposeDataServices ());
91+ _bookImportService.dispose ();
7992 _authProvider.dispose ();
8093 _syncSettingsProvider.dispose ();
8194 super .dispose ();
@@ -99,6 +112,8 @@ class _PapyrusState extends State<Papyrus> {
99112 if (user != null && ! _authProvider.isOfflineMode) {
100113 final userId = user.userId;
101114 unawaited (_powerSyncService.activateAuthenticated (userId, profileKey: _activeProfileKey));
115+ unawaited (_refreshMediaUsage ());
116+ unawaited (_processMediaUploads ());
102117 return ;
103118 }
104119
@@ -128,20 +143,52 @@ class _PapyrusState extends State<Papyrus> {
128143 await _powerSyncService.deactivate (clearAuthenticated: false );
129144 _authRepository = _buildAuthRepository (_syncSettingsProvider.activeApiConfig, _activeProfileKey);
130145 await _authProvider.replaceRepository (_authRepository, bootstrapNewRepository: ! _authProvider.isOfflineMode);
146+ unawaited (_refreshMediaUsage ());
131147 } finally {
132148 _switchingSyncProfile = false ;
133149 _syncPowerSyncAuthState ();
134150 }
135151 }
136152
153+ Future <void > _refreshMediaUsage () async {
154+ if (! _authProvider.isSignedIn || _authProvider.isOfflineMode) return ;
155+ try {
156+ await _mediaUploadQueue.refreshUsage (_authRepository.fetchMediaUsage);
157+ } catch (_) {
158+ // Usage is informational; failed refresh must not block data sync.
159+ }
160+ }
161+
162+ Future <void > _processMediaUploads () async {
163+ if (! _authProvider.isSignedIn || _authProvider.isOfflineMode) return ;
164+ await _mediaUploadQueue.processPending (
165+ dataStore: _dataStore,
166+ readBookFile: _bookImportService.getBookFile,
167+ uploadMedia: (payload) async {
168+ try {
169+ return await _authRepository.uploadMedia (payload);
170+ } on AuthApiException catch (error) {
171+ if (error.statusCode == 409 ) {
172+ throw const MediaUploadException .storageFull ();
173+ }
174+ rethrow ;
175+ }
176+ },
177+ );
178+ await _refreshMediaUsage ();
179+ }
180+
137181 @override
138182 Widget build (BuildContext context) {
139183 return MultiProvider (
140184 providers: [
141185 // Core data store - single source of truth
142186 ChangeNotifierProvider .value (value: _dataStore),
187+ ChangeNotifierProvider .value (value: _mediaUploadQueue),
143188 ChangeNotifierProvider .value (value: _syncSettingsProvider),
144189 Provider .value (value: _powerSyncService),
190+ Provider .value (value: _bookImportService),
191+ Provider (create: _createMediaCacheService),
145192 StreamProvider <SyncState >.value (value: _powerSyncService.syncStates, initialData: _powerSyncService.syncState),
146193 // Auth and UI state providers
147194 ChangeNotifierProvider .value (value: _authProvider),
@@ -165,3 +212,5 @@ class _PapyrusState extends State<Papyrus> {
165212 );
166213 }
167214}
215+
216+ MediaCacheService _createMediaCacheService (BuildContext _) => const MediaCacheService ();
0 commit comments