diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index e19fcbb7..8cb4f7f3 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -19,6 +19,7 @@ pub enum Error { Disconnected, ExitStatus { message: String, + command: String, exit_code: i32, stderr: Vec, }, diff --git a/src-tauri/src/plugins/cmd.rs b/src-tauri/src/plugins/cmd.rs index aaa5d6dc..e1cdf329 100644 --- a/src-tauri/src/plugins/cmd.rs +++ b/src-tauri/src/plugins/cmd.rs @@ -39,6 +39,7 @@ async fn exec( if exit_code != 0 { return Err(Error::ExitStatus { message: format!(""), + command: command.clone(), exit_code, stderr, }); diff --git a/src-tauri/src/plugins/local_file.rs b/src-tauri/src/plugins/local_file.rs index 47d973ad..b00b146c 100644 --- a/src-tauri/src/plugins/local_file.rs +++ b/src-tauri/src/plugins/local_file.rs @@ -2,7 +2,7 @@ use std::env::temp_dir; use tauri::plugin::{Builder, TauriPlugin}; use tauri::Runtime; use tokio::fs::File; -use tokio::io::AsyncReadExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use uuid::Uuid; use crate::error::Error; @@ -18,6 +18,36 @@ async fn checksum(path: String, algorithm: String) -> Result { }; } +#[tauri::command] +async fn download(url: String, target: String) -> Result<(), Error> { + let mut response = reqwest::get(&url) + .await + .map_err(|e| Error::new(format!("Failed to request {}: {}", url, e)))?; + let mut file = File::create(&target) + .await + .map_err(|e| Error::new(format!("Failed to open {} for download: {}", target, e)))?; + while let Some(chunk) = response + .chunk() + .await + .map_err(|e| Error::new(format!("Failed to fetch {}: {}", url, e)))? + { + file.write(&chunk) + .await + .map_err(|e| Error::new(format!("Failed to save downloaded file to {}: {}", url, e)))?; + } + return Ok(()); +} + +#[tauri::command] +async fn remove(path: String, recursive: bool) -> Result<(), Error> { + if recursive { + tokio::fs::remove_dir_all(&path).await?; + } else { + tokio::fs::remove_file(&path).await?; + } + return Ok(()); +} + #[tauri::command] async fn temp_path(extension: String) -> Result { let temp_path = temp_dir().join(format!("webos-dev-tmp-{}{}", Uuid::new_v4(), extension)); @@ -29,6 +59,8 @@ async fn temp_path(extension: String) -> Result { pub fn plugin(name: &'static str) -> TauriPlugin { Builder::new(name) - .invoke_handler(tauri::generate_handler![checksum, temp_path]) + .invoke_handler(tauri::generate_handler![ + checksum, download, remove, temp_path + ]) .build() } diff --git a/src/app/apps/apps.component.html b/src/app/apps/apps.component.html index a6d9c09e..9f32dbe1 100644 --- a/src/app/apps/apps.component.html +++ b/src/app/apps/apps.component.html @@ -25,6 +25,6 @@
- +
diff --git a/src/app/apps/apps.component.ts b/src/app/apps/apps.component.ts index 1d882c1d..92d539d6 100644 --- a/src/app/apps/apps.component.ts +++ b/src/app/apps/apps.component.ts @@ -1,17 +1,17 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; +import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; import {noop, Observable, Subscription} from 'rxjs'; import {Device, PackageInfo, RawPackageInfo} from '../types'; import {AppManagerService, DeviceManagerService, RepositoryItem} from '../core/services'; import {MessageDialogComponent} from '../shared/components/message-dialog/message-dialog.component'; import {ProgressDialogComponent} from '../shared/components/progress-dialog/progress-dialog.component'; -import {has, keyBy} from 'lodash'; +import {keyBy} from 'lodash'; import {open as showOpenDialog} from '@tauri-apps/api/dialog'; import {basename, downloadDir} from "@tauri-apps/api/path"; import {APP_ID_HBCHANNEL} from "../shared/constants"; import {HbchannelRemoveComponent} from "./hbchannel-remove/hbchannel-remove.component"; import {BaseDirectory, writeBinaryFile} from "@tauri-apps/api/fs"; -import {LocalFileService} from "../core/services/local-file.service"; +import {StatStorageInfoComponent} from "../shared/components/stat-storage-info/stat-storage-info.component"; @Component({ selector: 'app-apps', @@ -25,6 +25,8 @@ export class AppsComponent implements OnInit, OnDestroy { device: Device | null = null; tabId: string = 'installed'; + @ViewChild('storageInfo') storageInfo?: StatStorageInfoComponent; + private deviceSubscription?: Subscription; private packagesSubscription?: Subscription; @@ -32,7 +34,6 @@ export class AppsComponent implements OnInit, OnDestroy { private modalService: NgbModal, private deviceManager: DeviceManagerService, private appManager: AppManagerService, - private localFiles: LocalFileService, ) { } @@ -107,7 +108,7 @@ export class AppsComponent implements OnInit, OnDestroy { const progress = ProgressDialogComponent.open(this.modalService); const component = progress.componentInstance as ProgressDialogComponent; try { - await this.appManager.installByPath(this.device, file.webkitRelativePath, this.hasHbChannel, (progress, statusText) => { + await this.appManager.installByPath(this.device, file.webkitRelativePath, (progress, statusText) => { component.progress = progress; component.message = statusText; }); @@ -130,7 +131,7 @@ export class AppsComponent implements OnInit, OnDestroy { const progress = ProgressDialogComponent.open(this.modalService); const component = progress.componentInstance as ProgressDialogComponent; try { - await this.appManager.installByPath(this.device, path, this.hasHbChannel, (progress, statusText) => { + await this.appManager.installByPath(this.device, path, (progress, statusText) => { component.progress = progress; component.message = statusText; }); @@ -171,6 +172,7 @@ export class AppsComponent implements OnInit, OnDestroy { const progress = ProgressDialogComponent.open(this.modalService); try { await this.appManager.remove(this.device, pkg.id); + this.storageInfo?.refresh(); } catch (e) { MessageDialogComponent.open(this.modalService, { message: `Failed to remove ${pkg.title}`, @@ -182,42 +184,32 @@ export class AppsComponent implements OnInit, OnDestroy { } } - async installPackage(item: RepositoryItem): Promise { + async installPackage(item: RepositoryItem, channel: 'stable' | 'beta' = 'stable'): Promise { if (!this.device) return; - const progress = ProgressDialogComponent.open(this.modalService); - const component = progress.componentInstance as ProgressDialogComponent; - try { - await this.appManager.installByManifest(this.device, item.manifest!, this.hasHbChannel, (progress, statusText) => { - component.progress = progress; - component.message = statusText; + const manifest = channel === 'stable' ? item.manifest : item.manifestBeta; + if (!manifest) { + MessageDialogComponent.open(this.modalService, { + title: `Failed to install ${item.title}`, + message: `No manifest found for ${item.title} in channel ${channel}`, + positive: 'Close', }); - } catch (e: any) { - this.handleInstallationError(item.title, e as Error); - } finally { - progress.close(true); + return; } - } - - async installBetaPackage(item: RepositoryItem): Promise { - if (!this.device) return; const progress = ProgressDialogComponent.open(this.modalService); const component = progress.componentInstance as ProgressDialogComponent; try { - await this.appManager.installByManifest(this.device, item.manifestBeta!, this.hasHbChannel, (progress, statusText) => { + await this.appManager.installByManifest(this.device, manifest, (progress, statusText) => { component.progress = progress; component.message = statusText; }); - } catch (e) { + this.storageInfo?.refresh(); + } catch (e: any) { this.handleInstallationError(item.title, e as Error); } finally { progress.close(true); } } - private get hasHbChannel(): boolean { - return has(this.instPackages, APP_ID_HBCHANNEL); - } - private handleInstallationError(name: string, e: Error) { MessageDialogComponent.open(this.modalService, { title: `Failed to install ${name}`, diff --git a/src/app/apps/channel/channel.component.html b/src/app/apps/channel/channel.component.html index b3acb3d9..23ad7ba2 100644 --- a/src/app/apps/channel/channel.component.html +++ b/src/app/apps/channel/channel.component.html @@ -32,7 +32,7 @@