feat(core): bettery save mode (#12996)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Introduced a battery save mode that can pause background sync and
processing to conserve energy.
* Battery save mode is automatically enabled when the app window loses
focus and disabled when the window regains focus or is interacted with.
  * Available in both web and desktop (Electron) versions.

* **Improvements**
* Added user activity detection to optimize background operations for
better battery efficiency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
EYHN
2025-07-03 11:48:17 +08:00
committed by GitHub
parent 2ae3c3e2cd
commit b2bf591463
6 changed files with 102 additions and 0 deletions

View File

@@ -159,6 +159,14 @@ export class IndexerSyncImpl implements IndexerSync {
this.remote = Object.values(this.peers.remotes).find(remote => !!remote);
}
enableBatterySaveMode() {
this.status.enableBatterySaveMode();
}
disableBatterySaveMode() {
this.status.disableBatterySaveMode();
}
start() {
if (this.abort) {
this.abort.abort(MANUALLY_STOP);
@@ -611,6 +619,10 @@ class IndexerSyncStatus {
currentJob: string | null = null;
errorMessage: string | null = null;
statusUpdatedSubject$ = new Subject<string | true>();
batterySaveMode: {
promise: Promise<void>;
resolve: () => void;
} | null = null;
state$ = new Observable<IndexerSyncState>(subscribe => {
const next = () => {
@@ -685,6 +697,9 @@ class IndexerSyncStatus {
}
async acceptJob(abort?: AbortSignal) {
if (this.batterySaveMode) {
await this.batterySaveMode.promise;
}
const job = await this.jobs.asyncPop(abort);
this.currentJob = job;
this.statusUpdatedSubject$.next(job);
@@ -709,6 +724,18 @@ class IndexerSyncStatus {
};
}
enableBatterySaveMode() {
if (this.batterySaveMode) {
return;
}
this.batterySaveMode = Promise.withResolvers();
}
disableBatterySaveMode() {
this.batterySaveMode?.resolve();
this.batterySaveMode = null;
}
reset() {
// reset all state, except prioritySettings
this.isReadonly = false;

View File

@@ -86,6 +86,21 @@ export class StoreManagerClient {
connection.dispose();
});
}
enableBatterySaveMode() {
this.connections.forEach(connection => {
connection.store.enableBatterySaveMode().catch(err => {
console.error('error enabling battery save mode', err);
});
});
}
disableBatterySaveMode() {
this.connections.forEach(connection => {
connection.store.disableBatterySaveMode().catch(err => {
console.error('error disabling battery save mode', err);
});
});
}
}
export class StoreClient {
@@ -113,6 +128,13 @@ export class StoreClient {
readonly blobFrontend: BlobFrontend;
readonly awarenessFrontend: AwarenessFrontend;
readonly indexerFrontend: IndexerFrontend;
enableBatterySaveMode(): Promise<void> {
return this.client.call('sync.enableBatterySaveMode');
}
disableBatterySaveMode(): Promise<void> {
return this.client.call('sync.disableBatterySaveMode');
}
}
class WorkerDocStorage implements DocStorage {

View File

@@ -134,6 +134,35 @@ class StoreConsumer {
}
}
private readonly ENABLE_BATTERY_SAVE_MODE_DELAY = 1000;
private enableBatterySaveModeTimeout: NodeJS.Timeout | null = null;
private enabledBatterySaveMode = false;
enableBatterySaveMode() {
if (this.enableBatterySaveModeTimeout || this.enabledBatterySaveMode) {
return;
}
this.enableBatterySaveModeTimeout = setTimeout(() => {
if (!this.enabledBatterySaveMode) {
this.indexerSync.enableBatterySaveMode();
this.enabledBatterySaveMode = true;
console.log('[BatterySaveMode] enabled');
}
}, this.ENABLE_BATTERY_SAVE_MODE_DELAY);
}
disableBatterySaveMode() {
if (this.enableBatterySaveModeTimeout) {
clearTimeout(this.enableBatterySaveModeTimeout);
this.enableBatterySaveModeTimeout = null;
}
if (this.enabledBatterySaveMode) {
this.indexerSync.disableBatterySaveMode();
this.enabledBatterySaveMode = false;
console.log('[BatterySaveMode] disabled');
}
}
private registerHandlers(consumer: OpConsumer<WorkerOps>) {
const collectJobs = new Map<
string,
@@ -285,6 +314,8 @@ class StoreConsumer {
this.indexerSync.search$(table, query, options),
'indexerSync.subscribeAggregate': ({ table, query, field, options }) =>
this.indexerSync.aggregate$(table, query, field, options),
'sync.enableBatterySaveMode': () => this.enableBatterySaveMode(),
'sync.disableBatterySaveMode': () => this.disableBatterySaveMode(),
});
}
}

View File

@@ -140,6 +140,10 @@ interface GroupedWorkerOps {
AggregateResult<any, any>,
];
};
sync: {
enableBatterySaveMode: [void, void];
disableBatterySaveMode: [void, void];
};
}
type Values<T> = T extends { [k in keyof T]: any } ? T[keyof T] : never;