Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions app/lib/folders/watched_folders.dart
Original file line number Diff line number Diff line change
Expand Up @@ -812,8 +812,13 @@ class WatchedFolders extends ChangeNotifier with WidgetsBindingObserver {

final needsObservation = file.modified == 0 || file.modified > now;

if (needsObservation && (previous?['stamp'] != file.stamp || previous?['seen'] == null)) {
await repository.save('files', recordId, {...?previous, 'stamp': file.stamp, 'seen': now}, folderId: folder.id);
if (needsObservation && (previous?['observedStamp'] != file.stamp || previous?['seen'] == null)) {
await repository.save('files', recordId, {
...?previous,
'observedStamp': file.stamp,
'seen': now,
}, folderId: folder.id);

return _FileResult.deferred;
}

Expand Down
26 changes: 14 additions & 12 deletions app/lib/widgets/profile/storage_sync_settings_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -537,18 +537,20 @@ class StorageSyncSettingsSection extends StatelessWidget {
final database = context.read<PapyrusPowerSyncService>();
await folders?.suspend();

await database.clearGuestLibrary(
deleteFiles: (books) async {
for (final book in books) {
await files.deleteBookFile(book.id);
}

await files.clearCoverFiles(MediaStorageScope.localGuest);
await folders?.clear();
},
);

folders?.resume();
try {
await database.clearGuestLibrary(
deleteFiles: (books) async {
for (final book in books) {
await files.deleteBookFile(book.id);
}

await files.clearCoverFiles(MediaStorageScope.localGuest);
await folders?.clear();
},
);
} finally {
folders?.resume();
}

messenger.showSnackBar(
snackBarAnimationStyle: snackBarAnimationStyle,
Expand Down
22 changes: 22 additions & 0 deletions app/test/folders/watched_folders_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,28 @@ void main() {
expect(target.books, hasLength(1));
});

for (final clockCatchesUp in [false, true]) {
test('future-dated changes stay eligible when the clock catches up: $clockCatchesUp', () async {
access.put('book.epub', [1]);
final folder = await connect();
final original = target.books.single;
access.put('book.epub', [2]);
access.reportedModified = DateTime.now().add(const Duration(seconds: 10)).millisecondsSinceEpoch;
await folders.scanNow(folder.id);
expect(await folders.issues(folder.id), isEmpty);
expect(access.reads, 1);

if (clockCatchesUp) {
clockOffset = const Duration(seconds: 20);
}

await folders.scanNow(folder.id);
expect((await folders.issues(folder.id)).single['state'], 'changed');
expect(target.books.single, same(original));
expect(access.reads, 2);
});
}

for (final condition in ['recent', 'missing timestamp', 'future timestamp', 'changed during read']) {
test('one-time selections finish files deferred for $condition before pausing', () async {
await folders.activate(null);
Expand Down
33 changes: 33 additions & 0 deletions app/test/pages/profile_storage_sync_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:papyrus/auth/papyrus_api_config.dart';
import 'package:papyrus/auth/token_store.dart';
import 'package:papyrus/data/data_store.dart';
import 'package:papyrus/data/repositories/book_repository.dart';
import 'package:papyrus/folders/watched_folders.dart';
import 'package:papyrus/media/media_upload_queue.dart';
import 'package:papyrus/media/media_storage_scope.dart';
import 'package:papyrus/services/book_import_service_stub.dart'
Expand Down Expand Up @@ -129,6 +130,29 @@ class _Files extends BookImportService {
}
}

class _Folders extends ChangeNotifier implements WatchedFolders {
bool suspended = false;
int clearCalls = 0;

@override
Future<void> suspend() async {
suspended = true;
}

@override
void resume() {
suspended = false;
}

@override
Future<void> clear() async {
clearCalls++;
}

@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
Expand Down Expand Up @@ -162,6 +186,7 @@ void main() {
AcquisitionAvailabilityProvider? acquisitionAvailabilityProvider,
Widget Function(Widget page)? pageBuilder,
BookImportService? files,
WatchedFolders? folders,
}) async {
final prefs = await SharedPreferences.getInstance();

Expand All @@ -179,6 +204,7 @@ void main() {
),
Provider<PapyrusPowerSyncService>.value(value: powerSyncService),
if (files != null) Provider<BookImportService>.value(value: files),
if (folders != null) ChangeNotifierProvider<WatchedFolders>.value(value: folders),
StreamProvider<SyncState>.value(value: powerSyncService.syncStates, initialData: powerSyncService.syncState),
ChangeNotifierProvider<AuthProvider>.value(value: authProvider),
ChangeNotifierProvider<AcquisitionAvailabilityProvider>.value(
Expand Down Expand Up @@ -210,13 +236,16 @@ void main() {
final auth = await buildAuthProvider(guest: true);
final service = _FakePowerSyncService(currentMode: LibraryDatabaseMode.guest, currentSyncState: const SyncState());
final files = _Files()..failDeletion = true;
final folders = _Folders();
addTearDown(folders.dispose);
service.guestBooks = [Book(id: 'guest-book', title: 'Guest book', author: 'Reader', addedAt: DateTime.now())];

await tester.pumpWidget(
await buildPage(
authProvider: auth,
powerSyncService: service,
files: files,
folders: folders,
pageBuilder: (_) => const Scaffold(body: StorageSyncSettingsSection()),
),
);
Expand All @@ -229,6 +258,8 @@ void main() {
expect(find.textContaining('Could not clear local library'), findsOneWidget);
expect(service.guestBooks.single.id, 'guest-book');
expect(service.clearGuestLibraryCalls, 0);
expect(folders.suspended, isFalse);
expect(folders.clearCalls, 0);
files.failDeletion = false;
await tester.tap(find.text('Clear local library'));
await tester.pumpAndSettle();
Expand All @@ -238,6 +269,8 @@ void main() {
expect(files.clearedScopes, [MediaStorageScope.localGuest]);
expect(service.guestBooks, isEmpty);
expect(service.clearGuestLibraryCalls, 1);
expect(folders.suspended, isFalse);
expect(folders.clearCalls, 1);
expect(auth.isOfflineMode, isTrue);
});

Expand Down
Loading