diff --git a/core/interface/InterfaceChannel.js b/core/interface/InterfaceChannel.js index 3b7adc2..ac11201 100644 --- a/core/interface/InterfaceChannel.js +++ b/core/interface/InterfaceChannel.js @@ -14,4 +14,8 @@ module.exports = { setTheme: 'settings.setTheme', getLastSynchronizationDate: 'settings.getLastSynchronizationDate', }, + synchronization: { + isRunning: 'synchronization.isRunning', + isRunningSync: 'synchronization.isRunning.sync', + }, }; diff --git a/core/interface/index.js b/core/interface/index.js index 3e3f3cc..86fc7dc 100644 --- a/core/interface/index.js +++ b/core/interface/index.js @@ -1,2 +1,3 @@ require('./application/ApplicationInterface'); require('./settings/SettingsInterface'); +require('./synchronization/SynchronizationInterface'); diff --git a/core/interface/synchronization/SynchronizationInterface.js b/core/interface/synchronization/SynchronizationInterface.js new file mode 100644 index 0000000..3e7c778 --- /dev/null +++ b/core/interface/synchronization/SynchronizationInterface.js @@ -0,0 +1,21 @@ +const synchronizationService = require('../../service/synchronization/SynchornizationService'); +const { ipcMain } = require('electron'); +const InterfaceChannel = require('../InterfaceChannel'); + +let isSynchronizingCache = false; + +synchronizationService + .getSynchronizationStatus() + .subscribe((isSynchronizing) => { + if (isSynchronizing) { + isSynchronizingCache = isSynchronizing; + } + ipcMain.emit( + InterfaceChannel.synchronization.isRunning, + isSynchronizing, + ); + }); + +ipcMain?.handle(InterfaceChannel.synchronization.isRunningSync, () => { + return isSynchronizingCache; +}); diff --git a/core/service/synchronization/SynchornizationService.js b/core/service/synchronization/SynchornizationService.js index 169ae47..12be4f9 100644 --- a/core/service/synchronization/SynchornizationService.js +++ b/core/service/synchronization/SynchornizationService.js @@ -1,4 +1,4 @@ -const { forkJoin } = require('rxjs'); +const { forkJoin, Subject } = require('rxjs'); const flathubSynchronizer = require('./synchronizer/FlathubSynchronizer'); const appImageHubSynchronizer = require('./synchronizer/AppImageHubSynchronizer'); @@ -7,6 +7,9 @@ const settingsService = require('../settings/SettingsService'); const DAY_IN_MILLIS = 1000 * 60 * 60 * 24 * 7; +const isSynchronizationRunning = new Subject(); +isSynchronizationRunning.next(false); + async function shouldSynchronize() { const now = new Date(); const lastSync = await settingsService.getLastSynchronizationDate(); @@ -28,6 +31,7 @@ async function startSynchronization() { } function synchronize() { + isSynchronizationRunning.next(true); forkJoin([ flathubSynchronizer.startSynchronization(), appImageHubSynchronizer.startSynchronization(), @@ -36,13 +40,16 @@ function synchronize() { () => { console.log('Synchronization succeeded'); settingsService.setLastSynchronizationDate(new Date()); + isSynchronizationRunning.next(false); }, (error) => { console.error(error); + isSynchronizationRunning.next(false); }, ); } module.exports = { startSynchronization, + getSynchronizationStatus: () => isSynchronizationRunning, }; diff --git a/src/app/service/synchronization/synchronization.service.spec.ts b/src/app/service/synchronization/synchronization.service.spec.ts new file mode 100644 index 0000000..e715af6 --- /dev/null +++ b/src/app/service/synchronization/synchronization.service.spec.ts @@ -0,0 +1,24 @@ +import { SynchronizationService } from './synchronization.service'; + +describe('SynchronizationService', () => { + let service: SynchronizationService; + + const mockCoreService = { + invoke: jest.fn(), + }; + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + service = new SynchronizationService(mockCoreService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should get current synchronization status', async () => { + mockCoreService.invoke.mockReturnValue(Promise.resolve(true)); + expect(await service.getCurrentSynchronizationStatus()).toBeTruthy(); + }); +}); diff --git a/src/app/service/synchronization/synchronization.service.ts b/src/app/service/synchronization/synchronization.service.ts new file mode 100644 index 0000000..92fd4ad --- /dev/null +++ b/src/app/service/synchronization/synchronization.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { CoreService } from '../core/core.service'; +import * as InterfaceChannel from '../../../../core/interface/InterfaceChannel'; + +@Injectable() +export class SynchronizationService { + constructor(private coreService: CoreService) {} + + getCurrentSynchronizationStatus(): Promise { + return this.coreService.invoke( + InterfaceChannel.synchronization.isRunningSync, + ); + } +} diff --git a/src/app/ui/pages/main/main.component.html b/src/app/ui/pages/main/main.component.html index c55debb..4d24c12 100644 --- a/src/app/ui/pages/main/main.component.html +++ b/src/app/ui/pages/main/main.component.html @@ -11,5 +11,14 @@ + + {{ 'PAGES.MAIN.SYNCHRONIZATION_MESSAGE' | translate }} + diff --git a/src/app/ui/pages/main/main.component.scss b/src/app/ui/pages/main/main.component.scss index d8f9e2e..350e259 100644 --- a/src/app/ui/pages/main/main.component.scss +++ b/src/app/ui/pages/main/main.component.scss @@ -16,3 +16,11 @@ nb-layout-column { app-main-menu { padding-bottom: 15rem; } + +.synchronization-message { + position: sticky; + bottom: 0.5rem; + margin-left: 10rem; + margin-right: 10rem; + z-index: 1; +} diff --git a/src/app/ui/pages/main/main.component.spec.ts b/src/app/ui/pages/main/main.component.spec.ts index 938da4d..fe52334 100644 --- a/src/app/ui/pages/main/main.component.spec.ts +++ b/src/app/ui/pages/main/main.component.spec.ts @@ -1,42 +1,35 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - import { MainComponent } from './main.component'; -import { MainMenuModule } from '../../components/main-menu/main-menu.module'; -import { NbLayoutModule, NbSidebarModule, NbThemeModule } from '@nebular/theme'; -import { NbEvaIconsModule } from '@nebular/eva-icons'; -import { RouterModule } from '@angular/router'; -import { APP_BASE_HREF } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; -import { ToolbarModule } from '../../components/toolbar/toolbar.module'; describe('MainComponent', () => { let component: MainComponent; - let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - MainMenuModule, - NbLayoutModule, - NbEvaIconsModule, - NbSidebarModule.forRoot(), - RouterModule.forRoot([]), - NbThemeModule.forRoot(), - TranslateModule.forRoot(), - ToolbarModule, - ], - declarations: [MainComponent], - providers: [{ provide: APP_BASE_HREF, useValue: './' }], - }).compileComponents(); - }); + const mockSynchronizationService = { + getCurrentSynchronizationStatus: jest.fn(), + }; beforeEach(() => { - fixture = TestBed.createComponent(MainComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + component = new MainComponent(mockSynchronizationService); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should get current synchronization status', (done) => { + mockSynchronizationService.getCurrentSynchronizationStatus.mockReturnValue( + Promise.resolve(true), + ); + component.ngOnInit(); + setTimeout(() => { + expect(component.isSynchronizationRunning).toBeTruthy(); + done(); + }, 0); + }); + + it('should hide synchronization message', () => { + component.closeSynchronizationMessage(); + expect(component.shouldShowSynchronizationMessage).toBeFalsy(); + }); }); diff --git a/src/app/ui/pages/main/main.component.ts b/src/app/ui/pages/main/main.component.ts index c6dcc4d..e4d788d 100644 --- a/src/app/ui/pages/main/main.component.ts +++ b/src/app/ui/pages/main/main.component.ts @@ -1,8 +1,30 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { SynchronizationService } from '../../../service/synchronization/synchronization.service'; @Component({ selector: 'app-main', templateUrl: './main.component.html', styleUrls: ['./main.component.scss'], }) -export class MainComponent {} +export class MainComponent implements OnInit { + shouldShowSynchronizationMessage = true; + isSynchronizationRunning = false; + + constructor(private synchronizationService: SynchronizationService) {} + + ngOnInit(): void { + this.getCurrentSynchronizationStatus(); + } + + private getCurrentSynchronizationStatus(): void { + this.synchronizationService + .getCurrentSynchronizationStatus() + .then((status) => { + this.isSynchronizationRunning = status; + }); + } + + closeSynchronizationMessage(): void { + this.shouldShowSynchronizationMessage = false; + } +} diff --git a/src/app/ui/pages/main/main.module.ts b/src/app/ui/pages/main/main.module.ts index 7b3f50d..bcb25f1 100644 --- a/src/app/ui/pages/main/main.module.ts +++ b/src/app/ui/pages/main/main.module.ts @@ -3,10 +3,17 @@ import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; import { MainComponent } from './main.component'; -import { NbLayoutModule, NbSidebarModule } from '@nebular/theme'; +import { + NbAlertModule, + NbCardModule, + NbLayoutModule, + NbSidebarModule, +} from '@nebular/theme'; import { NbEvaIconsModule } from '@nebular/eva-icons'; import { MainMenuModule } from '../../components/main-menu/main-menu.module'; import { ToolbarModule } from '../../components/toolbar/toolbar.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { SynchronizationService } from '../../../service/synchronization/synchronization.service'; /* eslint-disable @typescript-eslint/explicit-function-return-type */ const routes: Routes = [ @@ -81,6 +88,12 @@ const routes: Routes = [ NbLayoutModule, NbEvaIconsModule, NbSidebarModule.forRoot(), + NbCardModule, + NbAlertModule, + + // Other + TranslateModule.forChild(), ], + providers: [SynchronizationService], }) export class MainModule {} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 1f27982..135eddd 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -66,7 +66,8 @@ "INTERNET": "Internet and Communication", "UTILITY": "Utility and Productivity", "MISC": "Miscellaneous" - } + }, + "SYNCHRONIZATION_MESSAGE": "App Outlet is synchronizing its database. It can affect the search performance. When synchronization ends, it will back to normal" }, "SEARCH": { "NO_RESULTS_FOUND": "No results found",