diff --git a/.dockerignore b/.dockerignore index e5c8b1c..ced1b76 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,8 @@ # compiled output out +__pycache__ +target +dist # IDEs and editors mediarepo-api/.idea @@ -36,4 +39,4 @@ mediarepo-daemon/*.folded # api mediarepo-api/.idea -mediarepo-api/target +mediarepo-api/target \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a29a7f..3135cc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,7 +91,7 @@ jobs: python-version: '^3.7' - name: Build - run: python build.py build --daemon --verbose + run: python scripts/build.py daemon --verbose --install-tooling - name: Upload artifacts if: ${{ !env.ACT }} @@ -150,7 +150,7 @@ jobs: DEBIAN_FRONTEND=noninteractive sudo apt-get install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev -y - name: Build project - run: python build.py build --ui --verbose + run: python scripts/build.py ui --verbose --install-tooling - name: Upload artifacts if: ${{ !env.ACT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 757ceb7..81f77d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: python-version: '^3.7' - name: Build Daemon - run: python build.py build --daemon --verbose + run: python scripts/build.py daemon --verbose --install-tooling - uses: vimtor/action-zip@v1 with: @@ -113,7 +113,7 @@ jobs: DEBIAN_FRONTEND=noninteractive sudo apt-get install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev -y - name: Build project - run: python build.py build --ui --verbose + run: python scripts/build.py ui --verbose --install-tooling - uses: vimtor/action-zip@v1 with: diff --git a/.gitignore b/.gitignore index 2ca27a3..df4f7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /out-tsc /target /out +__pycache__ # IDEs and editors mediarepo-api/.idea diff --git a/Dockerfile b/Dockerfile index e9caab0..d1dc609 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /usr/src COPY mediarepo-api ./mediarepo-api COPY mediarepo-daemon ./mediarepo-daemon COPY mediarepo-ui ./mediarepo-ui -COPY build.py . +COPY scripts ./scripts RUN apt-get update RUN apt-get install -y \ @@ -37,4 +37,6 @@ RUN apt remove cmdtest -y RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN python3 build.py build +RUN python3 scripts/clean.py +RUN python3 scripts/check.py --install +RUN python3 scripts/build.py all --verbose --ffmpeg diff --git a/README.md b/README.md index dd07f19..a837fd3 100644 --- a/README.md +++ b/README.md @@ -69,23 +69,32 @@ You also need to have a working [python](https://www.python.org/) installation o After all required dependencies are installed and tools are accessible in the `PATH`, you can build the project like follows: -> Note: You might need to make the `build.py` file executable with `chmod +x build.py`. +Check (and install) required tooling: +```sh +$ ./scripts/check.py --install +``` +> Note: this only installs tools that are installable via cargo or npm All Componens: ```sh -$ ./build.py build --ffmpeg +$ ./scripts/build.py all --ffmpeg ``` Daemon only: ```sh -$ ./build.py build --daemon --ffmpeg +$ ./scripts/build.py daemon --ffmpeg ``` If you don't want to build with ffmpeg support omit the `--ffmpeg` flag. UI only: ```sh -$ ./build.py build --ui +$ ./scripts/build.py ui +``` + +Clean the output directory: +```sh +$ ./scripts/clean.py ``` After building the `out` directory contains all the built binaries and bundles. diff --git a/build.py b/build.py deleted file mode 100755 index fc6be03..0000000 --- a/build.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/bin/env python3 -import shutil as shut -import os -import subprocess - -tauri_cli_version = '1.0.0-rc.5' -build_output = 'out' -verbose = False -ffmpeg = False - -windows = os.name == 'nt' - - -def exec(cmd: str, dir: str = None) -> str: - print('Running: {}'.format(cmd)) - child = subprocess.run(cmd, shell=True, cwd=dir) - child.check_returncode() - - -def check_exec(name: str): - print('Checking {}...'.format(name)) - - if shut.which(name) is None: - raise Exception('{} not found'.format(name)) - exec(name + ' --version') - - -def check_yarn(): - print('Checking yarn...') - - if shut.which('yarn') is None: - print('installing yarn...') - npm('install -g yarn') - check_exec('yarn') - exec('yarn --version') - - -def check_ng(): - print('Checking ng...') - - if shut.which('ng') is None: - print('installing ng...') - npm('install -g @angular/cli') - check_exec('ng') - exec('ng --version') - - -def store_artifact(path: str): - print('Storing {}'.format(path)) - if os.path.isdir(path): - shut.copytree(path, os.path.join( - build_output, os.path.basename(path)), dirs_exist_ok=True) - else: - shut.copy(path, build_output) - - -def cargo(cmd: str, dir: str = None): - if verbose: - exec('cargo {} --verbose'.format(cmd), dir) - else: - exec('cargo {}'.format(cmd), dir) - - -def npm(cmd: str, dir: str = None): - exec('npm {}'.format(cmd), dir) - - -def yarn(cmd: str, dir: str = None): - exec('yarn {}'.format(cmd), dir) - - -def build_daemon(): - '''Builds daemon''' - cargo('fetch', 'mediarepo-daemon') - - if not ffmpeg: - cargo('build --release --frozen --no-default-features', 'mediarepo-daemon') - else: - cargo('build --release --frozen', 'mediarepo-daemon') - - if windows: - store_artifact('mediarepo-daemon/target/release/mediarepo-daemon.exe') - else: - store_artifact('mediarepo-daemon/target/release/mediarepo-daemon') - - -def build_ui(): - '''Builds UI''' - cargo('install tauri-cli --version ^{}'.format(tauri_cli_version)) - yarn('install', 'mediarepo-ui') - cargo('tauri build', 'mediarepo-ui') - - if windows: - store_artifact( - 'mediarepo-ui/src-tauri/target/release/mediarepo-ui.exe') - else: - store_artifact('mediarepo-ui/src-tauri/target/release/mediarepo-ui') - - store_artifact('mediarepo-ui/src-tauri/target/release/bundle/') - - -def check_daemon(): - '''Checks dependencies for daemon''' - check_exec('clang') - check_exec('cargo') - - -def check_ui(): - '''Checks dependencies for UI''' - - if not windows: - check_exec('wget') - check_exec('curl') - check_exec('file') - - check_exec('clang') - check_exec('cargo') - check_exec('node') - check_exec('npm') - check_yarn() - check_ng() - - -def check(): - '''Checks dependencies''' - check_daemon() - check_ui() - print('All checks passed') - - -def create_output_dir(): - '''Creates build output directory''' - if not os.path.exists(build_output): - os.mkdir(build_output) - - -def clean(): - '''Removes build output''' - if os.path.exists(build_output): - shut.rmtree(build_output) - print('Cleaned') - - -def build(daemon=True, ui=True): - '''Builds both daemon and UI''' - clean() - create_output_dir() - - if daemon: - check_daemon() - build_daemon() - - if ui: - check_ui() - build_ui() - - print('Build complete') - - -def parse_args(): - import argparse - parser = argparse.ArgumentParser(description='Build mediarepo') - subparsers = parser.add_subparsers(dest='command') - subparsers.required = True - - subparsers.add_parser('check') - - build_parser = subparsers.add_parser('build') - build_parser.add_argument( - '--daemon', action='store_true', help='Build daemon') - build_parser.add_argument('--ui', action='store_true', help='Build UI') - build_parser.add_argument( - '--verbose', action='store_true', help='Verbose build') - build_parser.add_argument( - '--output', action='store', help='Build output directory') - build_parser.add_argument( - '--ffmpeg', action='store_true', help='Build with ffmpeg') - - subparsers.add_parser('clean') - args = parser.parse_args() - return args - - -def main(): - opts = parse_args() - - if opts.command == 'build': - global build_output - build_output = opts.output if opts.output else build_output - - global verbose - verbose = opts.verbose - - global ffmpeg - ffmpeg = opts.ffmpeg - - if opts.daemon: - build(True, False) - elif opts.ui: - build(False, True) - else: - build() - elif opts.command == 'check': - check() - elif opts.command == 'clean': - clean() - - -if __name__ == '__main__': - main() diff --git a/mediarepo-daemon/Cargo.lock b/mediarepo-daemon/Cargo.lock index 09f9992..a9e5bc4 100644 --- a/mediarepo-daemon/Cargo.lock +++ b/mediarepo-daemon/Cargo.lock @@ -1398,7 +1398,7 @@ dependencies = [ [[package]] name = "mediarepo-daemon" -version = "1.0.0-rc.1" +version = "1.0.0-rc.3" dependencies = [ "console-subscriber", "glob", diff --git a/mediarepo-daemon/Cargo.toml b/mediarepo-daemon/Cargo.toml index 6cf4e00..5268dfc 100644 --- a/mediarepo-daemon/Cargo.toml +++ b/mediarepo-daemon/Cargo.toml @@ -4,7 +4,7 @@ default-members = ["mediarepo-core", "mediarepo-database", "mediarepo-logic", "m [package] name = "mediarepo-daemon" -version = "1.0.0-rc.2" +version = "1.0.0-rc.3" edition = "2018" license = "gpl-3" repository = "https://github.com/Trivernis/mediarepo-daemon" diff --git a/mediarepo-ui/package.json b/mediarepo-ui/package.json index 2097b81..4bec025 100644 --- a/mediarepo-ui/package.json +++ b/mediarepo-ui/package.json @@ -1,6 +1,6 @@ { "name": "mediarepo-ui", - "version": "1.0.0-rc.2", + "version": "1.0.0-rc.3", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index 40b6e09..939030c 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -40,7 +40,7 @@ checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "app" -version = "1.0.0-rc.1" +version = "1.0.0-rc.3" dependencies = [ "mediarepo-api", "serde", diff --git a/mediarepo-ui/src-tauri/Cargo.toml b/mediarepo-ui/src-tauri/Cargo.toml index a84e54c..950b214 100644 --- a/mediarepo-ui/src-tauri/Cargo.toml +++ b/mediarepo-ui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "1.0.0-rc.2" +version = "1.0.0-rc.3" description = "The UI for the mediarepo media management tool" authors = ["you"] license = "" diff --git a/mediarepo-ui/src/app/components/core/core.component.html b/mediarepo-ui/src/app/components/core/core.component.html index def7489..94f56f3 100644 --- a/mediarepo-ui/src/app/components/core/core.component.html +++ b/mediarepo-ui/src/app/components/core/core.component.html @@ -1,7 +1,7 @@
- + diff --git a/mediarepo-ui/src/app/components/core/core.module.ts b/mediarepo-ui/src/app/components/core/core.module.ts index a514aba..bec4607 100644 --- a/mediarepo-ui/src/app/components/core/core.module.ts +++ b/mediarepo-ui/src/app/components/core/core.module.ts @@ -39,7 +39,9 @@ import {MatToolbarModule} from "@angular/material/toolbar"; import { RepositoryDetailsViewComponent } from "./repositories-tab/repository-details-view/repository-details-view.component"; -import { EmptyTabComponent } from './empty-tab/empty-tab.component'; +import {EmptyTabComponent} from "./empty-tab/empty-tab.component"; +import {RepositoryOverviewComponent} from "./repositories-tab/repository-overview/repository-overview.component"; +import {AboutDialogComponent} from "./repositories-tab/repository-overview/about-dialog/about-dialog.component"; @NgModule({ @@ -54,6 +56,8 @@ import { EmptyTabComponent } from './empty-tab/empty-tab.component'; DownloadDaemonDialogComponent, RepositoryDetailsViewComponent, EmptyTabComponent, + RepositoryOverviewComponent, + AboutDialogComponent, ], exports: [ CoreComponent, diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html index 48622cd..4093b2a 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html @@ -1,18 +1,6 @@ -
-
- -
-
-
- -
- -

There are no repositories yet. You can create a repository or add an existing one.

- -
-
+
+
-
- +
+
diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss index 4e2ab92..2e3c1d6 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss @@ -1,41 +1,3 @@ -.repository-container { - margin: 1em; -} - -.repo-page-content { - margin: 0 10%; - height: calc(100% - 2em); -} - -.add-repo-tools { - height: 5em; - display: flex; - flex-direction: row-reverse; - - button { - margin: 1em; - } -} - -.repository-list { - display: flex; - flex-direction: column; - overflow-y: auto; - height: calc(100% - 5em); -} - -app-repository-card { - position: relative; -} - -app-repository-details-view, .repo-details { +app-repository-details-view { height: 100%; } - -.add-repository-prompt { - button { - font-size: 1.5em; - padding: 0.5em 1em; - border-radius: 0.5em; - } -} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts index 3c4f421..4b17e68 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts @@ -1,164 +1,18 @@ -import {AfterViewInit, Component, OnInit} from "@angular/core"; -import {Repository} from "../../../../api/models/Repository"; +import {Component} from "@angular/core"; import {RepositoryService} from "../../../services/repository/repository.service"; -import {MatDialog, MatDialogRef} from "@angular/material/dialog"; -import {DownloadDaemonDialogComponent} from "./download-daemon-dialog/download-daemon-dialog.component"; -import { - AddRepositoryDialogComponent -} from "../../shared/repository/repository/add-repository-dialog/add-repository-dialog.component"; -import {LoggingService} from "../../../services/logging/logging.service"; -import {BehaviorSubject} from "rxjs"; -import {BusyDialogComponent} from "../../shared/app-common/busy-dialog/busy-dialog.component"; -import {JobService} from "../../../services/job/job.service"; -import {StateService} from "../../../services/state/state.service"; +import {Repository} from "../../../../api/models/Repository"; -type BusyDialogContext = { message: BehaviorSubject, dialog: MatDialogRef }; @Component({ selector: "app-repositories-tab", templateUrl: "./repositories-tab.component.html", styleUrls: ["./repositories-tab.component.scss"] }) -export class RepositoriesTabComponent implements OnInit, AfterViewInit { - public repositories: Repository[] = []; - public selectedRepository?: Repository; - - constructor( - private logger: LoggingService, - private repoService: RepositoryService, - private jobService: JobService, - private stateService: StateService, - public dialog: MatDialog - ) { - } - - ngOnInit(): void { - this.repoService.repositories.subscribe({ - next: (repos) => { - this.repositories = repos; - } - }); - this.repoService.selectedRepository.subscribe( - repo => this.selectedRepository = repo); - } +export class RepositoriesTabComponent { - public async ngAfterViewInit() { - await this.checkAndPromptDaemonExecutable(); - } - - public async startDaemonAndSelectRepository(repository: Repository) { - try { - let dialogContext = this.openStartupDialog(repository); - let daemonRunning = await this.repoService.checkDaemonRunning( - repository.path!); - if (!daemonRunning) { - dialogContext.message.next("Starting repository daemon..."); - await this.repoService.startDaemon(repository.path!); - - await new Promise((res, _) => { - setTimeout(res, 2000); // wait for the daemon to start - }); - } - await this.selectRepository(repository, dialogContext); - } catch (err: any) { - this.logger.error(err); - } - } - - public async selectRepository(repository: Repository, dialogContext?: BusyDialogContext) { - dialogContext = dialogContext ?? this.openStartupDialog(repository); - try { - dialogContext.message.next("Opening repository..."); - await this.repoService.setRepository(repository); - await this.runRepositoryStartupTasks(dialogContext); - dialogContext.message.next("Restoring previous tabs..."); - await this.repoService.loadRepositories(); - dialogContext.dialog.close(true); - } catch (err: any) { - this.logger.error(err); - dialogContext.message.next( - "Failed to open repository: " + err.toString()); - await this.forceCloseRepository(); - setTimeout(() => dialogContext!.dialog.close(true), 1000); - } - } - - public openAddRepositoryDialog() { - this.dialog.open(AddRepositoryDialogComponent, { - disableClose: true, - minWidth: "30%", - minHeight: "30%", - }); - } - - public async onOpenRepository(repository: Repository) { - if (!repository.local) { - await this.selectRepository(repository); - } else { - await this.startDaemonAndSelectRepository(repository); - } - } - - private async forceCloseRepository() { - try { - await this.repoService.closeSelectedRepository(); - } catch { - } - try { - await this.repoService.disconnectSelectedRepository(); - } catch { - } - } - - private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise { - dialogContext.message.next("Checking integrity..."); - await this.jobService.runJob("CheckIntegrity"); - dialogContext.message.next("Running a vacuum on the database..."); - await this.jobService.runJob("Vacuum"); - dialogContext.message.next( - "Migrating content descriptors to new format..."); - await this.jobService.runJob("MigrateContentDescriptors"); - dialogContext.message.next("Calculating repository sizes..."); - await this.jobService.runJob("CalculateSizes", false); - dialogContext.message.next("Generating missing thumbnails..."); - await this.jobService.runJob("GenerateThumbnails"); - dialogContext.message.next("Finished repository startup"); - } - - private openStartupDialog(repository: Repository): BusyDialogContext { - const dialogMessage = new BehaviorSubject( - "Opening repository..."); - let dialog = this.dialog.open(BusyDialogComponent, { - data: { - title: `Opening repository '${repository.name}'`, - message: dialogMessage, - allowCancel: true, - }, disableClose: true, - minWidth: "30%", - minHeight: "30%", - }); - dialog.afterClosed().subscribe(async (result) => { - if (!result) { - await this.forceCloseRepository(); - } - }); - - return { message: dialogMessage, dialog }; - } + public selectedRepository?: Repository; - private async checkAndPromptDaemonExecutable() { - if (!await this.repoService.checkDameonConfigured()) { - const result = await this.dialog.open( - DownloadDaemonDialogComponent, - { - disableClose: true, - } - ).afterClosed().toPromise(); - if (result) { - // recursion avoidance - setTimeout( - async () => await this.checkAndPromptDaemonExecutable(), 0); - } - } + constructor(private repositoryService: RepositoryService) { + const sub = this.repositoryService.selectedRepository.subscribe(repo => this.selectedRepository = repo); } } diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html new file mode 100644 index 0000000..9b57198 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html @@ -0,0 +1,24 @@ +

+ About mediarepo +

+
+ + {{version | async}} + + + {{tauriVersion | async}} + +
+ +

Built with

+
+ + {{lib[0]}} + +
+
+
+ +
diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss new file mode 100644 index 0000000..607eefd --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss @@ -0,0 +1,20 @@ +.title { + text-align: center; +} + +.actions { + display: block; + + button { + float: right; + } +} + +.content { + text-align: center; +} + +.lib-list { + display: flex; + flex-direction: column; +} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts new file mode 100644 index 0000000..e95648e --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import {ComponentFixture, TestBed} from "@angular/core/testing"; + +import {AboutDialogComponent} from "./about-dialog.component"; + +describe("AboutDialogComponent", () => { + let component: AboutDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AboutDialogComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AboutDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts new file mode 100644 index 0000000..e4c49ed --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts @@ -0,0 +1,31 @@ +import {ChangeDetectionStrategy, Component, OnInit} from "@angular/core"; +import {MatDialogRef} from "@angular/material/dialog"; +import {app} from "@tauri-apps/api"; +import {Subject} from "rxjs"; + +@Component({ + selector: "app-about-dialog", + templateUrl: "./about-dialog.component.html", + styleUrls: ["./about-dialog.component.scss"], + changeDetection: ChangeDetectionStrategy.Default +}) +export class AboutDialogComponent implements OnInit { + + public version = new Subject(); + public tauriVersion = new Subject(); + public usedLibs = [ + ["Tauri", "https://tauri.studio"], + ["Angular", "https://angular.io/"], + ["SeaORM", "https://www.sea-ql.org"], + ["Tokio", "https://tokio.rs/"], + ["bromine", "https://github.com/Trivernis/bromine"] + ]; + + constructor(public dialogRef: MatDialogRef) { + } + + public async ngOnInit() { + this.version.next(await app.getVersion()); + this.tauriVersion.next(await app.getTauriVersion()); + } +} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html new file mode 100644 index 0000000..8220df4 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html @@ -0,0 +1,18 @@ + +

All Repositories

+ + +
+
+
+
+ +
+ +

There are no repositories yet. You can create a repository or add an existing one.

+ +
+
+
diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss new file mode 100644 index 0000000..e16282d --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss @@ -0,0 +1,63 @@ +.repository-container { + padding: 1em; + margin: auto; + display: block; + width: calc(600px - 2em); + float: left; + + app-repository-card { + display: block; + position: relative; + } +} + +.repo-page-content { + margin: 0 10%; + height: calc(100% - 2em); +} + +.add-repo-tools { + height: 5em; + display: flex; + flex-direction: row-reverse; + + button { + margin: 1em; + } +} + +.repository-list { + display: flex; + flex-wrap: wrap; + overflow-y: auto; + overflow-x: hidden; + height: calc(100% - 5em); +} + + +.add-repository-prompt { + button { + font-size: 1.5em; + padding: 0.5em 1em; + border-radius: 0.5em; + } +} + +mat-toolbar { + position: relative; + overflow: hidden; + + .add-repo-button { + margin: 0 auto 0 0; + } + + .about-button { + margin: 0 0 0 auto; + } + + .page-title { + position: absolute; + width: calc(100% - 32px); + text-align: center; + } +} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts new file mode 100644 index 0000000..48e0d55 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts @@ -0,0 +1,25 @@ +import {ComponentFixture, TestBed} from "@angular/core/testing"; + +import {RepositoryOverviewComponent} from "./repository-overview.component"; + +describe("RepositoryOverviewComponent", () => { + let component: RepositoryOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RepositoryOverviewComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RepositoryOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts new file mode 100644 index 0000000..966a479 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts @@ -0,0 +1,167 @@ +import {AfterViewInit, ChangeDetectionStrategy, Component, OnInit} from "@angular/core"; +import {Repository} from "../../../../../api/models/Repository"; +import {LoggingService} from "../../../../services/logging/logging.service"; +import {RepositoryService} from "../../../../services/repository/repository.service"; +import {JobService} from "../../../../services/job/job.service"; +import {StateService} from "../../../../services/state/state.service"; +import {MatDialog, MatDialogRef} from "@angular/material/dialog"; +import { + AddRepositoryDialogComponent +} from "../../../shared/repository/repository/add-repository-dialog/add-repository-dialog.component"; +import {BehaviorSubject} from "rxjs"; +import {BusyDialogComponent} from "../../../shared/app-common/busy-dialog/busy-dialog.component"; +import {DownloadDaemonDialogComponent} from "../download-daemon-dialog/download-daemon-dialog.component"; +import {AboutDialogComponent} from "./about-dialog/about-dialog.component"; + +type BusyDialogContext = { message: BehaviorSubject, dialog: MatDialogRef }; + +@Component({ + selector: "app-repository-overview", + templateUrl: "./repository-overview.component.html", + styleUrls: ["./repository-overview.component.scss"], + changeDetection: ChangeDetectionStrategy.Default +}) +export class RepositoryOverviewComponent implements OnInit, AfterViewInit { + + public repositories: Repository[] = []; + + constructor( + private logger: LoggingService, + private repoService: RepositoryService, + private jobService: JobService, + private stateService: StateService, + public dialog: MatDialog + ) { + } + + ngOnInit(): void { + this.repoService.repositories.subscribe(repos => this.repositories = repos); + } + + public async ngAfterViewInit() { + await this.checkAndPromptDaemonExecutable(); + } + + public async startDaemonAndSelectRepository(repository: Repository) { + try { + let dialogContext = this.openStartupDialog(repository); + let daemonRunning = await this.repoService.checkDaemonRunning( + repository.path!); + if (!daemonRunning) { + dialogContext.message.next("Starting repository daemon..."); + await this.repoService.startDaemon(repository.path!); + + await new Promise((res, _) => { + setTimeout(res, 2000); // wait for the daemon to start + }); + } + await this.selectRepository(repository, dialogContext); + } catch (err: any) { + this.logger.error(err); + } + } + + public async selectRepository(repository: Repository, dialogContext?: BusyDialogContext) { + dialogContext = dialogContext ?? this.openStartupDialog(repository); + try { + dialogContext.message.next("Opening repository..."); + await this.repoService.setRepository(repository); + await this.runRepositoryStartupTasks(dialogContext); + dialogContext.message.next("Restoring previous tabs..."); + await this.repoService.loadRepositories(); + dialogContext.dialog.close(true); + } catch (err: any) { + this.logger.error(err); + dialogContext.message.next( + "Failed to open repository: " + err.toString()); + await this.forceCloseRepository(); + setTimeout(() => dialogContext!.dialog.close(true), 1000); + } + } + + public openAddRepositoryDialog() { + this.dialog.open(AddRepositoryDialogComponent, { + disableClose: true, + minWidth: "30%", + minHeight: "30%", + }); + } + + public async onOpenRepository(repository: Repository) { + if (!repository.local) { + await this.selectRepository(repository); + } else { + await this.startDaemonAndSelectRepository(repository); + } + } + + public openAboutDialog(): void { + this.dialog.open(AboutDialogComponent, { + minWidth: "30%", + minHeight: "50%", + }); + } + + private async forceCloseRepository() { + try { + await this.repoService.closeSelectedRepository(); + } catch { + } + try { + await this.repoService.disconnectSelectedRepository(); + } catch { + } + } + + private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise { + dialogContext.message.next("Checking integrity..."); + await this.jobService.runJob("CheckIntegrity"); + dialogContext.message.next("Running a vacuum on the database..."); + await this.jobService.runJob("Vacuum"); + dialogContext.message.next( + "Migrating content descriptors to new format..."); + await this.jobService.runJob("MigrateContentDescriptors"); + dialogContext.message.next("Calculating repository sizes..."); + await this.jobService.runJob("CalculateSizes", false); + dialogContext.message.next("Generating missing thumbnails..."); + await this.jobService.runJob("GenerateThumbnails"); + dialogContext.message.next("Finished repository startup"); + } + + private openStartupDialog(repository: Repository): BusyDialogContext { + const dialogMessage = new BehaviorSubject( + "Opening repository..."); + let dialog = this.dialog.open(BusyDialogComponent, { + data: { + title: `Opening repository '${repository.name}'`, + message: dialogMessage, + allowCancel: true, + }, disableClose: true, + minWidth: "30%", + minHeight: "30%", + }); + dialog.afterClosed().subscribe(async (result) => { + if (!result) { + await this.forceCloseRepository(); + } + }); + + return { message: dialogMessage, dialog }; + } + + private async checkAndPromptDaemonExecutable() { + if (!await this.repoService.checkDameonConfigured()) { + const result = await this.dialog.open( + DownloadDaemonDialogComponent, + { + disableClose: true, + } + ).afterClosed().toPromise(); + if (result) { + // recursion avoidance + setTimeout( + async () => await this.checkAndPromptDaemonExecutable(), 0); + } + } + } +} diff --git a/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts b/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts index a556c14..0a25e48 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts @@ -25,6 +25,7 @@ import {MatRippleModule} from "@angular/material/core"; import {FlapButtonComponent} from "./flap-button/flap-button.component"; import {MiddleCenteredComponent} from "./middle-centered/middle-centered.component"; import {FormatBytesPipe} from "./pipes/format-bytes.pipe"; +import {ExternalUrlComponent} from "./external-url/external-url.component"; @NgModule({ @@ -44,6 +45,7 @@ import {FormatBytesPipe} from "./pipes/format-bytes.pipe"; FlapButtonComponent, MiddleCenteredComponent, FormatBytesPipe, + ExternalUrlComponent, ], exports: [ ConfirmDialogComponent, @@ -60,6 +62,7 @@ import {FormatBytesPipe} from "./pipes/format-bytes.pipe"; FlapButtonComponent, MiddleCenteredComponent, FormatBytesPipe, + ExternalUrlComponent, ], imports: [ CommonModule, diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html new file mode 100644 index 0000000..7148987 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html @@ -0,0 +1,3 @@ + + + diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss new file mode 100644 index 0000000..3fccf25 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss @@ -0,0 +1,15 @@ +@import "src/colors"; + +.highlight { + cursor: pointer; + color: $primary-lighter-20; + text-decoration: underline; + + &:hover { + color: $primary-lighter-30; + } + + &:active { + color: $accent; + } +} diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts new file mode 100644 index 0000000..77018c4 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts @@ -0,0 +1,25 @@ +import {ComponentFixture, TestBed} from "@angular/core/testing"; + +import {ExternalUrlComponent} from "./external-url.component"; + +describe("ExternalUrlComponent", () => { + let component: ExternalUrlComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExternalUrlComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalUrlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts new file mode 100644 index 0000000..7d3c10b --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts @@ -0,0 +1,31 @@ +import {ChangeDetectionStrategy, Component, Input} from "@angular/core"; +import {shell} from "@tauri-apps/api"; + +@Component({ + selector: "app-external-url", + templateUrl: "./external-url.component.html", + styleUrls: ["./external-url.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ExternalUrlComponent { + + @Input() href!: string; + private opening = false; + + constructor() { + } + + public async openUrl() { + if (this.opening) { + return; + } + this.opening = true; + try { + await shell.open(this.href); + } catch (err) { + console.error(err); + } finally { + this.opening = false; + } + } +} diff --git a/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html b/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html index 9edc4cb..22dfb2f 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html +++ b/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html @@ -1,4 +1,6 @@ -