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
19 changes: 19 additions & 0 deletions lib/core/messages/library_messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ abstract class LibraryMessages {

static const String updateInstallerLaunchError = 'שגיאה בהפעלת מתקין העדכון';

static const String updateDiskSpaceError =
'אין מספיק מקום פנוי בדיסק לעדכון הספרייה';

static const String deltaApplyFailed = 'החלת עדכון הדלתא נכשלה';

static const String deltaResultMismatch =
Expand All @@ -99,4 +102,20 @@ abstract class LibraryMessages {

static String fullLibraryDownloadRequired(String reason, String size) =>
'$reason — נדרשת הורדה מלאה ($size)';

/// בחירת מסלול כשהחלת הדלתא צפויה להימשך זמן רב (issue #1211).
static String heavyDeltaRouteChoice({
required String reason,
required String deltaDownloadSize,
required String deltaApplySize,
required String fullDownloadSize,
}) =>
'$reason.\n'
'עדכון דלתא: הורדה קטנה ($deltaDownloadSize), אך פריסה של '
'$deltaApplySize והחלה ארוכה — עשרות דקות ומעלה. מתאים לרשת איטית.\n'
'הורדה מלאה: הורדה גדולה ($fullDownloadSize) והחלה מהירה — דקות.';

/// נלווה להודעת שלב ההחלה כשמסלול הדלתא כבד ואין הורדה מלאה חלופית.
static String applyStageWithHeavyDeltaNotice(String stageMessage) =>
'$stageMessage — ההחלה עשויה להימשך זמן רב';
}
1 change: 1 addition & 0 deletions lib/library/view/library_browser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ String libraryUpdateButtonTooltip(LibraryUpdateState state) =>
LibraryUpdateStatus.error => 'שגיאה בעדכון - לחץ לנסות שוב',
LibraryUpdateStatus.disconnected => '${state.message} - לחץ לנסות שוב',
LibraryUpdateStatus.needsFullConfirmation => state.message,
LibraryUpdateStatus.needsRouteChoice => state.message,
LibraryUpdateStatus.blocked => state.message,
_ when state.isBusy => state.message,
_ => 'עדכון ספרייה',
Expand Down
78 changes: 73 additions & 5 deletions lib/library_update/bloc/library_update_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
// לפני await של פתיחת ה-DB מחדש. כך Cancel/Reset לא יכולים לנצח את ה-state.
bool _fullDownloadDbReplaced = false;

// הריצה הנוכחית היא דלתא כבדה שאין לה חלופה מלאה — הודעות שלב ההחלה
// נושאות אזהרה שההחלה ארוכה.
bool _heavyDeltaNotice = false;

// שינוי נלווים שריצה מבוטלת לא יכלה לדווח כי ריצה חדשה כבר busy —
// הריצה החדשה תדווח אותו, אחרת הריענון/אינדוקס אובדים.
bool _unreportedAssetsChange = false;
Expand All @@ -80,6 +84,7 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
super(const LibraryUpdateState()) {
on<StartLibraryUpdate>(_onStart);
on<ConfirmFullDownload>(_onConfirmFull);
on<ConfirmHeavyDelta>(_onConfirmHeavyDelta);
on<DeclineFullDownload>(_onDeclineFull);
on<CancelLibraryUpdate>(_onCancel);
on<ResetLibraryUpdate>(_onReset);
Expand Down Expand Up @@ -151,6 +156,7 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
}

final opId = ++_operationId;
_heavyDeltaNotice = false;
_resetProgressThrottle();
emit(
const LibraryUpdateState(
Expand Down Expand Up @@ -188,6 +194,24 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
),
);
case LibraryUpdatePlanKind.delta:
// דלתא כבדה (issue #1211): ההחלה עשויה להימשך שעה. כשיש הורדה מלאה
// חלופית הבחירה היא של המשתמש; בלעדיה רק מזהירים וממשיכים.
if (plan.isHeavyDelta && plan.fullDbAsset != null) {
emit(
LibraryUpdateState(
status: LibraryUpdateStatus.needsRouteChoice,
message: LibraryMessages.heavyDeltaRouteChoice(
reason: plan.heavyDeltaReason!,
deltaDownloadSize: _formatSize(plan.totalDownloadSize),
deltaApplySize: _formatSize(plan.deltaUncompressedBytes),
fullDownloadSize: _formatSize(plan.fullDbAsset!.size),
),
plan: plan,
),
);
return;
}
_heavyDeltaNotice = plan.isHeavyDelta;
await _runDelta(plan, emit, opId);
case LibraryUpdatePlanKind.fullDownload:
emit(
Expand Down Expand Up @@ -325,7 +349,11 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
emit(
LibraryUpdateState(
status: LibraryUpdateStatus.error,
message: 'שגיאה בהחלת העדכון',
// חוסר מקום אינו כשל החלה: הוא נבדק לפני ההורדה, ולכן גם אינו מציע
// הורדה מלאה — היא דורשת עוד יותר מקום.
message: applyError is LibraryUpdateDiskSpaceException
? LibraryMessages.updateDiskSpaceError
: 'שגיאה בהחלת העדכון',
hasUpdate: partial?.hasDatabaseChanges ?? false,
changedBookIds: partial?.changedBookIds ?? const {},
requiresFullIndexRefresh: requiresFullIndexRefresh,
Expand All @@ -349,18 +377,26 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
Emitter<LibraryUpdateState> emit,
) async {
if (state.isBusy) return; // הורדה כבר רצה — מתעלמים מאישור כפול.
final plan = state.plan;
// בבחירת מסלול, ה-plan שב-state הוא תוכנית הדלתא — ההורדה המלאה היא
// ה-fallback שלה. חייב להיכנס ל-state, אחרת אין reconcile של האינדקס.
final plan = state.status == LibraryUpdateStatus.needsRouteChoice
? state.plan?.toFullDownloadFallback(
reason: state.plan?.heavyDeltaReason,
)
: state.plan;
if (plan == null || plan.kind != LibraryUpdatePlanKind.fullDownload) {
emit(const LibraryUpdateState());
return;
}
final opId = ++_operationId;
_heavyDeltaNotice = false;
_fullDownloadDbReplaced = false;
_resetProgressThrottle();
emit(
state.copyWith(
status: LibraryUpdateStatus.downloading,
message: 'מוריד ספרייה מלאה',
plan: plan,
),
);
try {
Expand Down Expand Up @@ -390,7 +426,9 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
emit(
LibraryUpdateState(
status: LibraryUpdateStatus.error,
message: 'שגיאה בהורדה המלאה',
message: e is LibraryUpdateDiskSpaceException
? LibraryMessages.updateDiskSpaceError
: 'שגיאה בהורדה המלאה',
// fallback אחרי דלתא חלקית: ההורדה המלאה נכשלה, אבל הצעדים שכבר
// נכתבו ל-DB עדיין דורשים ריענון ספרייה ואינדקס.
hasUpdate: dbReplaced || state.hasUpdate,
Expand All @@ -407,6 +445,28 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
}
}

Future<void> _onConfirmHeavyDelta(
ConfirmHeavyDelta event,
Emitter<LibraryUpdateState> emit,
) async {
if (state.isBusy) return;
final plan = state.plan;
if (plan == null || plan.kind != LibraryUpdatePlanKind.delta) {
emit(const LibraryUpdateState());
return;
}
final opId = ++_operationId;
_heavyDeltaNotice = plan.isHeavyDelta;
_resetProgressThrottle();
emit(
state.copyWith(
status: LibraryUpdateStatus.downloading,
message: 'מוריד עדכון ספרייה',
),
);
await _runDelta(plan, emit, opId);
}

/// מוודא שהקבצים הנלווים (תלמוד, קטלוגים, מילון) קיימים ומעודכנים, בסוף
/// כל בדיקת/החלת עדכון. best-effort — כשל לא הופך את העדכון לשגיאה.
/// מחזיר האם תוכן הספרייה השתנה (ראה [CompanionAssetsService.verifyAndUpdate]).
Expand Down Expand Up @@ -512,7 +572,9 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
case LibraryUpdateStatus.refreshing:
return false;
case LibraryUpdateStatus.applying:
return state.plan?.kind == LibraryUpdatePlanKind.fullDownload;
// בדלתא, applying לפני הכתיבה הוא אימות ה-patch הפרוס — עדיין ניתן לבטל.
return state.plan?.kind == LibraryUpdatePlanKind.fullDownload ||
!_deltaWriteStarted;
default:
return true;
}
Expand All @@ -523,6 +585,7 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
if (_fullDownloadDbReplaced && _pendingCompleted == null) return;
_operationId++;
_pendingCompleted = null;
_heavyDeltaNotice = false;
emit(const LibraryUpdateState());
}

Expand All @@ -542,7 +605,12 @@ class LibraryUpdateBloc extends Bloc<LibraryUpdateEvent, LibraryUpdateState> {
'מוריד עדכון ספרייה'
'${p.totalSteps > 1 ? ' (${p.stepIndex + 1}/${p.totalSteps})' : ''}',
LibraryUpdatePhase.verifying => 'מאמת קובץ עדכון',
LibraryUpdatePhase.applying => _applyStageMessage(p.stage),
LibraryUpdatePhase.applying =>
_heavyDeltaNotice
? LibraryMessages.applyStageWithHeavyDeltaNotice(
_applyStageMessage(p.stage),
)
: _applyStageMessage(p.stage),
LibraryUpdatePhase.refreshing => 'מרענן ספרייה',
LibraryUpdatePhase.done => 'מסיים',
};
Expand Down
5 changes: 5 additions & 0 deletions lib/library_update/bloc/library_update_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class ConfirmFullDownload extends LibraryUpdateEvent {
const ConfirmFullDownload();
}

/// המשתמש בחר במסלול הדלתא למרות שהחלתו צפויה להימשך זמן רב.
class ConfirmHeavyDelta extends LibraryUpdateEvent {
const ConfirmHeavyDelta();
}

/// המשתמש בחר לדחות הורדה מלאה ולהישאר עם הגרסה הנוכחית.
class DeclineFullDownload extends LibraryUpdateEvent {
const DeclineFullDownload();
Expand Down
6 changes: 5 additions & 1 deletion lib/library_update/bloc/library_update_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ enum LibraryUpdateStatus {
/// נדרש אישור משתמש להורדה מלאה גדולה.
needsFullConfirmation,

/// מסלול הדלתא זמין אך החלתו ארוכה מאוד — המשתמש בוחר בינו לבין הורדה מלאה.
needsRouteChoice,

/// מצב חסום שדורש פעולה ידנית.
blocked,

Expand All @@ -48,7 +51,8 @@ class LibraryUpdateState extends Equatable {
/// יחס התקדמות (0..1) בתוך שלב אימות ה-hash; null בשאר שלבי ה-apply.
final double? applyProgress;

/// התוכנית שנבחרה — זמינה במצב [LibraryUpdateStatus.needsFullConfirmation].
/// התוכנית שנבחרה — זמינה במצבי [LibraryUpdateStatus.needsFullConfirmation]
/// ו-[LibraryUpdateStatus.needsRouteChoice].
final LibraryUpdatePlan? plan;

/// מזהי ספרים (seforim.db) שתוכנם השתנה בעדכון דלתא — לרענון אינדקס החיפוש.
Expand Down
26 changes: 26 additions & 0 deletions lib/library_update/library_update_work_status.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/foundation.dart';
import 'package:otzaria/library_update/bloc/library_update_bloc.dart';
import 'package:otzaria/work_status/work_status_item.dart';
Expand All @@ -17,7 +18,32 @@ const kCheckFailureAutoDismiss = Duration(seconds: 8);
WorkStatusItem? libraryUpdateWorkStatusItem(
LibraryUpdateState state, {
required VoidCallback onRetry,
required VoidCallback onChooseDelta,
required VoidCallback onChooseFullDownload,
}) {
// שתי אפשרויות שקולות — אין המלצה: הבחירה תלויה במהירות הרשת של המשתמש.
if (state.status == LibraryUpdateStatus.needsRouteChoice) {
return WorkStatusItem(
id: kLibraryUpdateWorkStatusId,
title: 'עדכון ספרייה',
message: state.message,
detail: 'בחר כיצד לעדכן',
kind: WorkStatusKind.awaitingInput,
actions: [
WorkStatusAction(
label: 'עדכון דלתא',
icon: FluentIcons.arrow_download_24_regular,
onPressed: onChooseDelta,
),
WorkStatusAction(
label: 'הורדה מלאה',
icon: FluentIcons.database_24_regular,
onPressed: onChooseFullDownload,
),
],
);
}

if (state.isBusy && state.status != LibraryUpdateStatus.checking) {
return WorkStatusItem(
id: kLibraryUpdateWorkStatusId,
Expand Down
Loading